stat: add %m to output the mount point for a file
authorAaron Burgemeister <dajoker@gmail.com>
Fri, 16 Jul 2010 01:54:49 +0000 (19:54 -0600)
committerPádraig Brady <P@draigBrady.com>
Fri, 27 Aug 2010 21:16:10 +0000 (22:16 +0100)
* src/find-mount-point.c: A new file refactoring
find_mount_point() out from df.c
* src/find-mount-point.h: Likewise.
* src/df.c: Use the new find-mount-point module.
* src/stat.c (print_stat): Handle the new %m format.
(find_bind_mount): A new function to
return the bind mount for a file if any.
(out_mount_mount): Print the bind mount for a file, or else
the standard mount point given by the find-mount-point module.
(usage): Document the %m format directive.
* src/Makefile.am: Reference the refactored find-mount-point.c
* po/POTFILES.in: Add find_mount_point.c to the translation list
* doc/coreutils.texi (stat invocation): Document %m,
and how it may differ from the mount point that df outputs.
* test/misc/stat-mount: A new test to correlate mount points
output from df and stat.
* tests/Makefile.am: Reference the new test.
* NEWS: Mention the new feature
* THANKS: Add the author

Signed-off-by: Pádraig Brady <P@draigBrady.com>
NEWS
THANKS
doc/coreutils.texi
po/POTFILES.in
src/Makefile.am
src/df.c
src/find-mount-point.c [new file with mode: 0644]
src/find-mount-point.h [new file with mode: 0644]
src/stat.c
tests/Makefile.am
tests/misc/stat-mount [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index 85f55a2..93a1f96 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,9 @@ GNU coreutils NEWS                                    -*- outline -*-
 
   sort now supports -d, -f, -i, -R, and -V in any combination.
 
+  stat now accepts the %m format directive to output
+  the mount point for a file.
+
 ** Changes in behavior
 
   df now consistently prints the device name for a bind mounted file,
diff --git a/THANKS b/THANKS
index 34b95f1..c368a2c 100644 (file)
--- a/THANKS
+++ b/THANKS
@@ -8,6 +8,7 @@ the bug-report mailing list (as seen on last line of e.g., cp --help).
 
 ???                                 kytek@cybercomm.net
 A Costa                             agcosta@gis.net
+Aaron Burgemeister                  dajoker@gmail.com
 Aaron Hawley                        ashawley@uvm.edu
 Achim Blumensath                    blume@corona.oche.de
 Adam Jimerson                       vendion@charter.net
index 7eedec7..6771da9 100644 (file)
@@ -8487,6 +8487,7 @@ removal is requested.  Equivalent to @option{-I}.
 When removing a hierarchy recursively, skip any directory that is on a
 file system different from that of the corresponding command line argument.
 
+@cindex bind mount
 This option is useful when removing a build ``chroot'' hierarchy,
 which normally contains no valuable data.  However, it is not uncommon
 to bind-mount @file{/home} into such a hierarchy, to make it easier to
@@ -10685,6 +10686,7 @@ The valid @var{format} directives for files with @option{--format} and
 @item %G - Group name of owner
 @item %h - Number of hard links
 @item %i - Inode number
+@item %m - Mount point (See note below)
 @item %n - File name
 @item %N - Quoted file name with dereference if symbolic link
 @item %o - I/O block size
@@ -10701,6 +10703,22 @@ The valid @var{format} directives for files with @option{--format} and
 @item %Z - Time of last change as seconds since Epoch
 @end itemize
 
+The mount point printed by @samp{%m} is similar to that output
+by @command{df}, except that:
+@itemize @bullet
+@item
+stat does not dereference symlinks by default
+(unless @option{-L} is specified)
+@item
+stat does not search for specified device nodes in the
+file system list, instead operating on them directly
+@item
+@cindex bind mount
+stat outputs the alias for a bind mounted file,
+rather than its backing device.  One can recursively call stat
+until there is no change in output, to get the base mount point
+@end itemize
+
 When listing file system information (@option{--file-system} (@option{-f})),
 you must use a different set of @var{format} directives:
 
index be20f4c..673a7d9 100644 (file)
@@ -61,6 +61,7 @@ src/expand.c
 src/expr.c
 src/factor.c
 src/false.c
+src/find-mount-point.c
 src/fmt.c
 src/fold.c
 src/getlimits.c
index 1a19fa6..00c7ff7 100644 (file)
@@ -145,6 +145,7 @@ noinst_HEADERS =    \
   copy.h               \
   cp-hash.h            \
   dircolors.h          \
+  find-mount-point.h   \
   fs.h                 \
   group-list.h         \
   ls.h                 \
@@ -478,6 +479,9 @@ rm_SOURCES = rm.c remove.c
 mkdir_SOURCES = mkdir.c prog-fprintf.c
 rmdir_SOURCES = rmdir.c prog-fprintf.c
 
+df_SOURCES = df.c find-mount-point.c
+stat_SOURCES = stat.c find-mount-point.c
+
 uname_SOURCES = uname.c uname-uname.c
 arch_SOURCES = uname.c uname-arch.c
 nproc_SOURCES = nproc.c
index 24877ab..749ce1a 100644 (file)
--- a/src/df.c
+++ b/src/df.c
@@ -29,8 +29,7 @@
 #include "human.h"
 #include "mountlist.h"
 #include "quote.h"
-#include "save-cwd.h"
-#include "xgetcwd.h"
+#include "find-mount-point.h"
 
 /* The official name of this program (e.g., no `g' prefix).  */
 #define PROGRAM_NAME "df"
@@ -522,94 +521,6 @@ show_dev (char const *disk, char const *mount_point,
   putchar ('\n');
 }
 
-/* Return the root mountpoint of the file system on which FILE exists, in
-   malloced storage.  FILE_STAT should be the result of stating FILE.
-   Give a diagnostic and return NULL if unable to determine the mount point.
-   Exit if unable to restore current working directory.  */
-static char *
-find_mount_point (const char *file, const struct stat *file_stat)
-{
-  struct saved_cwd cwd;
-  struct stat last_stat;
-  char *mp = NULL;             /* The malloced mount point.  */
-
-  if (save_cwd (&cwd) != 0)
-    {
-      error (0, errno, _("cannot get current directory"));
-      return NULL;
-    }
-
-  if (S_ISDIR (file_stat->st_mode))
-    /* FILE is a directory, so just chdir there directly.  */
-    {
-      last_stat = *file_stat;
-      if (chdir (file) < 0)
-        {
-          error (0, errno, _("cannot change to directory %s"), quote (file));
-          return NULL;
-        }
-    }
-  else
-    /* FILE is some other kind of file; use its directory.  */
-    {
-      char *xdir = dir_name (file);
-      char *dir;
-      ASSIGN_STRDUPA (dir, xdir);
-      free (xdir);
-
-      if (chdir (dir) < 0)
-        {
-          error (0, errno, _("cannot change to directory %s"), quote (dir));
-          return NULL;
-        }
-
-      if (stat (".", &last_stat) < 0)
-        {
-          error (0, errno, _("cannot stat current directory (now %s)"),
-                 quote (dir));
-          goto done;
-        }
-    }
-
-  /* Now walk up FILE's parents until we find another file system or /,
-     chdiring as we go.  LAST_STAT holds stat information for the last place
-     we visited.  */
-  while (true)
-    {
-      struct stat st;
-      if (stat ("..", &st) < 0)
-        {
-          error (0, errno, _("cannot stat %s"), quote (".."));
-          goto done;
-        }
-      if (st.st_dev != last_stat.st_dev || st.st_ino == last_stat.st_ino)
-        /* cwd is the mount point.  */
-        break;
-      if (chdir ("..") < 0)
-        {
-          error (0, errno, _("cannot change to directory %s"), quote (".."));
-          goto done;
-        }
-      last_stat = st;
-    }
-
-  /* Finally reached a mount point, see what it's called.  */
-  mp = xgetcwd ();
-
-done:
-  /* Restore the original cwd.  */
-  {
-    int save_errno = errno;
-    if (restore_cwd (&cwd) != 0)
-      error (EXIT_FAILURE, errno,
-             _("failed to return to initial working directory"));
-    free_cwd (&cwd);
-    errno = save_errno;
-  }
-
-  return mp;
-}
-
 /* If DISK corresponds to a mount point, show its usage
    and return true.  Otherwise, return false.  */
 static bool
diff --git a/src/find-mount-point.c b/src/find-mount-point.c
new file mode 100644 (file)
index 0000000..f3a2074
--- /dev/null
@@ -0,0 +1,113 @@
+/* find-mount-point.c -- find the root mount point for a file.
+   Copyright (C) 2010 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+#include <sys/types.h>
+
+#include "system.h"
+#include "error.h"
+#include "quote.h"
+#include "save-cwd.h"
+#include "xgetcwd.h"
+#include "find-mount-point.h"
+
+/* Return the root mountpoint of the file system on which FILE exists, in
+   malloced storage.  FILE_STAT should be the result of stating FILE.
+   Give a diagnostic and return NULL if unable to determine the mount point.
+   Exit if unable to restore current working directory.  */
+extern char *
+find_mount_point (const char *file, const struct stat *file_stat)
+{
+  struct saved_cwd cwd;
+  struct stat last_stat;
+  char *mp = NULL;             /* The malloced mount point.  */
+
+  if (save_cwd (&cwd) != 0)
+    {
+      error (0, errno, _("cannot get current directory"));
+      return NULL;
+    }
+
+  if (S_ISDIR (file_stat->st_mode))
+    /* FILE is a directory, so just chdir there directly.  */
+    {
+      last_stat = *file_stat;
+      if (chdir (file) < 0)
+        {
+          error (0, errno, _("cannot change to directory %s"), quote (file));
+          return NULL;
+        }
+    }
+  else
+    /* FILE is some other kind of file; use its directory.  */
+    {
+      char *xdir = dir_name (file);
+      char *dir;
+      ASSIGN_STRDUPA (dir, xdir);
+      free (xdir);
+
+      if (chdir (dir) < 0)
+        {
+          error (0, errno, _("cannot change to directory %s"), quote (dir));
+          return NULL;
+        }
+
+      if (stat (".", &last_stat) < 0)
+        {
+          error (0, errno, _("cannot stat current directory (now %s)"),
+                 quote (dir));
+          goto done;
+        }
+    }
+
+  /* Now walk up FILE's parents until we find another file system or /,
+     chdiring as we go.  LAST_STAT holds stat information for the last place
+     we visited.  */
+  while (true)
+    {
+      struct stat st;
+      if (stat ("..", &st) < 0)
+        {
+          error (0, errno, _("cannot stat %s"), quote (".."));
+          goto done;
+        }
+      if (st.st_dev != last_stat.st_dev || st.st_ino == last_stat.st_ino)
+        /* cwd is the mount point.  */
+        break;
+      if (chdir ("..") < 0)
+        {
+          error (0, errno, _("cannot change to directory %s"), quote (".."));
+          goto done;
+        }
+      last_stat = st;
+    }
+
+  /* Finally reached a mount point, see what it's called.  */
+  mp = xgetcwd ();
+
+done:
+  /* Restore the original cwd.  */
+  {
+    int save_errno = errno;
+    if (restore_cwd (&cwd) != 0)
+      error (EXIT_FAILURE, errno,
+             _("failed to return to initial working directory"));
+    free_cwd (&cwd);
+    errno = save_errno;
+  }
+
+  return mp;
+}
diff --git a/src/find-mount-point.h b/src/find-mount-point.h
new file mode 100644 (file)
index 0000000..49be9ba
--- /dev/null
@@ -0,0 +1,17 @@
+/* find-mount-point.h -- find the root mount point for a file.
+   Copyright (C) 2010 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+extern char* find_mount_point (const char *, const struct stat *);
index 18a746f..f985978 100644 (file)
 #include "filemode.h"
 #include "fs.h"
 #include "getopt.h"
+#include "mountlist.h"
 #include "quote.h"
 #include "quotearg.h"
 #include "stat-time.h"
 #include "strftime.h"
+#include "find-mount-point.h"
 
 #if USE_STATVFS
 # define STRUCT_STATVFS struct statvfs
@@ -604,6 +606,94 @@ print_statfs (char *pformat, size_t prefix_len, char m, char const *filename,
   return fail;
 }
 
+/* Return any bind mounted source for a path.
+   The caller should not free the returned buffer.
+   Return NULL if no bind mount found.  */
+static char const * ATTRIBUTE_WARN_UNUSED_RESULT
+find_bind_mount (char const * name)
+{
+  char const * bind_mount = NULL;
+
+  static struct mount_entry *mount_list;
+  static bool tried_mount_list = false;
+  if (!tried_mount_list) /* attempt/warn once per process.  */
+    {
+      if (!(mount_list = read_file_system_list (false)))
+        error (0, errno, "%s", _("cannot read table of mounted file systems"));
+      tried_mount_list = true;
+    }
+
+  struct mount_entry *me;
+  for (me = mount_list; me; me = me->me_next)
+    {
+      if (me->me_dummy && me->me_devname[0] == '/'
+          && STREQ (me->me_mountdir, name))
+        {
+          struct stat name_stats;
+          struct stat dev_stats;
+
+          if (stat (name, &name_stats) == 0
+              && stat (me->me_devname, &dev_stats) == 0
+              && SAME_INODE (name_stats, dev_stats))
+            {
+              bind_mount = me->me_devname;
+              break;
+            }
+        }
+    }
+
+  return bind_mount;
+}
+
+/* Print mount point.  Return zero upon success, nonzero upon failure.  */
+static bool ATTRIBUTE_WARN_UNUSED_RESULT
+out_mount_point (char const *filename, char *pformat, size_t prefix_len,
+                 const struct stat *statp)
+{
+
+  char const *np = "?", *bp = NULL;
+  char *mp = NULL;
+  bool fail = true;
+
+  /* Look for bind mounts first.  Note we output the immediate alias,
+     rather than further resolving to a base device mount point.  */
+  if (follow_links || !S_ISLNK (statp->st_mode))
+    {
+      char *resolved = canonicalize_file_name (filename);
+      if (!resolved)
+        {
+          error (0, errno, _("failed to canonicalize %s"), quote (filename));
+          goto print_mount_point;
+        }
+      bp = find_bind_mount (resolved);
+      free (resolved);
+      if (bp)
+        {
+          fail = false;
+          goto print_mount_point;
+        }
+    }
+
+  /* If there is no direct bind mount, then navigate
+     back up the tree looking for a device change.
+     Note we don't detect if any of the directory components
+     are bind mounted to the same device, but that's OK
+     since we've not directly queried them.  */
+  if ((mp = find_mount_point (filename, statp)))
+    {
+      /* This dir might be bind mounted to another device,
+         so we resolve the bound source in that case also.  */
+      bp = find_bind_mount (mp);
+      fail = false;
+    }
+
+print_mount_point:
+
+  out_string (pformat, prefix_len, bp ? bp : mp ? mp : np);
+  free (mp);
+  return fail;
+}
+
 /* Print stat info.  Return zero upon success, nonzero upon failure.  */
 static bool
 print_stat (char *pformat, size_t prefix_len, char m,
@@ -680,6 +770,9 @@ print_stat (char *pformat, size_t prefix_len, char m,
     case 't':
       out_uint_x (pformat, prefix_len, major (statbuf->st_rdev));
       break;
+    case 'm':
+      fail |= out_mount_point (filename, pformat, prefix_len, statbuf);
+      break;
     case 'T':
       out_uint_x (pformat, prefix_len, minor (statbuf->st_rdev));
       break;
@@ -1026,6 +1119,7 @@ The valid format sequences for files (without --file-system):\n\
       fputs (_("\
   %h   Number of hard links\n\
   %i   Inode number\n\
+  %m   Mount point\n\
   %n   File name\n\
   %N   Quoted file name with dereference if symbolic link\n\
   %o   I/O block size\n\
index 4aa93bf..5619d0b 100644 (file)
@@ -241,6 +241,7 @@ TESTS =                                             \
   misc/split-l                                 \
   misc/stat-fmt                                        \
   misc/stat-hyphen                             \
+  misc/stat-mount                              \
   misc/stat-printf                             \
   misc/stat-slash                              \
   misc/stdbuf                                  \
diff --git a/tests/misc/stat-mount b/tests/misc/stat-mount
new file mode 100755 (executable)
index 0000000..c1c43d4
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+# Test stat -c%m
+
+# Copyright (C) 2010 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/init.sh"; path_prepend_ ../src
+
+# Note we assume that the current directory is not bind mounted
+# (as then, stat and df may have different results).
+# This should be the case given the directory is temporary
+# for the duration of the test.
+
+df_mnt=$(df -P . | sed -n '2s/.* \([^ ]*$\)/\1/p')
+stat_mnt=$(stat -c%m .)
+
+test "$df_mnt" = "$stat_mnt" || fail=1
+
+Exit $fail