third_party/rootdev: support btrfs using ioctl

rootdev assumes only one device exist for an FS (which is true for ext2/squashfs). FS like
btrfs supports multiple devices and uses an anonymous namespace for each device number. Thus sys
call 'stat' no longer works for btrfs. This change adds a function rootdev_wrapper_btrfs in
which we use ioctl to read the ioctl dev info from device directly. Then in main.c and rootdev
library call, we predicate on FS type to decide whether call rootdev_wrapper or
rootdev_wrapper_btrfs.

BUG=chromium:702400, chromium:698296
TEST=manual

Change-Id: I4a388192026ec36dd02c810cf5f31d02f5a87988
Reviewed-on: https://chromium-review.googlesource.com/486263
Commit-Ready: Xiaochu Liu <xiaochu@chromium.org>
Tested-by: Xiaochu Liu <xiaochu@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
diff --git a/main.c b/main.c
index 32f98fa..ca99723 100644
--- a/main.c
+++ b/main.c
@@ -136,10 +136,13 @@
                         flag_use_slave,
                         flag_strip_partition,
                         &root_dev,
+                        flag_path,
                         flag_block_path,
                         flag_dev_path);
 
-  if (ret == 1 && flag_create) {
+  /* root_dev can be zero-out by rootdev_wrapper when virtual
+   * device id is used (e.g. btrfs). */
+  if (root_dev != 0 && ret == 1 && flag_create) {
     /* TODO(wad) add flag_force to allow replacement */
     ret = 0;
     if (mknod(path, S_IFBLK | S_IRUSR | S_IWUSR, root_dev) && errno != EEXIST) {
diff --git a/rootdev.c b/rootdev.c
index ea88b39..6340ddd 100644
--- a/rootdev.c
+++ b/rootdev.c
@@ -17,9 +17,13 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/vfs.h>
 #include <unistd.h>
+#include <linux/btrfs.h>
+#include <linux/magic.h>
 
 /*
  * Limit prevents endless looping to find slave.
@@ -366,18 +370,61 @@
   return 0;
 }
 
+static int get_rootdev_btrfs(char *path, size_t size, const char *mount_path) {
+  const char *di_path;
+
+  /* Open the mount point path */
+  int fd = open(mount_path, O_RDONLY | O_CLOEXEC);
+  if (fd == -1) {
+    return -1;
+  }
+  /* Create space to hold the ioctl dev info. */
+  struct btrfs_ioctl_dev_info_args di_args;
+  /* Since we use always use single device in chromebook rootfs,
+   * the device id for this device is always 1. */
+  di_args.devid = 1;
+  /* Read the ioctl device info (btrfs). */
+  if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di_args) != 0) {
+    close(fd);
+    return -1;
+  }
+  close(fd);
+
+  di_path = (const char *)di_args.path;
+  /* Try to access the device at di_args->path to verify its existence. */
+  if (access((char *)di_path, F_OK) != 0) {
+    return -1;
+  }
+  if (strlen(di_path) >= size)
+    return -1;
+  strcpy(path, di_path);
+  return 0;
+}
+
 int rootdev_wrapper(char *path, size_t size,
                     bool full, bool strip,
                     dev_t *dev,
+                    const char *mount_path,
                     const char *search, const char *dev_path) {
   int res = 0;
   char devname[PATH_MAX];
+  struct statfs mount_statfs;
+
   if (!search)
     search = kDefaultSearchPath;
   if (!dev_path)
    dev_path = kDefaultDevPath;
   if (!dev)
     return -1;
+  if (statfs(mount_path, &mount_statfs) == 0) {
+    if (mount_statfs.f_type == BTRFS_SUPER_MAGIC) {
+      /* BTRFS uses virtual device id which is different from actual
+       * device id. So we zero-out dev_t indicating that dev_t is not
+       * a valid device id. */
+      *dev = 0;
+      return get_rootdev_btrfs(path, size, mount_path);
+    }
+  }
 
   res = rootdev_get_device(devname, sizeof(devname), *dev, search);
   if (res != 0)
@@ -426,6 +473,7 @@
                          full,
                          strip,
                          root_dev,
+                         "/",
                          NULL,  /* default /sys dir */
                          NULL);  /* default /dev dir */
 }
diff --git a/rootdev.h b/rootdev.h
index 74a48da..87361d2 100644
--- a/rootdev.h
+++ b/rootdev.h
@@ -37,6 +37,7 @@
 int rootdev_wrapper(char *path, size_t size,
                     bool full, bool strip,
                     dev_t *dev,
+                    const char *mount_path,
                     const char *search, const char *dev_path);
 /**
  * rootdev_get_device: finds the /dev path for @dev