dcavalca / rpms / grub2

Forked from rpms/grub2 3 years ago
Clone

Blame SOURCES/0088-grub2-btrfs-Add-ability-to-boot-from-subvolumes.patch

d9d99f
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
d9d99f
From: Jeff Mahoney <jeffm@suse.com>
d9d99f
Date: Wed, 18 Dec 2013 09:57:04 +0000
d9d99f
Subject: [PATCH] grub2/btrfs: Add ability to boot from subvolumes
d9d99f
d9d99f
This patch adds the ability to specify a different root on a btrfs
d9d99f
filesystem too boot from other than the default one.
d9d99f
d9d99f
btrfs-list-snapshots <dev> will list the subvolumes available on the
d9d99f
filesystem.
d9d99f
d9d99f
set btrfs_subvol=<path> and set btrfs_subvolid=<subvolid> will specify
d9d99f
which subvolume to use and any pathnames provided with either of those
d9d99f
variables set will start using that root. If the subvolume or subvolume id
d9d99f
doesn't exist, then an error case will result.
d9d99f
d9d99f
It is possible to boot into a separate GRUB instance by exporting the
d9d99f
variable and loading the config file from the subvolume.
d9d99f
d9d99f
Signed-off-by: Jeff Mahoney <jeffm@suse.com>
d9d99f
---
d9d99f
 grub-core/fs/btrfs.c | 552 +++++++++++++++++++++++++++++++++++++++++++++++++--
d9d99f
 include/grub/btrfs.h |   1 +
d9d99f
 2 files changed, 533 insertions(+), 20 deletions(-)
d9d99f
d9d99f
diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c
d9d99f
index be195448dbe..51ed63d429b 100644
d9d99f
--- a/grub-core/fs/btrfs.c
d9d99f
+++ b/grub-core/fs/btrfs.c
d9d99f
@@ -29,6 +29,9 @@
d9d99f
 #include <minilzo.h>
d9d99f
 #include <grub/i18n.h>
d9d99f
 #include <grub/btrfs.h>
d9d99f
+#include <grub/command.h>
d9d99f
+#include <grub/env.h>
d9d99f
+#include <grub/extcmd.h>
d9d99f
 
d9d99f
 GRUB_MOD_LICENSE ("GPLv3+");
d9d99f
 
d9d99f
@@ -64,9 +67,11 @@ struct grub_btrfs_superblock
d9d99f
   grub_uint64_t generation;
d9d99f
   grub_uint64_t root_tree;
d9d99f
   grub_uint64_t chunk_tree;
d9d99f
-  grub_uint8_t dummy2[0x20];
d9d99f
+  grub_uint8_t dummy2[0x18];
d9d99f
+  grub_uint64_t bytes_used;
d9d99f
   grub_uint64_t root_dir_objectid;
d9d99f
-  grub_uint8_t dummy3[0x41];
d9d99f
+  grub_uint64_t num_devices;
d9d99f
+  grub_uint8_t dummy3[0x39];
d9d99f
   struct grub_btrfs_device this_device;
d9d99f
   char label[0x100];
d9d99f
   grub_uint8_t dummy4[0x100];
d9d99f
@@ -105,6 +110,7 @@ struct grub_btrfs_data
d9d99f
   grub_uint64_t exttree;
d9d99f
   grub_size_t extsize;
d9d99f
   struct grub_btrfs_extent_data *extent;
d9d99f
+  grub_uint64_t fs_tree;
d9d99f
 };
d9d99f
 
d9d99f
 struct grub_btrfs_chunk_item
d9d99f
@@ -171,6 +177,14 @@ struct grub_btrfs_leaf_descriptor
d9d99f
   } *data;
d9d99f
 };
d9d99f
 
d9d99f
+struct grub_btrfs_root_ref
d9d99f
+{
d9d99f
+  grub_uint64_t dirid;
d9d99f
+  grub_uint64_t sequence;
d9d99f
+  grub_uint16_t name_len;
d9d99f
+  const char name[0];
d9d99f
+} __attribute__ ((packed));
d9d99f
+
d9d99f
 struct grub_btrfs_time
