Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optional more specific return codes #7928

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
adbb1d5
BORG_EXIT_CODES=modern can be set to get more specific process exit c…
ThomasWaldmann Nov 9, 2023
c147818
scripts/errorlist.py: improve error list docs generation
ThomasWaldmann Nov 9, 2023
2a6d86d
update "modern" error RCs (docs and code)
ThomasWaldmann Nov 9, 2023
6fb61d0
use print_warning also in borg delete ::archive --force --force
ThomasWaldmann Nov 12, 2023
9b62016
refactor set_ec usage
ThomasWaldmann Nov 12, 2023
32d0fbe
fix dealing with remote repo Locking Exceptions
ThomasWaldmann Nov 12, 2023
c6bde98
shorten TAMRequiredError error msg
ThomasWaldmann Nov 13, 2023
b31b630
get rid of some rare error classes, use RTError instead
ThomasWaldmann Nov 13, 2023
bd2136f
new warnings infrastructure to support modern exit codes
ThomasWaldmann Nov 15, 2023
b179dd1
move Backup*Error to errors module
ThomasWaldmann Nov 15, 2023
f77b6a7
BackupError->BackupWarning, BackupOSError->BackupOSWarning
ThomasWaldmann Nov 15, 2023
f88fac4
more detailled warnings for source file OSErrors
ThomasWaldmann Nov 15, 2023
df168f4
extend errorlist script to warnings, update docs
ThomasWaldmann Nov 15, 2023
af30559
print_warning*: support warning msgids, fixes #7080
ThomasWaldmann Nov 18, 2023
d13d98c
add NotFoundWarning
ThomasWaldmann Nov 18, 2023
7316d10
raise BackupOSError subclasses
ThomasWaldmann Nov 27, 2023
d3a11fb
BackupErrors get caught and give warning RCs
ThomasWaldmann Dec 2, 2023
5d7eb08
update frontends.rst error/warning list
ThomasWaldmann Dec 2, 2023
330b350
do not return the rc from Archiver methods
ThomasWaldmann Dec 5, 2023
38cb364
refactor (re-)init of exit_code and warnings_list globals
ThomasWaldmann Dec 15, 2023
3a18393
use get_reset_ec to internally re-init ec/warnings
ThomasWaldmann Dec 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 152 additions & 66 deletions docs/internals/frontends.rst
Original file line number Diff line number Diff line change
Expand Up @@ -538,92 +538,178 @@ Message IDs are strings that essentially give a log message or operation a name,
full text, since texts change more frequently. Message IDs are unambiguous and reduce the need to parse
log messages.

Assigned message IDs are:
Assigned message IDs and related error RCs (exit codes) are:

.. See scripts/errorlist.py; this is slightly edited.

Errors
Archive.AlreadyExists
Error rc: 2 traceback: no
Error: {}
ErrorWithTraceback rc: 2 traceback: yes
Error: {}

Buffer.MemoryLimitExceeded rc: 2 traceback: no
Requested buffer size {} is above the limit of {}.
EfficientCollectionQueue.SizeUnderflow rc: 2 traceback: no
Could not pop_front first {} elements, collection only has {} elements..
RTError rc: 2 traceback: no
Runtime Error: {}

CancelledByUser rc: 3 traceback: no
Cancelled by user.

CommandError rc: 4 traceback: no
Command Error: {}
PlaceholderError rc: 5 traceback: no
Formatting Error: "{}".format({}): {}({})
InvalidPlaceholder rc: 6 traceback: no
Invalid placeholder "{}" in string: {}

Repository.AlreadyExists rc: 10 traceback: no
A repository already exists at {}.
Repository.AtticRepository rc: 11 traceback: no
Attic repository detected. Please run "borg upgrade {}".
Repository.CheckNeeded rc: 12 traceback: yes
Inconsistency detected. Please run "borg check {}".
Repository.DoesNotExist rc: 13 traceback: no
Repository {} does not exist.
Repository.InsufficientFreeSpaceError rc: 14 traceback: no
Insufficient free space to complete transaction (required: {}, available: {}).
Repository.InvalidRepository rc: 15 traceback: no
{} is not a valid repository. Check repo config.
Repository.InvalidRepositoryConfig rc: 16 traceback: no
{} does not have a valid configuration. Check repo config [{}].
Repository.ObjectNotFound rc: 17 traceback: yes
Object with key {} not found in repository {}.
Repository.ParentPathDoesNotExist rc: 18 traceback: no
The parent path of the repo directory [{}] does not exist.
Repository.PathAlreadyExists rc: 19 traceback: no
There is already something at {}.
Repository.StorageQuotaExceeded rc: 20 traceback: no
The storage quota ({}) has been exceeded ({}). Try deleting some archives.

