diff --git a/src/borg/archiver.py b/src/borg/archiver.py index 41be9d620..083eccf1b 100644 --- a/src/borg/archiver.py +++ b/src/borg/archiver.py @@ -73,13 +73,16 @@ def with_repository(fake=False, create=False, lock=True, exclusive=False, manife @functools.wraps(method) def wrapper(self, args, **kwargs): location = args.location # note: 'location' must be always present in args + append_only = getattr(args, 'append_only', False) if argument(args, fake): return method(self, args, repository=None, **kwargs) elif location.proto == 'ssh': - repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait, lock=lock, args=args) + repository = RemoteRepository(location, create=create, lock_wait=self.lock_wait, lock=lock, + append_only=append_only, args=args) else: repository = Repository(location.path, create=create, exclusive=argument(args, exclusive), - lock_wait=self.lock_wait, lock=lock) + lock_wait=self.lock_wait, lock=lock, + append_only=append_only) with repository: if manifest or cache: kwargs['manifest'], kwargs['key'] = Manifest.load(repository) @@ -1304,6 +1307,8 @@ class Archiver: subparser.add_argument('-e', '--encryption', dest='encryption', choices=('none', 'keyfile', 'repokey'), default='repokey', help='select encryption key mode (default: "%(default)s")') + subparser.add_argument('-a', '--append-only', dest='append_only', action='store_true', + help='create an append-only mode repository') check_epilog = textwrap.dedent(""" The check command verifies the consistency of a repository and the corresponding archives. diff --git a/src/borg/remote.py b/src/borg/remote.py index 1a55b9eeb..a7d6cb89c 100644 --- a/src/borg/remote.py +++ b/src/borg/remote.py @@ -117,7 +117,7 @@ class RepositoryServer: # pragma: no cover def negotiate(self, versions): return RPC_PROTOCOL_VERSION - def open(self, path, create=False, lock_wait=None, lock=True): + def open(self, path, create=False, lock_wait=None, lock=True, append_only=False): path = os.fsdecode(path) if path.startswith('/~'): path = os.path.join(get_home_dir(), path[2:]) @@ -128,7 +128,7 @@ class RepositoryServer: # pragma: no cover break else: raise PathNotAllowed(path) - self.repository = Repository(path, create, lock_wait=lock_wait, lock=lock, append_only=self.append_only) + self.repository = Repository(path, create, lock_wait=lock_wait, lock=lock, append_only=self.append_only or append_only) self.repository.__enter__() # clean exit handled by serve() method return self.repository.id @@ -137,10 +137,14 @@ class RemoteRepository: extra_test_args = [] class RPCError(Exception): - def __init__(self, name): + def __init__(self, name, remote_type): self.name = name + self.remote_type = remote_type - def __init__(self, location, create=False, lock_wait=None, lock=True, args=None): + class NoAppendOnlyOnServer(Error): + """Server does not support --append-only.""" + + def __init__(self, location, create=False, lock_wait=None, lock=True, append_only=False, args=None): self.location = self._location = location self.preload_ids = [] self.msgid = 0 @@ -176,7 +180,17 @@ class RemoteRepository: if version != RPC_PROTOCOL_VERSION: raise Exception('Server insisted on using unsupported protocol version %d' % version) try: - self.id = self.call('open', self.location.path, create, lock_wait, lock) + # Because of protocol versions, only send append_only if necessary + if append_only: + try: + self.id = self.call('open', self.location.path, create, lock_wait, lock, append_only) + except self.RPCError as err: + if err.remote_type == 'TypeError': + raise self.NoAppendOnlyOnServer() from err + else: + raise + else: + self.id = self.call('open', self.location.path, create, lock_wait, lock) except Exception: self.close() raise @@ -272,7 +286,7 @@ class RemoteRepository: elif error == b'InvalidRPCMethod': raise InvalidRPCMethod(*res) else: - raise self.RPCError(res.decode('utf-8')) + raise self.RPCError(res.decode('utf-8'), error.decode('utf-8')) calls = list(calls) waiting_for = [] diff --git a/src/borg/repository.py b/src/borg/repository.py index 6af3f87d9..00a9c4e10 100644 --- a/src/borg/repository.py +++ b/src/borg/repository.py @@ -154,7 +154,7 @@ class Repository: config.set('repository', 'version', '1') config.set('repository', 'segments_per_dir', str(DEFAULT_SEGMENTS_PER_DIR)) config.set('repository', 'max_segment_size', str(DEFAULT_MAX_SEGMENT_SIZE)) - config.set('repository', 'append_only', '0') + config.set('repository', 'append_only', str(int(self.append_only))) config.set('repository', 'id', bin_to_hex(os.urandom(32))) self.save_config(path, config)