]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
Add btrfs probing support, currently only in the single-device case.
authorColin Watson <cjwatson@ubuntu.com>
Tue, 18 May 2010 12:01:59 +0000 (13:01 +0100)
committerColin Watson <cjwatson@ubuntu.com>
Tue, 18 May 2010 12:01:59 +0000 (13:01 +0100)
* kern/emu/getroot.c (find_root_device_from_mountinfo): New
function.
(grub_guess_root_device): Call find_root_device_from_mountinfo
before looking in /dev.

ChangeLog.btrfs-probe [new file with mode: 0644]
kern/emu/getroot.c

diff --git a/ChangeLog.btrfs-probe b/ChangeLog.btrfs-probe
new file mode 100644 (file)
index 0000000..77c9995
--- /dev/null
@@ -0,0 +1,8 @@
+2010-05-18  Colin Watson  <cjwatson@ubuntu.com>
+
+       Add btrfs probing support, currently only in the single-device case.
+
+       * kern/emu/getroot.c (find_root_device_from_mountinfo): New
+       function.
+       (grub_guess_root_device): Call find_root_device_from_mountinfo
+       before looking in /dev.
index 6875044dae6a0c39a7014f51467bb0e8c792ca51..da62089fa1b5c9f4e218d9565eec327b332d5457 100644 (file)
@@ -80,6 +80,84 @@ xgetcwd (void)
   return path;
 }
 
+#ifdef __linux__
+
+/* Statting something on a btrfs filesystem always returns a virtual device
+   major/minor pair rather than the real underlying device, because btrfs
+   can span multiple underlying devices (and even if it's currently only
+   using a single device it can be dynamically extended onto another).  We
+   can't deal with the multiple-device case yet, but in the meantime, we can
+   at least cope with the single-device case by scanning
+   /proc/self/mountinfo.  */
+static char *
+find_root_device_from_mountinfo (const char *dir)
+{
+  FILE *fp;
+  char buf[1024];      /* XXX */
+  char *ret = NULL;
+
+  fp = fopen ("/proc/self/mountinfo", "r");
+  if (! fp)
+    return NULL; /* fall through to other methods */
+
+  while (fgets (buf, sizeof (buf), fp))
+    {
+      int mnt_id, parent_mnt_id;
+      unsigned int major, minor;
+      char enc_root[PATH_MAX], enc_path[PATH_MAX];
+      int count;
+      size_t enc_path_len;
+      const char *sep;
+      char fstype[PATH_MAX], device[PATH_MAX];
+      struct stat st;
+
+      if (sscanf (buf, "%d %d %u:%u %s %s%n",
+                 &mnt_id, &parent_mnt_id, &major, &minor, enc_root, enc_path,
+                 &count) < 6)
+       continue;
+
+      if (strcmp (enc_root, "/") != 0)
+       continue; /* only a subtree is mounted */
+
+      enc_path_len = strlen (enc_path);
+      if (strncmp (dir, enc_path, enc_path_len) != 0 ||
+         (dir[enc_path_len] && dir[enc_path_len] != '/'))
+       continue;
+
+      /* This is a parent of the requested directory.  /proc/self/mountinfo
+        is in mount order, so it must be the closest parent we've
+        encountered so far.  If it's virtual, return its device node;
+        otherwise, carry on to try to find something closer.  */
+
+      free (ret);
+      ret = NULL;
+
+      if (major != 0)
+       continue; /* not a virtual device */
+
+      sep = strstr (buf + count, " - ");
+      if (!sep)
+       continue;
+
+      sep += strlen (" - ");
+      if (sscanf (sep, "%s %s", fstype, device) != 2)
+       continue;
+
+      if (stat (device, &st) < 0)
+       continue;
+
+      if (!S_ISBLK (st.st_mode))
+       continue; /* not a block device */
+
+      ret = strdup (device);
+    }
+
+  fclose (fp);
+  return ret;
+}
+
+#endif /* __linux__ */
+
 #ifdef __MINGW32__
 
 static char *
@@ -355,6 +433,12 @@ grub_guess_root_device (const char *dir)
 #else /* !__GNU__ */
   struct stat st;
 
+#ifdef __linux__
+  os_dev = find_root_device_from_mountinfo (dir);
+  if (os_dev)
+    return os_dev;
+#endif /* __linux__ */
+
   if (stat (dir, &st) < 0)
     grub_util_error ("cannot stat `%s'", dir);