Exemple #1
0
 def mid_activate(self, be_mountpoint: str):
     ZELogger.verbose_log(
         {
             "level": "INFO",
             "message": f"Running {self.bootloader} mid activate.\n"
         }, self.verbose)
     self.modify_fstab(be_mountpoint)
Exemple #2
0
    def check_zedenv_properties(self):
        """
        Get zedenv properties in format:
            {"property": <property val>}
        If prop unset, leave default
        """
        for prop in self.zedenv_properties:
            ZELogger.verbose_log(
                {
                    "level":
                    "INFO",
                    "message":
                    f"Checking prop: 'org.zedenv.{self.bootloader}:{prop}'"
                }, self.verbose)

            # Use the properties from the old boot environment
            # as we can not be sure if the new boot environment exists at this point.
            prop_val = zedenv.lib.be.get_property(
                "/".join([self.be_root, self.old_boot_environment]),
                f"org.zedenv.{self.bootloader}:{prop}")

            if prop_val is not None and prop_val != "-":
                self.zedenv_properties[prop] = prop_val
                ZELogger.verbose_log(
                    {
                        "level":
                        "INFO",
                        "message": (f"org.zedenv.{self.bootloader}:{prop}="
                                    f"{self.zedenv_properties[prop]}.\n")
                    }, self.verbose)
Exemple #3
0
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)
Exemple #4
0
    def __init__(self, zedenv_data: dict):

        super().__init__(zedenv_data)

        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}"

        # Set defaults
        for pr in self.allowed_properties:
            self.zedenv_properties[pr["property"]] = pr["default"]

        self.check_zedenv_properties()

        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"esp set to {self.zedenv_properties['esp']}\n"
            }, self.verbose)

        if not os.path.isdir(self.zedenv_properties["esp"]):
            self.plugin_property_error(self.zedenv_properties)
Exemple #5
0
    def post_activate(self):
        ZELogger.verbose_log(
            {
                "level":
                "INFO",
                "message": (f"Creating Temporary working directory. "
                            "No changes will be made until the end of "
                            "the systemd-boot configuration.\n")
            }, self.verbose)

        with tempfile.TemporaryDirectory(prefix="zedenv",
                                         suffix=self.bootloader) as t_esp:
            ZELogger.verbose_log(
                {
                    "level": "INFO",
                    "message": f"Created {t_esp}.\n"
                }, self.verbose)

            self.modify_bootloader(t_esp)

            self.edit_bootloader_entry(t_esp)

            self.recurse_move(t_esp, self.esp)

            self.edit_bootloader_default(t_esp, overwrite=True)
Exemple #6
0
    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)
Exemple #7
0
    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)
Exemple #8
0
    def mid_activate(self, be_mountpoint: str):
        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Running {self.bootloader} mid activate.\n"
            }, self.verbose)

        replace_pattern = r'(^{esp}/{env}/?)(.*)(\s.*{boot}\s.*$)'.format(
            esp=self.zedenv_properties["esp"],
            env=self.env_dir,
            boot=self.boot_mountpoint)

        self.modify_fstab(be_mountpoint, replace_pattern, self.new_entry)
Exemple #9
0
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)
Exemple #10
0
    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)
Exemple #11
0
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)
Exemple #12
0
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)
Exemple #13
0
    def setup_boot_env_tree(self):
        mount_root = os.path.join(self.zedenv_properties["boot"],
                                  self.zfs_env_dir)

        if not os.path.exists(mount_root):
            os.mkdir(mount_root)

        be_list = None
        be_list = zedenv.lib.be.list_boot_environments(self.be_root, ['name'])
        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Going over list {be_list}.\n"
            }, self.verbose)

        for b in be_list:
            if not pyzfscmds.utility.is_snapshot(b['name']):
                be_name = pyzfscmds.utility.dataset_child_name(
                    b['name'], False)

                if pyzfscmds.system.agnostic.dataset_mountpoint(
                        b['name']) == "/":
                    ZELogger.verbose_log(
                        {
                            "level": "INFO",
                            "message":
                            f"Dataset {b['name']} is root, skipping.\n"
                        }, self.verbose)
                else:
                    be_boot_mount = os.path.join(mount_root,
                                                 f"zedenv-{be_name}")
                    ZELogger.verbose_log(
                        {
                            "level": "INFO",
                            "message": f"Setting up {b['name']}.\n"
                        }, self.verbose)

                    if not os.path.exists(be_boot_mount):
                        os.mkdir(be_boot_mount)

                    if not os.listdir(be_boot_mount):
                        zedenv.cli.mount.zedenv_mount(be_name, be_boot_mount,
                                                      self.verbose,
                                                      self.be_root)
                    else:
                        ZELogger.verbose_log(
                            {
                                "level":
                                "WARNING",
                                "message":
                                f"Mount directory {be_boot_mount} wasn't empty, skipping.\n"
                            }, self.verbose)
