]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
some shell expansion features into grub-script
authorBVK Chaitanya <bvk.groups@gmail.com>
Tue, 18 May 2010 15:33:35 +0000 (21:03 +0530)
committerBVK Chaitanya <bvk.groups@gmail.com>
Tue, 18 May 2010 15:33:35 +0000 (21:03 +0530)
conf/tests.rmk
include/grub/script_sh.h
script/argv.c
script/execute.c
tests/grub_script_expansion.in [new file with mode: 0644]
tests/util/grub-shell.in

index 9144e552845b3a517dc7ff959b226bbfcc722e58..94187f1500d4843d790c5f05be0f4c229d46c6fc 100644 (file)
@@ -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
index 5455fc763436c7243d8a57b8c4e341c59ec26b46..cdf9b731d474b5579f19af345149fac0ed8aa3fb 100644 (file)
@@ -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);
index 1ac81f4b85e134b4b9e2cd78e5714ab36756d1ea..8446ded3df22f65ea9be295398dfbd8682a50e37 100644 (file)
  */
 
 #include <grub/mm.h>
+#include <grub/fs.h>
+#include <grub/env.h>
+#include <grub/file.h>
+#include <grub/device.h>
 #include <grub/script_sh.h>
 
+#include <regex.h>
+
 #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, &reg);
+      if (dir < reg)
+       {
+         if (make_regex (dir, reg, &regex))
+           goto fail;
+
+         paths = match_paths_with_escaped_suffix (paths, p, dir, &regex);
+         regfree (&regex);
+
+         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 (&regex);
+  return 1;
+}
index 2040be13cd53d6364e58902177b7b16f2a5fe378..0f31cdc6eccc6b81684818de296a13b2a61aa29e 100644 (file)
@@ -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 (file)
index 0000000..80f0a6a
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+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
+
index a41a6f6f46f6780930ab2cc91700e4bba09c0848..a287860578b6c8a31dae2904836caf366e948f4a 100644 (file)
@@ -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