If par2 generate is interrupted, it may leave empty *.par2 files, and
not just the top-level PATH.par2 index. To avoid that possiblity, run
par2 in a temporary directory and then move the recovery files to the
right place if par2 succeeds.
fsck currently checks whether the top-level index is empty, but
doesn't check any of the other vol files.
Thanks to Johannes Berg for reporting the problem.
Signed-off-by: Rob Browning <
r...@defaultvalue.org>
Tested-by: Rob Browning <
r...@defaultvalue.org>
---
lib/bup/cmd/fsck.py | 39 ++++++++++++++++++++++++++++++---------
note/
0.33.x.md | 6 ++++++
2 files changed, 36 insertions(+), 9 deletions(-)
diff --git a/lib/bup/cmd/fsck.py b/lib/bup/cmd/fsck.py
index 16a2f2178..f0aac3583 100644
--- a/lib/bup/cmd/fsck.py
+++ b/lib/bup/cmd/fsck.py
@@ -1,14 +1,14 @@
-from __future__ import absolute_import, print_function
from shutil import rmtree
from subprocess import PIPE
from tempfile import mkdtemp
from binascii import hexlify
+from os.path import join
import glob, os, subprocess, sys
from bup import options, git
from bup.compat import argv_bytes
-from bup.helpers import Sha1, chunkyreader, istty2, log, progress
+from bup.helpers import Sha1, chunkyreader, istty2, log, progress, temp_dir
from
bup.io import byte_stream
@@ -20,14 +20,14 @@ def debug(s):
if opt.verbose > 1:
log(s)
-def run(argv):
+def run(argv, *, cwd=None):
# at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
# doesn't actually work, because subprocess closes fd #2 right before
# execing for some reason. So we work around it by duplicating the fd
# first.
fd = os.dup(2) # copy stderr
try:
- p = subprocess.Popen(argv, stdout=fd, close_fds=False)
+ p = subprocess.Popen(argv, stdout=fd, close_fds=False, cwd=cwd)
return p.wait()
finally:
os.close(fd)
@@ -70,7 +70,7 @@ def is_par2_parallel():
_par2_parallel = None
-def par2(action, args, verb_floor=0):
+def par2(action, args, verb_floor=0, cwd=None):
global _par2_parallel
if _par2_parallel is None:
_par2_parallel = is_par2_parallel()
@@ -82,12 +82,33 @@ def par2(action, args, verb_floor=0):
if _par2_parallel:
cmd.append(b'-t1')
cmd.extend(args)
- return run(cmd)
+ return run(cmd, cwd=cwd)
def par2_generate(base):
- return par2(b'create',
- [b'-n1', b'-c200', b'--', base, base + b'.pack', base + b'.idx'],
- verb_floor=2)
+ parent, name = os.path.split(base)
+ # Work in a temp_dir because par2 was observed creating empty
+ # files when interrupted by C-c.
+ # cf.
https://github.com/Parchive/par2cmdline/issues/84
+ with temp_dir(dir=parent, prefix=(name + b'-bup-tmp-')) as tmpdir:
+ idx_name = name + b'.idx'
+ pack_name = name + b'.pack'
+ os.symlink(join(b'../', idx_name), join(tmpdir, idx_name))
+ os.symlink(join(b'../', pack_name), join(tmpdir, pack_name))
+ rc = par2(b'create',
+ [b'-n1', b'-c200', b'--', name, pack_name, idx_name],
+ verb_floor=2, cwd=tmpdir)
+ if rc == 0:
+ p2_idx = name + b'.par2'
+ for tmp in os.listdir(tmpdir):
+ if tmp in (p2_idx, idx_name, pack_name):
+ continue
+ os.rename(join(tmpdir, tmp), join(parent, tmp))
+ # Let this indicate success
+ os.rename(join(tmpdir, p2_idx), join(parent, p2_idx))
+ expected = frozenset((idx_name, pack_name))
+ remaining = frozenset(os.listdir(tmpdir))
+ assert expected == remaining
+ return rc
def par2_verify(base):
return par2(b'verify', [b'--', base], verb_floor=3)
diff --git a/note/
0.33.x.md b/note/
0.33.x.md
index 32d739697..1fbd153fa 100644
--- a/note/
0.33.x.md
+++ b/note/
0.33.x.md
@@ -4,6 +4,12 @@ Notable changes in main since 0.33.3 (incomplete)
May require attention
---------------------
+* The `par2` command (invoked by `bup fsck -g`) may generate empty
+ recovery files if interrupted (say via C-c). To mitigate this, bup
+ now runs `par2` in a temporary directory, and only moves the
+ recovery files into place if the generation succeeds. See also
+
https://github.com/Parchive/par2cmdline/issues/84
+
* Previously, any `bup on REMOTE ...` commands that attempted to read
from standard input (for example `bup on HOST split < something` or
`bup on HOST split --git-ids ...`) would read nothing instead of the
--
2.43.0