Richard W.M. Jones
2017-Apr-25 13:43 UTC
[Libguestfs] [PATCH supermin v2] init: Support root=UUID=... to specify the appliance disk by volume UUID.
v1 -> v2: - I fixed the end condition of the loop in parse_root_uuid. - Retested. Didn't change the busy wait loop into a function or macro, as per discussion on IRC. Rich.
Richard W.M. Jones
2017-Apr-25 13:43 UTC
[Libguestfs] [PATCH supermin v2] init: Support root=UUID=... to specify the appliance disk by volume UUID.
Instead of specifying a device name (eg. root=/dev/sdb), this permits specifying an ext4 volume UUID (root=UUID=12345678-...). This allows the appliance to be robust against the non-determinism of SCSI device enumeration. --- init/init.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 193 insertions(+), 34 deletions(-) diff --git a/init/init.c b/init/init.c index 6aa9ea2..60e099e 100644 --- a/init/init.c +++ b/init/init.c @@ -28,6 +28,7 @@ #include <stdlib.h> #include <stdint.h> #include <string.h> +#include <ctype.h> #include <inttypes.h> #include <limits.h> #include <unistd.h> @@ -89,6 +90,11 @@ static void read_cmdline (void); static void insmod (const char *filename); static void delete_initramfs_files (void); static void show_directory (const char *dir); +static void parse_root_uuid (const char *uuid, unsigned char *raw_uuid); +static int hexdigit (char d); +static int find_fs_uuid (const unsigned char *raw_uuid, int *major, int *minor); +static int parse_dev_file (const char *path, int *major, int *minor); +static void virtio_warning (uint64_t delay_ns, const char *what); static char cmdline[1024]; static char line[1024]; @@ -97,13 +103,11 @@ int main () { FILE *fp; - char *root, *path; + char *root; size_t len; int dax = 0; uint64_t delay_ns; - int virtio_message = 0; int major, minor; - char *p; const char *mount_options = ""; #define NANOSLEEP(ns) do { \ @@ -180,46 +184,59 @@ main () exit (EXIT_FAILURE); } root += 5; - if (strncmp (root, "/dev/", 5) == 0) - root += 5; - if (strncmp (root, "pmem", 4) == 0) - dax = 1; - len = strcspn (root, " "); - root[len] = '\0'; - - asprintf (&path, "/sys/block/%s/dev", root); - for (delay_ns = 250000; - delay_ns <= MAX_ROOT_WAIT * UINT64_C(1000000000); - delay_ns *= 2) { - fp = fopen (path, "r"); - if (fp != NULL) - break; + if (strncmp (root, "/dev/", 5) == 0) { + char *path; - if (delay_ns > 1000000000) { - fprintf (stderr, - "supermin: waiting another %" PRIu64 " ns for %s to appear\n", - delay_ns, path); - if (!virtio_message) { - fprintf (stderr, - "This usually means your kernel doesn't support virtio, or supermin was unable\n" - "to load some kernel modules (see module loading messages above).\n"); - virtio_message = 1; + root += 5; + if (strncmp (root, "pmem", 4) == 0) + dax = 1; + len = strcspn (root, " "); + root[len] = '\0'; + + asprintf (&path, "/sys/block/%s/dev", root); + + for (delay_ns = 250000; + delay_ns <= MAX_ROOT_WAIT * UINT64_C(1000000000); + delay_ns *= 2) { + if (parse_dev_file (path, &major, &minor) != -1) { + if (!quiet) + fprintf (stderr, "supermin: picked %s (%d:%d) as root device\n", + path, major, minor); + break; } + + virtio_warning (delay_ns, path); + NANOSLEEP (delay_ns); } - NANOSLEEP (delay_ns); + free (path); } + else if (strncmp (root, "UUID=", 5) == 0) { + unsigned char raw_uuid[16]; - if (!quiet) - fprintf (stderr, "supermin: picked %s as root device\n", path); + root += 5; + parse_root_uuid (root, raw_uuid); + + for (delay_ns = 250000; + delay_ns <= MAX_ROOT_WAIT * UINT64_C(1000000000); + delay_ns *= 2) { + if (find_fs_uuid (raw_uuid, &major, &minor) != -1) { + if (!quiet) + fprintf (stderr, "supermin: picked %d:%d as root device\n", + major, minor); + break; + } - fgets (line, sizeof line, fp); - major = atoi (line); - p = line + strcspn (line, ":") + 1; - minor = atoi (p); + virtio_warning (delay_ns, "root UUID"); + NANOSLEEP (delay_ns); + } + } + else { + fprintf (stderr, "supermin: unknown root= parameter on the command line\n"); + exit (EXIT_FAILURE); + } - fclose (fp); if (umount ("/sys") == -1) { perror ("umount: /sys"); exit (EXIT_FAILURE); @@ -492,3 +509,145 @@ show_directory (const char *dirname) closedir (dir); chdir ("/"); } + +static void +parse_root_uuid (const char *root, unsigned char *raw_uuid) +{ + size_t i; + + i = 0; + while (i < 16) { + if (*root == '-') { + ++root; + continue; + } + if (!isxdigit (root[0]) || !isxdigit (root[1])) { + fprintf (stderr, "supermin: root UUID is not a 16 byte UUID string\n"); + exit (EXIT_FAILURE); + } + raw_uuid[i] = hexdigit (root[0]) * 0x10 + hexdigit (root[1]); + ++i; + root += 2; + } + + if (*root && isxdigit (*root)) { + fprintf (stderr, "supermin: root UUID is longer than 16 bytes\n"); + exit (EXIT_FAILURE); + } +} + +static int +hexdigit (char d) +{ + switch (d) { + case '0'...'9': return d - '0'; + case 'a'...'f': return d - 'a' + 10; + case 'A'...'F': return d - 'A' + 10; + default: return -1; + } +} + +/* Search every block device under /sys/block to see if we can find + * one which contains a filesystem with the matching volume UUID. + */ +static int +find_fs_uuid (const unsigned char *raw_uuid, int *major, int *minor) +{ + DIR *dir; + struct dirent *d; + unsigned char uuid[16]; + + dir = opendir ("/sys/block"); + if (!dir) { + perror ("/sys/block"); + return -1; + } + + while ((d = readdir (dir)) != NULL) { + int fd = -1; + char *path = NULL; + + if (d->d_name[0] == '.') + goto cont; + + asprintf (&path, "/sys/block/%s/dev", d->d_name); + + if (parse_dev_file (path, major, minor) == -1) + goto cont; + + /* We have to make a dummy inode so we can open the device. */ + unlink ("/dev/disk"); + if (mknod ("/dev/disk", S_IFBLK|0700, makedev (*major, *minor)) == -1) { + perror ("mknod"); + goto cont; + } + + fd = open ("/dev/disk", O_RDONLY); + if (fd == -1) { + perror ("open"); + goto cont; + } + + if (pread (fd, uuid, sizeof uuid, 0x468) != sizeof uuid) { + /*perror ("pread"); - not an error, the device might just be small */ + goto cont; + } + + if (memcmp (uuid, raw_uuid, sizeof uuid) != 0) + goto cont; + + close (fd); + free (path); + closedir (dir); + unlink ("/dev/disk"); + return 0; + + cont: + if (fd >= 0) close (fd); + free (path); + } + + closedir (dir); + + return -1; +} + +/* Parse a /sys/block/X/dev file and extract the major:minor numbers. */ +static int +parse_dev_file (const char *path, int *major, int *minor) +{ + FILE *fp; + char *p; + + fp = fopen (path, "r"); + if (fp == NULL) + return -1; + + fgets (line, sizeof line, fp); + *major = atoi (line); + p = line + strcspn (line, ":") + 1; + *minor = atoi (p); + + fclose (fp); + + return 0; +} + +static void +virtio_warning (uint64_t delay_ns, const char *what) +{ + static int virtio_message = 0; + + if (delay_ns > 1000000000) { + fprintf (stderr, + "supermin: waiting another %" PRIu64 " ns for %s to appear\n", + delay_ns, what); + + if (!virtio_message) { + fprintf (stderr, + "This usually means your kernel doesn't support virtio, or supermin was unable\n" + "to load some kernel modules (see module loading messages above).\n"); + virtio_message = 1; + } + } +} -- 2.12.0
Maybe Matching Threads
- [PATCH supermin] init: Support root=UUID=... to specify the appliance disk by volume UUID.
- [PATCH supermin 0/3] Require root= parameter, refactor init.
- [PATCH supermin 0/2] Allow an alternate libc to be used for init.
- [PATCH] appliance: Pass root=UUID=... to supermin.
- [PATCH] Add virtio-scsi support to febootstrap.