+/* Compute a uuid hash as a simple xor of of its 4 32bit components */
+static size_t
+uuid_hash(const void *x, size_t table_size)
+{
+ const md_uuid *a = x;
+
+ size_t h = a->uuid[0];
+ for (size_t i = 1; i < 4; i++) {
+ h ^= a->uuid[i];
+ }
+
+ return h % table_size;
+}
+
+static bool
+uuid_cmp(const void *x, const void *y)
+{
+ const md_uuid *a = x;
+ const md_uuid *b = y;
+
+ for (size_t i = 0; i < 1; i++) {
+ if (a->uuid[i] != b->uuid[i]) return false;
+ }
+
+ return true;
+}
+
+static void
+md_uuid_free(void *x)
+{
+ md_uuid *a = x;
+ free(a->path);
+ free(a);
+}
+
+/* Taken from parse_uuid in mdadm */
+static int
+parse_uuid(const char *str, uint32_t *uuid)
+{
+ for (size_t i = 0; i < 4; i++) uuid[i] = 0;
+
+ int hit = 0; /* number of Hex digIT */
+ char c;
+ while ((c = *str++)) {
+ int n;
+ if (c >= '0' && c <= '9')
+ n = c - '0';
+ else if (c >= 'a' && c <= 'f')
+ n = 10 + c - 'a';
+ else if (c >= 'A' && c <= 'F')
+ n = 10 + c - 'A';
+ else if (strchr(":. -", c))
+ continue;
+ else return false;
+
+ if (hit < 32) {
+ uuid[hit / 8] <<= 4;
+ uuid[hit / 8] += n;
+ }
+ hit++;
+ }
+ if (hit == 32) return 0;
+
+ return -1;
+}
+
+/* Create a mapping of uuids to appliance md device names */
+static int
+map_app_md_devices (guestfs_h *g, Hash_table **map)
+{
+ char **mds = NULL;
+ int n = 0;
+
+ /* A hash mapping uuids to md device names */
+ *map = hash_initialize(16, NULL, uuid_hash, uuid_cmp, md_uuid_free);
+ if (*map == NULL) g->abort_cb();
+
+ mds = guestfs_list_md_devices(g);
+ if (mds == NULL) goto error;
+
+ for (char **md = mds; *md != NULL; md++) {
+ char **detail = guestfs_md_detail(g, *md);
+ if (detail == NULL) goto error;
+
+ /* Iterate over keys until we find uuid */
+ char **i;
+ for (i = detail; *i != NULL; i += 2) {
+ if (STREQ(*i, "uuid")) break;
+ }
+
+ /* We found it */
+ if (*i) {
+ /* Next item is the uuid value */
+ i++;
+
+ md_uuid *entry = safe_malloc(g, sizeof(md_uuid));
+ entry->path = safe_strdup(g, *md);
+
+ if (parse_uuid(*i, entry->uuid) == -1) {
+ /* Invalid UUID is weird, but not fatal. */
+ debug(g, "inspect-os: guestfs_md_detail returned invalid "
+ "uuid for %s: %s", *md, *i);
+ guestfs___free_string_list(detail);
+ md_uuid_free(entry);
+ continue;
+ }
+
+ const void *matched = NULL;
+ switch (hash_insert_if_absent(*map, entry, &matched)) {
+ case -1:
+ g->abort_cb();
+
+ case 0:
+ /* Duplicate uuid in for md device is weird, but not fatal. */
+ debug(g, "inspect-os: md devices %s and %s have the same uuid",
+ ((md_uuid *)matched)->path, entry->path);
+ md_uuid_free(entry);
+ break;
+
+ default:
+ n++;
+ }
+ }
+
+ guestfs___free_string_list(detail);
+ }
+
+ guestfs___free_string_list(mds);
+
+ return n;
+
+error:
+ hash_free(*map); *map = NULL;
+ guestfs___free_string_list(mds);
+
+ return -1;
+}
+
+static size_t
+mdadm_app_hash(const void *x, size_t table_size)
+{
+ const mdadm_app *a = x;
+ return hash_pjw(a->mdadm, table_size);
+}
+
+static bool
+mdadm_app_cmp(const void *x, const void *y)
+{
+ const mdadm_app *a = x;
+ const mdadm_app *b = y;
+
+ return strcmp(a->mdadm, b->mdadm) == 0;
+}
+
+static void
+mdadm_app_free(void *x)
+{
+ mdadm_app *a = x;
+ free(a->mdadm);
+ free(a->app);
+ free(a);
+}
+
+/* Get a map of md device names in mdadm.conf to their device names in the
+ * appliance */
+static int
+map_md_devices(guestfs_h *g, Hash_table **map)
+{
+ Hash_table *app_map = NULL;
+ char **matches = NULL;
+ *map = NULL;
+
+ /* Get a map of md device uuids to their device names in the appliance */
+ int n_app_md_devices = map_app_md_devices(g, &app_map);
+ if (n_app_md_devices == -1) goto error;
+
+ /* Nothing to do if there are no md devices */
+ if (n_app_md_devices == 0) {
+ hash_free(app_map);
+ return 0;
+ }
+
+ /* Get all arrays listed in mdadm.conf */
+ matches = guestfs_aug_match(g, "/files/etc/mdadm.conf/array");
+ if (!matches) goto error;
+
+ /* Log a debug message if we've got md devices, but nothing in mdadm.conf */
+ if (matches[0] == NULL) {
+ debug(g, "Appliance has MD devices, but augeas returned no array matches "
+ "in mdadm.conf");
+ guestfs___free_string_list(matches);
+ hash_free(app_map);
+ return 0;
+ }
+
+ *map = hash_initialize(16, NULL, mdadm_app_hash, mdadm_app_cmp,
+ mdadm_app_free);
+ if (!*map) g->abort_cb();
+
+ for (char **match = matches; *match != NULL; match++) {
+ /* Get device name and uuid for each array */
+ char *dev_path = safe_asprintf(g, "%s/devicename", *match);
+ char *dev = guestfs_aug_get(g, dev_path);
+ free(dev_path);
+ if (!dev) goto error;
+
+ char *uuid_path = safe_asprintf(g, "%s/uuid", *match);
+ char *uuid = guestfs_aug_get(g, uuid_path);
+ free(uuid_path);
+ if (!uuid) {
+ free(dev);
+ goto error;
+ }
+
+ /* Parse the uuid into an md_uuid structure so we can look it up in the
+ * uuid->appliance device map */
+ md_uuid mdadm;
+ mdadm.path = dev;
+ if (parse_uuid(uuid, mdadm.uuid) == -1) {
+ /* Invalid uuid. Weird, but not fatal. */
+ debug(g, "inspect-os: mdadm.conf contains invalid uuid for %s: %s",
+ dev, uuid);
+ free(dev);
+ free(uuid);
+ continue;
+ }
+ free(uuid);
+
+ /* If there's a corresponding uuid in the appliance, create a new
+ * entry in the transitive map */
+ md_uuid *app = hash_lookup(app_map, &mdadm);
+ if (app) {
+ mdadm_app *entry = safe_malloc(g, sizeof(mdadm_app));
+ entry->mdadm = dev;
+ entry->app = safe_strdup(g, app->path);
+
+ switch (hash_insert_if_absent(*map, entry, NULL)) {
+ case -1:
+ g->abort_cb();
+
+ case 0:
+ /* Duplicate uuid in for md device is weird, but not fatal. */
+ debug(g, "inspect-os: mdadm.conf contains multiple entries for %s",
+ app->path);
+ mdadm_app_free(entry);
+ continue;
+
+ default:
+ ;;
+ }
+ } else {
+ free(dev);
+ }
+ }
+
+ hash_free(app_map);
+ guestfs___free_string_list(matches);
+
+ return 0;
+
+error:
+ if (app_map) hash_free(app_map);
+ if (matches) guestfs___free_string_list(matches);
+ if (*map) hash_free(*map);
+
+ return -1;
+}
+