mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-11-04 12:55:16 +00:00 
			
		
		
		
	* include/grub/fs.h (grub_fs_dir_hook_t): New type. (struct grub_fs.dir): Add hook_data argument. Update all implementations and callers.
		
			
				
	
	
		
			623 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			623 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)
 | 
						|
    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,
 | 
						|
		  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;
 | 
						|
}
 |