From a0b20aad47ff02c7a1a0cd38a0407c4f2564cc08 Mon Sep 17 00:00:00 2001 From: BVK Chaitanya Date: Tue, 18 May 2010 21:03:35 +0530 Subject: [PATCH] some shell expansion features into grub-script --- conf/tests.rmk | 4 + include/grub/script_sh.h | 5 + script/argv.c | 538 ++++++++++++++++++++++++++++++++- script/execute.c | 22 +- tests/grub_script_expansion.in | 35 +++ tests/util/grub-shell.in | 2 +- 6 files changed, 593 insertions(+), 13 deletions(-) create mode 100644 tests/grub_script_expansion.in diff --git a/conf/tests.rmk b/conf/tests.rmk index 9144e5528..94187f150 100644 --- a/conf/tests.rmk +++ b/conf/tests.rmk @@ -74,6 +74,9 @@ grub_script_comments_SOURCES = tests/grub_script_comments.in check_SCRIPTS += grub_script_functions grub_script_functions_SOURCES = tests/grub_script_functions.in +check_SCRIPTS += grub_script_expansion +grub_script_expansion_SOURCES = tests/grub_script_expansion.in + # List of tests to execute on "make check" # SCRIPTED_TESTS = example_scripted_test # SCRIPTED_TESTS += example_grub_script_test @@ -91,6 +94,7 @@ SCRIPTED_TESTS += grub_script_final_semicolon SCRIPTED_TESTS += grub_script_dollar SCRIPTED_TESTS += grub_script_comments SCRIPTED_TESTS += grub_script_functions +SCRIPTED_TESTS += grub_script_expansion # dependencies between tests and testing-tools $(SCRIPTED_TESTS): grub-shell grub-shell-tester diff --git a/include/grub/script_sh.h b/include/grub/script_sh.h index 5455fc763..cdf9b731d 100644 --- a/include/grub/script_sh.h +++ b/include/grub/script_sh.h @@ -225,7 +225,12 @@ struct grub_parser_param void grub_script_argv_free (struct grub_script_argv *argv); int grub_script_argv_next (struct grub_script_argv *argv); int grub_script_argv_append (struct grub_script_argv *argv, const char *s); +int grub_script_argv_append_escaped (struct grub_script_argv *argv, + const char *s); +int grub_script_argv_append_unescaped (struct grub_script_argv *argv, + const char *s); int grub_script_argv_split_append (struct grub_script_argv *argv, char *s); +int grub_script_argv_expand (struct grub_script_argv *argv); struct grub_script_arglist * grub_script_create_arglist (struct grub_parser_param *state); diff --git a/script/argv.c b/script/argv.c index 1ac81f4b8..8446ded3d 100644 --- a/script/argv.c +++ b/script/argv.c @@ -18,11 +18,32 @@ */ #include +#include +#include +#include +#include #include +#include + #define ARG_ALLOCATION_UNIT (32 * sizeof (char)) #define ARGV_ALLOCATION_UNIT (8 * sizeof (void*)) +static inline int regexop (char ch); +static char ** merge (char **lhs, char **rhs); +static char *make_dir (const char *prefix, const char *start, const char *end); +static int make_regex (const char *regex_start, const char *regex_end, + regex_t *regexp); +static void split_path (char *path, char **suffix_end, char **regex_end); +static char ** match_devices (const regex_t *regexp); +static char ** match_files (const char *prefix, const char *suffix_start, + const char *suffix_end, const regex_t *regexp); +static char ** match_paths_with_escaped_suffix (char **paths, + const char *suffix_start, + const char *suffix_end, + const regex_t *regexp); +static int expand (char *arg, struct grub_script_argv *argv); + void grub_script_argv_free (struct grub_script_argv *argv) { @@ -73,29 +94,85 @@ grub_script_argv_next (struct grub_script_argv *argv) return 0; } -/* Append `s' to the last argument. */ -int -grub_script_argv_append (struct grub_script_argv *argv, const char *s) +enum append_type { + APPEND_RAW, + APPEND_ESCAPED, + APPEND_UNESCAPED +}; + +static int +append (struct grub_script_argv *argv, const char *s, enum append_type type) { - int a, b; + int a; + int b; + char ch; char *p = argv->args[argv->argc - 1]; if (! s) return 0; a = p ? grub_strlen (p) : 0; - b = grub_strlen (s); + b = grub_strlen (s) * (type == APPEND_ESCAPED ? 2 : 1); p = grub_realloc (p, ALIGN_UP ((a + b + 1) * sizeof (char), ARG_ALLOCATION_UNIT)); if (! p) return 1; - grub_strcpy (p + a, s); + switch (type) + { + case APPEND_RAW: + grub_strcpy (p + a, s); + break; + + case APPEND_ESCAPED: + while ((ch = *s++)) + { + if (regexop (ch)) + p[a++] = '\\'; + p[a++] = ch; + } + p[a] = '\0'; + break; + + case APPEND_UNESCAPED: + while ((ch = *s++)) + { + if (ch == '\\' && regexop (*s)) + p[a++] = *s++; + else + p[a++] = ch; + } + p[a] = '\0'; + break; + } + argv->args[argv->argc - 1] = p; return 0; } + +/* Append `s' to the last argument. */ +int +grub_script_argv_append (struct grub_script_argv *argv, const char *s) +{ + return append (argv, s, APPEND_RAW); +} + +/* Append `s' to the last argument, but escape any shell regex ops. */ +int +grub_script_argv_append_escaped (struct grub_script_argv *argv, const char *s) +{ + return append (argv, s, APPEND_ESCAPED); +} + +/* Append `s' to the last argument, but unescape any escaped shell regex ops. */ +int +grub_script_argv_append_unescaped (struct grub_script_argv *argv, const char *s) +{ + return append (argv, s, APPEND_UNESCAPED); +} + /* Split `s' and append words as multiple arguments. */ int grub_script_argv_split_append (struct grub_script_argv *argv, char *s) @@ -126,3 +203,452 @@ grub_script_argv_split_append (struct grub_script_argv *argv, char *s) } return errors; } + +/* Expand `argv' as per shell expansion rules. */ +int +grub_script_argv_expand (struct grub_script_argv *argv) +{ + int i; + struct grub_script_argv result = { 0, 0 }; + + for (i = 0; argv->args[i]; i++) + if (expand (argv->args[i], &result)) + goto fail; + + grub_script_argv_free (argv); + *argv = result; + return 0; + + fail: + + grub_script_argv_free (&result); + return 1; +} + +static char ** +merge (char **dest, char **ps) +{ + int i; + int j; + char **p; + + if (! dest) + return ps; + + if (! ps) + return dest; + + for (i = 0; dest[i]; i++) + ; + for (j = 0; ps[j]; j++) + ; + + p = grub_realloc (dest, sizeof (char*) * (i + j + 1)); + if (! p) + { + grub_free (dest); + grub_free (ps); + return 0; + } + + for (j = 0; ps[j]; j++) + dest[i++] = ps[j]; + dest[i] = 0; + + grub_free (ps); + return dest; +} + +static inline int +regexop (char ch) +{ + return grub_strchr ("*.\\", ch) ? 1 : 0; +} + +static char * +make_dir (const char *prefix, const char *start, const char *end) +{ + char ch; + unsigned i; + unsigned n; + char *result; + + i = grub_strlen (prefix); + n = i + end - start; + + result = grub_malloc (n + 1); + if (! result) + return 0; + + grub_strcpy (result, prefix); + while (start < end && (ch = *start++)) + if (ch == '\\' && regexop (*start)) + result[i++] = *start++; + else + result[i++] = ch; + + result[i] = '\0'; + return result; +} + +static int +make_regex (const char *start, const char *end, regex_t *regexp) +{ + char ch; + int i = 0; + unsigned len = end - start; + char *buffer = grub_malloc (len * 2 + 1); /* worst case size. */ + + while (start < end) + { + /* XXX Only * expansion for now. */ + switch ((ch = *start++)) + { + case '\\': + buffer[i++] = ch; + if (*start != '\0') + buffer[i++] = *start++; + break; + + case '.': + buffer[i++] = '\\'; + buffer[i++] = '.'; + break; + + case '*': + buffer[i++] = '.'; + buffer[i++] = '*'; + break; + + default: + buffer[i++] = ch; + } + } + buffer[i] = '\0'; + + if (regcomp (regexp, buffer, RE_SYNTAX_GNU_AWK)) + { + grub_free (buffer); + return 1; + } + + grub_free (buffer); + return 0; +} + +static void +split_path (char *str, char **suffix_end, char **regex_end) +{ + char ch = 0; + int regex = 0; + + char *end; + char *split; + + split = end = str; + while ((ch = *end)) + { + if (ch == '\\' && end[1]) + end++; + else if (regexop (ch)) + regex = 1; + else if (ch == '/' && ! regex) + split = end + 1; + else if (ch == '/' && regex) + break; + + end++; + } + + *regex_end = end; + if (! regex) + *suffix_end = end; + else + *suffix_end = split; +} + +static char ** +match_devices (const regex_t *regexp) +{ + int i; + int ndev; + char **devs; + char *buffer; + + auto int match (const char *name); + int match (const char *name) + { + void *t; + unsigned n; + + n = grub_strlen (name); + t = grub_realloc (buffer, n + 3); + if (! t) + return 1; + + buffer = (char *) t; + grub_snprintf (buffer, n + 3, "(%s)", name); + + grub_dprintf ("expand", "matching: %s\n", buffer); + if (regexec (regexp, buffer, 0, 0, 0)) + return 0; + + t = grub_realloc (devs, sizeof (char*) * (ndev + 2)); + if (! t) + return 1; + + devs = (char **) t; + devs[ndev++] = buffer; + devs[ndev] = 0; + buffer = 0; + return 0; + } + + ndev = 0; + devs = 0; + buffer = 0; + + if (grub_device_iterate (match)) + goto fail; + + if (buffer) + grub_free (buffer); + + return devs; + + fail: + + for (i = 0; devs && devs[i]; i++) + grub_free (devs[i]); + + if (devs) + grub_free (devs); + + if (buffer) + grub_free (buffer); + + return 0; +} + +static char ** +match_files (const char *prefix, const char *suffix, const char *end, + const regex_t *regexp) +{ + int i; + int error; + char **files; + char *buffer; + unsigned nfile; + char *dir; + unsigned dirlen; + const char *path; + char *device_name; + grub_fs_t fs; + grub_device_t dev; + + auto int match (const char *name, const struct grub_dirhook_info *info); + int match (const char *name, const struct grub_dirhook_info *info) + { + void *t; + unsigned n; + + /* skip hidden files, . and .. */ + if (name[0] == '.') + return 0; + + grub_dprintf ("expand", "matching: %s in %s\n", name, dir); + if (regexec (regexp, name, 0, 0, 0)) + return 0; + + n = dirlen + grub_strlen (name); + t = grub_realloc (buffer, n + 1); + if (! t) + return 1; + + buffer = (char *) t; + grub_snprintf (buffer, n + 1, "%s%s", dir, name); + + t = grub_realloc (files, sizeof (char*) * (nfile + 2)); + if (! t) + return 1; + + files = (char **) t; + files[nfile++] = buffer; + files[nfile] = 0; + buffer = 0; + return 0; + } + + nfile = 0; + files = 0; + dev = 0; + buffer = 0; + device_name = 0; + grub_error_push (); + + dir = make_dir (prefix, suffix, end); + if (! dir) + goto fail; + dirlen = grub_strlen (dir); + + device_name = grub_file_get_device_name (dir); + dev = grub_device_open (device_name); + if (! dev) + goto fail; + + fs = grub_fs_probe (dev); + if (! fs) + goto fail; + + path = grub_strchr (dir, ')'); + if (! path) + goto fail; + path++; + + if (fs->dir (dev, path, match)) + goto fail; + + if (buffer) + grub_free (buffer); + + grub_free (dir); + grub_device_close (dev); + grub_free (device_name); + grub_error_pop (); + return files; + + fail: + + if (dir) + grub_free (dir); + + for (i = 0; files && files[i]; i++) + grub_free (files[i]); + + if (files) + grub_free (files); + + if (dev) + grub_device_close (dev); + + if (device_name) + grub_free (device_name); + + if (buffer) + grub_free (buffer); + + grub_error_pop (); + return 0; +} + +static char ** +match_paths_with_escaped_suffix (char **paths, + const char *suffix, const char *end, + const regex_t *regexp) +{ + if (paths == 0 && suffix == end) + return match_devices (regexp); + + else if (paths == 0 && suffix[0] == '(') + return match_files ("", suffix, end, regexp); + + else if (paths == 0 && suffix[0] == '/') + { + char **r; + unsigned n; + char *root; + char *prefix; + + root = grub_env_get ("root"); + if (! root) + return 0; + + n = grub_strlen (root) + 2; + prefix = grub_malloc (n + 1); + if (! prefix) + return 0; + + grub_snprintf (prefix, n + 1, "(%s)", root); + r = match_files (prefix, suffix, end, regexp); + grub_free (prefix); + return r; + } + else if (paths) + { + int i, j; + char **r = 0; + + for (i = 0; paths[i]; i++) + { + char **p; + + p = match_files (paths[i], suffix, end, regexp); + if (! p) + continue; + + r = merge (r, p); + if (! r) + return 0; + } + return r; + } + + return 0; +} + +static int +expand (char *arg, struct grub_script_argv *argv) +{ + char *p; + char *dir; + char *reg; + char **paths = 0; + + unsigned i; + regex_t regex; + + p = arg; + while (*p) + { + /* split `p' into two components: (p..dir), (dir...reg) + + (p...dir): path that doesn't need expansion + + (dir...reg): part of path that needs expansion + */ + split_path (p, &dir, ®); + if (dir < reg) + { + if (make_regex (dir, reg, ®ex)) + goto fail; + + paths = match_paths_with_escaped_suffix (paths, p, dir, ®ex); + regfree (®ex); + + if (! paths) + goto done; + } + p = reg; + } + + if (! paths) + { + grub_script_argv_next (argv); + grub_script_argv_append_unescaped (argv, arg); + } + else + for (i = 0; paths[i]; i++) + { + grub_script_argv_next (argv); + grub_script_argv_append (argv, paths[i]); + } + + done: + + return 0; + + fail: + + regfree (®ex); + return 1; +} diff --git a/script/execute.c b/script/execute.c index 2040be13c..0f31cdc6e 100644 --- a/script/execute.c +++ b/script/execute.c @@ -177,7 +177,13 @@ grub_script_arglist_to_argv (struct grub_script_arglist *arglist, { if (i != 0) error += grub_script_argv_next (&result); - error += grub_script_argv_append (&result, values[i]); + + if (arg->type == GRUB_SCRIPT_ARG_TYPE_VAR) + error += grub_script_argv_append (&result, values[i]); + else + error += grub_script_argv_append_escaped (&result, values[i]); + + grub_free (values[i]); } grub_free (values); break; @@ -189,19 +195,23 @@ grub_script_arglist_to_argv (struct grub_script_arglist *arglist, case GRUB_SCRIPT_ARG_TYPE_DQSTR: case GRUB_SCRIPT_ARG_TYPE_SQSTR: - error += grub_script_argv_append (&result, arg->str); + error += grub_script_argv_append_escaped (&result, arg->str); break; } arg = arg->next; } } - if (error) - return 1; - if (! result.args[result.argc - 1]) result.argc--; + error += grub_script_argv_expand (&result); + if (error) + { + grub_script_argv_free (&result); + return 1; + } + *argv = result; return 0; } @@ -287,6 +297,7 @@ grub_script_execute_cmdline (struct grub_script_cmd *cmd) grub_snprintf (errnobuf, sizeof (errnobuf), "%d", grub_errno); grub_env_set ("?", errnobuf); + grub_script_argv_free (&argv); grub_print_error (); return 0; @@ -411,7 +422,6 @@ grub_script_execute_menuentry (struct grub_script_cmd *cmd) cmd_menuentry->sourcecode); grub_script_argv_free (&argv); - return grub_errno; } diff --git a/tests/grub_script_expansion.in b/tests/grub_script_expansion.in new file mode 100644 index 000000000..80f0a6afa --- /dev/null +++ b/tests/grub_script_expansion.in @@ -0,0 +1,35 @@ +#! /bin/bash -e + +# Run GRUB script in a Qemu instance +# 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 . + +disks=`echo ls | @builddir@/grub-shell` +other=`echo echo '*' | @builddir@/grub-shell` +for d in $disks; do + if ! echo "$other" | grep "$d" >/dev/null; then + echo "$d missing from * expansion" >&2 + exit 1 + fi +done + +other=`echo echo '(*)' | @builddir@/grub-shell` +for d in $disks; do + if ! echo "$other" | grep "$d" >/dev/null; then + echo "$d missing from (*) expansion" >&2 + exit 1 + fi +done + diff --git a/tests/util/grub-shell.in b/tests/util/grub-shell.in index a41a6f6f4..a28786057 100644 --- a/tests/util/grub-shell.in +++ b/tests/util/grub-shell.in @@ -95,7 +95,7 @@ done if [ "x${source}" = x ] ; then tmpfile=`mktemp` while read; do - echo $REPLY >> ${tmpfile} + echo "$REPLY" >> ${tmpfile} done source=${tmpfile} fi