diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 024f2a5ac9..ba962dcf89 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -590,31 +590,20 @@ def create_inner(archive, cache, fso): self.print_file_status(status, path) continue path = os.path.normpath(path) - parent_dir = os.path.dirname(path) or '.' - name = os.path.basename(path) try: - # 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(path=path, parent_fd=parent_fd, name=name, follow_symlinks=False) - except OSError as e: - self.print_warning('%s: %s', path, e) - continue - if args.one_file_system: - restrict_dev = st.st_dev - else: - restrict_dev = None - self._rec_walk(path=path, parent_fd=parent_fd, name=name, - fso=fso, cache=cache, matcher=matcher, - exclude_caches=args.exclude_caches, exclude_if_present=args.exclude_if_present, - keep_exclude_tags=args.keep_exclude_tags, skip_inodes=skip_inodes, - restrict_dev=restrict_dev, read_special=args.read_special, dry_run=dry_run) - # if we get back here, we've finished recursing into , - # we do not ever want to get back in there (even if path is given twice as recursion root) - skip_inodes.add((st.st_ino, st.st_dev)) + with backup_io('stat'): + st = os_stat(path=path, parent_fd=None, name=None, follow_symlinks=False) + restrict_dev = st.st_dev if args.one_file_system else None + self._rec_walk(path=path, parent_fd=None, name=None, + fso=fso, cache=cache, matcher=matcher, + exclude_caches=args.exclude_caches, exclude_if_present=args.exclude_if_present, + keep_exclude_tags=args.keep_exclude_tags, skip_inodes=skip_inodes, + restrict_dev=restrict_dev, read_special=args.read_special, dry_run=dry_run) + # if we get back here, we've finished recursing into , + # we do not ever want to get back in there (even if path is given twice as recursion root) + skip_inodes.add((st.st_ino, st.st_dev)) except (BackupOSError, BackupError) as e: - # this comes from OsOpen, self._rec_walk has own exception handler + # this comes from os.stat, self._rec_walk has own exception handler self.print_warning('%s: %s', path, e) continue if not dry_run: diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 2dc2bf5d3c..c586d2e255 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -487,6 +487,16 @@ def test_init_parent_dirs(self): self.cmd('init', '--encryption=none', '--make-parent-dirs', repository_location) assert os.path.exists(parent_path) + def test_create_unreadable_parent(self): + parent_dir = os.path.join(self.input_path, 'parent') + root_dir = os.path.join(self.input_path, 'parent', 'root') + os.mkdir(parent_dir) + os.mkdir(root_dir) + os.chmod(parent_dir, 0o111) # --x--x--x == parent dir traversable, but not readable + self.cmd('init', '--encryption=none', self.repository_location) + # issue #7746: we *can* read root_dir and we *can* traverse parent_dir, so this should work: + self.cmd('create', self.repository_location + '::test', root_dir) + def test_unix_socket(self): self.cmd('init', '--encryption=repokey', self.repository_location) try: