From db2b385a8bea44510c215cf5026d1eb36821e076 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Tue, 26 Feb 2019 11:18:37 +0100 Subject: [PATCH] fix openat/statat issues for root directory, fixes #4405 basename('/') == '' and we can't use that for openat/statat. also, there is no parent FD for the root directory... --- src/borg/archiver.py | 10 ++++++---- src/borg/helpers/fs.py | 26 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 4fa36a92b..385056b4d 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -50,7 +50,7 @@ from .helpers import format_timedelta, format_file_size, parse_file_size, format from .helpers import safe_encode, remove_surrogates, bin_to_hex, prepare_dump_dict from .helpers import interval, prune_within, prune_split, PRUNING_PATTERNS from .helpers import timestamp -from .helpers import get_cache_dir +from .helpers import get_cache_dir, os_stat from .helpers import Manifest, AI_HUMAN_SORT_KEYS from .helpers import hardlinkable from .helpers import StableDict @@ -486,9 +486,11 @@ class Archiver: path = os.path.normpath(path) parent_dir = os.path.dirname(path) or '.' name = os.path.basename(path) + # note: for path == '/': name == '' and parent_dir == '/'. + # the empty name will trigger a fall-back to path-based processing in os_stat and os_open. with OsOpen(path=parent_dir, flags=flags_root, noatime=True, op='open_root') as parent_fd: try: - st = os.stat(name, dir_fd=parent_fd, follow_symlinks=False) + st = os_stat(path=path, parent_fd=parent_fd, name=name, follow_symlinks=False) except OSError as e: self.print_warning('%s: %s', path, e) continue @@ -565,7 +567,7 @@ class Archiver: recurse_excluded_dir = False if matcher.match(path): with backup_io('stat'): - st = os.stat(name, dir_fd=parent_fd, follow_symlinks=False) + st = os_stat(path=path, parent_fd=parent_fd, name=name, follow_symlinks=False) else: self.print_file_status('x', path) # get out here as quickly as possible: @@ -575,7 +577,7 @@ class Archiver: if not matcher.recurse_dir: return with backup_io('stat'): - st = os.stat(name, dir_fd=parent_fd, follow_symlinks=False) + st = os_stat(path=path, parent_fd=parent_fd, name=name, follow_symlinks=False) recurse_excluded_dir = stat.S_ISDIR(st.st_mode) if not recurse_excluded_dir: return diff --git a/src/borg/helpers/fs.py b/src/borg/helpers/fs.py index 7e747e756..655b400f8 100644 --- a/src/borg/helpers/fs.py +++ b/src/borg/helpers/fs.py @@ -224,7 +224,11 @@ def os_open(*, flags, path=None, parent_fd=None, name=None, noatime=False): :param noatime: True if access time shall be preserved :return: file descriptor """ - fname = name if name is not None and parent_fd is not None else path + if name and parent_fd is not None: + # name is neither None nor empty, parent_fd given. + fname = name # use name relative to parent_fd + else: + fname, parent_fd = path, None # just use the path _flags_normal = flags if noatime: _flags_noatime = _flags_normal | O_('NOATIME') @@ -242,6 +246,26 @@ def os_open(*, flags, path=None, parent_fd=None, name=None, noatime=False): return fd +def os_stat(*, path=None, parent_fd=None, name=None, follow_symlinks=False): + """ + Use os.stat to open a fs item. + + If parent_fd and name are given, they are preferred and statat will be used, + path is not used in this case. + + :param path: full (but not necessarily absolute) path + :param parent_fd: open directory file descriptor + :param name: name relative to parent_fd + :return: stat info + """ + if name and parent_fd is not None: + # name is neither None nor empty, parent_fd given. + fname = name # use name relative to parent_fd + else: + fname, parent_fd = path, None # just use the path + return os.stat(fname, dir_fd=parent_fd, follow_symlinks=follow_symlinks) + + def umount(mountpoint): env = prepare_subprocess_env(system=True) try: