[syslinux:master] btrfs: Correctly determine the installation subvolume

syslinux-bot for Yi Yang yi.y.yang at intel.com
Thu Jul 28 14:15:06 PDT 2011


Commit-ID:  9bba61ccd4b4515bcf4d2dc06ddc4f6329318547
Gitweb:     http://syslinux.zytor.com/commit/9bba61ccd4b4515bcf4d2dc06ddc4f6329318547
Author:     Yi Yang <yi.y.yang at intel.com>
AuthorDate: Tue, 12 Jul 2011 14:53:50 +0800
Committer:  H. Peter Anvin <hpa at linux.intel.com>
CommitDate: Thu, 28 Jul 2011 14:10:18 -0700

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 at linux.intel.com>


---
 extlinux/btrfs.h |  105 +++++++++++++++++++
 extlinux/main.c  |  303 ++++++++++++++++++++++++++++++++++++++++++++++++------
 2 files changed, 376 insertions(+), 32 deletions(-)

diff --git a/extlinux/btrfs.h b/extlinux/btrfs.h
index 39a861a..be0c24e 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 26dba7b..eca8732 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);



More information about the Syslinux-commits mailing list