aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@linux.intel.com>2012-06-19 17:41:15 -0700
committerH. Peter Anvin <hpa@linux.intel.com>2012-06-19 17:41:15 -0700
commit0b49326e70074030a9d416d36264fe859cc16328 (patch)
tree51e8eb4e89f6236080e3d01f6eb19b18c73f77e6
parent753ab4bde224205876f09889ce928acb3682ba73 (diff)
downloadsyslinux-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/Makefile1
-rw-r--r--extlinux/btrfs.h76
-rw-r--r--extlinux/main.c129
-rw-r--r--extlinux/mountinfo.c277
-rw-r--r--extlinux/mountinfo.h35
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 */