d9d99f
 {
d9d99f
   grub_int64_t sec;
d9d99f
@@ -215,6 +229,14 @@ struct grub_btrfs_extent_data
d9d99f
 
d9d99f
 #define GRUB_BTRFS_OBJECT_ID_CHUNK 0x100
d9d99f
 
d9d99f
+#define GRUB_BTRFS_ROOT_TREE_OBJECTID 1ULL
d9d99f
+#define GRUB_BTRFS_FS_TREE_OBJECTID 5ULL
d9d99f
+#define GRUB_BTRFS_ROOT_REF_KEY     156
d9d99f
+#define GRUB_BTRFS_ROOT_ITEM_KEY     132
d9d99f
+
d9d99f
+static grub_uint64_t btrfs_default_subvolid = 0;
d9d99f
+static char *btrfs_default_subvol = NULL;
d9d99f
+
d9d99f
 static grub_disk_addr_t superblock_sectors[] = { 64 * 2, 64 * 1024 * 2,
d9d99f
   256 * 1048576 * 2, 1048576ULL * 1048576ULL * 2
d9d99f
 };
d9d99f
@@ -837,6 +859,62 @@ grub_btrfs_read_logical (struct grub_btrfs_data *data, grub_disk_addr_t addr,
d9d99f
   return GRUB_ERR_NONE;
d9d99f
 }
d9d99f
 
d9d99f
+static grub_err_t
d9d99f
+get_fs_root(struct grub_btrfs_data *data, grub_uint64_t tree,
d9d99f
+            grub_uint64_t objectid, grub_uint64_t offset,
d9d99f
+            grub_uint64_t *fs_root);
d9d99f
+
d9d99f
+static grub_err_t
d9d99f
+lookup_root_by_id(struct grub_btrfs_data *data, grub_uint64_t id)
d9d99f
+{
d9d99f
+  grub_err_t err;
d9d99f
+  grub_uint64_t tree;
d9d99f
+
d9d99f
+  err = get_fs_root(data, data->sblock.root_tree, id, -1, &tree);
d9d99f
+  if (!err)
d9d99f
+    data->fs_tree = tree;
d9d99f
+  return err;
d9d99f
+}
d9d99f
+
d9d99f
+static grub_err_t
d9d99f
+find_path (struct grub_btrfs_data *data,
d9d99f
+	   const char *path, struct grub_btrfs_key *key,
d9d99f
+	   grub_uint64_t *tree, grub_uint8_t *type);
d9d99f
+
d9d99f
+static grub_err_t
d9d99f
+lookup_root_by_name(struct grub_btrfs_data *data, const char *path)
d9d99f
+{
d9d99f
+  grub_err_t err;
d9d99f
+  grub_uint64_t tree = 0;
d9d99f
+  grub_uint8_t type;
d9d99f
+  struct grub_btrfs_key key;
d9d99f
+
d9d99f
+  err = find_path (data, path, &key, &tree, &type);
d9d99f
+  if (err)
d9d99f
+      return grub_error(GRUB_ERR_FILE_NOT_FOUND, "couldn't locate %s\n", path);
d9d99f
+
d9d99f
+  if (key.object_id != grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK) || tree == 0)
d9d99f
+    return grub_error(GRUB_ERR_BAD_FILE_TYPE, "%s: not a subvolume\n", path);
d9d99f
+
d9d99f
+  data->fs_tree = tree;
d9d99f
+  return GRUB_ERR_NONE;
d9d99f
+}
d9d99f
+
d9d99f
+static grub_err_t
d9d99f
+btrfs_handle_subvol(struct grub_btrfs_data *data __attribute__ ((unused)))
d9d99f
+{
d9d99f
+  if (btrfs_default_subvol)
d9d99f
+    return lookup_root_by_name(data, btrfs_default_subvol);
d9d99f
+
d9d99f
+  if (btrfs_default_subvolid)
d9d99f
+    return lookup_root_by_id(data, btrfs_default_subvolid);
d9d99f
+
d9d99f
+  data->fs_tree = 0;
d9d99f
+
d9d99f
+  return GRUB_ERR_NONE;
d9d99f
+}
d9d99f
+
d9d99f
+
d9d99f
 static struct grub_btrfs_data *
d9d99f
 grub_btrfs_mount (grub_device_t dev)
d9d99f
 {
d9d99f
@@ -872,6 +950,13 @@ grub_btrfs_mount (grub_device_t dev)
d9d99f
   data->devices_attached[0].dev = dev;
d9d99f
   data->devices_attached[0].id = data->sblock.this_device.device_id;
d9d99f
 
d9d99f
+  err = btrfs_handle_subvol (data);
d9d99f
+  if (err)
d9d99f
+    {
d9d99f
+      grub_free (data);
d9d99f
+      return NULL;
d9d99f
+    }
d9d99f
+
d9d99f
   return data;
d9d99f
 }
