summaryrefslogtreecommitdiffstats
path: root/grub-core/script
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/script')
-rw-r--r--grub-core/script/argv.c165
-rw-r--r--grub-core/script/execute.c1192
-rw-r--r--grub-core/script/function.c123
-rw-r--r--grub-core/script/lexer.c356
-rw-r--r--grub-core/script/main.c98
-rw-r--r--grub-core/script/parser.y356
-rw-r--r--grub-core/script/script.c396
-rw-r--r--grub-core/script/yylex.l393
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;
+}