Yi Yang
2011-Jul-12 07:40 UTC
[syslinux] [PATCH]: Use a general way to get the default subvolume for btrfs
>From 03115f064be2e074d84f4e2105d2cdebde10f6ba Mon Sep 17 00:00:00 2001From: Yi Yang <yi.y.yang at intel.com> Date: Tue, 12 Jul 2011 14:53:50 +0800 Subject: [PATCH] Use a general way to get the default subvolume for btrfs --- extlinux/btrfs.h | 105 +++++++++++++++++++ extlinux/main.c | 304 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 377 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..e6ae802 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); @@ -837,3 +1076,4 @@ int main(int argc, char *argv[]) return install_loader(opt.directory, opt.update_only); } + -- 1.7.4