When the control pipe indicates it's time to quit, finish
filtering/forwarding any existing source output first. The truncation
was easy to reproduce with an interactive "bup ls BRANCH" listing a
lot of saves.
This should fix:
39aced66ad1a6438c4ea9463621a2fa18120eb74
main: don't close filter pipes from another thread
Thanks to Christian Wolf for reporting the problem.
Signed-off-by: Rob Browning <
r...@defaultvalue.org>
Tested-by: Rob Browning <
r...@defaultvalue.org>
---
dev/with-tty | 14 ++++++++++++++
lib/bup/main.py | 29 ++++++++++++++++++++++-------
note/
0.33.x.md | 5 +++++
test/ext/test-ls | 16 ++++++++++++++++
4 files changed, 57 insertions(+), 7 deletions(-)
create mode 100755 dev/with-tty
diff --git a/dev/with-tty b/dev/with-tty
new file mode 100755
index 00000000..99be7857
--- /dev/null
+++ b/dev/with-tty
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+set -ueo pipefail
+
+usage() { echo 'Usage: with-tty command [arg ...]'; }
+misuse() { usage 1>&2; exit 2; }
+
+if script -qec true /dev/null; then
+ # linux flavor
+ script -qec "$(printf ' %q' "$@")" /dev/null
+else
+ # bsd flavor
+ script -qe /dev/null "$@"
+fi
diff --git a/lib/bup/main.py b/lib/bup/main.py
index f77de5ac..6801de3b 100755
--- a/lib/bup/main.py
+++ b/lib/bup/main.py
@@ -244,12 +244,31 @@ def filter_output(srcs, dests, control):
read_fds = [control, *srcs]
dest_for = dict(zip(srcs, dests))
pending = {}
+ finish = False
try:
while srcs:
- ready_fds, _, _ = select.select(read_fds, [], [])
+ # When finish is true, transfer any pending data and exit.
+ if not finish:
+ readable_fds, _, _ = select.select(read_fds, [], [])
+ else:
+ # Only reachable with control since only it sets
+ # finish false. Forward any data available (without
+ # blocking) from each src, i.e. whatever's already
+ # pending.
+ if readable_fds == [control]:
+ break
+ readable_fds, _, _ = select.select(read_fds, [], [], 0)
+ # Drop every src for which no data was available.
+ for tried in read_fds:
+ if tried not in readable_fds:
+ read_fds.remove(tried)
+ if tried != control: srcs.remove(tried)
width = tty_width()
- for fd in ready_fds:
- if fd == control:
+ for fd in readable_fds:
+ if control is not None and fd == control:
+ buf = os.read(control, 1)
+ assert buf == b'q'
+ finish = True
continue
buf = os.read(fd, 4096)
dest = dest_for[fd]
@@ -269,10 +288,6 @@ def filter_output(srcs, dests, control):
assert len(split) == 1
if split[0]:
pending.setdefault(fd, []).extend(split)
- if control in ready_fds:
- buf = os.read(control, 1)
- assert buf == b'q'
- break
except BaseException as ex:
pending_ex = ex
# Try to finish each of the streams
diff --git a/note/
0.33.x.md b/note/
0.33.x.md
index 3963ab8d..fee5c918 100644
--- a/note/
0.33.x.md
+++ b/note/
0.33.x.md
@@ -4,6 +4,11 @@ Notable changes in 0.33.x (incomplete)
Bugs
----
+* Internal subcommands (all except `import-rdiff-backup` and
+ `import-rsnapshot`) should no longer truncate their output sometimes
+ when run interactively (e.g. `bup ls ...`) due to a problem
+ introduced in 0.33.8.
+
* When running interactively, `bup` should no longer lose exceptions
raised by the internal "output filter" thread.
diff --git a/test/ext/test-ls b/test/ext/test-ls
index 86d8c58c..174c5af6 100755
--- a/test/ext/test-ls
+++ b/test/ext/test-ls
@@ -17,6 +17,7 @@ else
fi
bup() { "$top/bup" "$@"; }
+with-tty() { "$top/dev/with-tty" "$@"; }
bup-ls() {
if test "$BUP_TEST_REMOTE_REPO"; then
@@ -294,4 +295,19 @@ WVPASSEQ "$(bup-ls -l src/latest/bad-symlink | tr -s ' ' ' ')" \
"$bad_symlink_mode $user/$group $bad_symlink_size $bad_symlink_date src/latest/bad-symlink -> not-there"
+if ! command -v script; then
+ WVSKIP 'Skipping interactive ouput truncation test (no script command)'
+else
+ WVSTART "output isn't truncated when isatty"
+ WVPASS rm -rf src
+ WVPASS mkdir src
+ echo src/some-longer-name-{1..3000} | WVPASS xargs touch
+ WVPASS bup index src
+ WVPASS bup save -n src --strip src
+ WVPASS with-tty "$top/bup" ls -l src/latest | WVPASS wc -l > ls-rows
+ WVPASSEQ 3000 $(<ls-rows)
+fi
+
+
+WVPASS cd "$top"
WVPASS rm -rf "$tmpdir"
--
2.47.2