Richard W.M. Jones
2017-Apr-19 16:43 UTC
[Libguestfs] [PATCH supermin] 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 | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 192 insertions(+), 34 deletions(-) diff --git a/init/init.c b/init/init.c index ddfc437..3885cc6 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]; @@ -98,13 +104,11 @@ main () { FILE *fp; size_t n; - 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 { \ @@ -181,46 +185,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); @@ -493,3 +510,144 @@ 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]); + root += 2; + } + + if (i < 16) { + fprintf (stderr, "supermin: root UUID should be 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
Pino Toscano
2017-Apr-25 13:08 UTC
Re: [Libguestfs] [PATCH supermin] init: Support root=UUID=... to specify the appliance disk by volume UUID.
On Wednesday, 19 April 2017 18:43:41 CEST Richard W.M. Jones wrote:> 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. > --- > [...] > + > + 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); > }This code for busy waiting could be factored in a common function -- printing either the /dev/path or the UUID. +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]); > + root += 2; > + } > + > + if (i < 16) { > + fprintf (stderr, "supermin: root UUID should be 16 bytes\n"); > + exit (EXIT_FAILURE); > + }Is this check actually ever going to be true? It looks to me that the while loop above can be broken only in two cases: a) the any of the isxdigit checks fails b) the condition of the while becomes false In case of (a), there's exit() already, so it will not execute anything else anyway; in case of (b), then i will always be >= 16, and thus never going to satisfy the check above. Also, parse_root_uuid does not fail when the uuid string has more character than the ones needed. I'd suggest changing the last check to something like: if (*root) { fprintf (stderr, "supermin: root UUID contains more than 16 bytes\n"); exit (EXIT_FAILURE); } Thanks, -- Pino Toscano
Possibly Parallel Threads
- [PATCH supermin v2] init: Support root=UUID=... to specify the appliance disk by volume UUID.
- [PATCH supermin 3/3] init: Refactor for-loop which waits for root device to show up.
- [PATCH supermin 2/3] init: Move variable declarations to the top of the function.
- [PATCH supermin 0/3] Require root= parameter, refactor init.
- [PATCH supermin 0/2] Allow an alternate libc to be used for init.