/* lexer.c - The scripting lexer. */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2005,2006,2007,2008,2009,2010 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #include "grub_script.tab.h" #include "grub_script.yy.h" void grub_script_lexer_ref (struct grub_lexer_param *state) { state->refs++; } void grub_script_lexer_deref (struct grub_lexer_param *state) { state->refs--; } /* Start recording all characters passing through the lexer. */ void grub_script_lexer_record_start (struct grub_parser_param *parser) { struct grub_lexer_param *lexer = parser->lexerstate; lexer->record = 1; lexer->recordpos = 0; if (lexer->recording) /* reuse last record */ return; lexer->recordlen = GRUB_LEXER_INITIAL_RECORD_SIZE; lexer->recording = grub_malloc (lexer->recordlen); if (!lexer->recording) { grub_script_yyerror (parser, 0); lexer->record = 0; lexer->recordlen = 0; } } char * grub_script_lexer_record_stop (struct grub_parser_param *parser) { char *ptr; char *result; struct grub_lexer_param *lexer = parser->lexerstate; auto char *compact (char *start, char *end); char *compact (char *start, char *end) { /* Delete '{' and '}' characters and whitespaces. */ while (*start && grub_isspace (*start)) start++; if (*start == '{') start++; while (*start && grub_isspace (*start)) start++; while (*end && grub_isspace (*end)) end--; if (*end == '}') end--; while (*end && grub_isspace (*end)) end--; end[1] = '\0'; return start; } if (!lexer->record || !lexer->recording) return 0; /* XXX This is not necessary in BASH. */ ptr = compact (lexer->recording, lexer->recording + lexer->recordpos - 1); lexer->record = 0; lexer->recordpos = 0; /* This memory would be freed by, grub_script_free. */ result = grub_script_malloc (parser, grub_strlen (ptr) + 1); if (result) grub_strcpy (result, ptr); return result; } /* Record STR if input recording is enabled. */ void grub_script_lexer_record (struct grub_parser_param *parser, char *str) { int len; char *old; struct grub_lexer_param *lexer = parser->lexerstate; if (!lexer->record) return; len = grub_strlen (str); if (lexer->recordpos + len + 1 > lexer->recordlen) { old = lexer->recording; lexer->recordlen = grub_max (len, lexer->recordlen) * 2; lexer->recording = grub_realloc (lexer->recording, lexer->recordlen); if (!lexer->recording) { grub_free (old); lexer->record = 0; lexer->recordpos = 0; lexer->recordlen /= 2; grub_script_yyerror (parser, 0); return; } } grub_strcpy (lexer->recording + lexer->recordpos, str); lexer->recordpos += len; } /* Read next line of input if necessary, and set yyscanner buffers. */ int grub_script_lexer_yywrap (struct grub_parser_param *parserstate, const char *input) { int len; char *p = 0; char *line = 0; YY_BUFFER_STATE buffer; struct grub_lexer_param *lexerstate = parserstate->lexerstate; if (! lexerstate->refs && ! lexerstate->prefix && ! input) return 1; if (! lexerstate->getline && ! input) { grub_script_yyerror (parserstate, "unexpected end of file"); return 1; } line = 0; if (! input) lexerstate->getline (&line, 1); else line = grub_strdup (input); /* Ensure '\n' at the end. */ if (line && line[0] == '\0') { grub_free (line); line = grub_strdup ("\n"); } if (line && (len = grub_strlen(line)) && line[len - 1] != '\n') { p = grub_realloc (line, len + 2); if (p) { p[len++] = '\n'; p[len] = '\0'; } line = p; } if (! line) { grub_script_yyerror (parserstate, "out of memory"); return 1; } /* Prepend any left over unput-text. */ if (lexerstate->prefix) { int plen = grub_strlen (lexerstate->prefix); p = grub_malloc (len + plen + 1); if (! p) { grub_free (line); return 1; } grub_strcpy (p, lexerstate->prefix); lexerstate->prefix = 0; grub_strcpy (p + plen, line); grub_free (line); line = p; len = len + plen; } buffer = yy_scan_string (line, lexerstate->yyscanner); grub_free (line); if (! buffer) { grub_script_yyerror (parserstate, 0); return 1; } return 0; } struct grub_lexer_param * grub_script_lexer_init (struct grub_parser_param *parser, char *script, grub_reader_getline_t getline) { int len; char *script2; YY_BUFFER_STATE buffer; struct grub_lexer_param *lexerstate; lexerstate = grub_zalloc (sizeof (*lexerstate)); if (!lexerstate) return 0; lexerstate->size = GRUB_LEXER_INITIAL_TEXT_SIZE; lexerstate->text = grub_malloc (lexerstate->size); if (!lexerstate->text) { grub_free (lexerstate); return 0; } lexerstate->getline = getline; /* rest are all zeros already */ if (yylex_init (&lexerstate->yyscanner)) { grub_free (lexerstate->text); grub_free (lexerstate); return 0; } yyset_extra (parser, lexerstate->yyscanner); parser->lexerstate = lexerstate; if (grub_script_lexer_yywrap (parser, script ?: "\n")) { parser->lexerstate = 0; yylex_destroy (lexerstate->yyscanner); grub_free (lexerstate->yyscanner); grub_free (lexerstate->text); grub_free (lexerstate); return 0; } return lexerstate; } void grub_script_lexer_fini (struct grub_lexer_param *lexerstate) { if (!lexerstate) return; yylex_destroy (lexerstate->yyscanner); grub_free (lexerstate->recording); grub_free (lexerstate->text); grub_free (lexerstate); } int grub_script_yylex (union YYSTYPE *value, struct grub_parser_param *parserstate) { char *str; int token; grub_script_arg_type_t type; struct grub_lexer_param *lexerstate = parserstate->lexerstate; value->arg = 0; if (parserstate->err) return GRUB_PARSER_TOKEN_BAD; if (lexerstate->eof) return GRUB_PARSER_TOKEN_EOF; /* * Words with environment variables, like foo${bar}baz needs * multiple tokens to be merged into a single grub_script_arg. We * use two variables to achieve this: lexerstate->merge_start and * lexerstate->merge_end */ lexerstate->merge_start = 0; lexerstate->merge_end = 0; do { /* Empty lexerstate->text. */ lexerstate->used = 1; lexerstate->text[0] = '\0'; token = yylex (value, lexerstate->yyscanner); if (token == GRUB_PARSER_TOKEN_BAD) break; /* Merging feature uses lexerstate->text instead of yytext. */ if (lexerstate->merge_start) { str = lexerstate->text; type = lexerstate->type; } else { str = yyget_text (lexerstate->yyscanner); type = GRUB_SCRIPT_ARG_TYPE_TEXT; } grub_dprintf("lexer", "token %u text [%s]\n", token, str); value->arg = grub_script_arg_add (parserstate, value->arg, type, str); } while (lexerstate->merge_start && !lexerstate->merge_end); if (!value->arg || parserstate->err) return GRUB_PARSER_TOKEN_BAD; return token; } void grub_script_yyerror (struct grub_parser_param *state, char const *err) { if (err) grub_error (GRUB_ERR_INVALID_COMMAND, err); grub_print_error (); state->err++; }