ca0a4f689a
Let's provide file type info to the I/O layer. This way verifiers framework and its users will be able to differentiate files and verify only required ones. This is preparatory patch. Signed-off-by: Vladimir Serbinenko <phcoder@gmail.com> Signed-off-by: Daniel Kiper <daniel.kiper@oracle.com> Reviewed-by: Ross Philipson <ross.philipson@oracle.com>
455 lines
11 KiB
C
455 lines
11 KiB
C
/* test.c -- The test command.. */
|
||
/*
|
||
* GRUB -- GRand Unified Bootloader
|
||
* Copyright (C) 2005,2007,2009 Free Software Foundation, Inc.
|
||
*
|
||
* GRUB is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation, either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* GRUB is distributed in the hope that it will be useful,
|
||
* 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
|
||
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include <grub/dl.h>
|
||
#include <grub/misc.h>
|
||
#include <grub/mm.h>
|
||
#include <grub/env.h>
|
||
#include <grub/fs.h>
|
||
#include <grub/device.h>
|
||
#include <grub/file.h>
|
||
#include <grub/command.h>
|
||
#include <grub/i18n.h>
|
||
|
||
GRUB_MOD_LICENSE ("GPLv3+");
|
||
|
||
/* 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);
|
||
}
|
||
|
||
/* Context for test_parse. */
|
||
struct test_parse_ctx
|
||
{
|
||
int invert;
|
||
int or, and;
|
||
int file_exists;
|
||
struct grub_dirhook_info file_info;
|
||
char *filename;
|
||
};
|
||
|
||
/* Take care of discarding and inverting. */
|
||
static void
|
||
update_val (int val, struct test_parse_ctx *ctx)
|
||
{
|
||
ctx->and = ctx->and && (ctx->invert ? ! val : val);
|
||
ctx->invert = 0;
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
fs = grub_fs_probe (dev);
|
||
if (! fs)
|
||
{
|
||
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)
|
||
{
|
||
if (! fs->mtime (dev, &ctx->file_info.mtime))
|
||
ctx->file_info.mtimeset = 1;
|
||
grub_errno = GRUB_ERR_NONE;
|
||
}
|
||
}
|
||
else
|
||
(fs->dir) (dev, path, find_file, ctx);
|
||
|
||
grub_device_close (dev);
|
||
grub_free (path);
|
||
grub_free (device_name);
|
||
}
|
||
|
||
/* Parse a test expression starting from *argn. */
|
||
static int
|
||
test_parse (char **args, int *argn, int argc)
|
||
{
|
||
struct test_parse_ctx ctx = {
|
||
.and = 1,
|
||
.or = 0,
|
||
.invert = 0
|
||
};
|
||
|
||
/* 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)
|
||
{
|
||
update_val (grub_strcmp (args[*argn], args[*argn + 2]) == 0,
|
||
&ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], "!=") == 0)
|
||
{
|
||
update_val (grub_strcmp (args[*argn], args[*argn + 2]) != 0,
|
||
&ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
/* GRUB extension: lexicographical sorting. */
|
||
if (grub_strcmp (args[*argn + 1], "<") == 0)
|
||
{
|
||
update_val (grub_strcmp (args[*argn], args[*argn + 2]) < 0,
|
||
&ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], "<=") == 0)
|
||
{
|
||
update_val (grub_strcmp (args[*argn], args[*argn + 2]) <= 0,
|
||
&ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], ">") == 0)
|
||
{
|
||
update_val (grub_strcmp (args[*argn], args[*argn + 2]) > 0,
|
||
&ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], ">=") == 0)
|
||
{
|
||
update_val (grub_strcmp (args[*argn], args[*argn + 2]) >= 0,
|
||
&ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
/* Number tests. */
|
||
if (grub_strcmp (args[*argn + 1], "-eq") == 0)
|
||
{
|
||
update_val (grub_strtosl (args[*argn], 0, 0)
|
||
== grub_strtosl (args[*argn + 2], 0, 0), &ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], "-ge") == 0)
|
||
{
|
||
update_val (grub_strtosl (args[*argn], 0, 0)
|
||
>= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], "-gt") == 0)
|
||
{
|
||
update_val (grub_strtosl (args[*argn], 0, 0)
|
||
> grub_strtosl (args[*argn + 2], 0, 0), &ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], "-le") == 0)
|
||
{
|
||
update_val (grub_strtosl (args[*argn], 0, 0)
|
||
<= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], "-lt") == 0)
|
||
{
|
||
update_val (grub_strtosl (args[*argn], 0, 0)
|
||
< grub_strtosl (args[*argn + 2], 0, 0), &ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn + 1], "-ne") == 0)
|
||
{
|
||
update_val (grub_strtosl (args[*argn], 0, 0)
|
||
!= grub_strtosl (args[*argn + 2], 0, 0), &ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
/* GRUB extension: compare numbers skipping prefixes.
|
||
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. */
|
||
for (i = 0; args[*argn][i] == args[*argn + 2][i]
|
||
&& args[*argn][i]; i++);
|
||
|
||
/* Go the digits back. */
|
||
i--;
|
||
while (grub_isdigit (args[*argn][i]) && i > 0)
|
||
i--;
|
||
i++;
|
||
|
||
if (grub_strcmp (args[*argn + 1], "-pgt") == 0)
|
||
update_val (grub_strtoul (args[*argn] + i, 0, 0)
|
||
> grub_strtoul (args[*argn + 2] + i, 0, 0), &ctx);
|
||
else
|
||
update_val (grub_strtoul (args[*argn] + i, 0, 0)
|
||
< grub_strtoul (args[*argn + 2] + i, 0, 0), &ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
|
||
/* -nt and -ot tests. GRUB extension: when doing -?t<bias> bias
|
||
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;
|
||
|
||
/* Fetch fileinfo. */
|
||
get_fileinfo (args[*argn], &ctx);
|
||
file1 = ctx.file_info;
|
||
file1exists = ctx.file_exists;
|
||
get_fileinfo (args[*argn + 2], &ctx);
|
||
|
||
if (args[*argn + 1][3])
|
||
bias = grub_strtosl (args[*argn + 1] + 3, 0, 0);
|
||
|
||
if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0)
|
||
update_val ((file1exists && ! ctx.file_exists)
|
||
|| (file1.mtimeset && ctx.file_info.mtimeset
|
||
&& file1.mtime + bias > ctx.file_info.mtime),
|
||
&ctx);
|
||
else
|
||
update_val ((! file1exists && ctx.file_exists)
|
||
|| (file1.mtimeset && ctx.file_info.mtimeset
|
||
&& file1.mtime + bias < ctx.file_info.mtime),
|
||
&ctx);
|
||
(*argn) += 3;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
/* Two-argument tests. */
|
||
if (*argn + 1 < argc)
|
||
{
|
||
/* File tests. */
|
||
if (grub_strcmp (args[*argn], "-d") == 0)
|
||
{
|
||
get_fileinfo (args[*argn + 1], &ctx);
|
||
update_val (ctx.file_exists && ctx.file_info.dir, &ctx);
|
||
(*argn) += 2;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn], "-e") == 0)
|
||
{
|
||
get_fileinfo (args[*argn + 1], &ctx);
|
||
update_val (ctx.file_exists, &ctx);
|
||
(*argn) += 2;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn], "-f") == 0)
|
||
{
|
||
get_fileinfo (args[*argn + 1], &ctx);
|
||
/* FIXME: check for other types. */
|
||
update_val (ctx.file_exists && ! ctx.file_info.dir, &ctx);
|
||
(*argn) += 2;
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn], "-s") == 0)
|
||
{
|
||
grub_file_t file;
|
||
file = grub_file_open (args[*argn + 1], GRUB_FILE_TYPE_GET_SIZE
|
||
| GRUB_FILE_TYPE_NO_DECOMPRESS);
|
||
update_val (file && (grub_file_size (file) != 0), &ctx);
|
||
if (file)
|
||
grub_file_close (file);
|
||
grub_errno = GRUB_ERR_NONE;
|
||
(*argn) += 2;
|
||
continue;
|
||
}
|
||
|
||
/* String tests. */
|
||
if (grub_strcmp (args[*argn], "-n") == 0)
|
||
{
|
||
update_val (args[*argn + 1][0], &ctx);
|
||
|
||
(*argn) += 2;
|
||
continue;
|
||
}
|
||
if (grub_strcmp (args[*argn], "-z") == 0)
|
||
{
|
||
update_val (! args[*argn + 1][0], &ctx);
|
||
(*argn) += 2;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
/* Special modifiers. */
|
||
|
||
/* End of expression. return to parent. */
|
||
if (grub_strcmp (args[*argn], ")") == 0)
|
||
{
|
||
(*argn)++;
|
||
return ctx.or || ctx.and;
|
||
}
|
||
/* Recursively invoke if parenthesis. */
|
||
if (grub_strcmp (args[*argn], "(") == 0)
|
||
{
|
||
(*argn)++;
|
||
update_val (test_parse (args, argn, argc), &ctx);
|
||
continue;
|
||
}
|
||
|
||
if (grub_strcmp (args[*argn], "!") == 0)
|
||
{
|
||
ctx.invert = ! ctx.invert;
|
||
(*argn)++;
|
||
continue;
|
||
}
|
||
if (grub_strcmp (args[*argn], "-a") == 0)
|
||
{
|
||
(*argn)++;
|
||
continue;
|
||
}
|
||
if (grub_strcmp (args[*argn], "-o") == 0)
|
||
{
|
||
ctx.or = ctx.or || ctx.and;
|
||
ctx.and = 1;
|
||
(*argn)++;
|
||
continue;
|
||
}
|
||
|
||
/* No test found. Interpret if as just a string. */
|
||
update_val (args[*argn][0], &ctx);
|
||
(*argn)++;
|
||
}
|
||
return ctx.or || ctx.and;
|
||
}
|
||
|
||
static grub_err_t
|
||
grub_cmd_test (grub_command_t cmd __attribute__ ((unused)),
|
||
int argc, char **args)
|
||
{
|
||
int argn = 0;
|
||
|
||
if (argc >= 1 && grub_strcmp (args[argc - 1], "]") == 0)
|
||
argc--;
|
||
|
||
return test_parse (args, &argn, argc) ? GRUB_ERR_NONE
|
||
: grub_error (GRUB_ERR_TEST_FAILURE, N_("false"));
|
||
}
|
||
|
||
static grub_command_t cmd_1, cmd_2;
|
||
|
||
GRUB_MOD_INIT(test)
|
||
{
|
||
cmd_1 = grub_register_command ("[", grub_cmd_test,
|
||
N_("EXPRESSION ]"), N_("Evaluate an expression."));
|
||
cmd_1->flags |= GRUB_COMMAND_FLAG_EXTRACTOR;
|
||
cmd_2 = grub_register_command ("test", grub_cmd_test,
|
||
N_("EXPRESSION"), N_("Evaluate an expression."));
|
||
cmd_2->flags |= GRUB_COMMAND_FLAG_EXTRACTOR;
|
||
}
|
||
|
||
GRUB_MOD_FINI(test)
|
||
{
|
||
grub_unregister_command (cmd_1);
|
||
grub_unregister_command (cmd_2);
|
||
}
|