/* grub-mount.c - FUSE driver for filesystems that GRUB understands */ /* * 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/>. */ #define FUSE_USE_VERSION 26 #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/zfs/zfs.h> #include <grub/i18n.h> #include <fuse/fuse.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.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" #include "progname.h" static const char *root = NULL; grub_device_t dev = NULL; grub_fs_t fs = NULL; static char **images = NULL; static char *debug_str = NULL; static char **fuse_args = NULL; static int fuse_argc = 0; static int num_disks = 0; static int mount_crypt = 0; 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); } /* Translate GRUB error numbers into OS error numbers. Print any unexpected errors. */ static int translate_error (void) { int ret; switch (grub_errno) { case GRUB_ERR_NONE: ret = 0; break; case GRUB_ERR_OUT_OF_MEMORY: grub_print_error (); ret = -ENOMEM; break; case GRUB_ERR_BAD_FILE_TYPE: /* This could also be EISDIR. Take a guess. */ ret = -ENOTDIR; break; case GRUB_ERR_FILE_NOT_FOUND: ret = -ENOENT; break; case GRUB_ERR_FILE_READ_ERROR: case GRUB_ERR_READ_ERROR: case GRUB_ERR_IO: grub_print_error (); ret = -EIO; break; case GRUB_ERR_SYMLINK_LOOP: ret = -ELOOP; break; default: grub_print_error (); ret = -EINVAL; break; } /* Any previous errors were handled. */ grub_errno = GRUB_ERR_NONE; return ret; } /* Context for fuse_getattr. */ struct fuse_getattr_ctx { char *filename; struct grub_dirhook_info file_info; int file_exists; }; /* A hook for iterating directories. */ static int fuse_getattr_find_file (const char *cur_filename, const struct grub_dirhook_info *info, void *data) { struct fuse_getattr_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; } static int fuse_getattr (const char *path, struct stat *st) { struct fuse_getattr_ctx ctx; char *pathname, *path2; const char *pathname_t; if (path[0] == '/' && path[1] == 0) { st->st_dev = 0; st->st_ino = 0; st->st_mode = 0555 | S_IFDIR; st->st_uid = 0; st->st_gid = 0; st->st_rdev = 0; st->st_size = 0; st->st_blksize = 512; st->st_blocks = (st->st_blksize + 511) >> 9; st->st_atime = st->st_mtime = st->st_ctime = 0; return 0; } ctx.file_exists = 0; pathname_t = grub_strchr (path, ')'); if (! pathname_t) pathname_t = path; else pathname_t++; pathname = xstrdup (pathname_t); /* 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) { path2 = grub_strdup ("/"); ctx.filename = pathname; } else { ctx.filename++; path2 = grub_strdup (pathname); path2[ctx.filename - pathname] = 0; } /* It's the whole device. */ (fs->dir) (dev, path2, fuse_getattr_find_file, &ctx); grub_free (path2); if (!ctx.file_exists) { grub_errno = GRUB_ERR_NONE; return -ENOENT; } st->st_dev = 0; st->st_ino = 0; st->st_mode = ctx.file_info.dir ? (0555 | S_IFDIR) : (0444 | S_IFREG); st->st_uid = 0; st->st_gid = 0; st->st_rdev = 0; st->st_size = 0; if (!ctx.file_info.dir) { grub_file_t file; file = grub_file_open (path); if (! file && grub_errno == GRUB_ERR_BAD_FILE_TYPE) { grub_errno = GRUB_ERR_NONE; st->st_mode = (0555 | S_IFDIR); } else if (! file) return translate_error (); else { st->st_size = file->size; grub_file_close (file); } } st->st_blksize = 512; st->st_blocks = (st->st_size + 511) >> 9; st->st_atime = st->st_mtime = st->st_ctime = ctx.file_info.mtimeset ? ctx.file_info.mtime : 0; grub_errno = GRUB_ERR_NONE; return 0; } static int fuse_opendir (const char *path, struct fuse_file_info *fi) { return 0; } /* FIXME */ static grub_file_t files[65536]; static int first_fd = 1; static int fuse_open (const char *path, struct fuse_file_info *fi __attribute__ ((unused))) { grub_file_t file; file = grub_file_open (path); if (! file) return translate_error (); files[first_fd++] = file; fi->fh = first_fd; files[first_fd++] = file; grub_errno = GRUB_ERR_NONE; return 0; } static int fuse_read (const char *path, char *buf, size_t sz, off_t off, struct fuse_file_info *fi) { grub_file_t file = files[fi->fh]; grub_ssize_t size; if (off > file->size) return -EINVAL; file->offset = off; size = grub_file_read (file, buf, sz); if (size < 0) return translate_error (); else { grub_errno = GRUB_ERR_NONE; return size; } } static int fuse_release (const char *path, struct fuse_file_info *fi) { grub_file_close (files[fi->fh]); files[fi->fh] = NULL; grub_errno = GRUB_ERR_NONE; return 0; } /* Context for fuse_readdir. */ struct fuse_readdir_ctx { const char *path; void *buf; fuse_fill_dir_t fill; }; /* Helper for fuse_readdir. */ static int fuse_readdir_call_fill (const char *filename, const struct grub_dirhook_info *info, void *data) { struct fuse_readdir_ctx *ctx = data; struct stat st; grub_memset (&st, 0, sizeof (st)); st.st_mode = info->dir ? (0555 | S_IFDIR) : (0444 | S_IFREG); if (!info->dir) { grub_file_t file; char *tmp; tmp = xasprintf ("%s/%s", ctx->path, filename); file = grub_file_open (tmp); free (tmp); /* Symlink to directory. */ if (! file && grub_errno == GRUB_ERR_BAD_FILE_TYPE) { grub_errno = GRUB_ERR_NONE; st.st_mode = (0555 | S_IFDIR); } else if (!file) { grub_errno = GRUB_ERR_NONE; } else { st.st_size = file->size; grub_file_close (file); } } st.st_blksize = 512; st.st_blocks = (st.st_size + 511) >> 9; st.st_atime = st.st_mtime = st.st_ctime = info->mtimeset ? info->mtime : 0; ctx->fill (ctx->buf, filename, &st, 0); return 0; } static int fuse_readdir (const char *path, void *buf, fuse_fill_dir_t fill, off_t off, struct fuse_file_info *fi) { struct fuse_readdir_ctx ctx = { .path = path, .buf = buf, .fill = fill }; char *pathname; pathname = xstrdup (path); /* Remove trailing '/'. */ while (pathname [0] && pathname[1] && pathname[grub_strlen (pathname) - 1] == '/') pathname[grub_strlen (pathname) - 1] = 0; (fs->dir) (dev, pathname, fuse_readdir_call_fill, &ctx); free (pathname); grub_errno = GRUB_ERR_NONE; return 0; } struct fuse_operations grub_opers = { .getattr = fuse_getattr, .open = fuse_open, .release = fuse_release, .opendir = fuse_opendir, .readdir = fuse_readdir, .read = fuse_read }; static grub_err_t fuse_init (void) { int i; for (i = 0; i < num_disks; i++) { char *argv[2]; char *host_file; char *loop_name; 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_lvm_fini (); grub_mdraid09_fini (); grub_mdraid1x_fini (); grub_diskfilter_fini (); grub_diskfilter_init (); grub_mdraid09_init (); grub_mdraid1x_init (); grub_lvm_init (); dev = grub_device_open (0); if (! dev) return grub_errno; fs = grub_fs_probe (dev); if (! fs) { grub_device_close (dev); return grub_errno; } if (fuse_main (fuse_argc, fuse_args, &grub_opers, NULL)) grub_error (GRUB_ERR_IO, "fuse_main failed"); for (i = 0; i < num_disks; i++) { char *argv[2]; char *loop_name; 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 (argv[0]); grub_free (loop_name); } return grub_errno; } static struct argp_option options[] = { {"root", 'r', N_("DEVICE_NAME"), 0, N_("Set root device."), 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}, {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) { switch (key) { case 'r': root = arg; return 0; case 'K': if (strcmp (arg, "prompt") == 0) { char buf[1024]; grub_printf ("%s", _("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); if (real_size < 0) { printf (_("%s: error:"), program_name); printf (_("cannot read `%s': %s"), arg, strerror (errno)); printf ("\n"); fclose (f); return 0; } grub_zfs_add_key (buf, real_size, 0); } return 0; case 'C': mount_crypt = 1; return 0; case 'd': debug_str = arg; return 0; case 'v': verbosity++; return 0; case ARGP_KEY_ARG: if (arg[0] != '-') break; default: if (!arg) return 0; fuse_args = xrealloc (fuse_args, (fuse_argc + 1) * sizeof (fuse_args[0])); fuse_args[fuse_argc] = xstrdup (arg); fuse_argc++; return 0; } images = xrealloc (images, (num_disks + 1) * sizeof (images[0])); images[num_disks] = canonicalize_file_name (arg); num_disks++; return 0; } struct argp argp = { options, argp_parser, N_("IMAGE1 [IMAGE2 ...] MOUNTPOINT"), 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); fuse_args = xrealloc (fuse_args, (fuse_argc + 2) * sizeof (fuse_args[0])); fuse_args[fuse_argc] = xstrdup (argv[0]); fuse_argc++; /* Run single-threaded. */ fuse_args[fuse_argc] = xstrdup ("-s"); fuse_argc++; argp_parse (&argp, argc, argv, 0, 0, 0); if (num_disks < 2) grub_util_error ("%s", _("need an image and mountpoint")); fuse_args = xrealloc (fuse_args, (fuse_argc + 2) * sizeof (fuse_args[0])); fuse_args[fuse_argc] = images[num_disks - 1]; fuse_argc++; num_disks--; fuse_args[fuse_argc] = NULL; /* Initialize all modules. */ grub_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. */ fuse_init (); if (grub_errno) { grub_print_error (); return 1; } /* Free resources. */ grub_fini_all (); return 0; }