wildcard.c 11.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
/* 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);

BVK Chaitanya's avatar
BVK Chaitanya committed
41
struct grub_script_wildcard_translator grub_filename_translator = {
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
  .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;
    }

71
  dest = p;
72 73 74 75 76 77 78 79 80 81 82
  for (j = 0; ps[j]; j++)
    dest[i++] = ps[j];
  dest[i] = 0;

  grub_free (ps);
  return dest;
}

static inline int
isregexop (char ch)
{
83
  return grub_strchr ("*.\\|+{}[]?", ch) ? 1 : 0;
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
}

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)
    {
126
      /* XXX Only * and ? expansion for now.  */
127 128 129 130 131 132 133 134 135
      switch ((ch = *start++))
	{
	case '\\':
	  buffer[i++] = ch;
	  if (*start != '\0')
	    buffer[i++] = *start++;
	  break;

	case '.':
136 137
	case '(':
	case ')':
138
	case '@':
139 140 141 142 143 144
	case '+':
	case '|':
	case '{':
	case '}':
	case '[':
	case ']':
145
	  buffer[i++] = '\\';
146
	  buffer[i++] = ch;
147 148 149 150 151 152 153
	  break;

	case '*':
	  buffer[i++] = '.';
	  buffer[i++] = '*';
	  break;

154 155 156 157
	case '?':
	  buffer[i++] = '.';
	  break;

158 159 160 161 162 163
	default:
	  buffer[i++] = ch;
	}
    }
  buffer[i++] = '$';
  buffer[i] = '\0';
164
  grub_dprintf ("expand", "Regexp is %s\n", buffer);
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193

  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++;

194
      else if (ch == '*' || ch == '?')
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
	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;
}

213 214
/* Context for match_devices.  */
struct match_devices_ctx
215
{
216 217
  const regex_t *regexp;
  int noparts;
218 219
  int ndev;
  char **devs;
220
};
221

222 223 224 225 226 227 228
/* Helper for match_devices.  */
static int
match_devices_iter (const char *name, void *data)
{
  struct match_devices_ctx *ctx = data;
  char **t;
  char *buffer;
229

230 231 232
  /* skip partitions if asked to. */
  if (ctx->noparts && grub_strchr (name, ','))
    return 0;
233

234 235 236
  buffer = grub_xasprintf ("(%s)", name);
  if (! buffer)
    return 1;
237

238 239 240 241 242 243 244
  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;
    }
245

246 247
  t = grub_realloc (ctx->devs, sizeof (char*) * (ctx->ndev + 2));
  if (! t)
248 249 250 251
    {
      grub_free (buffer);
      return 1;
    }
252

253 254 255 256 257
  ctx->devs = t;
  ctx->devs[ctx->ndev++] = buffer;
  ctx->devs[ctx->ndev] = 0;
  return 0;
}
258

259 260 261 262 263 264 265 266 267 268
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;
269

270
  if (grub_device_iterate (match_devices_iter, &ctx))
271 272
    goto fail;

273
  return ctx.devs;
274 275 276

 fail:

277 278
  for (i = 0; ctx.devs && ctx.devs[i]; i++)
    grub_free (ctx.devs[i]);
279

280
  grub_free (ctx.devs);
281 282 283 284

  return 0;
}

285 286
/* Context for match_files.  */
struct match_files_ctx
287
{
288
  const regex_t *regexp;
289 290 291
  char **files;
  unsigned nfile;
  char *dir;
292
};
293

294 295
/* Helper for match_files.  */
static int
296 297
match_files_iter (const char *name,
		  const struct grub_dirhook_info *info __attribute__((unused)),
298 299 300 301 302
		  void *data)
{
  struct match_files_ctx *ctx = data;
  char **t;
  char *buffer;
303

304 305 306
  /* skip . and .. names */
  if (grub_strcmp(".", name) == 0 || grub_strcmp("..", name) == 0)
    return 0;
307

308 309 310
  grub_dprintf ("expand", "matching: %s in %s\n", name, ctx->dir);
  if (regexec (ctx->regexp, name, 0, 0, 0))
    return 0;
311

312
  grub_dprintf ("expand", "matched\n");
313

314 315 316 317 318 319 320 321
  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);
322
      return 1;
323
    }
324

