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