Modified rootdev to handle stacked device mappers

Rootdev only went down one level when traversing the device tree.
With the addition of the bootcache device mapper, we need to
go multiple levels for the -s option for rootdev.

BUG=chromium-os:25441
TEST=used it with bootcache to find boot device.

Change-Id: Ica82dc150e403d0e49e4d8074c0b920b20e4cccc
Reviewed-on: https://gerrit.chromium.org/gerrit/31851
Commit-Ready: Paul Taysom <taysom@chromium.org>
Reviewed-by: Paul Taysom <taysom@chromium.org>
Tested-by: Paul Taysom <taysom@chromium.org>
diff --git a/rootdev.c b/rootdev.c
index 3162be9..5ef0c3d 100644
--- a/rootdev.c
+++ b/rootdev.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  *
@@ -21,6 +21,13 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+/*
+ * A depth of more than about 4 slave devices
+ * will run out of kernel stack space, so setting
+ * the serach depth to 8 covers all possible cases.
+ */
+#define MAX_SLAVE_DEPTH 8
+
 static const char *kDefaultSearchPath = "/sys/block";
 static const char *kDefaultDevPath = "/dev";
 
@@ -75,9 +82,10 @@
   return dev;
 }
 
-/* Walks sysfs and will recurse into any directory/link that represents
- * a block device to find sub-devices (partitions).
- * If dev == 0, the first device in the directory will be returned. */
+/* Walks sysfs and recurses into any directory/link that represents
+ * a block device to find sub-devices (partitions) for dev.
+ * If dev == 0, the name fo the first device in the directory will be returned.
+ * Returns the device's name in "name" */
 static int match_sysfs_device(char *name, size_t name_len,
                               const char *basedir, dev_t *dev, int depth) {
   int found = -1;
@@ -253,25 +261,42 @@
   return 0;
 }
 
-int rootdev_get_device_slave(char *slave, size_t size, dev_t *dev,
-                             const char *device, const char *search) {
+/*
+ * rootdev_get_device_slave returns results in slave which
+ * may be the original device or the name of the slave.
+ *
+ * Because slave and device may point to the same data,
+ * must be careful how they are handled because slave
+ * is modified (can't use snprintf).
+ */
+void rootdev_get_device_slave(char *slave, size_t size, dev_t *dev,
+                              const char *device, const char *search) {
   char dst[PATH_MAX];
   int len = 0;
+  int i;
 
   if (search == NULL)
     search = kDefaultSearchPath;
 
-  /* So far, I've only seen top-level block devices with slaves. */
-  len = snprintf(dst, sizeof(dst), "%s/%s/slaves", search, device);
-  if (len < 0 || len != strlen(device) + strlen(search) + 8) {
-    warnx("rootdev_get_device_slave: device name too long");
-    return -1;
+  /*
+   * With stacked device mappers, we have to chain through all the levels
+   * and find the last device. For example, verity can be stacked on bootcache
+   * that is stacked on a disk partition.
+   */
+  strncpy(slave, device, size);
+  slave[size - 1] = '\0';
+  for (i = 0; i < MAX_SLAVE_DEPTH; i++) {
+    len = snprintf(dst, sizeof(dst), "%s/%s/slaves", search, slave);
+    if (len != strlen(device) + strlen(search) + 8) {
+      warnx("rootdev_get_device_slave: device name too long");
+      return;
+    }
+    *dev = 0;
+    if (match_sysfs_device(slave, size, dst, dev, 0) <= 0) {
+      return;
+    }
   }
-  *dev = 0;
-  if (match_sysfs_device(slave, size, dst, dev, 0) <= 0)
-    return -1;
-
-  return 0;
+  warnx("slave depth greater than %d at %s", i, slave);
 }
 
 int rootdev_create_devices(const char *name, dev_t dev, bool symlink) {
@@ -358,8 +383,8 @@
     return res;
 
   if (full)
-    res = rootdev_get_device_slave(devname, sizeof(devname), dev, devname,
-                                   search);
+    rootdev_get_device_slave(devname, sizeof(devname), dev, devname,
+                             search);
 
   /* TODO(wad) we should really just track the block dev, partition number, and
    *           dev path.  When we rewrite this, we can track all the sysfs info
diff --git a/rootdev.h b/rootdev.h
index 03b8bd8..74a48da 100644
--- a/rootdev.h
+++ b/rootdev.h
@@ -61,11 +61,10 @@
  * @device: name of the device to probe, like "sdb"
  * @search: path to search under. NULL for default.
  *
- * Returns 0 on success, non-zero on failure.
  * It is safe for @device == @slave.
  */
-int rootdev_get_device_slave(char *slave, size_t size, dev_t *dev,
-                             const char *device, const char *search);
+void rootdev_get_device_slave(char *slave, size_t size, dev_t *dev,
+                              const char *device, const char *search);
 
 /**
  * rootdev_get_path: converts a device name to a path in the device tree