blob: f0a17d00bb0eabadf3bf0d90849e24061bec2018 [file] [log] [blame]
/* grub-fstest.c - debug tool for filesystem driver */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2008,2009,2010 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 <config.h>
#include <grub/types.h>
#include <grub/emu/misc.h>
#include <grub/util/misc.h>
#include <grub/misc.h>
#include <grub/device.h>
#include <grub/disk.h>
#include <grub/file.h>
#include <grub/fs.h>
#include <grub/env.h>
#include <grub/term.h>
#include <grub/mm.h>
#include <grub/lib/hexdump.h>
#include <grub/crypto.h>
#include <grub/command.h>
#include <grub/i18n.h>
#include <grub/zfs/zfs.h>
#include <grub/emu/hostfile.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "progname.h"
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
#pragma GCC diagnostic ignored "-Wmissing-declarations"
#include "argp.h"
#pragma GCC diagnostic error "-Wmissing-prototypes"
#pragma GCC diagnostic error "-Wmissing-declarations"
static grub_err_t
execute_command (const char *name, int n, char **args)
{
grub_command_t cmd;
cmd = grub_command_find (name);
if (! cmd)
grub_util_error (_("can't find command `%s'"), name);
return (cmd->func) (cmd, n, args);
}
enum {
CMD_LS = 1,
CMD_CP,
CMD_CAT,
CMD_CMP,
CMD_HEX,
CMD_CRC,
CMD_BLOCKLIST,
CMD_TESTLOAD,
CMD_ZFSINFO,
CMD_XNU_UUID
};
#define BUF_SIZE 32256
static grub_disk_addr_t skip, leng;
static int uncompress = 0;
static void
read_file (char *pathname, int (*hook) (grub_off_t ofs, char *buf, int len, void *hook_arg), void *hook_arg)
{
static char buf[BUF_SIZE];
grub_file_t file;
if ((pathname[0] == '-') && (pathname[1] == 0))
{
grub_device_t dev;
dev = grub_device_open (0);
if ((! dev) || (! dev->disk))
grub_util_error ("%s", grub_errmsg);
grub_util_info ("total sectors : %" GRUB_HOST_PRIuLONG_LONG,
(unsigned long long) dev->disk->total_sectors);
if (! leng)
leng = (dev->disk->total_sectors << GRUB_DISK_SECTOR_BITS) - skip;
while (leng)
{
grub_size_t len;
len = (leng > BUF_SIZE) ? BUF_SIZE : leng;
if (grub_disk_read (dev->disk, 0, skip, len, buf))
{
char *msg = grub_xasprintf (_("disk read fails at offset %lld, length %lld"),
(long long) skip, (long long) len);
grub_util_error ("%s", msg);
}
if (hook (skip, buf, len, hook_arg))
break;
skip += len;
leng -= len;
}
grub_device_close (dev);
return;
}
file = grub_file_open (pathname, ((uncompress == 0)
? GRUB_FILE_TYPE_NO_DECOMPRESS : 0)
| GRUB_FILE_TYPE_FSTEST);
if (!file)
{
grub_util_error (_("cannot open `%s': %s"), pathname,
grub_errmsg);
return;
}
grub_util_info ("file size : %" GRUB_HOST_PRIuLONG_LONG,
(unsigned long long) file->size);
if (skip > file->size)
{
char *msg = grub_xasprintf (_("invalid skip value %lld"),
(unsigned long long) skip);
grub_util_error ("%s", msg);
return;
}
{
grub_off_t ofs, len;
ofs = skip;
len = file->size - skip;
if ((leng) && (leng < len))
len = leng;
file->offset = skip;
while (len)
{
grub_ssize_t sz;
sz = grub_file_read (file, buf, (len > BUF_SIZE) ? BUF_SIZE : len);
if (sz < 0)
{
char *msg = grub_xasprintf (_("read error at offset %llu: %s"),
(unsigned long long) ofs, grub_errmsg);
grub_util_error ("%s", msg);
break;
}
if ((sz == 0) || (hook (ofs, buf, sz, hook_arg)))
break;
ofs += sz;
len -= sz;
}
}
grub_file_close (file);
}
struct cp_hook_ctx
{
FILE *ff;
const char *dest;
};
static int
cp_hook (grub_off_t ofs, char *buf, int len, void *_ctx)
{
struct cp_hook_ctx *ctx = _ctx;
(void) ofs;
if ((int) fwrite (buf, 1, len, ctx->ff) != len)
{
grub_util_error (_("cannot write to `%s': %s"),
ctx->dest, strerror (errno));
return 1;
}
return 0;
}
static void
cmd_cp (char *src, const char *dest)
{
struct cp_hook_ctx ctx =
{
.dest = dest
};
ctx.ff = grub_util_fopen (dest, "wb");
if (ctx.ff == NULL)
{
grub_util_error (_("cannot open OS file `%s': %s"), dest,
strerror (errno));
return;
}
read_file (src, cp_hook, &ctx);
fclose (ctx.ff);
}
static int
cat_hook (grub_off_t ofs, char *buf, int len, void *_arg __attribute__ ((unused)))
{
(void) ofs;
if ((int) fwrite (buf, 1, len, stdout) != len)
{
grub_util_error (_("cannot write to the stdout: %s"),
strerror (errno));
return 1;
}
return 0;
}
static void
cmd_cat (char *src)
{
read_file (src, cat_hook, 0);
}
static int
cmp_hook (grub_off_t ofs, char *buf, int len, void *ff_in)
{
FILE *ff = ff_in;
static char buf_1[BUF_SIZE];
if ((int) fread (buf_1, 1, len, ff) != len)
{
char *msg = grub_xasprintf (_("read error at offset %llu: %s"),
(unsigned long long) ofs, grub_errmsg);
grub_util_error ("%s", msg);
return 1;
}
if (grub_memcmp (buf, buf_1, len) != 0)
{
int i;
for (i = 0; i < len; i++, ofs++)
if (buf_1[i] != buf[i])
{
char *msg = grub_xasprintf (_("compare fail at offset %llu"),
(unsigned long long) ofs);
grub_util_error ("%s", msg);
return 1;
}
}
return 0;
}
static void
cmd_cmp (char *src, char *dest)
{
FILE *ff;
if (grub_util_is_directory (dest))
{
grub_util_fd_dir_t dir = grub_util_fd_opendir (dest);
grub_util_fd_dirent_t entry;
if (dir == NULL)
{
grub_util_error (_("OS file %s open error: %s"), dest,
grub_util_fd_strerror ());
return;
}
while ((entry = grub_util_fd_readdir (dir)))
{
char *srcnew, *destnew;
char *ptr;
if (strcmp (entry->d_name, ".") == 0
|| strcmp (entry->d_name, "..") == 0)
continue;
srcnew = xmalloc (strlen (src) + sizeof ("/")
+ strlen (entry->d_name));
destnew = xmalloc (strlen (dest) + sizeof ("/")
+ strlen (entry->d_name));
ptr = grub_stpcpy (srcnew, src);
*ptr++ = '/';
strcpy (ptr, entry->d_name);
ptr = grub_stpcpy (destnew, dest);
*ptr++ = '/';
strcpy (ptr, entry->d_name);
if (grub_util_is_special_file (destnew))
continue;
cmd_cmp (srcnew, destnew);
}
grub_util_fd_closedir (dir);
return;
}
ff = grub_util_fopen (dest, "rb");
if (ff == NULL)
{
grub_util_error (_("OS file %s open error: %s"), dest,
strerror (errno));
return;
}
if ((skip) && (fseeko (ff, skip, SEEK_SET)))
grub_util_error (_("cannot seek `%s': %s"), dest,
strerror (errno));
read_file (src, cmp_hook, ff);
{
grub_uint64_t pre;
pre = ftell (ff);
fseek (ff, 0, SEEK_END);
if (pre != ftell (ff))
grub_util_error ("%s", _("unexpected end of file"));
}
fclose (ff);
}
static int
hex_hook (grub_off_t ofs, char *buf, int len, void *arg __attribute__ ((unused)))
{
hexdump (ofs, buf, len);
return 0;
}
static void
cmd_hex (char *pathname)
{
read_file (pathname, hex_hook, 0);
}
static int
crc_hook (grub_off_t ofs, char *buf, int len, void *crc_ctx)
{
(void) ofs;
GRUB_MD_CRC32->write(crc_ctx, buf, len);
return 0;
}
static void
cmd_crc (char *pathname)
{
grub_uint8_t *crc32_context = xmalloc (GRUB_MD_CRC32->contextsize);
GRUB_MD_CRC32->init(crc32_context);
read_file (pathname, crc_hook, crc32_context);
GRUB_MD_CRC32->final(crc32_context);
printf ("%08x\n",
grub_be_to_cpu32 (grub_get_unaligned32 (GRUB_MD_CRC32->read (crc32_context))));
free (crc32_context);
}
static const char *root = NULL;
static int args_count = 0;
static int nparm = 0;
static int num_disks = 1;
static char **images = NULL;
static int cmd = 0;
static char *debug_str = NULL;
static char **args = NULL;
static int mount_crypt = 0;
static void
fstest (int n)
{
char *host_file;
char *loop_name;
int i;
for (i = 0; i < num_disks; i++)
{
char *argv[2];
loop_name = grub_xasprintf ("loop%d", i);
if (!loop_name)
grub_util_error ("%s", grub_errmsg);
host_file = grub_xasprintf ("(host)%s", images[i]);
if (!host_file)
grub_util_error ("%s", grub_errmsg);
argv[0] = loop_name;
argv[1] = host_file;
if (execute_command ("loopback", 2, argv))
grub_util_error (_("`loopback' command fails: %s"), grub_errmsg);
grub_free (loop_name);
grub_free (host_file);
}
{
if (mount_crypt)
{
char *argv[2] = { xstrdup ("-a"), NULL};
if (execute_command ("cryptomount", 1, argv))
grub_util_error (_("`cryptomount' command fails: %s"),
grub_errmsg);
free (argv[0]);
}
}
grub_ldm_fini ();
grub_lvm_fini ();
grub_mdraid09_fini ();
grub_mdraid1x_fini ();
grub_diskfilter_fini ();
grub_diskfilter_init ();
grub_mdraid09_init ();
grub_mdraid1x_init ();
grub_lvm_init ();
grub_ldm_init ();
switch (cmd)
{
case CMD_LS:
execute_command ("ls", n, args);
break;
case CMD_ZFSINFO:
execute_command ("zfsinfo", n, args);
break;
case CMD_CP:
cmd_cp (args[0], args[1]);
break;
case CMD_CAT:
cmd_cat (args[0]);
break;
case CMD_CMP:
cmd_cmp (args[0], args[1]);
break;
case CMD_HEX:
cmd_hex (args[0]);
break;
case CMD_CRC:
cmd_crc (args[0]);
break;
case CMD_BLOCKLIST:
execute_command ("blocklist", n, args);
grub_printf ("\n");
break;
case CMD_TESTLOAD:
execute_command ("testload", n, args);
grub_printf ("\n");
break;
case CMD_XNU_UUID:
{
grub_device_t dev;
grub_fs_t fs;
char *uuid = 0;
char *argv[3] = { xstrdup ("-l"), NULL, NULL};
dev = grub_device_open (n ? args[0] : 0);
if (!dev)
grub_util_error ("%s", grub_errmsg);
fs = grub_fs_probe (dev);
if (!fs)
grub_util_error ("%s", grub_errmsg);
if (!fs->uuid)
grub_util_error ("%s", _("couldn't retrieve UUID"));
if (fs->uuid (dev, &uuid))
grub_util_error ("%s", grub_errmsg);
if (!uuid)
grub_util_error ("%s", _("couldn't retrieve UUID"));
argv[1] = uuid;
execute_command ("xnu_uuid", 2, argv);
grub_free (argv[0]);
grub_free (uuid);
grub_device_close (dev);
}
}
for (i = 0; i < num_disks; i++)
{
char *argv[2];
loop_name = grub_xasprintf ("loop%d", i);
if (!loop_name)
grub_util_error ("%s", grub_errmsg);
argv[0] = xstrdup ("-d");
argv[1] = loop_name;
execute_command ("loopback", 2, argv);
grub_free (loop_name);
grub_free (argv[0]);
}
}
static struct argp_option options[] = {
{0, 0, 0 , OPTION_DOC, N_("Commands:"), 1},
{N_("ls PATH"), 0, 0 , OPTION_DOC, N_("List files in PATH."), 1},
{N_("cp FILE LOCAL"), 0, 0, OPTION_DOC, N_("Copy FILE to local file LOCAL."), 1},
{N_("cat FILE"), 0, 0 , OPTION_DOC, N_("Copy FILE to standard output."), 1},
{N_("cmp FILE LOCAL"), 0, 0, OPTION_DOC, N_("Compare FILE with local file LOCAL."), 1},
{N_("hex FILE"), 0, 0 , OPTION_DOC, N_("Show contents of FILE in hex."), 1},
{N_("crc FILE"), 0, 0 , OPTION_DOC, N_("Get crc32 checksum of FILE."), 1},
{N_("blocklist FILE"), 0, 0, OPTION_DOC, N_("Display blocklist of FILE."), 1},
{N_("xnu_uuid DEVICE"), 0, 0, OPTION_DOC, N_("Compute XNU UUID of the device."), 1},
{"root", 'r', N_("DEVICE_NAME"), 0, N_("Set root device."), 2},
{"skip", 's', N_("NUM"), 0, N_("Skip N bytes from output file."), 2},
{"length", 'n', N_("NUM"), 0, N_("Handle N bytes in output file."), 2},
{"diskcount", 'c', N_("NUM"), 0, N_("Specify the number of input files."), 2},
{"debug", 'd', N_("STRING"), 0, N_("Set debug environment variable."), 2},
{"crypto", 'C', NULL, 0, N_("Mount crypto devices."), 2},
{"zfs-key", 'K',
/* TRANSLATORS: "prompt" is a keyword. */
N_("FILE|prompt"), 0, N_("Load zfs crypto key."), 2},
{"verbose", 'v', NULL, 0, N_("print verbose messages."), 2},
{"uncompress", 'u', NULL, 0, N_("Uncompress data."), 2},
{0, 0, 0, 0, 0, 0}
};
/* Print the version information. */
static void
print_version (FILE *stream, struct argp_state *state)
{
fprintf (stream, "%s (%s) %s\n", program_name, PACKAGE_NAME, PACKAGE_VERSION);
}
void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
static error_t
argp_parser (int key, char *arg, struct argp_state *state)
{
char *p;
switch (key)
{
case 'r':
root = arg;
return 0;
case 'K':
if (strcmp (arg, "prompt") == 0)
{
char buf[1024];
grub_puts_ (N_("Enter ZFS password: "));
if (grub_password_get (buf, 1023))
{
grub_zfs_add_key ((grub_uint8_t *) buf, grub_strlen (buf), 1);
}
}
else
{
FILE *f;
ssize_t real_size;
grub_uint8_t buf[1024];
f = grub_util_fopen (arg, "rb");
if (!f)
{
printf (_("%s: error:"), program_name);
printf (_("cannot open `%s': %s"), arg, strerror (errno));
printf ("\n");
return 0;
}
real_size = fread (buf, 1, 1024, f);
fclose (f);
if (real_size < 0)
{
printf (_("%s: error:"), program_name);
printf (_("cannot read `%s': %s"), arg, strerror (errno));
printf ("\n");
return 0;
}
grub_zfs_add_key (buf, real_size, 0);
}
return 0;
case 'C':
mount_crypt = 1;
return 0;
case 's':
skip = grub_strtoul (arg, &p, 0);
if (*p == 's')
skip <<= GRUB_DISK_SECTOR_BITS;
return 0;
case 'n':
leng = grub_strtoul (arg, &p, 0);
if (*p == 's')
leng <<= GRUB_DISK_SECTOR_BITS;
return 0;
case 'c':
num_disks = grub_strtoul (arg, NULL, 0);
if (num_disks < 1)
{
fprintf (stderr, "%s", _("Invalid disk count.\n"));
argp_usage (state);
}
if (args_count != 0)
{
/* TRANSLATORS: disk count is optional but if it's there it must
be before disk list. So please don't imply disk count as mandatory.
*/
fprintf (stderr, "%s", _("Disk count must precede disks list.\n"));
argp_usage (state);
}
return 0;
case 'd':
debug_str = arg;
return 0;
case 'v':
verbosity++;
return 0;
case 'u':
uncompress = 1;
return 0;
case ARGP_KEY_END:
if (args_count < num_disks)
{
fprintf (stderr, "%s", _("No command is specified.\n"));
argp_usage (state);
}
if (args_count - 1 - num_disks < nparm)
{
fprintf (stderr, "%s", _("Not enough parameters to command.\n"));
argp_usage (state);
}
return 0;
case ARGP_KEY_ARG:
break;
default:
return ARGP_ERR_UNKNOWN;
}
if (args_count < num_disks)
{
if (args_count == 0)
images = xmalloc (num_disks * sizeof (images[0]));
images[args_count] = grub_canonicalize_file_name (arg);
args_count++;
return 0;
}
if (args_count == num_disks)
{
if (!grub_strcmp (arg, "ls"))
{
cmd = CMD_LS;
}
else if (!grub_strcmp (arg, "zfsinfo"))
{
cmd = CMD_ZFSINFO;
}
else if (!grub_strcmp (arg, "cp"))
{
cmd = CMD_CP;
nparm = 2;
}
else if (!grub_strcmp (arg, "cat"))
{
cmd = CMD_CAT;
nparm = 1;
}
else if (!grub_strcmp (arg, "cmp"))
{
cmd = CMD_CMP;
nparm = 2;
}
else if (!grub_strcmp (arg, "hex"))
{
cmd = CMD_HEX;
nparm = 1;
}
else if (!grub_strcmp (arg, "crc"))
{
cmd = CMD_CRC;
nparm = 1;
}
else if (!grub_strcmp (arg, "blocklist"))
{
cmd = CMD_BLOCKLIST;
nparm = 1;
}
else if (!grub_strcmp (arg, "testload"))
{
cmd = CMD_TESTLOAD;
nparm = 1;
}
else if (grub_strcmp (arg, "xnu_uuid") == 0)
{
cmd = CMD_XNU_UUID;
nparm = 0;
}
else
{
fprintf (stderr, _("Invalid command %s.\n"), arg);
argp_usage (state);
}
args_count++;
return 0;
}
args[args_count - 1 - num_disks] = xstrdup (arg);
args_count++;
return 0;
}
struct argp argp = {
options, argp_parser, N_("IMAGE_PATH COMMANDS"),
N_("Debug tool for filesystem driver."),
NULL, NULL, NULL
};
int
main (int argc, char *argv[])
{
const char *default_root;
char *alloc_root;
grub_util_host_init (&argc, &argv);
args = xmalloc (argc * sizeof (args[0]));
argp_parse (&argp, argc, argv, 0, 0, 0);
/* Initialize all modules. */
grub_init_all ();
grub_gcry_init_all ();
if (debug_str)
grub_env_set ("debug", debug_str);
default_root = (num_disks == 1) ? "loop0" : "md0";
alloc_root = 0;
if (root)
{
if ((*root >= '0') && (*root <= '9'))
{
alloc_root = xmalloc (strlen (default_root) + strlen (root) + 2);
sprintf (alloc_root, "%s,%s", default_root, root);
root = alloc_root;
}
}
else
root = default_root;
grub_env_set ("root", root);
if (alloc_root)
free (alloc_root);
/* Do it. */
fstest (args_count - 1 - num_disks);
/* Free resources. */
grub_gcry_fini_all ();
grub_fini_all ();
return 0;
}