d9d99f
 
d9d99f
@@ -1232,6 +1317,91 @@ get_root (struct grub_btrfs_data *data, struct grub_btrfs_key *key,
d9d99f
   return GRUB_ERR_NONE;
d9d99f
 }
d9d99f
 
d9d99f
+static grub_err_t
d9d99f
+find_pathname(struct grub_btrfs_data *data, grub_uint64_t objectid,
d9d99f
+              grub_uint64_t fs_root, const char *name, char **pathname)
d9d99f
+{
d9d99f
+  grub_err_t err;
d9d99f
+  struct grub_btrfs_key key = {
d9d99f
+    .object_id = objectid,
d9d99f
+    .type = GRUB_BTRFS_ITEM_TYPE_INODE_REF,
d9d99f
+    .offset = 0,
d9d99f
+  };
d9d99f
+  struct grub_btrfs_key key_out;
d9d99f
+  struct grub_btrfs_leaf_descriptor desc;
d9d99f
+  char *p = grub_strdup (name);
d9d99f
+  grub_disk_addr_t elemaddr;
d9d99f
+  grub_size_t elemsize;
d9d99f
+  grub_size_t alloc = grub_strlen(name) + 1;
d9d99f
+
d9d99f
+  err = lower_bound(data, &key, &key_out, fs_root,
d9d99f
+                    &elemaddr, &elemsize, &desc, 0);
d9d99f
+  if (err)
d9d99f
+    return grub_error(err, "lower_bound caught %d\n", err);
d9d99f
+
d9d99f
+  if (key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_REF)
d9d99f
+    next(data, &desc, &elemaddr, &elemsize, &key_out);
d9d99f
+
d9d99f
+  if (key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_REF)
d9d99f
+    {
d9d99f
+      return grub_error(GRUB_ERR_FILE_NOT_FOUND,
d9d99f
+                        "Can't find inode ref for {%"PRIuGRUB_UINT64_T
d9d99f
+                        ", %u, %"PRIuGRUB_UINT64_T"} %"PRIuGRUB_UINT64_T
d9d99f
+                        "/%"PRIuGRUB_SIZE"\n",
d9d99f
+                        key_out.object_id, key_out.type,
d9d99f
+                        key_out.offset, elemaddr, elemsize);
d9d99f
+    }
d9d99f
+
d9d99f
+
d9d99f
+  while (key_out.type == GRUB_BTRFS_ITEM_TYPE_INODE_REF &&
d9d99f
+         key_out.object_id != key_out.offset) {
d9d99f
+    struct grub_btrfs_inode_ref *inode_ref;
d9d99f
+    char *new;
d9d99f
+
d9d99f
+    inode_ref = grub_malloc(elemsize + 1);
d9d99f
+    if (!inode_ref)
d9d99f
+      return grub_error(GRUB_ERR_OUT_OF_MEMORY,
d9d99f
+                        "couldn't allocate memory for inode_ref (%"PRIuGRUB_SIZE")\n", elemsize);
d9d99f
+
d9d99f
+    err = grub_btrfs_read_logical(data, elemaddr, inode_ref, elemsize, 0);
d9d99f
+    if (err)
d9d99f
+      return grub_error(err, "read_logical caught %d\n", err);
d9d99f
+
d9d99f
+    alloc += grub_le_to_cpu16 (inode_ref->n) + 2;
d9d99f
+    new = grub_malloc(alloc);
d9d99f
+    if (!new)
d9d99f
+      return grub_error(GRUB_ERR_OUT_OF_MEMORY,
d9d99f
+                        "couldn't allocate memory for name (%"PRIuGRUB_SIZE")\n", alloc);
d9d99f
+
d9d99f
+    grub_memcpy(new, inode_ref->name, grub_le_to_cpu16 (inode_ref->n));
d9d99f
+    if (p)
d9d99f
+      {
d9d99f
+        new[grub_le_to_cpu16 (inode_ref->n)] = '/';
d9d99f
+        grub_strcpy (new + grub_le_to_cpu16 (inode_ref->n) + 1, p);
d9d99f
+        grub_free(p);
d9d99f
+      }
d9d99f
+    else
d9d99f
+      new[grub_le_to_cpu16 (inode_ref->n)] = 0;
d9d99f
+    grub_free(inode_ref);
d9d99f
+
d9d99f
+    p = new;
d9d99f
+
d9d99f
+    key.object_id = key_out.offset;
d9d99f
+
d9d99f
+    err = lower_bound(data, &key, &key_out, fs_root, &elemaddr,
d9d99f
+                      &elemsize, &desc, 0);
d9d99f
+    if (err)
d9d99f
+      return grub_error(err, "lower_bound caught %d\n", err);
d9d99f
+
d9d99f
+    if (key_out.type != GRUB_BTRFS_ITEM_TYPE_INODE_REF)
d9d99f
+      next(data, &desc, &elemaddr, &elemsize, &key_out);
d9d99f
+
d9d99f
+  }
d9d99f
+
d9d99f
+  *pathname = p;
d9d99f
+  return 0;
d9d99f
+}
d9d99f
+
d9d99f
 static grub_err_t