Exemple #14
0
    def grub_mkconfig(self, location: str):
        env = dict(os.environ, ZPOOL_VDEV_NAME_PATH='1')
        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": (f"Generating "
                            "the GRUB configuration.\n")
            }, self.verbose)

        grub_call = ["grub-mkconfig", "-o", location]

        try:
            grub_output = subprocess.check_call(grub_call,
                                                env=env,
                                                universal_newlines=True,
                                                stderr=subprocess.PIPE)
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"Failed to generate GRUB config.\n{e}\n.")

        return grub_output
Exemple #15
0
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)
Exemple #16
0
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)
Exemple #17
0
    def post_activate(self):
        ZELogger.verbose_log(
            {
                "level":
                "INFO",
                "message": (f"Creating Temporary working directory. "
                            "No changes will be made until the end of "
                            "the GRUB configuration.\n")
            }, self.verbose)

        if not self.bootonzfs:
            with tempfile.TemporaryDirectory(prefix="zedenv",
                                             suffix=self.bootloader) as t_grub:
                ZELogger.verbose_log(
                    {
                        "level": "INFO",
                        "message": f"Created {t_grub}.\n"
                    }, self.verbose)

                self.modify_bootloader(t_grub)
                self.recurse_move(t_grub,
                                  self.zedenv_properties["boot"],
                                  overwrite=False)

        if self.bootonzfs:
            self.setup_boot_env_tree()

        if not self.skip_update_grub:
            try:
                self.grub_mkconfig(self.grub_cfg_path)
            except RuntimeError as e:
                ZELogger.verbose_log(
                    {
                        "level":
                        "INFO",
                        "message":
                        f"During 'post activate', 'grub-mkconfig' failed with:\n{e}.\n"
                    }, self.verbose)
            else:
                ZELogger.verbose_log(
                    {
                        "level":
                        "INFO",
                        "message":
                        f"Generated GRUB menu successfully at {self.grub_cfg_path}.\n"
                    }, self.verbose)

        if self.bootonzfs and not self.skip_cleanup:
            self.teardown_boot_env_tree()
Exemple #18
0
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})
Exemple #19
0
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)
Exemple #20
0
    def post_destroy(self, target):
        real_kernel_dir = os.path.join(self.zedenv_properties["esp"],
                                       self.env_dir)
        dataset_kernels = os.path.join(real_kernel_dir,
                                       f"{self.entry_prefix}-{target}")

        # if not self.noop:
        if os.path.exists(dataset_kernels):
            shutil.rmtree(dataset_kernels)
            ZELogger.verbose_log(
                {
                    "level": "INFO",
                    "message": f"Removed {dataset_kernels}.\n"
                }, self.verbose)

        real_entries_dir = os.path.join(self.zedenv_properties["esp"],
                                        "loader/entries")
        real_bootloader_file = os.path.join(real_entries_dir,
                                            f"zedenv-{target}.conf")

        if os.path.isfile(real_bootloader_file):
            try:
                os.remove(real_bootloader_file)
            except PermissionError:
                ZELogger.log(
                    {
                        "level":
                        "EXCEPTION",
                        "message": (f"Require Privileges to remove "
                                    f"'{real_bootloader_file}'\n")
                    },
                    exit_on_error=True)
            ZELogger.verbose_log(
                {
                    "level": "INFO",
                    "message": f"Removed {real_bootloader_file}.\n"
                }, self.verbose)
Exemple #21
0
def promote_origins(destroy_dataset, be_pool, origin_snaps, noop, verbose):
    # promote dependents of origins used by destroy_dataset
    origins = None
    try:
        origins = pyzfscmds.cmd.zfs_list(
            be_pool,
            recursive=True,
            columns=['name', 'origin'],
            zfs_types=['filesystem', 'snapshot', 'volume'])
    except RuntimeError:
        ZELogger.log(
            {
                "level": "EXCEPTION",
                "message": f"Failed to list origins of '{destroy_dataset}'.\n"
            },
            exit_on_error=True)
    origins_list = zedenv.lib.be.split_zfs_output(origins)

    for ors in origin_snaps:
        for ol in origins_list:
            if ors == ol[1].rstrip():
                if not noop:
                    try:
                        pyzfscmds.cmd.zfs_promote(ol[0].rstrip())
                    except RuntimeError:
                        ZELogger.log(
                            {
                                "level": "EXCEPTION",
                                "message":
                                f"Failed to promote {ol[0].rstrip()}\n"
                            },
                            exit_on_error=True)
                ZELogger.verbose_log(
                    {
                        "level": "INFO",
                        "message": f"Promoted {ol[0].rstrip()}.\n"
                    }, verbose)
