fix remote repository exception handling / exit codes, fixes #8631

also: reduce code duplication.
This commit is contained in:
Thomas Waldmann 2025-04-15 18:09:00 +02:00
parent 1659be23f8
commit dddb6b1556
No known key found for this signature in database
GPG key ID: 243ACFA951F78E01
2 changed files with 36 additions and 12 deletions

View file

@ -251,6 +251,19 @@ class RepositoryServer: # pragma: no cover
args = self.filter_args(f, args)
res = f(**args)
except BaseException as e:
# These exceptions are reconstructed on the client end in RemoteRepository.call_many(),
# and will be handled just like locally raised exceptions. Suppress the remote traceback
# for these, except ErrorWithTraceback, which should always display a traceback.
reconstructed_exceptions = (
Repository.InvalidRepository,
Repository.InvalidRepositoryConfig,
Repository.DoesNotExist,
Repository.AlreadyExists,
Repository.PathAlreadyExists,
PathNotAllowed,
Repository.InsufficientFreeSpaceError,
Repository.StorageQuotaExceeded,
)
if dictFormat:
ex_short = traceback.format_exception_only(e.__class__, e)
ex_full = traceback.format_exception(*sys.exc_info())
@ -258,12 +271,7 @@ class RepositoryServer: # pragma: no cover
if isinstance(e, Error):
ex_short = [e.get_message()]
ex_trace = e.traceback
if isinstance(e, (Repository.DoesNotExist, Repository.AlreadyExists, PathNotAllowed)):
# These exceptions are reconstructed on the client end in RemoteRepository.call_many(),
# and will be handled just like locally raised exceptions. Suppress the remote traceback
# for these, except ErrorWithTraceback, which should always display a traceback.
pass
else:
if not isinstance(e, reconstructed_exceptions):
logging.debug('\n'.join(ex_full))
try:
@ -286,12 +294,7 @@ class RepositoryServer: # pragma: no cover
os_write(stdout_fd, msg)
else:
if isinstance(e, (Repository.DoesNotExist, Repository.AlreadyExists, PathNotAllowed)):
# These exceptions are reconstructed on the client end in RemoteRepository.call_many(),
# and will be handled just like locally raised exceptions. Suppress the remote traceback
# for these, except ErrorWithTraceback, which should always display a traceback.
pass
else:
if not isinstance(e, reconstructed_exceptions):
if isinstance(e, Error):
tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
msg = e.get_message()
@ -759,6 +762,8 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
raise Error(args[0].decode())
elif error == 'ErrorWithTraceback':
raise ErrorWithTraceback(args[0].decode())
elif error == 'InvalidRepository':
raise Repository.InvalidRepository(self.location.processed)
elif error == 'DoesNotExist':
raise Repository.DoesNotExist(self.location.processed)
elif error == 'AlreadyExists':
@ -782,6 +787,8 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
raise PathNotAllowed(args[0].decode())
elif error == 'PathPermissionDenied':
raise Repository.PathPermissionDenied(args[0].decode())
elif error == 'PathAlreadyExists':
raise Repository.PathAlreadyExists(args[0].decode())
elif error == 'ParentPathDoesNotExist':
raise Repository.ParentPathDoesNotExist(args[0].decode())
elif error == 'ObjectNotFound':
@ -814,6 +821,12 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
raise NotMyLock('(not available)')
else:
raise NotMyLock(args[0].decode())
elif error == 'InsufficientFreeSpaceError':
raise Repository.InsufficientFreeSpaceError(args[0].decode(), args[1].decode())
elif error == 'InvalidRepositoryConfig':
raise Repository.InvalidRepositoryConfig(self.location.processed, args[1].decode())
elif error == 'StorageQuotaExceeded':
raise Repository.StorageQuotaExceeded(args[0].decode(), args[1].decode())
else:
raise self.RPCError(unpacked)

View file

@ -3935,6 +3935,17 @@ id: 2 / e29442 3506da 4e1ea7 / 25f62a 5a3d41 - 02
self.cmd('create', self.repository_location + '::test2', 'input')
assert os.path.exists(nonce)
def test_exit_codes(self):
# we create the repo path, but do NOT initialize the borg repo,
# so the borg create commands are expected to fail with InvalidRepository.
os.makedirs(self.repository_path, exist_ok=True)
with environment_variable(BORG_EXIT_CODES='classic'):
self.cmd('create', self.repository_location + '::archive', 'input', fork=True,
exit_code=EXIT_ERROR)
with environment_variable(BORG_EXIT_CODES='modern'):
self.cmd('create', self.repository_location + '::archive', 'input', fork=True,
exit_code=Repository.InvalidRepository.exit_mcode)
@unittest.skipUnless('binary' in BORG_EXES, 'no borg.exe available')
class ArchiverTestCaseBinary(ArchiverTestCase):