]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - GRUB2/MOD_SRC/grub-2.04/grub-core/commands/test.c
6f1900d27ad6299fd61e7e7746ccc41b0e40c4a4
[Ventoy.git] / GRUB2 / MOD_SRC / grub-2.04 / grub-core / commands / test.c
1 /* test.c -- The test command.. */
2 /*
3 * 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 * (at your option) any later version.
10 *
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.
15 *
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/>.
18 */
19
20 #include <grub/dl.h>
21 #include <grub/misc.h>
22 #include <grub/mm.h>
23 #include <grub/env.h>
24 #include <grub/fs.h>
25 #include <grub/device.h>
26 #include <grub/file.h>
27 #include <grub/command.h>
28 #include <grub/i18n.h>
29
30 GRUB_MOD_LICENSE ("GPLv3+");
31
32 static int g_test_case_insensitive = 0;
33
34 /* A simple implementation for signed numbers. */
35 static int
36 grub_strtosl (char *arg, char **end, int base)
37 {
38 if (arg[0] == '-')
39 return -grub_strtoul (arg + 1, end, base);
40 return grub_strtoul (arg, end, base);
41 }
42
43 /* Context for test_parse. */
44 struct test_parse_ctx
45 {
46 int invert;
47 int or, and;
48 int file_exists;
49 struct grub_dirhook_info file_info;
50 char *filename;
51 };
52
53 /* Take care of discarding and inverting. */
54 static void
55 update_val (int val, struct test_parse_ctx *ctx)
56 {
57 ctx->and = ctx->and && (ctx->invert ? ! val : val);
58 ctx->invert = 0;
59 }
60
61 /* A hook for iterating directories. */
62 static int
63 find_file (const char *cur_filename, const struct grub_dirhook_info *info,
64 void *data)
65 {
66 int case_insensitive = 0;
67 struct test_parse_ctx *ctx = data;
68
69 if (g_test_case_insensitive || info->case_insensitive)
70 case_insensitive = 1;
71
72 if ((case_insensitive ? grub_strcasecmp (cur_filename, ctx->filename)
73 : grub_strcmp (cur_filename, ctx->filename)) == 0)
74 {
75 ctx->file_info = *info;
76 ctx->file_exists = 1;
77 return 1;
78 }
79 return 0;
80 }
81
82 /* Check if file exists and fetch its information. */
83 static void
84 get_fileinfo (char *path, struct test_parse_ctx *ctx)
85 {
86 char *pathname;
87 char *device_name;
88 grub_fs_t fs;
89 grub_device_t dev;
90
91 ctx->file_exists = 0;
92 device_name = grub_file_get_device_name (path);
93 dev = grub_device_open (device_name);
94 if (! dev)
95 {
96 grub_free (device_name);
97 return;
98 }
99
100 fs = grub_fs_probe (dev);
101 if (! fs)
102 {
103 grub_free (device_name);
104 grub_device_close (dev);
105 return;
106 }
107
108 pathname = grub_strchr (path, ')');
109 if (! pathname)
110 pathname = path;
111 else
112 pathname++;
113
114 /* Remove trailing '/'. */
115 while (*pathname && pathname[grub_strlen (pathname) - 1] == '/')
116 pathname[grub_strlen (pathname) - 1] = 0;
117
118 /* Split into path and filename. */
119 ctx->filename = grub_strrchr (pathname, '/');
120 if (! ctx->filename)
121 {
122 path = grub_strdup ("/");
123 ctx->filename = pathname;
124 }
125 else
126 {
127 ctx->filename++;
128 path = grub_strdup (pathname);
129 path[ctx->filename - pathname] = 0;
130 }
131
132 /* It's the whole device. */
133 if (! *pathname)
134 {
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;
139
140 /* Fetch writing time. */
141 ctx->file_info.mtimeset = 0;
142 if (fs->fs_mtime)
143 {
144 if (! fs->fs_mtime (dev, &ctx->file_info.mtime))
145 ctx->file_info.mtimeset = 1;
146 grub_errno = GRUB_ERR_NONE;
147 }
148 }
149 else
150 (fs->fs_dir) (dev, path, find_file, ctx);
151
152 grub_device_close (dev);
153 grub_free (path);
154 grub_free (device_name);
155 }
156
157 /* Parse a test expression starting from *argn. */
158 static int
159 test_parse (char **args, int *argn, int argc)
160 {
161 struct test_parse_ctx ctx = {
162 .and = 1,
163 .or = 0,
164 .invert = 0
165 };
166
167 /* Here we have the real parsing. */
168 while (*argn < argc)
169 {
170 /* First try 3 argument tests. */
171 if (*argn + 2 < argc)
172 {
173 /* String tests. */
174 if (grub_strcmp (args[*argn + 1], "=") == 0
175 || grub_strcmp (args[*argn + 1], "==") == 0)
176 {
177 update_val (grub_strcmp (args[*argn], args[*argn + 2]) == 0,
178 &ctx);
179 (*argn) += 3;
180 continue;
181 }
182
183 if (grub_strcmp (args[*argn + 1], "!=") == 0)
184 {
185 update_val (grub_strcmp (args[*argn], args[*argn + 2]) != 0,
186 &ctx);
187 (*argn) += 3;
188 continue;
189 }
190
191 /* GRUB extension: lexicographical sorting. */
192 if (grub_strcmp (args[*argn + 1], "<") == 0)
193 {
194 update_val (grub_strcmp (args[*argn], args[*argn + 2]) < 0,
195 &ctx);
196 (*argn) += 3;
197 continue;
198 }
199
200 if (grub_strcmp (args[*argn + 1], "<=") == 0)
201 {
202 update_val (grub_strcmp (args[*argn], args[*argn + 2]) <= 0,
203 &ctx);
204 (*argn) += 3;
205 continue;
206 }
207
208 if (grub_strcmp (args[*argn + 1], ">") == 0)
209 {
210 update_val (grub_strcmp (args[*argn], args[*argn + 2]) > 0,
211 &ctx);
212 (*argn) += 3;
213 continue;
214 }
215
216 if (grub_strcmp (args[*argn + 1], ">=") == 0)
217 {
218 update_val (grub_strcmp (args[*argn], args[*argn + 2]) >= 0,
219 &ctx);
220 (*argn) += 3;
221 continue;
222 }
223
224 /* Number tests. */
225 if (grub_strcmp (args[*argn + 1], "-eq") == 0)
226 {
227 update_val (grub_strtosl (args[*argn], 0, 0)
228 == grub_strtosl (args[*argn + 2], 0, 0), &ctx);
229 (*argn) += 3;
230 continue;
231 }
232
233 if (grub_strcmp (args[*argn + 1], "-ge") == 0)
234 {
235 update_val (grub_strtosl (args[*argn], 0, 0)
236 >= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
237 (*argn) += 3;
238 continue;
239 }
240
241 if (grub_strcmp (args[*argn + 1], "-gt") == 0)
242 {
243 update_val (grub_strtosl (args[*argn], 0, 0)
244 > grub_strtosl (args[*argn + 2], 0, 0), &ctx);
245 (*argn) += 3;
246 continue;
247 }
248
249 if (grub_strcmp (args[*argn + 1], "-le") == 0)
250 {
251 update_val (grub_strtosl (args[*argn], 0, 0)
252 <= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
253 (*argn) += 3;
254 continue;
255 }
256
257 if (grub_strcmp (args[*argn + 1], "-lt") == 0)
258 {
259 update_val (grub_strtosl (args[*argn], 0, 0)
260 < grub_strtosl (args[*argn + 2], 0, 0), &ctx);
261 (*argn) += 3;
262 continue;
263 }
264
265 if (grub_strcmp (args[*argn + 1], "-ne") == 0)
266 {
267 update_val (grub_strtosl (args[*argn], 0, 0)
268 != grub_strtosl (args[*argn + 2], 0, 0), &ctx);
269 (*argn) += 3;
270 continue;
271 }
272
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)
277 {
278 int i;
279 /* Skip common prefix. */
280 for (i = 0; args[*argn][i] == args[*argn + 2][i]
281 && args[*argn][i]; i++);
282
283 /* Go the digits back. */
284 i--;
285 while (grub_isdigit (args[*argn][i]) && i > 0)
286 i--;
287 i++;
288
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);
292 else
293 update_val (grub_strtoul (args[*argn] + i, 0, 0)
294 < grub_strtoul (args[*argn + 2] + i, 0, 0), &ctx);
295 (*argn) += 3;
296 continue;
297 }
298
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)
303 {
304 struct grub_dirhook_info file1;
305 int file1exists;
306 int bias = 0;
307
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);
313
314 if (args[*argn + 1][3])
315 bias = grub_strtosl (args[*argn + 1] + 3, 0, 0);
316
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),
321 &ctx);
322 else
323 update_val ((! file1exists && ctx.file_exists)
324 || (file1.mtimeset && ctx.file_info.mtimeset
325 && file1.mtime + bias < ctx.file_info.mtime),
326 &ctx);
327 (*argn) += 3;
328 continue;
329 }
330 }
331
332 /* Two-argument tests. */
333 if (*argn + 1 < argc)
334 {
335 /* File tests. */
336 if (grub_strcmp (args[*argn], "-d") == 0)
337 {
338 get_fileinfo (args[*argn + 1], &ctx);
339 update_val (ctx.file_exists && ctx.file_info.dir, &ctx);
340 (*argn) += 2;
341 continue;
342 }
343
344 if (grub_strcmp (args[*argn], "-D") == 0)
345 {
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);
350 (*argn) += 2;
351 continue;
352 }
353
354 if (grub_strcmp (args[*argn], "-e") == 0)
355 {
356 get_fileinfo (args[*argn + 1], &ctx);
357 update_val (ctx.file_exists, &ctx);
358 (*argn) += 2;
359 continue;
360 }
361
362 if (grub_strcmp (args[*argn], "-E") == 0)
363 {
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);
368 (*argn) += 2;
369 continue;
370 }
371
372 if (grub_strcmp (args[*argn], "-f") == 0)
373 {
374 get_fileinfo (args[*argn + 1], &ctx);
375 /* FIXME: check for other types. */
376 update_val (ctx.file_exists && ! ctx.file_info.dir, &ctx);
377 (*argn) += 2;
378 continue;
379 }
380 if (grub_strcmp (args[*argn], "-F") == 0)
381 {
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);
387 (*argn) += 2;
388 continue;
389 }
390
391 if (grub_strcmp (args[*argn], "-s") == 0)
392 {
393 grub_file_t file;
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);
397 if (file)
398 grub_file_close (file);
399 grub_errno = GRUB_ERR_NONE;
400 (*argn) += 2;
401 continue;
402 }
403
404 /* String tests. */
405 if (grub_strcmp (args[*argn], "-n") == 0)
406 {
407 update_val (args[*argn + 1][0], &ctx);
408
409 (*argn) += 2;
410 continue;
411 }
412 if (grub_strcmp (args[*argn], "-z") == 0)
413 {
414 update_val (! args[*argn + 1][0], &ctx);
415 (*argn) += 2;
416 continue;
417 }
418 }
419
420 /* Special modifiers. */
421
422 /* End of expression. return to parent. */
423 if (grub_strcmp (args[*argn], ")") == 0)
424 {
425 (*argn)++;
426 return ctx.or || ctx.and;
427 }
428 /* Recursively invoke if parenthesis. */
429 if (grub_strcmp (args[*argn], "(") == 0)
430 {
431 (*argn)++;
432 update_val (test_parse (args, argn, argc), &ctx);
433 continue;
434 }
435
436 if (grub_strcmp (args[*argn], "!") == 0)
437 {
438 ctx.invert = ! ctx.invert;
439 (*argn)++;
440 continue;
441 }
442 if (grub_strcmp (args[*argn], "-a") == 0)
443 {
444 (*argn)++;
445 continue;
446 }
447 if (grub_strcmp (args[*argn], "-o") == 0)
448 {
449 ctx.or = ctx.or || ctx.and;
450 ctx.and = 1;
451 (*argn)++;
452 continue;
453 }
454
455 /* No test found. Interpret if as just a string. */
456 update_val (args[*argn][0], &ctx);
457 (*argn)++;
458 }
459 return ctx.or || ctx.and;
460 }
461
462 static grub_err_t
463 grub_cmd_test (grub_command_t cmd __attribute__ ((unused)),
464 int argc, char **args)
465 {
466 int argn = 0;
467
468 if (argc >= 1 && grub_strcmp (args[argc - 1], "]") == 0)
469 argc--;
470
471 return test_parse (args, &argn, argc) ? GRUB_ERR_NONE
472 : grub_error (GRUB_ERR_TEST_FAILURE, N_("false"));
473 }
474
475 static grub_command_t cmd_1, cmd_2;
476 \f
477 GRUB_MOD_INIT(test)
478 {
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;
485 }
486
487 GRUB_MOD_FINI(test)
488 {
489 grub_unregister_command (cmd_1);
490 grub_unregister_command (cmd_2);
491 }