diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 385056b4d..db0232eab 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -66,7 +66,7 @@ from .helpers import ChunkIteratorFileWrapper from .helpers import popen_with_error_handling, prepare_subprocess_env from .helpers import dash_open from .helpers import umount -from .helpers import flags_root, flags_dir, flags_follow +from .helpers import flags_root, flags_dir, flags_special_follow, flags_special from .helpers import msgpack from .nanorst import rst_to_terminal from .patterns import ArgparsePatternAction, ArgparseExcludeFileAction, ArgparsePatternFileAction, parse_exclude_pattern @@ -646,7 +646,7 @@ class Archiver: special = is_special(st_target.st_mode) if special: status = fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st_target, - cache=cache, flags=flags_follow) + cache=cache, flags=flags_special_follow) else: status = fso.process_symlink(path=path, parent_fd=parent_fd, name=name, st=st) elif stat.S_ISFIFO(st.st_mode): @@ -654,19 +654,22 @@ class Archiver: if not read_special: status = fso.process_fifo(path=path, parent_fd=parent_fd, name=name, st=st) else: - status = fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st, cache=cache) + status = fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st, + cache=cache, flags=flags_special) elif stat.S_ISCHR(st.st_mode): if not dry_run: if not read_special: status = fso.process_dev(path=path, parent_fd=parent_fd, name=name, st=st, dev_type='c') else: - status = fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st, cache=cache) + status = fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st, + cache=cache, flags=flags_special) elif stat.S_ISBLK(st.st_mode): if not dry_run: if not read_special: status = fso.process_dev(path=path, parent_fd=parent_fd, name=name, st=st, dev_type='b') else: - status = fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st, cache=cache) + status = fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st, + cache=cache, flags=flags_special) elif stat.S_ISSOCK(st.st_mode): # Ignore unix sockets return diff --git a/src/borg/helpers/fs.py b/src/borg/helpers/fs.py index 655b400f8..cb3f0ba8e 100644 --- a/src/borg/helpers/fs.py +++ b/src/borg/helpers/fs.py @@ -202,9 +202,10 @@ def O_(*flags): return result -flags_base = O_('BINARY', 'NONBLOCK', 'NOCTTY') -flags_follow = flags_base | O_('RDONLY') -flags_normal = flags_base | O_('RDONLY', 'NOFOLLOW') +flags_base = O_('BINARY', 'NOCTTY', 'RDONLY') +flags_special = flags_base | O_('NOFOLLOW') # BLOCK == wait when reading devices or fifos +flags_special_follow = flags_base # BLOCK == wait when reading symlinked devices or fifos +flags_normal = flags_base | O_('NONBLOCK', 'NOFOLLOW') flags_noatime = flags_normal | O_('NOATIME') flags_root = O_('RDONLY') flags_dir = O_('DIRECTORY', 'RDONLY', 'NOFOLLOW') diff --git a/src/borg/testsuite/archiver.py b/src/borg/testsuite/archiver.py index 0efaf8e9a..26f9f68b1 100644 --- a/src/borg/testsuite/archiver.py +++ b/src/borg/testsuite/archiver.py @@ -1780,19 +1780,38 @@ class ArchiverTestCase(ArchiverTestCaseBase): output = self.cmd('create', '--list', '--filter=AM', self.repository_location + '::test3', 'input') self.assert_in('file1', output) - def test_create_read_special(self): - self.create_regular_file('regular', size=1024) - os.symlink(os.path.join(self.input_path, 'file'), os.path.join(self.input_path, 'link_regular')) - if are_fifos_supported(): - os.mkfifo(os.path.join(self.input_path, 'fifo')) - os.symlink(os.path.join(self.input_path, 'fifo'), os.path.join(self.input_path, 'link_fifo')) + @pytest.mark.skipif(not are_fifos_supported(), reason='FIFOs not supported') + def test_create_read_special_symlink(self): + from threading import Thread + + def fifo_feeder(fifo_fn, data): + fd = os.open(fifo_fn, os.O_WRONLY) + try: + os.write(fd, data) + finally: + os.close(fd) + self.cmd('init', '--encryption=repokey', self.repository_location) archive = self.repository_location + '::test' - self.cmd('create', '--read-special', archive, 'input') - output = self.cmd('list', archive) - assert 'input/link_regular -> ' in output # not pointing to special file: archived as symlink - if are_fifos_supported(): - assert 'input/link_fifo\n' in output # pointing to a special file: archived following symlink + data = b'foobar' * 1000 + + fifo_fn = os.path.join(self.input_path, 'fifo') + link_fn = os.path.join(self.input_path, 'link_fifo') + os.mkfifo(fifo_fn) + os.symlink(fifo_fn, link_fn) + + t = Thread(target=fifo_feeder, args=(fifo_fn, data)) + t.start() + try: + self.cmd('create', '--read-special', archive, 'input/link_fifo') + finally: + t.join() + with changedir('output'): + self.cmd('extract', archive) + fifo_fn = 'input/link_fifo' + with open(fifo_fn, 'rb') as f: + extracted_data = f.read() + assert extracted_data == data def test_create_read_special_broken_symlink(self): os.symlink('somewhere doesnt exist', os.path.join(self.input_path, 'link'))