mirror of https://github.com/borgbackup/borg.git
Merge pull request #2528 from ThomasWaldmann/follow-symlinks
document follow_symlinks requirements, check libc, fixes #2507
This commit is contained in:
commit
2dcbe02e5a
|
@ -54,6 +54,21 @@ manages data.
|
||||||
|
|
||||||
.. _locking: https://en.wikipedia.org/wiki/File_locking#Lock_files
|
.. _locking: https://en.wikipedia.org/wiki/File_locking#Lock_files
|
||||||
|
|
||||||
|
(G)LIBC requirements
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Borg uses some filesytem functions from Python's `os` standard library module
|
||||||
|
with `follow_symlinks=False`. These are implemented since quite a while with
|
||||||
|
the non-symlink-following (g)libc functions like e.g. `lstat` or `lutimes`
|
||||||
|
(not: `stat` or `utimes`).
|
||||||
|
|
||||||
|
Some stoneage systems (like RHEL/CentOS 5) and also Python interpreter binaries
|
||||||
|
compiled to be able to run on such systems (like Python installed via Anaconda)
|
||||||
|
might miss these functions and Borg won't be able to work correctly.
|
||||||
|
This issue will be detected early and Borg will abort with a fatal error.
|
||||||
|
|
||||||
|
For the Borg binaries, there are additional (g)libc requirements, see below.
|
||||||
|
|
||||||
.. _distribution-package:
|
.. _distribution-package:
|
||||||
|
|
||||||
Distribution Package
|
Distribution Package
|
||||||
|
|
|
@ -569,7 +569,7 @@ Utilization of max. archive size: {csize_max:.0%}
|
||||||
path = os.path.join(dest, item.path)
|
path = os.path.join(dest, item.path)
|
||||||
# Attempt to remove existing files, ignore errors on failure
|
# Attempt to remove existing files, ignore errors on failure
|
||||||
try:
|
try:
|
||||||
st = os.lstat(path)
|
st = os.stat(path, follow_symlinks=False)
|
||||||
if stat.S_ISDIR(st.st_mode):
|
if stat.S_ISDIR(st.st_mode):
|
||||||
os.rmdir(path)
|
os.rmdir(path)
|
||||||
else:
|
else:
|
||||||
|
@ -674,7 +674,7 @@ Utilization of max. archive size: {csize_max:.0%}
|
||||||
if fd:
|
if fd:
|
||||||
os.fchown(fd, uid, gid)
|
os.fchown(fd, uid, gid)
|
||||||
else:
|
else:
|
||||||
os.lchown(path, uid, gid)
|
os.chown(path, uid, gid, follow_symlinks=False)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
if fd:
|
if fd:
|
||||||
|
|
|
@ -54,7 +54,7 @@ from .helpers import get_cache_dir
|
||||||
from .helpers import Manifest
|
from .helpers import Manifest
|
||||||
from .helpers import hardlinkable
|
from .helpers import hardlinkable
|
||||||
from .helpers import StableDict
|
from .helpers import StableDict
|
||||||
from .helpers import check_extension_modules
|
from .helpers import check_python, check_extension_modules
|
||||||
from .helpers import dir_is_tagged, is_slow_msgpack, yes, sysinfo
|
from .helpers import dir_is_tagged, is_slow_msgpack, yes, sysinfo
|
||||||
from .helpers import log_multi
|
from .helpers import log_multi
|
||||||
from .helpers import signal_handler, raising_signal_handler, SigHup, SigTerm
|
from .helpers import signal_handler, raising_signal_handler, SigHup, SigTerm
|
||||||
|
@ -436,7 +436,7 @@ class Archiver:
|
||||||
continue
|
continue
|
||||||
path = os.path.normpath(path)
|
path = os.path.normpath(path)
|
||||||
try:
|
try:
|
||||||
st = os.lstat(path)
|
st = os.stat(path, follow_symlinks=False)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.print_warning('%s: %s', path, e)
|
self.print_warning('%s: %s', path, e)
|
||||||
continue
|
continue
|
||||||
|
@ -498,7 +498,7 @@ class Archiver:
|
||||||
"""
|
"""
|
||||||
if st is None:
|
if st is None:
|
||||||
with backup_io('stat'):
|
with backup_io('stat'):
|
||||||
st = os.lstat(path)
|
st = os.stat(path, follow_symlinks=False)
|
||||||
|
|
||||||
recurse_excluded_dir = False
|
recurse_excluded_dir = False
|
||||||
if not matcher.match(path):
|
if not matcher.match(path):
|
||||||
|
@ -3829,6 +3829,7 @@ class Archiver:
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def prerun_checks(self, logger):
|
def prerun_checks(self, logger):
|
||||||
|
check_python()
|
||||||
check_extension_modules()
|
check_extension_modules()
|
||||||
selftest(logger)
|
selftest(logger)
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,16 @@ class InvalidPlaceholder(PlaceholderError):
|
||||||
"""Invalid placeholder "{}" in string: {}"""
|
"""Invalid placeholder "{}" in string: {}"""
|
||||||
|
|
||||||
|
|
||||||
|
class PythonLibcTooOld(Error):
|
||||||
|
"""FATAL: this Python was compiled for a too old (g)libc and misses required functionality."""
|
||||||
|
|
||||||
|
|
||||||
|
def check_python():
|
||||||
|
required_funcs = {os.stat, os.utime, os.chown}
|
||||||
|
if not os.supports_follow_symlinks.issuperset(required_funcs):
|
||||||
|
raise PythonLibcTooOld
|
||||||
|
|
||||||
|
|
||||||
def check_extension_modules():
|
def check_extension_modules():
|
||||||
from . import platform, compress, item
|
from . import platform, compress, item
|
||||||
if hashindex.API_VERSION != '1.1_01':
|
if hashindex.API_VERSION != '1.1_01':
|
||||||
|
@ -1754,7 +1764,7 @@ class GenericDirEntry:
|
||||||
|
|
||||||
def stat(self, follow_symlinks=True):
|
def stat(self, follow_symlinks=True):
|
||||||
assert not follow_symlinks
|
assert not follow_symlinks
|
||||||
return os.lstat(self.path)
|
return os.stat(self.path, follow_symlinks=follow_symlinks)
|
||||||
|
|
||||||
def _check_type(self, type):
|
def _check_type(self, type):
|
||||||
st = self.stat(False)
|
st = self.stat(False)
|
||||||
|
|
|
@ -30,13 +30,11 @@ except ImportError:
|
||||||
raises = None
|
raises = None
|
||||||
|
|
||||||
has_lchflags = hasattr(os, 'lchflags') or sys.platform.startswith('linux')
|
has_lchflags = hasattr(os, 'lchflags') or sys.platform.startswith('linux')
|
||||||
no_lchlfags_because = '' if has_lchflags else '(not supported on this platform)'
|
|
||||||
try:
|
try:
|
||||||
with tempfile.NamedTemporaryFile() as file:
|
with tempfile.NamedTemporaryFile() as file:
|
||||||
platform.set_flags(file.name, stat.UF_NODUMP)
|
platform.set_flags(file.name, stat.UF_NODUMP)
|
||||||
except OSError:
|
except OSError:
|
||||||
has_lchflags = False
|
has_lchflags = False
|
||||||
no_lchlfags_because = '(the file system at %s does not support flags)' % tempfile.gettempdir()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import llfuse
|
import llfuse
|
||||||
|
@ -67,7 +65,7 @@ def are_symlinks_supported():
|
||||||
with unopened_tempfile() as filepath:
|
with unopened_tempfile() as filepath:
|
||||||
try:
|
try:
|
||||||
os.symlink('somewhere', filepath)
|
os.symlink('somewhere', filepath)
|
||||||
if os.lstat(filepath) and os.readlink(filepath) == 'somewhere':
|
if os.stat(filepath, follow_symlinks=False) and os.readlink(filepath) == 'somewhere':
|
||||||
return True
|
return True
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
@ -109,7 +107,7 @@ def is_utime_fully_supported():
|
||||||
open(filepath, 'w').close()
|
open(filepath, 'w').close()
|
||||||
try:
|
try:
|
||||||
os.utime(filepath, (1000, 2000), follow_symlinks=False)
|
os.utime(filepath, (1000, 2000), follow_symlinks=False)
|
||||||
new_stats = os.lstat(filepath)
|
new_stats = os.stat(filepath, follow_symlinks=False)
|
||||||
if new_stats.st_atime == 1000 and new_stats.st_mtime == 2000:
|
if new_stats.st_atime == 1000 and new_stats.st_mtime == 2000:
|
||||||
return True
|
return True
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
|
@ -158,8 +156,8 @@ class BaseTestCase(unittest.TestCase):
|
||||||
for filename in diff.common:
|
for filename in diff.common:
|
||||||
path1 = os.path.join(diff.left, filename)
|
path1 = os.path.join(diff.left, filename)
|
||||||
path2 = os.path.join(diff.right, filename)
|
path2 = os.path.join(diff.right, filename)
|
||||||
s1 = os.lstat(path1)
|
s1 = os.stat(path1, follow_symlinks=False)
|
||||||
s2 = os.lstat(path2)
|
s2 = os.stat(path2, follow_symlinks=False)
|
||||||
# Assume path2 is on FUSE if st_dev is different
|
# Assume path2 is on FUSE if st_dev is different
|
||||||
fuse = s1.st_dev != s2.st_dev
|
fuse = s1.st_dev != s2.st_dev
|
||||||
attrs = ['st_uid', 'st_gid', 'st_rdev']
|
attrs = ['st_uid', 'st_gid', 'st_rdev']
|
||||||
|
|
Loading…
Reference in New Issue