If the platform/filesystem doesn't appear to support hard links, fall
back to copy2(). See also:
commit daa6bf3eeabf1f50630b17b0cbbbdcbbef59c90e
fsck: switch from symlinks to hard links for par2 sandbox
Thanks to Daniel Distler for reporting the issue and Greg Troxel,
and Aaron M. Ucko for help with the errno values.
Signed-off-by: Rob Browning <
r...@defaultvalue.org>
Tested-by: Rob Browning <
r...@defaultvalue.org>
(adapted from commit 337173a9f20fd9aa94c0da348e43d671fecc3d79)
---
Proposed for 0.33.x.
lib/bup/cmd/fsck.py | 27 ++++++++++++++++++++++++---
note/
0.33.x.md | 15 +++++++++++++++
2 files changed, 39 insertions(+), 3 deletions(-)
create mode 100644 note/
0.33.x.md
diff --git a/lib/bup/cmd/fsck.py b/lib/bup/cmd/fsck.py
index abdcbe2d..9d5cd937 100644
--- a/lib/bup/cmd/fsck.py
+++ b/lib/bup/cmd/fsck.py
@@ -4,7 +4,8 @@ from subprocess import PIPE
from tempfile import mkdtemp
from binascii import hexlify
from os.path import join
-import glob, os, subprocess, sys
+from shutil import copy2
+import errno, glob, os, subprocess, sys
from bup import options, git
from bup.compat import argv_bytes
@@ -86,6 +87,16 @@ def par2(action, args, verb_floor=0, cwd=None):
cmd.extend(args)
return run(cmd, cwd=cwd)
+_unable_to_link = set()
+_unable_to_link.add(getattr(errno, 'EMLINK', None))
+_unable_to_link.add(getattr(errno, 'EOPNOTSUPP', None)) # freebsd
+_unable_to_link.add(getattr(errno, 'EPERM', None)) # linux
+_unable_to_link.add(getattr(errno, 'ERANGE', None)) # cryfs
+_unable_to_link.add(getattr(errno, 'EREMOTEIO', None)) # kafs (cross-directory)
+_unable_to_link.add(getattr(errno, 'EXDEV', None)) # openafs (cross-directory)
+_unable_to_link.discard(None)
+_unable_to_link = frozenset(_unable_to_link)
+
def par2_generate(stem):
parent, base = os.path.split(stem)
# Work in a temp_dir because par2 was observed creating empty
@@ -94,8 +105,18 @@ def par2_generate(stem):
with temp_dir(dir=parent, prefix=(base + b'-bup-tmp-')) as tmpdir:
idx = base + b'.idx'
pack = base + b'.pack'
- os.link(join(tmpdir, b'..', idx), join(tmpdir, idx))
- os.link(join(tmpdir, b'..', pack), join(tmpdir, pack))
+ for entry in idx, pack:
+ entry_src = join(tmpdir, b'..', entry)
+ entry_dst = join(tmpdir, entry)
+ copy_instead = False
+ try:
+ os.link(entry_src, entry_dst)
+ except OSError as ex:
+ if not ex.errno in _unable_to_link:
+ raise
+ copy_instead = True
+ if copy_instead:
+ copy2(entry_src, entry_dst)
rc = par2(b'create', [b'-n1', b'-c200', b'--', base, pack, idx],
verb_floor=2, cwd=tmpdir)
if rc == 0:
diff --git a/note/
0.33.x.md b/note/
0.33.x.md
new file mode 100644
index 00000000..022b1a67
--- /dev/null
+++ b/note/
0.33.x.md
@@ -0,0 +1,15 @@
+Notable changes in 0.33.x (incomplete)
+======================================
+
+Bugs
+----
+
+* As noted in 0.33.8, `bup fsck` switched from symlinks to hardlinks
+ to accommodate an incompatible change in `par2` 1.0's behavior. To
+ allow the use of filesystems without hardlinks, `bup` now copies the
+ input files if hardlinking fails.
+
+Thanks to (at least)
+====================
+
+...
--
2.47.3