# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: address@hidden # target_branch: ../cmdlist/ # testament_sha1: d7f4cd00982ced62f3dc9a8e039ca883acc23046 # timestamp: 2010-05-18 21:26:14 +0530 # base_revision_id: address@hidden # en6z2k5s1ux46lg3 # # Begin patch === modified file 'conf/tests.rmk' --- conf/tests.rmk 2010-05-05 09:17:50 +0000 +++ conf/tests.rmk 2010-05-18 15:33:35 +0000 @@ -74,6 +74,9 @@ 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_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 === modified file 'include/grub/script_sh.h' --- include/grub/script_sh.h 2010-05-12 04:49:12 +0000 +++ include/grub/script_sh.h 2010-05-18 15:33:35 +0000 @@ -225,7 +225,12 @@ 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); === modified file 'script/argv.c' --- script/argv.c 2010-05-12 04:49:12 +0000 +++ script/argv.c 2010-05-18 15:33:35 +0000 @@ -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 @@ 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 @@ } 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; +} === modified file 'script/execute.c' --- script/execute.c 2010-05-12 07:42:49 +0000 +++ script/execute.c 2010-05-18 15:33:35 +0000 @@ -177,7 +177,13 @@ { 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 @@ 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_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 @@ cmd_menuentry->sourcecode); grub_script_argv_free (&argv); - return grub_errno; } === added file 'tests/grub_script_expansion.in' --- tests/grub_script_expansion.in 1970-01-01 00:00:00 +0000 +++ tests/grub_script_expansion.in 2010-05-18 15:33:35 +0000 @@ -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 + === modified file 'tests/util/grub-shell.in' --- tests/util/grub-shell.in 2010-04-06 06:51:11 +0000 +++ tests/util/grub-shell.in 2010-05-18 15:33:35 +0000 @@ -95,7 +95,7 @@ if [ "x${source}" = x ] ; then tmpfile=`mktemp` while read; do - echo $REPLY >> ${tmpfile} + echo "$REPLY" >> ${tmpfile} done source=${tmpfile} fi # Begin bundle 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