diff options
Diffstat (limited to '')
-rw-r--r-- | grub-core/script/argv.c | 165 | ||||
-rw-r--r-- | grub-core/script/execute.c | 1192 | ||||
-rw-r--r-- | grub-core/script/function.c | 123 | ||||
-rw-r--r-- | grub-core/script/lexer.c | 356 | ||||
-rw-r--r-- | grub-core/script/main.c | 98 | ||||
-rw-r--r-- | grub-core/script/parser.y | 356 | ||||
-rw-r--r-- | grub-core/script/script.c | 396 | ||||
-rw-r--r-- | grub-core/script/yylex.l | 393 |
8 files changed, 3079 insertions, 0 deletions
diff --git a/grub-core/script/argv.c b/grub-core/script/argv.c new file mode 100644 index 0000000..5751fdd --- /dev/null +++ b/grub-core/script/argv.c @@ -0,0 +1,165 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/script_sh.h> +#include <grub/safemath.h> + +/* 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_strlen (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; + grub_size_t sz; + + if (argv->args && argv->argc && argv->args[argv->argc - 1] == 0) + return 0; + + if (grub_add (argv->argc, 2, &sz) || + grub_mul (sz, sizeof (char *), &sz)) + return 1; + + p = grub_realloc (p, round_up_exp (sz)); + 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, + grub_size_t slen) +{ + grub_size_t a; + char *p = argv->args[argv->argc - 1]; + grub_size_t sz; + + if (! s) + return 0; + + a = p ? grub_strlen (p) : 0; + + if (grub_add (a, slen, &sz) || + grub_add (sz, 1, &sz) || + grub_mul (sz, sizeof (char), &sz)) + return 1; + + p = grub_realloc (p, round_up_exp (sz)); + if (! p) + return 1; + + grub_memcpy (p + a, s, slen); + p[a+slen] = 0; + 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, const char *s) +{ + const char *p; + int errors = 0; + + if (! s) + return 0; + + while (*s && grub_isspace (*s)) + s++; + + while (! errors && *s) + { + p = s; + while (*s && ! grub_isspace (*s)) + s++; + + errors += grub_script_argv_append (argv, p, s - p); + + 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..2515840 --- /dev/null +++ b/grub-core/script/execute.c @@ -0,0 +1,1192 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/env.h> +#include <grub/script_sh.h> +#include <grub/command.h> +#include <grub/menu.h> +#include <grub/lib/arg.h> +#include <grub/normal.h> +#include <grub/extcmd.h> +#include <grub/i18n.h> +#include <grub/verify.h> + +/* 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 char* +wildcard_escape (const char *s) +{ + int i; + int len; + char ch; + char *p; + + len = grub_strlen (s); + p = grub_malloc (len * 2 + 1); + if (! p) + return NULL; + + i = 0; + while ((ch = *s++)) + { + if (ch == '*' || ch == '\\' || ch == '?') + p[i++] = '\\'; + p[i++] = ch; + } + p[i] = '\0'; + return p; +} + +static char* +wildcard_unescape (const char *s) +{ + int i; + int len; + char ch; + char *p; + + len = grub_strlen (s); + p = grub_malloc (len + 1); + if (! p) + return NULL; + + i = 0; + while ((ch = *s++)) + { + if (ch == '\\') + p[i++] = *s++; + else + p[i++] = ch; + } + p[i] = '\0'; + return p; +} + +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[]) +{ + const char *p = NULL; + unsigned long count; + + if (argc == 0) + count = 1; + else if (argc > 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); + else + { + count = grub_strtoul (argv[0], &p, 10); + if (grub_errno) + return grub_errno; + if (*p != '\0') + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("unrecognized number")); + if (count == 0) + /* TRANSLATORS: 0 is a quantifier. "break" (similar to bash) + can be used e.g. to break 3 loops at once. + But asking it to break 0 loops makes no sense. */ + return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("can't break 0 loops")); + } + + is_continue = grub_strcmp (cmd->name, "break") ? 1 : 0; + active_breaks = count; + if (active_breaks > active_loops) + active_breaks = active_loops; + return GRUB_ERR_NONE; +} + +grub_err_t +grub_script_shift (grub_command_t cmd __attribute__((unused)), + int argc, char *argv[]) +{ + const char *p = NULL; + 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[]) +{ + const char *p = NULL; + unsigned long n; + + if (! scope || argc > 1) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + /* TRANSLATORS: It's about not being + inside a function. "return" can be used only + in a function and this error occurs if it's used + anywhere else. */ + N_("not in function body")); + + if (argc == 0) + { + const char *t; + function_return = 1; + t = grub_env_get ("?"); + if (!t) + return GRUB_ERR_NONE; + return grub_strtoul (t, NULL, 10); + } + + n = grub_strtoul (argv[0], &p, 10); + if (grub_errno) + return grub_errno; + if (*p != '\0') + return grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("unrecognized number")); + + function_return = 1; + return n ? grub_error (n, N_("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)) + { + const 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, grub_strlen (v))) + goto fail; + } + } + else if (! scope) + { + if (grub_script_argv_append (&result, 0, 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, grub_strlen (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, " ", 1)) + goto fail; + + if (grub_script_argv_append (&result, scope->argv.args[i], + grub_strlen (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], + grub_strlen (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], + grub_strlen (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, + N_("invalid variable name `%s'"), name); + + return grub_env_set (name, val); +} + +struct gettext_context +{ + char **allowed_strings; + grub_size_t nallowed_strings; + grub_size_t additional_len; +}; + +static int +parse_string (const char *str, + int (*hook) (const char *var, grub_size_t varlen, + char **ptr, struct gettext_context *ctx), + struct gettext_context *ctx, + char *put) +{ + const char *ptr; + int escaped = 0; + const char *optr; + + for (ptr = str; ptr && *ptr; ) + switch (*ptr) + { + case '\\': + escaped = !escaped; + if (!escaped && put) + *(put++) = '\\'; + ptr++; + break; + case '$': + if (escaped) + { + escaped = 0; + if (put) + *(put++) = *ptr; + ptr++; + break; + } + + ptr++; + switch (*ptr) + { + case '{': + { + optr = ptr + 1; + ptr = grub_strchr (optr, '}'); + if (!ptr) + break; + if (hook (optr, ptr - optr, &put, ctx)) + return 1; + ptr++; + break; + } + case '0' ... '9': + optr = ptr; + while (*ptr >= '0' && *ptr <= '9') + ptr++; + if (hook (optr, ptr - optr, &put, ctx)) + return 1; + break; + case 'a' ... 'z': + case 'A' ... 'Z': + case '_': + optr = ptr; + while ((*ptr >= '0' && *ptr <= '9') + || (*ptr >= 'a' && *ptr <= 'z') + || (*ptr >= 'A' && *ptr <= 'Z') + || *ptr == '_') + ptr++; + if (hook (optr, ptr - optr, &put, ctx)) + return 1; + break; + case '?': + case '#': + if (hook (ptr, 1, &put, ctx)) + return 1; + ptr++; + break; + default: + if (put) + *(put++) = '$'; + } + break; + default: + if (escaped && put) + *(put++) = '\\'; + escaped = 0; + if (put) + *(put++) = *ptr; + ptr++; + break; + } + if (put) + *(put++) = 0; + return 0; +} + +static int +gettext_putvar (const char *str, grub_size_t len, + char **ptr, struct gettext_context *ctx) +{ + const char *var; + grub_size_t i; + + for (i = 0; i < ctx->nallowed_strings; i++) + if (grub_strncmp (ctx->allowed_strings[i], str, len) == 0 + && ctx->allowed_strings[i][len] == 0) + { + break; + } + if (i == ctx->nallowed_strings) + return 0; + + /* Enough for any number. */ + if (len == 1 && str[0] == '#' && scope != NULL) + { + grub_snprintf (*ptr, 30, "%u", scope->argv.argc); + *ptr += grub_strlen (*ptr); + return 0; + } + var = grub_env_get (ctx->allowed_strings[i]); + if (var) + *ptr = grub_stpcpy (*ptr, var); + return 0; +} + +static int +gettext_save_allow (const char *str, grub_size_t len, + char **ptr __attribute__ ((unused)), + struct gettext_context *ctx) +{ + ctx->allowed_strings[ctx->nallowed_strings++] = grub_strndup (str, len); + if (!ctx->allowed_strings[ctx->nallowed_strings - 1]) + return 1; + return 0; +} + +static int +gettext_getlen (const char *str, grub_size_t len, + char **ptr __attribute__ ((unused)), + struct gettext_context *ctx) +{ + const char *var; + grub_size_t i; + + for (i = 0; i < ctx->nallowed_strings; i++) + if (grub_strncmp (ctx->allowed_strings[i], str, len) == 0 + && ctx->allowed_strings[i][len] == 0) + break; + if (i == ctx->nallowed_strings) + return 0; + + /* Enough for any number. */ + if (len == 1 && str[0] == '#') + { + ctx->additional_len += 30; + return 0; + } + var = grub_env_get (ctx->allowed_strings[i]); + if (var) + ctx->additional_len += grub_strlen (var); + return 0; +} + +static int +gettext_append (struct grub_script_argv *result, const char *orig_str) +{ + const char *template; + char *res = 0; + struct gettext_context ctx = { + .allowed_strings = 0, + .nallowed_strings = 0, + .additional_len = 1 + }; + int rval = 1; + const char *iptr; + + grub_size_t dollar_cnt = 0; + + for (iptr = orig_str; *iptr; iptr++) + if (*iptr == '$') + dollar_cnt++; + ctx.allowed_strings = grub_calloc (dollar_cnt, sizeof (ctx.allowed_strings[0])); + + if (parse_string (orig_str, gettext_save_allow, &ctx, 0)) + goto fail; + + template = _(orig_str); + + if (parse_string (template, gettext_getlen, &ctx, 0)) + goto fail; + + res = grub_malloc (grub_strlen (template) + ctx.additional_len); + if (!res) + goto fail; + + if (parse_string (template, gettext_putvar, &ctx, res)) + goto fail; + + char *escaped = 0; + escaped = wildcard_escape (res); + if (grub_script_argv_append (result, escaped, grub_strlen (escaped))) + { + grub_free (escaped); + goto fail; + } + grub_free (escaped); + + rval = 0; + fail: + grub_free (res); + { + grub_size_t i; + for (i = 0; i < ctx.nallowed_strings; i++) + grub_free (ctx.allowed_strings[i]); + } + grub_free (ctx.allowed_strings); + return rval; +} + +static int +append (struct grub_script_argv *result, + const char *s, int escape_type) +{ + int r; + char *p = 0; + + if (escape_type == 0) + return grub_script_argv_append (result, s, grub_strlen (s)); + + if (escape_type > 0) + p = wildcard_escape (s); + else if (escape_type < 0) + p = wildcard_unescape (s); + + if (! p) + return 1; + + r = grub_script_argv_append (result, p, grub_strlen (p)); + grub_free (p); + return r; +} + +/* 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 }; + + if (arglist == NULL) + return 1; + + 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: + { + int need_cleanup = 0; + + values = grub_script_env_get (arg->str, arg->type); + for (i = 0; values && values[i]; i++) + { + if (!need_cleanup) + { + if (i != 0 && grub_script_argv_next (&result)) + { + need_cleanup = 1; + goto cleanup; + } + + if (arg->type == GRUB_SCRIPT_ARG_TYPE_VAR) + { + int len; + char ch; + char *p; + char *op; + const char *s = values[i]; + + len = grub_strlen (values[i]); + /* \? -> \\\? */ + /* \* -> \\\* */ + /* \ -> \\ */ + p = grub_malloc (len * 2 + 1); + if (! p) + { + need_cleanup = 1; + goto cleanup; + } + + op = p; + while ((ch = *s++)) + { + if (ch == '\\') + { + *op++ = '\\'; + if (*s == '?' || *s == '*') + *op++ = '\\'; + } + *op++ = ch; + } + *op = '\0'; + + need_cleanup = grub_script_argv_append (&result, p, op - p); + grub_free (p); + /* Fall through to cleanup */ + } + else + { + need_cleanup = append (&result, values[i], 1); + /* Fall through to cleanup */ + } + } + +cleanup: + grub_free (values[i]); + } + grub_free (values); + + if (need_cleanup) + goto fail; + + break; + } + + case GRUB_SCRIPT_ARG_TYPE_BLOCK: + { + char *p; + if (grub_script_argv_append (&result, "{", 1)) + goto fail; + p = wildcard_escape (arg->str); + if (!p) + goto fail; + if (grub_script_argv_append (&result, p, + grub_strlen (p))) + { + grub_free (p); + goto fail; + } + grub_free (p); + if (grub_script_argv_append (&result, "}", 1)) + goto fail; + } + result.script = arg->script; + break; + + case GRUB_SCRIPT_ARG_TYPE_TEXT: + if (arg->str[0] && + grub_script_argv_append (&result, arg->str, + grub_strlen (arg->str))) + goto fail; + break; + + case GRUB_SCRIPT_ARG_TYPE_GETTEXT: + { + if (gettext_append (&result, arg->str)) + goto fail; + } + break; + + case GRUB_SCRIPT_ARG_TYPE_DQSTR: + case GRUB_SCRIPT_ARG_TYPE_SQSTR: + if (append (&result, arg->str, 1)) + goto fail; + break; + } + arg = arg->next; + } + } + + if (! result.args[result.argc - 1]) + result.argc--; + + /* Perform wildcard expansion. */ + + int j; + int failed = 0; + struct grub_script_argv unexpanded = result; + + result.argc = 0; + result.args = 0; + for (i = 0; unexpanded.args[i]; i++) + { + char **expansions = 0; + if (grub_wildcard_translator + && grub_wildcard_translator->expand (unexpanded.args[i], + &expansions)) + { + grub_script_argv_free (&unexpanded); + goto fail; + } + + if (! expansions) + { + grub_script_argv_next (&result); + append (&result, unexpanded.args[i], -1); + } + else + { + for (j = 0; expansions[j]; j++) + { + failed = (failed || grub_script_argv_next (&result) || + append (&result, expansions[j], 0)); + 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; + + func->executing++; + ret = grub_script_execute (func->func); + func->executing--; + + function_return = 0; + active_loops = loops; + replace_scope (old_scope); /* free any scopes by setparams */ + return ret; +} + +/* Helper for grub_script_execute_sourcecode. */ +static grub_err_t +grub_script_execute_sourcecode_getline (char **line, + int cont __attribute__ ((unused)), + void *data) +{ + const char **source = data; + 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; +} + +/* Execute a source script. */ +grub_err_t +grub_script_execute_sourcecode (const char *source) +{ + grub_err_t ret = 0; + struct grub_script *parsed_script; + + while (source) + { + char *line; + + grub_script_execute_sourcecode_getline (&line, 0, &source); + parsed_script = grub_script_parse + (line, grub_script_execute_sourcecode_getline, &source); + if (! parsed_script) + { + ret = grub_errno; + grub_free (line); + break; + } + + ret = grub_script_execute (parsed_script); + grub_script_free (parsed_script); + grub_free (line); + } + + return ret; +} + +/* Execute a source script in new scope. */ +grub_err_t +grub_script_execute_new_scope (const char *source, int argc, char **args) +{ + grub_err_t ret = 0; + struct grub_script_scope new_scope; + struct grub_script_scope *old_scope; + + new_scope.argv.argc = argc; + new_scope.argv.args = args; + new_scope.flags = 0; + + old_scope = scope; + scope = &new_scope; + + ret = grub_script_execute_sourcecode (source); + + 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, *cmdstring; + int argc, offset = 0, cmdlen = 0; + unsigned int i; + 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 || ! argv.args[0]) + return grub_errno; + + for (i = 0; i < argv.argc; i++) + { + cmdlen += grub_strlen (argv.args[i]) + 1; + } + + cmdstring = grub_malloc (cmdlen); + if (!cmdstring) + { + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + N_("cannot allocate command buffer")); + } + + for (i = 0; i < argv.argc; i++) + { + offset += grub_snprintf (cmdstring + offset, cmdlen - offset, "%s ", + argv.args[i]); + } + cmdstring[cmdlen - 1] = '\0'; + grub_verify_string (cmdstring, GRUB_VERIFY_COMMAND); + grub_free (cmdstring); + 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, + N_("no command is specified")); + } + + 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, N_("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; + const 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..3aad04b --- /dev/null +++ b/grub-core/script/function.c @@ -0,0 +1,123 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> +#include <grub/script_sh.h> +#include <grub/parser.h> +#include <grub/mm.h> +#include <grub/charset.h> + +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->executing = 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_free (func); + if (q->executing > 0) + { + grub_error (GRUB_ERR_BAD_ARGUMENT, + N_("attempt to redefine a function being executed")); + func = NULL; + } + else + { + grub_script_free (q->func); + q->func = cmd; + 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) + { + char tmp[21]; + grub_strncpy (tmp, functionname, 20); + tmp[20] = 0; + /* Avoid truncating inside UTF-8 character. */ + tmp[grub_getend (tmp, tmp + grub_strlen (tmp))] = 0; + grub_error (GRUB_ERR_UNKNOWN_COMMAND, N_("can't find command `%s'"), tmp); + } + + return func; +} diff --git a/grub-core/script/lexer.c b/grub-core/script/lexer.c new file mode 100644 index 0000000..52004d0 --- /dev/null +++ b/grub-core/script/lexer.c @@ -0,0 +1,356 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <grub/parser.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/script_sh.h> +#include <grub/i18n.h> +#include <grub/safemath.h> + +#define yytext_ptr char * +#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; + if (lexer->recordlen < len) + lexer->recordlen = len; + + if (grub_mul (lexer->recordlen, 2, &lexer->recordlen)) + goto fail; + + lexer->recording = grub_realloc (lexer->recording, lexer->recordlen); + if (!lexer->recording) + { + fail: + 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) +{ + grub_size_t len = 0, sz; + 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, N_("unexpected end of file")); + return 1; + } + + line = 0; + if (! input) + lexerstate->getline (&line, 1, lexerstate->getline_data); + else + line = grub_strdup (input); + + if (! line) + { + grub_script_yyerror (parserstate, N_("out of memory")); + return 1; + } + + len = grub_strlen (line); + + /* Ensure '\n' at the end. */ + if (line[0] == '\0') + { + grub_free (line); + line = grub_strdup ("\n"); + len = 1; + } + else if (len && line[len - 1] != '\n') + { + if (grub_add (len, 2, &sz)) + { + grub_free (line); + grub_script_yyerror (parserstate, N_("overflow is detected")); + return 1; + } + + p = grub_realloc (line, sz); + if (p) + { + p[len++] = '\n'; + p[len] = '\0'; + } + else + grub_free (line); + + line = p; + } + + if (! line) + { + grub_script_yyerror (parserstate, N_("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 arg_getline, void *getline_data) +{ + 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 = arg_getline; + lexerstate->getline_data = getline_data; + /* The other elements of lexerstate 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->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, const char *err) +{ + if (err) + grub_error (GRUB_ERR_INVALID_COMMAND, "%s", 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..854a25a --- /dev/null +++ b/grub-core/script/main.c @@ -0,0 +1,98 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <grub/dl.h> +#include <grub/i18n.h> +#include <grub/parser.h> +#include <grub/script_sh.h> + +grub_err_t +grub_normal_parse_line (char *line, + grub_reader_getline_t getline, void *getline_data) +{ + struct grub_script *parsed_script; + + /* Parse the script. */ + parsed_script = grub_script_parse (line, getline, getline_data); + + 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_("[NUM]"), N_("Exit from loops")); + cmd_continue = grub_register_command ("continue", grub_script_break, + N_("[NUM]"), N_("Continue loops")); + cmd_shift = grub_register_command ("shift", grub_script_shift, + N_("[NUM]"), + /* TRANSLATORS: Positional arguments are + arguments $0, $1, $2, ... */ + 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_("[NUM]"), + /* TRANSLATORS: It's a command description + and "Return" is a verb, not a noun. The + command in question is "return" and + has exactly the same semanics as bash + equivalent. */ + 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..4a18ab7 --- /dev/null +++ b/grub-core/script/parser.y @@ -0,0 +1,356 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +%{ +#include <grub/script_sh.h> +#include <grub/mm.h> +#include <grub/misc.h> +#include <grub/i18n.h> + +#define YYFREE grub_free +#define YYMALLOC grub_malloc +#define YYLTYPE_IS_TRIVIAL 0 +#define YYENABLE_NLS 0 + +#include "grub_script.tab.h" + +#pragma GCC diagnostic ignored "-Wmissing-declarations" + +%} + +%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 <arg> GRUB_PARSER_TOKEN_CASE "case" +%token <arg> GRUB_PARSER_TOKEN_DO "do" +%token <arg> GRUB_PARSER_TOKEN_DONE "done" +%token <arg> GRUB_PARSER_TOKEN_ELIF "elif" +%token <arg> GRUB_PARSER_TOKEN_ELSE "else" +%token <arg> GRUB_PARSER_TOKEN_ESAC "esac" +%token <arg> GRUB_PARSER_TOKEN_FI "fi" +%token <arg> GRUB_PARSER_TOKEN_FOR "for" +%token <arg> GRUB_PARSER_TOKEN_IF "if" +%token <arg> GRUB_PARSER_TOKEN_IN "in" +%token <arg> GRUB_PARSER_TOKEN_SELECT "select" +%token <arg> GRUB_PARSER_TOKEN_THEN "then" +%token <arg> GRUB_PARSER_TOKEN_UNTIL "until" +%token <arg> GRUB_PARSER_TOKEN_WHILE "while" +%token <arg> GRUB_PARSER_TOKEN_FUNCTION "function" +%token <arg> GRUB_PARSER_TOKEN_NAME "name" +%token <arg> GRUB_PARSER_TOKEN_WORD "word" + +%type <arg> block block0 +%type <arglist> word argument arguments0 arguments1 + +%type <cmd> script_init script +%type <cmd> grubcmd ifclause ifcmd forcmd whilecmd untilcmd +%type <cmd> 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, N_("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); } + | 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); + $<offset>$ = grub_script_lexer_record_start (state); + $<memory>$ = grub_script_mem_record (state); + + /* save currently known scripts. */ + $<scripts>$ = state->scripts; + state->scripts = 0; + } + commands1 delimiters0 "}" + { + char *p; + struct grub_script_mem *memory; + struct grub_script *s = $<scripts>2; + + memory = grub_script_mem_record_stop (state, $<memory>2); + if ((p = grub_script_lexer_record_stop (state, $<offset>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 = $<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); + + $<scripts>$ = state->scripts; + state->scripts = 0; + } + newlines0 "{" 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; + if (!grub_script_function_create ($2, script)) + grub_script_free (script); + } + + state->scripts = $<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..ec4d433 --- /dev/null +++ b/grub-core/script/script.c @@ -0,0 +1,396 @@ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include <grub/misc.h> +#include <grub/script_sh.h> +#include <grub/parser.h> +#include <grub/mm.h> + +/* 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, void *getline_data) +{ + 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, getline_data); + 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..b7203c8 --- /dev/null +++ b/grub-core/script/yylex.l @@ -0,0 +1,393 @@ +%{ +/* 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 <http://www.gnu.org/licenses/>. + */ + +#include <grub/parser.h> +#include <grub/misc.h> +#include <grub/mm.h> +#include <grub/script_sh.h> +#include <grub/i18n.h> +#include "grub_script.tab.h" + +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wmissing-prototypes" +#pragma GCC diagnostic ignored "-Wmissing-declarations" +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wsign-compare" + +#define yyalloc(size, scanner) (grub_malloc((size))) +#define yyfree(ptr, scanner) (grub_free((ptr))) +#define yyrealloc(ptr, size, scanner) (grub_realloc((ptr), (size))) + +/* + * As we don't have access to yyscanner, we cannot do much except to + * print the fatal error and exit. + */ +#define YY_FATAL_ERROR(msg) \ + do { \ + grub_fatal (_("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 copy_string (struct grub_parser_param *, const char *, + unsigned hint); + +%} + +%top{ + +#include <config.h> + +#include <sys/types.h> + +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(...) (void)0 +#define exit(...) grub_fatal("fatal error in lexer") +#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}*\" +I18NSTR \$\"{DQCHR}*\" +SQSTR \'{SQCHR}*\' +SPECIAL \?|\#|\*|\@ +VARIABLE ${NAME}|$\{{NAME}\}|${DIGITS}|$\{{DIGITS}\}|${SPECIAL}|$\{{SPECIAL}\} +WORD ({CHAR}|{DQSTR}|{SQSTR}|{ESC}|{VARIABLE}|{I18NSTR})+ + +MULTILINE {WORD}?((\"{DQCHR}*)|(\$\"{DQCHR}*)|(\'{SQCHR}*)) +POS_MULTILINE {WORD}?\\\n + +%x SPLIT +%x DQUOTE +%x I18NQUOTE +%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_LSQBR2; } +"]]" { RECORD; return GRUB_PARSER_TOKEN_RSQBR2; } +"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; + } + +{POS_MULTILINE} { + if (yyg->yy_c_buf_p + 1 == &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] ) + { + if (grub_lexer_unput (yytext, yyscanner)) + return GRUB_PARSER_TOKEN_BAD; + } + else + { + 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; + } + } + + +{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 */ + +<SPLIT>{ + \\. { COPY (yytext, yyleng); } + \\\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 (I18NQUOTE, yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_GETTEXT); + } + \$ { + yy_push_state (VAR, yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_TEXT); + } + \\ | + [^\"\'\$\\]+ { COPY (yytext, yyleng); } + <<EOF>> { + yy_pop_state (yyscanner); + yypop_buffer_state (yyscanner); + yyextra->lexerstate->resplit = 0; + yyextra->lexerstate->merge_end = 1; + ARG (GRUB_SCRIPT_ARG_TYPE_TEXT); + } +} + +<VAR>{ + {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; } +} + +<SQUOTE>{ + \' { + yy_pop_state (yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_SQSTR); + } + [^\']+ { COPY (yytext, yyleng); } +} + +<DQUOTE>{ + \\\$ { 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); } +} + +<I18NQUOTE>{ + \\\\ { COPY ("\\\\", 2); } + \\\" { COPY ("\"", 1); } + \\\n { /* ignore */ } + [^\"\\\n]+ { COPY (yytext, yyleng); } + \" { + yy_pop_state (yyscanner); + ARG (GRUB_SCRIPT_ARG_TYPE_GETTEXT); + } + \\ { COPY ("\\", 1); } + (.|\n) { COPY (yytext, yyleng); } +} + +<<EOF>> { + 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 copy_string (struct grub_parser_param *parser, const char *str, unsigned hint) +{ + grub_size_t size; + char *ptr; + unsigned len; + + len = hint ? hint : grub_strlen (str); + if (parser->lexerstate->used + len >= parser->lexerstate->size) + { + size = len * 2; + if (size < parser->lexerstate->size * 2) + size = 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; + + grub_free (lexerstate->prefix); + + lexerstate->prefix = grub_strdup (text); + if (! lexerstate->prefix) + { + grub_script_yyerror (yyget_extra (yyscanner), N_("out of memory")); + return 1; + } + return 0; +} |