[Libguestfs] [PATCH supermin] init: Support root=UUID=... to specify the appliance disk by volume UUID.

Richard W.M. Jones rjones at redhat.com
Wed Apr 19 16:43:41 UTC 2017


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




More information about the Libguestfs mailing list