Subject: bug#16539: More details on df command output for you

From: Pádraig Brady <[email-masked]>
Date: Sat, 25 Jan 2014 03:19:57 +0000

On 01/24/2014 10:27 PM, Bernhard Voelker wrote:
> tag 16539 notabug
> close
> thanks
> 
> On 01/24/2014 09:47 PM, Curtis Rubel wrote:
>>
>> df command output with no args:
>>
>> only 1 nfs mount is listed

> Just for info: df in openSUSE-13.1 is currently identical to that
> in coreutils-v8.21.
> 
> The above is the result of df suppressing duplicate entries like
> bind mounts.  This filtering is done based on the device number.
> As this example shows, a few exports of directories of the same file
> system from "host" are mounted - yet it's the same file system.

Right. Essentially df is showing storage for available file systems.
Noting that df also has a --total option, it makes sense by default
to not repeat file systems. This can be overridden easily with the
-a option as noted above.

Actually we should in fact be merging more entries!
Notice the following:

>> tmpfs                 4095336     4688   4090648   1% /run
>> tmpfs                 4095336     4688   4090648   1% /var/run
>> tmpfs                 4095336     4688   4090648   1% /var/lock

Hopefully the attached patch addresses this
(and a couple of other test issues).

thanks,
Pádraig.
>From 5991616451187db13ea9731ac06101081af8ccda Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Sat, 25 Jan 2014 01:14:29 +0000
Subject: [PATCH] df: also deduplicate virtual file systems

* src/df.c (filter_mountlist): Remove the constraint that
a '/' needs to be in the device name for a mount entry to
be considered for deduplication.  Virtual file systems also
have storage associated with them (like tmpfs for example),
and thus need to be deduplicated since they will be shown
in the default df output and subject to --total processing also.
* test/df/skip-duplicates.sh: Add a test to ensure we deduplicate
all entries, even for virtual file systems.  Also avoid possible
length operations on many remote file systems in the initial
check of df operation.  Also avoid the assumption that "/root"
is on the same file system as "/".
* NEWS: Mention the change in behavior.
---
 NEWS                        |    5 ++++
 src/df.c                    |   31 +++++++++++++----------------
 tests/df/skip-duplicates.sh |   45 ++++++++++++++++++++++++++++++------------
 3 files changed, 51 insertions(+), 30 deletions(-)

diff --git a/NEWS b/NEWS
index 88a4154..2bf110a 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,11 @@ GNU coreutils NEWS                                    -*- outline -*-
   it would display an error, requiring --no-dereference to avoid the issue.
   [bug introduced in coreutils-5.3.0]
 
+** Changes in behavior
+
+  df now properly outputs file system information for virtual file systems
+  like tmpfs, by skipping duplicate entries (identified by the device number).
+
 
 * Noteworthy changes in release 8.22 (2013-12-13) [stable]
 
diff --git a/src/df.c b/src/df.c
index e4fafb9..23b5156 100644
--- a/src/df.c
+++ b/src/df.c
@@ -630,26 +630,23 @@ filter_mount_list (void)
         }
       else
         {
-          /* If the device name is a real path name ...  */
-          if (strchr (me->me_devname, '/'))
+          /* If we've already seen this device...  */
+          for (devlist = devlist_head; devlist; devlist = devlist->next)
+            if (devlist->dev_num == buf.st_dev)
+              break;
+
+          if (devlist)
             {
-              /* ... try to find its device number in the devlist.  */
-              for (devlist = devlist_head; devlist; devlist = devlist->next)
-                if (devlist->dev_num == buf.st_dev)
-                  break;
+              discard_me = me;
 
-              if (devlist)
+              /* ...let the shorter mountdir win.  */
+              if ((strchr (me->me_devname, '/')
+                   && ! strchr (devlist->me->me_devname, '/'))
+                  || (strlen (devlist->me->me_mountdir)
+                      > strlen (me->me_mountdir)))
                 {
-                  discard_me = me;
-
-                  /* Let the shorter mountdir win.  */
-                  if (! strchr (devlist->me->me_devname, '/')
-                      || (strlen (devlist->me->me_mountdir)
-                         > strlen (me->me_mountdir)))
-                    {
-                      discard_me = devlist->me;
-                      devlist->me = me;
-                    }
+                  discard_me = devlist->me;
+                  devlist->me = me;
                 }
             }
         }
