mirror of https://github.com/borgbackup/borg.git
update "modern" error RCs (docs and code)
This commit is contained in:
parent
34bbce8e71
commit
9de07ebd46
|
@ -565,92 +565,150 @@ 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
|
||||
Archive {} already exists
|
||||
Archive.DoesNotExist
|
||||
Archive {} does not exist
|
||||
Archive.IncompatibleFilesystemEncodingError
|
||||
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
|
||||
Error rc: 2 traceback: no
|
||||
Error: {}
|
||||
ErrorWithTraceback rc: 2 traceback: yes
|
||||
Error: {}
|
||||
|
||||
ExtensionModuleError rc: 2 traceback: no
|
||||
The Borg binary extension modules do not seem to be properly installed.
|
||||
PythonLibcTooOld rc: 2 traceback: no
|
||||
FATAL: this Python was compiled for a too old (g)libc and misses required functionality.
|
||||
Buffer.MemoryLimitExceeded rc: 2 traceback: no
|
||||
Requested buffer size {} is above the limit of {}.
|
||||
ExtensionModuleError
|
||||
The Borg binary extension modules do not seem to be installed properly
|
||||
IntegrityError
|
||||
Data integrity error: {}
|
||||
NoManifestError
|
||||
Repository has no manifest.
|
||||
PlaceholderError
|
||||
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({}): {}({})
|
||||
KeyfileInvalidError
|
||||
Invalid key file for repository {} found in {}.
|
||||
KeyfileMismatchError
|
||||
Mismatch between repository {} and key file {}.
|
||||
KeyfileNotFoundError
|
||||
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
|
||||
InvalidPlaceholder rc: 6 traceback: no
|
||||
Invalid placeholder "{}" in string: {}
|
||||
|
||||
Repository.AlreadyExists rc: 10 traceback: no
|
||||
A repository already exists at {}.
|
||||
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.
|
||||
UnsupportedPayloadError
|
||||
Unsupported payload type {}. A newer version is required to access this repository.
|
||||
NotABorgKeyFile
|
||||
|
||||
Archive.AlreadyExists rc: 30 traceback: no
|
||||
Archive {} already exists
|
||||
Archive.DoesNotExist rc: 31 traceback: no
|
||||
Archive {} does not exist
|
||||
Archive.IncompatibleFilesystemEncodingError rc: 32 traceback: no
|
||||
Failed to encode filename "{}" into file system encoding "{}". Consider configuring the LANG environment variable.
|
||||
|
||||
KeyfileInvalidError rc: 40 traceback: no
|
||||
Invalid key file for repository {} found in {}.
|
||||
KeyfileMismatchError rc: 41 traceback: no
|
||||
Mismatch between repository {} and key file {}.
|
||||
KeyfileNotFoundError rc: 42 traceback: no
|
||||
No key file for repository {} found in {}.
|
||||
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.
|
||||
UnsupportedKeyFormatError rc: 49 traceback:no
|
||||
Your borg key is stored in an unsupported format. Try using a newer version of borg.
|
||||
|
||||
|
||||
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: {}
|
||||
|
||||
|
||||
Operations
|
||||
- cache.begin_transaction
|
||||
|
|
|
@ -36,6 +36,9 @@ General:
|
|||
Main usecase for this is to automate fully ``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
|
||||
|
|
|
@ -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)
|
||||
=========== =======
|
||||
|
||||
|
|
|
@ -454,15 +454,21 @@ def archive_put_items(chunk_ids, *, repo_objs, cache=None, stats=None, add_refer
|
|||
|
||||
|
||||
class Archive:
|
||||
class DoesNotExist(Error):
|
||||
"""Archive {} does not exist"""
|
||||
|
||||
class AlreadyExists(Error):
|
||||
"""Archive {} already exists"""
|
||||
|
||||
exit_mcode = 30
|
||||
|
||||
class DoesNotExist(Error):
|
||||
"""Archive {} does not exist"""
|
||||
|
||||
exit_mcode = 31
|
||||
|
||||
class IncompatibleFilesystemEncodingError(Error):
|
||||
"""Failed to encode filename "{}" into file system encoding "{}". Consider configuring the LANG environment variable."""
|
||||
|
||||
exit_mcode = 32
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
manifest,
|
||||
|
|
|
@ -25,7 +25,7 @@ try:
|
|||
from .. import __version__
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR, EXIT_SIGNAL_BASE
|
||||
from ..helpers import Error, set_ec
|
||||
from ..helpers import Error, CommandError, set_ec, modern_ec
|
||||
from ..helpers import format_file_size
|
||||
from ..helpers import remove_surrogates, text_to_json
|
||||
from ..helpers import DatetimeWrapper, replace_placeholders
|
||||
|
@ -128,11 +128,6 @@ class Archiver(
|
|||
self.prog = prog
|
||||
self.last_checkpoint = time.monotonic()
|
||||
|
||||
def print_error(self, msg, *args):
|
||||
msg = args and msg % args or msg
|
||||
self.exit_code = EXIT_ERROR
|
||||
logger.error(msg)
|
||||
|
||||
def print_warning(self, msg, *args):
|
||||
msg = args and msg % args or msg
|
||||
self.exit_code = EXIT_WARNING # we do not terminate here, so it is a warning
|
||||
|
@ -631,7 +626,7 @@ def main(): # pragma: no cover
|
|||
except argparse.ArgumentTypeError as e:
|
||||
# we might not have logging setup yet, so get out quickly
|
||||
print(str(e), file=sys.stderr)
|
||||
sys.exit(EXIT_ERROR)
|
||||
sys.exit(CommandError.exit_mcode if modern_ec else EXIT_ERROR)
|
||||
except Exception:
|
||||
msg = "Local Exception"
|
||||
tb = f"{traceback.format_exc()}\n{sysinfo()}"
|
||||
|
@ -687,9 +682,9 @@ def main(): # pragma: no cover
|
|||
exit_msg = "terminating with %s status, rc %d"
|
||||
if exit_code == EXIT_SUCCESS:
|
||||
rc_logger.info(exit_msg % ("success", exit_code))
|
||||
elif exit_code == EXIT_WARNING:
|
||||
elif exit_code == EXIT_WARNING or EXIT_WARNING_BASE <= exit_code < EXIT_SIGNAL_BASE:
|
||||
rc_logger.warning(exit_msg % ("warning", exit_code))
|
||||
elif exit_code == EXIT_ERROR:
|
||||
elif exit_code == EXIT_ERROR or EXIT_ERROR_BASE <= exit_code < EXIT_WARNING_BASE:
|
||||
rc_logger.error(exit_msg % ("error", exit_code))
|
||||
elif exit_code >= EXIT_SIGNAL_BASE:
|
||||
rc_logger.error(exit_msg % ("signal", exit_code))
|
||||
|
|
|
@ -2,7 +2,7 @@ import argparse
|
|||
from ._common import with_repository, Highlander
|
||||
from ..archive import ArchiveChecker
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import EXIT_SUCCESS, EXIT_WARNING, EXIT_ERROR
|
||||
from ..helpers import EXIT_SUCCESS, EXIT_WARNING, CancelledByUser, CommandError
|
||||
from ..helpers import yes
|
||||
|
||||
from ..logger import create_logger
|
||||
|
@ -30,22 +30,19 @@ class CheckMixIn:
|
|||
retry=False,
|
||||
env_var_override="BORG_CHECK_I_KNOW_WHAT_I_AM_DOING",
|
||||
):
|
||||
return EXIT_ERROR
|
||||
raise CancelledByUser()
|
||||
if args.repo_only and any((args.verify_data, args.first, args.last, args.match_archives)):
|
||||
self.print_error(
|
||||
raise CommandError(
|
||||
"--repository-only contradicts --first, --last, -a / --match-archives and --verify-data arguments."
|
||||
)
|
||||
return EXIT_ERROR
|
||||
if args.repair and args.max_duration:
|
||||
self.print_error("--repair does not allow --max-duration argument.")
|
||||
return EXIT_ERROR
|
||||
raise CommandError("--repair does not allow --max-duration argument.")
|
||||
if args.max_duration and not args.repo_only:
|
||||
# when doing a partial repo check, we can only check crc32 checksums in segment files,
|
||||
# we can't build a fresh repo index in memory to verify the on-disk index against it.
|
||||
# thus, we should not do an archives check based on a unknown-quality on-disk repo index.
|
||||
# also, there is no max_duration support in the archives check code anyway.
|
||||
self.print_error("--repository-only is required for --max-duration support.")
|
||||
return EXIT_ERROR
|
||||
raise CommandError("--repository-only is required for --max-duration support.")
|
||||
if not args.archives_only:
|
||||
if not repository.check(repair=args.repair, max_duration=args.max_duration):
|
||||
return EXIT_WARNING
|
||||
|
|
|
@ -7,7 +7,7 @@ from ._common import with_repository
|
|||
from ..cache import Cache, assert_secure
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import EXIT_SUCCESS, EXIT_WARNING
|
||||
from ..helpers import Error
|
||||
from ..helpers import Error, CommandError
|
||||
from ..helpers import Location
|
||||
from ..helpers import parse_file_size
|
||||
from ..manifest import Manifest
|
||||
|
@ -99,9 +99,7 @@ class ConfigMixIn:
|
|||
|
||||
if not args.list:
|
||||
if args.name is None:
|
||||
self.print_error("No config key name was provided.")
|
||||
return self.exit_code
|
||||
|
||||
raise CommandError("No config key name was provided.")
|
||||
try:
|
||||
section, name = args.name.split(".")
|
||||
except ValueError:
|
||||
|
|
|
@ -29,6 +29,7 @@ from ..helpers import prepare_subprocess_env
|
|||
from ..helpers import sig_int, ignore_sigint
|
||||
from ..helpers import iter_separated
|
||||
from ..helpers import MakePathSafeAction
|
||||
from ..helpers import Error, CommandError
|
||||
from ..manifest import Manifest
|
||||
from ..patterns import PatternMatcher
|
||||
from ..platform import is_win32
|
||||
|
@ -79,18 +80,15 @@ class CreateMixIn:
|
|||
preexec_fn=None if is_win32 else ignore_sigint,
|
||||
)
|
||||
except (FileNotFoundError, PermissionError) as e:
|
||||
self.print_error("Failed to execute command: %s", e)
|
||||
return self.exit_code
|
||||
raise CommandError("Failed to execute command: %s", e)
|
||||
status = fso.process_pipe(
|
||||
path=path, cache=cache, fd=proc.stdout, mode=mode, user=user, group=group
|
||||
)
|
||||
rc = proc.wait()
|
||||
if rc != 0:
|
||||
self.print_error("Command %r exited with status %d", args.paths[0], rc)
|
||||
return self.exit_code
|
||||
raise CommandError("Command %r exited with status %d", args.paths[0], rc)
|
||||
except BackupOSError as e:
|
||||
self.print_error("%s: %s", path, e)
|
||||
return self.exit_code
|
||||
raise Error("%s: %s", path, e)
|
||||
else:
|
||||
status = "+" # included
|
||||
self.print_file_status(status, path)
|
||||
|
@ -103,8 +101,7 @@ class CreateMixIn:
|
|||
args.paths, stdout=subprocess.PIPE, env=env, preexec_fn=None if is_win32 else ignore_sigint
|
||||
)
|
||||
except (FileNotFoundError, PermissionError) as e:
|
||||
self.print_error("Failed to execute command: %s", e)
|
||||
return self.exit_code
|
||||
raise CommandError("Failed to execute command: %s", e)
|
||||
pipe_bin = proc.stdout
|
||||
else: # args.paths_from_stdin == True
|
||||
pipe_bin = sys.stdin.buffer
|
||||
|
@ -135,8 +132,7 @@ class CreateMixIn:
|
|||
if args.paths_from_command:
|
||||
rc = proc.wait()
|
||||
if rc != 0:
|
||||
self.print_error("Command %r exited with status %d", args.paths[0], rc)
|
||||
return self.exit_code
|
||||
raise CommandError("Command %r exited with status %d", args.paths[0], rc)
|
||||
else:
|
||||
for path in args.paths:
|
||||
if path == "": # issue #5637
|
||||
|
@ -197,7 +193,7 @@ class CreateMixIn:
|
|||
if sig_int:
|
||||
# do not save the archive if the user ctrl-c-ed - it is valid, but incomplete.
|
||||
# we already have a checkpoint archive in this case.
|
||||
self.print_error("Got Ctrl-C / SIGINT.")
|
||||
raise Error("Got Ctrl-C / SIGINT.")
|
||||
else:
|
||||
archive.save(comment=args.comment, timestamp=args.timestamp)
|
||||
args.stats |= args.json
|
||||
|
|
|
@ -13,6 +13,7 @@ from ..helpers import bin_to_hex, prepare_dump_dict
|
|||
from ..helpers import dash_open
|
||||
from ..helpers import StableDict
|
||||
from ..helpers import positive_int_validator, archivename_validator
|
||||
from ..helpers import CommandError, RTError
|
||||
from ..manifest import Manifest
|
||||
from ..platform import get_process_id
|
||||
from ..repository import Repository, LIST_SCAN_LIMIT, TAG_PUT, TAG_DELETE, TAG_COMMIT
|
||||
|
@ -191,8 +192,7 @@ class DebugMixIn:
|
|||
except (ValueError, UnicodeEncodeError):
|
||||
wanted = None
|
||||
if not wanted:
|
||||
self.print_error("search term needs to be hex:123abc or str:foobar style")
|
||||
return EXIT_ERROR
|
||||
raise CommandError("search term needs to be hex:123abc or str:foobar style")
|
||||
|
||||
from ..crypto.key import key_factory
|
||||
|
||||
|
@ -245,13 +245,11 @@ class DebugMixIn:
|
|||
if len(id) != 32: # 256bit
|
||||
raise ValueError("id must be 256bits or 64 hex digits")
|
||||
except ValueError as err:
|
||||
print(f"object id {hex_id} is invalid [{str(err)}].")
|
||||
return EXIT_ERROR
|
||||
raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
|
||||
try:
|
||||
data = repository.get(id)
|
||||
except Repository.ObjectNotFound:
|
||||
print("object %s not found." % hex_id)
|
||||
return EXIT_ERROR
|
||||
raise RTError("object %s not found." % hex_id)
|
||||
with open(args.path, "wb") as f:
|
||||
f.write(data)
|
||||
print("object %s fetched." % hex_id)
|
||||
|
@ -278,8 +276,7 @@ class DebugMixIn:
|
|||
if len(id) != 32: # 256bit
|
||||
raise ValueError("id must be 256bits or 64 hex digits")
|
||||
except ValueError as err:
|
||||
print(f"object id {hex_id} is invalid [{str(err)}].")
|
||||
return EXIT_ERROR
|
||||
raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
|
||||
|
||||
with open(args.object_path, "rb") as f:
|
||||
cdata = f.read()
|
||||
|
@ -306,8 +303,7 @@ class DebugMixIn:
|
|||
if len(id) != 32: # 256bit
|
||||
raise ValueError("id must be 256bits or 64 hex digits")
|
||||
except ValueError as err:
|
||||
print(f"object id {hex_id} is invalid [{str(err)}].")
|
||||
return EXIT_ERROR
|
||||
raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
|
||||
|
||||
with open(args.binary_path, "rb") as f:
|
||||
data = f.read()
|
||||
|
@ -334,8 +330,8 @@ class DebugMixIn:
|
|||
if len(id) != 32: # 256bit
|
||||
raise ValueError("id must be 256bits or 64 hex digits")
|
||||
except ValueError as err:
|
||||
print(f"object id {hex_id} is invalid [{str(err)}].")
|
||||
return EXIT_ERROR
|
||||
raise CommandError(f"object id {hex_id} is invalid [{str(err)}].")
|
||||
|
||||
repository.put(id, data)
|
||||
print("object %s put." % hex_id)
|
||||
repository.commit(compact=False)
|
||||
|
|
|
@ -87,7 +87,7 @@ class DeleteMixIn:
|
|||
uncommitted_deletes = 0 if checkpointed else (uncommitted_deletes + 1)
|
||||
if sig_int:
|
||||
# Ctrl-C / SIGINT: do not checkpoint (commit) again, we already have a checkpoint in this case.
|
||||
self.print_error("Got Ctrl-C / SIGINT.")
|
||||
raise Error("Got Ctrl-C / SIGINT.")
|
||||
elif uncommitted_deletes > 0:
|
||||
checkpoint_func()
|
||||
if args.stats:
|
||||
|
|
|
@ -6,7 +6,7 @@ from ..constants import * # NOQA
|
|||
from ..crypto.key import AESOCBRepoKey, CHPORepoKey, Blake2AESOCBRepoKey, Blake2CHPORepoKey
|
||||
from ..crypto.key import AESOCBKeyfileKey, CHPOKeyfileKey, Blake2AESOCBKeyfileKey, Blake2CHPOKeyfileKey
|
||||
from ..crypto.keymanager import KeyManager
|
||||
from ..helpers import PathSpec
|
||||
from ..helpers import PathSpec, CommandError
|
||||
from ..manifest import Manifest
|
||||
|
||||
from ._common import with_repository
|
||||
|
@ -22,8 +22,7 @@ class KeysMixIn:
|
|||
"""Change repository key file passphrase"""
|
||||
key = manifest.key
|
||||
if not hasattr(key, "change_passphrase"):
|
||||
print("This repository is not encrypted, cannot change the passphrase.")
|
||||
return EXIT_ERROR
|
||||
raise CommandError("This repository is not encrypted, cannot change the passphrase.")
|
||||
key.change_passphrase()
|
||||
logger.info("Key updated")
|
||||
if hasattr(key, "find_key"):
|
||||
|
@ -36,8 +35,7 @@ class KeysMixIn:
|
|||
"""Change repository key location"""
|
||||
key = manifest.key
|
||||
if not hasattr(key, "change_passphrase"):
|
||||
print("This repository is not encrypted, cannot change the key location.")
|
||||
return EXIT_ERROR
|
||||
raise CommandError("This repository is not encrypted, cannot change the key location.")
|
||||
|
||||
if args.key_mode == "keyfile":
|
||||
if isinstance(key, AESOCBRepoKey):
|
||||
|
@ -109,8 +107,7 @@ class KeysMixIn:
|
|||
else:
|
||||
manager.export(args.path)
|
||||
except IsADirectoryError:
|
||||
self.print_error(f"'{args.path}' must be a file, not a directory")
|
||||
return EXIT_ERROR
|
||||
raise CommandError(f"'{args.path}' must be a file, not a directory")
|
||||
return EXIT_SUCCESS
|
||||
|
||||
@with_repository(lock=False, exclusive=False, manifest=False, cache=False)
|
||||
|
@ -119,16 +116,13 @@ class KeysMixIn:
|
|||
manager = KeyManager(repository)
|
||||
if args.paper:
|
||||
if args.path:
|
||||
self.print_error("with --paper import from file is not supported")
|
||||
return EXIT_ERROR
|
||||
raise CommandError("with --paper import from file is not supported")
|
||||
manager.import_paperkey(args)
|
||||
else:
|
||||
if not args.path:
|
||||
self.print_error("input file to import key from expected")
|
||||
return EXIT_ERROR
|
||||
raise CommandError("expected input file to import key from")
|
||||
if args.path != "-" and not os.path.exists(args.path):
|
||||
self.print_error("input file does not exist: " + args.path)
|
||||
return EXIT_ERROR
|
||||
raise CommandError("input file does not exist: " + args.path)
|
||||
manager.import_keyfile(args)
|
||||
return EXIT_SUCCESS
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import os
|
|||
|
||||
from ._common import with_repository, Highlander
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import EXIT_ERROR
|
||||
from ..helpers import RTError
|
||||
from ..helpers import PathSpec
|
||||
from ..helpers import umount
|
||||
from ..manifest import Manifest
|
||||
|
@ -22,16 +22,13 @@ class MountMixIn:
|
|||
from ..fuse_impl import llfuse, BORG_FUSE_IMPL
|
||||
|
||||
if llfuse is None:
|
||||
self.print_error("borg mount not available: no FUSE support, BORG_FUSE_IMPL=%s." % BORG_FUSE_IMPL)
|
||||
return self.exit_code
|
||||
raise RTError("borg mount not available: no FUSE support, BORG_FUSE_IMPL=%s." % BORG_FUSE_IMPL)
|
||||
|
||||
if not os.path.isdir(args.mountpoint):
|
||||
self.print_error(f"{args.mountpoint}: Mountpoint must be an **existing directory**")
|
||||
return self.exit_code
|
||||
raise RTError(f"{args.mountpoint}: Mountpoint must be an **existing directory**")
|
||||
|
||||
if not os.access(args.mountpoint, os.R_OK | os.W_OK | os.X_OK):
|
||||
self.print_error(f"{args.mountpoint}: Mountpoint must be a **writable** directory")
|
||||
return self.exit_code
|
||||
raise RTError(f"{args.mountpoint}: Mountpoint must be a **writable** directory")
|
||||
|
||||
return self._do_mount(args)
|
||||
|
||||
|
@ -46,7 +43,7 @@ class MountMixIn:
|
|||
operations.mount(args.mountpoint, args.options, args.foreground)
|
||||
except RuntimeError:
|
||||
# Relevant error message already printed to stderr by FUSE
|
||||
self.exit_code = EXIT_ERROR
|
||||
raise RTError("FUSE mount failed")
|
||||
return self.exit_code
|
||||
|
||||
def do_umount(self, args):
|
||||
|
|
|
@ -10,7 +10,7 @@ from ._common import with_repository, Highlander
|
|||
from ..archive import Archive, Statistics
|
||||
from ..cache import Cache
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import ArchiveFormatter, interval, sig_int, log_multi, ProgressIndicatorPercent
|
||||
from ..helpers import ArchiveFormatter, interval, sig_int, log_multi, ProgressIndicatorPercent, CommandError, Error
|
||||
from ..manifest import Manifest
|
||||
|
||||
from ..logger import create_logger
|
||||
|
@ -77,12 +77,12 @@ class PruneMixIn:
|
|||
if not any(
|
||||
(args.secondly, args.minutely, args.hourly, args.daily, args.weekly, args.monthly, args.yearly, args.within)
|
||||
):
|
||||
self.print_error(
|
||||
raise CommandError(
|
||||
'At least one of the "keep-within", "keep-last", '
|
||||
'"keep-secondly", "keep-minutely", "keep-hourly", "keep-daily", '
|
||||
'"keep-weekly", "keep-monthly" or "keep-yearly" settings must be specified.'
|
||||
)
|
||||
return self.exit_code
|
||||
|
||||
if args.format is not None:
|
||||
format = args.format
|
||||
elif args.short:
|
||||
|
@ -173,7 +173,7 @@ class PruneMixIn:
|
|||
pi.finish()
|
||||
if sig_int:
|
||||
# Ctrl-C / SIGINT: do not checkpoint (commit) again, we already have a checkpoint in this case.
|
||||
self.print_error("Got Ctrl-C / SIGINT.")
|
||||
raise Error("Got Ctrl-C / SIGINT.")
|
||||
elif uncommitted_deletes > 0:
|
||||
checkpoint_func()
|
||||
if args.stats:
|
||||
|
|
|
@ -3,7 +3,7 @@ import argparse
|
|||
from ._common import with_repository
|
||||
from ..cache import Cache, SecurityManager
|
||||
from ..constants import * # NOQA
|
||||
from ..helpers import EXIT_ERROR
|
||||
from ..helpers import CancelledByUser
|
||||
from ..helpers import format_archive
|
||||
from ..helpers import bin_to_hex
|
||||
from ..helpers import yes
|
||||
|
@ -72,8 +72,7 @@ class RDeleteMixIn:
|
|||
retry=False,
|
||||
env_var_override="BORG_DELETE_I_KNOW_WHAT_I_AM_DOING",
|
||||
):
|
||||
self.exit_code = EXIT_ERROR
|
||||
return self.exit_code
|
||||
raise CancelledByUser()
|
||||
if not dry_run:
|
||||
repository.destroy()
|
||||
logger.info("Repository deleted.")
|
||||
|
|
|
@ -5,7 +5,7 @@ from ._common import build_matcher
|
|||
from ..archive import ArchiveRecreater
|
||||
from ..constants import * # NOQA
|
||||
from ..compress import CompressionSpec
|
||||
from ..helpers import archivename_validator, comment_validator, PathSpec, ChunkerParams
|
||||
from ..helpers import archivename_validator, comment_validator, PathSpec, ChunkerParams, CommandError
|
||||
from ..helpers import timestamp
|
||||
from ..manifest import Manifest
|
||||
|
||||
|
@ -42,8 +42,7 @@ class RecreateMixIn:
|
|||
|
||||
archive_names = tuple(archive.name for archive in manifest.archives.list_considering(args))
|
||||
if args.target is not None and len(archive_names) != 1:
|
||||
self.print_error("--target: Need to specify single archive")
|
||||
return self.exit_code
|
||||
raise CommandError("--target: Need to specify single archive")
|
||||
for name in archive_names:
|
||||
if recreater.is_temporary_archive(name):
|
||||
continue
|
||||
|
|
|
@ -365,20 +365,30 @@ class CacheConfig:
|
|||
class Cache:
|
||||
"""Client Side cache"""
|
||||
|
||||
class RepositoryIDNotUnique(Error):
|
||||
"""Cache is newer than repository - do you have multiple, independently updated repos with same ID?"""
|
||||
|
||||
class RepositoryReplay(Error):
|
||||
"""Cache, or information obtained from the security directory is newer than repository - this is either an attack or unsafe (multiple repos with same ID)"""
|
||||
|
||||
class CacheInitAbortedError(Error):
|
||||
"""Cache initialization aborted"""
|
||||
|
||||
exit_mcode = 60
|
||||
|
||||
class EncryptionMethodMismatch(Error):
|
||||
"""Repository encryption method changed since last access, refusing to continue"""
|
||||
|
||||
exit_mcode = 61
|
||||
|
||||
class RepositoryAccessAborted(Error):
|
||||
"""Repository access aborted"""
|
||||
|
||||
class EncryptionMethodMismatch(Error):
|
||||
"""Repository encryption method changed since last access, refusing to continue"""
|
||||
exit_mcode = 62
|
||||
|
||||
class RepositoryIDNotUnique(Error):
|
||||
"""Cache is newer than repository - do you have multiple, independently updated repos with same ID?"""
|
||||
|
||||
exit_mcode = 63
|
||||
|
||||
class RepositoryReplay(Error):
|
||||
"""Cache, or information obtained from the security directory is newer than repository - this is either an attack or unsafe (multiple repos with same ID)"""
|
||||
|
||||
exit_mcode = 64
|
||||
|
||||
@staticmethod
|
||||
def break_lock(repository, path=None):
|
||||
|
|
|
@ -114,10 +114,11 @@ FILES_CACHE_MODE_UI_DEFAULT = "ctime,size,inode" # default for "borg create" co
|
|||
FILES_CACHE_MODE_DISABLED = "d" # most borg commands do not use the files cache at all (disable)
|
||||
|
||||
# return codes returned by borg command
|
||||
# when borg is killed by signal N, rc = 128 + N
|
||||
EXIT_SUCCESS = 0 # everything done, no problems
|
||||
EXIT_WARNING = 1 # reached normal end of operation, but there were issues
|
||||
EXIT_ERROR = 2 # terminated abruptly, did not reach end of operation
|
||||
EXIT_WARNING = 1 # reached normal end of operation, but there were issues (generic warning)
|
||||
EXIT_ERROR = 2 # terminated abruptly, did not reach end of operation (generic error)
|
||||
EXIT_ERROR_BASE = 3 # specific error codes are 3..99 (enabled by BORG_EXIT_CODES=modern)
|
||||
EXIT_WARNING_BASE = 100 # specific warning codes are 100..127 (enabled by BORG_EXIT_CODES=modern)
|
||||
EXIT_SIGNAL_BASE = 128 # terminated due to signal, rc = 128 + sig_no
|
||||
|
||||
ISO_FORMAT_NO_USECS = "%Y-%m-%dT%H:%M:%S"
|
||||
|
|
|
@ -124,6 +124,8 @@ SUPPORTED_ALGORITHMS = {
|
|||
class FileIntegrityError(IntegrityError):
|
||||
"""File failed integrity check: {}"""
|
||||
|
||||
exit_mcode = 91
|
||||
|
||||
|
||||
class IntegrityCheckedFile(FileLikeWrapper):
|
||||
def __init__(self, path, write, filename=None, override_fd=None, integrity_data=None):
|
||||
|
|
|
@ -38,30 +38,44 @@ AUTHENTICATED_NO_KEY = "authenticated_no_key" in workarounds
|
|||
class UnsupportedPayloadError(Error):
|
||||
"""Unsupported payload type {}. A newer version is required to access this repository."""
|
||||
|
||||
exit_mcode = 48
|
||||
|
||||
|
||||
class UnsupportedManifestError(Error):
|
||||
"""Unsupported manifest envelope. A newer version is required to access this repository."""
|
||||
|
||||
exit_mcode = 27
|
||||
|
||||
|
||||
class KeyfileNotFoundError(Error):
|
||||
"""No key file for repository {} found in {}."""
|
||||
|
||||
exit_mcode = 42
|
||||
|
||||
|
||||
class KeyfileInvalidError(Error):
|
||||
"""Invalid key file for repository {} found in {}."""
|
||||
|
||||
exit_mcode = 40
|
||||
|
||||
|
||||
class KeyfileMismatchError(Error):
|
||||
"""Mismatch between repository {} and key file {}."""
|
||||
|
||||
exit_mcode = 41
|
||||
|
||||
|
||||
class RepoKeyNotFoundError(Error):
|
||||
"""No key entry found in the config of repository {}."""
|
||||
|
||||
exit_mcode = 44
|
||||
|
||||
|
||||
class UnsupportedKeyFormatError(Error):
|
||||
"""Your borg key is stored in an unsupported format. Try using a newer version of borg."""
|
||||
|
||||
exit_mcode = 49
|
||||
|
||||
|
||||
def key_creator(repository, args, *, other_key=None):
|
||||
for key in AVAILABLE_KEY_TYPES:
|
||||
|
|
|
@ -13,20 +13,28 @@ from ..repoobj import RepoObj
|
|||
from .key import CHPOKeyfileKey, RepoKeyNotFoundError, KeyBlobStorage, identify_key
|
||||
|
||||
|
||||
class UnencryptedRepo(Error):
|
||||
"""Keymanagement not available for unencrypted repositories."""
|
||||
class NotABorgKeyFile(Error):
|
||||
"""This file is not a borg key backup, aborting."""
|
||||
|
||||
|
||||
class UnknownKeyType(Error):
|
||||
"""Keytype {0} is unknown."""
|
||||
exit_mcode = 43
|
||||
|
||||
|
||||
class RepoIdMismatch(Error):
|
||||
"""This key backup seems to be for a different backup repository, aborting."""
|
||||
|
||||
exit_mcode = 45
|
||||
|
||||
class NotABorgKeyFile(Error):
|
||||
"""This file is not a borg key backup, aborting."""
|
||||
|
||||
class UnencryptedRepo(Error):
|
||||
"""Key management not available for unencrypted repositories."""
|
||||
|
||||
exit_mcode = 46
|
||||
|
||||
|
||||
class UnknownKeyType(Error):
|
||||
"""Key type {0} is unknown."""
|
||||
|
||||
exit_mcode = 47
|
||||
|
||||
|
||||
def sha256_truncated(data, num):
|
||||
|
|
|
@ -10,7 +10,8 @@ import os
|
|||
from ..constants import * # NOQA
|
||||
from .checks import check_extension_modules, check_python
|
||||
from .datastruct import StableDict, Buffer, EfficientCollectionQueue
|
||||
from .errors import Error, ErrorWithTraceback, IntegrityError, DecompressionError
|
||||
from .errors import Error, ErrorWithTraceback, IntegrityError, DecompressionError, CancelledByUser, CommandError
|
||||
from .errors import RTError, modern_ec
|
||||
from .fs import ensure_dir, join_base_dir, get_socket_filename
|
||||
from .fs import get_security_dir, get_keys_dir, get_base_dir, get_cache_dir, get_config_dir, get_runtime_dir
|
||||
from .fs import dir_is_tagged, dir_is_cachedir, remove_dotdot_prefixes, make_path_safe, scandir_inorder
|
||||
|
|
|
@ -5,6 +5,9 @@ from ..constants import * # NOQA
|
|||
from ..crypto.low_level import IntegrityError as IntegrityErrorBase
|
||||
|
||||
|
||||
modern_ec = os.environ.get("BORG_EXIT_CODES", "legacy") == "modern"
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Error: {}"""
|
||||
|
||||
|
@ -30,9 +33,8 @@ class Error(Exception):
|
|||
@property
|
||||
def exit_code(self):
|
||||
# legacy: borg used to always use rc 2 (EXIT_ERROR) for all errors.
|
||||
# modern: users can opt in to more specific return codes, using BORG_RC_STYLE:
|
||||
modern = os.environ.get("BORG_EXIT_CODES", "legacy") == "modern"
|
||||
return self.exit_mcode if modern else EXIT_ERROR
|
||||
# modern: users can opt in to more specific return codes, using BORG_EXIT_CODES:
|
||||
return self.exit_mcode if modern_ec else EXIT_ERROR
|
||||
|
||||
|
||||
class ErrorWithTraceback(Error):
|
||||
|
@ -45,6 +47,26 @@ class ErrorWithTraceback(Error):
|
|||
class IntegrityError(ErrorWithTraceback, IntegrityErrorBase):
|
||||
"""Data integrity error: {}"""
|
||||
|
||||
exit_mcode = 90
|
||||
|
||||
|
||||
class DecompressionError(IntegrityError):
|
||||
"""Decompression error: {}"""
|
||||
|
||||
exit_mcode = 92
|
||||
|
||||
|
||||
class CancelledByUser(Error):
|
||||
"""Cancelled by user."""
|
||||
|
||||
exit_mcode = 3
|
||||
|
||||
|
||||
class RTError(Error):
|
||||
"""Runtime Error: {}"""
|
||||
|
||||
|
||||
class CommandError(Error):
|
||||
"""Command Error: {}"""
|
||||
|
||||
exit_mcode = 4
|
||||
|
|
|
@ -224,10 +224,14 @@ class DatetimeWrapper:
|
|||
class PlaceholderError(Error):
|
||||
"""Formatting Error: "{}".format({}): {}({})"""
|
||||
|
||||
exit_mcode = 5
|
||||
|
||||
|
||||
class InvalidPlaceholder(PlaceholderError):
|
||||
"""Invalid placeholder "{}" in string: {}"""
|
||||
|
||||
exit_mcode = 6
|
||||
|
||||
|
||||
def format_line(format, data):
|
||||
for _, key, _, conversion in Formatter().parse(format):
|
||||
|
|
|
@ -17,18 +17,26 @@ logger = create_logger()
|
|||
class NoPassphraseFailure(Error):
|
||||
"""can not acquire a passphrase: {}"""
|
||||
|
||||
|
||||
class PassphraseWrong(Error):
|
||||
"""passphrase supplied in BORG_PASSPHRASE, by BORG_PASSCOMMAND or via BORG_PASSPHRASE_FD is incorrect."""
|
||||
exit_mcode = 50
|
||||
|
||||
|
||||
class PasscommandFailure(Error):
|
||||
"""passcommand supplied in BORG_PASSCOMMAND failed: {}"""
|
||||
|
||||
exit_mcode = 51
|
||||
|
||||
|
||||
class PassphraseWrong(Error):
|
||||
"""passphrase supplied in BORG_PASSPHRASE, by BORG_PASSCOMMAND or via BORG_PASSPHRASE_FD is incorrect."""
|
||||
|
||||
exit_mcode = 52
|
||||
|
||||
|
||||
class PasswordRetriesExceeded(Error):
|
||||
"""exceeded the maximum password retries"""
|
||||
|
||||
exit_mcode = 53
|
||||
|
||||
|
||||
class Passphrase(str):
|
||||
@classmethod
|
||||
|
|
|
@ -71,26 +71,38 @@ class TimeoutTimer:
|
|||
class LockError(Error):
|
||||
"""Failed to acquire the lock {}."""
|
||||
|
||||
exit_mcode = 70
|
||||
|
||||
|
||||
class LockErrorT(ErrorWithTraceback):
|
||||
"""Failed to acquire the lock {}."""
|
||||
|
||||
|
||||
class LockTimeout(LockError):
|
||||
"""Failed to create/acquire the lock {} (timeout)."""
|
||||
exit_mcode = 71
|
||||
|
||||
|
||||
class LockFailed(LockErrorT):
|
||||
"""Failed to create/acquire the lock {} ({})."""
|
||||
|
||||
exit_mcode = 72
|
||||
|
||||
|
||||
class LockTimeout(LockError):
|
||||
"""Failed to create/acquire the lock {} (timeout)."""
|
||||
|
||||
exit_mcode = 73
|
||||
|
||||
|
||||
class NotLocked(LockErrorT):
|
||||
"""Failed to release the lock {} (was not locked)."""
|
||||
|
||||
exit_mcode = 74
|
||||
|
||||
|
||||
class NotMyLock(LockErrorT):
|
||||
"""Failed to release the lock {} (was/is locked, but not by me)."""
|
||||
|
||||
exit_mcode = 75
|
||||
|
||||
|
||||
class ExclusiveLock:
|
||||
"""An exclusive Lock based on mkdir fs operation being atomic.
|
||||
|
|
|
@ -18,12 +18,16 @@ from .patterns import get_regex_from_pattern
|
|||
from .repoobj import RepoObj
|
||||
|
||||
|
||||
class MandatoryFeatureUnsupported(Error):
|
||||
"""Unsupported repository feature(s) {}. A newer version of borg is required to access this repository."""
|
||||
|
||||
exit_mcode = 25
|
||||
|
||||
|
||||
class NoManifestError(Error):
|
||||
"""Repository has no manifest."""
|
||||
|
||||
|
||||
class MandatoryFeatureUnsupported(Error):
|
||||
"""Unsupported repository feature(s) {}. A newer version of borg is required to access this repository."""
|
||||
exit_mcode = 26
|
||||
|
||||
|
||||
ArchiveInfo = namedtuple("ArchiveInfo", "name id ts")
|
||||
|
|
|
@ -69,26 +69,38 @@ def os_write(fd, data):
|
|||
class ConnectionClosed(Error):
|
||||
"""Connection closed by remote host"""
|
||||
|
||||
exit_mcode = 80
|
||||
|
||||
|
||||
class ConnectionClosedWithHint(ConnectionClosed):
|
||||
"""Connection closed by remote host. {}"""
|
||||
|
||||
exit_mcode = 81
|
||||
|
||||
|
||||
class PathNotAllowed(Error):
|
||||
"""Repository path not allowed: {}"""
|
||||
|
||||
exit_mcode = 83
|
||||
|
||||
|
||||
class InvalidRPCMethod(Error):
|
||||
"""RPC method {} is not valid"""
|
||||
|
||||
exit_mcode = 82
|
||||
|
||||
|
||||
class UnexpectedRPCDataFormatFromClient(Error):
|
||||
"""Borg {}: Got unexpected RPC data format from client."""
|
||||
|
||||
exit_mcode = 85
|
||||
|
||||
|
||||
class UnexpectedRPCDataFormatFromServer(Error):
|
||||
"""Got unexpected RPC data format from server:\n{}"""
|
||||
|
||||
exit_mcode = 86
|
||||
|
||||
def __init__(self, data):
|
||||
try:
|
||||
data = data.decode()[:128]
|
||||
|
@ -513,6 +525,8 @@ class RemoteRepository:
|
|||
class RPCServerOutdated(Error):
|
||||
"""Borg server is too old for {}. Required version {}"""
|
||||
|
||||
exit_mcode = 84
|
||||
|
||||
@property
|
||||
def method(self):
|
||||
return self.args[0]
|
||||
|
|
|
@ -134,41 +134,61 @@ class Repository:
|
|||
will still get rid of them.
|
||||
"""
|
||||
|
||||
class DoesNotExist(Error):
|
||||
"""Repository {} does not exist."""
|
||||
|
||||
class AlreadyExists(Error):
|
||||
"""A repository already exists at {}."""
|
||||
|
||||
class PathAlreadyExists(Error):
|
||||
"""There is already something at {}."""
|
||||
|
||||
class ParentPathDoesNotExist(Error):
|
||||
"""The parent path of the repo directory [{}] does not exist."""
|
||||
|
||||
class InvalidRepository(Error):
|
||||
"""{} is not a valid repository. Check repo config."""
|
||||
|
||||
class InvalidRepositoryConfig(Error):
|
||||
"""{} does not have a valid configuration. Check repo config [{}]."""
|
||||
exit_mcode = 10
|
||||
|
||||
class CheckNeeded(ErrorWithTraceback):
|
||||
"""Inconsistency detected. Please run "borg check {}"."""
|
||||
|
||||
exit_mcode = 12
|
||||
|
||||
class DoesNotExist(Error):
|
||||
"""Repository {} does not exist."""
|
||||
|
||||
exit_mcode = 13
|
||||
|
||||
class InsufficientFreeSpaceError(Error):
|
||||
"""Insufficient free space to complete transaction (required: {}, available: {})."""
|
||||
|
||||
exit_mcode = 14
|
||||
|
||||
class InvalidRepository(Error):
|
||||
"""{} is not a valid repository. Check repo config."""
|
||||
|
||||
exit_mcode = 15
|
||||
|
||||
class InvalidRepositoryConfig(Error):
|
||||
"""{} does not have a valid configuration. Check repo config [{}]."""
|
||||
|
||||
exit_mcode = 16
|
||||
|
||||
class ObjectNotFound(ErrorWithTraceback):
|
||||
"""Object with key {} not found in repository {}."""
|
||||
|
||||
exit_mcode = 17
|
||||
|
||||
def __init__(self, id, repo):
|
||||
if isinstance(id, bytes):
|
||||
id = bin_to_hex(id)
|
||||
super().__init__(id, repo)
|
||||
|
||||
class InsufficientFreeSpaceError(Error):
|
||||
"""Insufficient free space to complete transaction (required: {}, available: {})."""
|
||||
class ParentPathDoesNotExist(Error):
|
||||
"""The parent path of the repo directory [{}] does not exist."""
|
||||
|
||||
exit_mcode = 18
|
||||
|
||||
class PathAlreadyExists(Error):
|
||||
"""There is already something at {}."""
|
||||
|
||||
exit_mcode = 19
|
||||
|
||||
class StorageQuotaExceeded(Error):
|
||||
"""The storage quota ({}) has been exceeded ({}). Try deleting some archives."""
|
||||
|
||||
exit_mcode = 20
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
path,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import os
|
||||
import pytest
|
||||
|
||||
from ...constants import * # NOQA
|
||||
from . import RK_ENCRYPTION, create_test_files, cmd, generate_archiver_tests
|
||||
from ...helpers import CommandError
|
||||
|
||||
pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,binary") # NOQA
|
||||
|
||||
|
@ -40,5 +42,9 @@ def test_config(archivers, request):
|
|||
cmd(archiver, "config", cfg_key, exit_code=1)
|
||||
|
||||
cmd(archiver, "config", "--list", "--delete", exit_code=2)
|
||||
cmd(archiver, "config", exit_code=2)
|
||||
if archiver.FORK_DEFAULT:
|
||||
cmd(archiver, "config", exit_code=2)
|
||||
else:
|
||||
with pytest.raises(CommandError):
|
||||
cmd(archiver, "config")
|
||||
cmd(archiver, "config", "invalid-option", exit_code=1)
|
||||
|
|
|
@ -16,6 +16,7 @@ from ...constants import * # NOQA
|
|||
from ...manifest import Manifest
|
||||
from ...platform import is_cygwin, is_win32, is_darwin
|
||||
from ...repository import Repository
|
||||
from ...helpers import CommandError
|
||||
from .. import has_lchflags
|
||||
from .. import changedir
|
||||
from .. import (
|
||||
|
@ -360,8 +361,12 @@ def test_create_content_from_command(archivers, request):
|
|||
def test_create_content_from_command_with_failed_command(archivers, request):
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
||||
output = cmd(archiver, "create", "--content-from-command", "test", "--", "sh", "-c", "exit 73;", exit_code=2)
|
||||
assert output.endswith("Command 'sh' exited with status 73" + os.linesep)
|
||||
if archiver.FORK_DEFAULT:
|
||||
output = cmd(archiver, "create", "--content-from-command", "test", "--", "sh", "-c", "exit 73;", exit_code=2)
|
||||
assert output.endswith("Command 'sh' exited with status 73" + os.linesep)
|
||||
else:
|
||||
with pytest.raises(CommandError):
|
||||
cmd(archiver, "create", "--content-from-command", "test", "--", "sh", "-c", "exit 73;")
|
||||
archive_list = json.loads(cmd(archiver, "rlist", "--json"))
|
||||
assert archive_list["archives"] == []
|
||||
|
||||
|
@ -408,8 +413,12 @@ def test_create_paths_from_command(archivers, request):
|
|||
def test_create_paths_from_command_with_failed_command(archivers, request):
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
||||
output = cmd(archiver, "create", "--paths-from-command", "test", "--", "sh", "-c", "exit 73;", exit_code=2)
|
||||
assert output.endswith("Command 'sh' exited with status 73" + os.linesep)
|
||||
if archiver.FORK_DEFAULT:
|
||||
output = cmd(archiver, "create", "--paths-from-command", "test", "--", "sh", "-c", "exit 73;", exit_code=2)
|
||||
assert output.endswith("Command 'sh' exited with status 73" + os.linesep)
|
||||
else:
|
||||
with pytest.raises(CommandError):
|
||||
cmd(archiver, "create", "--paths-from-command", "test", "--", "sh", "-c", "exit 73;")
|
||||
archive_list = json.loads(cmd(archiver, "rlist", "--json"))
|
||||
assert archive_list["archives"] == []
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import pytest
|
|||
from ...constants import * # NOQA
|
||||
from ...crypto.key import AESOCBRepoKey, AESOCBKeyfileKey, CHPOKeyfileKey, Passphrase
|
||||
from ...crypto.keymanager import RepoIdMismatch, NotABorgKeyFile
|
||||
from ...helpers import EXIT_ERROR
|
||||
from ...helpers import EXIT_ERROR, CommandError
|
||||
from ...helpers import bin_to_hex
|
||||
from ...helpers import msgpack
|
||||
from ...repository import Repository
|
||||
|
@ -170,7 +170,11 @@ def test_key_export_directory(archivers, request):
|
|||
export_directory = archiver.output_path + "/exported"
|
||||
os.mkdir(export_directory)
|
||||
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
||||
cmd(archiver, "key", "export", export_directory, exit_code=EXIT_ERROR)
|
||||
if archiver.FORK_DEFAULT:
|
||||
cmd(archiver, "key", "export", export_directory, exit_code=EXIT_ERROR)
|
||||
else:
|
||||
with pytest.raises(CommandError):
|
||||
cmd(archiver, "key", "export", export_directory)
|
||||
|
||||
|
||||
def test_key_export_qr_directory(archivers, request):
|
||||
|
@ -178,14 +182,22 @@ def test_key_export_qr_directory(archivers, request):
|
|||
export_directory = archiver.output_path + "/exported"
|
||||
os.mkdir(export_directory)
|
||||
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
||||
cmd(archiver, "key", "export", "--qr-html", export_directory, exit_code=EXIT_ERROR)
|
||||
if archiver.FORK_DEFAULT:
|
||||
cmd(archiver, "key", "export", "--qr-html", export_directory, exit_code=EXIT_ERROR)
|
||||
else:
|
||||
with pytest.raises(CommandError):
|
||||
cmd(archiver, "key", "export", "--qr-html", export_directory)
|
||||
|
||||
|
||||
def test_key_import_errors(archivers, request):
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
export_file = archiver.output_path + "/exported"
|
||||
cmd(archiver, "rcreate", KF_ENCRYPTION)
|
||||
cmd(archiver, "key", "import", export_file, exit_code=EXIT_ERROR)
|
||||
if archiver.FORK_DEFAULT:
|
||||
cmd(archiver, "key", "import", export_file, exit_code=EXIT_ERROR)
|
||||
else:
|
||||
with pytest.raises(CommandError):
|
||||
cmd(archiver, "key", "import", export_file)
|
||||
|
||||
with open(export_file, "w") as fd:
|
||||
fd.write("something not a key\n")
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from ...constants import * # NOQA
|
||||
from ...helpers import CancelledByUser
|
||||
from . import create_regular_file, cmd, generate_archiver_tests, RK_ENCRYPTION
|
||||
|
||||
pytest_generate_tests = lambda metafunc: generate_archiver_tests(metafunc, kinds="local,remote,binary") # NOQA
|
||||
|
@ -14,7 +17,11 @@ def test_delete_repo(archivers, request):
|
|||
cmd(archiver, "create", "test", "input")
|
||||
cmd(archiver, "create", "test.2", "input")
|
||||
os.environ["BORG_DELETE_I_KNOW_WHAT_I_AM_DOING"] = "no"
|
||||
cmd(archiver, "rdelete", exit_code=2)
|
||||
if archiver.FORK_DEFAULT:
|
||||
cmd(archiver, "rdelete", exit_code=2)
|
||||
else:
|
||||
with pytest.raises(CancelledByUser):
|
||||
cmd(archiver, "rdelete")
|
||||
assert os.path.exists(archiver.repository_path)
|
||||
os.environ["BORG_DELETE_I_KNOW_WHAT_I_AM_DOING"] = "YES"
|
||||
cmd(archiver, "rdelete")
|
||||
|
|
|
@ -5,6 +5,7 @@ from datetime import datetime
|
|||
import pytest
|
||||
|
||||
from ...constants import * # NOQA
|
||||
from ...helpers import CommandError
|
||||
from .. import changedir, are_hardlinks_supported
|
||||
from . import (
|
||||
_create_test_caches,
|
||||
|
@ -82,8 +83,12 @@ def test_recreate_hardlinked_tags(archivers, request): # test for issue #4911
|
|||
def test_recreate_target_rc(archivers, request):
|
||||
archiver = request.getfixturevalue(archivers)
|
||||
cmd(archiver, "rcreate", RK_ENCRYPTION)
|
||||
output = cmd(archiver, "recreate", "--target=asdf", exit_code=2)
|
||||
assert "Need to specify single archive" in output
|
||||
if archiver.FORK_DEFAULT:
|
||||
output = cmd(archiver, "recreate", "--target=asdf", exit_code=2)
|
||||
assert "Need to specify single archive" in output
|
||||
else:
|
||||
with pytest.raises(CommandError):
|
||||
cmd(archiver, "recreate", "--target=asdf")
|
||||
|
||||
|
||||
def test_recreate_target(archivers, request):
|
||||
|
|
Loading…
Reference in New Issue