grub2/grub2-btrfs-06-subvol-mount.patch

534 lines
13 KiB
Diff

V2:
* Fix grub2-install --root-directory does not work for /boot/grub2/<arch> on
separate btrfs subvolume (boo#1098420)
v3:
* Fix executable stack on which function trampoline is constructed to support
closure (nested function). The closure sematic is replaced.
v4:
* Fix btrfs subvolume for platform modules not mounting at runtime when the
default subvolume is the topmost root tree (bsc#1228124)
--- a/grub-core/fs/btrfs.c
+++ b/grub-core/fs/btrfs.c
@@ -44,6 +44,7 @@
#include <grub/command.h>
#include <grub/env.h>
#include <grub/extcmd.h>
+#include <grub/list.h>
GRUB_MOD_LICENSE ("GPLv3+");
@@ -266,6 +267,12 @@
grub_btrfs_read_logical (struct grub_btrfs_data *data,
grub_disk_addr_t addr, void *buf, grub_size_t size,
int recursion_depth);
+static grub_err_t
+get_root (struct grub_btrfs_data *data, struct grub_btrfs_key *key,
+ grub_uint64_t *tree, grub_uint8_t *type);
+
+grub_uint64_t
+find_mtab_subvol_tree (const char *path, char **path_in_subvol);
static grub_err_t
read_sblock (grub_disk_t disk, struct grub_btrfs_superblock *sb)
@@ -1302,9 +1309,26 @@
grub_err_t err;
grub_uint64_t tree = 0;
grub_uint8_t type;
+ grub_uint64_t saved_tree;
struct grub_btrfs_key key;
+ if (path[0] == '\0')
+ {
+ data->fs_tree = 0;
+ return GRUB_ERR_NONE;
+ }
+
+ err = get_root (data, &key, &tree, &type);
+ if (err)
+ return err;
+
+ saved_tree = data->fs_tree;
+ data->fs_tree = tree;
+
err = find_path (data, path, &key, &tree, &type);
+
+ data->fs_tree = saved_tree;
+
if (err)
return grub_error(GRUB_ERR_FILE_NOT_FOUND, "couldn't locate %s\n", path);
@@ -2323,11 +2347,20 @@
grub_uint64_t tree;
grub_uint8_t type;
grub_size_t est_size = 0;
+ char *new_path = NULL;
if (!data)
return grub_errno;
- err = find_path (data, path, &key_in, &tree, &type);
+ tree = find_mtab_subvol_tree (path, &new_path);
+
+ if (tree)
+ data->fs_tree = tree;
+
+ err = find_path (data, new_path ? new_path : path, &key_in, &tree, &type);
+ if (new_path)
+ grub_free (new_path);
+
if (err)
{
grub_btrfs_unmount (data);
@@ -2454,11 +2487,21 @@
struct grub_btrfs_inode inode;
grub_uint8_t type;
struct grub_btrfs_key key_in;
+ grub_uint64_t tree;
+ char *new_path = NULL;
if (!data)
return grub_errno;
- err = find_path (data, name, &key_in, &data->tree, &type);
+ tree = find_mtab_subvol_tree (name, &new_path);
+
+ if (tree)
+ data->fs_tree = tree;
+
+ err = find_path (data, new_path ? new_path : name, &key_in, &data->tree, &type);
+ if (new_path)
+ grub_free (new_path);
+
if (err)
{
grub_btrfs_unmount (data);
@@ -2693,6 +2736,150 @@
return 0;
}
+struct grub_btrfs_mtab
+{
+ struct grub_btrfs_mtab *next;
+ struct grub_btrfs_mtab **prev;
+ char *path;
+ char *subvol;
+ grub_uint64_t tree;
+};
+
+typedef struct grub_btrfs_mtab* grub_btrfs_mtab_t;
+
+static struct grub_btrfs_mtab *btrfs_mtab;
+
+#define FOR_GRUB_MTAB(var) FOR_LIST_ELEMENTS (var, btrfs_mtab)
+#define FOR_GRUB_MTAB_SAFE(var, next) FOR_LIST_ELEMENTS_SAFE((var), (next), btrfs_mtab)
+
+static void
+add_mountpoint (const char *path, const char *subvol, grub_uint64_t tree)
+{
+ grub_btrfs_mtab_t m = grub_malloc (sizeof (*m));
+
+ m->path = grub_strdup (path);
+ m->subvol = grub_strdup (subvol);
+ m->tree = tree;
+ grub_list_push (GRUB_AS_LIST_P (&btrfs_mtab), GRUB_AS_LIST (m));
+}
+
+static grub_err_t
+grub_cmd_btrfs_mount_subvol (grub_command_t cmd __attribute__ ((unused)), int argc,
+ char **argv)
+{
+ char *devname, *dirname, *subvol;
+ struct grub_btrfs_key key_in;
+ grub_uint8_t type;
+ grub_uint64_t tree;
+ grub_uint64_t saved_tree;
+ grub_err_t err;
+ struct grub_btrfs_data *data = NULL;
+ grub_device_t dev = NULL;
+
+ if (argc < 3)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "required <dev> <dir> and <subvol>");
+
+ devname = grub_file_get_device_name(argv[0]);
+ dev = grub_device_open (devname);
+ grub_free (devname);
+
+ if (!dev)
+ {
+ err = grub_errno;
+ goto err_out;
+ }
+
+ dirname = argv[1];
+ subvol = argv[2];
+
+ data = grub_btrfs_mount (dev);
+ if (!data)
+ {
+ err = grub_errno;
+ goto err_out;
+ }
+
+ err = find_path (data, dirname, &key_in, &tree, &type);
+ if (err)
+ goto err_out;
+
+ if (type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY)
+ {
+ err = grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("not a directory"));
+ goto err_out;
+ }
+
+ err = get_root (data, &key_in, &tree, &type);
+
+ if (err)
+ goto err_out;
+
+ saved_tree = data->fs_tree;
+ data->fs_tree = tree;
+ err = find_path (data, subvol, &key_in, &tree, &type);
+ data->fs_tree = saved_tree;
+
+ if (err)
+ goto err_out;
+
+ if (key_in.object_id != grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK) || tree == 0)
+ {
+ err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "%s: not a subvolume\n", subvol);
+ goto err_out;
+ }
+
+ grub_btrfs_unmount (data);
+ grub_device_close (dev);
+ add_mountpoint (dirname, subvol, tree);
+
+ return GRUB_ERR_NONE;
+
+err_out:
+
+ if (data)
+ grub_btrfs_unmount (data);
+
+ if (dev)
+ grub_device_close (dev);
+
+ return err;
+}
+
+grub_uint64_t
+find_mtab_subvol_tree (const char *path, char **path_in_subvol)
+{
+ grub_btrfs_mtab_t m, cm;
+ grub_uint64_t tree;
+
+ if (!path || !path_in_subvol)
+ return 0;
+
+ *path_in_subvol = NULL;
+ tree = 0;
+ cm = NULL;
+
+ FOR_GRUB_MTAB (m)
+ {
+ if (grub_strncmp (path, m->path, grub_strlen (m->path)) == 0)
+ {
+ if (!cm)
+ cm = m;
+ else
+ if (grub_strcmp (m->path, cm->path) > 0)
+ cm = m;
+ }
+ }
+
+ if (cm)
+ {
+ const char *s = path + grub_strlen (cm->path);
+ *path_in_subvol = (s[0] == '\0') ? grub_strdup ("/") : grub_strdup (s);
+ tree = cm->tree;
+ }
+
+ return tree;
+}
+
static grub_err_t
get_fs_root(struct grub_btrfs_data *data, grub_uint64_t tree,
grub_uint64_t objectid, grub_uint64_t offset,
@@ -2905,6 +3092,7 @@
};
static grub_command_t cmd_info;
+static grub_command_t cmd_mount_subvol;
static grub_extcmd_t cmd_list_subvols;
static char *
@@ -2968,6 +3156,9 @@
cmd_info = grub_register_command("btrfs-info", grub_cmd_btrfs_info,
"DEVICE",
"Print BtrFS info about DEVICE.");
+ cmd_mount_subvol = grub_register_command("btrfs-mount-subvol", grub_cmd_btrfs_mount_subvol,
+ "DEVICE DIRECTORY SUBVOL",
+ "Set btrfs DEVICE the DIRECTORY a mountpoint of SUBVOL.");
cmd_list_subvols = grub_register_extcmd("btrfs-list-subvols",
grub_cmd_btrfs_list_subvols, 0,
"[-p|-n] [-o var] DEVICE",
--- a/grub-core/osdep/linux/getroot.c
+++ b/grub-core/osdep/linux/getroot.c
@@ -103,6 +103,14 @@
grub_uint32_t unused[9];
};
+struct btrfs_ioctl_search_header {
+ grub_uint64_t transid;
+ grub_uint64_t objectid;
+ grub_uint64_t offset;
+ grub_uint32_t type;
+ grub_uint32_t len;
+};
+
struct btrfs_ioctl_search_args {
struct btrfs_ioctl_search_key key;
grub_uint64_t buf[(4096 - sizeof(struct btrfs_ioctl_search_key))
@@ -375,6 +383,117 @@
int use_relative_path_on_btrfs = 0;
+static char *
+get_btrfs_subvol (const char *path, grub_uint64_t *subvolid)
+{
+ struct btrfs_ioctl_ino_lookup_args args;
+ grub_uint64_t tree_id;
+ grub_uint64_t ret_id;
+ int fd = -1;
+ char *ret = NULL;
+
+ if (subvolid)
+ *subvolid = 0;
+
+ fd = open (path, O_RDONLY);
+
+ if (fd < 0)
+ return NULL;
+
+ memset (&args, 0, sizeof(args));
+ args.objectid = GRUB_BTRFS_TREE_ROOT_OBJECTID;
+
+ if (ioctl (fd, BTRFS_IOC_INO_LOOKUP, &args) < 0)
+ goto error;
+
+ tree_id = args.treeid;
+ ret_id = args.treeid;
+
+ while (tree_id != GRUB_BTRFS_ROOT_VOL_OBJECTID)
+ {
+ struct btrfs_ioctl_search_args sargs;
+ struct grub_btrfs_root_backref *br;
+ struct btrfs_ioctl_search_header *search_header;
+ char *old;
+ grub_uint16_t len;
+ grub_uint64_t inode_id;
+
+ memset (&sargs, 0, sizeof(sargs));
+
+ sargs.key.tree_id = 1;
+ sargs.key.min_objectid = tree_id;
+ sargs.key.max_objectid = tree_id;
+
+ sargs.key.min_offset = 0;
+ sargs.key.max_offset = ~0ULL;
+ sargs.key.min_transid = 0;
+ sargs.key.max_transid = ~0ULL;
+ sargs.key.min_type = GRUB_BTRFS_ITEM_TYPE_ROOT_BACKREF;
+ sargs.key.max_type = GRUB_BTRFS_ITEM_TYPE_ROOT_BACKREF;
+
+ sargs.key.nr_items = 1;
+
+ if (ioctl (fd, BTRFS_IOC_TREE_SEARCH, &sargs) < 0)
+ goto error;
+
+ if (sargs.key.nr_items == 0)
+ goto error;
+
+ search_header = (struct btrfs_ioctl_search_header *)sargs.buf;
+ br = (struct grub_btrfs_root_backref *) (search_header + 1);
+
+ len = grub_le_to_cpu16 (br->n);
+ inode_id = grub_le_to_cpu64 (br->inode_id);
+ tree_id = search_header->offset;
+
+ old = ret;
+ ret = malloc (len + 1);
+ memcpy (ret, br->name, len);
+ ret[len] = '\0';
+
+ if (inode_id != GRUB_BTRFS_TREE_ROOT_OBJECTID)
+ {
+ char *s;
+
+ memset(&args, 0, sizeof(args));
+ args.treeid = search_header->offset;
+ args.objectid = inode_id;
+
+ if (ioctl (fd, BTRFS_IOC_INO_LOOKUP, &args) < 0)
+ goto error;
+
+ s = xasprintf ("%s%s", args.name, ret);
+ free (ret);
+ ret = s;
+ }
+
+ if (old)
+ {
+ char *s = xasprintf ("%s/%s", ret, old);
+ free (ret);
+ free (old);
+ ret = s;
+ }
+ }
+
+ if (subvolid)
+ *subvolid = ret_id;
+
+ close (fd);
+ return ret;
+
+error:
+
+ if (fd >= 0)
+ close (fd);
+ if (ret)
+ free (ret);
+
+ return NULL;
+}
+
+static char *grub_btrfs_mount_path;
+
char **
grub_find_root_devices_from_mountinfo (const char *dir, char **relroot)
{
@@ -516,12 +635,17 @@
else if (grub_strcmp (entries[i].fstype, "btrfs") == 0)
{
ret = grub_find_root_devices_from_btrfs (dir);
- fs_prefix = get_btrfs_fs_prefix (entries[i].enc_path);
if (use_relative_path_on_btrfs)
{
- if (fs_prefix)
- free (fs_prefix);
fs_prefix = xstrdup ("/");
+
+ if (grub_btrfs_mount_path)
+ grub_free (grub_btrfs_mount_path);
+ grub_btrfs_mount_path = grub_strdup (entries[i].enc_path);
+ }
+ else
+ {
+ fs_prefix = get_btrfs_fs_prefix (entries[i].enc_path);
}
}
else if (!retry && grub_strcmp (entries[i].fstype, "autofs") == 0)
@@ -1202,6 +1326,24 @@
return grub_dev;
}
+
+char *
+grub_util_get_btrfs_subvol (const char *path, char **mount_path, grub_uint64_t *subvolid)
+{
+ if (mount_path)
+ *mount_path = NULL;
+
+ grub_free (grub_find_root_devices_from_mountinfo (path, NULL));
+
+ if (!grub_btrfs_mount_path)
+ return NULL;
+
+ if (mount_path)
+ *mount_path = grub_strdup (grub_btrfs_mount_path);
+
+ return get_btrfs_subvol (grub_btrfs_mount_path, subvolid);
+}
+
char *
grub_make_system_path_relative_to_its_root_os (const char *path)
{
--- a/util/grub-install.c
+++ b/util/grub-install.c
@@ -1646,6 +1646,58 @@
prefix_drive = xasprintf ("(%s)", grub_drives[0]);
}
+#ifdef __linux__
+
+ if (config.is_suse_btrfs_snapshot_enabled
+ && grub_strncmp(grub_fs->name, "btrfs", sizeof ("btrfs") - 1) == 0)
+ {
+ char *subvol = NULL;
+ char *mount_path = NULL;
+ grub_uint64_t subvolid = 0;
+ char **rootdir_devices = NULL;
+ char *t = grub_util_path_concat (2, "/", rootdir);
+ char *rootdir_path = grub_canonicalize_file_name (t);
+
+ if (rootdir_path && grub_util_is_directory (rootdir_path))
+ rootdir_devices = grub_guess_root_devices (rootdir_path);
+
+ if (rootdir_devices && rootdir_devices[0])
+ if (grub_strcmp (rootdir_devices[0], grub_devices[0]) == 0)
+ subvol = grub_util_get_btrfs_subvol (platdir, &mount_path, &subvolid);
+
+ if (subvol && mount_path)
+ {
+ grub_uint64_t def_subvolid = 0;
+
+ grub_free (grub_util_get_btrfs_subvol (rootdir_path, NULL, &def_subvolid));
+
+ if (def_subvolid)
+ {
+ char *rootdir_mount_path = NULL;
+ if (!load_cfg_f)
+ load_cfg_f = grub_util_fopen (load_cfg, "wb");
+ have_load_cfg = 1;
+
+ if (grub_strncmp (rootdir_path, mount_path, grub_strlen (rootdir_path)) == 0)
+ rootdir_mount_path = grub_util_path_concat (2, "/", mount_path + grub_strlen (rootdir_path));
+
+ if (subvolid != def_subvolid && rootdir_mount_path)
+ fprintf (load_cfg_f, "btrfs-mount-subvol ($root) %s %s\n", rootdir_mount_path, subvol);
+ free (rootdir_mount_path);
+ }
+ }
+
+ free (t);
+ free (rootdir_path);
+ for (curdev = rootdir_devices; *curdev; curdev++)
+ free (*curdev);
+ free (rootdir_devices);
+ free (subvol);
+ free (mount_path);
+ }
+
+#endif
+
char mkimage_target[200];
const char *core_name = NULL;
--- a/include/grub/emu/getroot.h
+++ b/include/grub/emu/getroot.h
@@ -53,6 +53,11 @@
grub_find_root_devices_from_mountinfo (const char *dir, char **relroot);
#endif
+#ifdef __linux__
+char *
+grub_util_get_btrfs_subvol (const char *path, char **mount_path, grub_uint64_t *subvolid);
+#endif
+
/* Devmapper functions provided by getroot_devmapper.c. */
void
grub_util_pull_devmapper (const char *os_dev);