diff --git a/tests/df/skip-duplicates.sh b/tests/df/skip-duplicates.sh
index 266520a..b41623f 100755
--- a/tests/df/skip-duplicates.sh
+++ b/tests/df/skip-duplicates.sh
@@ -21,19 +21,26 @@
 print_ver_ df
 require_gcc_shared_
 
-df || skip_ "df fails"
+# We use --local here so as to not activate
+# potentially very many remote mounts.
+df --local || skip_ "df fails"
 
-# Simulate an mtab file with two entries of the same device number.
-# Also add entries with unstatable mount dirs to ensure that's handled.
+export CU_NONROOT_FS=$(df --local --output=target 2>&1 | grep /. | head -n1)
+test -z "$CU_NONROOT_FS" && unique_entries=1 || unique_entries=2
+
+# Simulate an mtab file to test various cases.
 cat > k.c <<'EOF' || framework_failure_
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <mntent.h>
 
 struct mntent *getmntent (FILE *fp)
 {
+  static char *nonroot_fs;
+  static int done;
+
   /* Prove that LD_PRELOAD works. */
-  static int done = 0;
   if (!done)
     {
       fclose (fopen ("x", "w"));
@@ -43,18 +50,30 @@ struct mntent *getmntent (FILE *fp)
   static struct mntent mntents[] = {
     {.mnt_fsname="/short",  .mnt_dir="/invalid/mount/dir"},
     {.mnt_fsname="fsname",  .mnt_dir="/",},
-    {.mnt_fsname="/fsname", .mnt_dir="/root"},
+    {.mnt_fsname="/fsname", .mnt_dir="/."},
     {.mnt_fsname="/fsname", .mnt_dir="/"},
+    {.mnt_fsname="virtfs",  .mnt_dir="/NONROOT"},
+    {.mnt_fsname="virtfs",  .mnt_dir="/NONROOT"},
   };
 
-  if (!getenv ("CU_TEST_DUPE_INVALID") && done == 1)
+  if (done == 1)
+    {
+      nonroot_fs = getenv ("CU_NONROOT_FS");
+      if (!nonroot_fs || !*nonroot_fs)
+        nonroot_fs = "/"; /* merge into / entries.  */
+    }
+
+  if (done == 1 && !getenv ("CU_TEST_DUPE_INVALID"))
     done++;  /* skip the first entry.  */
 
-  while (done++ <= 4)
+  while (done++ <= 6)
     {
       mntents[done-2].mnt_type = "-";
+      if (strcmp (mntents[done-2].mnt_dir, "/NONROOT") == 0)
+        mntents[done-2].mnt_dir = nonroot_fs;
       return &mntents[done-2];
     }
+
   return NULL;
 }
 EOF
@@ -69,22 +88,22 @@ test -f x || skip_ "internal test failure: maybe LD_PRELOAD doesn't work?"
 
 # The fake mtab file should only contain entries
 # having the same device number; thus the output should
-# consist of a header and one entry.
+# consist of a header and unique entries.
 LD_PRELOAD=./k.so df >out || fail=1
-test $(wc -l <out) -eq 2 || { fail=1; cat out; }
+test $(wc -l <out) -eq $(expr 1 + $unique_entries) || { fail=1; cat out; }
 
 # Ensure we fail when unable to stat invalid entries
 LD_PRELOAD=./k.so CU_TEST_DUPE_INVALID=1 df >out && fail=1
-test $(wc -l <out) -eq 2 || { fail=1; cat out; }
+test $(wc -l <out) -eq $(expr 1 + $unique_entries) || { fail=1; cat out; }
 
 # df should also prefer "/fsname" over "fsname"
 test $(grep -c '/fsname' <out) -eq 1 || { fail=1; cat out; }
-# ... and "/fsname" with '/' as Mounted on over '/root'
-test $(grep -c '/root' <out) -eq 0 || { fail=1; cat out; }
+# ... and "/fsname" with '/' as Mounted on over '/.'
+test $(grep -cF '/.' <out) -eq 0 || { fail=1; cat out; }
 
 # Ensure that filtering duplicates does not affect -a processing.
 LD_PRELOAD=./k.so df -a >out || fail=1
-test $(wc -l <out) -eq 4 || { fail=1; cat out; }
+test $(wc -l <out) -eq 6 || { fail=1; cat out; }
 
 # Ensure that filtering duplicates does not affect
 # argument processing (now without the fake getmntent()).
-- 
1.7.7.6