1 /* test.c -- The test command.. */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2005,2007,2009 Free Software Foundation, Inc.
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 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
21 #include <grub/misc.h>
25 #include <grub/device.h>
26 #include <grub/file.h>
27 #include <grub/command.h>
28 #include <grub/i18n.h>
30 GRUB_MOD_LICENSE ("GPLv3+");
32 static int g_test_case_insensitive
= 0;
34 /* A simple implementation for signed numbers. */
36 grub_strtosl (char *arg
, char **end
, int base
)
39 return -grub_strtoul (arg
+ 1, end
, base
);
40 return grub_strtoul (arg
, end
, base
);
43 /* Context for test_parse. */
49 struct grub_dirhook_info file_info
;
53 /* Take care of discarding and inverting. */
55 update_val (int val
, struct test_parse_ctx
*ctx
)
57 ctx
->and = ctx
->and && (ctx
->invert
? ! val
: val
);
61 /* A hook for iterating directories. */
63 find_file (const char *cur_filename
, const struct grub_dirhook_info
*info
,
66 int case_insensitive
= 0;
67 struct test_parse_ctx
*ctx
= data
;
69 if (g_test_case_insensitive
|| info
->case_insensitive
)
72 if ((case_insensitive
? grub_strcasecmp (cur_filename
, ctx
->filename
)
73 : grub_strcmp (cur_filename
, ctx
->filename
)) == 0)
75 ctx
->file_info
= *info
;
82 /* Check if file exists and fetch its information. */
84 get_fileinfo (char *path
, struct test_parse_ctx
*ctx
)
92 device_name
= grub_file_get_device_name (path
);
93 dev
= grub_device_open (device_name
);
96 grub_free (device_name
);
100 fs
= grub_fs_probe (dev
);
103 grub_free (device_name
);
104 grub_device_close (dev
);
108 pathname
= grub_strchr (path
, ')');
114 /* Remove trailing '/'. */
115 while (*pathname
&& pathname
[grub_strlen (pathname
) - 1] == '/')
116 pathname
[grub_strlen (pathname
) - 1] = 0;
118 /* Split into path and filename. */
119 ctx
->filename
= grub_strrchr (pathname
, '/');
122 path
= grub_strdup ("/");
123 ctx
->filename
= pathname
;
128 path
= grub_strdup (pathname
);
129 path
[ctx
->filename
- pathname
] = 0;
132 /* It's the whole device. */
135 ctx
->file_exists
= 1;
136 grub_memset (&ctx
->file_info
, 0, sizeof (ctx
->file_info
));
137 /* Root is always a directory. */
138 ctx
->file_info
.dir
= 1;
140 /* Fetch writing time. */
141 ctx
->file_info
.mtimeset
= 0;
144 if (! fs
->fs_mtime (dev
, &ctx
->file_info
.mtime
))
145 ctx
->file_info
.mtimeset
= 1;
146 grub_errno
= GRUB_ERR_NONE
;
150 (fs
->fs_dir
) (dev
, path
, find_file
, ctx
);
152 grub_device_close (dev
);
154 grub_free (device_name
);
157 /* Parse a test expression starting from *argn. */
159 test_parse (char **args
, int *argn
, int argc
)
161 struct test_parse_ctx ctx
= {
167 /* Here we have the real parsing. */
170 /* First try 3 argument tests. */
171 if (*argn
+ 2 < argc
)
174 if (grub_strcmp (args
[*argn
+ 1], "=") == 0
175 || grub_strcmp (args
[*argn
+ 1], "==") == 0)
177 update_val (grub_strcmp (args
[*argn
], args
[*argn
+ 2]) == 0,
183 if (grub_strcmp (args
[*argn
+ 1], "!=") == 0)
185 update_val (grub_strcmp (args
[*argn
], args
[*argn
+ 2]) != 0,
191 /* GRUB extension: lexicographical sorting. */
192 if (grub_strcmp (args
[*argn
+ 1], "<") == 0)
194 update_val (grub_strcmp (args
[*argn
], args
[*argn
+ 2]) < 0,
200 if (grub_strcmp (args
[*argn
+ 1], "<=") == 0)
202 update_val (grub_strcmp (args
[*argn
], args
[*argn
+ 2]) <= 0,
208 if (grub_strcmp (args
[*argn
+ 1], ">") == 0)
210 update_val (grub_strcmp (args
[*argn
], args
[*argn
+ 2]) > 0,
216 if (grub_strcmp (args
[*argn
+ 1], ">=") == 0)
218 update_val (grub_strcmp (args
[*argn
], args
[*argn
+ 2]) >= 0,
225 if (grub_strcmp (args
[*argn
+ 1], "-eq") == 0)
227 update_val (grub_strtosl (args
[*argn
], 0, 0)
228 == grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
232 if (grub_strcmp (args
[*argn
+ 1], "-EQ") == 0)
234 update_val (grub_strtoull (args
[*argn
], 0, 0)
235 == grub_strtoull (args
[*argn
+ 2], 0, 0), &ctx
);
240 if (grub_strcmp (args
[*argn
+ 1], "-ge") == 0)
242 update_val (grub_strtosl (args
[*argn
], 0, 0)
243 >= grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
247 if (grub_strcmp (args
[*argn
+ 1], "-GE") == 0)
249 update_val (grub_strtoull (args
[*argn
], 0, 0)
250 >= grub_strtoull (args
[*argn
+ 2], 0, 0), &ctx
);
255 if (grub_strcmp (args
[*argn
+ 1], "-gt") == 0)
257 update_val (grub_strtosl (args
[*argn
], 0, 0)
258 > grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
262 if (grub_strcmp (args
[*argn
+ 1], "-GT") == 0)
264 update_val (grub_strtoull (args
[*argn
], 0, 0)
265 > grub_strtoull (args
[*argn
+ 2], 0, 0), &ctx
);
270 if (grub_strcmp (args
[*argn
+ 1], "-le") == 0)
272 update_val (grub_strtosl (args
[*argn
], 0, 0)
273 <= grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
277 if (grub_strcmp (args
[*argn
+ 1], "-LE") == 0)
279 update_val (grub_strtoull (args
[*argn
], 0, 0)
280 <= grub_strtoull (args
[*argn
+ 2], 0, 0), &ctx
);
285 if (grub_strcmp (args
[*argn
+ 1], "-lt") == 0)
287 update_val (grub_strtosl (args
[*argn
], 0, 0)
288 < grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
292 if (grub_strcmp (args
[*argn
+ 1], "-LT") == 0)
294 update_val (grub_strtoull (args
[*argn
], 0, 0)
295 < grub_strtoull (args
[*argn
+ 2], 0, 0), &ctx
);
300 if (grub_strcmp (args
[*argn
+ 1], "-ne") == 0)
302 update_val (grub_strtosl (args
[*argn
], 0, 0)
303 != grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
307 if (grub_strcmp (args
[*argn
+ 1], "-NE") == 0)
309 update_val (grub_strtoull (args
[*argn
], 0, 0)
310 != grub_strtoull (args
[*argn
+ 2], 0, 0), &ctx
);
315 /* GRUB extension: compare numbers skipping prefixes.
316 Useful for comparing versions. E.g. vmlinuz-2 -plt vmlinuz-11. */
317 if (grub_strcmp (args
[*argn
+ 1], "-pgt") == 0
318 || grub_strcmp (args
[*argn
+ 1], "-plt") == 0)
321 /* Skip common prefix. */
322 for (i
= 0; args
[*argn
][i
] == args
[*argn
+ 2][i
]
323 && args
[*argn
][i
]; i
++);
325 /* Go the digits back. */
327 while (grub_isdigit (args
[*argn
][i
]) && i
> 0)
331 if (grub_strcmp (args
[*argn
+ 1], "-pgt") == 0)
332 update_val (grub_strtoul (args
[*argn
] + i
, 0, 0)
333 > grub_strtoul (args
[*argn
+ 2] + i
, 0, 0), &ctx
);
335 update_val (grub_strtoul (args
[*argn
] + i
, 0, 0)
336 < grub_strtoul (args
[*argn
+ 2] + i
, 0, 0), &ctx
);
341 /* -nt and -ot tests. GRUB extension: when doing -?t<bias> bias
342 will be added to the first mtime. */
343 if (grub_memcmp (args
[*argn
+ 1], "-nt", 3) == 0
344 || grub_memcmp (args
[*argn
+ 1], "-ot", 3) == 0)
346 struct grub_dirhook_info file1
;
350 /* Fetch fileinfo. */
351 get_fileinfo (args
[*argn
], &ctx
);
352 file1
= ctx
.file_info
;
353 file1exists
= ctx
.file_exists
;
354 get_fileinfo (args
[*argn
+ 2], &ctx
);
356 if (args
[*argn
+ 1][3])
357 bias
= grub_strtosl (args
[*argn
+ 1] + 3, 0, 0);
359 if (grub_memcmp (args
[*argn
+ 1], "-nt", 3) == 0)
360 update_val ((file1exists
&& ! ctx
.file_exists
)
361 || (file1
.mtimeset
&& ctx
.file_info
.mtimeset
362 && file1
.mtime
+ bias
> ctx
.file_info
.mtime
),
365 update_val ((! file1exists
&& ctx
.file_exists
)
366 || (file1
.mtimeset
&& ctx
.file_info
.mtimeset
367 && file1
.mtime
+ bias
< ctx
.file_info
.mtime
),
374 /* Two-argument tests. */
375 if (*argn
+ 1 < argc
)
378 if (grub_strcmp (args
[*argn
], "-d") == 0)
380 get_fileinfo (args
[*argn
+ 1], &ctx
);
381 update_val (ctx
.file_exists
&& ctx
.file_info
.dir
, &ctx
);
386 if (grub_strcmp (args
[*argn
], "-D") == 0)
388 g_test_case_insensitive
= 1;
389 get_fileinfo (args
[*argn
+ 1], &ctx
);
390 g_test_case_insensitive
= 0;
391 update_val (ctx
.file_exists
&& ctx
.file_info
.dir
, &ctx
);
396 if (grub_strcmp (args
[*argn
], "-e") == 0)
398 get_fileinfo (args
[*argn
+ 1], &ctx
);
399 update_val (ctx
.file_exists
, &ctx
);
404 if (grub_strcmp (args
[*argn
], "-E") == 0)
406 g_test_case_insensitive
= 1;
407 get_fileinfo (args
[*argn
+ 1], &ctx
);
408 g_test_case_insensitive
= 0;
409 update_val (ctx
.file_exists
, &ctx
);
414 if (grub_strcmp (args
[*argn
], "-f") == 0)
416 get_fileinfo (args
[*argn
+ 1], &ctx
);
417 /* FIXME: check for other types. */
418 update_val (ctx
.file_exists
&& ! ctx
.file_info
.dir
, &ctx
);
422 if (grub_strcmp (args
[*argn
], "-F") == 0)
424 g_test_case_insensitive
= 1;
425 get_fileinfo (args
[*argn
+ 1], &ctx
);
426 g_test_case_insensitive
= 0;
427 /* FIXME: check for other types. */
428 update_val (ctx
.file_exists
&& ! ctx
.file_info
.dir
, &ctx
);
433 if (grub_strcmp (args
[*argn
], "-s") == 0)
436 file
= grub_file_open (args
[*argn
+ 1], GRUB_FILE_TYPE_GET_SIZE
437 | GRUB_FILE_TYPE_NO_DECOMPRESS
);
438 update_val (file
&& (grub_file_size (file
) != 0), &ctx
);
440 grub_file_close (file
);
441 grub_errno
= GRUB_ERR_NONE
;
447 if (grub_strcmp (args
[*argn
], "-n") == 0)
449 update_val (args
[*argn
+ 1][0], &ctx
);
454 if (grub_strcmp (args
[*argn
], "-z") == 0)
456 update_val (! args
[*argn
+ 1][0], &ctx
);
462 /* Special modifiers. */
464 /* End of expression. return to parent. */
465 if (grub_strcmp (args
[*argn
], ")") == 0)
468 return ctx
.or || ctx
.and;
470 /* Recursively invoke if parenthesis. */
471 if (grub_strcmp (args
[*argn
], "(") == 0)
474 update_val (test_parse (args
, argn
, argc
), &ctx
);
478 if (grub_strcmp (args
[*argn
], "!") == 0)
480 ctx
.invert
= ! ctx
.invert
;
484 if (grub_strcmp (args
[*argn
], "-a") == 0)
489 if (grub_strcmp (args
[*argn
], "-o") == 0)
491 ctx
.or = ctx
.or || ctx
.and;
497 /* No test found. Interpret if as just a string. */
498 update_val (args
[*argn
][0], &ctx
);
501 return ctx
.or || ctx
.and;
505 grub_cmd_test (grub_command_t cmd
__attribute__ ((unused
)),
506 int argc
, char **args
)
510 if (argc
>= 1 && grub_strcmp (args
[argc
- 1], "]") == 0)
513 return test_parse (args
, &argn
, argc
) ? GRUB_ERR_NONE
514 : grub_error (GRUB_ERR_TEST_FAILURE
, N_("false"));
517 static grub_command_t cmd_1
, cmd_2
;
521 cmd_1
= grub_register_command ("[", grub_cmd_test
,
522 N_("EXPRESSION ]"), N_("Evaluate an expression."));
523 cmd_1
->flags
|= GRUB_COMMAND_FLAG_EXTRACTOR
;
524 cmd_2
= grub_register_command ("test", grub_cmd_test
,
525 N_("EXPRESSION"), N_("Evaluate an expression."));
526 cmd_2
->flags
|= GRUB_COMMAND_FLAG_EXTRACTOR
;
531 grub_unregister_command (cmd_1
);
532 grub_unregister_command (cmd_2
);