]> git.proxmox.com Git - grub2.git/blob - grub-core/commands/test.c
verifiers: File type for fine-grained signature-verification controlling
[grub2.git] / 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 /* A simple implementation for signed numbers. */
33 static int
34 grub_strtosl (char *arg, char **end, int base)
35 {
36 if (arg[0] == '-')
37 return -grub_strtoul (arg + 1, end, base);
38 return grub_strtoul (arg, end, base);
39 }
40
41 /* Context for test_parse. */
42 struct test_parse_ctx
43 {
44 int invert;
45 int or, and;
46 int file_exists;
47 struct grub_dirhook_info file_info;
48 char *filename;
49 };
50
51 /* Take care of discarding and inverting. */
52 static void
53 update_val (int val, struct test_parse_ctx *ctx)
54 {
55 ctx->and = ctx->and && (ctx->invert ? ! val : val);
56 ctx->invert = 0;
57 }
58
59 /* A hook for iterating directories. */
60 static int
61 find_file (const char *cur_filename, const struct grub_dirhook_info *info,
62 void *data)
63 {
64 struct test_parse_ctx *ctx = data;
65
66 if ((info->case_insensitive ? grub_strcasecmp (cur_filename, ctx->filename)
67 : grub_strcmp (cur_filename, ctx->filename)) == 0)
68 {
69 ctx->file_info = *info;
70 ctx->file_exists = 1;
71 return 1;
72 }
73 return 0;
74 }
75
76 /* Check if file exists and fetch its information. */
77 static void
78 get_fileinfo (char *path, struct test_parse_ctx *ctx)
79 {
80 char *pathname;
81 char *device_name;
82 grub_fs_t fs;
83 grub_device_t dev;
84
85 ctx->file_exists = 0;
86 device_name = grub_file_get_device_name (path);
87 dev = grub_device_open (device_name);
88 if (! dev)
89 {
90 grub_free (device_name);
91 return;
92 }
93
94 fs = grub_fs_probe (dev);
95 if (! fs)
96 {
97 grub_free (device_name);
98 grub_device_close (dev);
99 return;
100 }
101
102 pathname = grub_strchr (path, ')');
103 if (! pathname)
104 pathname = path;
105 else
106 pathname++;
107
108 /* Remove trailing '/'. */
109 while (*pathname && pathname[grub_strlen (pathname) - 1] == '/')
110 pathname[grub_strlen (pathname) - 1] = 0;
111
112 /* Split into path and filename. */
113 ctx->filename = grub_strrchr (pathname, '/');
114 if (! ctx->filename)
115 {
116 path = grub_strdup ("/");
117 ctx->filename = pathname;
118 }
119 else
120 {
121 ctx->filename++;
122 path = grub_strdup (pathname);
123 path[ctx->filename - pathname] = 0;
124 }
125
126 /* It's the whole device. */
127 if (! *pathname)
128 {
129 ctx->file_exists = 1;
130 grub_memset (&ctx->file_info, 0, sizeof (ctx->file_info));
131 /* Root is always a directory. */
132 ctx->file_info.dir = 1;
133
134 /* Fetch writing time. */
135 ctx->file_info.mtimeset = 0;
136 if (fs->mtime)
137 {
138 if (! fs->mtime (dev, &ctx->file_info.mtime))
139 ctx->file_info.mtimeset = 1;
140 grub_errno = GRUB_ERR_NONE;
141 }
142 }
143 else
144 (fs->dir) (dev, path, find_file, ctx);
145
146 grub_device_close (dev);
147 grub_free (path);
148 grub_free (device_name);
149 }
150
151 /* Parse a test expression starting from *argn. */
152 static int
153 test_parse (char **args, int *argn, int argc)
154 {
155 struct test_parse_ctx ctx = {
156 .and = 1,
157 .or = 0,
158 .invert = 0
159 };
160
161 /* Here we have the real parsing. */
162 while (*argn < argc)
163 {
164 /* First try 3 argument tests. */
165 if (*argn + 2 < argc)
166 {
167 /* String tests. */
168 if (grub_strcmp (args[*argn + 1], "=") == 0
169 || grub_strcmp (args[*argn + 1], "==") == 0)
170 {
171 update_val (grub_strcmp (args[*argn], args[*argn + 2]) == 0,
172 &ctx);
173 (*argn) += 3;
174 continue;
175 }
176
177 if (grub_strcmp (args[*argn + 1], "!=") == 0)
178 {
179 update_val (grub_strcmp (args[*argn], args[*argn + 2]) != 0,
180 &ctx);
181 (*argn) += 3;
182 continue;
183 }
184
185 /* GRUB extension: lexicographical sorting. */
186 if (grub_strcmp (args[*argn + 1], "<") == 0)
187 {
188 update_val (grub_strcmp (args[*argn], args[*argn + 2]) < 0,
189 &ctx);
190 (*argn) += 3;
191 continue;
192 }
193
194 if (grub_strcmp (args[*argn + 1], "<=") == 0)
195 {
196 update_val (grub_strcmp (args[*argn], args[*argn + 2]) <= 0,
197 &ctx);
198 (*argn) += 3;
199 continue;
200 }
201
202 if (grub_strcmp (args[*argn + 1], ">") == 0)
203 {
204 update_val (grub_strcmp (args[*argn], args[*argn + 2]) > 0,
205 &ctx);
206 (*argn) += 3;
207 continue;
208 }
209
210 if (grub_strcmp (args[*argn + 1], ">=") == 0)
211 {
212 update_val (grub_strcmp (args[*argn], args[*argn + 2]) >= 0,
213 &ctx);
214 (*argn) += 3;
215 continue;
216 }
217
218 /* Number tests. */
219 if (grub_strcmp (args[*argn + 1], "-eq") == 0)
220 {
221 update_val (grub_strtosl (args[*argn], 0, 0)
222 == grub_strtosl (args[*argn + 2], 0, 0), &ctx);
223 (*argn) += 3;
224 continue;
225 }
226
227 if (grub_strcmp (args[*argn + 1], "-ge") == 0)
228 {
229 update_val (grub_strtosl (args[*argn], 0, 0)
230 >= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
231 (*argn) += 3;
232 continue;
233 }
234
235 if (grub_strcmp (args[*argn + 1], "-gt") == 0)
236 {
237 update_val (grub_strtosl (args[*argn], 0, 0)
238 > grub_strtosl (args[*argn + 2], 0, 0), &ctx);
239 (*argn) += 3;
240 continue;
241 }
242
243 if (grub_strcmp (args[*argn + 1], "-le") == 0)
244 {
245 update_val (grub_strtosl (args[*argn], 0, 0)
246 <= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
247 (*argn) += 3;
248 continue;
249 }
250
251 if (grub_strcmp (args[*argn + 1], "-lt") == 0)
252 {
253 update_val (grub_strtosl (args[*argn], 0, 0)
254 < grub_strtosl (args[*argn + 2], 0, 0), &ctx);
255 (*argn) += 3;
256 continue;
257 }
258
259 if (grub_strcmp (args[*argn + 1], "-ne") == 0)
260 {
261 update_val (grub_strtosl (args[*argn], 0, 0)
262 != grub_strtosl (args[*argn + 2], 0, 0), &ctx);
263 (*argn) += 3;
264 continue;
265 }
266
267 /* GRUB extension: compare numbers skipping prefixes.
268 Useful for comparing versions. E.g. vmlinuz-2 -plt vmlinuz-11. */
269 if (grub_strcmp (args[*argn + 1], "-pgt") == 0
270 || grub_strcmp (args[*argn + 1], "-plt") == 0)
271 {
272 int i;
273 /* Skip common prefix. */
274 for (i = 0; args[*argn][i] == args[*argn + 2][i]
275 && args[*argn][i]; i++);
276
277 /* Go the digits back. */
278 i--;
279 while (grub_isdigit (args[*argn][i]) && i > 0)
280 i--;
281 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 (*argn) += 3;
290 continue;
291 }
292
293 /* -nt and -ot tests. GRUB extension: when doing -?t<bias> bias
294 will be added to the first mtime. */
295 if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0
296 || grub_memcmp (args[*argn + 1], "-ot", 3) == 0)
297 {
298 struct grub_dirhook_info file1;
299 int file1exists;
300 int bias = 0;
301
302 /* Fetch fileinfo. */
303 get_fileinfo (args[*argn], &ctx);
304 file1 = ctx.file_info;
305 file1exists = ctx.file_exists;
306 get_fileinfo (args[*argn + 2], &ctx);
307
308 if (args[*argn + 1][3])
309 bias = grub_strtosl (args[*argn + 1] + 3, 0, 0);
310
311 if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0)
312 update_val ((file1exists && ! ctx.file_exists)
313 || (file1.mtimeset && ctx.file_info.mtimeset
314 && file1.mtime + bias > ctx.file_info.mtime),
315 &ctx);
316 else
317 update_val ((! file1exists && ctx.file_exists)
318 || (file1.mtimeset && ctx.file_info.mtimeset
319 && file1.mtime + bias < ctx.file_info.mtime),
320 &ctx);
321 (*argn) += 3;
322 continue;
323 }
324 }
325
326 /* Two-argument tests. */
327 if (*argn + 1 < argc)
328 {
329 /* File tests. */
330 if (grub_strcmp (args[*argn], "-d") == 0)
331 {
332 get_fileinfo (args[*argn + 1], &ctx);
333 update_val (ctx.file_exists && ctx.file_info.dir, &ctx);
334 (*argn) += 2;
335 continue;
336 }
337
338 if (grub_strcmp (args[*argn], "-e") == 0)
339 {
340 get_fileinfo (args[*argn + 1], &ctx);
341 update_val (ctx.file_exists, &ctx);
342 (*argn) += 2;
343 continue;
344 }
345
346 if (grub_strcmp (args[*argn], "-f") == 0)
347 {
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 if (grub_strcmp (args[*argn], "-s") == 0)
356 {
357 grub_file_t file;
358 file = grub_file_open (args[*argn + 1], GRUB_FILE_TYPE_GET_SIZE
359 | GRUB_FILE_TYPE_NO_DECOMPRESS);
360 update_val (file && (grub_file_size (file) != 0), &ctx);
361 if (file)
362 grub_file_close (file);
363 grub_errno = GRUB_ERR_NONE;
364 (*argn) += 2;
365 continue;
366 }
367
368 /* String tests. */
369 if (grub_strcmp (args[*argn], "-n") == 0)
370 {
371 update_val (args[*argn + 1][0], &ctx);
372
373 (*argn) += 2;
374 continue;
375 }
376 if (grub_strcmp (args[*argn], "-z") == 0)
377 {
378 update_val (! args[*argn + 1][0], &ctx);
379 (*argn) += 2;
380 continue;
381 }
382 }
383
384 /* Special modifiers. */
385
386 /* End of expression. return to parent. */
387 if (grub_strcmp (args[*argn], ")") == 0)
388 {
389 (*argn)++;
390 return ctx.or || ctx.and;
391 }
392 /* Recursively invoke if parenthesis. */
393 if (grub_strcmp (args[*argn], "(") == 0)
394 {
395 (*argn)++;
396 update_val (test_parse (args, argn, argc), &ctx);
397 continue;
398 }
399
400 if (grub_strcmp (args[*argn], "!") == 0)
401 {
402 ctx.invert = ! ctx.invert;
403 (*argn)++;
404 continue;
405 }
406 if (grub_strcmp (args[*argn], "-a") == 0)
407 {
408 (*argn)++;
409 continue;
410 }
411 if (grub_strcmp (args[*argn], "-o") == 0)
412 {
413 ctx.or = ctx.or || ctx.and;
414 ctx.and = 1;
415 (*argn)++;
416 continue;
417 }
418
419 /* No test found. Interpret if as just a string. */
420 update_val (args[*argn][0], &ctx);
421 (*argn)++;
422 }
423 return ctx.or || ctx.and;
424 }
425
426 static grub_err_t
427 grub_cmd_test (grub_command_t cmd __attribute__ ((unused)),
428 int argc, char **args)
429 {
430 int argn = 0;
431
432 if (argc >= 1 && grub_strcmp (args[argc - 1], "]") == 0)
433 argc--;
434
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 \f
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 grub_unregister_command (cmd_1);
454 grub_unregister_command (cmd_2);
455 }