d9d99f
 find_path (struct grub_btrfs_data *data,
d9d99f
 	   const char *path, struct grub_btrfs_key *key,
d9d99f
@@ -1250,14 +1420,26 @@ find_path (struct grub_btrfs_data *data,
d9d99f
   char *origpath = NULL;
d9d99f
   unsigned symlinks_max = 32;
d9d99f
 
d9d99f
-  err = get_root (data, key, tree, type);
d9d99f
-  if (err)
d9d99f
-    return err;
d9d99f
-
d9d99f
   origpath = grub_strdup (path);
d9d99f
   if (!origpath)
d9d99f
     return grub_errno;
d9d99f
 
d9d99f
+  if (data->fs_tree)
d9d99f
+    {
d9d99f
+      *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
d9d99f
+      *tree = data->fs_tree;
d9d99f
+      /* This is a tree root, so everything starts at objectid 256 */
d9d99f
+      key->object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK);
d9d99f
+      key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
d9d99f
+      key->offset = 0;
d9d99f
+    }
d9d99f
+  else
d9d99f
+    {
d9d99f
+      err = get_root (data, key, tree, type);
d9d99f
+      if (err)
d9d99f
+	return err;
d9d99f
+    }
d9d99f
+
d9d99f
   while (1)
d9d99f
     {
d9d99f
       while (path[0] == '/')
d9d99f
@@ -1430,9 +1612,21 @@ find_path (struct grub_btrfs_data *data,
d9d99f
 	  path = path_alloc = tmp;
d9d99f
 	  if (path[0] == '/')
d9d99f
 	    {
d9d99f
-	      err = get_root (data, key, tree, type);
d9d99f
-	      if (err)
d9d99f
-		return err;
d9d99f
+	      if (data->fs_tree)
d9d99f
+		{
d9d99f
+		  *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY;
d9d99f
+		  *tree = data->fs_tree;
d9d99f
+		  /* This is a tree root, so everything starts at objectid 256 */
d9d99f
+		  key->object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK);
d9d99f
+		  key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM;
d9d99f
+		  key->offset = 0;
d9d99f
+		}
d9d99f
+	      else
d9d99f
+		{
d9d99f
+		  err = get_root (data, key, tree, type);
d9d99f
+		  if (err)
d9d99f
+		    return err;
d9d99f
+		}
d9d99f
 	    }
d9d99f
 	  continue;
d9d99f
 	}
d9d99f
@@ -1673,18 +1867,10 @@ grub_btrfs_read (grub_file_t file, char *buf, grub_size_t len)
d9d99f
 				 data->tree, file->offset, buf, len);
d9d99f
 }
d9d99f
 
d9d99f
-static grub_err_t
d9d99f
-grub_btrfs_uuid (grub_device_t device, char **uuid)
d9d99f
+static char *
d9d99f
+btrfs_unparse_uuid(struct grub_btrfs_data *data)
d9d99f
 {
d9d99f
-  struct grub_btrfs_data *data;
d9d99f
-
d9d99f
-  *uuid = NULL;
d9d99f
-
d9d99f
-  data = grub_btrfs_mount (device);
d9d99f
-  if (!data)
d9d99f
-    return grub_errno;
d9d99f
-
d9d99f
-  *uuid = grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
d9d99f
+  return  grub_xasprintf ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
d9d99f
 			  grub_be_to_cpu16 (data->sblock.uuid[0]),
d9d99f
 			  grub_be_to_cpu16 (data->sblock.uuid[1]),
d9d99f
 			  grub_be_to_cpu16 (data->sblock.uuid[2]),
d9d99f
@@ -1693,6 +1879,20 @@ grub_btrfs_uuid (grub_device_t device, char **uuid)
d9d99f
 			  grub_be_to_cpu16 (data->sblock.uuid[5]),
d9d99f
 			  grub_be_to_cpu16 (data->sblock.uuid[6]),
d9d99f
 			  grub_be_to_cpu16 (data->sblock.uuid[7]));
