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
);
233 if (grub_strcmp (args
[*argn
+ 1], "-ge") == 0)
235 update_val (grub_strtosl (args
[*argn
], 0, 0)
236 >= grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
241 if (grub_strcmp (args
[*argn
+ 1], "-gt") == 0)
243 update_val (grub_strtosl (args
[*argn
], 0, 0)
244 > grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
249 if (grub_strcmp (args
[*argn
+ 1], "-le") == 0)
251 update_val (grub_strtosl (args
[*argn
], 0, 0)
252 <= grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
257 if (grub_strcmp (args
[*argn
+ 1], "-lt") == 0)
259 update_val (grub_strtosl (args
[*argn
], 0, 0)
260 < grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
265 if (grub_strcmp (args
[*argn
+ 1], "-ne") == 0)
267 update_val (grub_strtosl (args
[*argn
], 0, 0)
268 != grub_strtosl (args
[*argn
+ 2], 0, 0), &ctx
);
273 /* GRUB extension: compare numbers skipping prefixes.
274 Useful for comparing versions. E.g. vmlinuz-2 -plt vmlinuz-11. */
275 if (grub_strcmp (args
[*argn
+ 1], "-pgt") == 0
276 || grub_strcmp (args
[*argn
+ 1], "-plt") == 0)
279 /* Skip common prefix. */
280 for (i
= 0; args
[*argn
][i
] == args
[*argn
+ 2][i
]
281 && args
[*argn
][i
]; i
++);
283 /* Go the digits back. */
285 while (grub_isdigit (args
[*argn
][i
]) && i
> 0)
289 if (grub_strcmp (args
[*argn
+ 1], "-pgt") == 0)
290 update_val (grub_strtoul (args
[*argn
] + i
, 0, 0)
291 > grub_strtoul (args
[*argn
+ 2] + i
, 0, 0), &ctx
);
293 update_val (grub_strtoul (args
[*argn
] + i
, 0, 0)
294 < grub_strtoul (args
[*argn
+ 2] + i
, 0, 0), &ctx
);
299 /* -nt and -ot tests. GRUB extension: when doing -?t<bias> bias
300 will be added to the first mtime. */
301 if (grub_memcmp (args
[*argn
+ 1], "-nt", 3) == 0
302 || grub_memcmp (args
[*argn
+ 1], "-ot", 3) == 0)
304 struct grub_dirhook_info file1
;
308 /* Fetch fileinfo. */
309 get_fileinfo (args
[*argn
], &ctx
);
310 file1
= ctx
.file_info
;
311 file1exists
= ctx
.file_exists
;
312 get_fileinfo (args
[*argn
+ 2], &ctx
);
314 if (args
[*argn
+ 1][3])
315 bias
= grub_strtosl (args
[*argn
+ 1] + 3, 0, 0);
317 if (grub_memcmp (args
[*argn
+ 1], "-nt", 3) == 0)
318 update_val ((file1exists
&& ! ctx
.file_exists
)
319 || (file1
.mtimeset
&& ctx
.file_info
.mtimeset
320 && file1
.mtime
+ bias
> ctx
.file_info
.mtime
),
323 update_val ((! file1exists
&& ctx
.file_exists
)
324 || (file1
.mtimeset
&& ctx
.file_info
.mtimeset
325 && file1
.mtime
+ bias
< ctx
.file_info
.mtime
),
332 /* Two-argument tests. */
333 if (*argn
+ 1 < argc
)
336 if (grub_strcmp (args
[*argn
], "-d") == 0)
338 get_fileinfo (args
[*argn
+ 1], &ctx
);
339 update_val (ctx
.file_exists
&& ctx
.file_info
.dir
, &ctx
);
344 if (grub_strcmp (args
[*argn
], "-D") == 0)
346 g_test_case_insensitive
= 1;
347 get_fileinfo (args
[*argn
+ 1], &ctx
);
348 g_test_case_insensitive
= 0;
349 update_val (ctx
.file_exists
&& ctx
.file_info
.dir
, &ctx
);
354 if (grub_strcmp (args
[*argn
], "-e") == 0)
356 get_fileinfo (args
[*argn
+ 1], &ctx
);
357 update_val (ctx
.file_exists
, &ctx
);
362 if (grub_strcmp (args
[*argn
], "-E") == 0)
364 g_test_case_insensitive
= 1;
365 get_fileinfo (args
[*argn
+ 1], &ctx
);
366 g_test_case_insensitive
= 0;
367 update_val (ctx
.file_exists
, &ctx
);
372 if (grub_strcmp (args
[*argn
], "-f") == 0)
374 get_fileinfo (args
[*argn
+ 1], &ctx
);
375 /* FIXME: check for other types. */
376 update_val (ctx
.file_exists
&& ! ctx
.file_info
.dir
, &ctx
);
380 if (grub_strcmp (args
[*argn
], "-F") == 0)
382 g_test_case_insensitive
= 1;
383 get_fileinfo (args
[*argn
+ 1], &ctx
);
384 g_test_case_insensitive
= 0;
385 /* FIXME: check for other types. */
386 update_val (ctx
.file_exists
&& ! ctx
.file_info
.dir
, &ctx
);
391 if (grub_strcmp (args
[*argn
], "-s") == 0)
394 file
= grub_file_open (args
[*argn
+ 1], GRUB_FILE_TYPE_GET_SIZE
395 | GRUB_FILE_TYPE_NO_DECOMPRESS
);
396 update_val (file
&& (grub_file_size (file
) != 0), &ctx
);
398 grub_file_close (file
);
399 grub_errno
= GRUB_ERR_NONE
;
405 if (grub_strcmp (args
[*argn
], "-n") == 0)
407 update_val (args
[*argn
+ 1][0], &ctx
);
412 if (grub_strcmp (args
[*argn
], "-z") == 0)
414 update_val (! args
[*argn
+ 1][0], &ctx
);
420 /* Special modifiers. */
422 /* End of expression. return to parent. */
423 if (grub_strcmp (args
[*argn
], ")") == 0)
426 return ctx
.or || ctx
.and;
428 /* Recursively invoke if parenthesis. */
429 if (grub_strcmp (args
[*argn
], "(") == 0)
432 update_val (test_parse (args
, argn
, argc
), &ctx
);
436 if (grub_strcmp (args
[*argn
], "!") == 0)
438 ctx
.invert
= ! ctx
.invert
;
442 if (grub_strcmp (args
[*argn
], "-a") == 0)
447 if (grub_strcmp (args
[*argn
], "-o") == 0)
449 ctx
.or = ctx
.or || ctx
.and;
455 /* No test found. Interpret if as just a string. */
456 update_val (args
[*argn
][0], &ctx
);
459 return ctx
.or || ctx
.and;
463 grub_cmd_test (grub_command_t cmd
__attribute__ ((unused
)),
464 int argc
, char **args
)
468 if (argc
>= 1 && grub_strcmp (args
[argc
- 1], "]") == 0)
471 return test_parse (args
, &argn
, argc
) ? GRUB_ERR_NONE
472 : grub_error (GRUB_ERR_TEST_FAILURE
, N_("false"));
475 static grub_command_t cmd_1
, cmd_2
;
479 cmd_1
= grub_register_command ("[", grub_cmd_test
,
480 N_("EXPRESSION ]"), N_("Evaluate an expression."));
481 cmd_1
->flags
|= GRUB_COMMAND_FLAG_EXTRACTOR
;
482 cmd_2
= grub_register_command ("test", grub_cmd_test
,
483 N_("EXPRESSION"), N_("Evaluate an expression."));
484 cmd_2
->flags
|= GRUB_COMMAND_FLAG_EXTRACTOR
;
489 grub_unregister_command (cmd_1
);
490 grub_unregister_command (cmd_2
);