Exemple #22
0
    def edit_bootloader_default(self, temp_esp: str, overwrite: bool):
        real_loader_dir_path = os.path.join(self.esp, "loader")
        temp_loader_dir_path = os.path.join(temp_esp, "loader")

        real_loader_conf_path = os.path.join(real_loader_dir_path,
                                             "loader.conf")
        temp_loader_conf_path = os.path.join(temp_loader_dir_path,
                                             "loader.conf")

        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Updating {real_loader_conf_path}\n"
            }, self.verbose)

        if not os.path.isdir(temp_loader_dir_path):
            try:
                os.makedirs(temp_loader_dir_path)
            except PermissionError as e:
                ZELogger.log(
                    {
                        "level":
                        "EXCEPTION",
                        "message":
                        f"Require Privileges to write to {temp_loader_dir_path}\n{e}"
                    },
                    exit_on_error=True)
            except OSError as os_err:
                ZELogger.log({
                    "level": "EXCEPTION",
                    "message": os_err
                },
                             exit_on_error=True)

        if not os.path.isfile(real_loader_conf_path):
            ZELogger.log(
                {
                    "level": "EXCEPTION",
                    "message": f"Missing file: {real_loader_conf_path}\n"
                },
                exit_on_error=True)

        try:
            shutil.copy(real_loader_conf_path, temp_loader_conf_path)
        except PermissionError as e:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"Require Privileges to write to '{temp_loader_conf_path}'\n{e}"
                },
                exit_on_error=True)
        except IOError as e:
            ZELogger.log(
                {
                    "level": "EXCEPTION",
                    "message":
                    f"IOError writing to '{temp_loader_conf_path}'\n{e}"
                },
                exit_on_error=True)

        with open(temp_loader_conf_path, "r") as loader_conf:
            conf_list = loader_conf.readlines()

        line_num = next((l for l, val in enumerate(conf_list)
                         if val.split(' ', 1)[0] == "default"), None)

        if line_num:
            conf_list[line_num] = f"default    {self.new_entry}\n"

        if not self.noop:
            if os.path.isfile(real_loader_conf_path):
                ZELogger.verbose_log(
                    {
                        "level":
                        "INFO",
                        "message":
                        (f"File {real_loader_conf_path} already exists, backed up to "
                         f"'{real_loader_conf_path}.bak' and replaced.\n")
                    }, self.verbose)
                if os.path.isfile(f"{real_loader_conf_path}.bak"):
                    try:
                        os.remove(f"{real_loader_conf_path}.bak")
                    except PermissionError:
                        ZELogger.log(
                            {
                                "level":
                                "EXCEPTION",
                                "message": (f"Require Privileges to remove "
                                            f"'{real_loader_conf_path}.bak'\n")
                            },
                            exit_on_error=True)
                try:
                    shutil.move(real_loader_conf_path,
                                f"{real_loader_conf_path}.bak")
                except PermissionError:
                    ZELogger.log(
                        {
                            "level":
                            "EXCEPTION",
                            "message": (f"Require Privileges to write to "
                                        f"'{real_loader_conf_path}.bak'\n")
                        },
                        exit_on_error=True)

            with open(real_loader_conf_path, "w") as loader_conf:
                loader_conf.writelines(conf_list)

            if not self.noconfirm:
                if click.confirm(
                        "Would you like to edit the generated 'loader.conf'?",
                        default=True):
                    click.edit(filename=real_loader_conf_path)
Exemple #23
0
def zedenv_mount(boot_environment: str, mountpoint: Optional[str],
                 verbose: bool, be_root: str):
    """
    Create a temporary directory and mount a boot environment.
    If an extra argument is given, mount the boot environment at the given mountpoint.
    """

    ZELogger.verbose_log(
        {
            "level": "INFO",
            "message": f"Mounting boot environment '{boot_environment}'.\n"
        }, verbose)

    if not mountpoint:
        mountpoint_used = tempfile.mkdtemp(suffix=f"-{boot_environment}",
                                           prefix="zedenv-")
        ZELogger.verbose_log(
            {
                "level":
                "INFO",
                "message":
                f"No mountpoint given, using a temporary directory {mountpoint_used}.\n"
            }, verbose)
    else:
        mountpoint_used = mountpoint[0]
        if os.path.ismount(mountpoint_used):
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"There is already a file system mounted at {mountpoint_used}"
                },
                exit_on_error=True)

        if not os.path.isdir(mountpoint_used):
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    (f"The path'{mountpoint_used}' is not a directory, "
                     "cannot be used as mountpoint.\n")
                },
                exit_on_error=True)
        ZELogger.verbose_log(
            {
                "level":
                "INFO",
                "message":
                f"Mountpoint '{mountpoint_used}' given, using as mountpoint.\n"
            }, verbose)

    be_dataset = f"{be_root}/{boot_environment}"

    try:
        zedenv.lib.system.zfs_manual_mount(be_dataset, mountpoint_used)
    except RuntimeError as e:
        ZELogger.log(
            {
                "level": "EXCEPTION",
                "message":
                f"Failed mounting dataset to '{mountpoint_used}'.\n{e}"
            },
            exit_on_error=True)

    ZELogger.verbose_log(
        {
            "level": "INFO",
            "message": f"Mounted dataset to '{mountpoint_used}'.\n"
        }, verbose)
    if not verbose:
        ZELogger.log({"level": "INFO", "message": mountpoint_used})

    child_datasets = zedenv.lib.be.list_child_mountpoints(be_dataset)

    if child_datasets:
        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Mounting children of '{boot_environment}'.\n"
            }, verbose)
        mount_children(child_datasets, mountpoint_used, verbose)
Exemple #24
0
def zedenv_create(parent_dataset: str, root_dataset: str,
                  boot_environment: str, verbose: bool,
                  existing: Optional[str], bootloader: Optional[str]):
    """
    :Parameters:
      parent_dataset : str
        Dataset boot environment root, commonly 'zpool/ROOT'.
      root_dataset : str
        Current boot dataset.
      boot_environment : str
        Name of new boot environment, e.g. default-02
      verbose : bool
        Print information verbosely.
      existing : bool
        Create boot environment from certain dataset.
    :return:
    """

    ZELogger.verbose_log(
        {
            "level": "INFO",
            "message": "Creating Boot Environment:\n"
        }, verbose)

    # Remove the final part of the data set after the last / and add new name
    boot_environment_dataset = f"{parent_dataset}/{boot_environment}"

    zpool = zedenv.lib.be.dataset_pool(boot_environment_dataset)
    current_be = None
    try:
        current_be = pyzfscmds.utility.dataset_child_name(
            zedenv.lib.be.bootfs_for_pool(zpool))
    except RuntimeError:
        ZELogger.log(
            {
                "level": "EXCEPTION",
                "message": f"Failed to get active boot environment'\n"
            },
            exit_on_error=True)

    bootloader_plugin = None
    if bootloader:
        bootloader_plugin = zedenv.lib.configure.get_bootloader(
            boot_environment, current_be, bootloader, verbose, False, False,
            parent_dataset)
        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Using plugin {bootloader}\n"
            }, verbose)

    if zfs_utility.dataset_exists(boot_environment_dataset):
        ZELogger.log(
            {
                "level":
                "EXCEPTION",
                "message":
                f"Failed to create {boot_environment_dataset}, already exists."
            },
            exit_on_error=True)

    # Getting snapshot for clone
    clone_sources = get_clones(root_dataset, existing)
    ZELogger.verbose_log(
        {
            "level":
            "INFO",
            "message":
            f"Getting properties of {boot_environment_dataset} for clones {clone_sources}\n"
        }, verbose)

    for source in clone_sources:
        if source['datasetchild'] == '':
            be_clone = f"{boot_environment_dataset}"
        else:
            be_clone = f"{boot_environment_dataset}/{source['datasetchild']}"

        try:
            pyzfscmds.cmd.zfs_clone(source['snapshot'],
                                    be_clone,
                                    properties=source['properties'])
        except RuntimeError as e:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message": (f"Failed to create {boot_environment_dataset}",
                                f" from {clone_sources['snapshot']}")
                },
                exit_on_error=True)

    if bootloader_plugin:
        try:
            bootloader_plugin.post_create()
        except RuntimeWarning as err:
            ZELogger.verbose_log(
                {
                    "level":
                    "WARNING",
                    "message":
                    f"During {bootloader_plugin.bootloader} post create the following"
                    f" occurred:\n\n{err}\nContinuing creation.\n"
                }, verbose)
        except RuntimeError as err:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"During {bootloader_plugin.bootloader} post create the following "
                    f"occurred:\n\n{err}\nStopping creation.\n"
                },
                exit_on_error=True)
        except AttributeError:
            ZELogger.verbose_log(
                {
                    "level":
                    "INFO",
                    "message":
                    f"Tried to run {bootloader_plugin.bootloader} 'post create', "
                    f"not implemented.\n"
                }, verbose)