325 326 327 328 329
  ctx->files = t;
  ctx->files[ctx->nfile++] = buffer;
  ctx->files[ctx->nfile] = 0;
  return 0;
}
330

331 332 333 334 335 336 337 338 339 340 341 342 343 344
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;
345 346 347 348 349

  dev = 0;
  device_name = 0;
  grub_error_push ();

350 351
  ctx.dir = make_dir (prefix, suffix, end);
  if (! ctx.dir)
352 353
    goto fail;

354
  device_name = grub_file_get_device_name (ctx.dir);
355 356 357 358 359 360 361 362
  dev = grub_device_open (device_name);
  if (! dev)
    goto fail;

  fs = grub_fs_probe (dev);
  if (! fs)
    goto fail;

363
  if (ctx.dir[0] == '(')
364
    {
365
      path = grub_strchr (ctx.dir, ')');
366 367 368 369 370
      if (!path)
	goto fail;
      path++;
    }
  else
371
    path = ctx.dir;
372

373
  if (fs->dir (dev, path, match_files_iter, &ctx))
374 375
    goto fail;

376
  grub_free (ctx.dir);
377 378 379
  grub_device_close (dev);
  grub_free (device_name);
  grub_error_pop ();
380
  return ctx.files;
381 382 383

 fail:

384
  grub_free (ctx.dir);
385

386 387
  for (i = 0; ctx.files && ctx.files[i]; i++)
    grub_free (ctx.files[i]);
388

389
  grub_free (ctx.files);
390 391 392 393

  if (dev)
    grub_device_close (dev);

394
  grub_free (device_name);
395 396 397 398 399

  grub_error_pop ();
  return 0;
}

400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
/* 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;
}

425 426 427
static int
check_file (const char *dir, const char *basename)
{
428 429 430 431
  struct check_file_ctx ctx = {
    .basename = basename,
    .found = 0
  };
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
  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;

455
  fs->dir (dev, path[0] ? path : "/", check_file_iter, &ctx);
456
  if (grub_errno == 0 && basename[0] == 0)
457
    ctx.found = 1;
458 459 460 461

 fail:
  grub_errno = 0;

462
  return ctx.found;
463 464
}

465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
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;
}

486 487 488 489 490 491 492
static grub_err_t
wildcard_expand (const char *s, char ***strs)
{
  const char *start;
  const char *regexop;
  const char *noregexop;
  char **paths = 0;
493
  int had_regexp = 0;
494 495 496 497

  unsigned i;
  regex_t regexp;

498
  *strs = 0;
499 500 501
  if (s[0] != '/' && s[0] != '(' && s[0] != '*')
    return 0;

502 503 504 505 506
  start = s;
  while (*start)
    {
      split_path (start, &noregexop, &regexop);

507 508 509 510 511 512 513 514 515 516 517
      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;
518
	      unescape (paths[0], start, regexop);
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
	      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);
535 536

		  unescape (n + (oend - o), start, regexop);
537 538 539 540 541 542 543 544 545 546 547 548 549
		  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))
		    {
550 551
		      grub_dprintf ("expand", "file <%s> in <%s> not found\n",
				    p + 1, n);
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
		      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;
	}

573 574 575
      if (make_regex (noregexop, regexop, &regexp))
	goto fail;

576 577
      had_regexp = 1;

578 579 580 581 582
      if (paths == 0)
	{
	  if (start == noregexop) /* device part has regexop */
	    paths = match_devices (&regexp, *start != '(');

583
	  else  /* device part explicit wo regexop */
584 585 586 587 588 589 590 591 592 593 594
	    paths = match_files ("", start, noregexop, &regexp);
	}
      else
	{
	  char **r = 0;

	  for (i = 0; paths[i]; i++)
	    {
	      char **p;

	      p = match_files (paths[i], start, noregexop, &regexp);
595
	      grub_free (paths[i]);
596 597 598 599 600 601 602
	      if (! p)
		continue;

	      r = merge (r, p);
	      if (! r)
		goto fail;
	    }
603
	  grub_free (paths);
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
	  paths = r;
	}

      regfree (&regexp);
      if (! paths)
	goto done;

      start = regexop;
    }

 done:

  *strs = paths;
  return 0;

 fail:

  for (i = 0; paths && paths[i]; i++)
    grub_free (paths[i]);
623
  grub_free (paths);
624 625 626
  regfree (&regexp);
  return grub_errno;
}