summaryrefslogtreecommitdiffstats
path: root/tests/du/move-dir-while-traversing.sh
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xtests/du/move-dir-while-traversing.sh99
1 files changed, 99 insertions, 0 deletions
diff --git a/tests/du/move-dir-while-traversing.sh b/tests/du/move-dir-while-traversing.sh
new file mode 100755
index 0000000..37147d2
--- /dev/null
+++ b/tests/du/move-dir-while-traversing.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+# Trigger a failed assertion in coreutils-8.9 and earlier.
+
+# Copyright (C) 2011-2020 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 <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ du
+require_trap_signame_
+
+# We use a python-inotify script, so...
+python -m pyinotify -h > /dev/null \
+ || skip_ 'python inotify package not installed'
+
+# Move a directory "up" while du is processing its sub-directories.
+# While du is processing a hierarchy .../B/C/D/... this script
+# detects when du opens D/, and then moves C/ "up" one level
+# so that it is a sibling of B/.
+# Given the inherent race condition, we have to add enough "weight"
+# under D/ so that in most cases, the monitor performs the single
+# rename syscall before du finishes processing the subtree under D/.
+
+cat <<'EOF' > inotify-watch-for-dir-access.py
+#!/usr/bin/env python
+import pyinotify as pn
+import os,sys
+
+dir = sys.argv[1]
+dest_parent = os.path.dirname(os.path.dirname(dir))
+dest = os.path.join(dest_parent, os.path.basename(dir))
+
+class ProcessDir(pn.ProcessEvent):
+
+ def process_IN_OPEN(self, event):
+ os.rename(dir, dest)
+ sys.exit(0)
+
+ def process_default(self, event):
+ pass
+
+wm = pn.WatchManager()
+notifier = pn.Notifier(wm)
+wm.watch_transient_file(dir, pn.IN_OPEN, ProcessDir)
+sys.stdout.write('started\n')
+sys.stdout.flush()
+notifier.loop()
+EOF
+chmod a+x inotify-watch-for-dir-access.py
+
+t=T/U
+mkdir d2 || framework_failure_
+long=d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z
+# One iteration of this loop creates a tree with which
+# du sometimes completes its traversal before the above rename.
+# Five iterations was not enough in 2 of 7 "make -j20 check" runs on a
+# 6/12-core system. However, using "10", I saw no failure in 20 trials.
+# Using 10 iterations was not enough, either.
+# Using 50, I saw no failure in 200 trials.
+for i in $(seq 50); do
+ mkdir -p $t/3/a/b/c/$i/$long || framework_failure_
+done
+
+# Terminate any background cp process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Prohibit suspension, which could otherwise cause a timeout-induced FP failure.
+trap '' TSTP
+
+timeout 6 ./inotify-watch-for-dir-access.py $t/3/a/b > start-msg & pid=$!
+
+# Wait for the watcher to start...
+nonempty() { sleep $1; test -s start-msg; }
+retry_delay_ nonempty .1 5 || fail=1
+
+# The above watches for an IN_OPEN event on $t/3/a/b,
+# and when it triggers, moves the parent, $t/3/a, up one level
+# so it's directly under $t.
+
+# Before coreutils-8.10, du would abort.
+returns_ 1 du -a $t d2 2> err || fail=1
+
+# check for the new diagnostic
+printf "du: fts_read failed: $t/3/a/b: No such file or directory\n" > exp \
+ || fail=1
+compare exp err || fail=1
+
+Exit $fail