import sys import argparse import configparser from binascii import unhexlify 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, CommandError from ..helpers import Location from ..helpers import parse_file_size from ..manifest import Manifest from ..logger import create_logger logger = create_logger() class ConfigMixIn: @with_repository(exclusive=True, manifest=False) def do_config(self, args, repository): """get, set, and delete values in a repository or cache config file""" def repo_validate(section, name, value=None, check_value=True): if section not in ["repository"]: raise ValueError("Invalid section") if name in ["segments_per_dir", "last_segment_checked"]: if check_value: try: int(value) except ValueError: raise ValueError("Invalid value") from None elif name in ["max_segment_size", "additional_free_space", "storage_quota"]: if check_value: try: parse_file_size(value) except ValueError: raise ValueError("Invalid value") from None if name == "storage_quota": if parse_file_size(value) < parse_file_size("10M"): raise ValueError("Invalid value: storage_quota < 10M") elif name == "max_segment_size": if parse_file_size(value) >= MAX_SEGMENT_SIZE_LIMIT: raise ValueError("Invalid value: max_segment_size >= %d" % MAX_SEGMENT_SIZE_LIMIT) elif name in ["append_only"]: if check_value and value not in ["0", "1"]: raise ValueError("Invalid value") elif name in ["id"]: if check_value: try: bin_id = unhexlify(value) except: # noqa raise ValueError("Invalid value, must be 64 hex digits") from None if len(bin_id) != 32: raise ValueError("Invalid value, must be 64 hex digits") else: raise ValueError("Invalid name") def cache_validate(section, name, value=None, check_value=True): if section not in ["cache"]: raise ValueError("Invalid section") if name in ["previous_location"]: if check_value: Location(value) else: raise ValueError("Invalid name") def list_config(config): default_values = { "version": "1", "segments_per_dir": str(DEFAULT_SEGMENTS_PER_DIR), "max_segment_size": str(MAX_SEGMENT_SIZE_LIMIT), "additional_free_space": "0", "storage_quota": repository.storage_quota, "append_only": repository.append_only, } print("[repository]") for key in [ "version", "segments_per_dir", "max_segment_size", "storage_quota", "additional_free_space", "append_only", "id", ]: value = config.get("repository", key, fallback=False) if value is None: value = default_values.get(key) if value is None: raise Error("The repository config is missing the %s key which has no default value" % key) print(f"{key} = {value}") for key in ["last_segment_checked"]: value = config.get("repository", key, fallback=None) if value is None: continue print(f"{key} = {value}") if not args.list: if args.name is None: raise CommandError("No config key name was provided.") try: section, name = args.name.split(".") except ValueError: section = args.cache and "cache" or "repository" name = args.name if args.cache: manifest = Manifest.load(repository, (Manifest.Operation.WRITE,)) assert_secure(repository, manifest, self.lock_wait) cache = Cache(repository, manifest, lock_wait=self.lock_wait) try: if args.cache: cache.cache_config.load() config = cache.cache_config._config save = cache.cache_config.save validate = cache_validate else: config = repository.config save = lambda: repository.save_config(repository.path, repository.config) # noqa validate = repo_validate if args.delete: validate(section, name, check_value=False) config.remove_option(section, name) if len(config.options(section)) == 0: config.remove_section(section) save() elif args.list: list_config(config) elif args.value: validate(section, name, args.value) if section not in config.sections(): config.add_section(section) config.set(section, name, args.value) save() else: try: print(config.get(section, name)) except (configparser.NoOptionError, configparser.NoSectionError) as e: print(e, file=sys.stderr) return EXIT_WARNING return EXIT_SUCCESS finally: if args.cache: cache.close() def build_parser_config(self, subparsers, common_parser, mid_common_parser): from ._common import process_epilog config_epilog = process_epilog( """ This command gets and sets options in a local repository or cache config file. For security reasons, this command only works on local repositories. To delete a config value entirely, use ``--delete``. To list the values of the configuration file or the default values, use ``--list``. To get and existing key, pass only the key name. To set a key, pass both the key name and the new value. Keys can be specified in the format "section.name" or simply "name"; the section will default to "repository" and "cache" for the repo and cache configs, respectively. By default, borg config manipulates the repository config file. Using ``--cache`` edits the repository cache's config file instead. """ ) subparser = subparsers.add_parser( "config", parents=[common_parser], add_help=False, description=self.do_config.__doc__, epilog=config_epilog, formatter_class=argparse.RawDescriptionHelpFormatter, help="get and set configuration values", ) subparser.set_defaults(func=self.do_config) subparser.add_argument( "-c", "--cache", dest="cache", action="store_true", help="get and set values from the repo cache" ) group = subparser.add_mutually_exclusive_group() group.add_argument( "-d", "--delete", dest="delete", action="store_true", help="delete the key from the config file" ) group.add_argument("-l", "--list", dest="list", action="store_true", help="list the configuration of the repo") subparser.add_argument("name", metavar="NAME", nargs="?", help="name of config key") subparser.add_argument("value", metavar="VALUE", nargs="?", help="new value for key")