| /* 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; |
| |
| 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 = xstrdup (path); |
| |
| /* 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, GRUB_FILE_TYPE_GET_SIZE); |
| 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, GRUB_FILE_TYPE_MOUNT); |
| 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, GRUB_FILE_TYPE_GET_SIZE); |
| 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); |
| fclose (f); |
| } |
| 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; |
| |
| /* FALLTHROUGH */ |
| 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] = grub_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; |
| } |