Oops, I seem to have forgotten to add the test
to the last patch I sent, sorry about the trouble, here
it is again with the test added.
Cheers,
Ondrej.
>From d8eab7abdf19b668065ff8be3700c9966eefdc39 Mon Sep 17 00:00:00 2001
From: Ondrej Oprala <[email protected]>
Date: Thu, 9 Aug 2012 17:34:09 +0200
Subject: [PATCH] du: Fix an issue with bogus warnings on bind-mounted
directories
* NEWS: Mention the fix.
* src/du.c: Include "mountlist.h".
(di_mnt): New global set.
(fill_mount_table): New function.
(hash_ins): Add HT parameter.
(process_file): Look up each dir dev/ino pair in the new set.
(main): Allocate, initialize, and free the new set.
* tests/Makefile.am: Add a new file.
* tests/du/bind-mount-dir-cycle: Add a test for the fix.
---
NEWS | 5 ++++
src/du.c | 61 ++++++++++++++++++++++++++++++++++++++++---
tests/Makefile.am | 1 +
tests/du/bind-mount-dir-cycle | 44 +++++++++++++++++++++++++++++++
4 files changed, 108 insertions(+), 3 deletions(-)
create mode 100755 tests/du/bind-mount-dir-cycle
diff --git a/NEWS b/NEWS
index 46d0a41..d2d2fe2 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,11 @@ GNU coreutils NEWS -*-
outline -*-
certain options like -a, -l, -t and -x.
[This bug was present in "the beginning".]
+** Bug fixes
+
+ du no longer emits bogus warnings when traversing bind-mounted
+ directory cycles.
+
* Noteworthy changes in release 8.18 (2012-08-12) [stable]
diff --git a/src/du.c b/src/du.c
index 7333941..bfe543e 100644
--- a/src/du.c
+++ b/src/du.c
@@ -35,6 +35,7 @@
#include "exclude.h"
#include "fprintftime.h"
#include "human.h"
+#include "mountlist.h"
#include "quote.h"
#include "quotearg.h"
#include "stat-size.h"
@@ -63,6 +64,9 @@ extern bool fts_debug;
/* A set of dev/ino pairs. */
static struct di_set *di_set;
+/* A hash table for mount points. */
+static struct di_set *di_mnt;
+
/* Keep track of the preceding "level" (depth in hierarchy)
from one call of process_file to the next. */
static size_t prev_level;
@@ -333,11 +337,11 @@ Mandatory arguments to long options are mandatory for
short options too.\n\
exit (status);
}
-/* Try to insert the INO/DEV pair into the global table, HTAB.
+/* Try to insert the INO/DEV pair into the di_set table.
Return true if the pair is successfully inserted,
false if the pair is already in the table. */
static bool
-hash_ins (ino_t ino, dev_t dev)
+hash_ins (struct di_set *di_set, ino_t ino, dev_t dev)
{
int inserted = di_set_insert (di_set, dev, ino);
if (inserted < 0)
@@ -461,7 +465,7 @@ process_file (FTS *fts, FTSENT *ent)
if (excluded
|| (! opt_count_all
&& (hash_all || (! S_ISDIR (sb->st_mode) && 1 < sb->st_nlink))
- && ! hash_ins (sb->st_ino, sb->st_dev)))
+ && ! hash_ins (di_set, sb->st_ino, sb->st_dev)))
{
/* If ignoring a directory in preorder, skip its children.
Ignore the next fts_read output too, as it's a postorder
@@ -476,6 +480,17 @@ process_file (FTS *fts, FTSENT *ent)
return true;
}
+ /*Check if dir is already in the mount table. */
+ if (S_ISDIR (sb->st_mode))
+ {
+ if (di_set_lookup (di_mnt, sb->st_dev, sb->st_ino))
+ {
+ fts_set (fts, ent, FTS_SKIP);
+ error (0, 0, _("mount point %s already traversed"), quote
(file));
+ return false;
+ }
+ }
+
switch (info)
{
case FTS_D:
@@ -623,6 +638,38 @@ du_files (char **files, int bit_flags)
return ok;
}
+/* Fill the di_mnt table with dev/ino pairs of mount points. */
+
+static void
+fill_mount_table (void)
+{
+ struct mount_entry *mnt_ent;
+ struct mount_entry *mnt_free;
+ struct stat *buf = xmalloc (sizeof *buf);
+
+ mnt_ent = read_file_system_list (false);
+ while (mnt_ent)
+ {
+ if (!mnt_ent->me_remote && !mnt_ent->me_dummy)
+ {
+ if (!stat (mnt_ent->me_mountdir, buf))
+ hash_ins (di_mnt, buf->st_ino, buf->st_dev);
+ else
+ error (0, errno, _("warning: unable to stat %s"),
+ mnt_ent->me_mountdir);
+ }
+
+ mnt_free = mnt_ent;
+ mnt_ent = mnt_ent->me_next;
+
+ free (mnt_free->me_devname);
+ free (mnt_free->me_mountdir);
+ free (mnt_free->me_type);
+ free (mnt_free);
+ }
+ free (buf);
+}
+
int
main (int argc, char **argv)
{
@@ -922,6 +969,13 @@ main (int argc, char **argv)
xalloc_die ();
/* Initialize the set of dev,inode pairs. */
+
+ di_mnt = di_set_alloc ();
+ if (!di_mnt)
+ xalloc_die ();
+
+ fill_mount_table ();
+
di_set = di_set_alloc ();
if (!di_set)
xalloc_die ();
@@ -1002,6 +1056,7 @@ main (int argc, char **argv)
argv_iter_free (ai);
di_set_free (di_set);
+ di_set_free (di_mnt);
if (files_from && (ferror (stdin) || fclose (stdin) != 0) && ok)
error (EXIT_FAILURE, 0, _("error reading %s"), quote (files_from));
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 273405f..45ab3b1 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -32,6 +32,7 @@ root_tests = \
cp/sparse-fiemap \
dd/skip-seek-past-dev \
df/problematic-chars \
+ du/bind-mount-dir-cycle \
install/install-C-root \
ls/capability \
ls/nameless-uid \
diff --git a/tests/du/bind-mount-dir-cycle b/tests/du/bind-mount-dir-cycle
new file mode 100755
index 0000000..a8a4220
--- /dev/null
+++ b/tests/du/bind-mount-dir-cycle
@@ -0,0 +1,44 @@
+#!/bin/sh
+# Demonstrate rm's new --one-file-system option.
+
+# Copyright (C) 2006-2012 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
+print_ver_ rm
+require_root_
+
+cleanup_()
+{
+ # When you take the undesirable shortcut of making /etc/mtab a link
+ # to /proc/mounts, unmounting "$other_partition_tmpdir" would fail.
+ # So, here we unmount a/b instead.
+ umount a/b
+}
+
+mkdir -p a/b
+mount --bind a a/b \
+ || skip_ "This test requires mount with a working --bind option."
+
+cat <<\EOF > exp || framework_failure_
+rm: skipping 'a/b', since it's on a different device
+EOF
+
+echo "du: mount point 'a' already traversed" > exp
+du a > /dev/null 2> out
+
+compare exp out || fail=1
+
+Exit $fail
--
1.7.11.2