mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-10-31 19:15:14 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			627 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			627 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* wildcard.c - Wildcard character expansion for GRUB script.  */
 | |
| /*
 | |
|  *  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/fs.h>
 | |
| #include <grub/env.h>
 | |
| #include <grub/file.h>
 | |
| #include <grub/device.h>
 | |
| #include <grub/script_sh.h>
 | |
| 
 | |
| #include <regex.h>
 | |
| 
 | |
| static inline int isregexop (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 (const char *path, const char **suffix_end, const char **regex_end);
 | |
| static char ** match_devices (const regex_t *regexp, int noparts);
 | |
| static char ** match_files (const char *prefix, const char *suffix_start,
 | |
| 			    const char *suffix_end, const regex_t *regexp);
 | |
| 
 | |
| static grub_err_t wildcard_expand (const char *s, char ***strs);
 | |
| 
 | |
| struct grub_script_wildcard_translator grub_filename_translator = {
 | |
|   .expand = wildcard_expand,
 | |
| };
 | |
| 
 | |
| 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;
 | |
|     }
 | |
| 
 | |
|   dest = p;
 | |
|   for (j = 0; ps[j]; j++)
 | |
|     dest[i++] = ps[j];
 | |
|   dest[i] = 0;
 | |
| 
 | |
|   grub_free (ps);
 | |
|   return dest;
 | |
| }
 | |
| 
 | |
| static inline int
 | |
| isregexop (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 == '\\' && isregexop (*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 + 2 + 1); /* worst case size. */
 | |
| 
 | |
|   if (! buffer)
 | |
|     return 1;
 | |
| 
 | |
|   buffer[i++] = '^';
 | |
|   while (start < end)
 | |
|     {
 | |
|       /* XXX Only * and ? expansion for now.  */
 | |
|       switch ((ch = *start++))
 | |
| 	{
 | |
| 	case '\\':
 | |
| 	  buffer[i++] = ch;
 | |
| 	  if (*start != '\0')
 | |
| 	    buffer[i++] = *start++;
 | |
| 	  break;
 | |
| 
 | |
| 	case '.':
 | |
| 	case '(':
 | |
| 	case ')':
 | |
| 	case '@':
 | |
| 	case '+':
 | |
| 	case '|':
 | |
| 	case '{':
 | |
| 	case '}':
 | |
| 	case '[':
 | |
| 	case ']':
 | |
| 	  buffer[i++] = '\\';
 | |
| 	  buffer[i++] = ch;
 | |
| 	  break;
 | |
| 
 | |
| 	case '*':
 | |
| 	  buffer[i++] = '.';
 | |
| 	  buffer[i++] = '*';
 | |
| 	  break;
 | |
| 
 | |
| 	case '?':
 | |
| 	  buffer[i++] = '.';
 | |
| 	  break;
 | |
| 
 | |
| 	default:
 | |
| 	  buffer[i++] = ch;
 | |
| 	}
 | |
|     }
 | |
|   buffer[i++] = '$';
 | |
|   buffer[i] = '\0';
 | |
|   grub_dprintf ("expand", "Regexp is %s\n", buffer);
 | |
| 
 | |
|   if (regcomp (regexp, buffer, RE_SYNTAX_GNU_AWK))
 | |
