diff options
author | H. Peter Anvin <hpa@linux.intel.com> | 2012-06-19 17:41:15 -0700 |
---|---|---|
committer | H. Peter Anvin <hpa@linux.intel.com> | 2012-06-19 17:41:15 -0700 |
commit | 0b49326e70074030a9d416d36264fe859cc16328 (patch) | |
tree | 51e8eb4e89f6236080e3d01f6eb19b18c73f77e6 | |
parent | 753ab4bde224205876f09889ce928acb3682ba73 (diff) | |
download | syslinux-0b49326e70074030a9d416d36264fe859cc16328.tar.gz syslinux-0b49326e70074030a9d416d36264fe859cc16328.tar.xz syslinux-0b49326e70074030a9d416d36264fe859cc16328.zip |
extlinux: better methods for finding device matches
1. Support parsing /proc/self/mountinfo for devices;
2. For btrfs, query the device names from btrfs itself.
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
-rw-r--r-- | extlinux/Makefile | 1 | ||||
-rw-r--r-- | extlinux/btrfs.h | 76 | ||||
-rw-r--r-- | extlinux/main.c | 129 | ||||
-rw-r--r-- | extlinux/mountinfo.c | 277 | ||||
-rw-r--r-- | extlinux/mountinfo.h | 35 |
5 files changed, 474 insertions, 44 deletions
diff --git a/extlinux/Makefile b/extlinux/Makefile index 865c7a64..6cde574e 100644 --- a/extlinux/Makefile +++ b/extlinux/Makefile @@ -25,6 +25,7 @@ CFLAGS = $(GCCWARN) -Wno-sign-compare -D_FILE_OFFSET_BITS=64 \ LDFLAGS = SRCS = main.c \ + mountinfo.c \ ../libinstaller/syslxmod.c \ ../libinstaller/syslxopt.c \ ../libinstaller/syslxcom.c \ diff --git a/extlinux/btrfs.h b/extlinux/btrfs.h index be0c24ef..4e2cb317 100644 --- a/extlinux/btrfs.h +++ b/extlinux/btrfs.h @@ -8,8 +8,10 @@ #define BTRFS_SUPER_INFO_OFFSET (64 * 1024) #define BTRFS_SUPER_INFO_SIZE 4096 #define BTRFS_MAGIC "_BHRfS_M" +#define BTRFS_MAGIC_L 8 #define BTRFS_CSUM_SIZE 32 #define BTRFS_FSID_SIZE 16 +#define BTRFS_UUID_SIZE 16 typedef __u64 u64; typedef __u32 u32; @@ -46,17 +48,52 @@ struct btrfs_dir_item { } __attribute__ ((__packed__)); struct btrfs_super_block { - unsigned char csum[BTRFS_CSUM_SIZE]; - /* the first 3 fields must match struct btrfs_header */ - unsigned char fsid[BTRFS_FSID_SIZE]; /* FS specific uuid */ - u64 bytenr; /* this block number */ - u64 flags; - - /* allowed to be different from the btrfs_header from here own down */ - u64 magic; + uint8_t csum[32]; + uint8_t fsid[16]; + uint64_t bytenr; + uint64_t flags; + uint8_t magic[8]; + uint64_t generation; + uint64_t root; + uint64_t chunk_root; + uint64_t log_root; + uint64_t log_root_transid; + uint64_t total_bytes; + uint64_t bytes_used; + uint64_t root_dir_objectid; + uint64_t num_devices; + uint32_t sectorsize; + uint32_t nodesize; + uint32_t leafsize; + uint32_t stripesize; + uint32_t sys_chunk_array_size; + uint64_t chunk_root_generation; + uint64_t compat_flags; + uint64_t compat_ro_flags; + uint64_t incompat_flags; + uint16_t csum_type; + uint8_t root_level; + uint8_t chunk_root_level; + uint8_t log_root_level; + struct btrfs_dev_item { + uint64_t devid; + uint64_t total_bytes; + uint64_t bytes_used; + uint32_t io_align; + uint32_t io_width; + uint32_t sector_size; + uint64_t type; + uint64_t generation; + uint64_t start_offset; + uint32_t dev_group; + uint8_t seek_speed; + uint8_t bandwidth; + uint8_t uuid[16]; + uint8_t fsid[16]; + } __attribute__ ((__packed__)) dev_item; + uint8_t label[256]; } __attribute__ ((__packed__)); - #define BTRFS_IOCTL_MAGIC 0x94 #define BTRFS_VOL_NAME_MAX 255 #define BTRFS_PATH_NAME_MAX 4087 @@ -110,6 +147,23 @@ struct btrfs_ioctl_search_header { __u32 len; } __attribute__((may_alias)); +#define BTRFS_DEVICE_PATH_NAME_MAX 1024 +struct btrfs_ioctl_dev_info_args { + __u64 devid; /* in/out */ + __u8 uuid[BTRFS_UUID_SIZE]; /* in/out */ + __u64 bytes_used; /* out */ + __u64 total_bytes; /* out */ + __u64 unused[379]; /* pad to 4k */ + __u8 path[BTRFS_DEVICE_PATH_NAME_MAX]; /* out */ +}; + +struct btrfs_ioctl_fs_info_args { + __u64 max_id; /* out */ + __u64 num_devices; /* out */ + __u8 fsid[BTRFS_FSID_SIZE]; /* out */ + __u64 reserved[124]; /* pad to 1k */ +}; + #define BTRFS_SEARCH_ARGS_BUFSIZE (4096 - sizeof(struct btrfs_ioctl_search_key)) /* * the buf is an array of search headers where @@ -123,5 +177,9 @@ struct btrfs_ioctl_search_args { #define BTRFS_IOC_TREE_SEARCH _IOWR(BTRFS_IOCTL_MAGIC, 17, \ struct btrfs_ioctl_search_args) +#define BTRFS_IOC_DEV_INFO _IOWR(BTRFS_IOCTL_MAGIC, 30, \ + struct btrfs_ioctl_dev_info_args) +#define BTRFS_IOC_FS_INFO _IOR(BTRFS_IOCTL_MAGIC, 31, \ + struct btrfs_ioctl_fs_info_args) #endif diff --git a/extlinux/main.c b/extlinux/main.c index 5da89e2d..15ea0379 100644 --- a/extlinux/main.c +++ b/extlinux/main.c @@ -52,6 +52,7 @@ #include "syslxfs.h" #include "setadv.h" #include "syslxopt.h" /* unified options */ +#include "mountinfo.h" #ifdef DEBUG # define dprintf printf @@ -342,7 +343,7 @@ int install_bootblock(int fd, const char *device) perror("reading superblock"); return 1; } - if (sb2.magic == *(u64 *)BTRFS_MAGIC) + if (!memcmp(sb2.magic, BTRFS_MAGIC, BTRFS_MAGIC_L)) ok = true; } else if (fs_type == VFAT) { if (xpread(fd, &sb3, sizeof sb3, 0) != sizeof sb3) { @@ -535,35 +536,6 @@ static int test_issubvolume(char *path) } /* - * 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 @@ -585,7 +557,7 @@ static char * get_default_subvol(char * rootdir, char * subvol) ret = test_issubvolume(rootdir); if (ret == 1) { - fd = open_file_or_dir(rootdir); + fd = open(rootdir, O_RDONLY); if (fd < 0) { fprintf(stderr, "ERROR: failed to open %s\n", rootdir); } @@ -921,6 +893,83 @@ err: return NULL; } +static const char *find_device_mountinfo(const char *path, dev_t dev) +{ + const struct mountinfo *m; + struct stat st; + + m = find_mount(path, NULL); + + if (m->devpath[0] == '/' && m->dev == dev && + !stat(m->devpath, &st) && S_ISBLK(st.st_mode) && st.st_rdev == dev) + return m->devpath; + else + return NULL; +} + +static const char *find_device_btrfs(const char *path) +{ + int fd; + struct btrfs_ioctl_fs_info_args fsinfo; + static struct btrfs_ioctl_dev_info_args devinfo; + struct btrfs_super_block sb2; + const char *rv = NULL; + + fd = open(path, O_RDONLY); + if (fd < 0) + goto err; + + if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsinfo)) + goto err; + + /* We do not support multi-device btrfs yet */ + if (fsinfo.num_devices != 1) + goto err; + + /* The one device will have the max devid */ + memset(&devinfo, 0, sizeof devinfo); + devinfo.devid = fsinfo.max_id; + if (ioctl(fd, BTRFS_IOC_DEV_INFO, &devinfo)) + goto err; + close(fd); + fd = -1; + + if (devinfo.path[0] != '/') + goto err; + + fd = open((const char *)devinfo.path, O_RDONLY); + if (fd < 0) + goto err; + + if (xpread(fd, &sb2, sizeof sb2, BTRFS_SUPER_INFO_OFFSET) != sizeof sb2) + goto err; + + if (memcmp(sb2.magic, BTRFS_MAGIC, BTRFS_MAGIC_L)) + goto err; + + if (memcmp(sb2.fsid, fsinfo.fsid, sizeof fsinfo.fsid)) + goto err; + + if (sb2.num_devices != 1) + goto err; + + if (sb2.dev_item.devid != devinfo.devid) + goto err; + + if (memcmp(sb2.dev_item.uuid, devinfo.uuid, sizeof devinfo.uuid)) + goto err; + + if (memcmp(sb2.dev_item.fsid, fsinfo.fsid, sizeof fsinfo.fsid)) + goto err; + + rv = (const char *)devinfo.path; /* It's good! */ + +err: + if (fd >= 0) + close(fd); + return rv; +} + static const char *get_devname(const char *path) { const char *devname = NULL; @@ -936,10 +985,19 @@ static const char *get_devname(const char *path) return devname; } -#ifdef __KLIBC__ + if (fs_type == BTRFS) { + /* For btrfs try to get the device name from btrfs itself */ + devname = find_device_btrfs(path); + } - devname = find_device_sysfs(st.st_dev); + if (!devname) { + devname = find_device_mountinfo(path, st.st_dev); + } +#ifdef __KLIBC__ + if (!devname) { + devname = find_device_sysfs(st.st_dev); + } if (!devname) { /* klibc doesn't have getmntent and friends; instead, just create a new device with the appropriate device type */ @@ -956,8 +1014,9 @@ static const char *get_devname(const char *path) } #else - - devname = find_device("/proc/mounts", st.st_dev); + if (!devname) { + devname = find_device("/proc/mounts", st.st_dev); + } if (!devname) { /* Didn't find it in /proc/mounts, try /etc/mtab */ devname = find_device("/etc/mtab", st.st_dev); diff --git a/extlinux/mountinfo.c b/extlinux/mountinfo.c new file mode 100644 index 00000000..2be87580 --- /dev/null +++ b/extlinux/mountinfo.c @@ -0,0 +1,277 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2012 Intel Corporation; All Rights Reserved + * + * This program 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, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> +#include "mountinfo.h" + +/* + * Parse /proc/self/mountinfo + */ +static int get_string(FILE *f, char *string_buf, size_t string_len, char *ec) +{ + int ch; + char *p = string_buf; + + for (;;) { + if (!string_len) + return -2; /* String too long */ + + ch = getc(f); + if (ch == EOF) { + return -1; /* Got EOF */ + } else if (ch == ' ' || ch == '\t' || ch == '\n') { + *ec = ch; + *p = '\0'; + return p - string_buf; + } else if (ch == '\\') { + /* Should always be followed by 3 octal digits in 000..377 */ + int oc = 0; + int i; + for (i = 0; i < 3; i++) { + ch = getc(f); + if (ch < '0' || ch > '7' || (i == 0 && ch > '3')) + return -1; /* Bad escape sequence */ + oc = (oc << 3) + (ch - '0'); + } + if (!oc) + return -1; /* We can't handle \000 */ + *p++ = oc; + string_len--; + } else { + *p++ = ch; + string_len--; + } + } +} + +static void free_mountinfo(struct mountinfo *m) +{ + struct mountinfo *nx; + + while (m) { + free((char *)m->root); + free((char *)m->path); + free((char *)m->fstype); + free((char *)m->devpath); + free((char *)m->mountopt); + nx = m->next; + free(m); + m = nx; + } +} + +static struct mountinfo *head = NULL, **tail = &head; + +static void parse_mountinfo(void) +{ + FILE *f; + struct mountinfo *m, *mm; + char string_buf[PATH_MAX*8]; + int n; + char ec, *ep; + unsigned int ma, mi; + + f = fopen("/proc/self/mountinfo", "r"); + if (!f) + return; + + for (;;) { + m = malloc(sizeof(struct mountinfo)); + if (!m) + break; + memset(m, 0, sizeof *m); + + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 0 || ec == '\n') + break; + + m->mountid = strtoul(string_buf, &ep, 10); + if (*ep) + break; + + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 0 || ec == '\n') + break; + + m->parentid = strtoul(string_buf, &ep, 10); + if (*ep) + break; + + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 0 || ec == '\n') + break; + + if (sscanf(string_buf, "%u:%u", &ma, &mi) != 2) + break; + + m->dev = makedev(ma, mi); + + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 1 || ec == '\n' || string_buf[0] != '/') + break; + + m->root = strdup(string_buf); + if (!m->root) + break; + + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 1 || ec == '\n' || string_buf[0] != '/') + break; + + m->path = strdup(string_buf); + m->pathlen = (n == 1) ? 0 : n; /* Treat / as empty */ + + /* Skip tagged attributes */ + do { + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 0 || ec == '\n') + goto quit; + } while (n != 1 || string_buf[0] != '-'); + + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 0 || ec == '\n') + break; + + m->fstype = strdup(string_buf); + if (!m->fstype) + break; + + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 0 || ec == '\n') + break; + + m->devpath = strdup(string_buf); + if (!m->devpath) + break; + + n = get_string(f, string_buf, sizeof string_buf, &ec); + if (n < 0) + break; + + m->mountopt = strdup(string_buf); + if (!m->mountopt) + break; + + /* Skip any previously unknown fields */ + while (ec != '\n' && ec != EOF) + ec = getc(f); + + *tail = m; + tail = &m->next; + } +quit: + fclose(f); + free_mountinfo(m); + + /* Create parent links */ + for (m = head; m; m = m->next) { + for (mm = head; mm; mm = mm->next) { + if (m->parentid == mm->mountid) { + m->parent = mm; + if (!strcmp(m->path, mm->path)) + mm->hidden = 1; /* Hidden under another mount */ + break; + } + } + } +} + +const struct mountinfo *find_mount(const char *path, char **subpath) +{ + static int done_init; + char *real_path; + const struct mountinfo *m, *best; + struct stat st; + int len, matchlen; + + if (!done_init) { + parse_mountinfo(); + done_init = 1; + } + + if (stat(path, &st)) + return NULL; + + real_path = realpath(path, NULL); + if (!real_path) + return NULL; + + /* + * Tricky business: we need the longest matching subpath + * which isn't a parent of the same subpath. + */ + len = strlen(real_path); + matchlen = 0; + best = NULL; + for (m = head; m; m = m->next) { + if (m->hidden) + continue; /* Hidden underneath another mount */ + + if (m->pathlen > len) + continue; /* Cannot possibly match */ + + if (m->pathlen < matchlen) + continue; /* No point in testing this one */ + + if (st.st_dev == m->dev && + !memcmp(m->path, real_path, m->pathlen) && + (real_path[m->pathlen] == '/' || real_path[m->pathlen] == '\0')) { + matchlen = m->pathlen; + best = m; + } + } + + if (best && subpath) { + if (real_path[best->pathlen] == '\0') + *subpath = strdup("/"); + else + *subpath = strdup(real_path + best->pathlen); + } + + return best; +} + +#ifdef TEST + +int main(int argc, char *argv[]) +{ + int i; + const struct mountinfo *m; + char *subpath; + + parse_mountinfo(); + + for (i = 1; i < argc; i++) { + m = find_mount(argv[i], &subpath); + if (!m) { + printf("%s: %s\n", argv[i], strerror(errno)); + continue; + } + + printf("%s -> %s @ %s(%u,%u):%s %s %s\n", + argv[i], subpath, m->devpath, major(m->dev), minor(m->dev), + m->root, m->fstype, m->mountopt); + printf("Usable device: %s\n", find_device(m->dev, m->devpath)); + free(subpath); + } + + return 0; +} + +#endif diff --git a/extlinux/mountinfo.h b/extlinux/mountinfo.h new file mode 100644 index 00000000..9cbcac12 --- /dev/null +++ b/extlinux/mountinfo.h @@ -0,0 +1,35 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2012 Intel Corporation; All Rights Reserved + * + * This program 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, Inc., 51 Franklin St, Fifth Floor, + * Boston MA 02110-1301, USA; either version 2 of the License, or + * (at your option) any later version; incorporated herein by reference. + * + * ----------------------------------------------------------------------- */ + +#ifndef SYSLINUX_MOUNTINFO_H +#define SYSLINUX_MOUNTINFO_H + +#include <sys/types.h> + +struct mountinfo { + struct mountinfo *next; + struct mountinfo *parent; + const char *root; + const char *path; + const char *fstype; + const char *devpath; + const char *mountopt; + int mountid; + int parentid; + int pathlen; + int hidden; + dev_t dev; +}; + +const struct mountinfo *find_mount(const char *path, char **subpath); + +#endif /* SYSLINUX_MOUNTINFO_H */ |