diff --git a/src/simplezfs/zfs.py b/src/simplezfs/zfs.py index ef56067..d013b7b 100644 --- a/src/simplezfs/zfs.py +++ b/src/simplezfs/zfs.py @@ -52,11 +52,15 @@ class ZFS: parameter for the get/set functions is used. :param metadata_namespace: Default namespace + :param pe_helper: Privilege escalation (PE) helper to use for actions that require elevated privileges (root). + :param use_pe_helper: Whether to use the PE helper for creating and (u)mounting.# :param kwargs: Extra arguments, ignored ''' - def __init__(self, *, metadata_namespace: Optional[str] = None, mount_helper: Optional[str] = None, **kwargs) -> None: + def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None, + use_pe_helper: bool = False, **kwargs) -> None: self.metadata_namespace = metadata_namespace - self.mount_helper = mount_helper + self.pe_helper = pe_helper + self.use_pe_helper = use_pe_helper @property def metadata_namespace(self) -> Optional[str]: @@ -75,35 +79,49 @@ class ZFS: self._metadata_namespace = namespace @property - def mount_helper(self) -> Optional[str]: + def pe_helper(self) -> Optional[str]: ''' - Returns the mount_helper, which may be None if not set. + Returns the pe_helper, which may be None if not set. ''' - return self._mount_helper + return self._pe_helper - @mount_helper.setter - def mount_helper(self, helper: Optional[str]) -> None: + @pe_helper.setter + def pe_helper(self, helper: Optional[str]) -> None: ''' - Sets the mount helper. Some basic checks for existance and executablility are performed, but these are not - sufficient for secure operation and are provided to aid the user in configuring the library. + Sets the privilege escalation (PE) helper. Some basic checks for existance and executablility are performed, + but these are not sufficient for secure operation and are provided to aid the user in configuring the library. :note: This method does not follow symlinks. :raises FileNotFoundError: if the script can't be found or is not executable. ''' if helper is None: - log.debug('Mount helper is None') - self._mount_helper = None + log.debug('PE helper is None') + self._pe_helper = None else: candidate = helper.strip() mode = os.lstat(candidate).st_mode if not stat.S_ISREG(mode): - raise FileNotFoundError('Mount helper must be a file') + raise FileNotFoundError('PE helper must be a file') if not os.access(candidate, os.X_OK): - raise FileNotFoundError('Mount helper must be executable') - log.debug(f'Setting mount helper to "{candidate}"') - self._mount_helper = candidate + raise FileNotFoundError('PE helper must be executable') + log.debug(f'Setting privilege escalation helper to "{candidate}"') + self._pe_helper = candidate + + @property + def use_pe_helper(self) -> bool: + ''' + Returns whether the privilege escalation (PE) helper should be used. + ''' + return self._use_pe_helper + + @use_pe_helper.setter + def use_pe_helper(self, use: bool) -> None: + ''' + Enable or disable using the privilege escalation (PE) helper. + ''' + self._use_pe_helper = use def dataset_exists(self, name: str) -> bool: ''' @@ -130,6 +148,20 @@ class ZFS: ''' raise NotImplementedError(f'{self} has not implemented this function') + def set_mountpoint(self, fileset: str, mountpoint: str, *, use_pe_helper: bool = False) -> None: + ''' + Sets or changes the mountpoint property of a fileset. While this can be achieved using the generic function + :func:`~ZFS.set_property`, it allows for using the privilege escalation (PE) helper if so desired. + + :param fileset: The fileset to modify. + :param mountpoint: The new value for the ``mountpoint`` property. + :param use_pe_helper: Overwrite the default for using the privilege escalation (PE) helper for this task. + :raises DatasetNotFound: if the fileset could not be found. + :raises ValidationError: if validating the parameters failed. + ''' + real_use_pe_helper = use_pe_helper if use_pe_helper is not None else self.use_pe_helper + raise NotImplementedError(f'not implemented yet') + def set_property(self, dataset: str, key: str, value: str, *, metadata: bool = False, overwrite_metadata_namespace: Optional[str] = None) -> None: ''' Sets the ``value`` of the native property ``key``. By default, only native properties can be set. If diff --git a/src/simplezfs/zfs_cli.py b/src/simplezfs/zfs_cli.py index fed53d1..068f8f0 100644 --- a/src/simplezfs/zfs_cli.py +++ b/src/simplezfs/zfs_cli.py @@ -28,7 +28,8 @@ class ZFSCli(ZFS): If ``zfs_exe`` is supplied, it is assumed that it points to the path of the ``zfs(8)`` executable. ''' - def __init__(self, *, metadata_namespace: Optional[str] = None, zfs_exe: Optional[str] = None, **kwargs) -> None: + def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None, + use_pe_helper: bool = False, zfs_exe: Optional[str] = None, **kwargs) -> None: super().__init__(metadata_namespace=metadata_namespace) self.find_executable(path=zfs_exe) diff --git a/src/simplezfs/zfs_native.py b/src/simplezfs/zfs_native.py index 992a6ed..dda8d6c 100644 --- a/src/simplezfs/zfs_native.py +++ b/src/simplezfs/zfs_native.py @@ -18,7 +18,8 @@ class ZFSNative(ZFS): :class:`~zfs.zfs.ZFS`. It is recommended to use :func:`~zfs.zfs.get_zfs` to obtain an instance, using ``native`` as api. ''' - def __init__(self, *, metadata_namespace: Optional[str] = None, **kwargs) -> None: + def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None, + use_pe_helper: bool = False, **kwargs) -> None: super().__init__(metadata_namespace=metadata_namespace) def set_property(self, dataset: str, key: str, value: str, *, metadata: bool = False, overwrite_metadata_namespace: Optional[str] = None) -> None: diff --git a/src/simplezfs/zpool.py b/src/simplezfs/zpool.py index b6fccb4..171f9f8 100644 --- a/src/simplezfs/zpool.py +++ b/src/simplezfs/zpool.py @@ -1,7 +1,13 @@ +import logging +import os +import stat from typing import Optional +log = logging.getLogger('simplezfs.zpool') + + class ZPool: ''' ZPool interface class. This API generally covers only the zpool(8) tool, for zfs(8) please see class :class:`~ZFS`. @@ -15,8 +21,11 @@ class ZPool: When creating an instance of this class, select one or the other as the ``api`` argument. ''' - def __init__(self, *, metadata_namespace: Optional[str] = None, **kwargs) -> None: + def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None, + use_pe_helper: bool = False, **kwargs) -> None: self.metadata_namespace = metadata_namespace + self.pe_helper = pe_helper + self.use_pe_helper = use_pe_helper @property def metadata_namespace(self) -> Optional[str]: @@ -34,6 +43,37 @@ class ZPool: ''' self._metadata_namespace = namespace + @property + def pe_helper(self) -> Optional[str]: + ''' + Returns the pe_helper, which may be None if not set. + ''' + return self._pe_helper + + @pe_helper.setter + def pe_helper(self, helper: Optional[str]) -> None: + ''' + Sets the privilege escalation (PE) helper. Some basic checks for existance and executability are performed, + but these are not sufficient for secure operation and are provided to aid the user in configuring the library. + + :note: This method does not follow symlinks. + + :raises FileNotFoundError: if the helper can't be found or is not executable. + ''' + if helper is None: + log.debug('PE helper is None') + self._pe_helper = None + else: + candidate = helper.strip() + + mode = os.lstat(candidate).st_mode + if not stat.S_ISREG(mode): + raise FileNotFoundError('PE helper must be a file') + if not os.access(candidate, os.X_OK): + raise FileNotFoundError('PE helper must be executable') + log.debug(f'Setting privilege escalation helper to "{candidate}"') + self._pe_helper = candidate + def get_zpool(api: str = 'cli', metadata_namespace: Optional[str] = None, **kwargs) -> ZPool: ''' diff --git a/src/simplezfs/zpool_cli.py b/src/simplezfs/zpool_cli.py index 61a5e3e..dba087d 100644 --- a/src/simplezfs/zpool_cli.py +++ b/src/simplezfs/zpool_cli.py @@ -15,8 +15,9 @@ log = logging.getLogger('zfs.zpool_cli') class ZPoolCli(ZPool): - def __init__(self, *, metadata_namespace: Optional[str] = None, zpool_exe: Optional[str] = None, **kwargs) -> None: - super().__init__(metadata_namespace=metadata_namespace) + def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None, + use_pe_helper: bool = False, zpool_exe: Optional[str] = None, **kwargs) -> None: + super().__init__(metadata_namespace=metadata_namespace, pe_helper=pe_helper, use_pe_helper=use_pe_helper) self.find_executable(path=zpool_exe) def find_executable(self, path: str = None) -> None: diff --git a/src/simplezfs/zpool_native.py b/src/simplezfs/zpool_native.py index e735873..3f1603e 100644 --- a/src/simplezfs/zpool_native.py +++ b/src/simplezfs/zpool_native.py @@ -9,5 +9,6 @@ from .zpool import ZPool class ZPoolNative(ZPool): - def __init__(self, *, metadata_namespace: Optional[str] = None, **kwargs) -> None: - super().__init__(metadata_namespace=metadata_namespace) + def __init__(self, *, metadata_namespace: Optional[str] = None, pe_helper: Optional[str] = None, + use_pe_helper: bool = False, **kwargs) -> None: + super().__init__(metadata_namespace=metadata_namespace, pe_helper=pe_helper, use_pe_helper=use_pe_helper)