|     {
 | |
|       grub_free (buffer);
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|   grub_free (buffer);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* Split `str' into two parts: (1) dirname that is regexop free (2)
 | |
|    dirname that has a regexop.  */
 | |
| static void
 | |
| split_path (const char *str, const char **noregexop, const char **regexop)
 | |
| {
 | |
|   char ch = 0;
 | |
|   int regex = 0;
 | |
| 
 | |
|   const char *end;
 | |
|   const char *split;  /* points till the end of dirnaname that doesn't
 | |
| 			 need expansion.  */
 | |
| 
 | |
|   split = end = str;
 | |
|   while ((ch = *end))
 | |
|     {
 | |
|       if (ch == '\\' && end[1])
 | |
| 	end++;
 | |
| 
 | |
|       else if (ch == '*' || ch == '?')
 | |
| 	regex = 1;
 | |
| 
 | |
|       else if (ch == '/' && ! regex)
 | |
| 	split = end + 1;  /* forward to next regexop-free dirname */
 | |
| 
 | |
|       else if (ch == '/' && regex)
 | |
| 	break;  /* stop at the first dirname with a regexop */
 | |
| 
 | |
|       end++;
 | |
|     }
 | |
| 
 | |
|   *regexop = end;
 | |
|   if (! regex)
 | |
|     *noregexop = end;
 | |
|   else
 | |
|     *noregexop = split;
 | |
| }
 | |
| 
 | |
| /* Context for match_devices.  */
 | |
| struct match_devices_ctx
 | |
| {
 | |
|   const regex_t *regexp;
 | |
|   int noparts;
 | |
|   int ndev;
 | |
|   char **devs;
 | |
| };
 | |
| 
 | |
| /* Helper for match_devices.  */
 | |
| static int
 | |
| match_devices_iter (const char *name, void *data)
 | |
| {
 | |
|   struct match_devices_ctx *ctx = data;
 | |
|   char **t;
 | |
|   char *buffer;
 | |
| 
 | |
|   /* skip partitions if asked to. */
 | |
|   if (ctx->noparts && grub_strchr (name, ','))
 | |
|     return 0;
 | |
| 
 | |
|   buffer = grub_xasprintf ("(%s)", name);
 | |
|   if (! buffer)
 | |
|     return 1;
 | |
| 
 | |
|   grub_dprintf ("expand", "matching: %s\n", buffer);
 | |
|   if (regexec (ctx->regexp, buffer, 0, 0, 0))
 | |
|     {
 | |
|       grub_dprintf ("expand", "not matched\n");
 | |
|       grub_free (buffer);
 | |
|       return 0;
 | |
|     }
 | |
| 
 | |
|   t = grub_realloc (ctx->devs, sizeof (char*) * (ctx->ndev + 2));
 | |
|   if (! t)
 | |
|     {
 | |
|       grub_free (buffer);
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|   ctx->devs = t;
 | |
|   ctx->devs[ctx->ndev++] = buffer;
 | |
|   ctx->devs[ctx->ndev] = 0;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static char **
 | |
| match_devices (const regex_t *regexp, int noparts)
 | |
| {
 | |
|   struct match_devices_ctx ctx = {
 | |
|     .regexp = regexp,
 | |
|     .noparts = noparts,
 | |
|     .ndev = 0,
 | |
|     .devs = 0
 | |
|   };
 | |
|   int i;
 | |
| 
 | |
|   if (grub_device_iterate (match_devices_iter, &ctx))
 | |
|     goto fail;
 | |
| 
 | |
|   return ctx.devs;
 | |
| 
 | |
|  fail:
 | |
| 
 | |
|   for (i = 0; ctx.devs && ctx.devs[i]; i++)
 | |
|     grub_free (ctx.devs[i]);
 | |
| 
 | |
|   grub_free (ctx.devs);
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* Context for match_files.  */
 | |
| struct match_files_ctx
 | |
| {
 | |
|   const regex_t *regexp;
 | |
|   char **files;
 | |
|   unsigned nfile;
 | |
|   char *dir;
 | |
| };
 | |
| 
 | |
| /* Helper for match_files.  */
 | |
| static int
 | |
| match_files_iter (const char *name,
 | |
| 		  const struct grub_dirhook_info *info __attribute__((unused)),
 | |
| 		  void *data)
 | |
| {
 | |
|   struct match_files_ctx *ctx = data;
 | |
|   char **t;
 | |
|   char *buffer;
 | |
| 
 | |
|   /* skip . and .. names */
 | |
|   if (grub_strcmp(".", name) == 0 || grub_strcmp("..", name) == 0)
 | |
|     return 0;
 | |
| 
 | |
|   grub_dprintf ("expand", "matching: %s in %s\n", name, ctx->dir);
 | |
|   if (regexec (ctx->regexp, name, 0, 0, 0))
 | |
|     return 0;
 | |
| 
 | |
|   grub_dprintf ("expand", "matched\n");
 | |
| 
 | |
|   buffer = grub_xasprintf ("%s%s", ctx->dir, name);
 | |
|   if (! buffer)
 | |
|     return 1;
 | |
| 
 | |
|   t = grub_realloc (ctx->files, sizeof (char*) * (ctx->nfile + 2));
 | |
|   if (! t)
 | |
|     {
 | |
|       grub_free (buffer);
 | |
|       return 1;
 | |
|     }
 | |
| 
 | |
|   ctx->files = t;
 | |
|   ctx->files[ctx->nfile++] = buffer;
 | |
|   ctx->files[ctx->nfile] = 0;
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static char **
 | |
| match_files (const char *prefix, const char *suffix, const char *end,
 | |
| 	     const regex_t *regexp)
 | |
| {
 | |
|   struct match_files_ctx ctx = {
 | |
|     .regexp = regexp,
 | |
|     .nfile = 0,
 | |
|     .files = 0
 | |
|   };
 | |
|   int i;
 | |
|   const char *path;
 | |
|   char *device_name;
 | |
|   grub_fs_t fs;
 | |
|   grub_device_t dev;
 | |
| 
 | |
|   dev = 0;
 | |
|   device_name = 0;
 | |
|   grub_error_push ();
 | |
| 
 | |
|   ctx.dir = make_dir (prefix, suffix, end);
 | |
|   if (! ctx.dir)
 | |
|     goto fail;
 | |
| 
 | |
|   device_name = grub_file_get_device_name (ctx.dir);
 | |
|   dev = grub_device_open (device_name);
 | |
|   if (! dev)
 | |
|     goto fail;
 | |
| 
 | |
|   fs = grub_fs_probe (dev);
 | |
|   if (! fs)
 | |
|     goto fail;
 | |
| 
 | |
|   if (ctx.dir[0] == '(')
 | |
|     {
 | |
|       path = grub_strchr (ctx.dir, ')');
 | |
|       if (!path)
 | |
| 	goto fail;
 | |
|       path++;
 | |
|     }
 | |
|   else
 | |
|     path = ctx.dir;
 | |
| 
 | |
|   if (fs->dir (dev, path, match_files_iter, &ctx))
 | |
|     goto fail;
 | |
| 
 | |
|   grub_free (ctx.dir);
 | |
|   grub_device_close (dev);
 | |
|   grub_free (device_name);
 | |
|   grub_error_pop ();
 | |
|   return ctx.files;
 | |
| 
 | |
|  fail:
 | |
| 
 | |
|   grub_free (ctx.dir);
 | |
| 
 | |
|   for (i = 0; ctx.files && ctx.files[i]; i++)
 | |
|     grub_free (ctx.files[i]);
 | |
| 
 | |
|   grub_free (ctx.files);
 | |
| 
 | |
|   if (dev)
 | |
|     grub_device_close (dev);
 | |
| 
 | |
|   grub_free (device_name);
 | |
| 
 | |
|   grub_error_pop ();
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /* Context for check_file.  */
 | |
| struct check_file_ctx
 | |
| {
 | |
|   const char *basename;
 | |
|   int found;
 | |
| };
 | |
| 
 | |
| /* Helper for check_file.  */
 | |
| static int
 | |
| check_file_iter (const char *name, const struct grub_dirhook_info *info,
 | |
| 		 void *data)
 | |
| {
 | |
|   struct check_file_ctx *ctx = data;
 | |
| 
 | |
|   if (ctx->basename[0] == 0
 | |
|       || (info->case_insensitive ? grub_strcasecmp (name, ctx->basename) == 0
 | |
| 	  : grub_strcmp (name, ctx->basename) == 0))
 | |
|     {
 | |
|       ctx->found = 1;
 | |
|       return 1;
 | |
|     }
 | |
|   
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| check_file (const char *dir, const char *basename)
 | |
| {
 | |
|   struct check_file_ctx ctx = {
 | |
|     .basename = basename,
 | |
|     .found = 0
 | |
|   };
 | |
|   grub_fs_t fs;
 | |
|   grub_device_t dev;
 | |
|   const char *device_name, *path;
 | |
| 
 | |
|   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;
 | |
| 
 | |
|   if (dir[0] == '(')
 | |
|     {
 | |
|       path = grub_strchr (dir, ')');
 | |
|       if (!path)
 | |
| 	goto fail;
 | |
|       path++;
 | |
|     }
 | |
|   else
 | |
|     path = dir;
 | |
| 
 | |
|   fs->dir (dev, path[0] ? path : "/", check_file_iter, &ctx);
 | |
|   if (grub_errno == 0 && basename[0] == 0)
 | |
|     ctx.found = 1;
 | |
| 
 | |
|  fail:
 | |
|   grub_errno = 0;
 | |
| 
 | |
|   return ctx.found;
 | |
| }
 | |
| 
 | |
| static void
 | |
| unescape (char *out, const char *in, const char *end)
 | |
| {
 | |
|   char *optr;
 | |
|   const char *iptr;
 | |
| 
 | |
|   for (optr = out, iptr = in; iptr < end;)
 | |
|     {
 | |
|       if (*iptr == '\\' && iptr + 1 < end)
 | |
| 	{
 | |
| 	  *optr++ = iptr[1];
 | |
| 	  iptr += 2;
 | |
| 	  continue;
 | |
| 	}
 | |
|       if (*iptr == '\\')
 | |
| 	break;
 | |
|       *optr++ = *iptr++;
 | |
|     }
 | |
|   *optr = 0;
 | |
| }
 | |
| 
 | |
| static grub_err_t
 | |
| wildcard_expand (const char *s, char ***strs)
 | |
| {
 | |
|   const char *start;
 | |
|   const char *regexop;
 | |
|   const char *noregexop;
 | |
|   char **paths = 0;
 | |
|   int had_regexp = 0;
 | |
| 
 | |
|   unsigned i;
 | |
|   regex_t regexp;
 | |
| 
 | |
|   *strs = 0;
 | |
|   if (s[0] != '/' && s[0] != '(' && s[0] != '*')
 | |
|     return 0;
 | |
| 
 | |
|   start = s;
 | |
|   while (*start)
 | |
|     {
 | |
|       split_path (start, &noregexop, ®exop);
 | |
| 
 | |
|       if (noregexop == regexop)
 | |
| 	{
 | |
| 	  grub_dprintf ("expand", "no expansion needed\n");
 | |
| 	  if (paths == 0)
 | |
| 	    {
 | |
| 	      paths = grub_malloc (sizeof (char *) * 2);
 | |
| 	      if (!paths)
 | |
| 		goto fail;
 | |
| 	      paths[0] = grub_malloc (regexop - start + 1);
 | |
| 	      if (!paths[0])
 | |
| 		goto fail;
 | |
| 	      unescape (paths[0], start, regexop);
 | |
| 	      paths[1] = 0;
 | |
| 	    }
 | |
| 	  else
 | |
| 	    {
 | |
| 	      int j = 0;
 | |
| 	      for (i = 0; paths[i]; i++)
 | |
| 		{
 | |
| 		  char *o, *oend;
 | |
| 		  char *n;
 | |
| 		  char *p;
 | |
| 		  o = paths[i];
 | |
| 		  oend = o + grub_strlen (o);
 | |
| 		  n = grub_malloc ((oend - o) + (regexop - start) + 1);
 | |
| 		  if (!n)
 | |
| 		    goto fail;
 | |
| 		  grub_memcpy (n, o, oend - o);
 | |
| 
 | |
| 		  unescape (n + (oend - o), start, regexop);
 | |
| 		  if (had_regexp)
 | |
| 		    p = grub_strrchr (n, '/');
 | |
| 		  else
 | |
| 		    p = 0;
 | |
| 		  if (!p)
 | |
| 		    {
 | |
| 		      grub_free (o);
 | |
| 		      paths[j++] = n;
 | |
| 		      continue;
 | |
| 		    }
 | |
| 		  *p = 0;
 | |
| 		  if (!check_file (n, p + 1))
 | |
| 		    {
 | |
| 		      grub_dprintf ("expand", "file <%s> in <%s> not found\n",
 | |
| 				    p + 1, n);
 | |
| 		      grub_free (o);
 | |
| 		      grub_free (n);
 | |
| 			      continue;
 | |
| 		    }
 | |
| 		  *p = '/';
 | |
| 		  grub_free (o);
 | |
| 		  paths[j++] = n;
 | |
| 		}
 | |
| 	      if (j == 0)
 | |
| 		{
 | |
| 		  grub_free (paths);
 | |
| 		  paths = 0;
 | |
| 		  goto done;
 | |
| 		}
 | |
| 	      paths[j] = 0;
 | |
| 	    }
 | |
| 	  grub_dprintf ("expand", "paths[0] = `%s'\n", paths[0]);
 | |
| 	  start = regexop;
 | |
| 	  continue;
 | |
| 	}
 | |
