Author: Olivier Tilloy <olivier.tilloy@canonical.com>
Description: Read and parse /proc/self/mountinfo (if available)
 instead of /proc/mounts to get a list of mount points.
Bug: https://bugzilla.gnome.org/show_bug.cgi?id=522053

=== modified file 'gio/gunixmounts.c'
--- gio/gunixmounts.c	2011-02-21 13:43:04 +0000
+++ gio/gunixmounts.c	2011-08-04 14:23:19 +0000
@@ -187,6 +187,17 @@
 #define endmntent(f) fclose(f)
 #endif
 
+// for documentation on /proc/self/mountinfo,
+// see http://www.kernel.org/doc/Documentation/filesystems/proc.txt
+#define MOUNTINFO "/proc/self/mountinfo"
+
+static gboolean
+mountinfo_exists (void)
+{
+  struct stat buf;
+  return (stat (MOUNTINFO, &buf) == 0) ? TRUE : FALSE;
+}
+
 static gboolean
 is_in (const char *value, const char *set[])
 {
@@ -322,7 +333,10 @@
 {
 #ifdef _PATH_MOUNTED
 # ifdef __linux__
-  return "/proc/mounts";
+  if (mountinfo_exists ())
+    return MOUNTINFO;
+  else
+    return "/proc/mounts";
 # else
   return _PATH_MOUNTED;
 # endif
@@ -341,6 +355,20 @@
 #endif
 }
 
+static gboolean
+has_mount_option (char *mount_options, const char *option)
+{
+  const char *delim = ",";
+  char *token = strtok (mount_options, delim);
+  while (token != NULL)
+    {
+      if (strcmp (token, option) == 0)
+        return TRUE;
+      token = strtok (NULL, delim);
+    }
+  return FALSE;
+}
+
 #ifndef HAVE_GETMNTENT_R
 G_LOCK_DEFINE_STATIC(getmntent);
 #endif
@@ -358,71 +386,117 @@
   GUnixMountEntry *mount_entry;
   GHashTable *mounts_hash;
   GList *return_list;
+
+  gboolean have_mountinfo;
+  /* Used to manually parse /proc/self/mountinfo. */
+  int mountid, parentid;
+  int major, minor;
+  char root[PATH_MAX];
+  char mount_point[PATH_MAX];
+  char mount_options[PATH_MAX];
+  /* ignoring optional fields */
+  char fstype[PATH_MAX];
+  char mount_source[PATH_MAX];
+  char super_options[PATH_MAX];
   
   read_file = get_mtab_read_file ();
 
-  file = setmntent (read_file, "r");
+  have_mountinfo = mountinfo_exists ();
+  if (have_mountinfo)
+    file = fopen(read_file, "r");
+  else
+    file = setmntent (read_file, "r");
   if (file == NULL)
     return NULL;
 
   return_list = NULL;
   
   mounts_hash = g_hash_table_new (g_str_hash, g_str_equal);
-  
+
+  if (have_mountinfo)
+    {
+      while (fscanf (file, "%d %d %d:%d %s %s %s%*[^-]- %s %s %s%*[^\n]",
+                     &mountid, &parentid, &major, &minor, root, mount_point,
+                     mount_options, fstype, mount_source, super_options) == 10)
+        {
+          mount_entry = g_new0 (GUnixMountEntry, 1);
+          mount_entry->mount_path = g_strdup (mount_point);
+          if (strcmp (mount_source, "/dev/root") == 0)
+            mount_entry->device_path = g_strdup (_resolve_dev_root ());
+          else
+            mount_entry->device_path = g_strdup (mount_source);
+          mount_entry->filesystem_type = g_strdup (fstype);
+          mount_entry->is_read_only = has_mount_option (mount_options, MNTOPT_RO);
+          mount_entry->is_system_internal =
+            guess_system_internal (mount_entry->mount_path,
+                                   mount_entry->filesystem_type,
+                                   mount_entry->device_path);
+
+          g_hash_table_insert (mounts_hash,
+                               mount_entry->device_path,
+                               mount_entry->device_path);
+
+          return_list = g_list_prepend (return_list, mount_entry);
+        }
+      fclose (file);
+    }
+  else
+    {
 #ifdef HAVE_GETMNTENT_R
-  while ((mntent = getmntent_r (file, &ent, buf, sizeof (buf))) != NULL)
+      while ((mntent = getmntent_r (file, &ent, buf, sizeof (buf))) != NULL)
 #else
-  G_LOCK (getmntent);
-  while ((mntent = getmntent (file)) != NULL)
+      G_LOCK (getmntent);
+      while ((mntent = getmntent (file)) != NULL)
 #endif
-    {
-      /* ignore any mnt_fsname that is repeated and begins with a '/'
-       *
-       * We do this to avoid being fooled by --bind mounts, since
-       * these have the same device as the location they bind to.
-       * It's not an ideal solution to the problem, but it's likely that
-       * the most important mountpoint is first and the --bind ones after
-       * that aren't as important. So it should work.
-       *
-       * The '/' is to handle procfs, tmpfs and other no device mounts.
-       */
-      if (mntent->mnt_fsname != NULL &&
-	  mntent->mnt_fsname[0] == '/' &&
-	  g_hash_table_lookup (mounts_hash, mntent->mnt_fsname))
-        continue;
-      
-      mount_entry = g_new0 (GUnixMountEntry, 1);
-      mount_entry->mount_path = g_strdup (mntent->mnt_dir);
-      if (strcmp (mntent->mnt_fsname, "/dev/root") == 0)
-        mount_entry->device_path = g_strdup (_resolve_dev_root ());
-      else
-        mount_entry->device_path = g_strdup (mntent->mnt_fsname);
-      mount_entry->filesystem_type = g_strdup (mntent->mnt_type);
+        {
+          /* ignore any mnt_fsname that is repeated and begins with a '/'
+           *
+           * We do this to avoid being fooled by --bind mounts, since
+           * these have the same device as the location they bind to.
+           * It's not an ideal solution to the problem, but it's likely that
+           * the most important mountpoint is first and the --bind ones after
+           * that aren't as important. So it should work.
+           *
+           * The '/' is to handle procfs, tmpfs and other no device mounts.
+           */
+          if (mntent->mnt_fsname != NULL &&
+            mntent->mnt_fsname[0] == '/' &&
+            g_hash_table_lookup (mounts_hash, mntent->mnt_fsname))
+            continue;
+
+          mount_entry = g_new0 (GUnixMountEntry, 1);
+          mount_entry->mount_path = g_strdup (mntent->mnt_dir);
+          if (strcmp (mntent->mnt_fsname, "/dev/root") == 0)
+            mount_entry->device_path = g_strdup (_resolve_dev_root ());
+          else
+            mount_entry->device_path = g_strdup (mntent->mnt_fsname);
+          mount_entry->filesystem_type = g_strdup (mntent->mnt_type);
       
 #if defined (HAVE_HASMNTOPT)
-      if (hasmntopt (mntent, MNTOPT_RO) != NULL)
-	mount_entry->is_read_only = TRUE;
+          if (hasmntopt (mntent, MNTOPT_RO) != NULL)
+            mount_entry->is_read_only = TRUE;
 #endif
       
-      mount_entry->is_system_internal =
-	guess_system_internal (mount_entry->mount_path,
-			       mount_entry->filesystem_type,
-			       mount_entry->device_path);
-      
-      g_hash_table_insert (mounts_hash,
-			   mount_entry->device_path,
-			   mount_entry->device_path);
-      
-      return_list = g_list_prepend (return_list, mount_entry);
-    }
-  g_hash_table_destroy (mounts_hash);
+          mount_entry->is_system_internal =
+            guess_system_internal (mount_entry->mount_path,
+                                   mount_entry->filesystem_type,
+                                   mount_entry->device_path);
+      
+          g_hash_table_insert (mounts_hash,
+                               mount_entry->device_path,
+                               mount_entry->device_path);
+      
+          return_list = g_list_prepend (return_list, mount_entry);
+        }
+      g_hash_table_destroy (mounts_hash);
   
-  endmntent (file);
+      endmntent (file);
 
 #ifndef HAVE_GETMNTENT_R
-  G_UNLOCK (getmntent);
+      G_UNLOCK (getmntent);
 #endif
-  
+    }
+
   return g_list_reverse (return_list);
 }
 