Exemple #25
0
def show_source_properties(property_list, verbose):
    ZELogger.verbose_log({"level": "INFO", "message": "PROPERTIES"}, verbose)
    for p in property_list:
        ZELogger.verbose_log({"level": "INFO", "message": p}, verbose)
    ZELogger.verbose_log({"level": "INFO", "message": ""}, verbose)
Exemple #26
0
    def __init__(self,
                 zedenv_data: dict,
                 skip_update: bool = False,
                 skip_cleanup: bool = False):

        super().__init__(zedenv_data)

        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}"

        self.boot_mountpoint = "/boot"
        self.env_dir = "env"
        self.zfs_env_dir = "zfsenv"

        if not os.path.isdir(self.boot_mountpoint):
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"Boot mountpoint {self.boot_mountpoint} does not exist. Exiting.\n"
                },
                exit_on_error=True)

        self.skip_update_grub = skip_update
        self.skip_cleanup = skip_cleanup

        # Set defaults
        for pr in self.allowed_properties:
            self.zedenv_properties[pr["property"]] = pr["default"]

        self.check_zedenv_properties()

        if self.zedenv_properties["bootonzfs"] in ("yes", "1"):
            self.bootonzfs = True
        elif self.zedenv_properties["bootonzfs"] in ("no", "0"):
            self.bootonzfs = False
        else:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    (f"Property 'bootonzfs' is set to invalid value "
                     f"{self.zedenv_properties['bootonzfs']}, should be "
                     "'yes', 'no', '0', or '1'. Exiting.\n")
                },
                exit_on_error=True)

        if self.bootonzfs:
            if not self.noop:
                if not os.path.isdir(self.zedenv_properties["boot"]):
                    try:
                        os.makedirs(self.zedenv_properties["boot"])
                    except PermissionError as e:
                        ZELogger.log(
                            {
                                "level":
                                "EXCEPTION",
                                "message":
                                ("Require Privileges to write to "
                                 f"{self.zedenv_properties['boot']}\n{e}")
                            },
                            exit_on_error=True)
                    except OSError as os_err:
                        ZELogger.log({
                            "level": "EXCEPTION",
                            "message": os_err
                        },
                                     exit_on_error=True)
                    ZELogger.verbose_log(
                        {
                            "level":
                            "INFO",
                            "message": ("Created mount directory "
                                        f"{self.zedenv_properties['boot']}\n")
                        }, self.verbose)

                zfs_env_dir_path = os.path.join(self.zedenv_properties["boot"],
                                                self.zfs_env_dir)
                if not os.path.isdir(zfs_env_dir_path):
                    try:
                        os.makedirs(zfs_env_dir_path)
                    except PermissionError as e:
                        ZELogger.log(
                            {
                                "level":
                                "EXCEPTION",
                                "message": (f"Require Privileges to write to "
                                            f"{zfs_env_dir_path}\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 os.path.isdir(self.zedenv_properties["boot"]):
                self.plugin_property_error("boot")

        self.grub_boot_dir = os.path.join(self.boot_mountpoint,
                                          self.zedenv_properties["grubsubdir"])

        if not os.path.isdir(self.grub_boot_dir):
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    (f"Directory {self.grub_boot_dir} does not exist. "
                     "Check 'grubsubdir' property is set correctly")
                },
                exit_on_error=True)

        self.grub_cfg = "grub.cfg"

        self.grub_cfg_path = os.path.join(self.grub_boot_dir, self.grub_cfg)
Exemple #27
0
    def teardown_boot_env_tree(self):
        def ismount(path, boot):
            if not os.path.ismount(path):
                """
                This is required because `os.path.ismount()` returns False if a ZFS dataset is
                being mounted again to a subfolder of itself. E.g. bpool/boot/env/zedenv-default
                is mounted to
                 - `/boot` and
                 - `/boot/zfsenv/zedenv-default`
                 """
                s1 = os.lstat(path)
                s2 = os.lstat(boot)
                return s1.st_ino == s2.st_ino
            else:
                return True

        mount_root = os.path.join(self.zedenv_properties["boot"],
                                  self.zfs_env_dir)
        cleanup = True

        if not os.path.exists(mount_root):
            ZELogger.verbose_log(
                {
                    "level": "INFO",
                    "message": f"Mount root: '{mount_root}' doesnt exist.\n"
                }, self.verbose)
        else:
            for m in os.listdir(mount_root):
                mount_path = os.path.join(mount_root, m)
                ZELogger.verbose_log(
                    {
                        "level": "INFO",
                        "message": f"Unmounting {m}\n"
                    }, self.verbose)
                if ismount(mount_path, self.boot_mountpoint):
                    try:
                        zedenv.lib.system.umount(mount_path)
                    except RuntimeError as e:
                        ZELogger.log(
                            {
                                "level":
                                "WARNING",
                                "message":
                                f"Failed Un-mountingdataset from '{m}'.\n{e}"
                            },
                            exit_on_error=True)
                        cleanup = False
                    else:
                        ZELogger.verbose_log(
                            {
                                "level": "INFO",
                                "message":
                                f"Unmounted {m} from {mount_path}.\n"
                            }, self.verbose)
                        try:
                            os.rmdir(mount_path)
                        except OSError as ex:
                            ZELogger.verbose_log(
                                {
                                    "level":
                                    "WARNING",
                                    "message":
                                    f"Couldn't remove directory {mount_path}.\n{ex}\n"
                                }, self.verbose)
                            cleanup = False
                        else:
                            ZELogger.verbose_log(
                                {
                                    "level": "INFO",
                                    "message":
                                    f"Removed directory {mount_path}.\n"
                                }, self.verbose)

        if cleanup and os.path.exists(mount_root):
            try:
                os.rmdir(mount_root)
            except OSError as ex:
                ZELogger.verbose_log(
                    {
                        "level":
                        "WARNING",
                        "message":
                        f"Couldn't remove directory {mount_root}.\n{ex}\n"
                    }, self.verbose)
Exemple #28
0
def zedenv_rename(be_root: str, boot_environment: str,
                  new_boot_environment: str, bootloader: Optional[str],
                  verbose: Optional[bool]):

    old_be_dataset = f"{be_root}/{boot_environment}"
    new_be_dataset = f"{be_root}/{new_boot_environment}"

    zpool = zedenv.lib.be.dataset_pool(old_be_dataset)
    current_be = None
    try:
        current_be = pyzfscmds.utility.dataset_child_name(
            zedenv.lib.be.bootfs_for_pool(zpool))
    except RuntimeError:
        ZELogger.log(
            {
                "level": "EXCEPTION",
                "message": f"Failed to get active boot environment'\n"
            },
            exit_on_error=True)

    bootloader_plugin = None
    if bootloader:
        bootloader_plugin = zedenv.lib.configure.get_bootloader(
            boot_environment, current_be, bootloader, verbose, False, False,
            be_root)
        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Using plugin {bootloader}\n"
            }, verbose)

    dataset_mountpoint = pyzfscmds.system.agnostic.dataset_mountpoint(
        old_be_dataset)

    if pyzfscmds.utility.dataset_exists(new_be_dataset):
        ZELogger.log(
            {
                "level":
                "EXCEPTION",
                "message":
                f"Boot environment '{new_boot_environment}' already exists.\n"
            },
            exit_on_error=True)

    if zedenv.lib.be.is_current_boot_environment(boot_environment):
        ZELogger.log(
            {
                "level":
                "EXCEPTION",
                "message":
                f"Cannot rename current boot environment '{boot_environment}.\n"
            },
            exit_on_error=True)

    if zedenv.lib.be.is_active_boot_environment(
            old_be_dataset, zedenv.lib.be.dataset_pool(old_be_dataset)):
        ZELogger.log(
            {
                "level":
                "EXCEPTION",
                "message":
                f"Cannot rename active boot environment '{boot_environment}.\n"
            },
            exit_on_error=True)

    if dataset_mountpoint:
        ZELogger.log(
            {
                "level":
                "EXCEPTION",
                "message":
                f"Dataset is mounted to '{dataset_mountpoint}', unmount and try again\n"
            },
            exit_on_error=True)

    try:
        pyzfscmds.cmd.zfs_rename(old_be_dataset, new_be_dataset)
    except RuntimeError as err:
        ZELogger.log({
            "level": "EXCEPTION",
            "message": err
        },
                     exit_on_error=True)

    # Rename the boot dataset if a separate ZFS boot pool is used
    if zedenv.lib.be.extra_bpool():
        be_boot = zedenv.lib.be.root('/boot')
        old_be_boot_dataset = f"{be_boot}/zedenv-{boot_environment}"
        new_be_boot_dataset = f"{be_boot}/zedenv-{new_boot_environment}"
        try:
            pyzfscmds.cmd.zfs_rename(old_be_boot_dataset, new_be_boot_dataset)
        except RuntimeError as e:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    (f"Failed to rename the boot dataset '{old_be_boot_dataset}' to"
                     f" '{new_be_boot_dataset}'. The following error occured:\n\n{e}"
                     "\nStopping rename.\n")
                },
                exit_on_error=True)

    if bootloader_plugin:
        try:
            bootloader_plugin.post_rename()
        except RuntimeWarning as err:
            ZELogger.verbose_log(
                {
                    "level":
                    "WARNING",
                    "message":
                    f"During {bootloader_plugin.bootloader} post rename the following"
                    f" occurred:\n\n{err}\nContinuing rename.\n"
                }, verbose)
        except RuntimeError as err:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"During {bootloader_plugin.bootloader} post rename the following "
                    f"occurred:\n\n{err}\nStopping rename.\n"
                },
                exit_on_error=True)
        except AttributeError:
            ZELogger.verbose_log(
                {
                    "level":
                    "INFO",
                    "message":
                    f"Tried to run {bootloader_plugin.bootloader} 'post rename', "
                    f"not implemented.\n"
                }, verbose)
