|
@@ -6,12 +6,14 @@ Code used to be in borg/helpers.py but was split into the modules in this
|
|
|
package, which are imported into here for compatibility.
|
|
|
"""
|
|
|
import os
|
|
|
+from collections import namedtuple
|
|
|
|
|
|
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, CancelledByUser, CommandError
|
|
|
from .errors import RTError, modern_ec
|
|
|
+from .errors import BorgWarning, FileChangedWarning, BackupExcWarning, IncludePatternNeverMatchedWarning
|
|
|
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
|
|
@@ -44,11 +46,37 @@ from .yes_no import yes, TRUISH, FALSISH, DEFAULTISH
|
|
|
from .msgpack import is_slow_msgpack, is_supported_msgpack, get_limited_unpacker
|
|
|
from . import msgpack
|
|
|
|
|
|
+from ..logger import create_logger
|
|
|
+
|
|
|
+logger = create_logger()
|
|
|
+
|
|
|
+
|
|
|
# generic mechanism to enable users to invoke workarounds by setting the
|
|
|
# BORG_WORKAROUNDS environment variable to a list of comma-separated strings.
|
|
|
# see the docs for a list of known workaround strings.
|
|
|
workarounds = tuple(os.environ.get("BORG_WORKAROUNDS", "").split(","))
|
|
|
|
|
|
+
|
|
|
+# element data type for warnings_list:
|
|
|
+warning_info = namedtuple("warning_info", "wc,msg,args,wt")
|
|
|
+
|
|
|
+"""
|
|
|
+The global warnings_list variable is used to collect warning_info elements while borg is running.
|
|
|
+
|
|
|
+Note: keep this in helpers/__init__.py as the code expects to be able to assign to helpers.warnings_list.
|
|
|
+"""
|
|
|
+warnings_list = []
|
|
|
+
|
|
|
+
|
|
|
+def add_warning(msg, *args, **kwargs):
|
|
|
+ global warnings_list
|
|
|
+ warning_code = kwargs.get("wc", EXIT_WARNING)
|
|
|
+ assert isinstance(warning_code, int)
|
|
|
+ warning_type = kwargs.get("wt", "percent")
|
|
|
+ assert warning_type in ("percent", "curly")
|
|
|
+ warnings_list.append(warning_info(warning_code, msg, args, warning_type))
|
|
|
+
|
|
|
+
|
|
|
"""
|
|
|
The global exit_code variable is used so that modules other than archiver can increase the program exit code if a
|
|
|
warning or error occurred during their operation. This is different from archiver.exit_code, which is only accessible
|
|
@@ -59,13 +87,72 @@ Note: keep this in helpers/__init__.py as the code expects to be able to assign
|
|
|
exit_code = EXIT_SUCCESS
|
|
|
|
|
|
|
|
|
+def classify_ec(ec):
|
|
|
+ if not isinstance(ec, int):
|
|
|
+ raise TypeError("ec must be of type int")
|
|
|
+ if EXIT_SIGNAL_BASE <= ec <= 255:
|
|
|
+ return "signal"
|
|
|
+ elif ec == EXIT_ERROR or EXIT_ERROR_BASE <= ec < EXIT_WARNING_BASE:
|
|
|
+ return "error"
|
|
|
+ elif ec == EXIT_WARNING or EXIT_WARNING_BASE <= ec < EXIT_SIGNAL_BASE:
|
|
|
+ return "warning"
|
|
|
+ elif ec == EXIT_SUCCESS:
|
|
|
+ return "success"
|
|
|
+ else:
|
|
|
+ raise ValueError(f"invalid error code: {ec}")
|
|
|
+
|
|
|
+
|
|
|
+def max_ec(ec1, ec2):
|
|
|
+ """return the more severe error code of ec1 and ec2"""
|
|
|
+ # note: usually, there can be only 1 error-class ec, the other ec is then either success or warning.
|
|
|
+ ec1_class = classify_ec(ec1)
|
|
|
+ ec2_class = classify_ec(ec2)
|
|
|
+ if ec1_class == "signal":
|
|
|
+ return ec1
|
|
|
+ if ec2_class == "signal":
|
|
|
+ return ec2
|
|
|
+ if ec1_class == "error":
|
|
|
+ return ec1
|
|
|
+ if ec2_class == "error":
|
|
|
+ return ec2
|
|
|
+ if ec1_class == "warning":
|
|
|
+ return ec1
|
|
|
+ if ec2_class == "warning":
|
|
|
+ return ec2
|
|
|
+ assert ec1 == ec2 == EXIT_SUCCESS
|
|
|
+ return EXIT_SUCCESS
|
|
|
+
|
|
|
+
|
|
|
def set_ec(ec):
|
|
|
"""
|
|
|
- Sets the exit code of the program, if an exit code higher or equal than this is set, this does nothing. This
|
|
|
- makes EXIT_ERROR override EXIT_WARNING, etc..
|
|
|
+ Sets the exit code of the program to ec IF ec is more severe than the current exit code.
|
|
|
+ """
|
|
|
+ global exit_code
|
|
|
+ exit_code = max_ec(exit_code, ec)
|
|
|
+
|
|
|
|
|
|
- ec: exit code to set
|
|
|
+def get_ec(ec=None):
|
|
|
"""
|
|
|
+ compute the final return code of the borg process
|
|
|
+ """
|
|
|
+ if ec is not None:
|
|
|
+ set_ec(ec)
|
|
|
+
|
|
|
global exit_code
|
|
|
- exit_code = max(exit_code, ec)
|
|
|
- return exit_code
|
|
|
+ exit_code_class = classify_ec(exit_code)
|
|
|
+ if exit_code_class in ("signal", "error", "warning"):
|
|
|
+ # there was a signal/error/warning, return its exit code
|
|
|
+ return exit_code
|
|
|
+ assert exit_code_class == "success"
|
|
|
+ global warnings_list
|
|
|
+ if not warnings_list:
|
|
|
+ # we do not have any warnings in warnings list, return success exit code
|
|
|
+ return exit_code
|
|
|
+ # looks like we have some warning(s)
|
|
|
+ rcs = sorted(set(w_info.wc for w_info in warnings_list))
|
|
|
+ logger.debug(f"rcs: {rcs!r}")
|
|
|
+ if len(rcs) == 1:
|
|
|
+ # easy: there was only one kind of warning, so we can be specific
|
|
|
+ return rcs[0]
|
|
|
+ # there were different kinds of warnings
|
|
|
+ return EXIT_WARNING # generic warning rc, user has to look into the logs
|