rootdev: Prevent infinite recursion when parsing /sys/block.

The mmcblk driver of the 3.0.8 kernel adds mmcblk0boot0 and mmcblk0boot1
directories under /sys/block or /sys/block/mmcblk0.  I have seen it both ways.
The device symlink in those directories points to "../../mmcblk0" which
points to the grandparent directory.

When rootdev scans /sys/block looking for a device that matches "/", it
can fall into this loop and recurse until failure.

While a proper fix would detect loops to prevent recursion, that adds
considerable complexity to the directory scan.
This relatively simple solution limits recursion depth to 5 to prevent
infinitely looping.  It will fail to find device matches beyond 5 levels
of directories, but we are nowhere near that in today's systems.  We find
matches at level 3 as best I can tell.

BUG=chromium-os:22855
TEST=Run rootdev and check for failures.

Change-Id: I9ef3aa0a6d6f8143dd0b9e012ba4bb4adfa0c73b
Reviewed-on: https://gerrit.chromium.org/gerrit/11575
Reviewed-by: Will Drewry <wad@chromium.org>
Commit-Ready: Bryan Freed <bfreed@chromium.org>
Tested-by: Bryan Freed <bfreed@chromium.org>
diff --git a/rootdev.c b/rootdev.c
index 867974f..3162be9 100644
--- a/rootdev.c
+++ b/rootdev.c
@@ -79,7 +79,7 @@
  * a block device to find sub-devices (partitions).
  * If dev == 0, the first device in the directory will be returned. */
 static int match_sysfs_device(char *name, size_t name_len,
-                              const char *basedir, dev_t *dev) {
+                              const char *basedir, dev_t *dev, int depth) {
   int found = -1;
   size_t basedir_len;
   DIR *dirp = NULL;
@@ -165,10 +165,14 @@
       break;
     }
 
+    /* Prevent infinite recursion on symlink loops by limiting depth. */
+    if (depth > 5)
+      break;
+
     /* Recurse one level for devices that may have a matching partition. */
     if (major(found_devt) == major(*dev) && minor(*dev) > minor(found_devt)) {
       sprintf(working_path, "%s/%s", basedir, entry->d_name);
-      found = match_sysfs_device(name, name_len, working_path, dev);
+      found = match_sysfs_device(name, name_len, working_path, dev, depth + 1);
       if (found > 0)
         break;
     }
@@ -241,7 +245,7 @@
   }
 
   snprintf(dst, size, "%s", search);
-  if (match_sysfs_device(dst, size, dst, &dev) <= 0) {
+  if (match_sysfs_device(dst, size, dst, &dev, 0) <= 0) {
     fprintf (stderr, "unable to find match\n");
     return 1;
   }
@@ -264,7 +268,7 @@
     return -1;
   }
   *dev = 0;
-  if (match_sysfs_device(slave, size, dst, dev) <= 0)
+  if (match_sysfs_device(slave, size, dst, dev, 0) <= 0)
     return -1;
 
   return 0;