Browse Source

Merge pull request #4406 from ThomasWaldmann/fix-backup-root

fix openat/statat issues for root directory, fixes #4405
TW 6 years ago
parent
commit
81f9a8ccde
2 changed files with 31 additions and 5 deletions
  1. 6 4
      src/borg/archiver.py
  2. 25 1
      src/borg/helpers/fs.py

+ 6 - 4
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

+ 25 - 1
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: