1 /*
   2  *  GRUB  --  GRand Unified Bootloader
   3  *  Copyright (C) 1999,2000,2001,2002,2003,2004,2009  Free Software Foundation, Inc.
   4  *  Copyright 2008  Sun Microsystems, Inc.
   5  *
   6  *  GRUB is free software; you can redistribute it and/or modify
   7  *  it under the terms of the GNU General Public License as published by
   8  *  the Free Software Foundation; either version 3 of the License, or
   9  *  (at your option) any later version.
  10  *
  11  *  GRUB is distributed in the hope that it will be useful,
  12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14  *  GNU General Public License for more details.
  15  *
  16  *  You should have received a copy of the GNU General Public License
  17  *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
  18  */
  19 
  20 #include <grub/zfs/zfs.h>
  21 #include <grub/device.h>
  22 #include <grub/file.h>
  23 #include <grub/command.h>
  24 #include <grub/misc.h>
  25 #include <grub/mm.h>
  26 #include <grub/dl.h>
  27 #include <grub/env.h>
  28 #include <grub/i18n.h>
  29 
  30 GRUB_MOD_LICENSE ("GPLv3+");
  31 
  32 static inline void
  33 print_tabs (int n)
  34 {
  35   int i;
  36 
  37   for (i = 0; i < n; i++)
  38     grub_printf (" ");
  39 }
  40 
  41 static grub_err_t
  42 print_state (char *nvlist, int tab)
  43 {
  44   grub_uint64_t ival;
  45   int isok = 1;
  46 
  47   print_tabs (tab);
  48 
  49   if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_REMOVED, &ival))
  50     {
  51       grub_puts_ (N_("Virtual device is removed"));
  52       isok = 0;
  53     }
  54 
  55   if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_FAULTED, &ival))
  56     {
  57       grub_puts_ (N_("Virtual device is faulted"));
  58       isok = 0;
  59     }
  60 
  61   if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_OFFLINE, &ival))
  62     {
  63       grub_puts_ (N_("Virtual device is offline"));
  64       isok = 0;
  65     }
  66 
  67   if (grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_FAULTED, &ival))
  68     /* TRANSLATORS: degraded doesn't mean broken but that some of
  69        component are missing but virtual device as whole is still usable.  */
  70     grub_puts_ (N_("Virtual device is degraded"));
  71 
  72   if (isok)
  73     grub_puts_ (N_("Virtual device is online"));
  74   grub_xputs ("\n");
  75 
  76   return GRUB_ERR_NONE;
  77 }
  78 
  79 static grub_err_t
  80 print_vdev_info (char *nvlist, int tab)
  81 {
  82   char *type = 0;
  83 
  84   type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE);
  85 
  86   if (!type)
  87     {
  88       print_tabs (tab);
  89       grub_puts_ (N_("Incorrect virtual device: no type available"));
  90       return grub_errno;
  91     }
  92 
  93   if (grub_strcmp (type, VDEV_TYPE_DISK) == 0)
  94     {
  95       char *bootpath = 0;
  96       char *path = 0;
  97       char *devid = 0;
  98 
  99       print_tabs (tab);
 100       /* TRANSLATORS: The virtual devices form a tree (in graph-theoretical
 101          sense). The nodes like mirror or raidz have children: member devices.
 102          The "real" devices which actually store data are called "leafs"
 103          (again borrowed from graph theory) and can be either disks
 104          (or partitions) or files.  */
 105       grub_puts_ (N_("Leaf virtual device (file or disk)"));
 106 
 107       print_state (nvlist, tab);
 108 
 109       bootpath =
 110         grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_PHYS_PATH);
 111       print_tabs (tab);
 112       if (!bootpath)
 113         grub_puts_ (N_("Bootpath: unavailable\n"));
 114       else
 115         grub_printf_ (N_("Bootpath: %s\n"), bootpath);
 116 
 117       path = grub_zfs_nvlist_lookup_string (nvlist, "path");
 118       print_tabs (tab);
 119       if (!path)
 120         grub_puts_ (N_("Path: unavailable"));
 121       else
 122         grub_printf_ (N_("Path: %s\n"), path);
 123 
 124       devid = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_DEVID);
 125       print_tabs (tab);
 126       if (!devid)
 127         grub_puts_ (N_("Devid: unavailable"));
 128       else
 129         grub_printf_ (N_("Devid: %s\n"), devid);
 130       grub_free (bootpath);
 131       grub_free (devid);
 132       grub_free (path);
 133       return GRUB_ERR_NONE;
 134     }
 135 
 136   if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0)
 137     {
 138       int nelm, i;
 139 
 140       nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm
 141         (nvlist, ZPOOL_CONFIG_CHILDREN);
 142 
 143       print_tabs (tab);
 144       if (nelm <= 0)
 145         {
 146           grub_puts_ (N_("Incorrect mirror"));
 147           return GRUB_ERR_NONE;
 148         }
 149       grub_printf_ (N_("Mirror with %d children\n"), nelm);
 150       print_state (nvlist, tab);
 151       for (i = 0; i < nelm; i++)
 152         {
 153           char *child;
 154 
 155           child = grub_zfs_nvlist_lookup_nvlist_array
 156             (nvlist, ZPOOL_CONFIG_CHILDREN, i);
 157 
 158           print_tabs (tab);
 159           if (!child)
 160             {
 161               /* TRANSLATORS: it's the element carying the number %d, not
 162                  total element number. And the number itself is fine,
 163                  only the element isn't.
 164               */
 165               grub_printf_ (N_("Mirror element number %d isn't correct\n"), i);
 166               continue;
 167             }
 168 
 169           /* TRANSLATORS: it's the element carying the number %d, not
 170              total element number. This is used in enumeration
 171              "Element number 1", "Element number 2", ... */
 172           grub_printf_ (N_("Mirror element number %d:\n"), i);
 173           print_vdev_info (child, tab + 1);
 174 
 175           grub_free (child);
 176         }
 177       return GRUB_ERR_NONE;
 178     }
 179 
 180   print_tabs (tab);
 181   grub_printf_ (N_("Unknown virtual device type: %s\n"), type);
 182 
 183   return GRUB_ERR_NONE;
 184 }
 185 
 186 static grub_err_t
 187 get_bootpath (char *nvlist, char **bootpath, char **devid)
 188 {
 189   char *type = 0;
 190 
 191   type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE);
 192 
 193   if (!type)
 194     return grub_errno;
 195 
 196   if (grub_strcmp (type, VDEV_TYPE_DISK) == 0)
 197     {
 198       *bootpath = grub_zfs_nvlist_lookup_string (nvlist,
 199                                                  ZPOOL_CONFIG_PHYS_PATH);
 200       *devid = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_DEVID);
 201       if (!*bootpath || !*devid)
 202         {
 203           grub_free (*bootpath);
 204           grub_free (*devid);
 205           *bootpath = 0;
 206           *devid = 0;
 207         }
 208       return GRUB_ERR_NONE;
 209     }
 210 
 211   if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0)
 212     {
 213       int nelm, i;
 214 
 215       nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm
 216         (nvlist, ZPOOL_CONFIG_CHILDREN);
 217 
 218       for (i = 0; i < nelm; i++)
 219         {
 220           char *child;
 221 
 222           child = grub_zfs_nvlist_lookup_nvlist_array (nvlist,
 223                                                        ZPOOL_CONFIG_CHILDREN,
 224                                                        i);
 225 
 226           get_bootpath (child, bootpath, devid);
 227 
 228           grub_free (child);
 229 
 230           if (*bootpath && *devid)
 231             return GRUB_ERR_NONE;
 232         }
 233     }
 234 
 235   return GRUB_ERR_NONE;
 236 }
 237 
 238 static const char *poolstates[] = {
 239   /* TRANSLATORS: Here we speak about ZFS pools it's semi-marketing,
 240      semi-technical term by Sun/Oracle and should be translated in sync with
 241      other ZFS-related software and documentation.  */
 242   [POOL_STATE_ACTIVE] = N_("Pool state: active"),
 243   [POOL_STATE_EXPORTED] = N_("Pool state: exported"),
 244   [POOL_STATE_DESTROYED] = N_("Pool state: destroyed"),
 245   [POOL_STATE_SPARE] = N_("Pool state: reserved for hot spare"),
 246   [POOL_STATE_L2CACHE] = N_("Pool state: level 2 ARC device"),
 247   [POOL_STATE_UNINITIALIZED] = N_("Pool state: uninitialized"),
 248   [POOL_STATE_UNAVAIL] = N_("Pool state: unavailable"),
 249   [POOL_STATE_POTENTIALLY_ACTIVE] = N_("Pool state: potentially active")
 250 };
 251 
 252 static grub_err_t
 253 grub_cmd_zfsinfo (grub_command_t cmd __attribute__ ((unused)), int argc,
 254                   char **args)
 255 {
 256   grub_device_t dev;
 257   char *devname;
 258   grub_err_t err;
 259   char *nvlist = 0;
 260   char *nv = 0;
 261   char *poolname;
 262   grub_uint64_t guid;
 263   grub_uint64_t pool_state;
 264   int found;
 265 
 266   if (argc < 1)
 267     return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
 268 
 269   if (args[0][0] == '(' && args[0][grub_strlen (args[0]) - 1] == ')')
 270     {
 271       devname = grub_strdup (args[0] + 1);
 272       if (devname)
 273         devname[grub_strlen (devname) - 1] = 0;
 274     }
 275   else
 276     devname = grub_strdup (args[0]);
 277   if (!devname)
 278     return grub_errno;
 279 
 280   dev = grub_device_open (devname);
 281   grub_free (devname);
 282   if (!dev)
 283     return grub_errno;
 284 
 285   err = grub_zfs_fetch_nvlist (dev, &nvlist);
 286 
 287   grub_device_close (dev);
 288 
 289   if (err)
 290     return err;
 291 
 292   poolname = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME);
 293   if (!poolname)
 294     grub_puts_ (N_("Pool name: unavailable"));
 295   else
 296     grub_printf_ (N_("Pool name: %s\n"), poolname);
 297 
 298   found =
 299     grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID, &guid);
 300   if (!found)
 301     grub_puts_ (N_("Pool GUID: unavailable"));
 302   else
 303     grub_printf_ (N_("Pool GUID: %016llx\n"), (long long unsigned) guid);
 304 
 305   found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_STATE,
 306                                          &pool_state);
 307   if (!found)
 308     grub_puts_ (N_("Unable to retrieve pool state"));
 309   else if (pool_state >= ARRAY_SIZE (poolstates))
 310     grub_puts_ (N_("Unrecognized pool state"));
 311   else
 312     grub_puts_ (poolstates[pool_state]);
 313 
 314   nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE);
 315 
 316   if (!nv)
 317     /* TRANSLATORS: There are undetermined number of virtual devices
 318        in a device tree, not just one.
 319      */
 320     grub_puts_ (N_("No virtual device tree available"));
 321   else
 322     print_vdev_info (nv, 1);
 323 
 324   grub_free (nv);
 325   grub_free (nvlist);
 326 
 327   return GRUB_ERR_NONE;
 328 }
 329 
 330 static grub_err_t
 331 grub_cmd_zfs_bootfs (grub_command_t cmd __attribute__ ((unused)), int argc,
 332                      char **args)
 333 {
 334   grub_device_t dev;
 335   char *devname;
 336   grub_err_t err;
 337   char *nvlist = 0;
 338   char *nv = 0;
 339   char *bootpath = 0, *devid = 0;
 340   char *fsname;
 341   char *bootfs;
 342   char *poolname;
 343   grub_uint64_t mdnobj;
 344 
 345   if (argc < 1)
 346     return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
 347 
 348   devname = grub_file_get_device_name (args[0]);
 349   if (grub_errno)
 350     return grub_errno;
 351 
 352   dev = grub_device_open (devname);
 353   grub_free (devname);
 354   if (!dev)
 355     return grub_errno;
 356 
 357   err = grub_zfs_fetch_nvlist (dev, &nvlist);
 358 
 359   fsname = grub_strchr (args[0], ')');
 360   if (fsname)
 361     fsname++;
 362   else
 363     fsname = args[0];
 364 
 365   if (!err)
 366     err = grub_zfs_getmdnobj (dev, fsname, &mdnobj);
 367 
 368   grub_device_close (dev);
 369 
 370   if (err)
 371     return err;
 372 
 373   poolname = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME);
 374   if (!poolname)
 375     {
 376       if (!grub_errno)
 377         grub_error (GRUB_ERR_BAD_FS, "No poolname found");
 378       return grub_errno;
 379     }
 380 
 381   nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE);
 382 
 383   if (nv)
 384     get_bootpath (nv, &bootpath, &devid);
 385 
 386   grub_free (nv);
 387   grub_free (nvlist);
 388 
 389   bootfs = grub_xasprintf ("zfs-bootfs=%s/%llu%s%s%s%s%s%s",
 390                            poolname, (unsigned long long) mdnobj,
 391                            bootpath ? ",bootpath=\"" : "",
 392                            bootpath ? : "",
 393                            bootpath ? "\"" : "",
 394                            devid ? ",diskdevid=\"" : "",
 395                            devid ? : "",
 396                            devid ? "\"" : "");
 397   if (!bootfs)
 398     return grub_errno;
 399   if (argc >= 2)
 400     grub_env_set (args[1], bootfs);
 401   else
 402     grub_printf ("%s\n", bootfs);
 403 
 404   grub_free (bootfs);
 405   grub_free (poolname);
 406   grub_free (bootpath);
 407   grub_free (devid);
 408 
 409   return GRUB_ERR_NONE;
 410 }
 411 
 412 
 413 static grub_command_t cmd_info, cmd_bootfs;
 414 
 415 GRUB_MOD_INIT (zfsinfo)
 416 {
 417   cmd_info = grub_register_command ("zfsinfo", grub_cmd_zfsinfo,
 418                                     N_("DEVICE"),
 419                                     N_("Print ZFS info about DEVICE."));
 420   cmd_bootfs = grub_register_command ("zfs-bootfs", grub_cmd_zfs_bootfs,
 421                                       N_("FILESYSTEM [VARIABLE]"),
 422                                       N_("Print ZFS-BOOTFSOBJ or store it into VARIABLE"));
 423 }
 424 
 425 GRUB_MOD_FINI (zfsinfo)
 426 {
 427   grub_unregister_command (cmd_info);
 428   grub_unregister_command (cmd_bootfs);
 429 }