MandatoryFeatureUnsupported rc: 25 traceback: no
Unsupported repository feature(s) {}. A newer version of borg is required to access this repository.
NoManifestError rc: 26 traceback: no
Repository has no manifest.
UnsupportedManifestError rc: 27 traceback: no
Unsupported manifest envelope. A newer version is required to access this repository.

Archive.AlreadyExists rc: 30 traceback: no
Archive {} already exists
Archive.DoesNotExist
Archive.DoesNotExist rc: 31 traceback: no
Archive {} does not exist
Archive.IncompatibleFilesystemEncodingError
Archive.IncompatibleFilesystemEncodingError rc: 32 traceback: no
Failed to encode filename "{}" into file system encoding "{}". Consider configuring the LANG environment variable.
Cache.CacheInitAbortedError
Cache initialization aborted
Cache.EncryptionMethodMismatch
Repository encryption method changed since last access, refusing to continue
Cache.RepositoryAccessAborted
Repository access aborted
Cache.RepositoryIDNotUnique
Cache is newer than repository - do you have multiple, independently updated repos with same ID?
Cache.RepositoryReplay
Cache is newer than repository - this is either an attack or unsafe (multiple repos with same ID)
Buffer.MemoryLimitExceeded
Requested buffer size {} is above the limit of {}.
ExtensionModuleError
The Borg binary extension modules do not seem to be properly installed
IntegrityError
Data integrity error: {}
NoManifestError
Repository has no manifest.
PlaceholderError
Formatting Error: "{}".format({}): {}({})
KeyfileInvalidError

KeyfileInvalidError rc: 40 traceback: no
Invalid key file for repository {} found in {}.
KeyfileMismatchError
KeyfileMismatchError rc: 41 traceback: no
Mismatch between repository {} and key file {}.
KeyfileNotFoundError
KeyfileNotFoundError rc: 42 traceback: no
No key file for repository {} found in {}.
PassphraseWrong
passphrase supplied in BORG_PASSPHRASE is incorrect
PasswordRetriesExceeded
exceeded the maximum password retries
RepoKeyNotFoundError
No key entry found in the config of repository {}.
UnsupportedManifestError
Unsupported manifest envelope. A newer version is required to access this repository.
UnsupportedPayloadError
Unsupported payload type {}. A newer version is required to access this repository.
NotABorgKeyFile
NotABorgKeyFile rc: 43 traceback: no
This file is not a borg key backup, aborting.
RepoIdMismatch
RepoKeyNotFoundError rc: 44 traceback: no
No key entry found in the config of repository {}.
RepoIdMismatch rc: 45 traceback: no
This key backup seems to be for a different backup repository, aborting.
UnencryptedRepo
Keymanagement not available for unencrypted repositories.
UnknownKeyType
Keytype {0} is unknown.
LockError
UnencryptedRepo rc: 46 traceback: no
Key management not available for unencrypted repositories.
UnknownKeyType rc: 47 traceback: no
Key type {0} is unknown.
UnsupportedPayloadError rc: 48 traceback: no
Unsupported payload type {}. A newer version is required to access this repository.

NoPassphraseFailure rc: 50 traceback: no
can not acquire a passphrase: {}
PasscommandFailure rc: 51 traceback: no
passcommand supplied in BORG_PASSCOMMAND failed: {}
PassphraseWrong rc: 52 traceback: no
passphrase supplied in BORG_PASSPHRASE, by BORG_PASSCOMMAND or via BORG_PASSPHRASE_FD is incorrect.
PasswordRetriesExceeded rc: 53 traceback: no
exceeded the maximum password retries

Cache.CacheInitAbortedError rc: 60 traceback: no
Cache initialization aborted
Cache.EncryptionMethodMismatch rc: 61 traceback: no
Repository encryption method changed since last access, refusing to continue
Cache.RepositoryAccessAborted rc: 62 traceback: no
Repository access aborted
Cache.RepositoryIDNotUnique rc: 63 traceback: no
Cache is newer than repository - do you have multiple, independently updated repos with same ID?
Cache.RepositoryReplay rc: 64 traceback: no
Cache, or information obtained from the security directory is newer than repository - this is either an attack or unsafe (multiple repos with same ID)

