Subject: bug#19238: Fully fix du circular warning on bind mounts

From: Boris Ranto <[email-masked]>
Date: Mon, 01 Dec 2014 09:54:38 +0100

The du circular warning can still be hit even though a file system is in
good condition. All we need to do is to get the message is to begin
traversing the file system between the bind mount source and bind mount
target directories, i.e this short script reproduces the problem:

# mkdir -p a/b/c
# mount -o bind a a/b/c
# du a/b

The problem is that in this case, the first directory that is detected
by fts as a duplicate directory is directory a/b/c/b which is not a
mount point.

The solution is to traverse the structure all the way to a/b (excluding
a/b) which is detected as the base of the cycle and look up all these
directories in the mount table.

I'm attaching the patch that fixed this problem for me.

-Boris
>From 2359875e69d7dedfe505c45beea9277561368b45 Mon Sep 17 00:00:00 2001
From: Boris Ranto <[email-masked]>
Date: Mon, 1 Dec 2014 09:24:14 +0100
Subject: [PATCH] du: handle sub-bind-mount cycles gracefully

This patch fixes the handling of sub-bind-mount cycles which are
incorrectly detected as the filesystem errors. If you bind mount the
directory 'a' to its subdirectory 'a/b/c' and then run 'du a/b' you
will get the circular dependency warning even though nothing is wrong
with the file system. This happens because the first directory that is
traversed twice in this case is not a bind mount but a child of bind
mount. The solution is to traverse all the directories in the cycle
that fts detected and check whether they are not a (bind) mount.

* src/du.c (mount_point_in_fts_cycle): New function that checks whether
any of the directories in the cycle that fts detected is a mount point.
* src/du.c (process_file): Update the function to use the new function
that looks up all the directories in the fts cycle instead of only the
last one.
* tests/du/bind-mount-dir-cycle-v2.sh: New test case that exhibits the
described behaviour.
---
 src/du.c                            | 21 +++++++++++++++++++-
 tests/du/bind-mount-dir-cycle-v2.sh | 38 +++++++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)
 create mode 100755 tests/du/bind-mount-dir-cycle-v2.sh

diff --git a/src/du.c b/src/du.c
index ba20120..a145a4c 100644
--- a/src/du.c
+++ b/src/du.c
@@ -419,6 +419,25 @@ print_size (const struct duinfo *pdui, const char *string)
   fflush (stdout);
 }
 
+/* This function checks whether any of the directories in the cycle that
+ * fts detected is a mount point. */
+
+static bool
+mount_point_in_fts_cycle(FTSENT *ent)
+{
+  FTSENT *cycle_ent = ent->fts_cycle;
+
+  while(ent && ent != cycle_ent)
+    {
+      if (di_set_lookup(di_mnt, ent->fts_statp->st_dev, ent->fts_statp->st_ino) > 0)
+        {
+          return true;
+        }
+      ent = ent->fts_parent;
+    }
+  return false;
+}
+
 /* This function is called once for every file system object that fts
    encounters.  fts does a depth-first traversal.  This function knows
    that and accumulates per-directory totals based on changes in
@@ -516,7 +535,7 @@ process_file (FTS *fts, FTSENT *ent)
         case FTS_DC:
           /* If not following symlinks and not a (bind) mount point.  */
           if (cycle_warning_required (fts, ent)
-              && ! di_set_lookup (di_mnt, sb->st_dev, sb->st_ino))
+              && ! mount_point_in_fts_cycle (ent))
             {
               emit_cycle_warning (file);
               return false;
diff --git a/tests/du/bind-mount-dir-cycle-v2.sh b/tests/du/bind-mount-dir-cycle-v2.sh
new file mode 100755
index 0000000..384d3e2
--- /dev/null
+++ b/tests/du/bind-mount-dir-cycle-v2.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Check that du can handle sub-bind-mounts cycles as well.
+
+# Copyright (C) 2014 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=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ du
+require_root_
+
+cleanup_() { umount a/b/c; }
+
+mkdir -p a/b/c || framework_failure_
+mount --bind a a/b/c \
+  || skip_ "This test requires mount with a working --bind option."
+
+echo a/b/c > exp || framework_failure_
+echo a/b >> exp || framework_failure_
+
+du a/b > out 2> err || fail=1
+sed 's/^[0-9][0-9]*\t//' out > k && mv k out
+
+compare /dev/null err || fail=1
+compare exp out || fail=1
+
+Exit $fail
-- 
1.9.3