From 041d1ea37802bf7178a31a53f96c26efa6b8fb7b Mon Sep 17 00:00:00 2001 From: James Date: Fri, 16 Nov 2012 10:41:01 +0000 Subject: fish --- grub-core/script/argv.c | 153 +++++++++ grub-core/script/execute.c | 783 ++++++++++++++++++++++++++++++++++++++++++++ grub-core/script/function.c | 105 ++++++ grub-core/script/lexer.c | 327 ++++++++++++++++++ grub-core/script/main.c | 88 +++++ grub-core/script/parser.y | 353 ++++++++++++++++++++ grub-core/script/script.c | 394 ++++++++++++++++++++++ grub-core/script/yylex.l | 368 +++++++++++++++++++++ 8 files changed, 2571 insertions(+) create mode 100644 grub-core/script/argv.c create mode 100644 grub-core/script/execute.c create mode 100644 grub-core/script/function.c create mode 100644 grub-core/script/lexer.c create mode 100644 grub-core/script/main.c create mode 100644 grub-core/script/parser.y create mode 100644 grub-core/script/script.c create mode 100644 grub-core/script/yylex.l (limited to 'grub-core/script') diff --git a/grub-core/script/argv.c b/grub-core/script/argv.c new file mode 100644 index 0000000..856828d --- /dev/null +++ b/grub-core/script/argv.c @@ -0,0 +1,153 @@ +/* argv.c - methods for constructing argument vector */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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 + +/* Return nearest power of two that is >= v. */ +static unsigned +round_up_exp (unsigned v) +{ + COMPILE_TIME_ASSERT (sizeof (v) == 4); + + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + + v++; + v += (v == 0); + + return v; +} + +void +grub_script_argv_free (struct grub_script_argv *argv) +{ + unsigned i; + + if (argv->args) + { + for (i = 0; i < argv->argc; i++) + grub_free (argv->args[i]); + + grub_free (argv->args); + } + + argv->argc = 0; + argv->args = 0; + argv->script = 0; +} + +/* Make argv from argc, args pair. */ +int +grub_script_argv_make (struct grub_script_argv *argv, int argc, char **args) +{ + int i; + struct grub_script_argv r = { 0, 0, 0 }; + + for (i = 0; i < argc; i++) + if (grub_script_argv_next (&r) || grub_script_argv_append (&r, args[i])) + { + grub_script_argv_free (&r); + return 1; + } + *argv = r; + return 0; +} + +/* Prepare for next argc. */ +int +grub_script_argv_next (struct grub_script_argv *argv) +{ + char **p = argv->args; + + if (argv->args && argv->argc && argv->args[argv->argc - 1] == 0) + return 0; + + p = grub_realloc (p, round_up_exp ((argv->argc + 2) * sizeof (char *))); + if (! p) + return 1; + + argv->argc++; + argv->args = p; + + if (argv->argc == 1) + argv->args[0] = 0; + argv->args[argv->argc] = 0; + return 0; +} + +/* Append `s' to the last argument. */ +int +grub_script_argv_append (struct grub_script_argv *argv, const char *s) +{ + int a; + int b; + char *p = argv->args[argv->argc - 1]; + + if (! s) + return 0; + + a = p ? grub_strlen (p) : 0; + b = grub_strlen (s); + + p = grub_realloc (p, round_up_exp ((a + b + 1) * sizeof (char))); + if (! p) + return 1; + + grub_strcpy (p + a, s); + argv->args[argv->argc - 1] = p; + + return 0; +} + +/* Split `s' and append words as multiple arguments. */ +int +grub_script_argv_split_append (struct grub_script_argv *argv, char *s) +{ + char ch; + char *p; + int errors = 0; + + if (! s) + return 0; + + while (! errors && *s) + { + p = s; + while (*s && ! grub_isspace (*s)) + s++; + + ch = *s; + *s = '\0'; + errors += grub_script_argv_append (argv, p); + *s = ch; + + while (*s && grub_isspace (*s)) + s++; + + if (*s) + errors += grub_script_argv_next (argv); + } + return errors; +} diff --git a/grub-core/script/execute.c b/grub-core/script/execute.c new file mode 100644 index 0000000..72d1997 --- /dev/null +++ b/grub-core/script/execute.c @@ -0,0 +1,783 @@ +/* execute.c -- Execute a GRUB script. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2005,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 +#include +#include +#include +#include + +/* Max digits for a char is 3 (0xFF is 255), similarly for an int it + is sizeof (int) * 3, and one extra for a possible -ve sign. */ +#define ERRNO_DIGITS_MAX (sizeof (int) * 3 + 1) + +static unsigned long is_continue; +static unsigned long active_loops; +static unsigned long active_breaks; +static unsigned long function_return; + +#define GRUB_SCRIPT_SCOPE_MALLOCED 1 +#define GRUB_SCRIPT_SCOPE_ARGS_MALLOCED 2 + +/* Scope for grub script functions. */ +struct grub_script_scope +{ + unsigned flags; + unsigned shifts; + struct grub_script_argv argv; +}; +static struct grub_script_scope *scope = 0; + +/* Wildcard translator for GRUB script. */ +struct grub_script_wildcard_translator *grub_wildcard_translator; + +static void +replace_scope (struct grub_script_scope *new_scope) +{ + if (scope) + { + scope->argv.argc += scope->shifts; + scope->argv.args -= scope->shifts; + + if (scope->flags & GRUB_SCRIPT_SCOPE_ARGS_MALLOCED) + grub_script_argv_free (&scope->argv); + + if (scope->flags & GRUB_SCRIPT_SCOPE_MALLOCED) + grub_free (scope); + } + scope = new_scope; +} + +grub_err_t +grub_script_break (grub_command_t cmd, int argc, char *argv[]) +{ + char *p = 0; + unsigned long count; + + if (argc == 0) + count = 1; + + else if ((argc > 1) || (count = grub_strtoul (argv[0], &p, 10)) == 0 || + (*p != '\0')) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad break"); + + is_continue = grub_strcmp (cmd->name, "break") ? 1 : 0; + active_breaks = grub_min (active_loops, count); + return GRUB_ERR_NONE; +} + +grub_err_t +grub_script_shift (grub_command_t cmd __attribute__((unused)), + int argc, char *argv[]) +{ + char *p = 0; + unsigned long n = 0; + + if (! scope) + return GRUB_ERR_NONE; + + if (argc == 0) + n = 1; + + else if (argc > 1) + return GRUB_ERR_BAD_ARGUMENT; + + else + { + n = grub_strtoul (argv[0], &p, 10); + if (*p != '\0') + return GRUB_ERR_BAD_ARGUMENT; + } + + if (n > scope->argv.argc) + return GRUB_ERR_BAD_ARGUMENT; + + scope->shifts += n; + scope->argv.argc -= n; + scope->argv.args += n; + return GRUB_ERR_NONE; +} + +grub_err_t +grub_script_setparams (grub_command_t cmd __attribute__((unused)), + int argc, char **args) +{ + struct grub_script_scope *new_scope; + struct grub_script_argv argv = { 0, 0, 0 }; + + if (! scope) + return GRUB_ERR_INVALID_COMMAND; + + new_scope = grub_malloc (sizeof (*new_scope)); + if (! new_scope) + return grub_errno; + + if (grub_script_argv_make (&argv, argc, args)) + { + grub_free (new_scope); + return grub_errno; + } + + new_scope->shifts = 0; + new_scope->argv = argv; + new_scope->flags = GRUB_SCRIPT_SCOPE_MALLOCED | + GRUB_SCRIPT_SCOPE_ARGS_MALLOCED; + + replace_scope (new_scope); + return GRUB_ERR_NONE; +} + +grub_err_t +grub_script_return (grub_command_t cmd __attribute__((unused)), + int argc, char *argv[]) +{ + char *p; + unsigned long n; + + if (! scope || argc > 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "not in function scope"); + + if (argc == 0) + { + function_return = 1; + return grub_strtoul (grub_env_get ("?"), NULL, 10); + } + + n = grub_strtoul (argv[0], &p, 10); + if (*p != '\0') + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad argument"); + + function_return = 1; + return n ? grub_error (GRUB_ERR_TEST_FAILURE, "false") : GRUB_ERR_NONE; +} + +static int +grub_env_special (const char *name) +{ + if (grub_isdigit (name[0]) || + grub_strcmp (name, "#") == 0 || + grub_strcmp (name, "*") == 0 || + grub_strcmp (name, "@") == 0) + return 1; + return 0; +} + +static char ** +grub_script_env_get (const char *name, grub_script_arg_type_t type) +{ + unsigned i; + struct grub_script_argv result = { 0, 0, 0 }; + + if (grub_script_argv_next (&result)) + goto fail; + + if (! grub_env_special (name)) + { + char *v = grub_env_get (name); + if (v && v[0]) + { + if (type == GRUB_SCRIPT_ARG_TYPE_VAR) + { + if (grub_script_argv_split_append (&result, v)) + goto fail; + } + else + if (grub_script_argv_append (&result, v)) + goto fail; + } + } + else if (! scope) + { + if (grub_script_argv_append (&result, 0)) + goto fail; + } + else if (grub_strcmp (name, "#") == 0) + { + char buffer[ERRNO_DIGITS_MAX + 1]; + grub_snprintf (buffer, sizeof (buffer), "%u", scope->argv.argc); + if (grub_script_argv_append (&result, buffer)) + goto fail; + } + else if (grub_strcmp (name, "*") == 0) + { + for (i = 0; i < scope->argv.argc; i++) + if (type == GRUB_SCRIPT_ARG_TYPE_VAR) + { + if (i != 0 && grub_script_argv_next (&result)) + goto fail; + + if (grub_script_argv_split_append (&result, scope->argv.args[i])) + goto fail; + } + else + { + if (i != 0 && grub_script_argv_append (&result, " ")) + goto fail; + + if (grub_script_argv_append (&result, scope->argv.args[i])) + goto fail; + } + } + else if (grub_strcmp (name, "@") == 0) + { + for (i = 0; i < scope->argv.argc; i++) + { + if (i != 0 && grub_script_argv_next (&result)) + goto fail; + + if (type == GRUB_SCRIPT_ARG_TYPE_VAR) + { + if (grub_script_argv_split_append (&result, scope->argv.args[i])) + goto fail; + } + else + if (grub_script_argv_append (&result, scope->argv.args[i])) + goto fail; + } + } + else + { + unsigned long num = grub_strtoul (name, 0, 10); + if (num == 0) + ; /* XXX no file name, for now. */ + + else if (num <= scope->argv.argc) + { + if (type == GRUB_SCRIPT_ARG_TYPE_VAR) + { + if (grub_script_argv_split_append (&result, + scope->argv.args[num - 1])) + goto fail; + } + else + if (grub_script_argv_append (&result, scope->argv.args[num - 1])) + goto fail; + } + } + + return result.args; + + fail: + + grub_script_argv_free (&result); + return 0; +} + +static grub_err_t +grub_script_env_set (const char *name, const char *val) +{ + if (grub_env_special (name)) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bad variable name"); + + return grub_env_set (name, val); +} + +/* Convert arguments in ARGLIST into ARGV form. */ +static int +grub_script_arglist_to_argv (struct grub_script_arglist *arglist, + struct grub_script_argv *argv) +{ + int i; + char **values = 0; + struct grub_script_arg *arg = 0; + struct grub_script_argv result = { 0, 0, 0 }; + + auto int append (char *s, int escape_type); + int append (char *s, int escape_type) + { + int r; + char *p = 0; + + if (! grub_wildcard_translator || escape_type == 0) + return grub_script_argv_append (&result, s); + + if (escape_type > 0) + p = grub_wildcard_translator->escape (s); + else if (escape_type < 0) + p = grub_wildcard_translator->unescape (s); + + if (! p) + return 1; + + r = grub_script_argv_append (&result, p); + grub_free (p); + return r; + } + + for (; arglist && arglist->arg; arglist = arglist->next) + { + if (grub_script_argv_next (&result)) + goto fail; + + arg = arglist->arg; + while (arg) + { + switch (arg->type) + { + case GRUB_SCRIPT_ARG_TYPE_VAR: + case GRUB_SCRIPT_ARG_TYPE_DQVAR: + values = grub_script_env_get (arg->str, arg->type); + for (i = 0; values && values[i]; i++) + { + if (i != 0 && grub_script_argv_next (&result)) + goto fail; + + if (arg->type == GRUB_SCRIPT_ARG_TYPE_VAR) + { + if (grub_script_argv_append (&result, values[i])) + goto fail; + } + else + { + if (append (values[i], 1)) + goto fail; + } + + grub_free (values[i]); + } + grub_free (values); + break; + + case GRUB_SCRIPT_ARG_TYPE_BLOCK: + if (grub_script_argv_append (&result, "{") || + grub_script_argv_append (&result, arg->str) || + grub_script_argv_append (&result, "}")) + goto fail; + result.script = arg->script; + break; + + case GRUB_SCRIPT_ARG_TYPE_TEXT: + if (grub_strlen (arg->str) && + grub_script_argv_append (&result, arg->str)) + goto fail; + break; + + case GRUB_SCRIPT_ARG_TYPE_DQSTR: + case GRUB_SCRIPT_ARG_TYPE_SQSTR: + if (append (arg->str, 1)) + goto fail; + break; + } + arg = arg->next; + } + } + + if (! result.args[result.argc - 1]) + result.argc--; + + /* Perform wildcard expansion. */ + + if (grub_wildcard_translator) + { + int j; + int failed = 0; + char **expansions = 0; + struct grub_script_argv unexpanded = result; + + result.argc = 0; + result.args = 0; + for (i = 0; unexpanded.args[i]; i++) + { + if (grub_wildcard_translator->expand (unexpanded.args[i], + &expansions)) + { + grub_script_argv_free (&unexpanded); + goto fail; + } + + if (! expansions) + { + grub_script_argv_next (&result); + append (unexpanded.args[i], -1); + } + else + { + for (j = 0; expansions[j]; j++) + { + failed = (failed || grub_script_argv_next (&result) || + append (expansions[j], -1)); + grub_free (expansions[j]); + } + grub_free (expansions); + + if (failed) + { + grub_script_argv_free (&unexpanded); + goto fail; + } + } + } + grub_script_argv_free (&unexpanded); + } + + *argv = result; + return 0; + + fail: + + grub_script_argv_free (&result); + return 1; +} + +static grub_err_t +grub_script_execute_cmd (struct grub_script_cmd *cmd) +{ + int ret; + char errnobuf[ERRNO_DIGITS_MAX + 1]; + + if (cmd == 0) + return 0; + + ret = cmd->exec (cmd); + + grub_snprintf (errnobuf, sizeof (errnobuf), "%d", ret); + grub_env_set ("?", errnobuf); + return ret; +} + +/* Execute a function call. */ +grub_err_t +grub_script_function_call (grub_script_function_t func, int argc, char **args) +{ + grub_err_t ret = 0; + unsigned long loops = active_loops; + struct grub_script_scope *old_scope; + struct grub_script_scope new_scope; + + active_loops = 0; + new_scope.flags = 0; + new_scope.shifts = 0; + new_scope.argv.argc = argc; + new_scope.argv.args = args; + + old_scope = scope; + scope = &new_scope; + + ret = grub_script_execute (func->func); + + function_return = 0; + active_loops = loops; + replace_scope (old_scope); /* free any scopes by setparams */ + return ret; +} + +/* Execute a source script. */ +grub_err_t +grub_script_execute_sourcecode (const char *source, int argc, char **args) +{ + grub_err_t ret = 0; + struct grub_script *parsed_script; + struct grub_script_scope new_scope; + struct grub_script_scope *old_scope; + + auto grub_err_t getline (char **line, int cont); + grub_err_t getline (char **line, int cont __attribute__ ((unused))) + { + const char *p; + + if (! source) + { + *line = 0; + return 0; + } + + p = grub_strchr (source, '\n'); + + if (p) + *line = grub_strndup (source, p - source); + else + *line = grub_strdup (source); + source = p ? p + 1 : 0; + return 0; + } + + new_scope.argv.argc = argc; + new_scope.argv.args = args; + new_scope.flags = 0; + + old_scope = scope; + scope = &new_scope; + + while (source) + { + char *line; + + getline (&line, 0); + parsed_script = grub_script_parse (line, getline); + if (! parsed_script) + { + ret = grub_errno; + break; + } + + ret = grub_script_execute (parsed_script); + grub_free (line); + } + + scope = old_scope; + return ret; +} + +/* Execute a single command line. */ +grub_err_t +grub_script_execute_cmdline (struct grub_script_cmd *cmd) +{ + struct grub_script_cmdline *cmdline = (struct grub_script_cmdline *) cmd; + grub_command_t grubcmd; + grub_err_t ret = 0; + grub_script_function_t func = 0; + char errnobuf[18]; + char *cmdname; + int argc; + char **args; + int invert; + struct grub_script_argv argv = { 0, 0, 0 }; + + /* Lookup the command. */ + if (grub_script_arglist_to_argv (cmdline->arglist, &argv) || ! argv.args[0]) + return grub_errno; + + invert = 0; + argc = argv.argc - 1; + args = argv.args + 1; + cmdname = argv.args[0]; + if (grub_strcmp (cmdname, "!") == 0) + { + if (argv.argc < 2 || ! argv.args[1]) + { + grub_script_argv_free (&argv); + return grub_error (GRUB_ERR_BAD_ARGUMENT, "missing arguments"); + } + + invert = 1; + argc = argv.argc - 2; + args = argv.args + 2; + cmdname = argv.args[1]; + } + grubcmd = grub_command_find (cmdname); + if (! grubcmd) + { + grub_errno = GRUB_ERR_NONE; + + /* It's not a GRUB command, try all functions. */ + func = grub_script_function_find (cmdname); + if (! func) + { + /* As a last resort, try if it is an assignment. */ + char *assign = grub_strdup (cmdname); + char *eq = grub_strchr (assign, '='); + + if (eq) + { + /* This was set because the command was not found. */ + grub_errno = GRUB_ERR_NONE; + + /* Create two strings and set the variable. */ + *eq = '\0'; + eq++; + grub_script_env_set (assign, eq); + } + grub_free (assign); + + grub_snprintf (errnobuf, sizeof (errnobuf), "%d", grub_errno); + grub_script_env_set ("?", errnobuf); + + grub_script_argv_free (&argv); + grub_print_error (); + + return 0; + } + } + + /* Execute the GRUB command or function. */ + if (grubcmd) + { + if (grub_extractor_level && !(grubcmd->flags + & GRUB_COMMAND_FLAG_EXTRACTOR)) + ret = grub_error (GRUB_ERR_EXTRACTOR, + "%s isn't allowed to execute in an extractor", + cmdname); + else if ((grubcmd->flags & GRUB_COMMAND_FLAG_BLOCKS) && + (grubcmd->flags & GRUB_COMMAND_FLAG_EXTCMD)) + ret = grub_extcmd_dispatcher (grubcmd, argc, args, argv.script); + else + ret = (grubcmd->func) (grubcmd, argc, args); + } + else + ret = grub_script_function_call (func, argc, args); + + if (invert) + { + if (ret == GRUB_ERR_TEST_FAILURE) + grub_errno = ret = GRUB_ERR_NONE; + else if (ret == GRUB_ERR_NONE) + ret = grub_error (GRUB_ERR_TEST_FAILURE, "false"); + else + { + grub_print_error (); + ret = GRUB_ERR_NONE; + } + } + + /* Free arguments. */ + grub_script_argv_free (&argv); + + if (grub_errno == GRUB_ERR_TEST_FAILURE) + grub_errno = GRUB_ERR_NONE; + + grub_print_error (); + + grub_snprintf (errnobuf, sizeof (errnobuf), "%d", ret); + grub_env_set ("?", errnobuf); + + return ret; +} + +/* Execute a block of one or more commands. */ +grub_err_t +grub_script_execute_cmdlist (struct grub_script_cmd *list) +{ + int ret = 0; + struct grub_script_cmd *cmd; + + /* Loop over every command and execute it. */ + for (cmd = list->next; cmd; cmd = cmd->next) + { + if (active_breaks) + break; + + ret = grub_script_execute_cmd (cmd); + + if (function_return) + break; + } + + return ret; +} + +/* Execute an if statement. */ +grub_err_t +grub_script_execute_cmdif (struct grub_script_cmd *cmd) +{ + int ret; + char *result; + struct grub_script_cmdif *cmdif = (struct grub_script_cmdif *) cmd; + + /* Check if the commands results in a true or a false. The value is + read from the env variable `?'. */ + ret = grub_script_execute_cmd (cmdif->exec_to_evaluate); + if (function_return) + return ret; + + result = grub_env_get ("?"); + grub_errno = GRUB_ERR_NONE; + + /* Execute the `if' or the `else' part depending on the value of + `?'. */ + if (result && ! grub_strcmp (result, "0")) + return grub_script_execute_cmd (cmdif->exec_on_true); + else + return grub_script_execute_cmd (cmdif->exec_on_false); +} + +/* Execute a for statement. */ +grub_err_t +grub_script_execute_cmdfor (struct grub_script_cmd *cmd) +{ + unsigned i; + grub_err_t result; + struct grub_script_argv argv = { 0, 0, 0 }; + struct grub_script_cmdfor *cmdfor = (struct grub_script_cmdfor *) cmd; + + if (grub_script_arglist_to_argv (cmdfor->words, &argv)) + return grub_errno; + + active_loops++; + result = 0; + for (i = 0; i < argv.argc; i++) + { + if (is_continue && active_breaks == 1) + active_breaks = 0; + + if (! active_breaks) + { + grub_script_env_set (cmdfor->name->str, argv.args[i]); + result = grub_script_execute_cmd (cmdfor->list); + if (function_return) + break; + } + } + + if (active_breaks) + active_breaks--; + + active_loops--; + grub_script_argv_free (&argv); + return result; +} + +/* Execute a "while" or "until" command. */ +grub_err_t +grub_script_execute_cmdwhile (struct grub_script_cmd *cmd) +{ + int result; + struct grub_script_cmdwhile *cmdwhile = (struct grub_script_cmdwhile *) cmd; + + active_loops++; + do { + result = grub_script_execute_cmd (cmdwhile->cond); + if (function_return) + break; + + if (cmdwhile->until ? !result : result) + break; + + result = grub_script_execute_cmd (cmdwhile->list); + if (function_return) + break; + + if (active_breaks == 1 && is_continue) + active_breaks = 0; + + if (active_breaks) + break; + + } while (1); /* XXX Put a check for ^C here */ + + if (active_breaks) + active_breaks--; + + active_loops--; + return result; +} + +/* Execute any GRUB pre-parsed command or script. */ +grub_err_t +grub_script_execute (struct grub_script *script) +{ + if (script == 0) + return 0; + + return grub_script_execute_cmd (script->cmd); +} + diff --git a/grub-core/script/function.c b/grub-core/script/function.c new file mode 100644 index 0000000..47b2366 --- /dev/null +++ b/grub-core/script/function.c @@ -0,0 +1,105 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2005,2007,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 + +grub_script_function_t grub_script_function_list; + +grub_script_function_t +grub_script_function_create (struct grub_script_arg *functionname_arg, + struct grub_script *cmd) +{ + grub_script_function_t func; + grub_script_function_t *p; + + func = (grub_script_function_t) grub_malloc (sizeof (*func)); + if (! func) + return 0; + + func->name = grub_strdup (functionname_arg->str); + if (! func->name) + { + grub_free (func); + return 0; + } + + func->func = cmd; + + /* Keep the list sorted for simplicity. */ + p = &grub_script_function_list; + while (*p) + { + if (grub_strcmp ((*p)->name, func->name) >= 0) + break; + + p = &((*p)->next); + } + + /* If the function already exists, overwrite the old function. */ + if (*p && grub_strcmp ((*p)->name, func->name) == 0) + { + grub_script_function_t q; + + q = *p; + grub_script_free (q->func); + q->func = cmd; + grub_free (func); + func = q; + } + else + { + func->next = *p; + *p = func; + } + + return func; +} + +void +grub_script_function_remove (const char *name) +{ + grub_script_function_t *p, q; + + for (p = &grub_script_function_list, q = *p; q; p = &(q->next), q = q->next) + if (grub_strcmp (name, q->name) == 0) + { + *p = q->next; + grub_free (q->name); + grub_script_free (q->func); + grub_free (q); + break; + } +} + +grub_script_function_t +grub_script_function_find (char *functionname) +{ + grub_script_function_t func; + + for (func = grub_script_function_list; func; func = func->next) + if (grub_strcmp (functionname, func->name) == 0) + break; + + if (! func) + grub_error (GRUB_ERR_UNKNOWN_COMMAND, "unknown command `%.20s'", functionname); + + return func; +} diff --git a/grub-core/script/lexer.c b/grub-core/script/lexer.c new file mode 100644 index 0000000..9827907 --- /dev/null +++ b/grub-core/script/lexer.c @@ -0,0 +1,327 @@ +/* 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 + +#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. */ +unsigned +grub_script_lexer_record_start (struct grub_parser_param *parser) +{ + struct grub_lexer_param *lexer = parser->lexerstate; + + lexer->record++; + if (lexer->recording) + return lexer->recordpos; + + lexer->recordpos = 0; + lexer->recordlen = GRUB_LEXER_INITIAL_RECORD_SIZE; + lexer->recording = grub_malloc (lexer->recordlen); + if (!lexer->recording) + { + grub_script_yyerror (parser, 0); + lexer->recordlen = 0; + } + return lexer->recordpos; +} + +char * +grub_script_lexer_record_stop (struct grub_parser_param *parser, unsigned offset) +{ + int count; + char *result; + struct grub_lexer_param *lexer = parser->lexerstate; + + if (!lexer->record) + return 0; + + lexer->record--; + if (!lexer->recording) + return 0; + + count = lexer->recordpos - offset; + result = grub_script_malloc (parser, count + 1); + if (result) { + grub_strncpy (result, lexer->recording + offset, count); + result[count] = '\0'; + } + + if (lexer->record == 0) + { + grub_free (lexer->recording); + lexer->recording = 0; + lexer->recordlen = 0; + lexer->recordpos = 0; + } + 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 || !lexer->recording) + 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->recordpos = 0; + lexer->recordlen = 0; + 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 = 0; + 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) +{ + 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++; +} diff --git a/grub-core/script/main.c b/grub-core/script/main.c new file mode 100644 index 0000000..482bb3f --- /dev/null +++ b/grub-core/script/main.c @@ -0,0 +1,88 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 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 + +grub_err_t +grub_normal_parse_line (char *line, grub_reader_getline_t getline) +{ + struct grub_script *parsed_script; + + /* Parse the script. */ + parsed_script = grub_script_parse (line, getline); + + if (parsed_script) + { + /* Execute the command(s). */ + grub_script_execute (parsed_script); + + /* The parsed script was executed, throw it away. */ + grub_script_unref (parsed_script); + } + + return grub_errno; +} + +static grub_command_t cmd_break; +static grub_command_t cmd_continue; +static grub_command_t cmd_shift; +static grub_command_t cmd_setparams; +static grub_command_t cmd_return; + +void +grub_script_init (void) +{ + cmd_break = grub_register_command ("break", grub_script_break, + N_("[n]"), N_("Exit from loops")); + cmd_continue = grub_register_command ("continue", grub_script_break, + N_("[n]"), N_("Continue loops")); + cmd_shift = grub_register_command ("shift", grub_script_shift, + N_("[n]"), N_("Shift positional parameters.")); + cmd_setparams = grub_register_command ("setparams", grub_script_setparams, + N_("[VALUE]..."), + N_("Set positional parameters.")); + cmd_return = grub_register_command ("return", grub_script_return, + N_("[n]"), N_("Return from a function.")); +} + +void +grub_script_fini (void) +{ + if (cmd_break) + grub_unregister_command (cmd_break); + cmd_break = 0; + + if (cmd_continue) + grub_unregister_command (cmd_continue); + cmd_continue = 0; + + if (cmd_shift) + grub_unregister_command (cmd_shift); + cmd_shift = 0; + + if (cmd_setparams) + grub_unregister_command (cmd_setparams); + cmd_setparams = 0; + + if (cmd_return) + grub_unregister_command (cmd_return); + cmd_return = 0; +} diff --git a/grub-core/script/parser.y b/grub-core/script/parser.y new file mode 100644 index 0000000..683b3ac --- /dev/null +++ b/grub-core/script/parser.y @@ -0,0 +1,353 @@ +/* parser.y - The scripting parser. */ +/* + * 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 + +#define YYFREE grub_free +#define YYMALLOC grub_malloc +#define YYLTYPE_IS_TRIVIAL 0 +#define YYENABLE_NLS 0 + +#include "grub_script.tab.h" +%} + +%union { + struct grub_script_cmd *cmd; + struct grub_script_arglist *arglist; + struct grub_script_arg *arg; + char *string; + struct { + unsigned offset; + struct grub_script_mem *memory; + struct grub_script *scripts; + }; +} + +%token GRUB_PARSER_TOKEN_BAD +%token GRUB_PARSER_TOKEN_EOF 0 "end-of-input" + +%token GRUB_PARSER_TOKEN_NEWLINE "\n" +%token GRUB_PARSER_TOKEN_AND "&&" +%token GRUB_PARSER_TOKEN_OR "||" +%token GRUB_PARSER_TOKEN_SEMI2 ";;" +%token GRUB_PARSER_TOKEN_PIPE "|" +%token GRUB_PARSER_TOKEN_AMP "&" +%token GRUB_PARSER_TOKEN_SEMI ";" +%token GRUB_PARSER_TOKEN_LBR "{" +%token GRUB_PARSER_TOKEN_RBR "}" +%token GRUB_PARSER_TOKEN_NOT "!" +%token GRUB_PARSER_TOKEN_LSQBR2 "[" +%token GRUB_PARSER_TOKEN_RSQBR2 "]" +%token GRUB_PARSER_TOKEN_LT "<" +%token GRUB_PARSER_TOKEN_GT ">" + +%token GRUB_PARSER_TOKEN_CASE "case" +%token GRUB_PARSER_TOKEN_DO "do" +%token GRUB_PARSER_TOKEN_DONE "done" +%token GRUB_PARSER_TOKEN_ELIF "elif" +%token GRUB_PARSER_TOKEN_ELSE "else" +%token GRUB_PARSER_TOKEN_ESAC "esac" +%token GRUB_PARSER_TOKEN_FI "fi" +%token GRUB_PARSER_TOKEN_FOR "for" +%token GRUB_PARSER_TOKEN_IF "if" +%token GRUB_PARSER_TOKEN_IN "in" +%token GRUB_PARSER_TOKEN_SELECT "select" +%token GRUB_PARSER_TOKEN_THEN "then" +%token GRUB_PARSER_TOKEN_UNTIL "until" +%token GRUB_PARSER_TOKEN_WHILE "while" +%token GRUB_PARSER_TOKEN_TIME "time" +%token GRUB_PARSER_TOKEN_FUNCTION "function" +%token GRUB_PARSER_TOKEN_NAME "name" +%token GRUB_PARSER_TOKEN_WORD "word" + +%type block block0 +%type word argument arguments0 arguments1 + +%type script_init script +%type grubcmd ifclause ifcmd forcmd whilecmd untilcmd +%type command commands1 statement + +%pure-parser +%lex-param { struct grub_parser_param *state }; +%parse-param { struct grub_parser_param *state }; + +%start script_init + +%% +/* It should be possible to do this in a clean way... */ +script_init: { state->err = 0; } script { state->parsed = $2; state->err = 0; } +; + +script: newlines0 + { + $$ = 0; + } + | script statement delimiter newlines0 + { + $$ = grub_script_append_cmd (state, $1, $2); + } + | error + { + $$ = 0; + yyerror (state, "Incorrect command"); + yyerrok; + } +; + +newlines0: /* Empty */ | newlines1 ; +newlines1: newlines0 "\n" ; + +delimiter: ";" + | "\n" +; +delimiters0: /* Empty */ | delimiters1 ; +delimiters1: delimiter + | delimiters1 "\n" +; + +word: GRUB_PARSER_TOKEN_NAME { $$ = grub_script_add_arglist (state, 0, $1); } + | GRUB_PARSER_TOKEN_WORD { $$ = grub_script_add_arglist (state, 0, $1); } +; + +statement: command { $$ = $1; } + | function { $$ = 0; } +; + +argument : "case" { $$ = grub_script_add_arglist (state, 0, $1); } + | "do" { $$ = grub_script_add_arglist (state, 0, $1); } + | "done" { $$ = grub_script_add_arglist (state, 0, $1); } + | "elif" { $$ = grub_script_add_arglist (state, 0, $1); } + | "else" { $$ = grub_script_add_arglist (state, 0, $1); } + | "esac" { $$ = grub_script_add_arglist (state, 0, $1); } + | "fi" { $$ = grub_script_add_arglist (state, 0, $1); } + | "for" { $$ = grub_script_add_arglist (state, 0, $1); } + | "if" { $$ = grub_script_add_arglist (state, 0, $1); } + | "in" { $$ = grub_script_add_arglist (state, 0, $1); } + | "select" { $$ = grub_script_add_arglist (state, 0, $1); } + | "then" { $$ = grub_script_add_arglist (state, 0, $1); } + | "until" { $$ = grub_script_add_arglist (state, 0, $1); } + | "while" { $$ = grub_script_add_arglist (state, 0, $1); } + | "function" { $$ = grub_script_add_arglist (state, 0, $1); } + | "time" { $$ = grub_script_add_arglist (state, 0, $1); } + | word { $$ = $1; } +; + +/* + Block parameter is passed to commands in two forms: as unparsed + string and as pre-parsed grub_script object. Passing as grub_script + object makes memory management difficult, because: + + (1) Command may want to keep a reference to grub_script objects for + later use, so script framework may not free the grub_script + object after command completes. + + (2) Command may get called multiple times with same grub_script + object under loops, so we should not let command implementation + to free the grub_script object. + + To solve above problems, we rely on reference counting for + grub_script objects. Commands that want to keep the grub_script + object must take a reference to it. + + Other complexity comes with arbitrary nesting of grub_script + objects: a grub_script object may have commands with several block + parameters, and each block parameter may further contain multiple + block parameters nested. We use temporary variable, state->scripts + to collect nested child scripts (that are linked by siblings and + children members), and will build grub_scripts tree from bottom. + */ +block: "{" + { + grub_script_lexer_ref (state->lexerstate); + $$ = grub_script_lexer_record_start (state); + $$ = grub_script_mem_record (state); + + /* save currently known scripts. */ + $$ = state->scripts; + state->scripts = 0; + } + commands1 delimiters0 "}" + { + char *p; + struct grub_script_mem *memory; + struct grub_script *s = $2; + + memory = grub_script_mem_record_stop (state, $2); + if ((p = grub_script_lexer_record_stop (state, $2))) + *grub_strrchr (p, '}') = '\0'; + + $$ = grub_script_arg_add (state, 0, GRUB_SCRIPT_ARG_TYPE_BLOCK, p); + if (! $$ || ! ($$->script = grub_script_create ($3, memory))) + grub_script_mem_free (memory); + + else { + /* attach nested scripts to $$->script as children */ + $$->script->children = state->scripts; + + /* restore old scripts; append $$->script to siblings. */ + state->scripts = $2 ?: $$->script; + if (s) { + while (s->next_siblings) + s = s->next_siblings; + s->next_siblings = $$->script; + } + } + + grub_script_lexer_deref (state->lexerstate); + } +; +block0: /* Empty */ { $$ = 0; } + | block { $$ = $1; } +; + +arguments0: /* Empty */ { $$ = 0; } + | arguments1 { $$ = $1; } +; +arguments1: argument arguments0 + { + if ($1 && $2) + { + $1->next = $2; + $1->argcount += $2->argcount; + $2->argcount = 0; + } + $$ = $1; + } +; + +grubcmd: word arguments0 block0 + { + struct grub_script_arglist *x = $2; + + if ($3) + x = grub_script_add_arglist (state, $2, $3); + + if ($1 && x) { + $1->next = x; + $1->argcount += x->argcount; + x->argcount = 0; + } + $$ = grub_script_create_cmdline (state, $1); + } +; + +/* A single command. */ +command: grubcmd { $$ = $1; } + | ifcmd { $$ = $1; } + | forcmd { $$ = $1; } + | whilecmd { $$ = $1; } + | untilcmd { $$ = $1; } +; + +/* A list of commands. */ +commands1: newlines0 command + { + $$ = grub_script_append_cmd (state, 0, $2); + } + | commands1 delimiters1 command + { + $$ = grub_script_append_cmd (state, $1, $3); + } +; + +function: "function" "name" + { + grub_script_lexer_ref (state->lexerstate); + state->func_mem = grub_script_mem_record (state); + + $$ = state->scripts; + state->scripts = 0; + } + delimiters0 "{" commands1 delimiters1 "}" + { + struct grub_script *script; + state->func_mem = grub_script_mem_record_stop (state, + state->func_mem); + script = grub_script_create ($6, state->func_mem); + if (! script) + grub_script_mem_free (state->func_mem); + else { + script->children = state->scripts; + grub_script_function_create ($2, script); + } + + state->scripts = $3; + grub_script_lexer_deref (state->lexerstate); + } +; + +ifcmd: "if" + { + grub_script_lexer_ref (state->lexerstate); + } + ifclause "fi" + { + $$ = $3; + grub_script_lexer_deref (state->lexerstate); + } +; +ifclause: commands1 delimiters1 "then" commands1 delimiters1 + { + $$ = grub_script_create_cmdif (state, $1, $4, 0); + } + | commands1 delimiters1 "then" commands1 delimiters1 "else" commands1 delimiters1 + { + $$ = grub_script_create_cmdif (state, $1, $4, $7); + } + | commands1 delimiters1 "then" commands1 delimiters1 "elif" ifclause + { + $$ = grub_script_create_cmdif (state, $1, $4, $7); + } +; + +forcmd: "for" "name" + { + grub_script_lexer_ref (state->lexerstate); + } + "in" arguments0 delimiters1 "do" commands1 delimiters1 "done" + { + $$ = grub_script_create_cmdfor (state, $2, $5, $8); + grub_script_lexer_deref (state->lexerstate); + } +; + +whilecmd: "while" + { + grub_script_lexer_ref (state->lexerstate); + } + commands1 delimiters1 "do" commands1 delimiters1 "done" + { + $$ = grub_script_create_cmdwhile (state, $3, $6, 0); + grub_script_lexer_deref (state->lexerstate); + } +; + +untilcmd: "until" + { + grub_script_lexer_ref (state->lexerstate); + } + commands1 delimiters1 "do" commands1 delimiters1 "done" + { + $$ = grub_script_create_cmdwhile (state, $3, $6, 1); + grub_script_lexer_deref (state->lexerstate); + } +; diff --git a/grub-core/script/script.c b/grub-core/script/script.c new file mode 100644 index 0000000..ad30183 --- /dev/null +++ b/grub-core/script/script.c @@ -0,0 +1,394 @@ +/* script.c -- Functions to create an in memory description of the script. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2005,2006,2007,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 + +/* It is not possible to deallocate the memory when a syntax error was + found. Because of that it is required to keep track of all memory + allocations. The memory is freed in case of an error, or assigned + to the parsed script when parsing was successful. + + In case of the normal malloc, some additional bytes are allocated + for this datastructure. All reserved memory is stored in a linked + list so it can be easily freed. The original memory can be found + from &mem. */ +struct grub_script_mem +{ + struct grub_script_mem *next; + char mem; +}; + +/* Return malloc'ed memory and keep track of the allocation. */ +void * +grub_script_malloc (struct grub_parser_param *state, grub_size_t size) +{ + struct grub_script_mem *mem; + mem = (struct grub_script_mem *) grub_malloc (size + sizeof (*mem) + - sizeof (char)); + if (!mem) + return 0; + + grub_dprintf ("scripting", "malloc %p\n", mem); + mem->next = state->memused; + state->memused = mem; + return (void *) &mem->mem; +} + +/* Free all memory described by MEM. */ +void +grub_script_mem_free (struct grub_script_mem *mem) +{ + struct grub_script_mem *memfree; + + while (mem) + { + memfree = mem->next; + grub_dprintf ("scripting", "free %p\n", mem); + grub_free (mem); + mem = memfree; + } +} + +/* Start recording memory usage. Returns the memory that should be + restored when calling stop. */ +struct grub_script_mem * +grub_script_mem_record (struct grub_parser_param *state) +{ + struct grub_script_mem *mem = state->memused; + state->memused = 0; + + return mem; +} + +/* Stop recording memory usage. Restore previous recordings using + RESTORE. Return the recorded memory. */ +struct grub_script_mem * +grub_script_mem_record_stop (struct grub_parser_param *state, + struct grub_script_mem *restore) +{ + struct grub_script_mem *mem = state->memused; + state->memused = restore; + return mem; +} + +/* Free the memory reserved for CMD and all of it's children. */ +void +grub_script_free (struct grub_script *script) +{ + struct grub_script *s; + struct grub_script *t; + + if (! script) + return; + + if (script->mem) + grub_script_mem_free (script->mem); + + s = script->children; + while (s) { + t = s->next_siblings; + grub_script_unref (s); + s = t; + } + grub_free (script); +} + + + +/* Extend the argument arg with a variable or string of text. If ARG + is zero a new list is created. */ +struct grub_script_arg * +grub_script_arg_add (struct grub_parser_param *state, + struct grub_script_arg *arg, grub_script_arg_type_t type, + char *str) +{ + struct grub_script_arg *argpart; + struct grub_script_arg *ll; + int len; + + argpart = + (struct grub_script_arg *) grub_script_malloc (state, sizeof (*arg)); + if (!argpart) + return arg; + + argpart->type = type; + argpart->script = 0; + + len = grub_strlen (str) + 1; + argpart->str = grub_script_malloc (state, len); + if (!argpart->str) + return arg; /* argpart is freed later, during grub_script_free. */ + + grub_memcpy (argpart->str, str, len); + argpart->next = 0; + + if (!arg) + return argpart; + + for (ll = arg; ll->next; ll = ll->next); + ll->next = argpart; + + return arg; +} + +/* Add the argument ARG to the end of the argument list LIST. If LIST + is zero, a new list will be created. */ +struct grub_script_arglist * +grub_script_add_arglist (struct grub_parser_param *state, + struct grub_script_arglist *list, + struct grub_script_arg *arg) +{ + struct grub_script_arglist *link; + struct grub_script_arglist *ll; + + grub_dprintf ("scripting", "arglist\n"); + + link = + (struct grub_script_arglist *) grub_script_malloc (state, sizeof (*link)); + if (!link) + return list; + + link->next = 0; + link->arg = arg; + link->argcount = 0; + + if (!list) + { + link->argcount++; + return link; + } + + list->argcount++; + + /* Look up the last link in the chain. */ + for (ll = list; ll->next; ll = ll->next); + ll->next = link; + + return list; +} + +/* Create a command that describes a single command line. CMDLINE + contains the name of the command that should be executed. ARGLIST + holds all arguments for this command. */ +struct grub_script_cmd * +grub_script_create_cmdline (struct grub_parser_param *state, + struct grub_script_arglist *arglist) +{ + struct grub_script_cmdline *cmd; + + grub_dprintf ("scripting", "cmdline\n"); + + cmd = grub_script_malloc (state, sizeof (*cmd)); + if (!cmd) + return 0; + + cmd->cmd.exec = grub_script_execute_cmdline; + cmd->cmd.next = 0; + cmd->arglist = arglist; + + return (struct grub_script_cmd *) cmd; +} + +/* Create a command that functions as an if statement. If BOOL is + evaluated to true (the value is returned in envvar '?'), the + interpreter will run the command TRUE, otherwise the interpreter + runs the command FALSE. */ +struct grub_script_cmd * +grub_script_create_cmdif (struct grub_parser_param *state, + struct grub_script_cmd *exec_to_evaluate, + struct grub_script_cmd *exec_on_true, + struct grub_script_cmd *exec_on_false) +{ + struct grub_script_cmdif *cmd; + + grub_dprintf ("scripting", "cmdif\n"); + + cmd = grub_script_malloc (state, sizeof (*cmd)); + if (!cmd) + return 0; + + cmd->cmd.exec = grub_script_execute_cmdif; + cmd->cmd.next = 0; + cmd->exec_to_evaluate = exec_to_evaluate; + cmd->exec_on_true = exec_on_true; + cmd->exec_on_false = exec_on_false; + + return (struct grub_script_cmd *) cmd; +} + +/* Create a command that functions as a for statement. */ +struct grub_script_cmd * +grub_script_create_cmdfor (struct grub_parser_param *state, + struct grub_script_arg *name, + struct grub_script_arglist *words, + struct grub_script_cmd *list) +{ + struct grub_script_cmdfor *cmd; + + grub_dprintf ("scripting", "cmdfor\n"); + + cmd = grub_script_malloc (state, sizeof (*cmd)); + if (! cmd) + return 0; + + cmd->cmd.exec = grub_script_execute_cmdfor; + cmd->cmd.next = 0; + cmd->name = name; + cmd->words = words; + cmd->list = list; + + return (struct grub_script_cmd *) cmd; +} + +/* Create a "while" or "until" command. */ +struct grub_script_cmd * +grub_script_create_cmdwhile (struct grub_parser_param *state, + struct grub_script_cmd *cond, + struct grub_script_cmd *list, + int is_an_until_loop) +{ + struct grub_script_cmdwhile *cmd; + + cmd = grub_script_malloc (state, sizeof (*cmd)); + if (! cmd) + return 0; + + cmd->cmd.exec = grub_script_execute_cmdwhile; + cmd->cmd.next = 0; + cmd->cond = cond; + cmd->list = list; + cmd->until = is_an_until_loop; + + return (struct grub_script_cmd *) cmd; +} + +/* Create a chain of commands. LAST contains the command that should + be added at the end of LIST's list. If LIST is zero, a new list + will be created. */ +struct grub_script_cmd * +grub_script_append_cmd (struct grub_parser_param *state, + struct grub_script_cmd *list, + struct grub_script_cmd *last) +{ + struct grub_script_cmd *ptr; + + grub_dprintf ("scripting", "append command\n"); + + if (! last) + return list; + + if (! list) + { + list = grub_script_malloc (state, sizeof (*list)); + if (! list) + return 0; + + list->exec = grub_script_execute_cmdlist; + list->next = last; + } + else + { + ptr = list; + while (ptr->next) + ptr = ptr->next; + + ptr->next = last; + } + + return list; +} + + + +struct grub_script * +grub_script_create (struct grub_script_cmd *cmd, struct grub_script_mem *mem) +{ + struct grub_script *parsed; + + parsed = grub_malloc (sizeof (*parsed)); + if (! parsed) + return 0; + + parsed->mem = mem; + parsed->cmd = cmd; + parsed->refcnt = 0; + parsed->children = 0; + parsed->next_siblings = 0; + + return parsed; +} + +/* Parse the script passed in SCRIPT and return the parsed + datastructure that is ready to be interpreted. */ +struct grub_script * +grub_script_parse (char *script, grub_reader_getline_t getline) +{ + struct grub_script *parsed; + struct grub_script_mem *membackup; + struct grub_lexer_param *lexstate; + struct grub_parser_param *parsestate; + + parsed = grub_script_create (0, 0); + if (!parsed) + return 0; + + parsestate = grub_zalloc (sizeof (*parsestate)); + if (!parsestate) + { + grub_free (parsed); + return 0; + } + + /* Initialize the lexer. */ + lexstate = grub_script_lexer_init (parsestate, script, getline); + if (!lexstate) + { + grub_free (parsed); + grub_free (parsestate); + return 0; + } + + parsestate->lexerstate = lexstate; + + membackup = grub_script_mem_record (parsestate); + + /* Parse the script. */ + if (grub_script_yyparse (parsestate) || parsestate->err) + { + struct grub_script_mem *memfree; + memfree = grub_script_mem_record_stop (parsestate, membackup); + grub_script_mem_free (memfree); + grub_script_lexer_fini (lexstate); + grub_free (parsestate); + grub_free (parsed); + return 0; + } + + parsed->mem = grub_script_mem_record_stop (parsestate, membackup); + parsed->cmd = parsestate->parsed; + parsed->children = parsestate->scripts; + + grub_script_lexer_fini (lexstate); + grub_free (parsestate); + + return parsed; +} diff --git a/grub-core/script/yylex.l b/grub-core/script/yylex.l new file mode 100644 index 0000000..53ae4c5 --- /dev/null +++ b/grub-core/script/yylex.l @@ -0,0 +1,368 @@ +%{ +/* yylex.l The scripting lexer. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 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" + +#define yyfree grub_lexer_yyfree +#define yyalloc grub_lexer_yyalloc +#define yyrealloc grub_lexer_yyrealloc + +/* + * As we don't have access to yyscanner, we cannot do much except to + * print the fatal error. + */ +#define YY_FATAL_ERROR(msg) \ + do { \ + grub_printf ("fatal error: %s\n", msg); \ + } while (0) + +#define COPY(str, hint) \ + do { \ + copy_string (yyextra, str, hint); \ + } while (0) + + +#define RECORD \ + do { \ + grub_script_lexer_record (yyextra, yytext); \ + } while (0) + +#define ARG(t) \ + do { \ + yyextra->lexerstate->type = t; \ + return GRUB_PARSER_TOKEN_WORD; \ + } while (0) + +/* We don't need YY_INPUT, as we rely on yy_scan_strings */ +#define YY_INPUT(buf,res,max) do { res = 0; } while (0) + +/* forward declarations */ +static int grub_lexer_unput (const char *input, yyscan_t yyscanner); +static int grub_lexer_resplit (const char *input, yyscan_t yyscanner); + +static void grub_lexer_yyfree (void *, yyscan_t yyscanner); +static void* grub_lexer_yyalloc (yy_size_t, yyscan_t yyscanner); +static void* grub_lexer_yyrealloc (void*, yy_size_t, yyscan_t yyscanner); +static void copy_string (struct grub_parser_param *, const char *, + unsigned hint); + +%} + +%top{ + +#include + +#include + +typedef size_t yy_size_t; +#define YY_TYPEDEF_YY_SIZE_T 1 + +/* + * Some flex hacks for -nostdinc; XXX We need to fix these when libc + * support becomes availble in GRUB. + */ + +#ifndef GRUB_UTIL +#define stdin 0 +#define stdout 0 + +#define fprintf(...) 0 +#define exit(...) +#endif + +} + +%option ecs +%option meta-ecs + +%option warn +%option array +%option stack +%option reentrant +%option bison-bridge +%option never-interactive + +%option noyyfree noyyalloc noyyrealloc +%option nounistd nostdinit nodefault noyylineno + +/* Reduce lexer size, by not defining these. */ +%option noyy_top_state +%option noinput nounput +%option noyyget_in noyyset_in +%option noyyget_out noyyset_out +%option noyyget_debug noyyset_debug +%option noyyget_lineno noyyset_lineno + +%option extra-type="struct grub_parser_param*" + +BLANK [ \t] +COMMENT #.*$ + +CHAR [^{}|&$;<> \t\n\'\"\\] +DIGITS [[:digit:]]+ +NAME [[:alpha:]_][[:alnum:]_]* + +ESC \\(.|\n) +SQCHR [^\'] +DQCHR {ESC}|[^\\\"] +DQSTR \"{DQCHR}*\" +SQSTR \'{SQCHR}*\' +SPECIAL \?|\#|\*|\@ +VARIABLE ${NAME}|$\{{NAME}\}|${DIGITS}|$\{{DIGITS}\}|${SPECIAL}|$\{{SPECIAL}\} +WORD ({CHAR}|{DQSTR}|{SQSTR}|{ESC}|{VARIABLE})+ + +MULTILINE {WORD}?((\"{DQCHR}*)|(\'{SQCHR}*)|(\\\n)) + +%x SPLIT +%x DQUOTE +%x SQUOTE +%x VAR + +%% + + /* White spaces */ +{BLANK}+ { RECORD; } +{COMMENT} { RECORD; } + + /* Special symbols */ +"\n" { RECORD; return GRUB_PARSER_TOKEN_NEWLINE; } +"||" { RECORD; return GRUB_PARSER_TOKEN_OR; } +"&&" { RECORD; return GRUB_PARSER_TOKEN_AND; } +";;" { RECORD; return GRUB_PARSER_TOKEN_SEMI2; } +"|" { RECORD; return GRUB_PARSER_TOKEN_PIPE; } +"&" { RECORD; return GRUB_PARSER_TOKEN_AMP; } +";" { RECORD; return GRUB_PARSER_TOKEN_SEMI; } +"<" { RECORD; return GRUB_PARSER_TOKEN_LT; } +">" { RECORD; return GRUB_PARSER_TOKEN_GT; } + + /* Reserved words */ +"{" { RECORD; return GRUB_PARSER_TOKEN_LBR; } +"}" { RECORD; return GRUB_PARSER_TOKEN_RBR; } +"[[" { RECORD; return GRUB_PARSER_TOKEN_RSQBR2; } +"]]" { RECORD; return GRUB_PARSER_TOKEN_LSQBR2; } +"time" { RECORD; return GRUB_PARSER_TOKEN_TIME; } +"case" { RECORD; return GRUB_PARSER_TOKEN_CASE; } +"do" { RECORD; return GRUB_PARSER_TOKEN_DO; } +"done" { RECORD; return GRUB_PARSER_TOKEN_DONE; } +"elif" { RECORD; return GRUB_PARSER_TOKEN_ELIF; } +"else" { RECORD; return GRUB_PARSER_TOKEN_ELSE; } +"esac" { RECORD; return GRUB_PARSER_TOKEN_ESAC; } +"fi" { RECORD; return GRUB_PARSER_TOKEN_FI; } +"for" { RECORD; return GRUB_PARSER_TOKEN_FOR; } +"if" { RECORD; return GRUB_PARSER_TOKEN_IF; } +"in" { RECORD; return GRUB_PARSER_TOKEN_IN; } +"select" { RECORD; return GRUB_PARSER_TOKEN_SELECT; } +"then" { RECORD; return GRUB_PARSER_TOKEN_THEN; } +"until" { RECORD; return GRUB_PARSER_TOKEN_UNTIL; } +"while" { RECORD; return GRUB_PARSER_TOKEN_WHILE; } +"function" { RECORD; return GRUB_PARSER_TOKEN_FUNCTION; } + +{MULTILINE} { + if (grub_lexer_unput (yytext, yyscanner)) + return GRUB_PARSER_TOKEN_BAD; + } + +{NAME} { RECORD; return GRUB_PARSER_TOKEN_NAME; } +{WORD} { + RECORD; + yypush_buffer_state (YY_CURRENT_BUFFER, yyscanner); + if (grub_lexer_resplit (yytext, yyscanner)) + { + yypop_buffer_state (yyscanner); + return GRUB_PARSER_TOKEN_WORD; + } + yyextra->lexerstate->resplit = 1; + } +. { + grub_script_yyerror (yyextra, yytext); + return GRUB_PARSER_TOKEN_BAD; + } + + /* Split word into multiple args */ + +{ + \\. { COPY (yytext + 1, yyleng - 1); } + \\\n { /* ignore */ } + \" { + yy_push_state (DQUOTE, yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_TEXT); + } + \' { + yy_push_state (SQUOTE, yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_TEXT); + } + \$ { + yy_push_state (VAR, yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_TEXT); + } + \\ | + [^\"\'\$\\]+ { COPY (yytext, yyleng); } + <> { + yy_pop_state (yyscanner); + yypop_buffer_state (yyscanner); + yyextra->lexerstate->resplit = 0; + yyextra->lexerstate->merge_end = 1; + ARG (GRUB_SCRIPT_ARG_TYPE_TEXT); + } +} + +{ + {SPECIAL} | + {DIGITS} | + {NAME} { + COPY (yytext, yyleng); + yy_pop_state (yyscanner); + if (YY_START == SPLIT) + ARG (GRUB_SCRIPT_ARG_TYPE_VAR); + else + ARG (GRUB_SCRIPT_ARG_TYPE_DQVAR); + } + \{{SPECIAL}\} | + \{{DIGITS}\} | + \{{NAME}\} { + yytext[yyleng - 1] = '\0'; + COPY (yytext + 1, yyleng - 2); + yy_pop_state (yyscanner); + if (YY_START == SPLIT) + ARG (GRUB_SCRIPT_ARG_TYPE_VAR); + else + ARG (GRUB_SCRIPT_ARG_TYPE_DQVAR); + } + .|\n { return GRUB_PARSER_TOKEN_BAD; } +} + +{ + \' { + yy_pop_state (yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_SQSTR); + } + [^\']+ { COPY (yytext, yyleng); } +} + +{ + \\\$ { COPY ("$", 1); } + \\\\ { COPY ("\\", 1); } + \\\" { COPY ("\"", 1); } + \\\n { /* ignore */ } + [^\"\$\\\n]+ { COPY (yytext, yyleng); } + \" { + yy_pop_state (yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_DQSTR); + } + \$ { + yy_push_state (VAR, yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_DQSTR); + } + (.|\n) { COPY (yytext, yyleng); } +} + +<> { + yypop_buffer_state (yyscanner); + yyextra->lexerstate->eof = 1; + return GRUB_PARSER_TOKEN_EOF; + } +%% + +int +yywrap (yyscan_t yyscanner) +{ + if (yyget_extra (yyscanner)->lexerstate->resplit) + return 1; + + return grub_script_lexer_yywrap (yyget_extra (yyscanner), 0); +} + +static void +grub_lexer_yyfree (void *ptr, yyscan_t yyscanner __attribute__ ((unused))) +{ + grub_free(ptr); +} + +static void* +grub_lexer_yyalloc (yy_size_t size, yyscan_t yyscanner __attribute__ ((unused))) +{ + return grub_malloc (size); +} + +static void* +grub_lexer_yyrealloc (void *ptr, yy_size_t size, + yyscan_t yyscanner __attribute__ ((unused))) +{ + return grub_realloc (ptr, size); +} + +static void copy_string (struct grub_parser_param *parser, const char *str, unsigned hint) +{ + int size; + char *ptr; + unsigned len; + + len = hint ? hint : grub_strlen (str); + if (parser->lexerstate->used + len >= parser->lexerstate->size) + { + size = grub_max (len, parser->lexerstate->size) * 2; + ptr = grub_realloc (parser->lexerstate->text, size); + if (!ptr) + { + grub_script_yyerror (parser, 0); + return; + } + + parser->lexerstate->text = ptr; + parser->lexerstate->size = size; + } + grub_strcpy (parser->lexerstate->text + parser->lexerstate->used - 1, str); + parser->lexerstate->used += len; +} + +static int +grub_lexer_resplit (const char *text, yyscan_t yyscanner) +{ + /* resplit text */ + if (yy_scan_string (text, yyscanner)) + { + yyget_extra (yyscanner)->lexerstate->merge_start = 1; + yy_push_state (SPLIT, yyscanner); + return 0; + } + grub_script_yyerror (yyget_extra (yyscanner), 0); + return 1; +} + +static int +grub_lexer_unput (const char *text, yyscan_t yyscanner) +{ + struct grub_lexer_param *lexerstate = yyget_extra (yyscanner)->lexerstate; + + if (lexerstate->prefix) + grub_free (lexerstate->prefix); + + lexerstate->prefix = grub_strdup (text); + if (! lexerstate->prefix) + { + grub_script_yyerror (yyget_extra (yyscanner), "out of memory"); + return 1; + } + return 0; +} -- cgit v1.2.3