From 45815554ce2d9ce0a12c21e0a5b37bc71435d74a Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 19 Jan 2024 18:13:53 +0100 Subject: [PATCH 1/2] check_can_create_repository: deal with PermissionErrors, see #7016 borg init calls this. If there is a PermissionError, it is usually fs permission issue at path or its parent directory. Don't give a traceback, but rather an error msg and a specific exit code. --- docs/internals/frontends.rst | 2 ++ src/borg/remote.py | 2 ++ src/borg/repository.py | 19 +++++++++++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/internals/frontends.rst b/docs/internals/frontends.rst index e90ce61a9..786667fd4 100644 --- a/docs/internals/frontends.rst +++ b/docs/internals/frontends.rst @@ -612,6 +612,8 @@ Errors There is already something at {}. Repository.StorageQuotaExceeded rc: 20 traceback: no The storage quota ({}) has been exceeded ({}). Try deleting some archives. + Repository.PathPermissionDenied rc: 21 traceback: no + Permission denied to {}. MandatoryFeatureUnsupported rc: 25 traceback: no Unsupported repository feature(s) {}. A newer version of borg is required to access this repository. diff --git a/src/borg/remote.py b/src/borg/remote.py index 0aa636220..37d74150f 100644 --- a/src/borg/remote.py +++ b/src/borg/remote.py @@ -780,6 +780,8 @@ class RemoteRepository: raise IntegrityError(args[0]) elif error == "PathNotAllowed": raise PathNotAllowed(args[0]) + elif error == "PathPermissionDenied": + raise Repository.PathPermissionDenied(args[0]) elif error == "ParentPathDoesNotExist": raise Repository.ParentPathDoesNotExist(args[0]) elif error == "ObjectNotFound": diff --git a/src/borg/repository.py b/src/borg/repository.py index 7499946fa..079fbc21e 100644 --- a/src/borg/repository.py +++ b/src/borg/repository.py @@ -188,6 +188,11 @@ class Repository: exit_mcode = 20 + class PathPermissionDenied(Error): + """Permission denied to {}.""" + + exit_mcode = 21 + def __init__( self, path, @@ -299,13 +304,23 @@ class Repository: st = os.stat(path) except FileNotFoundError: pass # nothing there! + except PermissionError: + raise self.PathPermissionDenied(path) from None else: # there is something already there! if self.is_repository(path): raise self.AlreadyExists(path) - if not stat.S_ISDIR(st.st_mode) or os.listdir(path): + if not stat.S_ISDIR(st.st_mode): raise self.PathAlreadyExists(path) - # an empty directory is acceptable for us. + try: + files = os.listdir(path) + except PermissionError: + raise self.PathPermissionDenied(path) from None + else: + if files: # a dir, but not empty + raise self.PathAlreadyExists(path) + else: # an empty directory is acceptable for us. + pass while True: # Check all parent directories for Borg's repository README From 43a20b052e1d0ac4881625832610702621486713 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Fri, 19 Jan 2024 18:38:07 +0100 Subject: [PATCH 2/2] add ConnectionBrokenWithHint for BrokenPipeErrors and similar, see #7016 no traceback, but error message and specific exit code. --- docs/internals/frontends.rst | 2 ++ src/borg/remote.py | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/internals/frontends.rst b/docs/internals/frontends.rst index 786667fd4..08e2f4f4a 100644 --- a/docs/internals/frontends.rst +++ b/docs/internals/frontends.rst @@ -699,6 +699,8 @@ Errors UnexpectedRPCDataFormatFromServer rc: 86 traceback: no Got unexpected RPC data format from server: {} + ConnectionBrokenWithHint rc: 87 traceback: no + Connection to remote host is broken. {} IntegrityError rc: 90 traceback: yes Data integrity error: {} diff --git a/src/borg/remote.py b/src/borg/remote.py index 37d74150f..924b36ad7 100644 --- a/src/borg/remote.py +++ b/src/borg/remote.py @@ -112,6 +112,12 @@ class UnexpectedRPCDataFormatFromServer(Error): super().__init__(data) +class ConnectionBrokenWithHint(Error): + """Connection to remote host is broken. {}""" + + exit_mcode = 87 + + # Protocol compatibility: # In general the server is responsible for rejecting too old clients and the client it responsible for rejecting # too old servers. This ensures that the knowledge what is compatible is always held by the newer component. @@ -428,7 +434,10 @@ class SleepingBandwidthLimiter: self.ratelimit_last = time.monotonic() if len(to_send) > self.ratelimit_quota: to_send = to_send[: self.ratelimit_quota] - written = os.write(fd, to_send) + try: + written = os.write(fd, to_send) + except BrokenPipeError: + raise ConnectionBrokenWithHint("Broken Pipe") from None if self.ratelimit: self.ratelimit_quota -= written return written