| 
 | |
|       if (make_regex (noregexop, regexop, ®exp))
 | |
| 	goto fail;
 | |
| 
 | |
|       had_regexp = 1;
 | |
| 
 | |
|       if (paths == 0)
 | |
| 	{
 | |
| 	  if (start == noregexop) /* device part has regexop */
 | |
| 	    paths = match_devices (®exp, *start != '(');
 | |
| 
 | |
| 	  else  /* device part explicit wo regexop */
 | |
| 	    paths = match_files ("", start, noregexop, ®exp);
 | |
| 	}
 | |
|       else
 | |
| 	{
 | |
| 	  char **r = 0;
 | |
| 
 | |
| 	  for (i = 0; paths[i]; i++)
 | |
| 	    {
 | |
| 	      char **p;
 | |
| 
 | |
| 	      p = match_files (paths[i], start, noregexop, ®exp);
 | |
| 	      grub_free (paths[i]);
 | |
| 	      if (! p)
 | |
| 		continue;
 | |
| 
 | |
| 	      r = merge (r, p);
 | |
| 	      if (! r)
 | |
| 		goto fail;
 | |
| 	    }
 | |
| 	  grub_free (paths);
 | |
| 	  paths = r;
 | |
| 	}
 | |
| 
 | |
|       regfree (®exp);
 | |
|       if (! paths)
 | |
| 	goto done;
 | |
| 
 | |
|       start = regexop;
 | |
|     }
 | |
| 
 | |
|  done:
 | |
| 
 | |
|   *strs = paths;
 | |
|   return 0;
 | |
| 
 | |
|  fail:
 | |
| 
 | |
|   for (i = 0; paths && paths[i]; i++)
 | |
|     grub_free (paths[i]);
 | |
|   grub_free (paths);
 | |
|   regfree (®exp);
 | |
|   return grub_errno;
 | |
| }
 | 
