test.c 11.1 KB
Newer Older
1 2 3
/* test.c -- The test command..  */
/*
 *  GRUB  --  GRand Unified Bootloader
4
 *  Copyright (C) 2005,2007,2009  Free Software Foundation, Inc.
5
 *
6
 *  GRUB is free software: you can redistribute it and/or modify
7
 *  it under the terms of the GNU General Public License as published by
8
 *  the Free Software Foundation, either version 3 of the License, or
9 10
 *  (at your option) any later version.
 *
11
 *  GRUB is distributed in the hope that it will be useful,
12 13 14 15 16
 *  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
17
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
18 19 20 21 22 23
 */

#include <grub/dl.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/env.h>
24 25 26
#include <grub/fs.h>
#include <grub/device.h>
#include <grub/file.h>
27
#include <grub/command.h>
28
#include <grub/i18n.h>
29

30 31
GRUB_MOD_LICENSE ("GPLv3+");

32 33 34 35 36 37 38 39 40
/* A simple implementation for signed numbers. */
static int
grub_strtosl (char *arg, char **end, int base)
{
  if (arg[0] == '-')
    return -grub_strtoul (arg + 1, end, base);
  return grub_strtoul (arg, end, base);
}

41 42
/* Context for test_parse.  */
struct test_parse_ctx
43
{
44 45
  int invert;
  int or, and;
46 47
  int file_exists;
  struct grub_dirhook_info file_info;
48 49 50 51 52 53 54
  char *filename;
};

/* Take care of discarding and inverting. */
static void
update_val (int val, struct test_parse_ctx *ctx)
{
55 56
  ctx->and = ctx->and && (ctx->invert ? ! val : val);
  ctx->invert = 0;
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
}

/* A hook for iterating directories. */
static int
find_file (const char *cur_filename, const struct grub_dirhook_info *info,
	   void *data)
{
  struct test_parse_ctx *ctx = data;

  if ((info->case_insensitive ? grub_strcasecmp (cur_filename, ctx->filename)
       : grub_strcmp (cur_filename, ctx->filename)) == 0)
    {
      ctx->file_info = *info;
      ctx->file_exists = 1;
      return 1;
    }
  return 0;
}

/* Check if file exists and fetch its information. */
static void
get_fileinfo (char *path, struct test_parse_ctx *ctx)
{
  char *pathname;
  char *device_name;
  grub_fs_t fs;
  grub_device_t dev;

  ctx->file_exists = 0;
  device_name = grub_file_get_device_name (path);
  dev = grub_device_open (device_name);
  if (! dev)
    {
      grub_free (device_name);
      return;
    }
93

94 95
  fs = grub_fs_probe (dev);
  if (! fs)
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 126 127 128 129 130 131 132 133 134 135 136
      grub_free (device_name);
      grub_device_close (dev);
      return;
    }

  pathname = grub_strchr (path, ')');
  if (! pathname)
    pathname = path;
  else
    pathname++;

  /* Remove trailing '/'. */
  while (*pathname && pathname[grub_strlen (pathname) - 1] == '/')
    pathname[grub_strlen (pathname) - 1] = 0;

  /* Split into path and filename. */
  ctx->filename = grub_strrchr (pathname, '/');
  if (! ctx->filename)
    {
      path = grub_strdup ("/");
      ctx->filename = pathname;
    }
  else
    {
      ctx->filename++;
      path = grub_strdup (pathname);
      path[ctx->filename - pathname] = 0;
    }

  /* It's the whole device. */
  if (! *pathname)
    {
      ctx->file_exists = 1;
      grub_memset (&ctx->file_info, 0, sizeof (ctx->file_info));
      /* Root is always a directory. */
      ctx->file_info.dir = 1;

      /* Fetch writing time. */
      ctx->file_info.mtimeset = 0;
      if (fs->mtime)
137
	{
138 139 140
	  if (! fs->mtime (dev, &ctx->file_info.mtime))
	    ctx->file_info.mtimeset = 1;
	  grub_errno = GRUB_ERR_NONE;
141 142
	}
    }
