summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYi Yang <yi.y.yang@intel.com>2011-07-12 14:53:50 +0800
committerH. Peter Anvin <hpa@linux.intel.com>2011-07-28 14:10:18 -0700
commit9bba61ccd4b4515bcf4d2dc06ddc4f6329318547 (patch)
treeedb15b6ed30c55be919bb1071cb4ef7b96ba2176
parentc2d4f9b611c43697e889d991d8a4edea92ef2d76 (diff)
downloadsyslinux-9bba61ccd4b4515bcf4d2dc06ddc4f6329318547.tar.gz
btrfs: Correctly determine the installation subvolume
There are multiple ways to set up subvolumes in btrfs. Use a general determination method which works for all schemes. Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
-rw-r--r--extlinux/btrfs.h105
-rwxr-xr-xextlinux/main.c303
2 files changed, 376 insertions, 32 deletions
diff --git a/extlinux/btrfs.h b/extlinux/btrfs.h
index 39a861a5..be0c24ef 100644
--- a/extlinux/btrfs.h
+++ b/extlinux/btrfs.h
@@ -1,6 +1,9 @@
#ifndef _BTRFS_H_
#define _BTRFS_H_
+#include <asm/types.h>
+#include <linux/ioctl.h>
+
#define BTRFS_SUPER_MAGIC 0x9123683E
#define BTRFS_SUPER_INFO_OFFSET (64 * 1024)
#define BTRFS_SUPER_INFO_SIZE 4096
@@ -8,6 +11,40 @@
#define BTRFS_CSUM_SIZE 32
#define BTRFS_FSID_SIZE 16
+typedef __u64 u64;
+typedef __u32 u32;
+typedef __u16 u16;
+typedef __u8 u8;
+typedef u64 __le64;
+typedef u16 __le16;
+
+#define BTRFS_ROOT_BACKREF_KEY 144
+#define BTRFS_ROOT_TREE_DIR_OBJECTID 6ULL
+#define BTRFS_DIR_ITEM_KEY 84
+
+/*
+ * * this is used for both forward and backward root refs
+ * */
+struct btrfs_root_ref {
+ __le64 dirid;
+ __le64 sequence;
+ __le16 name_len;
+} __attribute__ ((__packed__));
+
+struct btrfs_disk_key {
+ __le64 objectid;
+ u8 type;
+ __le64 offset;
+} __attribute__ ((__packed__));
+
+struct btrfs_dir_item {
+ struct btrfs_disk_key location;
+ __le64 transid;
+ __le16 data_len;
+ __le16 name_len;
+ u8 type;
+} __attribute__ ((__packed__));
+
struct btrfs_super_block {
unsigned char csum[BTRFS_CSUM_SIZE];
/* the first 3 fields must match struct btrfs_header */
@@ -19,4 +56,72 @@ struct btrfs_super_block {
u64 magic;
} __attribute__ ((__packed__));
+
+#define BTRFS_IOCTL_MAGIC 0x94
+#define BTRFS_VOL_NAME_MAX 255
+#define BTRFS_PATH_NAME_MAX 4087
+
+struct btrfs_ioctl_vol_args {
+ __s64 fd;
+ char name[BTRFS_PATH_NAME_MAX + 1];
+};
+
+struct btrfs_ioctl_search_key {
+ /* which root are we searching. 0 is the tree of tree roots */
+ __u64 tree_id;
+
+ /* keys returned will be >= min and <= max */
+ __u64 min_objectid;
+ __u64 max_objectid;
+
+ /* keys returned will be >= min and <= max */
+ __u64 min_offset;
+ __u64 max_offset;
+
+ /* max and min transids to search for */
+ __u64 min_transid;
+ __u64 max_transid;
+
+ /* keys returned will be >= min and <= max */
+ __u32 min_type;
+ __u32 max_type;
+
+ /*
+ * how many items did userland ask for, and how many are we
+ * returning
+ */
+ __u32 nr_items;
+
+ /* align to 64 bits */
+ __u32 unused;
+
+ /* some extra for later */
+ __u64 unused1;
+ __u64 unused2;
+ __u64 unused3;
+ __u64 unused4;
+};
+
+struct btrfs_ioctl_search_header {
+ __u64 transid;
+ __u64 objectid;
+ __u64 offset;
+ __u32 type;
+ __u32 len;
+} __attribute__((may_alias));
+
+#define BTRFS_SEARCH_ARGS_BUFSIZE (4096 - sizeof(struct btrfs_ioctl_search_key))
+/*
+ * the buf is an array of search headers where
+ * each header is followed by the actual item
+ * the type field is expanded to 32 bits for alignment
+ */
+struct btrfs_ioctl_search_args {
+ struct btrfs_ioctl_search_key key;
+ char buf[BTRFS_SEARCH_ARGS_BUFSIZE];
+};
+
+#define BTRFS_IOC_TREE_SEARCH _IOWR(BTRFS_IOCTL_MAGIC, 17, \
+ struct btrfs_ioctl_search_args)
+
#endif
diff --git a/extlinux/main.c b/extlinux/main.c
index 26dba7ba..eca87326 100755
--- a/extlinux/main.c
+++ b/extlinux/main.c
@@ -20,12 +20,12 @@
#define _GNU_SOURCE /* Enable everything */
#include <inttypes.h>
/* This is needed to deal with the kernel headers imported into glibc 3.3.3. */
-typedef uint64_t u64;
#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
+#include <dirent.h>
#ifndef __KLIBC__
#include <mntent.h>
#endif
@@ -65,7 +65,6 @@ typedef uint64_t u64;
boot image, the boot sector is from 0~512, the boot image starts after */
#define BTRFS_BOOTSECT_AREA 65536
#define BTRFS_EXTLINUX_OFFSET SECTOR_SIZE
-#define BTRFS_SUBVOL_OPT "subvol="
#define BTRFS_SUBVOL_MAX 256 /* By btrfs specification */
static char subvol[BTRFS_SUBVOL_MAX];
@@ -492,6 +491,270 @@ int btrfs_install_file(const char *path, int devfd, struct stat *rst)
return 0;
}
+/*
+ * * test if path is a subvolume:
+ * * this function return
+ * * 0-> path exists but it is not a subvolume
+ * * 1-> path exists and it is a subvolume
+ * * -1 -> path is unaccessible
+ * */
+static int test_issubvolume(char *path)
+{
+
+ struct stat st;
+ int res;
+
+ res = stat(path, &st);
+ if(res < 0 )
+ return -1;
+
+ return (st.st_ino == 256) && S_ISDIR(st.st_mode);
+
+}
+
+/*
+ * Get file handle for a file or dir
+ */
+static int open_file_or_dir(const char *fname)
+{
+ int ret;
+ struct stat st;
+ DIR *dirstream;
+ int fd;
+
+ ret = stat(fname, &st);
+ if (ret < 0) {
+ return -1;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ dirstream = opendir(fname);
+ if (!dirstream) {
+ return -2;
+ }
+ fd = dirfd(dirstream);
+ } else {
+ fd = open(fname, O_RDWR);
+ }
+ if (fd < 0) {
+ return -3;
+ }
+ return fd;
+}
+
+/*
+ * Get the default subvolume of a btrfs filesystem
+ * rootdir: btrfs root dir
+ * subvol: this function will save the default subvolume name here
+ */
+static char * get_default_subvol(char * rootdir, char * subvol)
+{
+ struct btrfs_ioctl_search_args args;
+ struct btrfs_ioctl_search_key *sk = &args.key;
+ struct btrfs_ioctl_search_header *sh;
+ int ret, i;
+ int fd;
+ struct btrfs_root_ref *ref;
+ struct btrfs_dir_item *dir_item;
+ unsigned long off = 0;
+ int name_len;
+ char *name;
+ u64 dir_id;
+ char dirname[4096];
+ u64 defaultsubvolid = 0;
+
+ ret = test_issubvolume(rootdir);
+ if (ret == 1) {
+ fd = open_file_or_dir(rootdir);
+ if (fd < 0) {
+ fprintf(stderr, "ERROR: failed to open %s\n", rootdir);
+ }
+ ret = fd;
+ }
+ if (ret <= 0) {
+ subvol[0] = '\0';
+ return NULL;
+ }
+
+ memset(&args, 0, sizeof(args));
+
+ /* search in the tree of tree roots */
+ sk->tree_id = 1;
+
+ /*
+ * set the min and max to backref keys. The search will
+ * only send back this type of key now.
+ */
+ sk->max_type = BTRFS_DIR_ITEM_KEY;
+ sk->min_type = BTRFS_DIR_ITEM_KEY;
+
+ /*
+ * set all the other params to the max, we'll take any objectid
+ * and any trans
+ */
+ sk->min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+ sk->max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+
+ sk->max_offset = (u64)-1;
+ sk->min_offset = 0;
+ sk->max_transid = (u64)-1;
+
+ /* just a big number, doesn't matter much */
+ sk->nr_items = 4096;
+
+ while(1) {
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: can't perform the search\n");
+ subvol[0] = '\0';
+ return NULL;
+ }
+ /* the ioctl returns the number of item it found in nr_items */
+ if (sk->nr_items == 0) {
+ break;
+ }
+
+ off = 0;
+
+ /*
+ * for each item, pull the key out of the header and then
+ * read the root_ref item it contains
+ */
+ for (i = 0; i < sk->nr_items; i++) {
+ sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
+ off += sizeof(*sh);
+ if (sh->type == BTRFS_DIR_ITEM_KEY) {
+ dir_item = (struct btrfs_dir_item *)(args.buf + off);
+ name_len = dir_item->name_len;
+ name = (char *)(dir_item + 1);
+
+
+ /*add_root(&root_lookup, sh->objectid, sh->offset,
+ dir_id, name, name_len);*/
+ strncpy(dirname, name, name_len);
+ dirname[name_len] = '\0';
+ if (strcmp(dirname, "default") == 0) {
+ defaultsubvolid = dir_item->location.objectid;
+ break;
+ }
+ }
+ off += sh->len;
+
+ /*
+ * record the mins in sk so we can make sure the
+ * next search doesn't repeat this root
+ */
+ sk->min_objectid = sh->objectid;
+ sk->min_type = sh->type;
+ sk->max_type = sh->type;
+ sk->min_offset = sh->offset;
+ }
+ if (defaultsubvolid != 0)
+ break;
+ sk->nr_items = 4096;
+ /* this iteration is done, step forward one root for the next
+ * ioctl
+ */
+ if (sk->min_objectid < (u64)-1) {
+ sk->min_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+ sk->max_objectid = BTRFS_ROOT_TREE_DIR_OBJECTID;
+ sk->max_type = BTRFS_ROOT_BACKREF_KEY;
+ sk->min_type = BTRFS_ROOT_BACKREF_KEY;
+ sk->min_offset = 0;
+ } else
+ break;
+ }
+
+ if (defaultsubvolid == 0) {
+ subvol[0] = '\0';
+ return NULL;
+ }
+
+ memset(&args, 0, sizeof(args));
+
+ /* search in the tree of tree roots */
+ sk->tree_id = 1;
+
+ /*
+ * set the min and max to backref keys. The search will
+ * only send back this type of key now.
+ */
+ sk->max_type = BTRFS_ROOT_BACKREF_KEY;
+ sk->min_type = BTRFS_ROOT_BACKREF_KEY;
+
+ /*
+ * set all the other params to the max, we'll take any objectid
+ * and any trans
+ */
+ sk->max_objectid = (u64)-1;
+ sk->max_offset = (u64)-1;
+ sk->max_transid = (u64)-1;
+
+ /* just a big number, doesn't matter much */
+ sk->nr_items = 4096;
+
+ while(1) {
+ ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+ if (ret < 0) {
+ fprintf(stderr, "ERROR: can't perform the search\n");
+ subvol[0] = '\0';
+ return NULL;
+ }
+ /* the ioctl returns the number of item it found in nr_items */
+ if (sk->nr_items == 0)
+ break;
+
+ off = 0;
+
+ /*
+ * for each item, pull the key out of the header and then
+ * read the root_ref item it contains
+ */
+ for (i = 0; i < sk->nr_items; i++) {
+ sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
+ off += sizeof(*sh);
+ if (sh->type == BTRFS_ROOT_BACKREF_KEY) {
+ ref = (struct btrfs_root_ref *)(args.buf + off);
+ name_len = ref->name_len;
+ name = (char *)(ref + 1);
+ dir_id = ref->dirid;
+
+ /*add_root(&root_lookup, sh->objectid, sh->offset,
+ dir_id, name, name_len);*/
+ if (sh->objectid == defaultsubvolid) {
+ strncpy(subvol, name, name_len);
+ subvol[name_len] = '\0';
+ dprintf("The default subvolume: %s, ID: %llu\n", subvol, sh->objectid);
+ break;
+ }
+
+ }
+
+ off += sh->len;
+
+ /*
+ * record the mins in sk so we can make sure the
+ * next search doesn't repeat this root
+ */
+ sk->min_objectid = sh->objectid;
+ sk->min_type = sh->type;
+ sk->min_offset = sh->offset;
+ }
+ if (subvol[0] != '\0')
+ break;
+ sk->nr_items = 4096;
+ /* this iteration is done, step forward one root for the next
+ * ioctl
+ */
+ if (sk->min_objectid < (u64)-1) {
+ sk->min_objectid++;
+ sk->min_type = BTRFS_ROOT_BACKREF_KEY;
+ sk->min_offset = 0;
+ } else
+ break;
+ }
+ return subvol;
+}
+
int install_file(const char *path, int devfd, struct stat *rst)
{
if (fs_type == EXT2 || fs_type == VFAT)
@@ -546,19 +809,9 @@ static const char *find_device(const char *mtab_file, dev_t dev)
if (!strcmp(mnt->mnt_type, "btrfs") &&
!stat(mnt->mnt_dir, &dst) &&
dst.st_dev == dev) {
- char *opt = strstr(mnt->mnt_opts, BTRFS_SUBVOL_OPT);
-
- if (opt) {
- if (!subvol[0]) {
- char *tmp;
-
- strcpy(subvol, opt + sizeof(BTRFS_SUBVOL_OPT) - 1);
- tmp = strchr(subvol, 32);
- if (tmp)
- *tmp = '\0';
- }
- break; /* should break and let upper layer try again */
- } else
+ if (!subvol[0]) {
+ get_default_subvol(mnt->mnt_dir, subvol);
+ }
done = true;
}
break;
@@ -623,24 +876,10 @@ static const char *get_devname(const char *path)
#else
- /* check /etc/mtab first, since btrfs subvol info is only in here */
- devname = find_device("/etc/mtab", st.st_dev);
- if (subvol[0] && !devname) { /* we just find it is a btrfs subvol */
- char parent[256];
- char *tmp;
-
- strcpy(parent, path);
- tmp = strrchr(parent, '/');
- if (tmp) {
- *tmp = '\0';
- fprintf(stderr, "%s is subvol, try its parent dir %s\n", path, parent);
- devname = get_devname(parent);
- } else
- devname = NULL;
- }
+ devname = find_device("/proc/mounts", st.st_dev);
if (!devname) {
- /* Didn't find it in /etc/mtab, try /proc/mounts */
- devname = find_device("/proc/mounts", st.st_dev);
+ /* Didn't find it in /proc/mounts, try /etc/mtab */
+ devname = find_device("/etc/mtab", st.st_dev);
}
if (!devname) {
fprintf(stderr, "%s: cannot find device for path %s\n", program, path);