Bug#609049: [coreutils] Re: Bug#609049: du: process_file: Assertion `level == prev_level - 1' failed
Jim Meyering wrote: ... Here's the incremental I'm testing with: diff --git a/tests/du/move-dir-while-traversing b/tests/du/move-dir-while-traversing index e42bc93..fe1615c 100755 Thanks again for the quick feedback. I squashed in the changes above and pushed the result. I also updated to the latest gnulib in a separate commit, since it passed the tests I ran. -- To UNSUBSCRIBE, email to debian-bugs-dist-requ...@lists.debian.org with a subject of unsubscribe. Trouble? Contact listmas...@lists.debian.org
Bug#609049: [coreutils] Re: Bug#609049: du: process_file: Assertion `level == prev_level - 1' failed
Jim Meyering wrote: ... While I do have a reproducer that will become a test suite addition (coming soon), it relies on python (a first) and the python-inotify package, so I'll have to be careful to skip the test when those prerequisites aren't installed. Also, there's an inherent race condition, so I'll have to find the right compromise between absolute test-robustness (too expensive in time and inodes) and reasonableness. Here's a complete patch, including the test. I would have preferred to avoid the one-second sleep. Maybe someone here can see a better way. From 9bf47055f64efb56d022feca01ad901e85e21bc1 Mon Sep 17 00:00:00 2001 From: Jim Meyering meyer...@redhat.com Date: Sat, 8 Jan 2011 17:44:55 +0100 Subject: [PATCH] du: don't abort when a subdir is renamed during traversal * NEWS (Bug fixes): Mention it. * src/du.c (prev_level): Move declaration up to file-scope global. (du_files): Reset prev_level to 0 upon abnormal fts_read termination. Reported by Johathan Nieder in http://bugs.debian.org/609049 Also, improve a diagnostic. * tests/du/move-dir-while-traversing: Test for the above. * tests/Makefile.am (TESTS): Add it. --- NEWS |8 +++ src/du.c | 15 +-- tests/Makefile.am |1 + tests/du/move-dir-while-traversing | 85 4 files changed, 105 insertions(+), 4 deletions(-) create mode 100755 tests/du/move-dir-while-traversing diff --git a/NEWS b/NEWS index 2a71ca6..5a70243 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,14 @@ GNU coreutils NEWS-*- outline -*- * Noteworthy changes in release ?.? (-??-??) [?] +** Bug fixes + + du would abort with a failed assertion when two conditions are met: + part of the hierarchy being traversed is moved to a higher level in the + directory tree, and there is at least one more command line directory + argument following the one containing the moved sub-tree. + [bug introduced in coreutils-5.1.0] + * Noteworthy changes in release 8.9 (2011-01-04) [stable] diff --git a/src/du.c b/src/du.c index 77deb0c..671cac7 100644 --- a/src/du.c +++ b/src/du.c @@ -63,8 +63,11 @@ extern bool fts_debug; /* A set of dev/ino pairs. */ static struct di_set *di_set; -/* Define a class for collecting directory information. */ +/* Keep track of the preceding level (depth in hierarchy) + from one call of process_file to the next. */ +static size_t prev_level; +/* Define a class for collecting directory information. */ struct duinfo { /* Size of files in directory. */ @@ -399,7 +402,6 @@ process_file (FTS *fts, FTSENT *ent) struct duinfo dui; struct duinfo dui_to_print; size_t level; - static size_t prev_level; static size_t n_alloc; /* First element of the structure contains: The sum of the st_size values of all entries in the single directory @@ -582,10 +584,15 @@ du_files (char **files, int bit_flags) { if (errno != 0) { - /* FIXME: try to give a better message */ - error (0, errno, _(fts_read failed)); + error (0, errno, _(fts_read failed: %s), + quotearg_colon (fts-fts_path)); ok = false; } + + /* When exiting this loop early, be careful to reset the + global, prev_level, used in process_file. Otherwise, its + (level == prev_level - 1) assertion could fail. */ + prev_level = 0; break; } FTS_CROSS_CHECK (fts); diff --git a/tests/Makefile.am b/tests/Makefile.am index 06d81f0..a5dbd3e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -370,6 +370,7 @@ TESTS = \ du/long-from-unreadable \ du/long-sloop\ du/max-depth \ + du/move-dir-while-traversing \ du/no-deref \ du/no-x \ du/one-file-system \ diff --git a/tests/du/move-dir-while-traversing b/tests/du/move-dir-while-traversing new file mode 100755 index 000..e42bc93 --- /dev/null +++ b/tests/du/move-dir-while-traversing @@ -0,0 +1,85 @@ +#!/bin/sh +# Trigger a failed assertion in coreutils-8.9 and earlier. + +# Copyright (C) 2011 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
Bug#609049: [coreutils] Re: Bug#609049: du: process_file: Assertion `level == prev_level - 1' failed
On 09/01/11 22:05, Jim Meyering wrote: diff --git a/tests/du/move-dir-while-traversing b/tests/du/move-dir-while-traversing +# We use a python-inotify script, so... +python -m pyinotify -h /dev/null \ + || skip_ 'python-inotify package not installed' A small point is that error is a bit fedora specific. The package is python-pyinotify on debian/ubuntu for example. + +# 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 +from pyinotify import * I generally don't include everything from a module, to keep the namespace clean. I'd do instead: 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(ProcessEvent): + +def process_IN_OPEN(self, event): +os.rename(dir, dest) +sys.exit(0) + +def process_default(self, event): +pass + +wm = WatchManager() +notifier = Notifier(wm) +wm.watch_transient_file(dir, IN_OPEN, ProcessDir) +print 'started' The above print is buffered by default. As we're using it for synchronization I'd sys.stdout.write('started\n') sys.stdout.flush() +notifier.loop() +EOF +chmod a+x inotify-watch-for-dir-access.py + +long=d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z +t=T/U +mkdir -p $t/3/a/b/c/$long d2 || framework_failure +timeout 6 ./inotify-watch-for-dir-access.py $t/3/a/b start-msg + +# Wait for the watcher to start... +nonempty() { test -s start-msg return 0; sleep $1; } +retry_delay_ nonempty .1 5 I think the above may iterate only once? How about: nonempty() { test -s start-msg || { sleep $1; return 1; } } retry_delay_ nonempty .1 5 || framework_failure + +# The above delay is insufficient in ~50% of my trials. +# Sometimes, when under heavy load, a parallel make check would +# fail this test when sleeping just 0.1 seconds here. +sleep 1 Hopefully this extra sleep is not required now Nice fix BTW! cheers, Pádraig. -- To UNSUBSCRIBE, email to debian-bugs-dist-requ...@lists.debian.org with a subject of unsubscribe. Trouble? Contact listmas...@lists.debian.org
Bug#609049: [coreutils] Re: Bug#609049: du: process_file: Assertion `level == prev_level - 1' failed
Pádraig Brady wrote: On 09/01/11 22:05, Jim Meyering wrote: diff --git a/tests/du/move-dir-while-traversing b/tests/du/move-dir-while-traversing +# We use a python-inotify script, so... +python -m pyinotify -h /dev/null \ + || skip_ 'python-inotify package not installed' A small point is that error is a bit fedora specific. The package is python-pyinotify on debian/ubuntu for example. Good point. I've dropped the -: || skip_ 'python inotify package not installed' +from pyinotify import * I generally don't include everything from a module, to keep the namespace clean. I'd do instead: import pyinotify as pn import os,sys ... I prefer that, too. +dir = sys.argv[1] ... +print 'started' The above print is buffered by default. As we're using it for synchronization I'd sys.stdout.write('started\n') sys.stdout.flush() Good catch! ... +nonempty() { test -s start-msg return 0; sleep $1; } +retry_delay_ nonempty .1 5 I think the above may iterate only once? How about: nonempty() { test -s start-msg || { sleep $1; return 1; } } retry_delay_ nonempty .1 5 || framework_failure Yes, that fixes another bug. +# The above delay is insufficient in ~50% of my trials. +# Sometimes, when under heavy load, a parallel make check would +# fail this test when sleeping just 0.1 seconds here. +sleep 1 Hopefully this extra sleep is not required now Bingo. It does, so far. Thanks for the advice and fixes. Here's the incremental I'm testing with: diff --git a/tests/du/move-dir-while-traversing b/tests/du/move-dir-while-traversing index e42bc93..fe1615c 100755 --- a/tests/du/move-dir-while-traversing +++ b/tests/du/move-dir-while-traversing @@ -21,7 +21,7 @@ print_ver_ du # We use a python-inotify script, so... python -m pyinotify -h /dev/null \ - || skip_ 'python-inotify package not installed' + || 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 @@ -33,12 +33,14 @@ python -m pyinotify -h /dev/null \ cat 'EOF' inotify-watch-for-dir-access.py #!/usr/bin/env python -from pyinotify import * +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(ProcessEvent): +class ProcessDir(pn.ProcessEvent): def process_IN_OPEN(self, event): os.rename(dir, dest) @@ -47,10 +49,11 @@ class ProcessDir(ProcessEvent): def process_default(self, event): pass -wm = WatchManager() -notifier = Notifier(wm) -wm.watch_transient_file(dir, IN_OPEN, ProcessDir) -print 'started' +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 @@ -61,14 +64,9 @@ mkdir -p $t/3/a/b/c/$long d2 || framework_failure timeout 6 ./inotify-watch-for-dir-access.py $t/3/a/b start-msg # Wait for the watcher to start... -nonempty() { test -s start-msg return 0; sleep $1; } +nonempty() { test -s start-msg || { sleep $1; return 1; }; } retry_delay_ nonempty .1 5 -# The above delay is insufficient in ~50% of my trials. -# Sometimes, when under heavy load, a parallel make check would -# fail this test when sleeping just 0.1 seconds here. -sleep 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. -- To UNSUBSCRIBE, email to debian-bugs-dist-requ...@lists.debian.org with a subject of unsubscribe. Trouble? Contact listmas...@lists.debian.org