143 144 145 146 147 148 149
  else
    (fs->dir) (dev, path, find_file, ctx);

  grub_device_close (dev);
  grub_free (path);
  grub_free (device_name);
}
150

151 152 153 154 155
/* Parse a test expression starting from *argn. */
static int
test_parse (char **args, int *argn, int argc)
{
  struct test_parse_ctx ctx = {
156 157
    .and = 1,
    .or = 0,
158 159
    .invert = 0
  };
160 161 162 163 164 165 166 167 168 169 170

  /* Here we have the real parsing. */
  while (*argn < argc)
    {
      /* First try 3 argument tests. */
      if (*argn + 2 < argc)
	{
	  /* String tests. */
	  if (grub_strcmp (args[*argn + 1], "=") == 0
	      || grub_strcmp (args[*argn + 1], "==") == 0)
	    {
171 172
	      update_val (grub_strcmp (args[*argn], args[*argn + 2]) == 0,
			  &ctx);
173 174 175 176 177 178
	      (*argn) += 3;
	      continue;
	    }

	  if (grub_strcmp (args[*argn + 1], "!=") == 0)
	    {
179 180
	      update_val (grub_strcmp (args[*argn], args[*argn + 2]) != 0,
			  &ctx);
181 182 183
	      (*argn) += 3;
	      continue;
	    }
184

185 186 187
	  /* GRUB extension: lexicographical sorting. */
	  if (grub_strcmp (args[*argn + 1], "<") == 0)
	    {
188 189
	      update_val (grub_strcmp (args[*argn], args[*argn + 2]) < 0,
			  &ctx);
190 191 192
	      (*argn) += 3;
	      continue;
	    }
193

194 195
	  if (grub_strcmp (args[*argn + 1], "<=") == 0)
	    {
196 197
	      update_val (grub_strcmp (args[*argn], args[*argn + 2]) <= 0,
			  &ctx);
198 199 200
	      (*argn) += 3;
	      continue;
	    }
201

202 203
	  if (grub_strcmp (args[*argn + 1], ">") == 0)
	    {
204 205
	      update_val (grub_strcmp (args[*argn], args[*argn + 2]) > 0,
			  &ctx);
206 207 208
	      (*argn) += 3;
	      continue;
	    }
209

210 211
	  if (grub_strcmp (args[*argn + 1], ">=") == 0)
	    {
212 213
	      update_val (grub_strcmp (args[*argn], args[*argn + 2]) >= 0,
			  &ctx);
214 215 216 217 218 219 220
	      (*argn) += 3;
	      continue;
	    }

	  /* Number tests. */
	  if (grub_strcmp (args[*argn + 1], "-eq") == 0)
	    {
221
	      update_val (grub_strtosl (args[*argn], 0, 0)
222
			  == grub_strtosl (args[*argn + 2], 0, 0), &ctx);
223 224 225 226 227 228
	      (*argn) += 3;
	      continue;
	    }

	  if (grub_strcmp (args[*argn + 1], "-ge") == 0)
	    {
229
	      update_val (grub_strtosl (args[*argn], 0, 0)
230
			  >= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
231 232 233
	      (*argn) += 3;
	      continue;
	    }
234

235 236
	  if (grub_strcmp (args[*argn + 1], "-gt") == 0)
	    {
237
	      update_val (grub_strtosl (args[*argn], 0, 0)
238
			  > grub_strtosl (args[*argn + 2], 0, 0), &ctx);
239 240 241 242 243 244
	      (*argn) += 3;
	      continue;
	    }

	  if (grub_strcmp (args[*argn + 1], "-le") == 0)
	    {
245
	      update_val (grub_strtosl (args[*argn], 0, 0)
246
		      <= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
247 248 249
	      (*argn) += 3;
	      continue;
	    }
250

251 252
	  if (grub_strcmp (args[*argn + 1], "-lt") == 0)
	    {
253
	      update_val (grub_strtosl (args[*argn], 0, 0)
254
			  < grub_strtosl (args[*argn + 2], 0, 0), &ctx);
255 256 257
	      (*argn) += 3;
	      continue;
	    }
258

259 260
	  if (grub_strcmp (args[*argn + 1], "-ne") == 0)
	    {
261
	      update_val (grub_strtosl (args[*argn], 0, 0)
262
			  != grub_strtosl (args[*argn + 2], 0, 0), &ctx);
263 264 265 266
	      (*argn) += 3;
	      continue;
	    }

267
	  /* GRUB extension: compare numbers skipping prefixes.
268 269 270 271 272 273
	     Useful for comparing versions. E.g. vmlinuz-2 -plt vmlinuz-11. */
	  if (grub_strcmp (args[*argn + 1], "-pgt") == 0
	      || grub_strcmp (args[*argn + 1], "-plt") == 0)
	    {
	      int i;
	      /* Skip common prefix. */
274
	      for (i = 0; args[*argn][i] == args[*argn + 2][i]
275
		     && args[*argn][i]; i++);
276

277 278 279 280 281
	      /* Go the digits back. */
	      i--;
	      while (grub_isdigit (args[*argn][i]) && i > 0)
		i--;
	      i++;
282

283
	      if (grub_strcmp (args[*argn + 1], "-pgt") == 0)
284
		update_val (grub_strtoul (args[*argn] + i, 0, 0)
285
			    > grub_strtoul (args[*argn + 2] + i, 0, 0), &ctx);
286
	      else
287
		update_val (grub_strtoul (args[*argn] + i, 0, 0)
288
			    < grub_strtoul (args[*argn + 2] + i, 0, 0), &ctx);
289 290 291 292
	      (*argn) += 3;
	      continue;
	    }

293
	  /* -nt and -ot tests. GRUB extension: when doing -?t<bias> bias
294 295 296 297 298 299 300
	     will be added to the first mtime. */
	  if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0
	      || grub_memcmp (args[*argn + 1], "-ot", 3) == 0)
	    {
	      struct grub_dirhook_info file1;
	      int file1exists;
	      int bias = 0;
301

302
	      /* Fetch fileinfo. */
303 304 305 306
	      get_fileinfo (args[*argn], &ctx);
	      file1 = ctx.file_info;
	      file1exists = ctx.file_exists;
	      get_fileinfo (args[*argn + 2], &ctx);
307

308 309
	      if (args[*argn + 1][3])
		bias = grub_strtosl (args[*argn + 1] + 3, 0, 0);
310

311
	      if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0)
312 313 314 315
		update_val ((file1exists && ! ctx.file_exists)
			    || (file1.mtimeset && ctx.file_info.mtimeset
				&& file1.mtime + bias > ctx.file_info.mtime),
			    &ctx);
316
	      else
317 318 319 320
		update_val ((! file1exists && ctx.file_exists)
			    || (file1.mtimeset && ctx.file_info.mtimeset
				&& file1.mtime + bias < ctx.file_info.mtime),
			    &ctx);
321 322 323 324 325 326 327 328 329 330 331
	      (*argn) += 3;
	      continue;
	    }
	}

      /* Two-argument tests. */
      if (*argn + 1 < argc)
	{
	  /* File tests. */
	  if (grub_strcmp (args[*argn], "-d") == 0)
	    {
332 333
	      get_fileinfo (args[*argn + 1], &ctx);
	      update_val (ctx.file_exists && ctx.file_info.dir, &ctx);
334
	      (*argn) += 2;
335
	      continue;
336
	    }
337

338 339
	  if (grub_strcmp (args[*argn], "-e") == 0)
	    {
340 341
	      get_fileinfo (args[*argn + 1], &ctx);
	      update_val (ctx.file_exists, &ctx);
342
	      (*argn) += 2;
343
	      continue;
344 345 346 347
	    }

	  if (grub_strcmp (args[*argn], "-f") == 0)
	    {
348
	      get_fileinfo (args[*argn + 1], &ctx);
349
	      /* FIXME: check for other types. */
350
	      update_val (ctx.file_exists && ! ctx.file_info.dir, &ctx);
351
	      (*argn) += 2;
352
	      continue;
353
	    }
354

355 356 357
	  if (grub_strcmp (args[*argn], "-s") == 0)
	    {
	      grub_file_t file;
358
	      grub_file_filter_disable_compression ();
359
	      file = grub_file_open (args[*argn + 1]);
360
	      update_val (file && (grub_file_size (file) != 0), &ctx);
361 362 363 364
	      if (file)
		grub_file_close (file);
	      grub_errno = GRUB_ERR_NONE;
	      (*argn) += 2;
365
	      continue;
366
	    }
367

368 369 370
	  /* String tests. */
	  if (grub_strcmp (args[*argn], "-n") == 0)
	    {
371
	      update_val (args[*argn + 1][0], &ctx);
372

373 374 375 376 377
	      (*argn) += 2;
	      continue;
	    }
	  if (grub_strcmp (args[*argn], "-z") == 0)
	    {
378
	      update_val (! args[*argn + 1][0], &ctx);
379 380 381 382 383 384
	      (*argn) += 2;
	      continue;
	    }
	}

      /* Special modifiers. */
385

386 387 388 389
      /* End of expression. return to parent. */
      if (grub_strcmp (args[*argn], ")") == 0)
	{
	  (*argn)++;
390
	  return ctx.or || ctx.and;
391 392 393 394 395
	}
      /* Recursively invoke if parenthesis. */
      if (grub_strcmp (args[*argn], "(") == 0)
	{
	  (*argn)++;
396
	  update_val (test_parse (args, argn, argc), &ctx);
397 398
	  continue;
	}
399

400 401
      if (grub_strcmp (args[*argn], "!") == 0)
	{
402
	  ctx.invert = ! ctx.invert;
403 404 405 406 407 408 409 410 411 412
	  (*argn)++;
	  continue;
	}
      if (grub_strcmp (args[*argn], "-a") == 0)
	{
	  (*argn)++;
	  continue;
	}
      if (grub_strcmp (args[*argn], "-o") == 0)
	{
413 414
	  ctx.or = ctx.or || ctx.and;
	  ctx.and = 1;
415 416 417 418 419
	  (*argn)++;
	  continue;
	}

      /* No test found. Interpret if as just a string. */
420
      update_val (args[*argn][0], &ctx);
421 422
      (*argn)++;
    }
423
  return ctx.or || ctx.and;
424 425
}

426
static grub_err_t
427 428
grub_cmd_test (grub_command_t cmd __attribute__ ((unused)),
	       int argc, char **args)
429
{
430 431 432 433 434
  int argn = 0;

  if (argc >= 1 && grub_strcmp (args[argc - 1], "]") == 0)
    argc--;

435
  return test_parse (args, &argn, argc) ? GRUB_ERR_NONE
436
    : grub_error (GRUB_ERR_TEST_FAILURE, N_("false"));
437 438
}

439
static grub_command_t cmd_1, cmd_2;
440

441
GRUB_MOD_INIT(test)
442
{
443
  cmd_1 = grub_register_command ("[", grub_cmd_test,
444
				 N_("EXPRESSION ]"), N_("Evaluate an expression."));
445
  cmd_1->flags |= GRUB_COMMAND_FLAG_EXTRACTOR;
446
  cmd_2 = grub_register_command ("test", grub_cmd_test,
447
				 N_("EXPRESSION"), N_("Evaluate an expression."));
448
  cmd_2->flags |= GRUB_COMMAND_FLAG_EXTRACTOR;
449 450
}

451
GRUB_MOD_FINI(test)
452
{
453 454
  grub_unregister_command (cmd_1);
  grub_unregister_command (cmd_2);
455
}