2N/A/* test.c -- The test command.. */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2005,2007,2009 Free Software Foundation, Inc.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/dl.h>
2N/A#include <grub/misc.h>
2N/A#include <grub/mm.h>
2N/A#include <grub/env.h>
2N/A#include <grub/fs.h>
2N/A#include <grub/device.h>
2N/A#include <grub/file.h>
2N/A#include <grub/command.h>
2N/A#include <grub/i18n.h>
2N/A
2N/AGRUB_MOD_LICENSE ("GPLv3+");
2N/A
2N/A/* A simple implementation for signed numbers. */
2N/Astatic int
2N/Agrub_strtosl (char *arg, char **end, int base)
2N/A{
2N/A if (arg[0] == '-')
2N/A return -grub_strtoul (arg + 1, end, base);
2N/A return grub_strtoul (arg, end, base);
2N/A}
2N/A
2N/A/* Parse a test expression starting from *argn. */
2N/Astatic int
2N/Atest_parse (char **args, int *argn, int argc)
2N/A{
2N/A int ret = 0, discard = 0, invert = 0;
2N/A int file_exists;
2N/A struct grub_dirhook_info file_info;
2N/A
2N/A auto void update_val (int val);
2N/A auto void get_fileinfo (char *pathname);
2N/A
2N/A /* Take care of discarding and inverting. */
2N/A void update_val (int val)
2N/A {
2N/A if (! discard)
2N/A ret = invert ? ! val : val;
2N/A invert = discard = 0;
2N/A }
2N/A
2N/A /* Check if file exists and fetch its information. */
2N/A void get_fileinfo (char *path)
2N/A {
2N/A char *filename, *pathname;
2N/A char *device_name;
2N/A grub_fs_t fs;
2N/A grub_device_t dev;
2N/A
2N/A /* A hook for iterating directories. */
2N/A auto int find_file (const char *cur_filename,
2N/A const struct grub_dirhook_info *info);
2N/A int find_file (const char *cur_filename,
2N/A const struct grub_dirhook_info *info)
2N/A {
2N/A if ((info->case_insensitive ? grub_strcasecmp (cur_filename, filename)
2N/A : grub_strcmp (cur_filename, filename)) == 0)
2N/A {
2N/A file_info = *info;
2N/A file_exists = 1;
2N/A return 1;
2N/A }
2N/A return 0;
2N/A }
2N/A
2N/A file_exists = 0;
2N/A device_name = grub_file_get_device_name (path);
2N/A dev = grub_device_open (device_name);
2N/A if (! dev)
2N/A {
2N/A grub_free (device_name);
2N/A return;
2N/A }
2N/A
2N/A fs = grub_fs_probe (dev);
2N/A if (! fs)
2N/A {
2N/A grub_free (device_name);
2N/A grub_device_close (dev);
2N/A return;
2N/A }
2N/A
2N/A pathname = grub_strchr (path, ')');
2N/A if (! pathname)
2N/A pathname = path;
2N/A else
2N/A pathname++;
2N/A
2N/A /* Remove trailing '/'. */
2N/A while (*pathname && pathname[grub_strlen (pathname) - 1] == '/')
2N/A pathname[grub_strlen (pathname) - 1] = 0;
2N/A
2N/A /* Split into path and filename. */
2N/A filename = grub_strrchr (pathname, '/');
2N/A if (! filename)
2N/A {
2N/A path = grub_strdup ("/");
2N/A filename = pathname;
2N/A }
2N/A else
2N/A {
2N/A filename++;
2N/A path = grub_strdup (pathname);
2N/A path[filename - pathname] = 0;
2N/A }
2N/A
2N/A /* It's the whole device. */
2N/A if (! *pathname)
2N/A {
2N/A file_exists = 1;
2N/A grub_memset (&file_info, 0, sizeof (file_info));
2N/A /* Root is always a directory. */
2N/A file_info.dir = 1;
2N/A
2N/A /* Fetch writing time. */
2N/A file_info.mtimeset = 0;
2N/A if (fs->mtime)
2N/A {
2N/A if (! fs->mtime (dev, &file_info.mtime))
2N/A file_info.mtimeset = 1;
2N/A grub_errno = GRUB_ERR_NONE;
2N/A }
2N/A }
2N/A else
2N/A (fs->dir) (dev, path, find_file);
2N/A
2N/A grub_device_close (dev);
2N/A grub_free (path);
2N/A grub_free (device_name);
2N/A }
2N/A
2N/A /* Here we have the real parsing. */
2N/A while (*argn < argc)
2N/A {
2N/A /* First try 3 argument tests. */
2N/A if (*argn + 2 < argc)
2N/A {
2N/A /* String tests. */
2N/A if (grub_strcmp (args[*argn + 1], "=") == 0
2N/A || grub_strcmp (args[*argn + 1], "==") == 0)
2N/A {
2N/A update_val (grub_strcmp (args[*argn], args[*argn + 2]) == 0);
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], "!=") == 0)
2N/A {
2N/A update_val (grub_strcmp (args[*argn], args[*argn + 2]) != 0);
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A /* GRUB extension: lexicographical sorting. */
2N/A if (grub_strcmp (args[*argn + 1], "<") == 0)
2N/A {
2N/A update_val (grub_strcmp (args[*argn], args[*argn + 2]) < 0);
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], "<=") == 0)
2N/A {
2N/A update_val (grub_strcmp (args[*argn], args[*argn + 2]) <= 0);
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], ">") == 0)
2N/A {
2N/A update_val (grub_strcmp (args[*argn], args[*argn + 2]) > 0);
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], ">=") == 0)
2N/A {
2N/A update_val (grub_strcmp (args[*argn], args[*argn + 2]) >= 0);
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A /* Number tests. */
2N/A if (grub_strcmp (args[*argn + 1], "-eq") == 0)
2N/A {
2N/A update_val (grub_strtosl (args[*argn], 0, 0)
2N/A == grub_strtosl (args[*argn + 2], 0, 0));
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], "-ge") == 0)
2N/A {
2N/A update_val (grub_strtosl (args[*argn], 0, 0)
2N/A >= grub_strtosl (args[*argn + 2], 0, 0));
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], "-gt") == 0)
2N/A {
2N/A update_val (grub_strtosl (args[*argn], 0, 0)
2N/A > grub_strtosl (args[*argn + 2], 0, 0));
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], "-le") == 0)
2N/A {
2N/A update_val (grub_strtosl (args[*argn], 0, 0)
2N/A <= grub_strtosl (args[*argn + 2], 0, 0));
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], "-lt") == 0)
2N/A {
2N/A update_val (grub_strtosl (args[*argn], 0, 0)
2N/A < grub_strtosl (args[*argn + 2], 0, 0));
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn + 1], "-ne") == 0)
2N/A {
2N/A update_val (grub_strtosl (args[*argn], 0, 0)
2N/A != grub_strtosl (args[*argn + 2], 0, 0));
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A /* GRUB extension: compare numbers skipping prefixes.
2N/A Useful for comparing versions. E.g. vmlinuz-2 -plt vmlinuz-11. */
2N/A if (grub_strcmp (args[*argn + 1], "-pgt") == 0
2N/A || grub_strcmp (args[*argn + 1], "-plt") == 0)
2N/A {
2N/A int i;
2N/A /* Skip common prefix. */
2N/A for (i = 0; args[*argn][i] == args[*argn + 2][i]
2N/A && args[*argn][i]; i++);
2N/A
2N/A /* Go the digits back. */
2N/A i--;
2N/A while (grub_isdigit (args[*argn][i]) && i > 0)
2N/A i--;
2N/A i++;
2N/A
2N/A if (grub_strcmp (args[*argn + 1], "-pgt") == 0)
2N/A update_val (grub_strtoul (args[*argn] + i, 0, 0)
2N/A > grub_strtoul (args[*argn + 2] + i, 0, 0));
2N/A else
2N/A update_val (grub_strtoul (args[*argn] + i, 0, 0)
2N/A < grub_strtoul (args[*argn + 2] + i, 0, 0));
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A
2N/A /* -nt and -ot tests. GRUB extension: when doing -?t<bias> bias
2N/A will be added to the first mtime. */
2N/A if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0
2N/A || grub_memcmp (args[*argn + 1], "-ot", 3) == 0)
2N/A {
2N/A struct grub_dirhook_info file1;
2N/A int file1exists;
2N/A int bias = 0;
2N/A
2N/A /* Fetch fileinfo. */
2N/A get_fileinfo (args[*argn]);
2N/A file1 = file_info;
2N/A file1exists = file_exists;
2N/A get_fileinfo (args[*argn + 2]);
2N/A
2N/A if (args[*argn + 1][3])
2N/A bias = grub_strtosl (args[*argn + 1] + 3, 0, 0);
2N/A
2N/A if (grub_memcmp (args[*argn + 1], "-nt", 3) == 0)
2N/A update_val ((file1exists && ! file_exists)
2N/A || (file1.mtimeset && file_info.mtimeset
2N/A && file1.mtime + bias > file_info.mtime));
2N/A else
2N/A update_val ((! file1exists && file_exists)
2N/A || (file1.mtimeset && file_info.mtimeset
2N/A && file1.mtime + bias < file_info.mtime));
2N/A (*argn) += 3;
2N/A continue;
2N/A }
2N/A }
2N/A
2N/A /* Two-argument tests. */
2N/A if (*argn + 1 < argc)
2N/A {
2N/A /* File tests. */
2N/A if (grub_strcmp (args[*argn], "-d") == 0)
2N/A {
2N/A get_fileinfo (args[*argn + 1]);
2N/A update_val (file_exists && file_info.dir);
2N/A (*argn) += 2;
2N/A return ret;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn], "-e") == 0)
2N/A {
2N/A get_fileinfo (args[*argn + 1]);
2N/A update_val (file_exists);
2N/A (*argn) += 2;
2N/A return ret;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn], "-f") == 0)
2N/A {
2N/A get_fileinfo (args[*argn + 1]);
2N/A /* FIXME: check for other types. */
2N/A update_val (file_exists && ! file_info.dir);
2N/A (*argn) += 2;
2N/A return ret;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn], "-s") == 0)
2N/A {
2N/A grub_file_t file;
2N/A grub_file_filter_disable_compression ();
2N/A file = grub_file_open (args[*argn + 1]);
2N/A update_val (file && (grub_file_size (file) != 0));
2N/A if (file)
2N/A grub_file_close (file);
2N/A grub_errno = GRUB_ERR_NONE;
2N/A (*argn) += 2;
2N/A return ret;
2N/A }
2N/A
2N/A /* String tests. */
2N/A if (grub_strcmp (args[*argn], "-n") == 0)
2N/A {
2N/A update_val (args[*argn + 1][0]);
2N/A
2N/A (*argn) += 2;
2N/A continue;
2N/A }
2N/A if (grub_strcmp (args[*argn], "-z") == 0)
2N/A {
2N/A update_val (! args[*argn + 1][0]);
2N/A (*argn) += 2;
2N/A continue;
2N/A }
2N/A }
2N/A
2N/A /* Special modifiers. */
2N/A
2N/A /* End of expression. return to parent. */
2N/A if (grub_strcmp (args[*argn], ")") == 0)
2N/A {
2N/A (*argn)++;
2N/A return ret;
2N/A }
2N/A /* Recursively invoke if parenthesis. */
2N/A if (grub_strcmp (args[*argn], "(") == 0)
2N/A {
2N/A (*argn)++;
2N/A update_val (test_parse (args, argn, argc));
2N/A continue;
2N/A }
2N/A
2N/A if (grub_strcmp (args[*argn], "!") == 0)
2N/A {
2N/A invert = ! invert;
2N/A (*argn)++;
2N/A continue;
2N/A }
2N/A if (grub_strcmp (args[*argn], "-a") == 0)
2N/A {
2N/A /* If current value is 0 second value is to be discarded. */
2N/A discard = ! ret;
2N/A (*argn)++;
2N/A continue;
2N/A }
2N/A if (grub_strcmp (args[*argn], "-o") == 0)
2N/A {
2N/A /* If current value is 1 second value is to be discarded. */
2N/A discard = ret;
2N/A (*argn)++;
2N/A continue;
2N/A }
2N/A
2N/A /* No test found. Interpret if as just a string. */
2N/A update_val (args[*argn][0]);
2N/A (*argn)++;
2N/A }
2N/A return ret;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_cmd_test (grub_command_t cmd __attribute__ ((unused)),
2N/A int argc, char **args)
2N/A{
2N/A int argn = 0;
2N/A
2N/A if (argc >= 1 && grub_strcmp (args[argc - 1], "]") == 0)
2N/A argc--;
2N/A
2N/A return test_parse (args, &argn, argc) ? GRUB_ERR_NONE
2N/A : grub_error (GRUB_ERR_TEST_FAILURE, "false");
2N/A}
2N/A
2N/Astatic grub_command_t cmd_1, cmd_2;
2N/A
2N/AGRUB_MOD_INIT(test)
2N/A{
2N/A cmd_1 = grub_register_command ("[", grub_cmd_test,
2N/A N_("EXPRESSION ]"), N_("Evaluate an expression."));
2N/A cmd_1->flags |= GRUB_COMMAND_FLAG_EXTRACTOR;
2N/A cmd_2 = grub_register_command ("test", grub_cmd_test,
2N/A N_("EXPRESSION"), N_("Evaluate an expression."));
2N/A cmd_2->flags |= GRUB_COMMAND_FLAG_EXTRACTOR;
2N/A}
2N/A
2N/AGRUB_MOD_FINI(test)
2N/A{
2N/A grub_unregister_command (cmd_1);
2N/A grub_unregister_command (cmd_2);
2N/A}