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