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, grub_uint64_t inguid)
 188 {
 189   char *type = 0;
 190   grub_uint64_t diskguid = 0;
 191 
 192   type = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_TYPE);
 193 
 194   if (!type)
 195     return grub_errno;
 196 
 197   if (grub_strcmp (type, VDEV_TYPE_DISK) == 0)
 198     {
 199       grub_zfs_nvlist_lookup_uint64 (nvlist, "guid", &diskguid);
 200       *bootpath = grub_zfs_nvlist_lookup_string (nvlist,
 201                                                  ZPOOL_CONFIG_PHYS_PATH);
 202       *devid = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_DEVID);
 203       if (!*bootpath || !*devid || (diskguid != inguid))
 204         {
 205           grub_free (*bootpath);
 206           grub_free (*devid);
 207           *bootpath = 0;
 208           *devid = 0;
 209         }
 210       return GRUB_ERR_NONE;
 211     }
 212 
 213   if (grub_strcmp (type, VDEV_TYPE_MIRROR) == 0)
 214     {
 215       int nelm, i;
 216 
 217       nelm = grub_zfs_nvlist_lookup_nvlist_array_get_nelm
 218         (nvlist, ZPOOL_CONFIG_CHILDREN);
 219 
 220       for (i = 0; i < nelm; i++)
 221         {
 222           char *child;
 223 
 224           child = grub_zfs_nvlist_lookup_nvlist_array (nvlist,
 225                                                        ZPOOL_CONFIG_CHILDREN,
 226                                                        i);
 227 
 228           get_bootpath (child, bootpath, devid, inguid);
 229 
 230           grub_free (child);
 231 
 232           if (*bootpath && *devid)
 233             return GRUB_ERR_NONE;
 234         }
 235     }
 236 
 237   return GRUB_ERR_NONE;
 238 }
 239 
 240 static const char *poolstates[] = {
 241   /* TRANSLATORS: Here we speak about ZFS pools it's semi-marketing,
 242      semi-technical term by Sun/Oracle and should be translated in sync with
 243      other ZFS-related software and documentation.  */
 244   [POOL_STATE_ACTIVE] = N_("Pool state: active"),
 245   [POOL_STATE_EXPORTED] = N_("Pool state: exported"),
 246   [POOL_STATE_DESTROYED] = N_("Pool state: destroyed"),
 247   [POOL_STATE_SPARE] = N_("Pool state: reserved for hot spare"),
 248   [POOL_STATE_L2CACHE] = N_("Pool state: level 2 ARC device"),
 249   [POOL_STATE_UNINITIALIZED] = N_("Pool state: uninitialized"),
 250   [POOL_STATE_UNAVAIL] = N_("Pool state: unavailable"),
 251   [POOL_STATE_POTENTIALLY_ACTIVE] = N_("Pool state: potentially active")
 252 };
 253 
 254 static grub_err_t
 255 grub_cmd_zfsinfo (grub_command_t cmd __attribute__ ((unused)), int argc,
 256                   char **args)
 257 {
 258   grub_device_t dev;
 259   char *devname;
 260   grub_err_t err;
 261   char *nvlist = 0;
 262   char *nv = 0;
 263   char *poolname;
 264   grub_uint64_t guid;
 265   grub_uint64_t pool_state;
 266   int found;
 267 
 268   if (argc < 1)
 269     return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
 270 
 271   if (args[0][0] == '(' && args[0][grub_strlen (args[0]) - 1] == ')')
 272     {
 273       devname = grub_strdup (args[0] + 1);
 274       if (devname)
 275         devname[grub_strlen (devname) - 1] = 0;
 276     }
 277   else
 278     devname = grub_strdup (args[0]);
 279   if (!devname)
 280     return grub_errno;
 281 
 282   dev = grub_device_open (devname);
 283   grub_free (devname);
 284   if (!dev)
 285     return grub_errno;
 286 
 287   err = grub_zfs_fetch_nvlist (dev, &nvlist);
 288 
 289   grub_device_close (dev);
 290 
 291   if (err)
 292     return err;
 293 
 294   poolname = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME);
 295   if (!poolname)
 296     grub_puts_ (N_("Pool name: unavailable"));
 297   else
 298     grub_printf_ (N_("Pool name: %s\n"), poolname);
 299 
 300   found =
 301     grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_GUID, &guid);
 302   if (!found)
 303     grub_puts_ (N_("Pool GUID: unavailable"));
 304   else
 305     grub_printf_ (N_("Pool GUID: %016llx\n"), (long long unsigned) guid);
 306 
 307   found = grub_zfs_nvlist_lookup_uint64 (nvlist, ZPOOL_CONFIG_POOL_STATE,
 308                                          &pool_state);
 309   if (!found)
 310     grub_puts_ (N_("Unable to retrieve pool state"));
 311   else if (pool_state >= ARRAY_SIZE (poolstates))
 312     grub_puts_ (N_("Unrecognized pool state"));
 313   else
 314     grub_puts_ (poolstates[pool_state]);
 315 
 316   nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE);
 317 
 318   if (!nv)
 319     /* TRANSLATORS: There are undetermined number of virtual devices
 320        in a device tree, not just one.
 321      */
 322     grub_puts_ (N_("No virtual device tree available"));
 323   else
 324     print_vdev_info (nv, 1);
 325 
 326   grub_free (nv);
 327   grub_free (nvlist);
 328 
 329   return GRUB_ERR_NONE;
 330 }
 331 
 332 static grub_err_t
 333 grub_cmd_zfs_bootfs (grub_command_t cmd __attribute__ ((unused)), int argc,
 334                      char **args)
 335 {
 336   grub_device_t dev;
 337   char *devname;
 338   grub_err_t err;
 339   char *nvlist = 0;
 340   char *nv = 0;
 341   char *bootpath = 0, *devid = 0;
 342   char *fsname;
 343   char *bootfs;
 344   char *poolname;
 345   char def_path[512];
 346   struct grub_zfs_data * data;
 347   grub_uint64_t mdnobj, guid;
 348   int def = 0;
 349 
 350   if (argc <  1) 
 351     return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
 352 
 353   devname = grub_file_get_device_name (args[0]);
 354 
 355   if (grub_errno)
 356     return grub_errno;
 357 
 358   dev = grub_device_open (devname);
 359   grub_free (devname);
 360   if (!dev)
 361     return grub_errno;
 362 
 363   err = grub_zfs_fetch_nvlist (dev, &nvlist);
 364 
 365   fsname = grub_strchr (args[0], ')');
 366   if (fsname)
 367     fsname++;
 368   else
 369     fsname = args[0];
 370 
 371   if (grub_strcmp(fsname, "default") == 0)
 372     ++def;
 373 
 374   if (! def) {
 375     err = grub_zfs_getmdnobj (dev, fsname, &mdnobj);
 376   } else {
 377     err = get_default_bootfs_obj(dev, def_path, &mdnobj);
 378   }
 379 
 380   grub_device_close (dev);
 381 
 382   if (err)
 383     return err;
 384 
 385   poolname = grub_zfs_nvlist_lookup_string (nvlist, ZPOOL_CONFIG_POOL_NAME);
 386   if (!poolname)
 387     {
 388       if (!grub_errno)
 389         grub_error (GRUB_ERR_BAD_FS, "No poolname found");
 390       return grub_errno;
 391     }
 392 
 393   nv = grub_zfs_nvlist_lookup_nvlist (nvlist, ZPOOL_CONFIG_VDEV_TREE);
 394   grub_zfs_nvlist_lookup_uint64 (nvlist, "guid", &guid);
 395 
 396   if (nv && guid)
 397     get_bootpath (nv, &bootpath, &devid, guid);
 398 
 399   grub_free (nv);
 400   grub_free (nvlist);
 401 
 402   bootfs = grub_xasprintf ("zfs-bootfs=%s/%llu%s%s%s%s%s%s",
 403                            poolname, (unsigned long long) mdnobj,
 404                            bootpath ? ",bootpath=\"" : "",
 405                            bootpath ? : "",
 406                            bootpath ? "\"" : "",
 407                            devid ? ",diskdevid=\"" : "",
 408                            devid ? : "",
 409                            devid ? "\"" : "");
 410   if (!bootfs)
 411     return grub_errno;
 412   if (argc >= 2)
 413     grub_env_set (args[1], bootfs);
 414   else
 415     grub_printf ("%s\n", bootfs);
 416 
 417   if ((def) && (argc >= 3))
 418     grub_env_set(args[2], def_path);
 419   else if (def)
 420     grub_printf("%s\n", def_path);
 421 
 422   grub_free (bootfs);
 423   grub_free (poolname);
 424   grub_free (bootpath);
 425   grub_free (devid);
 426 
 427   return GRUB_ERR_NONE;
 428 }
 429 
 430 
 431 static grub_command_t cmd_info, cmd_bootfs;
 432 
 433 GRUB_MOD_INIT (zfsinfo)
 434 {
 435   cmd_info = grub_register_command ("zfsinfo", grub_cmd_zfsinfo,
 436                                     N_("DEVICE"),
 437                                     N_("Print ZFS info about DEVICE."));
 438   cmd_bootfs = grub_register_command ("zfs-bootfs", grub_cmd_zfs_bootfs,
 439                                       N_("FILESYSTEM [VARIABLE]"),
 440                                       N_("Print ZFS-BOOTFSOBJ or store it into VARIABLE"));
 441 }
 442 
 443 GRUB_MOD_FINI (zfsinfo)
 444 {
 445   grub_unregister_command (cmd_info);
 446   grub_unregister_command (cmd_bootfs);
 447 }