LockError rc: 70 traceback: no
Failed to acquire the lock {}.
LockErrorT
LockErrorT rc: 71 traceback: yes
Failed to acquire the lock {}.
ConnectionClosed
LockFailed rc: 72 traceback: yes
Failed to create/acquire the lock {} ({}).
LockTimeout rc: 73 traceback: no
Failed to create/acquire the lock {} (timeout).
NotLocked rc: 74 traceback: yes
Failed to release the lock {} (was not locked).
NotMyLock rc: 75 traceback: yes
Failed to release the lock {} (was/is locked, but not by me).

ConnectionClosed rc: 80 traceback: no
Connection closed by remote host
InvalidRPCMethod
ConnectionClosedWithHint rc: 81 traceback: no
Connection closed by remote host. {}
InvalidRPCMethod rc: 82 traceback: no
RPC method {} is not valid
PathNotAllowed
Repository path not allowed
RemoteRepository.RPCServerOutdated
PathNotAllowed rc: 83 traceback: no
Repository path not allowed: {}
RemoteRepository.RPCServerOutdated rc: 84 traceback: no
Borg server is too old for {}. Required version {}
UnexpectedRPCDataFormatFromClient
UnexpectedRPCDataFormatFromClient rc: 85 traceback: no
Borg {}: Got unexpected RPC data format from client.
UnexpectedRPCDataFormatFromServer
UnexpectedRPCDataFormatFromServer rc: 86 traceback: no
Got unexpected RPC data format from server:
{}
Repository.AlreadyExists
Repository {} already exists.
Repository.CheckNeeded
Inconsistency detected. Please run "borg check {}".
Repository.DoesNotExist
Repository {} does not exist.
Repository.InsufficientFreeSpaceError
Insufficient free space to complete transaction (required: {}, available: {}).
Repository.InvalidRepository
{} is not a valid repository. Check repo config.
Repository.AtticRepository
Attic repository detected. Please run "borg upgrade {}".
Repository.ObjectNotFound
Object with key {} not found in repository {}.

IntegrityError rc: 90 traceback: yes
Data integrity error: {}
FileIntegrityError rc: 91 traceback: yes
File failed integrity check: {}
DecompressionError rc: 92 traceback: yes
Decompression error: {}

ArchiveTAMInvalid rc: 95 traceback: yes
Data integrity error: {}
ArchiveTAMRequiredError rc: 96 traceback: yes
Archive '{}' is unauthenticated, but it is required for this repository.
TAMInvalid rc: 97 traceback: yes
Data integrity error: {}
TAMRequiredError rc: 98 traceback: yes
Manifest is unauthenticated, but it is required for this repository.
TAMUnsupportedSuiteError rc: 99 traceback: yes
Could not verify manifest: Unsupported suite {!r}; a newer version is needed.

Warnings
BorgWarning rc: 1
Warning: {}
BackupWarning rc: 1
{}: {}

FileChangedWarning rc: 100
{}: file changed while we backed it up
IncludePatternNeverMatchedWarning rc: 101
Include pattern '{}' never matched.
BackupError rc: 102
{}: backup error
BackupRaceConditionError rc: 103
{}: file type or inode changed while we backed it up (race condition, skipped file)
BackupOSError rc: 104
{}: {}
BackupPermissionError rc: 105
{}: {}
BackupIOError rc: 106
{}: {}
BackupFileNotFoundError rc: 107
{}: {}