d9d99f
+}
d9d99f
+
d9d99f
+static grub_err_t
d9d99f
+grub_btrfs_uuid (grub_device_t device, char **uuid)
d9d99f
+{
d9d99f
+  struct grub_btrfs_data *data;
d9d99f
+
d9d99f
+  *uuid = NULL;
d9d99f
+
d9d99f
+  data = grub_btrfs_mount (device);
d9d99f
+  if (!data)
d9d99f
+    return grub_errno;
d9d99f
+
d9d99f
+  *uuid = btrfs_unparse_uuid(data);
d9d99f
 
d9d99f
   grub_btrfs_unmount (data);
d9d99f
 
d9d99f
@@ -1749,6 +1949,242 @@ grub_btrfs_embed (grub_device_t device __attribute__ ((unused)),
d9d99f
 }
d9d99f
 #endif
d9d99f
 
d9d99f
+static grub_err_t
d9d99f
+grub_cmd_btrfs_info (grub_command_t cmd __attribute__ ((unused)), int argc,
d9d99f
+		     char **argv)
d9d99f
+{
d9d99f
+  grub_device_t dev;
d9d99f
+  char *devname;
d9d99f
+  struct grub_btrfs_data *data;
d9d99f
+  char *uuid;
d9d99f
+
d9d99f
+  if (argc < 1)
d9d99f
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name required");
d9d99f
+
d9d99f
+  devname = grub_file_get_device_name(argv[0]);
d9d99f
+
d9d99f
+  if (!devname)
d9d99f
+    return grub_errno;
d9d99f
+
d9d99f
+  dev = grub_device_open (devname);
d9d99f
+  grub_free (devname);
d9d99f
+  if (!dev)
d9d99f
+    return grub_errno;
d9d99f
+
d9d99f
+  data = grub_btrfs_mount (dev);
d9d99f
+  if (!data)
d9d99f
+    {
d9d99f
+      grub_device_close(dev);
d9d99f
+      return grub_error (GRUB_ERR_BAD_ARGUMENT, "failed to open fs");
d9d99f
+    }
d9d99f
+
d9d99f
+  if (data->sblock.label)
d9d99f
+    grub_printf("Label: '%s' ", data->sblock.label);
d9d99f
+  else
d9d99f
+    grub_printf("Label: none ");
d9d99f
+
d9d99f
+  uuid = btrfs_unparse_uuid(data);
d9d99f
+
d9d99f
+  grub_printf(" uuid: %s\n\tTotal devices %" PRIuGRUB_UINT64_T
d9d99f
+              " FS bytes used %" PRIuGRUB_UINT64_T "\n",
d9d99f
+	      uuid, grub_cpu_to_le64(data->sblock.num_devices),
d9d99f
+	      grub_cpu_to_le64(data->sblock.bytes_used));
d9d99f
+
d9d99f
+  grub_btrfs_unmount (data);
d9d99f
+
d9d99f
+  return 0;
d9d99f
+}
d9d99f
+
d9d99f
+static grub_err_t
d9d99f
+get_fs_root(struct grub_btrfs_data *data, grub_uint64_t tree,
d9d99f
+            grub_uint64_t objectid, grub_uint64_t offset,
d9d99f
+            grub_uint64_t *fs_root)
d9d99f
+{
d9d99f
+  grub_err_t err;
d9d99f
+  struct grub_btrfs_key key_in = {
d9d99f
+    .object_id = objectid,
d9d99f
+    .type = GRUB_BTRFS_ROOT_ITEM_KEY,
d9d99f
+    .offset = offset,
d9d99f
+  }, key_out;
d9d99f
+  struct grub_btrfs_leaf_descriptor desc;
d9d99f
+  grub_disk_addr_t elemaddr;
d9d99f
+  grub_size_t elemsize;
d9d99f
+  struct grub_btrfs_root_item ri;
d9d99f
+
d9d99f
+  err = lower_bound(data, &key_in, &key_out, tree,
d9d99f
+                    &elemaddr, &elemsize, &desc, 0);
d9d99f
+
d9d99f
+  if (err)
d9d99f
+    return err;
d9d99f
+
d9d99f
+  if (key_out.type != GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM || elemaddr == 0)
d9d99f
+    return grub_error(GRUB_ERR_FILE_NOT_FOUND,
d9d99f
+                    N_("can't find fs root for subvol %"PRIuGRUB_UINT64_T"\n"),
d9d99f
+                    key_in.object_id);
d9d99f
+
d9d99f
+  err = grub_btrfs_read_logical (data, elemaddr, &ri, sizeof (ri), 0);
d9d99f
+  if (err)
d9d99f
+    return err;
d9d99f
+
d9d99f
+  *fs_root = ri.tree;
d9d99f
+
d9d99f
+  return GRUB_ERR_NONE;
d9d99f
+}
d9d99f
+
d9d99f
+static const struct grub_arg_option options[] = {
d9d99f
+  {"output", 'o', 0, N_("Output to a variable instead of the console."),
d9d99f
+   N_("VARNAME"), ARG_TYPE_STRING},
d9d99f
+  {"path-only", 'p', 0, N_("Show only the path of the subvolume."), 0, 0},
d9d99f
+  {"id-only", 'i', 0, N_("Show only the id of the subvolume."), 0, 0},
d9d99f
+  {0, 0, 0, 0, 0, 0}
d9d99f
+};
d9d99f
+
d9d99f
+static grub_err_t
d9d99f
+grub_cmd_btrfs_list_subvols (struct grub_extcmd_context *ctxt,
d9d99f
+			     int argc, char **argv)
d9d99f
+{
d9d99f
+  struct grub_btrfs_data *data;
d9d99f
+  grub_device_t dev;
d9d99f
+  char *devname;
d9d99f
+  grub_uint64_t tree;
d9d99f
+  struct grub_btrfs_key key_in = {
d9d99f
+    .object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_FS_TREE_OBJECTID),
d9d99f
+    .type = GRUB_BTRFS_ROOT_REF_KEY,
d9d99f
+    .offset = 0,
d9d99f
+  }, key_out;
d9d99f
+  struct grub_btrfs_leaf_descriptor desc;
d9d99f
+  grub_disk_addr_t elemaddr;
d9d99f
+  grub_uint64_t fs_root = 0;
d9d99f
+  grub_size_t elemsize;
d9d99f
+  grub_size_t allocated = 0;
d9d99f
+  int r = 0;
d9d99f
+  grub_err_t err;
d9d99f
+  char *buf = NULL;
d9d99f
+  int print = 1;
d9d99f
+  int path_only = ctxt->state[1].set;
d9d99f
+  int num_only = ctxt->state[2].set;
d9d99f
+  char *varname = NULL;
d9d99f
+  char *output = NULL;
d9d99f
+
d9d99f
+  if (ctxt->state[0].set) {
d9d99f
+    varname = ctxt->state[0].arg;
d9d99f
+    print = 0;
d9d99f
+  }
d9d99f
+
d9d99f
+  if (argc < 1)
d9d99f
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name required");
d9d99f
+
d9d99f
+  devname = grub_file_get_device_name(argv[0]);
d9d99f
+  if (!devname)
d9d99f
+    return grub_errno;
d9d99f
+
d9d99f
+  dev = grub_device_open (devname);
d9d99f
+  grub_free (devname);
d9d99f
+  if (!dev)
d9d99f
+    return grub_errno;
d9d99f
+
d9d99f
+  data = grub_btrfs_mount(dev);
d9d99f
+  if (!data)
d9d99f
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, "could not open device");
d9d99f
+
d9d99f
+  tree = data->sblock.root_tree;
d9d99f
+  err = get_fs_root(data, tree, grub_cpu_to_le64_compile_time (GRUB_BTRFS_FS_TREE_OBJECTID),
d9d99f
+                    0, &fs_root);
d9d99f
+  if (err)
d9d99f
+    goto out;
d9d99f
+
d9d99f
+  err = lower_bound(data, &key_in, &key_out, tree,
d9d99f
+                    &elemaddr, &elemsize, &desc, 0);
d9d99f
+
d9d99f
+  if (err)
d9d99f
+    {
d9d99f
+      grub_btrfs_unmount(data);
d9d99f
+      return err;
d9d99f
+    }
d9d99f
+
d9d99f
+  if (key_out.type != GRUB_BTRFS_ITEM_TYPE_ROOT_REF || elemaddr == 0)
d9d99f
+    {
d9d99f
+      r = next(data, &desc, &elemaddr, &elemsize, &key_out);
d9d99f
+    }
d9d99f
+
d9d99f
+  if (key_out.type != GRUB_BTRFS_ITEM_TYPE_ROOT_REF) {
d9d99f
+    err = GRUB_ERR_FILE_NOT_FOUND;
d9d99f
+    grub_error(GRUB_ERR_FILE_NOT_FOUND, N_("can't find root refs"));
d9d99f
+    goto out;
d9d99f
+  }
d9d99f
+
d9d99f
+  do
d9d99f
+    {
d9d99f
+      struct grub_btrfs_root_ref *ref;
d9d99f
+      char *p = NULL;
d9d99f
+
d9d99f
+      if (key_out.type != GRUB_BTRFS_ITEM_TYPE_ROOT_REF)
d9d99f
+        {
d9d99f
+          r = 0;
d9d99f
+          break;
d9d99f
+        }
d9d99f
+
d9d99f
+      if (elemsize > allocated)
d9d99f
+        {
d9d99f
+          grub_free(buf);
d9d99f
+          allocated = 2 * elemsize;
d9d99f
+          buf = grub_malloc(allocated + 1);
d9d99f
+          if (!buf)
d9d99f
+            {
d9d99f
+              r = -grub_errno;
d9d99f
+              break;
d9d99f
+            }
d9d99f
+        }
d9d99f
+      ref = (struct grub_btrfs_root_ref *)buf;
d9d99f
+
d9d99f
+      err = grub_btrfs_read_logical(data, elemaddr, buf, elemsize, 0);
d9d99f
+      if (err)
d9d99f
+        {
d9d99f
+          r = -err;
d9d99f
+          break;
d9d99f
+        }
d9d99f
+        buf[elemsize] = 0;
d9d99f
+
d9d99f
+      find_pathname(data, ref->dirid, fs_root, ref->name, &p);
d9d99f
+
d9d99f
+      if (print)
d9d99f
+        {
d9d99f
+          if (num_only)
d9d99f
+            grub_printf("ID %"PRIuGRUB_UINT64_T"\n", key_out.offset);
d9d99f
+          else if (path_only)
d9d99f
+            grub_printf("%s\n", p);
d9d99f
+          else
d9d99f
+            grub_printf("ID %"PRIuGRUB_UINT64_T" path %s\n", key_out.offset, p);
d9d99f
+        } else {
d9d99f
+          char *old = output;
d9d99f
+          if (num_only)
d9d99f
+            output = grub_xasprintf("%s%"PRIuGRUB_UINT64_T"\n",
d9d99f
+                                    old ?: "", key_out.offset);
d9d99f
+          else if (path_only)
d9d99f
+            output = grub_xasprintf("%s%s\n", old ?: "", p);
d9d99f
+          else
d9d99f
+            output = grub_xasprintf("%sID %"PRIuGRUB_UINT64_T" path %s\n",
d9d99f
+                                    old ?: "", key_out.offset, p);
d9d99f
+
d9d99f
+          if (old)
d9d99f
+            grub_free(old);
d9d99f
+        }
d9d99f
+
d9d99f
+      r = next(data, &desc, &elemaddr, &elemsize, &key_out);
d9d99f
+  } while(r > 0);
d9d99f
+
d9d99f
+  if (output)
d9d99f
+    grub_env_set(varname, output);
d9d99f
+
d9d99f
+out:
d9d99f
+  free_iterator(&desc);
d9d99f
+  grub_btrfs_unmount(data);
d9d99f
+
d9d99f
+  grub_device_close (dev);
d9d99f
+
d9d99f
+  return 0;
d9d99f
+}
d9d99f
+
d9d99f
 static struct grub_fs grub_btrfs_fs = {
d9d99f
   .name = "btrfs",
d9d99f
   .dir = grub_btrfs_dir,
d9d99f
@@ -1764,12 +2200,88 @@ static struct grub_fs grub_btrfs_fs = {
d9d99f
 #endif
d9d99f
 };
d9d99f
 
d9d99f
+static grub_command_t cmd_info;
d9d99f
+static grub_extcmd_t cmd_list_subvols;
d9d99f
+
d9d99f
+static char *
d9d99f
+subvolid_set_env (struct grub_env_var *var __attribute__ ((unused)),
d9d99f
+                  const char *val)
d9d99f
+{
d9d99f
+  unsigned long long result = 0;
d9d99f
+
d9d99f
+  grub_errno = GRUB_ERR_NONE;
d9d99f
+  if (*val)
d9d99f
+    {
d9d99f
+      result = grub_strtoull(val, NULL, 10);
d9d99f
+      if (grub_errno)
d9d99f
+        return NULL;
d9d99f
+    }
d9d99f
+
d9d99f
+  grub_free (btrfs_default_subvol);
d9d99f
+  btrfs_default_subvol = NULL;
d9d99f
+  btrfs_default_subvolid = result;
d9d99f
+  return grub_strdup(val);
d9d99f
+}
d9d99f
+
d9d99f
+static const char *
d9d99f
+subvolid_get_env (struct grub_env_var *var __attribute__ ((unused)),
d9d99f
+                  const char *val __attribute__ ((unused)))
d9d99f
+{
d9d99f
+  if (btrfs_default_subvol)
d9d99f
+    return grub_xasprintf("subvol:%s", btrfs_default_subvol);
d9d99f
+  else if (btrfs_default_subvolid)
d9d99f
+    return grub_xasprintf("%"PRIuGRUB_UINT64_T, btrfs_default_subvolid);
d9d99f
+  else
d9d99f
+    return "";
d9d99f
+}
d9d99f
+
d9d99f
+static char *
d9d99f
+subvol_set_env (struct grub_env_var *var __attribute__ ((unused)),
d9d99f
+                const char *val)
d9d99f
+{
d9d99f
+  grub_free (btrfs_default_subvol);
d9d99f
+  btrfs_default_subvol = grub_strdup (val);
d9d99f
+  btrfs_default_subvolid = 0;
d9d99f
+  return grub_strdup(val);
d9d99f
+}
d9d99f
+
d9d99f
+static const char *
d9d99f
+subvol_get_env (struct grub_env_var *var __attribute__ ((unused)),
d9d99f
+                const char *val __attribute__ ((unused)))
d9d99f
+{
d9d99f
+  if (btrfs_default_subvol)
d9d99f
+    return btrfs_default_subvol;
d9d99f
+  else if (btrfs_default_subvolid)
d9d99f
+    return grub_xasprintf("subvolid:%" PRIuGRUB_UINT64_T,
d9d99f
+                          btrfs_default_subvolid);
d9d99f
+  else
d9d99f
+    return "";
d9d99f
+}
d9d99f
+
d9d99f
 GRUB_MOD_INIT (btrfs)
d9d99f
 {
d9d99f
   grub_fs_register (&grub_btrfs_fs);
d9d99f
+  cmd_info = grub_register_command("btrfs-info", grub_cmd_btrfs_info,
d9d99f
+				   "DEVICE",
d9d99f
+				   "Print BtrFS info about DEVICE.");
d9d99f
+  cmd_list_subvols = grub_register_extcmd("btrfs-list-subvols",
d9d99f
+					 grub_cmd_btrfs_list_subvols, 0,
d9d99f
+					 "[-p|-n] [-o var] DEVICE",
d9d99f
+					 "Print list of BtrFS subvolumes on "
d9d99f
+					 "DEVICE.", options);
d9d99f
+  grub_register_variable_hook ("btrfs_subvol", subvol_get_env,
d9d99f
+                               subvol_set_env);
d9d99f
+  grub_register_variable_hook ("btrfs_subvolid", subvolid_get_env,
d9d99f
+                               subvolid_set_env);
d9d99f
 }
d9d99f
 
d9d99f
 GRUB_MOD_FINI (btrfs)
d9d99f
 {
d9d99f
+  grub_register_variable_hook ("btrfs_subvol", NULL, NULL);
d9d99f
+  grub_register_variable_hook ("btrfs_subvolid", NULL, NULL);
d9d99f
+  grub_unregister_command (cmd_info);
d9d99f
+  grub_unregister_extcmd (cmd_list_subvols);
d9d99f
   grub_fs_unregister (&grub_btrfs_fs);
d9d99f
 }
d9d99f
+
d9d99f
+// vim: si et sw=2:
d9d99f
diff --git a/include/grub/btrfs.h b/include/grub/btrfs.h
d9d99f
index 9d93fb6c182..234ad976771 100644
d9d99f
--- a/include/grub/btrfs.h
d9d99f
+++ b/include/grub/btrfs.h
d9d99f
@@ -29,6 +29,7 @@ enum
d9d99f
     GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM = 0x84,
d9d99f
     GRUB_BTRFS_ITEM_TYPE_ROOT_BACKREF = 0x90,
d9d99f
     GRUB_BTRFS_ITEM_TYPE_DEVICE = 0xd8,
d9d99f
+    GRUB_BTRFS_ITEM_TYPE_ROOT_REF = 0x9c,
d9d99f
     GRUB_BTRFS_ITEM_TYPE_CHUNK = 0xe4
d9d99f
   };
d9d99f