Exemple #29
0
def zedenv_activate(boot_environment: str, boot_environment_root: str,
                    verbose: Optional[bool], bootloader: Optional[str],
                    noconfirm: Optional[bool], noop: Optional[bool]):
    """
    If a plugin is found that can be run on the system,
    run the activate command from the plugin.
    """

    ZELogger.verbose_log(
        {
            "level": "INFO",
            "message": f"Activating Boot Environment: {boot_environment}\n"
        }, verbose)

    be_requested = f"{boot_environment_root}/{boot_environment}"

    zpool = zedenv.lib.be.dataset_pool(be_requested)
    current_be = None
    try:
        current_be = pyzfscmds.utility.dataset_child_name(
            zedenv.lib.be.bootfs_for_pool(zpool))
    except RuntimeError:
        ZELogger.log(
            {
                "level": "EXCEPTION",
                "message": f"Failed to get active boot environment'\n"
            },
            exit_on_error=True)

    bootloader_set = zedenv.lib.be.get_property(be_requested,
                                                "org.zedenv:bootloader")
    if not bootloader and bootloader_set:
        bootloader = bootloader_set if bootloader_set != '-' else None

    bootloader_plugin = None
    if bootloader:
        bootloader_plugin = zedenv.lib.configure.get_bootloader(
            boot_environment, current_be, bootloader, verbose, noconfirm, noop,
            boot_environment_root)
        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Using plugin {bootloader}\n"
            }, verbose)

    if bootloader_plugin:
        try:
            bootloader_plugin.pre_activate()
        except RuntimeWarning as err:
            ZELogger.verbose_log(
                {
                    "level":
                    "WARNING",
                    "message":
                    f"During {plugin.bootloader} mid activate the following occurred:\n"
                    f"\n{err}\nContinuing activation.\n"
                }, verbose)
        except RuntimeError as err:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"During {plugin.bootloader} mid activate the following occurred:\n"
                    f"\n{err}\nStopping activation.\n"
                },
                exit_on_error=True)

    if not pyzfscmds.utility.dataset_exists(be_requested):
        ds_is_clone = None
        try:
            ds_is_clone = pyzfscmds.utility.is_clone(be_requested)
        except RuntimeError:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"Boot environment {boot_environment} doesn't exist'\n"
                },
                exit_on_error=True)

        if not ds_is_clone:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"Boot environment {boot_environment} doesn't exist'\n"
                },
                exit_on_error=True)

    ZELogger.verbose_log(
        {
            "level": "INFO",
            "message": f"Boot environment {boot_environment} exists'\n"
        }, verbose)

    if current_be == be_requested:
        ZELogger.verbose_log(
            {
                "level":
                "INFO",
                "message":
                f"Boot Environment {boot_environment} is already active.\n"
            }, verbose)
    else:
        # Set bootfs on dataset
        dataset_mountpoint = pyzfscmds.system.agnostic.dataset_mountpoint(
            be_requested)
        activate_boot_environment(be_requested, dataset_mountpoint, verbose,
                                  noop, bootloader_plugin)

    be_child_datasets = None
    try:
        be_child_datasets = pyzfscmds.cmd.zfs_list(boot_environment_root,
                                                   recursive=True,
                                                   columns=["name"],
                                                   zfs_types=["filesystem"])
    except RuntimeError as e:
        ZELogger.log(
            {
                "level":
                "EXCEPTION",
                "message":
                f"Failed to list datasets under {boot_environment_root}\n{e}\n"
            },
            exit_on_error=True)

    be_child_datasets_list = [line for line in be_child_datasets.splitlines()]
    if not noop:
        disable_children_automount(be_child_datasets_list, be_requested,
                                   boot_environment_root, verbose)

        apply_settings_to_child_datasets(be_child_datasets_list, be_requested,
                                         verbose)

    if bootloader_plugin:
        try:
            bootloader_plugin.post_activate()
        except RuntimeWarning as err:
            ZELogger.verbose_log(
                {
                    "level":
                    "WARNING",
                    "message":
                    f"During {plugin.bootloader} mid activate the following occurred:\n"
                    f"\n{err}\nContinuing activation.\n"
                }, verbose)
        except RuntimeError as err:
            ZELogger.log(
                {
                    "level":
                    "EXCEPTION",
                    "message":
                    f"During {plugin.bootloader} mid activate the following occurred:\n"
                    f"\n{err}\nStopping activation.\n"
                },
                exit_on_error=True)
