forked from pool/grub2
660 lines
18 KiB
Diff
660 lines
18 KiB
Diff
|
From: Jeff Mahoney <jeffm@suse.com>
|
||
|
Subject: 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 | 529 +++++++++++++++++++++++++++++++++++++++++++++++++--
|
||
|
1 file changed, 518 insertions(+), 11 deletions(-)
|
||
|
|
||
|
--- a/grub-core/fs/btrfs.c 2013-07-29 09:26:01.585376955 -0400
|
||
|
+++ b/grub-core/fs/btrfs.c 2013-07-29 09:26:02.513377553 -0400
|
||
|
@@ -28,6 +28,9 @@
|
||
|
#include <grub/deflate.h>
|
||
|
#include <minilzo.h>
|
||
|
#include <grub/i18n.h>
|
||
|
+#include <grub/command.h>
|
||
|
+#include <grub/env.h>
|
||
|
+#include <grub/extcmd.h>
|
||
|
|
||
|
GRUB_MOD_LICENSE ("GPLv3+");
|
||
|
|
||
|
@@ -63,9 +66,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];
|
||
|
@@ -104,6 +109,7 @@ struct grub_btrfs_data
|
||
|
grub_uint64_t exttree;
|
||
|
grub_size_t extsize;
|
||
|
struct grub_btrfs_extent_data *extent;
|
||
|
+ grub_uint64_t fs_tree;
|
||
|
};
|
||
|
|
||
|
enum
|
||
|
@@ -113,6 +119,7 @@ enum
|
||
|
GRUB_BTRFS_ITEM_TYPE_DIR_ITEM = 0x54,
|
||
|
GRUB_BTRFS_ITEM_TYPE_EXTENT_ITEM = 0x6c,
|
||
|
GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM = 0x84,
|
||
|
+ GRUB_BTRFS_ITEM_TYPE_ROOT_REF = 0x9c,
|
||
|
GRUB_BTRFS_ITEM_TYPE_DEVICE = 0xd8,
|
||
|
GRUB_BTRFS_ITEM_TYPE_CHUNK = 0xe4
|
||
|
};
|
||
|
@@ -195,6 +202,21 @@ struct grub_btrfs_root_item
|
||
|
grub_uint64_t inode;
|
||
|
};
|
||
|
|
||
|
+struct grub_btrfs_inode_ref
|
||
|
+{
|
||
|
+ grub_uint64_t index;
|
||
|
+ grub_uint16_t name_len;
|
||
|
+ const char name[0];
|
||
|
+} __attribute__ ((packed));
|
||
|
+
|
||
|
+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;
|
||
|
@@ -239,6 +261,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
|
||
|
};
|
||
|
@@ -854,6 +884,62 @@ grub_btrfs_read_logical (struct grub_btr
|
||
|
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_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)
|
||
|
{
|
||
|
@@ -889,6 +975,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;
|
||
|
}
|
||
|
|
||
|
@@ -1197,6 +1290,91 @@ grub_btrfs_extent_read (struct grub_btrf
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
+ int 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 (%d)\n");
|
||
|
+
|
||
|
+ err = grub_btrfs_read_logical(data, elemaddr, inode_ref, elemsize, 0);
|
||
|
+ if (err)
|
||
|
+ return grub_error(err, "read_logical caught %d\n", err);
|
||
|
+
|
||
|
+ alloc += inode_ref->name_len + 2;
|
||
|
+ new = grub_malloc(alloc);
|
||
|
+ if (!new)
|
||
|
+ return grub_error(GRUB_ERR_OUT_OF_MEMORY,
|
||
|
+ "couldn't allocate memory for name (%d)\n", alloc);
|
||
|
+
|
||
|
+ grub_memcpy(new, inode_ref->name, inode_ref->name_len);
|
||
|
+ new[inode_ref->name_len] = 0;
|
||
|
+ grub_free(inode_ref);
|
||
|
+ if (p)
|
||
|
+ {
|
||
|
+ grub_strcat(new, "/");
|
||
|
+ grub_strcat(new, p);
|
||
|
+ new[alloc - 1] = 0;
|
||
|
+ grub_free(p);
|
||
|
+ }
|
||
|
+
|
||
|
+ 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,
|
||
|
grub_uint64_t *tree, grub_uint8_t *type)
|
||
|
@@ -1222,6 +1400,17 @@ find_path (struct grub_btrfs_data *data,
|
||
|
key->offset = 0;
|
||
|
follow_default = 1;
|
||
|
origpath = grub_strdup (path);
|
||
|
+
|
||
|
+ if (data->fs_tree)
|
||
|
+ {
|
||
|
+ *tree = data->fs_tree;
|
||
|
+
|
||
|
+ follow_default = 0; /* only the toplevel root has a special 'default' */
|
||
|
+
|
||
|
+ /* 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);
|
||
|
+ }
|
||
|
+
|
||
|
if (!origpath)
|
||
|
return grub_errno;
|
||
|
|
||
|
@@ -1657,6 +1846,20 @@ grub_btrfs_read (grub_file_t file, char
|
||
|
data->tree, file->offset, buf, len);
|
||
|
}
|
||
|
|
||
|
+static char *
|
||
|
+btrfs_unparse_uuid(struct grub_btrfs_data *data)
|
||
|
+{
|
||
|
+ 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]),
|
||
|
+ grub_be_to_cpu16 (data->sblock.uuid[3]),
|
||
|
+ grub_be_to_cpu16 (data->sblock.uuid[4]),
|
||
|
+ 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)
|
||
|
{
|
||
|
@@ -1668,15 +1871,7 @@ grub_btrfs_uuid (grub_device_t device, c
|
||
|
if (!data)
|
||
|
return grub_errno;
|
||
|
|
||
|
- *uuid = 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]),
|
||
|
- grub_be_to_cpu16 (data->sblock.uuid[3]),
|
||
|
- grub_be_to_cpu16 (data->sblock.uuid[4]),
|
||
|
- grub_be_to_cpu16 (data->sblock.uuid[5]),
|
||
|
- grub_be_to_cpu16 (data->sblock.uuid[6]),
|
||
|
- grub_be_to_cpu16 (data->sblock.uuid[7]));
|
||
|
+ *uuid = btrfs_unparse_uuid(data);
|
||
|
|
||
|
grub_btrfs_unmount (data);
|
||
|
|
||
|
@@ -1733,6 +1928,242 @@ grub_btrfs_embed (grub_device_t device _
|
||
|
}
|
||
|
#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(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 = grub_le_to_cpu64(data->sblock.root_tree);
|
||
|
+ err = get_fs_root(data, tree, grub_cpu_to_le64(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",
|
||
|
.dir = grub_btrfs_dir,
|
||
|
@@ -1748,12 +2179,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:
|