def properties(dataset, appended_properties: Optional[list]) -> list: dataset_properties = None try: dataset_properties = pyzfscmds.cmd.zfs_get( dataset, columns=["property", "value"], source=["local", "received"], properties=["all"]) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to get properties of '{dataset}'" }, exit_on_error=True) """ Take each line of output containing properties and convert it to a list of property=value strings """ dp = [line.split() for line in dataset_properties.splitlines()] remove_props = [rp[0] for rp in appended_properties] used_props = ["=".join(p) for p in dp if p[0] not in remove_props] used_props.extend(["=".join(pa) for pa in appended_properties]) return used_props
def get_promote_snapshots(be_pool: str, destroy_dataset: str) -> list: """ Look for clone we need to promote because they're dependent on snapshots """ promote_snaps = None try: promote_snaps = pyzfscmds.cmd.zfs_list( be_pool, recursive=True, columns=['name', 'origin'], zfs_types=['filesystem', 'snapshot', 'volume']) except RuntimeError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to list snapshots for promote in '{be_pool}'.\n{e}" }, exit_on_error=True) split_promote_snaps = zedenv.lib.be.split_zfs_output(promote_snaps) target = re.compile(r'\b' + destroy_dataset + r'(@|/.*@).*' + r'\b') return [ds[0] for ds in split_promote_snaps if target.match(ds[1])]
def __enter__(self): if os.path.exists(self.pidfile): pid = None with open(self.pidfile) as f: pid = self._check() if pid: self.pidfd = None raise ProcessRunningException( f'process already running in {self.pidfile} as {pid}') else: os.remove(self.pidfile) self.pidfd = f if pid: ProcessRunningException( f'process already running in {self.pidfile} as {pid}') try: with open(self.pidfile, 'w+') as f: f.write(str(os.getpid())) except OSError: ZELogger.log( { "level": "EXCEPTION", "message": f"Cannot write to pidfile {self.pidfile}" }, exit_on_error=True) return self
def apply_settings_to_child_datasets(be_child_datasets_list, be_requested, verbose): canmount_setting = "canmount=noauto" for ds in be_child_datasets_list: if be_requested == ds: try: pyzfscmds.cmd.zfs_set(ds, canmount_setting) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to set {canmount_setting} for {ds}\n{e}\n" }, exit_on_error=True) if pyzfscmds.utility.is_clone(ds): try: pyzfscmds.cmd.zfs_promote(ds) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to promote BE {ds}\n{e}\n" }, exit_on_error=True) ZELogger.verbose_log( { "level": "INFO", "message": f"Promoted {ds}.\n" }, verbose)
def plugin_property_error(self, prop): ZELogger.log({ "level": "EXCEPTION", "message": (f"To use the {self.bootloader} plugin, use default{prop}, or set props\n" f"To set it use the command (replacing with your pool and dataset)\n'" f"zfs set org.zedenv:{prop}='<new mount>' zpool/ROOT/default\n") }, exit_on_error=True)
def modify_bootloader(self, temp_boot: str): real_kernel_dir = os.path.join(self.zedenv_properties["boot"], "env") temp_kernel_dir = os.path.join(temp_boot, "env") real_old_dataset_kernel = os.path.join(real_kernel_dir, self.old_entry) temp_new_dataset_kernel = os.path.join(temp_kernel_dir, self.new_entry) if not os.path.isdir(real_old_dataset_kernel): ZELogger.log({ "level": "INFO", "message": (f"No directory for Boot environments kernels found at " f"'{real_old_dataset_kernel}', creating empty directory." f"Don't forget to add your kernel to " f"{real_kernel_dir}/zedenv-{self.boot_environment}.") }) if not self.noop: try: os.makedirs(temp_new_dataset_kernel) except PermissionError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Require Privileges to write to {temp_new_dataset_kernel}\n{e}" }, exit_on_error=True) except OSError as os_err: ZELogger.log({ "level": "EXCEPTION", "message": os_err }, exit_on_error=True) else: if not self.noop: try: shutil.copytree(real_old_dataset_kernel, temp_new_dataset_kernel) except PermissionError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Require Privileges to write to {temp_new_dataset_kernel}\n{e}" }, exit_on_error=True) except IOError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"IOError writing to {temp_new_dataset_kernel}\n{e}" }, exit_on_error=True)
def plugin_property_error(self, prop): ZELogger.log({ "level": "EXCEPTION", "message": (f"To use the {self.bootloader} plugin, use the default setting '{prop}', " f"or set a different value\n. To set it use the command (replacing with " f"your pool and dataset)\n'zedenv set " f"org.zedenv.{self.bootloader}:{prop}='<new mount>'\n") }, exit_on_error=True)
def cli(verbose: Optional[bool], zedenv_properties: Optional[list]): try: zedenv.lib.check.startup_check() except RuntimeError as err: ZELogger.log({"level": "EXCEPTION", "message": err}, exit_on_error=True) zedenv_set(verbose, zedenv_properties, zedenv.lib.be.root())
def properties(dataset, appended_properties: Optional[list]) -> list: dataset_properties = None try: dataset_properties = pyzfscmds.cmd.zfs_get( dataset, columns=["property", "value"], source=["local", "received"], properties=["all"]) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to get properties of '{dataset}'" }, exit_on_error=True) """ Take each line of output containing properties and convert it to a list of property=value strings """ dp = [line.split() for line in dataset_properties.splitlines()] remove_props = [rp[0] for rp in appended_properties] used_props = ["=".join(p) for p in dp if p[0] not in remove_props] used_props.extend(["=".join(pa) for pa in appended_properties]) # Make sure that the mountpoint is correct even if we are in a chroot environment. # In this case, the ZFS pool is mounted with an alternative root (e.g. to `/mnt`). rpool = zedenv.lib.be.dataset_pool(dataset) altroot = None try: altroot = pyzfscmds.cmd.zpool_get(rpool, columns=["value"], properties=["altroot"]) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to get properties of '{dataset}'" }, exit_on_error=True) if altroot.strip() != '-': # Search and remove the alternative root at the beginning of the mountpoint for i, p in enumerate(used_props): prop, val = p.split("=") if prop != "mountpoint": continue alt_len, mp_len = len(altroot), len(val) if (mp_len >= alt_len) and (val[:alt_len] == altroot): mountpoint = "mountpoint=/" if mp_len != alt_len: mountpoint = mountpoint + val[alt_len:] used_props[i] = mountpoint return used_props
def post_activate(self): canmount_setting = "canmount=noauto" if self.zfs_be else "canmount=on" try: pyzfscmds.cmd.zfs_set(f"{self.be_root}/{self.boot_environment}", canmount_setting) except RuntimeError: ZELogger.log({ "level": "EXCEPTION", "message": f"Failed to set {canmount_setting} for {ds}\n{e}\n" }, exit_on_error=True)
def cli(boot_environment: str, mountpoint: Optional[list], verbose: Optional[bool]): try: zedenv.lib.check.startup_check() except RuntimeError as err: ZELogger.log({ "level": "EXCEPTION", "message": err }, exit_on_error=True) be_root = zedenv.lib.be.root() dataset_mountpoint = pyzfscmds.system.agnostic.dataset_mountpoint( f"{be_root}/{boot_environment}") if not pyzfscmds.utility.dataset_exists(f"{be_root}/{boot_environment}"): ZELogger.log( { "level": "EXCEPTION", "message": f"Boot environment doesn't exist {boot_environment}.\n" }, exit_on_error=True) if dataset_mountpoint: if dataset_mountpoint == "/": ZELogger.log( { "level": "EXCEPTION", "message": f"Cannot Mount root dataset.\n" }, exit_on_error=True) ZELogger.log( { "level": "EXCEPTION", "message": f"Dataset already mounted to {dataset_mountpoint}\n" }, exit_on_error=True) real_mountpoint = None if mountpoint: if len(mountpoint) > 1: ZELogger.log( { "level": "EXCEPTION", "message": f"Boot environments can only view mounted to one location at once.\n" }, exit_on_error=True) real_mountpoint = mountpoint[0] zedenv_mount(boot_environment, real_mountpoint, verbose, be_root)
def _loader_replace(self, configs: list): be_dataset = f"{self.be_root}/{self.boot_environment}" target = re.compile(r'^vfs.root.mountfrom=.*$') for c in configs: with open(c, "r") as loader_conf: conf_list = loader_conf.readlines() line_nums = [ l for l, val in enumerate(conf_list) if target.search(val) ] for lnum in line_nums: conf_list[lnum] = f"vfs.root.mountfrom={be_dataset}\n" if not self.noop: if os.path.isfile(c): ZELogger.verbose_log( { "level": "INFO", "message": (f"File {c} already exists, backed up to " f"'{c}.bak' and replaced.\n") }, self.verbose) if os.path.isfile(f"{c}.bak"): try: os.remove(f"{c}.bak") except PermissionError: ZELogger.log( { "level": "EXCEPTION", "message": (f"Require Privileges to remove " f"'{c}.bak'\n") }, exit_on_error=True) try: shutil.move(c, f"{c}.bak") except PermissionError: ZELogger.log( { "level": "EXCEPTION", "message": (f"Require Privileges to write to " f"'{c}.bak'\n") }, exit_on_error=True) with open(c, "w") as loader_conf: loader_conf.writelines(conf_list)
def cli(boot_environment: str, verbose: Optional[bool]): try: zedenv.lib.check.startup_check() except RuntimeError as err: ZELogger.log({"level": "EXCEPTION", "message": err}, exit_on_error=True) be_root = zedenv.lib.be.root() dataset_mountpoint = pyzfscmds.system.agnostic.dataset_mountpoint( f"{be_root}/{boot_environment}") if not pyzfscmds.utility.dataset_exists(f"{be_root}/{boot_environment}"): ZELogger.log({ "level": "EXCEPTION", "message": f"Boot environment doesn't exist {boot_environment}.\n" }, exit_on_error=True) if dataset_mountpoint == "/": ZELogger.log({ "level": "EXCEPTION", "message": f"Cannot Unmount root dataset.\n" }, exit_on_error=True) if not dataset_mountpoint: ZELogger.log({ "level": "EXCEPTION", "message": f"Boot environment already un-mounted\n" }, exit_on_error=True) zedenv_umount(boot_environment, verbose, be_root)
def check_zedenv_properties(self): """ Get zedenv properties in format: {"property": <property val>} If prop unset, leave default """ for prop in self.zedenv_properties: prop_val = zedenv.lib.be.get_property( "/".join([self.be_root, self.boot_environment]), f"org.zedenv:{prop}") if prop_val is not None and prop_val != "-": self.zedenv_properties[prop] = prop_val ZELogger.log({"level": "INFO", "message": f"Found: {prop}"})
def cli(boot_environment: str, verbose: Optional[bool], bootloader: Optional[str], noconfirm: Optional[bool], noop: Optional[bool]): try: zedenv.lib.check.startup_check() except RuntimeError as err: ZELogger.log({ "level": "EXCEPTION", "message": err }, exit_on_error=True) try: with zedenv.lib.check.Pidfile(): boot_environment_root = zedenv.lib.be.root() bootloader_set = zedenv.lib.be.get_property( boot_environment_root, "org.zedenv:bootloader") if not bootloader and bootloader_set: if bootloader_set != '-': bootloader = bootloader_set if not bootloader: ZELogger.log({ "level": "WARNING", "message": ("WARNING: Running activate without a bootloader. " "Re-run with a default bootloader, or with the " "'--bootloader/-b' flag. If you plan to manually edit your " "bootloader config this message can safely be ignored.\n") }) if noconfirm: sys.exit( "The '--noconfirm/-y' flag requires the bootloader option " "'--bootloader/-b'.") zedenv_activate(boot_environment, boot_environment_root, verbose, bootloader, noconfirm, noop) except IOError as e: if e[0] == errno.EPERM: ZELogger.log( { "level": "EXCEPTION", "message": "You need root permissions to activate" }, exit_on_error=True) except zedenv.lib.check.ProcessRunningException as pr: ZELogger.log( { "level": "EXCEPTION", "message": f"Already running activate.\n {pr}" }, exit_on_error=True)
def zedenv_set(verbose: Optional[bool], zedenv_properties: Optional[list], be_root: str): for prop in zedenv_properties: try: pyzfscmds.cmd.zfs_set(be_root, prop) except RuntimeError: ZELogger.log({ "level": "EXCEPTION", "message": f"Failed to set zedenv property '{prop}'\n" }, exit_on_error=True) if verbose: ZELogger.verbose_log({ "level": "INFO", "message": f"Set '{prop}' successfully" }, verbose)
def cli(boot_environment: str, verbose: Optional[bool], existing: Optional[str]): try: zedenv.lib.check.startup_check() except RuntimeError as err: ZELogger.log({ "level": "EXCEPTION", "message": err }, exit_on_error=True) parent_dataset = zedenv.lib.be.root() root_dataset = pyzfscmds.system.agnostic.mountpoint_dataset("/") zedenv_create(parent_dataset, root_dataset, boot_environment, verbose, existing)
def __init__(self, zedenv_data: dict): for k in zedenv_data: if k not in plugin_config.allowed_keys: raise ValueError(f"Type {k} is not in allowed keys") self.boot_environment = zedenv_data['boot_environment'] self.old_boot_environment = zedenv_data['old_boot_environment'] self.bootloader = zedenv_data['bootloader'] self.verbose = zedenv_data['verbose'] self.noconfirm = zedenv_data['noconfirm'] self.noop = zedenv_data['noop'] self.be_root = zedenv_data['boot_environment_root'] self.env_dir = "env" self.boot_mountpoint = "/boot" self.entry_prefix = "zedenv" self.old_entry = f"{self.entry_prefix}-{self.old_boot_environment}" self.new_entry = f"{self.entry_prefix}-{self.boot_environment}" esp = zedenv.lib.be.get_property( "/".join([self.be_root, self.boot_environment]), "org.zedenv:esp") if esp is None or esp == "-": self.esp = "/mnt/efi" else: self.esp = esp ZELogger.verbose_log( { "level": "INFO", "message": f"esp set to {esp}\n" }, self.verbose) if not os.path.isdir(self.esp): ZELogger.log( { "level": "EXCEPTION", "message": ("To use the systemdboot plugin, an 'esp' must be mounted at the " "default location of `/mnt/esp`, or at another location, with the " "property 'org.zedenv:esp' set on the dataset. To set it use the " "command (replacing with your pool and dataset)\n'" "zfs set org.zedenv:esp='/mnt/efi' zpool/ROOT/default\n") }, exit_on_error=True)
def zedenv_umount(boot_environment: str, verbose: bool, be_root: str): boot_environment_dataset = f"{be_root}/{boot_environment}" child_datasets_unformatted = None try: child_datasets_unformatted = pyzfscmds.cmd.zfs_list( boot_environment_dataset, sort_properties_descending=['name'], recursive=True, columns=['name']) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to get list of datasets for '{boot_environment}'.\n{e}" }, exit_on_error=True) for d in zedenv.lib.be.split_zfs_output(child_datasets_unformatted): mountpoint = pyzfscmds.system.agnostic.dataset_mountpoint(d[0]) if mountpoint: try: zedenv.lib.system.umount(mountpoint) except RuntimeError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed Un-mounting child dataset from '{mountpoint}'.\n{e}" }, exit_on_error=True) ZELogger.verbose_log( { "level": "INFO", "message": f"Unmounted {d[0]} from {mountpoint}.\n" }, verbose) else: ZELogger.verbose_log( { "level": "INFO", "message": f"Child dataset {d[0]} wasn't mounted, won't unmount.\n" }, verbose)
def cli( boot_environment: str, verbose: Optional[bool], # unmount: Optional[bool], noconfirm: Optional[bool], noop: Optional[bool]): try: zedenv.lib.check.startup_check() except RuntimeError as err: ZELogger.log({ "level": "EXCEPTION", "message": err }, exit_on_error=True) zedenv_destroy(boot_environment, zedenv.lib.be.root(), pyzfscmds.system.agnostic.mountpoint_dataset("/"), verbose, noconfirm, noop)
def cli(zedenv_properties: Optional[list], scripting: Optional[bool], recursive: Optional[bool], defaults: Optional[bool]): try: zedenv.lib.check.startup_check() except RuntimeError as err: ZELogger.log({"level": "EXCEPTION", "message": err}, exit_on_error=True) formatted_list_entries = zedenv_get(zedenv_properties, scripting, recursive, defaults, zedenv.lib.be.root()) for k in formatted_list_entries: ZELogger.log({"level": "INFO", "message": k})
def snapshot(boot_environment_name, boot_environment_root, snap_prefix: Optional[str] = None, snap_suffix_time_format: str = "%Y-%m-%d-%H-%f") -> str: """ Recursively Snapshot BE :param boot_environment_name: Name of BE to snapshot. :param boot_environment_root: Root dataset for BEs. :param snap_prefix: Prefix on snapshot names. :param snap_suffix_time_format: Suffix on snapshot names. :return: Name of snapshot without dataset. """ if "/" in boot_environment_name: ZELogger.log( { "level": "EXCEPTION", "message": ("Failed to get snapshot.\n", "Existing boot environment name ", f"{boot_environment_name} should not contain '/'") }, exit_on_error=True) dataset_name = f"{boot_environment_root}/{boot_environment_name}" with zedenv.lib.system.setlocale(): suffix_time = datetime.datetime.now().strftime(snap_suffix_time_format) full_snap_suffix = f"{snap_prefix}-{suffix_time}" if snap_prefix else suffix_time try: pyzfscmds.cmd.zfs_snapshot(dataset_name, full_snap_suffix, recursive=True) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to create snapshot: '{dataset_name}@{full_snap_suffix}'" }, exit_on_error=True) return full_snap_suffix
def activate_boot_environment(be_requested: str, dataset_mountpoint: Optional[str], verbose: Optional[bool], noop: Optional[bool], bootloader_plugin): if dataset_mountpoint != "/": if dataset_mountpoint: ZELogger.verbose_log( { "level": "INFO", "message": f"Unmounting {dataset_mountpoint}.\n" }, verbose) if not noop: try: zedenv.lib.system.umount(dataset_mountpoint) except RuntimeError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed unmounting dataset {be_requested}\n{e}\n" }, exit_on_error=True) mount_and_modify_dataset(be_requested, pre_mount_properties=["canmount=noauto"], post_mount_properties=["mountpoint=/"], verbose=verbose, noop=noop, plugin=bootloader_plugin) if not noop: try: pyzfscmds.cmd.zpool_set(zedenv.lib.be.dataset_pool(be_requested), f"bootfs={be_requested}") except RuntimeError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to set bootfs to {be_requested}\n{e}\n" }, exit_on_error=True)
def cli(boot_environment: str, verbose: Optional[bool], bootloader: Optional[str], noconfirm: Optional[bool], noop: Optional[bool]): if noconfirm and not bootloader: sys.exit( "The '--noconfirm/-y' flag requires the bootloader option '--bootloader/-b'." ) try: zedenv.lib.check.startup_check() except RuntimeError as err: ZELogger.log({ "level": "EXCEPTION", "message": err }, exit_on_error=True) zedenv_activate(boot_environment, zedenv.lib.be.root(), verbose, bootloader, noconfirm, noop)
def get_origin_snapshots(destroy_dataset: str) -> list: origin_all_snaps = None try: origin_all_snaps = pyzfscmds.cmd.zfs_list( destroy_dataset, recursive=True, columns=['origin'], zfs_types=['filesystem', 'snapshot', 'volume']) except RuntimeError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to list origin snapshots for '{destroy_dataset}'.\n{e}" }, exit_on_error=True) split_snaps = zedenv.lib.be.split_zfs_output(origin_all_snaps) return [ds[0].rstrip() for ds in split_snaps if ds[0].rstrip() != '-']
def zedenv_list( verbose: Optional[bool], # alldatasets: Optional[bool], spaceused: Optional[bool], scripting: Optional[bool], # snapshots: Optional[bool], origin: Optional[bool], be_root: str): """ Main list command. Separate for testing. """ ZELogger.verbose_log( { "level": "INFO", "message": "Listing Boot Environments:\n" }, verbose) columns = ["name"] # TODO: Complete # if spaceused: # columns.extend(["used", "usedds", "usedbysnapshots", "usedrefreserv", "refer"]) """ TODO: if all_datasets: if snapshots: """ if origin: columns.append("origin") columns.append("creation") boot_environments = configure_boot_environment_list( be_root, columns, scripting) for list_output in boot_environments: ZELogger.log({"level": "INFO", "message": list_output})
def mount_children(child_datasets: list, mountpoint: str, verbose: bool): for cd in child_datasets: if cd['mountpoint'] == "none" or cd['mountpoint'] == "legacy": ZELogger.verbose_log( { "level": "INFO", "message": (f"Skipped mounting dataset {cd['name']} " f"since mountpoint is {cd['mountpoint']}.\n") }, verbose) else: if cd['source'] == 'local': child = pyzfscmds.utility.dataset_child_name( cd['name'], check_exists=False) new_mount = os.path.join(mountpoint, child.lstrip('/')) else: new_mount = os.path.join(mountpoint, cd['mountpoint'].lstrip('/')) if not os.path.exists(new_mount): os.makedirs(new_mount) try: zedenv.lib.system.zfs_manual_mount(cd['name'], new_mount) except RuntimeError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed mounting child dataset to '{new_mount}'.\n{e}" }, exit_on_error=True) ZELogger.verbose_log( { "level": "INFO", "message": f"Mounted dataset {cd['name']} to '{new_mount}'.\n" }, verbose)
def destroy_origin_snapshots(destroy_dataset, be_pool, origin_snaps, noop, verbose): # destroy origin snapshots used by destroy_dataset snapshots = None try: snapshots = pyzfscmds.cmd.zfs_list(be_pool, recursive=True, columns=['name'], zfs_types=['snapshot']) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to list origins snapshots of '{destroy_dataset}'.\n" }, exit_on_error=True) snapshots_list = zedenv.lib.be.split_zfs_output(snapshots) for ors in origin_snaps: for ol in snapshots_list: snap = ol[0].rstrip() if ors == snap: if not noop: try: pyzfscmds.cmd.zfs_destroy_snapshot(snap) except RuntimeError: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to destroy {snap}\n" }, exit_on_error=True) ZELogger.verbose_log( { "level": "INFO", "message": f"Destroyed {snap}.\n" }, verbose)
def disable_children_automount(be_child_datasets: List[str], be_requested: str, boot_environment_root: str, verbose: Optional[bool]): """ Dont run if noop """ for ds in be_child_datasets: if not (be_requested in ds) and not (boot_environment_root == ds): try: pyzfscmds.cmd.zfs_set(ds, "canmount=noauto") except RuntimeError as e: ZELogger.log( { "level": "EXCEPTION", "message": f"Failed to set canmount=noauto on {ds}\n{e}\n" }, exit_on_error=True) ZELogger.verbose_log( { "level": "INFO", "message": f"Disabled automount for {ds}\n" }, verbose)
def recurse_move(self, source, dest, overwrite=False): for tf in os.listdir(source): tf_path_src = os.path.join(source, tf) tf_path_dst = os.path.join(dest, tf) if os.path.isfile(tf_path_src): if os.path.isfile(tf_path_dst) and not overwrite: ZELogger.verbose_log({ "level": "INFO", "message": f"File {tf_path_dst} already exists, will not modify.\n" }, self.verbose) else: try: shutil.copy(tf_path_src, tf_path_dst) except PermissionError: ZELogger.log({ "level": "EXCEPTION", "message": f"Require Privileges to write to '{tf_path_dst}.'\n" }, exit_on_error=True) ZELogger.verbose_log({ "level": "INFO", "message": f"Copied file {tf_path_src} -> {tf_path_dst}\n" }, self.verbose) elif os.path.isdir(tf_path_src): if os.path.isdir(tf_path_dst) and not overwrite: ZELogger.verbose_log({ "level": "INFO", "message": f"Directory {tf_path_dst} already exists, will not modify.\n" }, self.verbose) # Call again, may be empty self.recurse_move(tf_path_src, tf_path_dst, overwrite=overwrite) else: if os.path.isdir(tf_path_dst): shutil.move(tf_path_dst, f"{tf_path_dst}.bak") ZELogger.verbose_log({ "level": "INFO", "message": (f"Directory {tf_path_dst} already exists, " f"creating backup {tf_path_dst}.bak.\n") }, self.verbose) try: shutil.copytree(tf_path_src, tf_path_dst) except PermissionError as e: ZELogger.log({ "level": "EXCEPTION", "message": f"Require Privileges to write to {tf_path_dst}\n{e}" }, exit_on_error=True) except IOError as e: ZELogger.log({ "level": "EXCEPTION", "message": f"IOError writing to {tf_path_dst}\n{e}" }, exit_on_error=True) ZELogger.verbose_log({ "level": "INFO", "message": f"Copied dir {tf_path_src} -> {tf_path_dst}\n" }, self.verbose)