Operations
- cache.begin_transaction
Expand Down
3 changes: 3 additions & 0 deletions docs/usage/general/environment.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ General:
Main usecase for this is to fully automate ``borg change-passphrase``.
BORG_DISPLAY_PASSPHRASE
When set, use the value to answer the "display the passphrase for verification" question when defining a new passphrase for encrypted repositories.
BORG_EXIT_CODES
When set to "modern", the borg process will return more specific exit codes (rc).
Default is "legacy" and returns rc 2 for all errors, 1 for all warnings, 0 for success.
BORG_HOST_ID
Borg usually computes a host id from the FQDN plus the results of ``uuid.getnode()`` (which usually returns
a unique id based on the MAC address of the network interface. Except if that MAC happens to be all-zero - in
Expand Down
6 changes: 4 additions & 2 deletions docs/usage/general/return-codes.rst.inc
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ Borg can exit with the following return codes (rc):
Return code Meaning
=========== =======
0 success (logged as INFO)
1 warning (operation reached its normal end, but there were warnings --
1 generic warning (operation reached its normal end, but there were warnings --
you should check the log, logged as WARNING)
2 error (like a fatal error, a local or remote exception, the operation
2 generic error (like a fatal error, a local or remote exception, the operation
did not reach its normal end, logged as ERROR)
3..99 specific error (enabled by BORG_EXIT_CODES=modern)
100..127 specific warning (enabled by BORG_EXIT_CODES=modern)
128+N killed by signal N (e.g. 137 == kill -9)
=========== =======

Expand Down
63 changes: 56 additions & 7 deletions scripts/errorlist.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,63 @@
#!/usr/bin/env python3
# this script automatically generates the error list for the docs by
# looking at the "Error" class and its subclasses.

from textwrap import indent

import borg.archiver # noqa: F401 - need import to get Error and ErrorWithTraceback subclasses.
from borg.helpers import Error, ErrorWithTraceback
import borg.archiver # noqa: F401 - need import to get Error subclasses.
from borg.constants import * # NOQA
from borg.helpers import Error, BackupError, BorgWarning

classes = Error.__subclasses__() + ErrorWithTraceback.__subclasses__()

for cls in sorted(classes, key=lambda cls: (cls.__module__, cls.__qualname__)):
if cls is ErrorWithTraceback:
continue
print(' ', cls.__qualname__)
def subclasses(cls):
direct_subclasses = cls.__subclasses__()
return set(direct_subclasses) | set(s for c in direct_subclasses for s in subclasses(c))


# 0, 1, 2 are used for success, generic warning, generic error
# 3..99 are available for specific errors
# 100..127 are available for specific warnings
# 128+ are reserved for signals
free_error_rcs = set(range(EXIT_ERROR_BASE, EXIT_WARNING_BASE)) # 3 .. 99
free_warning_rcs = set(range(EXIT_WARNING_BASE, EXIT_SIGNAL_BASE)) # 100 .. 127

# these classes map to rc 2
generic_error_rc_classes = set()
generic_warning_rc_classes = set()

error_classes = {Error} | subclasses(Error)

for cls in sorted(error_classes, key=lambda cls: (cls.__module__, cls.__qualname__)):
traceback = "yes" if cls.traceback else "no"
rc = cls.exit_mcode
print(' ', cls.__qualname__, 'rc:', rc, 'traceback:', traceback)
print(indent(cls.__doc__, ' ' * 8))
if rc in free_error_rcs:
free_error_rcs.remove(rc)
elif rc == 2:
generic_error_rc_classes.add(cls.__qualname__)
else: # rc != 2
# if we did not intentionally map this to the generic error rc, this might be an issue:
print(f'ERROR: {rc} is not a free/available RC, but either duplicate or invalid')

print()
print('free error RCs:', sorted(free_error_rcs))
print('generic errors:', sorted(generic_error_rc_classes))

warning_classes = {BorgWarning} | subclasses(BorgWarning) | {BackupError} | subclasses(BackupError)

for cls in sorted(warning_classes, key=lambda cls: (cls.__module__, cls.__qualname__)):
rc = cls.exit_mcode
print(' ', cls.__qualname__, 'rc:', rc)
print(indent(cls.__doc__, ' ' * 8))
if rc in free_warning_rcs:
free_warning_rcs.remove(rc)
elif rc == 1:
generic_warning_rc_classes.add(cls.__qualname__)
else: # rc != 1
# if we did not intentionally map this to the generic warning rc, this might be an issue:
print(f'ERROR: {rc} is not a free/available RC, but either duplicate or invalid')

print("\n")
print('free warning RCs:', sorted(free_warning_rcs))
print('generic warnings:', sorted(generic_warning_rc_classes))
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ ignore = E226, W503
# flake8 failures that appear with your change.
per_file_ignores =
docs/conf.py:E121,E126,E265,E305,E401,E402
scripts/errorlist.py:F405
src/borg/archive.py:E122,E125,E127,E402,E501,F401,F405,W504
src/borg/archiver.py:E125,E126,E127,E128,E501,E722,E731,E741,F401,F405,W504
src/borg/cache.py:E127,E128,E402,E501,E722,W504
Expand Down Expand Up @@ -79,7 +80,7 @@ per_file_ignores =
src/borg/testsuite/crypto.py:E126,E501,E741
src/borg/testsuite/file_integrity.py:F401
src/borg/testsuite/hashindex.py:F401
src/borg/testsuite/helpers.py:E126,E127,E128,E501,F401
src/borg/testsuite/helpers.py:E126,E127,E128,E501,F401,F405
src/borg/testsuite/key.py:E501,F401
src/borg/testsuite/locking.py:E126,E128,E501,E722,F401
src/borg/testsuite/patterns.py:E123
Expand Down
Loading
Loading