From 82591fa6e7941efe2723a23cb1d924dfe0641974 Mon Sep 17 00:00:00 2001 From: Vladimir Serbinenko Date: Mon, 28 Oct 2013 01:37:19 +0100 Subject: [PATCH] Make / in btrfs refer to real root, not the default volume. Modify mkrelpath to work even if device is mounted with subvolid option. --- ChangeLog | 9 +- grub-core/fs/btrfs.c | 122 +++++++++----------- grub-core/osdep/linux/getroot.c | 194 +++++++++++++++++++++++++++----- grub-core/osdep/unix/relpath.c | 36 ++---- include/grub/btrfs.h | 71 ++++++++++++ include/grub/emu/getroot.h | 2 + 6 files changed, 311 insertions(+), 123 deletions(-) create mode 100644 include/grub/btrfs.h diff --git a/ChangeLog b/ChangeLog index d697f20af..625385181 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,11 @@ -2013-10-14 Andrey Borzenkov +2013-10-28 Vladimir Serbinenko - * Makefile.util.def: Add grub-core/kern/disk_common.c to library + Make / in btrfs refer to real root, not the default volume. + Modify mkrelpath to work even if device is mounted with subvolid option. + +2013-10-28 Andrey Borzenkov + + * Makefile.util.def: Add grub-core/kern/disk_common.c to library extra_dist. * grub-core/Makefile.core.def: Add kern/disk_common.c to disk module extra_dist. diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c index 196f3017c..49f11cc3c 100644 --- a/grub-core/fs/btrfs.c +++ b/grub-core/fs/btrfs.c @@ -1,7 +1,7 @@ /* btrfs.c - B-tree file system. */ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2010 Free Software Foundation, Inc. + * Copyright (C) 2010,2011,2012,2013 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ #include #include #include +#include GRUB_MOD_LICENSE ("GPLv3+"); @@ -106,24 +107,6 @@ struct grub_btrfs_data struct grub_btrfs_extent_data *extent; }; -enum - { - GRUB_BTRFS_ITEM_TYPE_INODE_ITEM = 0x01, - GRUB_BTRFS_ITEM_TYPE_INODE_REF = 0x0c, - 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_DEVICE = 0xd8, - GRUB_BTRFS_ITEM_TYPE_CHUNK = 0xe4 - }; - -struct grub_btrfs_key -{ - grub_uint64_t object_id; - grub_uint8_t type; - grub_uint64_t offset; -} __attribute__ ((packed)); - struct grub_btrfs_chunk_item { grub_uint64_t size; @@ -188,13 +171,6 @@ struct grub_btrfs_leaf_descriptor } *data; }; -struct grub_btrfs_root_item -{ - grub_uint8_t dummy[0xb0]; - grub_uint64_t tree; - grub_uint64_t inode; -}; - struct grub_btrfs_time { grub_int64_t sec; @@ -1196,6 +1172,40 @@ grub_btrfs_extent_read (struct grub_btrfs_data *data, return pos - pos0; } +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_err_t err; + grub_disk_addr_t elemaddr; + grub_size_t elemsize; + struct grub_btrfs_key key_out, key_in; + struct grub_btrfs_root_item ri; + + key_in.object_id = GRUB_BTRFS_ROOT_VOL_OBJECTID; + key_in.offset = 0; + key_in.type = GRUB_BTRFS_ITEM_TYPE_ROOT_ITEM; + err = lower_bound (data, &key_in, &key_out, + data->sblock.root_tree, + &elemaddr, &elemsize, NULL, 0); + if (err) + return err; + if (key_in.object_id != key_out.object_id + || key_in.type != key_out.type + || key_in.offset != key_out.offset) + return grub_error (GRUB_ERR_BAD_FS, "no root"); + err = grub_btrfs_read_logical (data, elemaddr, &ri, + sizeof (ri), 0); + if (err) + return err; + key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; + key->offset = 0; + key->object_id = grub_cpu_to_le64_compile_time (GRUB_BTRFS_OBJECT_ID_CHUNK); + *tree = ri.tree; + *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY; + return GRUB_ERR_NONE; +} + static grub_err_t find_path (struct grub_btrfs_data *data, const char *path, struct grub_btrfs_key *key, @@ -1208,42 +1218,31 @@ find_path (struct grub_btrfs_data *data, grub_size_t allocated = 0; struct grub_btrfs_dir_item *direl = NULL; struct grub_btrfs_key key_out; - int skip_default; const char *ctoken; grub_size_t ctokenlen; char *path_alloc = NULL; char *origpath = NULL; unsigned symlinks_max = 32; - *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY; - *tree = data->sblock.root_tree; - key->object_id = data->sblock.root_dir_objectid; - key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; - key->offset = 0; - skip_default = 1; + err = get_root (data, key, tree, type); + if (err) + return err; + origpath = grub_strdup (path); if (!origpath) return grub_errno; while (1) { - if (!skip_default) - { - while (path[0] == '/') - path++; - if (!path[0]) - break; - slash = grub_strchr (path, '/'); - if (!slash) - slash = path + grub_strlen (path); - ctoken = path; - ctokenlen = slash - path; - } - else - { - ctoken = "default"; - ctokenlen = sizeof ("default") - 1; - } + while (path[0] == '/') + path++; + if (!path[0]) + break; + slash = grub_strchr (path, '/'); + if (!slash) + slash = path + grub_strlen (path); + ctoken = path; + ctokenlen = slash - path; if (*type != GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY) { @@ -1254,10 +1253,8 @@ find_path (struct grub_btrfs_data *data, if (ctokenlen == 1 && ctoken[0] == '.') { - if (!skip_default) - path = slash; - skip_default = 0; - continue; + path = slash; + continue; } if (ctokenlen == 2 && ctoken[0] == '.' && ctoken[1] == '.') { @@ -1287,9 +1284,7 @@ find_path (struct grub_btrfs_data *data, *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY; key->object_id = key_out.offset; - if (!skip_default) - path = slash; - skip_default = 0; + path = slash; continue; } @@ -1359,9 +1354,7 @@ find_path (struct grub_btrfs_data *data, return err; } - if (!skip_default) - path = slash; - skip_default = 0; + path = slash; if (cdirel->type == GRUB_BTRFS_DIR_ITEM_TYPE_SYMLINK) { struct grub_btrfs_inode inode; @@ -1411,12 +1404,9 @@ find_path (struct grub_btrfs_data *data, path = path_alloc = tmp; if (path[0] == '/') { - *type = GRUB_BTRFS_DIR_ITEM_TYPE_DIRECTORY; - *tree = data->sblock.root_tree; - key->object_id = data->sblock.root_dir_objectid; - key->type = GRUB_BTRFS_ITEM_TYPE_DIR_ITEM; - key->offset = 0; - skip_default = 1; + err = get_root (data, key, tree, type); + if (err) + return err; } continue; } diff --git a/grub-core/osdep/linux/getroot.c b/grub-core/osdep/linux/getroot.c index 77d80838e..9bf841fa5 100644 --- a/grub-core/osdep/linux/getroot.c +++ b/grub-core/osdep/linux/getroot.c @@ -49,15 +49,18 @@ #include +#include #include #include #include #include #include #include +#include #define LVM_DEV_MAPPER_STRING "/dev/mapper/" +#if 0 /* Defines taken from btrfs/ioctl.h. */ struct btrfs_ioctl_dev_info_args @@ -82,7 +85,7 @@ struct btrfs_ioctl_fs_info_args struct btrfs_ioctl_dev_info_args) #define BTRFS_IOC_FS_INFO _IOR(0x94, 31, \ struct btrfs_ioctl_fs_info_args) - +#endif static int grub_util_is_imsm (const char *os_dev); @@ -224,6 +227,117 @@ grub_find_root_devices_from_btrfs (const char *dir) return ret; } +static char * +get_btrfs_fs_prefix (const char *mount_path) +{ + struct btrfs_ioctl_ino_lookup_args args; + struct stat st; + int fd; + grub_uint64_t tree_id, inode_id; + char *ret = NULL; + + fd = open (mount_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) + return NULL; + tree_id = args.treeid; + + if (fstat (fd, &st) < 0) + return NULL; + inode_id = st.st_ino; + + while (tree_id != GRUB_BTRFS_ROOT_VOL_OBJECTID + || inode_id != GRUB_BTRFS_TREE_ROOT_OBJECTID) + { + grub_uint64_t *nid; + const char *name; + size_t namelen; + struct btrfs_ioctl_search_args sargs; + char *old; + + memset (&sargs, 0, sizeof(sargs)); + + if (inode_id == GRUB_BTRFS_TREE_ROOT_OBJECTID) + { + struct grub_btrfs_root_backref *br; + + 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) + return NULL; + + if (sargs.key.nr_items == 0) + return NULL; + + nid = (grub_uint64_t *) (sargs.buf + 16); + tree_id = *nid; + br = (struct grub_btrfs_root_backref *) (sargs.buf + 32); + inode_id = br->inode_id; + name = br->name; + namelen = br->n; + } + else + { + struct grub_btrfs_inode_ref *ir; + + sargs.key.tree_id = tree_id; + sargs.key.min_objectid = inode_id; + sargs.key.max_objectid = inode_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_INODE_REF; + sargs.key.max_type = GRUB_BTRFS_ITEM_TYPE_INODE_REF; + + if (ioctl (fd, BTRFS_IOC_TREE_SEARCH, &sargs) < 0) + return NULL; + + if (sargs.key.nr_items == 0) + return NULL; + + nid = (grub_uint64_t *) (sargs.buf + 16); + inode_id = *nid; + + ir = (struct grub_btrfs_inode_ref *) (sargs.buf + 32); + name = ir->name; + namelen = ir->n; + } + old = ret; + ret = xmalloc (namelen + (old ? strlen (old) : 0) + 2); + ret[0] = '/'; + memcpy (ret + 1, name, namelen); + if (old) + { + strcpy (ret + 1 + namelen, old); + free (old); + } + else + ret[1+namelen] = '\0'; + } + if (!ret) + return xstrdup ("/"); + return ret; +} + + char ** grub_find_root_devices_from_mountinfo (const char *dir, char **relroot) { @@ -332,6 +446,7 @@ grub_find_root_devices_from_mountinfo (const char *dir, char **relroot) for (i = entry_len - 1; i >= 0; i--) { char **ret = NULL; + char *fs_prefix = NULL; if (!*entries[i].device) continue; @@ -348,45 +463,52 @@ grub_find_root_devices_from_mountinfo (const char *dir, char **relroot) if (relroot) { if (!slash) - *relroot = xasprintf ("/@%s", entries[i].enc_root); + fs_prefix = xasprintf ("/@%s", entries[i].enc_root); else if (strchr (slash + 1, '@')) - *relroot = xasprintf ("/%s%s", slash + 1, entries[i].enc_root); + fs_prefix = xasprintf ("/%s%s", slash + 1, entries[i].enc_root); else - *relroot = xasprintf ("/%s@%s", slash + 1, entries[i].enc_root); + fs_prefix = xasprintf ("/%s@%s", slash + 1, + entries[i].enc_root); } } else if (grub_strcmp (entries[i].fstype, "btrfs") == 0) { ret = grub_find_root_devices_from_btrfs (dir); - if (relroot) - { - char *ptr; - *relroot = xmalloc (strlen (entries[i].enc_root) + - 2 + strlen (dir)); - ptr = grub_stpcpy (*relroot, entries[i].enc_root); - if (strlen (dir) > strlen (entries[i].enc_path)) - { - while (ptr > *relroot && *(ptr - 1) == '/') - ptr--; - if (dir[strlen (entries[i].enc_path)] != '/') - *ptr++ = '/'; - ptr = grub_stpcpy (ptr, dir + strlen (entries[i].enc_path)); - } - *ptr = 0; - } + fs_prefix = get_btrfs_fs_prefix (entries[i].enc_path); } if (!ret) { ret = xmalloc (2 * sizeof (ret[0])); ret[0] = strdup (entries[i].device); ret[1] = 0; - if (relroot) - *relroot = strdup (entries[i].enc_root); } - free (buf); - free (entries); - fclose (fp); - return ret; + if (!fs_prefix) + fs_prefix = entries[i].enc_root; + if (relroot) + { + char *ptr; + grub_size_t enc_root_len = strlen (fs_prefix); + grub_size_t enc_path_len = strlen (entries[i].enc_path); + grub_size_t dir_strlen = strlen (dir); + *relroot = xmalloc (enc_root_len + + 2 + dir_strlen); + ptr = grub_stpcpy (*relroot, fs_prefix); + if (dir_strlen > enc_path_len) + { + while (ptr > *relroot && *(ptr - 1) == '/') + ptr--; + if (dir[enc_path_len] != '/') + *ptr++ = '/'; + ptr = grub_stpcpy (ptr, dir + enc_path_len); + } + *ptr = 0; + } + if (fs_prefix != entries[i].enc_root) + free (fs_prefix); + free (buf); + free (entries); + fclose (fp); + return ret; } free (buf); @@ -913,3 +1035,23 @@ grub_util_get_grub_dev_os (const char *os_dev) return grub_dev; } + +char * +grub_make_system_path_relative_to_its_root_os (const char *path) +{ + char *bind = NULL; + grub_size_t len; + grub_free (grub_find_root_devices_from_mountinfo (path, &bind)); + if (bind && bind[0]) + { + len = strlen (bind); + while (len > 0 && bind[len - 1] == '/') + { + bind[len - 1] = '\0'; + len--; + } + return bind; + } + grub_free (bind); + return NULL; +} diff --git a/grub-core/osdep/unix/relpath.c b/grub-core/osdep/unix/relpath.c index 56d3923a8..71c19d867 100644 --- a/grub-core/osdep/unix/relpath.c +++ b/grub-core/osdep/unix/relpath.c @@ -48,7 +48,13 @@ grub_make_system_path_relative_to_its_root (const char *path) if (p == NULL) grub_util_error (_("failed to get canonical path of `%s'"), path); - /* For ZFS sub-pool filesystems, could be extended to others (btrfs?). */ +#ifdef __linux__ + ret = grub_make_system_path_relative_to_its_root_os (p); + if (ret) + return ret; +#endif + + /* For ZFS sub-pool filesystems. */ #ifndef __HAIKU__ { char *dummy; @@ -98,18 +104,6 @@ grub_make_system_path_relative_to_its_root (const char *path) if (offset == 0) { free (buf); -#ifdef __linux__ - { - char *bind; - grub_free (grub_find_root_devices_from_mountinfo (buf2, &bind)); - if (bind && bind[0] && bind[1]) - { - buf3 = bind; - goto parsedir; - } - grub_free (bind); - } -#endif free (buf2); if (poolfs) return xasprintf ("/%s/@", poolfs); @@ -131,25 +125,9 @@ grub_make_system_path_relative_to_its_root (const char *path) free (buf); buf3 = xstrdup (buf2 + offset); buf2[offset] = 0; -#ifdef __linux__ - { - char *bind; - grub_free (grub_find_root_devices_from_mountinfo (buf2, &bind)); - if (bind && bind[0] && bind[1]) - { - char *temp = buf3; - buf3 = grub_xasprintf ("%s%s%s", bind, buf3[0] == '/' ?"":"/", buf3); - grub_free (temp); - } - grub_free (bind); - } -#endif free (buf2); -#ifdef __linux__ - parsedir: -#endif /* Remove trailing slashes, return empty string if root directory. */ len = strlen (buf3); while (len > 0 && buf3[len - 1] == '/') diff --git a/include/grub/btrfs.h b/include/grub/btrfs.h new file mode 100644 index 000000000..77531d105 --- /dev/null +++ b/include/grub/btrfs.h @@ -0,0 +1,71 @@ +/* btrfs.c - B-tree file system. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2010,2011,2012,2013 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_BTRFS_H +#define GRUB_BTRFS_H 1 + +enum + { + GRUB_BTRFS_ITEM_TYPE_INODE_ITEM = 0x01, + GRUB_BTRFS_ITEM_TYPE_INODE_REF = 0x0c, + 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_BACKREF = 0x90, + GRUB_BTRFS_ITEM_TYPE_DEVICE = 0xd8, + GRUB_BTRFS_ITEM_TYPE_CHUNK = 0xe4 + }; + +enum + { + GRUB_BTRFS_ROOT_VOL_OBJECTID = 5, + GRUB_BTRFS_TREE_ROOT_OBJECTID = 0x100, + }; + +struct grub_btrfs_root_item +{ + grub_uint8_t dummy[0xb0]; + grub_uint64_t tree; + grub_uint64_t inode; +}; + +struct grub_btrfs_key +{ + grub_uint64_t object_id; + grub_uint8_t type; + grub_uint64_t offset; +} __attribute__ ((packed)); + + +struct grub_btrfs_root_backref +{ + grub_uint64_t inode_id; + grub_uint64_t seqnr; + grub_uint16_t n; + char name[0]; +}; + +struct grub_btrfs_inode_ref +{ + grub_uint64_t idxid; + grub_uint16_t n; + char name[0]; +}; + +#endif diff --git a/include/grub/emu/getroot.h b/include/grub/emu/getroot.h index 6684e372a..675cf78be 100644 --- a/include/grub/emu/getroot.h +++ b/include/grub/emu/getroot.h @@ -36,6 +36,8 @@ void grub_util_pull_device (const char *osname); char **grub_guess_root_devices (const char *dir); int grub_util_get_dev_abstraction (const char *os_dev); char *grub_make_system_path_relative_to_its_root (const char *path); +char * +grub_make_system_path_relative_to_its_root_os (const char *path); char *grub_util_get_grub_dev (const char *os_dev); #if defined (__FreeBSD__) || defined(__FreeBSD_kernel__) void grub_util_follow_gpart_up (const char *name, grub_disk_addr_t *off_out,