|
@@ -191,25 +191,53 @@ class RepositoryServer: # pragma: no cover
|
|
|
args = self.filter_args(f, args)
|
|
|
res = f(**args)
|
|
|
except BaseException as e:
|
|
|
- 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 dictFormat:
|
|
|
+ ex_short = traceback.format_exception_only(e.__class__, e)
|
|
|
+ ex_full = traceback.format_exception(*sys.exc_info())
|
|
|
if isinstance(e, Error):
|
|
|
- tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
|
|
|
- msg = e.get_message()
|
|
|
+ ex_short = e.get_message()
|
|
|
+ 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:
|
|
|
- tb_log_level = logging.ERROR
|
|
|
- msg = '%s Exception in RPC call' % e.__class__.__name__
|
|
|
- tb = '%s\n%s' % (traceback.format_exc(), sysinfo())
|
|
|
- logging.error(msg)
|
|
|
- logging.log(tb_log_level, tb)
|
|
|
- exc = 'Remote Exception (see remote log for the traceback)'
|
|
|
- if dictFormat:
|
|
|
- os.write(stdout_fd, msgpack.packb({MSGID: msgid, b'exception_class': e.__class__.__name__}))
|
|
|
+ logging.debug('\n'.join(ex_full))
|
|
|
+
|
|
|
+ try:
|
|
|
+ msg = msgpack.packb({MSGID: msgid,
|
|
|
+ b'exception_class': e.__class__.__name__,
|
|
|
+ b'exception_args': e.args,
|
|
|
+ b'exception_full': ex_full,
|
|
|
+ b'exception_short': ex_short,
|
|
|
+ b'sysinfo': sysinfo()})
|
|
|
+ except TypeError:
|
|
|
+ msg = msgpack.packb({MSGID: msgid,
|
|
|
+ b'exception_class': e.__class__.__name__,
|
|
|
+ b'exception_args': [x if isinstance(x, (str, bytes, int)) else None
|
|
|
+ for x in e.args],
|
|
|
+ b'exception_full': ex_full,
|
|
|
+ b'exception_short': ex_short,
|
|
|
+ b'sysinfo': sysinfo()})
|
|
|
+
|
|
|
+ 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 isinstance(e, Error):
|
|
|
+ tb_log_level = logging.ERROR if e.traceback else logging.DEBUG
|
|
|
+ msg = e.get_message()
|
|
|
+ else:
|
|
|
+ tb_log_level = logging.ERROR
|
|
|
+ msg = '%s Exception in RPC call' % e.__class__.__name__
|
|
|
+ tb = '%s\n%s' % (traceback.format_exc(), sysinfo())
|
|
|
+ logging.error(msg)
|
|
|
+ logging.log(tb_log_level, tb)
|
|
|
+ exc = 'Remote Exception (see remote log for the traceback)'
|
|
|
os.write(stdout_fd, msgpack.packb((1, msgid, e.__class__.__name__, exc)))
|
|
|
else:
|
|
|
if dictFormat:
|
|
@@ -341,9 +369,34 @@ class RemoteRepository:
|
|
|
extra_test_args = []
|
|
|
|
|
|
class RPCError(Exception):
|
|
|
- def __init__(self, name, remote_type):
|
|
|
- self.name = name
|
|
|
- self.remote_type = remote_type
|
|
|
+ def __init__(self, unpacked):
|
|
|
+ # for borg < 1.1: unpacked only has b'exception_class' as key
|
|
|
+ # for borg 1.1+: unpacked has keys: b'exception_args', b'exception_full', b'exception_short', b'sysinfo'
|
|
|
+ self.unpacked = unpacked
|
|
|
+
|
|
|
+ def get_message(self):
|
|
|
+ if b'exception_short' in self.unpacked:
|
|
|
+ return b'\n'.join(self.unpacked[b'exception_short']).decode()
|
|
|
+ else:
|
|
|
+ return self.exception_class
|
|
|
+
|
|
|
+ @property
|
|
|
+ def exception_class(self):
|
|
|
+ return self.unpacked[b'exception_class'].decode()
|
|
|
+
|
|
|
+ @property
|
|
|
+ def exception_full(self):
|
|
|
+ if b'exception_full' in self.unpacked:
|
|
|
+ return b'\n'.join(self.unpacked[b'exception_full']).decode()
|
|
|
+ else:
|
|
|
+ return self.get_message() + '\nRemote Exception (see remote log for the traceback)'
|
|
|
+
|
|
|
+ @property
|
|
|
+ def sysinfo(self):
|
|
|
+ if b'sysinfo' in self.unpacked:
|
|
|
+ return self.unpacked[b'sysinfo'].decode()
|
|
|
+ else:
|
|
|
+ return ''
|
|
|
|
|
|
class RPCServerOutdated(Error):
|
|
|
"""Borg server is too old for {}. Required version {}"""
|
|
@@ -411,7 +464,7 @@ class RemoteRepository:
|
|
|
|
|
|
def do_open():
|
|
|
self.id = self.open(path=self.location.path, create=create, lock_wait=lock_wait,
|
|
|
- lock=lock, exclusive=exclusive, append_only=append_only)
|
|
|
+ lock=lock, exclusive=exclusive, append_only=append_only)
|
|
|
|
|
|
if self.dictFormat:
|
|
|
do_open()
|
|
@@ -420,7 +473,7 @@ class RemoteRepository:
|
|
|
try:
|
|
|
do_open()
|
|
|
except self.RPCError as err:
|
|
|
- if err.remote_type != 'TypeError':
|
|
|
+ if err.exception_class != 'TypeError':
|
|
|
raise
|
|
|
msg = """\
|
|
|
Please note:
|
|
@@ -524,8 +577,11 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
|
|
|
del self.chunkid_to_msgids[chunkid]
|
|
|
return msgid
|
|
|
|
|
|
- def handle_error(error, res):
|
|
|
- error = error.decode('utf-8')
|
|
|
+ def handle_error(unpacked):
|
|
|
+ error = unpacked[b'exception_class'].decode()
|
|
|
+ old_server = b'exception_args' not in unpacked
|
|
|
+ args = unpacked.get(b'exception_args')
|
|
|
+
|
|
|
if error == 'DoesNotExist':
|
|
|
raise Repository.DoesNotExist(self.location.orig)
|
|
|
elif error == 'AlreadyExists':
|
|
@@ -533,15 +589,24 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
|
|
|
elif error == 'CheckNeeded':
|
|
|
raise Repository.CheckNeeded(self.location.orig)
|
|
|
elif error == 'IntegrityError':
|
|
|
- raise IntegrityError('(not available)')
|
|
|
+ if old_server:
|
|
|
+ raise IntegrityError('(not available)')
|
|
|
+ else:
|
|
|
+ raise IntegrityError(args[0].decode())
|
|
|
elif error == 'PathNotAllowed':
|
|
|
raise PathNotAllowed()
|
|
|
elif error == 'ObjectNotFound':
|
|
|
- raise Repository.ObjectNotFound('(not available)', self.location.orig)
|
|
|
+ if old_server:
|
|
|
+ raise Repository.ObjectNotFound('(not available)', self.location.orig)
|
|
|
+ else:
|
|
|
+ raise Repository.ObjectNotFound(args[0].decode(), self.location.orig)
|
|
|
elif error == 'InvalidRPCMethod':
|
|
|
- raise InvalidRPCMethod('(not available)')
|
|
|
+ if old_server:
|
|
|
+ raise InvalidRPCMethod('(not available)')
|
|
|
+ else:
|
|
|
+ raise InvalidRPCMethod(args[0].decode())
|
|
|
else:
|
|
|
- raise self.RPCError(res.decode('utf-8'), error)
|
|
|
+ raise self.RPCError(unpacked)
|
|
|
|
|
|
calls = list(calls)
|
|
|
waiting_for = []
|
|
@@ -551,7 +616,7 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
|
|
|
unpacked = self.responses.pop(waiting_for[0])
|
|
|
waiting_for.pop(0)
|
|
|
if b'exception_class' in unpacked:
|
|
|
- handle_error(unpacked[b'exception_class'], None)
|
|
|
+ handle_error(unpacked)
|
|
|
else:
|
|
|
yield unpacked[RESULT]
|
|
|
if not waiting_for and not calls:
|
|
@@ -577,6 +642,7 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
|
|
|
elif isinstance(unpacked, tuple) and len(unpacked) == 4:
|
|
|
type, msgid, error, res = unpacked
|
|
|
if error:
|
|
|
+ # ignore res, because it is only a fixed string anyway.
|
|
|
unpacked = {MSGID: msgid, b'exception_class': error}
|
|
|
else:
|
|
|
unpacked = {MSGID: msgid, RESULT: res}
|
|
@@ -585,7 +651,7 @@ This problem will go away as soon as the server has been upgraded to 1.0.7+.
|
|
|
if msgid in self.ignore_responses:
|
|
|
self.ignore_responses.remove(msgid)
|
|
|
if b'exception_class' in unpacked:
|
|
|
- handle_error(unpacked[b'exception_class'], None)
|
|
|
+ handle_error(unpacked)
|
|
|
else:
|
|
|
self.responses[msgid] = unpacked
|
|
|
elif fd is self.stderr_fd:
|