| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 1999,2000,2001,2002,2003,2004,2009 Free Software Foundation, Inc. |
| * Copyright 2008 Sun Microsystems, 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/zfs/zfs.h> |
| #include <grub/device.h> |
| #include <grub/file.h> |
| #include <grub/command.h> |
| #include <grub/misc.h> |
| #include <grub/mm.h> |
| #include <grub/dl.h> |
| #include <grub/env.h> |
| #include <grub/i18n.h> |
| |
| GRUB_MOD_LICENSE ("GPLv3+"); |
| |
| static inline void |
| print_tabs (int n) |
| { |
| int i; |
| |
| for (i = 0; i < n; i++) |
| grub_printf (" "); |
| } |
| |
| static grub_err_t |
| print_state (char *nvlist, int tab) |
| { |
| grub_uint64_t ival; |
| int isok = 1; |
| |
| print_tabs (tab); |
| |
| if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_REMOVED, &ival)) |
| { |
| grub_puts_ (N_("Virtual device is removed")); |
| isok = 0; |
| } |
| |
| if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_FAULTED, &ival)) |
| { |
| grub_puts_ (N_("Virtual device is faulted")); |
| isok = 0; |
| } |
| |
| if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_OFFLINE, &ival)) |
| { |
| grub_puts_ (N_("Virtual device is offline")); |
| isok = 0; |
| } |
| |
| if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_FAULTED, &ival)) |
| /* TRANSLATORS: degraded doesn't mean broken but that some of |
| component are missing but virtual device as whole is still usable. */ |
| grub_puts_ (N_("Virtual device is degraded")); |
| |
| if (isok) |
| grub_puts_ (N_("Virtual device is online")); |
| grub_xputs ("\n"); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| print_vdev_info (char *nvlist, int tab) |
| { |
| char *type = 0; |
| |
| type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE); |
| |
| if (!type) |
| { |
| print_tabs (tab); |
| grub_puts_ (N_("Incorrect virtual device: no type available")); |
| return grub_errno; |
| } |
| |
| if (grub_strcmp (type, VDEV_TYPE_DISK) == 0) |
| { |
| char *bootpath = 0; |
| char *path = 0; |
| char *devid = 0; |
| |
| print_tabs (tab); |
| /* TRANSLATORS: The virtual devices form a tree (in graph-theoretical |
| sense). The nodes like mirror or raidz have children: member devices. |
| The "real" devices which actually store data are called "leafs" |
| (again borrowed from graph theory) and can be either disks |
| (or partitions) or files. */ |
| grub_puts_ (N_("Leaf virtual device (file or disk)")); |
| |
| print_state (nvlist, tab); |
| |
| bootpath = |
| grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_PHYS_PATH); |
| print_tabs (tab); |
| if (!bootpath) |
| grub_puts_ (N_("Bootpath: unavailable\n")); |
| else |
| grub_printf_ (N_("Bootpath: %s\n"), bootpath); |
| |
| path = grub_zfs_nvlist_lookup_string (nvlist, "path"); |
| print_tabs (tab); |
| if (!path) |
| grub_puts_ (N_("Path: unavailable")); |
| else |
| grub_printf_ (N_("Path: %s\n"), path); |
| |
| devid = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_DEVID); |
| print_tabs (tab); |
| if (!devid) |
| grub_puts_ (N_("Devid: unavailable")); |
| else |
| grub_printf_ (N_("Devid: %s\n"), devid); |
| grub_free (bootpath); |
| grub_free (devid); |
| grub_free (path); |
| return GRUB_ERR_NONE; |
| } |
| |
| if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0) |
| { |
| int nelm, i; |
| |
| nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm |
| (nvlist, ZPOOL_CONFIG_CHILDREN); |
| |
| print_tabs (tab); |
| if (nelm <= 0) |
| { |
| grub_puts_ (N_("Incorrect mirror")); |
| return GRUB_ERR_NONE; |
| } |
| grub_printf_ (N_("Mirror with %d children\n"), nelm); |
| print_state (nvlist, tab); |
| for (i = 0; i < nelm; i++) |
| { |
| char *child; |
| |
| child = grub_zfs_nvlist_lookup_nvlist_array |
| (nvlist, ZPOOL_CONFIG_CHILDREN, i); |
| |
| print_tabs (tab); |
| if (!child) |
| { |
| /* TRANSLATORS: it's the element carying the number %d, not |
| total element number. And the number itself is fine, |
| only the element isn't. |
| */ |
| grub_printf_ (N_("Mirror element number %d isn't correct\n"), i); |
| continue; |
| } |
| |
| /* TRANSLATORS: it's the element carying the number %d, not |
| total element number. This is used in enumeration |
| "Element number 1", "Element number 2", ... */ |
| grub_printf_ (N_("Mirror element number %d:\n"), i); |
| print_vdev_info (child, tab + 1); |
| |
| grub_free (child); |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| print_tabs (tab); |
| grub_printf_ (N_("Unknown virtual device type: %s\n"), type); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| get_bootpath (char *nvlist, char **bootpath, char **devid) |
| { |
| char *type = 0; |
| |
| type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE); |
| |
| if (!type) |
| return grub_errno; |
| |
| if (grub_strcmp (type, VDEV_TYPE_DISK) == 0) |
| { |
| *bootpath = grub_zfs_nvlist_lookup_string (nvlist, |
| ZPOOL_CONFIG_PHYS_PATH); |
| *devid = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_DEVID); |
| if (!*bootpath || !*devid) |
| { |
| grub_free (*bootpath); |
| grub_free (*devid); |
| *bootpath = 0; |
| *devid = 0; |
| } |
| return GRUB_ERR_NONE; |
| } |
| |
| if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0) |
| { |
| int nelm, i; |
| |
| nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm |
| (nvlist, ZPOOL_CONFIG_CHILDREN); |
| |
| for (i = 0; i < nelm; i++) |
| { |
| char *child; |
| |
| child = grub_zfs_nvlist_lookup_nvlist_array (nvlist, |
| ZPOOL_CONFIG_CHILDREN, |
| i); |
| |
| get_bootpath (child, bootpath, devid); |
| |
| grub_free (child); |
| |
| if (*bootpath && *devid) |
| return GRUB_ERR_NONE; |
| } |
| } |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static const char *poolstates[] = { |
| /* TRANSLATORS: Here we speak about ZFS pools it's semi-marketing, |
| semi-technical term by Sun/Oracle and should be translated in sync with |
| other ZFS-related software and documentation. */ |
| [POOL_STATE_ACTIVE] = N_("Pool state: active"), |
| [POOL_STATE_EXPORTED] = N_("Pool state: exported"), |
| [POOL_STATE_DESTROYED] = N_("Pool state: destroyed"), |
| [POOL_STATE_SPARE] = N_("Pool state: reserved for hot spare"), |
| [POOL_STATE_L2CACHE] = N_("Pool state: level 2 ARC device"), |
| [POOL_STATE_UNINITIALIZED] = N_("Pool state: uninitialized"), |
| [POOL_STATE_UNAVAIL] = N_("Pool state: unavailable"), |
| [POOL_STATE_POTENTIALLY_ACTIVE] = N_("Pool state: potentially active") |
| }; |
| |
| static grub_err_t |
| grub_cmd_zfsinfo (grub_command_t cmd __attribute__ ((unused)), int argc, |
| char **args) |
| { |
| grub_device_t dev; |
| char *devname; |
| grub_err_t err; |
| char *nvlist = 0; |
| char *nv = 0; |
| char *poolname; |
| grub_uint64_t guid; |
| grub_uint64_t pool_state; |
| int found; |
| |
| if (argc < 1) |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); |
| |
| if (args[0][0] == '(' && args[0][grub_strlen (args[0]) - 1] == ')') |
| { |
| devname = grub_strdup (args[0] + 1); |
| if (devname) |
| devname[grub_strlen (devname) - 1] = 0; |
| } |
| else |
| devname = grub_strdup (args[0]); |
| if (!devname) |
| return grub_errno; |
| |
| dev = grub_device_open (devname); |
| grub_free (devname); |
| if (!dev) |
| return grub_errno; |
| |
| err = grub_zfs_fetch_nvlist (dev, &nvlist); |
| |
| grub_device_close (dev); |
| |
| if (err) |
| return err; |
| |
| poolname = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME); |
| if (!poolname) |
| grub_puts_ (N_("Pool name: unavailable")); |
| else |
| grub_printf_ (N_("Pool name: %s\n"), poolname); |
| |
| found = |
| grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID, &guid); |
| if (!found) |
| grub_puts_ (N_("Pool GUID: unavailable")); |
| else |
| grub_printf_ (N_("Pool GUID: %016llx\n"), (long long unsigned) guid); |
| |
| found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_STATE, |
| &pool_state); |
| if (!found) |
| grub_puts_ (N_("Unable to retrieve pool state")); |
| else if (pool_state >= ARRAY_SIZE (poolstates)) |
| grub_puts_ (N_("Unrecognized pool state")); |
| else |
| grub_puts_ (poolstates[pool_state]); |
| |
| nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE); |
| |
| if (!nv) |
| /* TRANSLATORS: There are undetermined number of virtual devices |
| in a device tree, not just one. |
| */ |
| grub_puts_ (N_("No virtual device tree available")); |
| else |
| print_vdev_info (nv, 1); |
| |
| grub_free (nv); |
| grub_free (nvlist); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| static grub_err_t |
| grub_cmd_zfs_bootfs (grub_command_t cmd __attribute__ ((unused)), int argc, |
| char **args) |
| { |
| grub_device_t dev; |
| char *devname; |
| grub_err_t err; |
| char *nvlist = 0; |
| char *nv = 0; |
| char *bootpath = 0, *devid = 0; |
| char *fsname; |
| char *bootfs; |
| char *poolname; |
| grub_uint64_t mdnobj; |
| |
| if (argc < 1) |
| return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected")); |
| |
| devname = grub_file_get_device_name (args[0]); |
| if (grub_errno) |
| return grub_errno; |
| |
| dev = grub_device_open (devname); |
| grub_free (devname); |
| if (!dev) |
| return grub_errno; |
| |
| err = grub_zfs_fetch_nvlist (dev, &nvlist); |
| |
| fsname = grub_strchr (args[0], ')'); |
| if (fsname) |
| fsname++; |
| else |
| fsname = args[0]; |
| |
| if (!err) |
| err = grub_zfs_getmdnobj (dev, fsname, &mdnobj); |
| |
| grub_device_close (dev); |
| |
| if (err) |
| return err; |
| |
| poolname = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME); |
| if (!poolname) |
| { |
| if (!grub_errno) |
| grub_error (GRUB_ERR_BAD_FS, "No poolname found"); |
| return grub_errno; |
| } |
| |
| nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE); |
| |
| if (nv) |
| get_bootpath (nv, &bootpath, &devid); |
| |
| grub_free (nv); |
| grub_free (nvlist); |
| |
| bootfs = grub_xasprintf ("zfs-bootfs=%s/%llu%s%s%s%s%s%s", |
| poolname, (unsigned long long) mdnobj, |
| bootpath ? ",bootpath=\"" : "", |
| bootpath ? : "", |
| bootpath ? "\"" : "", |
| devid ? ",diskdevid=\"" : "", |
| devid ? : "", |
| devid ? "\"" : ""); |
| if (!bootfs) |
| return grub_errno; |
| if (argc >= 2) |
| grub_env_set (args[1], bootfs); |
| else |
| grub_printf ("%s\n", bootfs); |
| |
| grub_free (bootfs); |
| grub_free (poolname); |
| grub_free (bootpath); |
| grub_free (devid); |
| |
| return GRUB_ERR_NONE; |
| } |
| |
| |
| static grub_command_t cmd_info, cmd_bootfs; |
| |
| GRUB_MOD_INIT (zfsinfo) |
| { |
| cmd_info = grub_register_command ("zfsinfo", grub_cmd_zfsinfo, |
| N_("DEVICE"), |
| N_("Print ZFS info about DEVICE.")); |
| cmd_bootfs = grub_register_command ("zfs-bootfs", grub_cmd_zfs_bootfs, |
| N_("FILESYSTEM [VARIABLE]"), |
| N_("Print ZFS-BOOTFSOBJ or store it into VARIABLE")); |
| } |
| |
| GRUB_MOD_FINI (zfsinfo) |
| { |
| grub_unregister_command (cmd_info); |
| grub_unregister_command (cmd_bootfs); |
| } |