#include "exclude.h"
#include "fprintftime.h"
#include "human.h"
+#include "mountlist.h"
#include "quote.h"
#include "quotearg.h"
#include "stat-size.h"
# define FTS_CROSS_CHECK(Fts)
#endif
-/* A set of dev/ino pairs. */
-static struct di_set *di_set;
+/* A set of dev/ino pairs to help identify files and directories
+ whose sizes have already been counted. */
+static struct di_set *di_files;
+
+/* A set containing a dev/ino pair for each local mount point directory. */
+static struct di_set *di_mnt;
/* Keep track of the preceding "level" (depth in hierarchy)
from one call of process_file to the next. */
exit (status);
}
-/* Try to insert the INO/DEV pair into the global table, HTAB.
+/* Try to insert the INO/DEV pair into DI_SET.
Return true if the pair is successfully inserted,
- false if the pair is already in the table. */
+ false if the pair was already there. */
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)
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_files, 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
case FTS_DC:
if (cycle_warning_required (fts, ent))
{
- emit_cycle_warning (file);
+ /* If this is a mount point, then diagnose it and avoid
+ the cycle. */
+ if (di_set_lookup (di_mnt, sb->st_dev, sb->st_ino))
+ error (0, 0, _("mount point %s already traversed"),
+ quote (file));
+ else
+ emit_cycle_warning (file);
return false;
}
return true;
return ok;
}
+/* Fill the di_mnt set with local mount point dev/ino pairs. */
+
+static void
+fill_mount_table (void)
+{
+ struct mount_entry *mnt_ent = read_file_system_list (false);
+ while (mnt_ent)
+ {
+ struct mount_entry *mnt_free;
+ if (!mnt_ent->me_remote && !mnt_ent->me_dummy)
+ {
+ struct stat buf;
+ if (!stat (mnt_ent->me_mountdir, &buf))
+ hash_ins (di_mnt, buf.st_ino, buf.st_dev);
+ else
+ {
+ /* Ignore stat failure. False positives are too common.
+ E.g., "Permission denied" on /run/user/<name>/gvfs. */
+ }
+ }
+
+ 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);
+ }
+}
+
int
main (int argc, char **argv)
{
xalloc_die ();
/* Initialize the set of dev,inode pairs. */
- di_set = di_set_alloc ();
- if (!di_set)
+
+ di_mnt = di_set_alloc ();
+ if (!di_mnt)
+ xalloc_die ();
+
+ fill_mount_table ();
+
+ di_files = di_set_alloc ();
+ if (!di_files)
xalloc_die ();
/* If not hashing everything, process_file won't find cycles on its
argv_iter_done:
argv_iter_free (ai);
- di_set_free (di_set);
+ di_set_free (di_files);
+ di_set_free (di_mnt);
if (files_from && (ferror (stdin) || fclose (stdin) != 0) && ok)
error (EXIT_FAILURE, 0, _("error reading %s"), quote (files_from));
--- /dev/null
+#!/bin/sh
+# Exercise du's new ability to handle bind-mount-induced dir cycles.
+
+# Copyright (C) 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 || framework_failure_
+mount --bind a a/b \
+ || skip_ "This test requires mount with a working --bind option."
+
+echo a > exp || framework_failure_
+echo "du: mount point 'a/b' already traversed" > exp-err || framework_failure_
+
+du a > out 2> err && fail=1
+sed 's/^[0-9][0-9]* //' out > k && mv k out
+
+compare exp-err err || fail=1
+compare exp out || fail=1
+
+Exit $fail