Exemple #30
0
def mount_and_modify_dataset(dataset: str,
                             verbose: bool = False,
                             noop: bool = False,
                             pre_mount_properties: List[str] = None,
                             post_mount_properties: List[str] = None,
                             plugin=None):
    ZELogger.verbose_log(
        {
            "level": "INFO",
            "message": f"Mount dataset for customization\n"
        }, verbose)

    if not noop:
        if pre_mount_properties:
            for pre_prop in pre_mount_properties:
                try:
                    pyzfscmds.cmd.zfs_set(dataset, pre_prop)
                except RuntimeError as e:
                    ZELogger.log(
                        {
                            "level":
                            "EXCEPTION",
                            "message":
                            f"Failed to set {pre_prop} on {dataset}\n{e}\n"
                        },
                        exit_on_error=True)

    # Allow even with noop, just mounts and runs plugin
    with tempfile.TemporaryDirectory() as tmpdir:
        try:
            pyzfscmds.cmd.zfs_set(dataset, f"mountpoint={tmpdir}")
        except RuntimeError as e:
            ZELogger.log(
                {
                    "level": "EXCEPTION",
                    "message": f"Failed to set mountpoint={tmpdir}\n{e}\n"
                },
                exit_on_error=True)

        try:
            pyzfscmds.cmd.zfs_mount(dataset)
        except RuntimeError as e:
            ZELogger.log(
                {
                    "level": "EXCEPTION",
                    "message": f"Failed to mount mountpoint={tmpdir}\n{e}\n"
                },
                exit_on_error=True)

        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Mounted {dataset} to {tmpdir}\n"
            }, verbose)

        # Do stuff while mounted
        if plugin is not None:
            ZELogger.verbose_log(
                {
                    "level":
                    "INFO",
                    "message":
                    f"Running plugin: '{plugin.bootloader}' - mid_activate\n"
                }, verbose)
            try:
                plugin.mid_activate(tmpdir)
            except RuntimeWarning as err:
                ZELogger.verbose_log(
                    {
                        "level":
                        "WARNING",
                        "message":
                        f"During {plugin.bootloader} mid activate the following occurred:\n"
                        f"\n{err}\nContinuing activation.\n"
                    }, verbose)
            except RuntimeError as err:
                ZELogger.log(
                    {
                        "level":
                        "EXCEPTION",
                        "message":
                        f"During {plugin.bootloader} mid activate the following occurred:\n"
                        f"\n{err}\nStopping activation.\n"
                    },
                    exit_on_error=True)

        try:
            pyzfscmds.cmd.zfs_unmount(dataset)
        except RuntimeError as e:
            ZELogger.log(
                {
                    "level": "EXCEPTION",
                    "message":
                    f"Failed to unmount {dataset} from {tmpdir}\n{e}\n"
                },
                exit_on_error=True)
        ZELogger.verbose_log(
            {
                "level": "INFO",
                "message": f"Unmounted {dataset} from {tmpdir}\n"
            }, verbose)

    if not noop:
        if post_mount_properties:
            for post_prop in post_mount_properties:
                try:
                    pyzfscmds.cmd.zfs_set(dataset, post_prop)
                except RuntimeError as e:
                    ZELogger.log(
                        {
                            "level":
                            "EXCEPTION",
                            "message":
                            f"Failed to set {post_prop} on {dataset}\n{e}\n"
                        },
                        exit_on_error=True)