diff --git a/.github/workflows/anaconda_tests.yml b/.github/workflows/anaconda_tests.yml index b3e3188ec..2a94c3c60 100644 --- a/.github/workflows/anaconda_tests.yml +++ b/.github/workflows/anaconda_tests.yml @@ -39,7 +39,7 @@ jobs: podman run -i --rm -v ./blivet:/blivet:z -v ./anaconda:/anaconda:z quay.io/rhinstaller/anaconda-ci:$TARGET_BRANCH sh -c " \ set -xe; \ dnf remove -y python3-blivet; \ - dnf install -y python3-blockdev libblockdev-plugins-all python3-bytesize libbytesize python3-pyparted parted libselinux-python3; \ + dnf install -y python3-blockdev libblockdev-plugins-all python3-bytesize libbytesize python3-pyparted python3-libmount parted libselinux-python3; \ cd /blivet; \ python3 ./setup.py install; \ cd /anaconda; \ diff --git a/Makefile b/Makefile index 052e8fdb8..106f9a205 100644 --- a/Makefile +++ b/Makefile @@ -165,36 +165,7 @@ rpmlog: @echo bumpver: po-pull - @opts="-n $(PKGNAME) -v $(VERSION) -r $(RPMRELEASE)" ; \ - if [ ! -z "$(IGNORE)" ]; then \ - opts="$${opts} -i $(IGNORE)" ; \ - fi ; \ - if [ ! -z "$(MAP)" ]; then \ - opts="$${opts} -m $(MAP)" ; \ - fi ; \ - if [ ! -z "$(SKIP_ACKS)" ]; then \ - opts="$${opts} -s" ; \ - fi ; \ - if [ ! -z "$(BZDEBUG)" ]; then \ - opts="$${opts} -d" ; \ - fi ; \ - ( scripts/makebumpver $${opts} ) || exit 1 ; \ - -scratch-bumpver: - @opts="-n $(PKGNAME) -v $(RPMVERSION) -r $(RPMRELEASE) --newrelease $(RC_RELEASE)" ; \ - if [ ! -z "$(IGNORE)" ]; then \ - opts="$${opts} -i $(IGNORE)" ; \ - fi ; \ - if [ ! -z "$(MAP)" ]; then \ - opts="$${opts} -m $(MAP)" ; \ - fi ; \ - if [ ! -z "$(SKIP_ACKS)" ]; then \ - opts="$${opts} -s" ; \ - fi ; \ - if [ ! -z "$(BZDEBUG)" ]; then \ - opts="$${opts} -d" ; \ - fi ; \ - ( scripts/makebumpver $${opts} ) || exit 1 ; \ + ( scripts/makebumpver -n $(PKGNAME) -v $(VERSION) -r $(RPMRELEASE) ) || exit 1 ; scratch: @rm -f ChangeLog diff --git a/README.md b/README.md index 7986a84fb..b0655f625 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ To install these dependencies use following commands: # dnf install python3-blockdev libblockdev-plugins-all python3-bytesize libbytesize python3-pyparted parted libselinux-python3 * On Debian and Ubuntu based distributions: - # apt-get install python3-blockdev python3-bytesize python3-parted python3-selinux gir1.2-blockdev-3.0 libblockdev-lvm3 libblockdev-btrfs3 libblockdev-swap3 libblockdev-loop3 libblockdev-crypto3 libblockdev-mpath3 libblockdev-dm3 libblockdev-mdraid3 libblockdev-nvdimm3 + # apt-get install python3-blockdev python3-bytesize python3-parted python3-selinux gir1.2-blockdev-3.0 libblockdev-lvm3 libblockdev-btrfs3 libblockdev-swap3 libblockdev-loop3 libblockdev-crypto3 libblockdev-mpath3 libblockdev-dm3 libblockdev-mdraid3 libblockdev-nvdimm3 libblockdev-fs3 ### Development diff --git a/blivet/__init__.py b/blivet/__init__.py index 0253cc61c..7582bb73b 100644 --- a/blivet/__init__.py +++ b/blivet/__init__.py @@ -63,9 +63,9 @@ def log_bd_message(level, msg): from gi.repository import GLib from gi.repository import BlockDev as blockdev if arch.is_s390(): - _REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "s390", "nvdimm", "nvme")) + _REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "s390", "nvdimm", "nvme", "fs")) else: - _REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "nvdimm", "nvme")) + _REQUESTED_PLUGIN_NAMES = set(("lvm", "btrfs", "swap", "crypto", "loop", "mdraid", "mpath", "dm", "nvdimm", "nvme", "fs")) _requested_plugins = blockdev.plugin_specs_from_names(_REQUESTED_PLUGIN_NAMES) try: diff --git a/blivet/actionlist.py b/blivet/actionlist.py index c7674cc20..9b3f727e9 100644 --- a/blivet/actionlist.py +++ b/blivet/actionlist.py @@ -268,24 +268,39 @@ def _post_process(self, devices=None): partition.parted_partition = pdisk.getPartitionByPath(partition.path) @with_flag("processing") - def process(self, callbacks=None, devices=None, dry_run=None): + def process(self, callbacks=None, devices=None, fstab=None, dry_run=None): """ Execute all registered actions. :param callbacks: callbacks to be invoked when actions are executed :param devices: a list of all devices current in the devicetree + :param fstab: FSTabManagerObject tied to blivet, if None fstab file will not be modified :type callbacks: :class:`~.callbacks.DoItCallbacks` """ devices = devices or [] self._pre_process(devices=devices) + skip_fstab = fstab is None or fstab.dest_file is None + for action in self._actions[:]: log.info("executing action: %s", action) if dry_run: continue + # get (b)efore (a)ction.(e)xecute fstab entry + # (device may not exist afterwards) + if not skip_fstab: + try: + entry = fstab.entry_from_device(action.device) + except ValueError: + # this device should not be in fstab + bae_entry = None + else: + bae_entry = fstab.find_entry(entry=entry) + with blivet_lock: + try: action.execute(callbacks) except DiskLabelCommitError: @@ -315,4 +330,8 @@ def process(self, callbacks=None, devices=None, dry_run=None): self._completed_actions.append(self._actions.pop(0)) _callbacks.action_executed(action=action) + if not skip_fstab: + fstab.update(action, bae_entry) + fstab.write() + self._post_process(devices=devices) diff --git a/blivet/blivet.py b/blivet/blivet.py index f2bb32c63..e1c0042d8 100644 --- a/blivet/blivet.py +++ b/blivet/blivet.py @@ -40,6 +40,7 @@ from .errors import StorageError, DependencyError from .size import Size from .devicetree import DeviceTree +from .fstab import FSTabManager from .formats import get_default_filesystem_type from .flags import flags from .formats import get_format @@ -55,6 +56,9 @@ log = logging.getLogger("blivet") +FSTAB_PATH = "/etc/fstab" + + @six.add_metaclass(SynchronizedMeta) class Blivet(object): @@ -71,6 +75,10 @@ def __init__(self): self.size_sets = [] self.set_default_fstype(get_default_filesystem_type()) + # fstab write location purposedly set to None. It has to be overriden + # manually when using blivet. + self.fstab = FSTabManager(src_file=FSTAB_PATH, dest_file=None) + self._short_product_name = 'blivet' self._next_id = 0 @@ -111,7 +119,8 @@ def do_it(self, callbacks=None): """ - self.devicetree.actions.process(callbacks=callbacks, devices=self.devices) + self.devicetree.actions.process(callbacks=callbacks, devices=self.devices, fstab=self.fstab) + self.fstab.read() @property def next_id(self): @@ -141,6 +150,8 @@ def reset(self, cleanup_only=False): self.edd_dict = get_edd_dict(self.partitioned) self.devicetree.edd_dict = self.edd_dict + self.fstab.read() + if flags.include_nodev: self.devicetree.handle_nodev_filesystems() diff --git a/blivet/fcoe.py b/blivet/fcoe.py index 31297d2e3..371f8c201 100644 --- a/blivet/fcoe.py +++ b/blivet/fcoe.py @@ -18,6 +18,12 @@ # import os + +import gi +gi.require_version("BlockDev", "3.0") + +from gi.repository import BlockDev + from . import errors from . import udev from . import util @@ -33,11 +39,18 @@ def has_fcoe(): global _fcoe_module_loaded if not _fcoe_module_loaded: - util.run_program(["modprobe", "libfc"]) - _fcoe_module_loaded = True + try: + BlockDev.utils.load_kernel_module("libfc", None) + except BlockDev.UtilsError as e: + log.error("failed to load libfc: %s", str(e)) + else: + _fcoe_module_loaded = True if "bnx2x" in util.lsmod(): log.info("fcoe: loading bnx2fc") - util.run_program(["modprobe", "bnx2fc"]) + try: + BlockDev.utils.load_kernel_module("bnx2fc", None) + except BlockDev.UtilsError as e: + log.error("failed to load bnx2fc: %s", str(e)) return os.access("/sys/module/libfc", os.X_OK) diff --git a/blivet/flags.py b/blivet/flags.py index 716f0df47..e1f2279b2 100644 --- a/blivet/flags.py +++ b/blivet/flags.py @@ -50,8 +50,6 @@ def __init__(self): self.noiswmd = False self.gfs2 = True - self.jfs = True - self.reiserfs = True # for this flag to take effect, # blockdev.mpath.set_friendly_names(flags.multipath_friendly_names) must @@ -112,8 +110,6 @@ def update_from_boot_cmdline(self): self.multipath = "nompath" not in self.boot_cmdline self.noiswmd = "noiswmd" in self.boot_cmdline self.gfs2 = "gfs2" in self.boot_cmdline - self.jfs = "jfs" in self.boot_cmdline - self.reiserfs = "reiserfs" in self.boot_cmdline flags = Flags() diff --git a/blivet/formats/__init__.py b/blivet/formats/__init__.py index 1de64f3d1..306300de4 100644 --- a/blivet/formats/__init__.py +++ b/blivet/formats/__init__.py @@ -25,6 +25,11 @@ import importlib from six import add_metaclass +import gi +gi.require_version("BlockDev", "3.0") + +from gi.repository import BlockDev as blockdev + from .. import udev from ..util import get_sysfs_path_by_name from ..util import run_program @@ -560,19 +565,10 @@ def _pre_destroy(self, **kwargs): raise DeviceFormatError("device path does not exist or is not writable") def _destroy(self, **kwargs): - rc = 0 - err = "" try: - rc = run_program(["wipefs", "-f", "-a", self.device]) - except OSError as e: - err = str(e) - else: - if rc: - err = str(rc) - - if err: - msg = "error wiping old signatures from %s: %s" % (self.device, err) - raise FormatDestroyError(msg) + blockdev.fs.clean(self.device, force=True) + except blockdev.FSError as e: + raise FormatDestroyError("error wiping old signatures from %s: %s" % (self.device, str(e))) def _post_destroy(self, **kwargs): udev.settle() diff --git a/blivet/formats/fs.py b/blivet/formats/fs.py index acc1f9cb9..9148f4ed6 100644 --- a/blivet/formats/fs.py +++ b/blivet/formats/fs.py @@ -31,6 +31,11 @@ from parted import fileSystemType, PARTITION_BOOT +import gi +gi.require_version("BlockDev", "3.0") + +from gi.repository import BlockDev as blockdev + from ..tasks import fsck from ..tasks import fsinfo from ..tasks import fslabeling @@ -49,6 +54,7 @@ from . import DeviceFormat, register_device_format from .. import util from ..flags import flags +from ..fstab import FSTabOptions from ..storage_log import log_exception_info, log_method_call from .. import arch from ..size import Size, ROUND_UP @@ -91,7 +97,8 @@ class FS(DeviceFormat): # support for resize: grow/shrink, online/offline _resize_support = 0 - config_actions_map = {"label": "write_label"} + config_actions_map = {"label": "write_label", + "mountpoint": "change_mountpoint"} def __init__(self, **kwargs): """ @@ -120,6 +127,8 @@ def __init__(self, **kwargs): DeviceFormat.__init__(self, **kwargs) + self.fstab = FSTabOptions() + # Create task objects self._fsck = self._fsck_class(self) self._mkfs = self._mkfs_class(self) @@ -425,7 +434,7 @@ def _create(self, **kwargs): " is unacceptable for this filesystem.", self.uuid, self.type) except FSError as e: - raise FormatCreateError(e, self.device) + raise FormatCreateError(e) def _post_create(self, **kwargs): super(FS, self)._post_create(**kwargs) @@ -485,13 +494,13 @@ def check_module(self): for module in self._modules: try: - rc = util.run_program(["modprobe", "--dry-run", module]) - except OSError as e: - log.error("Could not check kernel module availability %s: %s", module, e) + avail = blockdev.utils.have_kernel_module(module) + except blockdev.UtilsError as e: + log.error("Could not check kernel module availability %s: %s", module, str(e)) self._supported = False return - if rc: + if not avail: log.debug("Kernel module %s not available", module) self._supported = False return @@ -645,6 +654,11 @@ def _teardown(self, **kwargs): udev.settle() + def change_mountpoint(self, dry_run=False): + # This function is intentionally left blank. Mountpoint change utilizes + # only the generic part of this code branch + pass + def read_label(self): """Read this filesystem's label. @@ -661,6 +675,13 @@ def read_label(self): if not self._readlabel.available: raise FSReadLabelError("can not read label for filesystem %s" % self.type) + + try: + if self._info.available: + self._current_info = self._info.do_task() + except FSError as e: + log.info("Failed to obtain info for device %s: %s", self.device, e) + return self._readlabel.do_task() def write_label(self, dry_run=False): @@ -1048,26 +1069,7 @@ class JFS(FS): """ JFS filesystem """ _type = "jfs" - _modules = ["jfs"] - _labelfs = fslabeling.JFSLabeling() - _uuidfs = fsuuid.JFSUUID() - _max_size = Size("8 TiB") - _formattable = True _linux_native = True - _dump = True - _check = True - _info_class = fsinfo.JFSInfo - _mkfs_class = fsmkfs.JFSMkfs - _size_info_class = fssize.JFSSize - _writelabel_class = fswritelabel.JFSWriteLabel - _writeuuid_class = fswriteuuid.JFSWriteUUID - _metadata_size_factor = 0.99 # jfs metadata may take 1% of space - parted_system = fileSystemType["jfs"] - - @property - def supported(self): - """ Is this filesystem a supported type? """ - return self.utils_available if flags.jfs else self._supported register_device_format(JFS) @@ -1077,27 +1079,7 @@ class ReiserFS(FS): """ reiserfs filesystem """ _type = "reiserfs" - _labelfs = fslabeling.ReiserFSLabeling() - _uuidfs = fsuuid.ReiserFSUUID() - _modules = ["reiserfs"] - _max_size = Size("16 TiB") - _formattable = True _linux_native = True - _dump = True - _check = True - _packages = ["reiserfs-utils"] - _info_class = fsinfo.ReiserFSInfo - _mkfs_class = fsmkfs.ReiserFSMkfs - _size_info_class = fssize.ReiserFSSize - _writelabel_class = fswritelabel.ReiserFSWriteLabel - _writeuuid_class = fswriteuuid.ReiserFSWriteUUID - _metadata_size_factor = 0.98 # reiserfs metadata may take 2% of space - parted_system = fileSystemType["reiserfs"] - - @property - def supported(self): - """ Is this filesystem a supported type? """ - return self.utils_available if flags.reiserfs else self._supported register_device_format(ReiserFS) @@ -1194,32 +1176,11 @@ def _destroy(self, **kwargs): # pylint: disable=unused-argument class HFS(FS): _type = "hfs" - _modules = ["hfs"] - _labelfs = fslabeling.HFSLabeling() - _formattable = True - _mkfs_class = fsmkfs.HFSMkfs - parted_system = fileSystemType["hfs"] register_device_format(HFS) -class AppleBootstrapFS(HFS): - _type = "appleboot" - _name = N_("Apple Bootstrap") - _min_size = Size("768 KiB") - _max_size = Size("1 MiB") - _supported = True - _mount_class = fsmount.AppleBootstrapFSMount - - @property - def supported(self): - return super(AppleBootstrapFS, self).supported and arch.is_pmac() - - -register_device_format(AppleBootstrapFS) - - class HFSPlus(FS): _type = "hfs+" _modules = ["hfsplus"] @@ -1477,7 +1438,7 @@ def _size_option(self, size): This is not impossible, since a special option for mounting is size=%. """ - return "size=%s" % (self._resize.size_fmt % size.convert_to(self._resize.unit)) + return "size=%sm" % size.convert_to(self._resize.unit) def _get_options(self): # Returns the regular mount options with the special size option, diff --git a/blivet/formats/swap.py b/blivet/formats/swap.py index 03aa3cc68..03be883cc 100644 --- a/blivet/formats/swap.py +++ b/blivet/formats/swap.py @@ -24,9 +24,9 @@ from parted import PARTITION_SWAP, fileSystemType from ..errors import FSWriteUUIDError, SwapSpaceError +from ..fstab import FSTabOptions from ..storage_log import log_method_call from ..tasks import availability -from ..tasks import fsuuid from . import DeviceFormat, register_device_format from ..size import Size from .. import udev @@ -78,6 +78,8 @@ def __init__(self, **kwargs): log_method_call(self, **kwargs) DeviceFormat.__init__(self, **kwargs) + self.fstab = FSTabOptions() + self.priority = kwargs.get("priority", -1) self.label = kwargs.get("label") @@ -114,8 +116,13 @@ def relabels(self): return True and self._plugin.available def label_format_ok(self, label): - """Returns True since no known restrictions on the label.""" - return True + """Check whether the given label is correct (16 characters or shorter).""" + try: + blockdev.swap.check_label(label) + except blockdev.SwapError: + return False + else: + return True def write_label(self, dry_run=False): """ Create a label for this format. @@ -154,7 +161,12 @@ def write_label(self, dry_run=False): def uuid_format_ok(self, uuid): """Check whether the given UUID is correct according to RFC 4122.""" - return fsuuid.FSUUID._check_rfc4122_uuid(uuid) + try: + blockdev.swap.check_uuid(uuid) + except blockdev.SwapError: + return False + else: + return True def _set_priority(self, priority): # pylint: disable=attribute-defined-outside-init @@ -228,19 +240,12 @@ def _teardown(self, **kwargs): def _create(self, **kwargs): log_method_call(self, device=self.device, type=self.type, status=self.status) - if self.uuid is None: - try: - blockdev.swap.mkswap(self.device, label=self.label) - except blockdev.SwapError as err: - raise SwapSpaceError(str(err)) - else: - if not self.uuid_format_ok(self.uuid): - raise FSWriteUUIDError("bad UUID format for swap filesystem") - try: - blockdev.swap.mkswap(self.device, label=self.label, - extra={"-U": self.uuid}) - except blockdev.SwapError as err: - raise SwapSpaceError(str(err)) + if self.uuid and not self.uuid_format_ok(self.uuid): + raise FSWriteUUIDError("bad UUID format for swap filesystem") + try: + blockdev.swap.mkswap(self.device, label=self.label, uuid=self.uuid) + except blockdev.SwapError as err: + raise SwapSpaceError(str(err)) register_device_format(SwapSpace) diff --git a/blivet/fstab.py b/blivet/fstab.py new file mode 100644 index 000000000..5981a9514 --- /dev/null +++ b/blivet/fstab.py @@ -0,0 +1,795 @@ +# fstab.py +# Fstab management. +# +# Copyright (C) 2023 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU Lesser General Public License v.2, or (at your option) any later +# version. This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY expressed or implied, including the implied +# warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU Lesser General Public License for more details. You should have +# received a copy of the GNU Lesser General Public License along with this +# program; if not, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks +# that are incorporated in the source code or documentation are not subject +# to the GNU Lesser General Public License and may only be used or +# replicated with the express permission of Red Hat, Inc. +# +# Red Hat Author(s): Jan Pokorny +# + +import os + +from libmount import Table, Fs, MNT_ITER_FORWARD +from libmount import Error as LibmountException + +import logging +log = logging.getLogger("blivet") + + +class FSTabOptions(object): + """ User prefered fstab settings object intended to be attached to device.format. + Set variables override otherwise automatically obtained values put into fstab. + """ + + def __init__(self): + self.freq = None + self.passno = None + + # prefered spec identification type; default "UUID" + # possible values: None, "UUID", "LABEL", "PARTLABEL", "PARTUUID", "PATH" + self.spec_type = None + + # list of fstab options to be used + self.mntops = [] + + +class FSTabEntry(object): + """ One processed line of fstab + """ + + def __init__(self, spec=None, file=None, vfstype=None, mntops=None, + freq=None, passno=None, comment=None, *, entry=None): + + # Note: "*" in arguments means that every following parameter can be used + # only with its key (e.g. "FSTabEntry(entry=Fs())") + + if entry is None: + self._entry = Fs() + else: + self._entry = entry + + if spec is not None: + self.spec = spec + if file is not None: + self.file = file + if vfstype is not None: + self.vfstype = vfstype + if mntops is not None: + self.mntops = mntops + if freq is not None: + self.freq = freq + if passno is not None: + self.passno = passno + if comment is not None: + self.comment = comment + + self._none_to_empty() + + def __repr__(self): + _comment = "" + if self._entry.comment not in ("", None): + _comment = "%s\n" % self._entry.comment + _line = "%s\t%s\t%s\t%s\t%s\t%s\t" % (self._entry.source, self._entry.target, self._entry.fstype, + self._entry.options, self._entry.freq, self._entry.passno) + return _comment + _line + + def __eq__(self, other): + if not isinstance(other, FSTabEntry): + return False + + if self._entry.source != other._entry.source: + return False + + if self._entry.target != other._entry.target: + return False + + if self._entry.fstype != other._entry.fstype: + return False + + if self._entry.options != other._entry.options: + return False + + if self._entry.freq != other._entry.freq: + return False + + if self._entry.passno != other._entry.passno: + return False + + return True + + def _none_to_empty(self): + """ Workaround function that internally replaces all None values with empty strings. + Reason: While libmount.Fs() initializes with parameters set to None, it does not + allow to store None as a valid value, blocking all value resets. + """ + + affected_params = [self._entry.source, + self._entry.target, + self._entry.fstype, + self._entry.options, + self._entry.comment] + + for param in affected_params: + if param is None: + param = "" + + @property + def entry(self): + return self._entry + + @entry.setter + def entry(self, value): + """ Setter for the whole internal entry value + + :param value: fstab entry + :type value: :class: `libmount.Fs` + """ + self._entry = value + + @property + def spec(self): + return self._entry.source if self._entry.source != "" else None + + @spec.setter + def spec(self, value): + self._entry.source = value if value is not None else "" + + @property + def file(self): + return self._entry.target if self._entry.target != "" else None + + @file.setter + def file(self, value): + self._entry.target = value if value is not None else "" + + @property + def vfstype(self): + return self._entry.fstype if self._entry.fstype != "" else None + + @vfstype.setter + def vfstype(self, value): + self._entry.fstype = value if value is not None else "" + + @property + def mntops(self): + """ Return mount options + + :returns: list of mount options or None when not set + :rtype: list of str + """ + + if self._entry.options == "": + return None + + return self._entry.options.split(',') + + def get_raw_mntops(self): + """ Return mount options + + :returns: comma separated string of mount options or None when not set + :rtype: str + """ + + return self._entry.options if self._entry.options != "" else None + + @mntops.setter + def mntops(self, values): + """ Set new mount options from the list of strings + + :param values: mount options (see man fstab(5) fs_mntops) + :type values: list of str + """ + + # libmount.Fs() internally stores options as a comma separated string + if values is None: + self._entry.options = "" + else: + self._entry.options = ','.join([x for x in values if x != ""]) + + def mntops_add(self, values): + """ Append new mount options to already existing ones + + :param values: mount options (see man fstab(5) fs_mntops) + :type values: list of str + """ + + self._entry.append_options(','.join([x for x in values if x != ""])) + + @property + def freq(self): + return self._entry.freq + + @freq.setter + def freq(self, value): + self._entry.freq = value + + @property + def passno(self): + return self._entry.passno + + @passno.setter + def passno(self, value): + if value not in [None, 0, 1, 2]: + raise ValueError("fstab field passno must be 0, 1 or 2 (got '%s')" % value) + self._entry.passno = value + + @property + def comment(self): + return self._entry.comment if self._entry.comment != "" else None + + @comment.setter + def comment(self, value): + if value is None: + self._entry.comment = "" + return + self._entry.comment = value + + def is_valid(self): + """ Verify that this instance has enough data for valid fstab entry + + :returns: False if any of the listed values is not set; otherwise True + :rtype: bool + """ + items = [self.spec, self.file, self.vfstype, self.mntops, self.freq, self.passno] + + # (Property getters replace empty strings with None) + return not any(x is None for x in items) + + +class FSTabManagerIterator(object): + """ Iterator class for FSTabManager + Iteration over libmount Table entries is weird - only one iterator can run at a time. + This class purpose is to mitigate that. + """ + + def __init__(self, fstab): + # To avoid messing up the libmount Table iterator which is singleton, + # set up independent entry list + entry = fstab._table.next_fs() + self.entries = [] + while entry: + self.entries.append(entry) + entry = fstab._table.next_fs() + self.cur_index = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.cur_index < len(self.entries): + self.cur_index += 1 + return FSTabEntry(entry=self.entries[self.cur_index - 1]) + raise StopIteration + + +class FSTabManager(object): + """ Read, write and modify fstab file. + This class is meant to work even without blivet. + However some of its methods require blivet and will not function without it. + """ + + def __init__(self, src_file=None, dest_file=None): + """ Initialize internal table; load the file if specified + + :keyword src_file: Path to fstab file which will be read + :type src_file: str + :keyword dest_file: Path to file which will be overwritten with results + :type src_file: str + """ + + self._table = Table() # Space for parsed fstab contents + self._table.enable_comments(True) + + self.src_file = src_file + self.dest_file = dest_file + + # prefered spec identification type; default "UUID" + # possible values: None, "UUID", "LABEL", "PARTLABEL", "PARTUUID", "PATH" + self.spec_type = None + + if self.src_file is not None: + # self.read() will raise an exception in case of invalid fstab path. + # This can interrupt object initialization thus preventing even setting read path + # to something else. + # This suppresses the exception. + if os.path.isfile(self.src_file): + self.read() + else: + # Acceptable at this point, but notify the user + log.info("Fstab file '%s' does not exist, setting fstab read path to None", self.src_file) + self.src_file = None + + def __deepcopy__(self, memo): + clone = FSTabManager(src_file=self.src_file, dest_file=self.dest_file) + clone._table = Table() + clone._table.enable_comments(True) + + # Two special variables for the first and last comment. When assigning None to them, libmount fails. + # Notice that we are copying the value from the instance of the same type. + if self._table.intro_comment is not None: + clone._table.intro_comment = self._table.intro_comment + if self._table.trailing_comment is not None: + clone._table.trailing_comment = self._table.trailing_comment + + entry = self._table.next_fs() + entries = [] + while entry: + entries.append(entry) + entry = self._table.next_fs() + + for entry in entries: + new_entry = self._copy_fs_entry(entry) + clone._table.add_fs(new_entry) + + return clone + + def __str__(self): + entry = self._table.next_fs() + entries_str = "" + while entry: + entries_str += repr(FSTabEntry(entry=entry)) + '\n' + entry = self._table.next_fs() + return entries_str + + def __iter__(self): + return FSTabManagerIterator(self) + + def _copy_fs_entry(self, entry): + """ Create copy of libmount.Fs() + """ + # Nope, it has to be done like this. Oh well... + new_entry = Fs() + new_entry.source = entry.source + new_entry.target = entry.target + new_entry.fstype = entry.fstype + new_entry.append_options(entry.options) + new_entry.freq = entry.freq + new_entry.passno = entry.passno + if entry.comment is not None: + new_entry.comment = entry.comment + return new_entry + + def _parser_errcb(self, tb, fname, line): # pylint: disable=unused-argument + """ Libmount interface error reporting function + """ + log.error("Fstab parse error '%s:%s'", fname, line) + return 1 + + def entry_from_device(self, device): + """ Generate FSTabEntry object based on given blivet Device + + :keyword device: device to process + :type device: :class: `blivet.devices.StorageDevice` + :returns: fstab entry object based on device + :rtype: :class: `FSTabEntry` + """ + + entry = FSTabEntry() + + entry.file = None + if device.format.mountable: + entry.file = device.format.mountpoint + elif device.format.type == "swap": + entry.file = "swap" + else: + raise ValueError("""cannot generate fstab entry from device '%s' because + it is neither mountable nor swap type""" % device.format.name) + + entry.spec = self._get_spec(device) + if entry.spec is None: + entry.spec = getattr(device, "fstab_spec", None) + + entry.vfstype = device.format.type + + return entry + + def read(self): + """ Read the fstab file from path stored in self.src_file. Resets currently loaded table contents. + """ + + # Reset table + self._table = Table() + self._table.enable_comments(True) + + # resolve which file to read + if self.src_file is None: + return + + self._table.errcb = self._parser_errcb + + if not os.path.isfile(self.src_file): + raise FileNotFoundError("Fstab file '%s' does not exist" % self.src_file) + + self._table.parse_fstab(self.src_file) + + def find_device(self, blivet, spec=None, mntops=None, blkid_tab=None, crypt_tab=None, *, entry=None): + """ Find a blivet device, based on spec or entry. Mount options can be used to refine the search. + If both entry and spec/mntops are given, spec/mntops are prioritized over entry values. + + :param blivet: Blivet instance with populated devicetree + :type blivet: :class: `Blivet` + :keyword spec: searched device specs (see man fstab(5) fs_spec) + :type spec: str + :keyword mntops: list of mount option strings (see man fstab(5) fs_mntops) + :type mnops: list + :keyword blkid_tab: Blkidtab object + :type blkid_tab: :class: `BlkidTab` + :keyword crypt_tab: Crypttab object + :type crypt_tab: :class: `CryptTab` + :keyword entry: fstab entry with its spec (and mntops) filled as an alternative input type + :type: :class: `FSTabEntry` + :returns: found device or None + :rtype: :class: `~.devices.StorageDevice` or None + """ + + _spec = spec or (entry.spec if entry is not None else None) + _mntops = mntops or (entry.mntops if entry is not None else None) + _mntops_str = ",".join(_mntops) if mntops is not None else None + + return blivet.devicetree.resolve_device(_spec, options=_mntops_str, blkid_tab=blkid_tab, crypt_tab=crypt_tab) + + def get_device(self, blivet, spec=None, file=None, vfstype=None, + mntops=None, blkid_tab=None, crypt_tab=None, *, entry=None): + """ Parse an fstab entry for a device and return the corresponding device from the devicetree. + If not found, try to create a new device based on given values. + Raises UnrecognizedFSTabError in case of invalid or incomplete data. + + :param blivet: Blivet instance with populated devicetree + :type blivet: :class: `Blivet` + :keyword spec: searched device specs (see man fstab(5) fs_spec) + :type spec: str + :keyword mntops: list of mount option strings (see man fstab(5) fs_mntops) + :type mnops: list + :keyword blkid_tab: Blkidtab object + :type blkid_tab: :class: `BlkidTab` + :keyword crypt_tab: Crypttab object + :type crypt_tab: :class: `CryptTab` + :keyword entry: fstab entry with its values filled as an alternative input type + :type: :class: `FSTabEntry` + :returns: found device + :rtype: :class: `~.devices.StorageDevice` + """ + + from blivet.formats import get_format + from blivet.devices import DirectoryDevice, FileDevice + from blivet.errors import UnrecognizedFSTabEntryError + + _spec = spec or (entry.spec if entry is not None else None) + _mntops = mntops or (entry.mntops if entry is not None else None) + _mntops_str = ",".join(_mntops) if mntops is not None else None + + # find device in the tree + device = blivet.devicetree.resolve_device(_spec, options=_mntops_str, blkid_tab=blkid_tab, crypt_tab=crypt_tab) + + if device is None: + if vfstype == "swap": + # swap file + device = FileDevice(_spec, + parents=blivet.devicetree.resolve_device(_spec), + fmt=get_format(vfstype, device=_spec, exists=True), + exists=True) + elif vfstype == "bind" or (_mntops is not None and "bind" in _mntops): + # bind mount... set vfstype so later comparison won't + # turn up false positives + vfstype = "bind" + + parents = blivet.devicetree.resolve_device(_spec) + device = DirectoryDevice(_spec, parents=parents, exists=True) + device.format = get_format("bind", device=device.path, exists=True) + + if device is None: + raise UnrecognizedFSTabEntryError("Could not resolve entry %s %s" % (_spec, vfstype)) + + fmt = get_format(vfstype, device=device.path, exists=True) + if vfstype != "auto" and None in (device.format.type, fmt.type): + raise UnrecognizedFSTabEntryError("Unrecognized filesystem type for %s: '%s'" % (_spec, vfstype)) + + if hasattr(device.format, "mountpoint"): + device.format.mountpoint = file + + device.format.options = _mntops + + return device + + def add_entry(self, spec=None, file=None, vfstype=None, mntops=None, + freq=None, passno=None, comment=None, *, entry=None): + """ Add a new entry into the table + If both entry and other values are given, these values are prioritized over entry values. + If mntops/freq/passno is not set uses their respective default values. + + :keyword spec: device specs (see man fstab(5) fs_spec) + :type spec: str + :keyword file: device mount path (see man fstab(5) fs_file) + :type file: str + :keyword vfstype: device file system type (see man fstab(5) fs_vfstype) + :type vfstype: str + :keyword mntops: list of mount option strings (see man fstab(5) fs_mntops) + :type mnops: list + :keyword freq: whether to dump the filesystem (see man fstab(5) fs_freq) + :type freq: int + :keyword passno: fsck order or disable fsck if 0 (see man fstab(5) fs_passno) + :type passno: int + :keyword comment: multiline comment added to fstab before entry; each line will be prefixed with "# " + :type comment: str + :keyword entry: fstab entry as an alternative input type + :type: :class: `FSTabEntry` + """ + + # Default mount options + if mntops is None: + mntops = ['defaults'] + + # Use existing FSTabEntry or create a new one + _entry = entry or FSTabEntry() + + if spec is not None: + _entry.spec = spec + + if file is not None: + _entry.file = file + + if vfstype is not None: + _entry.vfstype = vfstype + + if mntops is not None: + _entry.mntops = mntops + elif _entry.mntops is None: + _entry.mntops = ['defaults'] + + if freq is not None: + # Whether the fs should be dumped by dump(8) (default: 0, i.e. no) + _entry.freq = freq + elif _entry.freq is None: + _entry.freq = 0 + + if passno is not None: + _entry.passno = passno + elif _entry.passno is None: + # 'passno' represents order of fsck run at the boot time (default: 0, i.e. disabled). + # '/' should have 1, other checked should have 2 + if _entry.file == '/': + _entry.passno = 1 + elif _entry.file.startswith('/boot'): + _entry.passno = 2 + else: + _entry.passno = 0 + + if comment is not None: + # Add '# ' at the start of any comment line and newline to the end of comment. + # Has to be done here since libmount won't do it. + modif_comment = '# ' + comment.replace('\n', '\n# ') + '\n' + _entry.comment = modif_comment + + self._table.add_fs(_entry.entry) + + def remove_entry(self, spec=None, file=None, *, entry=None): + """ Find and remove entry from fstab based on spec/file. + If both entry and spec/file are given, spec/file are prioritized over entry values. + + :keyword spec: device specs (see man fstab(5) fs_spec) + :type spec: str + :keyword file: device mount path (see man fstab(5) fs_file) + :type file: str + :keyword entry: fstab entry as an alternative input type + :type: :class: `FSTabEntry` + """ + + fs = self.find_entry(spec, file, entry=entry) + if fs: + self._table.remove_fs(fs.entry) + else: + raise ValueError("Cannot remove entry (%s) from fstab, because it is not there" % entry) + + def write(self, dest_file=None): + """ Commit the self._table into the self._dest_file. Setting dest_file parameter overrides + writing path with its value. + + :keyword dest_file: When set, writes fstab to the path specified in it + :type dest_file: str + """ + + if dest_file is None: + dest_file = self.dest_file + if dest_file is None: + log.info("Fstab path for writing was not specified") + return + + # Output sanity check to prevent saving an incomplete file entries + # since libmount happily inserts incomplete lines into the fstab. + # Invalid lines should be skipped, but the whole table is written at once. + # Also the incomplete lines need to be preserved in the object. + # Conclusion: Create the second table, prune invalid/incomplete lines and write it. + + clean_table = Table() + clean_table.enable_comments(True) + + # Two special variables for the first and last comment. When assigning None libmount fails. + # Notice that we are copying the value from the same type instance. + if self._table.intro_comment is not None: + clean_table.intro_comment = self._table.intro_comment + if self._table.trailing_comment is not None: + clean_table.trailing_comment = self._table.trailing_comment + + entry = self._table.next_fs() + while entry: + if FSTabEntry(entry=entry).is_valid(): + new_entry = self._copy_fs_entry(entry) + clean_table.add_fs(new_entry) + else: + log.warning("Fstab entry: '%s' is incomplete, it will not be written into the file", entry) + entry = self._table.next_fs() + + if os.path.exists(dest_file): + clean_table.replace_file(dest_file) + else: + try: + clean_table.write_file(dest_file) + except Exception as e: # pylint: disable=broad-except + # libmount throws general Exception if underlying directories do not exist. Okay... + if str(e) == "No such file or directory": + log.info("Underlying directory of fstab '%s' does not exist. creating...", dest_file) + os.makedirs(os.path.split(dest_file)[0]) + else: + raise + + def find_entry(self, spec=None, file=None, *, entry=None): + """ Return the line of loaded fstab with given spec and/or file. + If both entry and spec/file are given, spec/file are prioritized over entry values. + + :keyword spec: searched device specs (see man fstab(5) fs_spec) + :type spec: str + :keyword file: device mount path (see man fstab(5) fs_file) + :type file: str + :keyword entry: fstab entry as an alternative input type + :type: :class: `FSTabEntry` + :returns: found fstab entry object + :rtype: :class: `FSTabEntry` or None + """ + + _spec = spec or (entry.spec if entry is not None else None) + _file = file or (entry.file if entry is not None else None) + + found_entry = None + + if _spec is not None and _file is not None: + try: + found_entry = self._table.find_pair(_spec, _file, MNT_ITER_FORWARD) + except LibmountException: + return None + return FSTabEntry(entry=found_entry) + + if _spec is not None: + try: + found_entry = self._table.find_source(_spec, MNT_ITER_FORWARD) + except LibmountException: + return None + return FSTabEntry(entry=found_entry) + + if file is not None: + try: + found_entry = self._table.find_target(file, MNT_ITER_FORWARD) + except LibmountException: + return None + return FSTabEntry(entry=found_entry) + + return None + + def _get_spec(self, device): + """ Resolve which device spec should be used and return it in a form accepted by fstab. + Returns None if desired spec was not found + """ + + # Use device specific spec type if it is set + # Use "globally" set (on FSTabManager level) spec type otherwise + + spec = None + spec_type = device.format.fstab.spec_type or self.spec_type + + if spec_type == "LABEL" and device.format.label: + spec = "LABEL=%s" % device.format.label + elif spec_type == "PARTUUID" and device.uuid: + spec = "PARTUUID=%s" % device.uuid + elif spec_type == "PARTLABEL" and device.format.name: + spec = "PARTLABEL=%s" % device.format.name + elif spec_type == "PATH": + spec = device.path + elif device.format.uuid: + # default choice + spec = "UUID=%s" % device.format.uuid + else: + # if everything else failed, let blivet decide + return None + + return spec + + def update(self, action, bae_entry): + """ Update fstab based on action type and device. Does not commit changes to a file. + + :param action: just executed blivet action + :type action: :class: `~.deviceaction.DeviceAction` + :param bae_entry: fstab entry based on action.device before action.execute was called + :type bae_entry: :class: `FSTabEntry` or None + """ + + if not action._applied: + return + + if action.is_destroy and bae_entry is not None: + # remove destroyed device from the fstab + self.remove_entry(entry=bae_entry) + return + + if action.is_create and action.is_device and action.device.type == "luks/dm-crypt": + # when creating luks format, two actions are made. Device creation + # does not have UUID assigned yet, so we skip that one + return + + if action.is_create and action.device.format.mountable: + # add the device to the fstab + # make sure it is not already present there + try: + entry = self.entry_from_device(action.device) + except ValueError: + # this device should not be at fstab + found = None + else: + found = self.find_entry(entry=entry) + + # get correct spec type to use (if None, the one already present in entry is used) + spec = self._get_spec(action.device) + + if found is None and action.device.format.mountpoint is not None: + # device is not present in fstab and has a defined mountpoint => add it + self.add_entry(spec=spec, + mntops=action.device.format.fstab.mntops, + freq=action.device.format.fstab.freq, + passno=action.device.format.fstab.passno, + entry=entry) + elif found and found.spec != spec and action.device.format.mountpoint is not None: + # allow change of spec of existing devices + self.remove_entry(entry=found) + self.add_entry(spec=spec, + mntops=action.device.format.fstab.mntops, + freq=action.device.format.fstab.freq, + passno=action.device.format.fstab.passno, + entry=found) + elif found and found.file != action.device.format.mountpoint and action.device.format.mountpoint is not None: + # device already exists in fstab but with a different mountpoint => add it + self.add_entry(spec=spec, + file=action.device.format.mountpoint, + mntops=action.device.format.fstab.mntops, + freq=action.device.format.fstab.freq, + passno=action.device.format.fstab.passno, + entry=found) + return + + if action.is_configure and action.is_format and bae_entry is not None: + # Handle change of the mountpoint: + # Change its value if it is defined, remove the fstab entry if it is None + + # get correct spec type to use (if None, the one already present in entry is used) + spec = self._get_spec(action.device) + + if action.device.format.mountpoint is not None and bae_entry.file != action.device.format.mountpoint: + self.remove_entry(entry=bae_entry) + self.add_entry(spec=spec, + file=action.device.format.mountpoint, + mntops=action.device.format.fstab.mntops, + freq=action.device.format.fstab.freq, + passno=action.device.format.fstab.passno, + entry=bae_entry) + elif action.device.format.mountpoint is None: + self.remove_entry(entry=bae_entry) diff --git a/blivet/iscsi.py b/blivet/iscsi.py index 3b243f6fb..b63468e9b 100644 --- a/blivet/iscsi.py +++ b/blivet/iscsi.py @@ -33,7 +33,9 @@ import gi gi.require_version("GLib", "2.0") +gi.require_version("BlockDev", "3.0") from gi.repository import GLib +from gi.repository import BlockDev import logging log = logging.getLogger("blivet") @@ -314,7 +316,10 @@ def _start_ibft(self): # Make sure iscsi_ibft is loaded otherwise any atttempts will fail with # 'Could not get list of targets from firmware. (err 21)' - util.run_program(['modprobe', '-a', 'iscsi_ibft']) + try: + BlockDev.utils.load_kernel_module("iscsi_ibft", None) + except BlockDev.UtilsError as e: + log.error("failed to load iscsi_ibft: %s", str(e)) args = GLib.Variant("(a{sv})", ([], )) try: @@ -394,7 +399,12 @@ def startup(self): os.makedirs(fulldir, 0o755) log.info("iSCSI startup") - util.run_program(['modprobe', '-a'] + ISCSI_MODULES) + for module in ISCSI_MODULES: + try: + BlockDev.utils.load_kernel_module(module, None) + except BlockDev.UtilsError as e: + log.error("failed to load %s: %s", module, str(e)) + # iscsiuio is needed by Broadcom offload cards (bnx2i). Currently # not present in iscsi-initiator-utils for Fedora. iscsiuio = shutil.which('iscsiuio') diff --git a/blivet/populator/helpers/__init__.py b/blivet/populator/helpers/__init__.py index 972b76226..4376a7645 100644 --- a/blivet/populator/helpers/__init__.py +++ b/blivet/populator/helpers/__init__.py @@ -5,7 +5,7 @@ from .formatpopulator import FormatPopulator from .btrfs import BTRFSFormatPopulator -from .boot import AppleBootFormatPopulator, EFIFormatPopulator, MacEFIFormatPopulator +from .boot import EFIFormatPopulator, MacEFIFormatPopulator from .disk import DiskDevicePopulator, iScsiDevicePopulator, FCoEDevicePopulator, MDBiosRaidDevicePopulator, DASDDevicePopulator, ZFCPDevicePopulator, NVDIMMNamespaceDevicePopulator, NVMeNamespaceDevicePopulator, NVMeFabricsNamespaceDevicePopulator from .disklabel import DiskLabelFormatPopulator from .dm import DMDevicePopulator diff --git a/blivet/populator/helpers/boot.py b/blivet/populator/helpers/boot.py index 4340b28f0..ca1496755 100644 --- a/blivet/populator/helpers/boot.py +++ b/blivet/populator/helpers/boot.py @@ -64,9 +64,3 @@ def match(cls, data, device): except AttributeError: # just in case device.parted_partition has no name attr return False - - -class AppleBootFormatPopulator(BootFormatPopulator): - _type_specifier = "appleboot" - _base_type_specifier = "hfs" - _bootable = True diff --git a/blivet/tasks/availability.py b/blivet/tasks/availability.py index 4b947d6cd..626b2633c 100644 --- a/blivet/tasks/availability.py +++ b/blivet/tasks/availability.py @@ -251,6 +251,50 @@ def availability_errors(self, resource): return [] +class BlockDevFSMethod(Method): + + """ Methods for when application is actually a libblockdev FS plugin functionality. """ + + def __init__(self, operation, check_fn, fstype): + """ Initializer. + + :param operation: operation to check for support availability + :param check_fn: function used to check for support availability + :param fstype: filesystem type to check for the support availability + """ + self.operation = operation + self.check_fn = check_fn + self.fstype = fstype + self._availability_errors = None + + def availability_errors(self, resource): + """ Returns [] if the plugin is loaded and functionality available. + + :param resource: a libblockdev plugin + :type resource: :class:`ExternalResource` + + :returns: [] if the name of the plugin is loaded + :rtype: list of str + """ + if "fs" not in blockdev.get_available_plugin_names(): + return ["libblockdev fs plugin not loaded"] + else: + try: + if self.operation in (FSOperation.UUID, FSOperation.LABEL, FSOperation.INFO): + avail, utility = self.check_fn(self.fstype) + elif self.operation == FSOperation.RESIZE: + avail, _mode, utility = self.check_fn(self.fstype) + elif self.operation == FSOperation.MKFS: + avail, _options, utility = self.check_fn(self.fstype) + except blockdev.FSError as e: + return [str(e)] + if not avail: + return ["libblockdev fs plugin is loaded but some required runtime " + "dependencies are not available: %s" % utility] + else: + return [] + + class DBusMethod(Method): """ Methods for when application is actually a DBus service. """ @@ -334,6 +378,11 @@ def blockdev_plugin(name, blockdev_method): return ExternalResource(blockdev_method, name) +def blockdev_fs_plugin_operation(blockdev_fs_method): + """ Construct an external resource that is a libblockdev FS plugin functionality. """ + return ExternalResource(blockdev_fs_method, "libblockdev FS plugin method") + + def dbus_service(name, dbus_method): """ Construct an external resource that is a DBus service. """ return ExternalResource(dbus_method, name) @@ -419,23 +468,17 @@ def available_resource(name): blockdev.LVMTechMode.MODIFY)}) BLOCKDEV_LVM_TECH = BlockDevMethod(BLOCKDEV_LVM) -if hasattr(blockdev.LVMTech, "VDO"): - BLOCKDEV_LVM_VDO = BlockDevTechInfo(plugin_name="lvm", - check_fn=blockdev.lvm_is_tech_avail, - technologies={blockdev.LVMTech.VDO: (blockdev.LVMTechMode.CREATE | - blockdev.LVMTechMode.REMOVE | - blockdev.LVMTechMode.QUERY)}) - BLOCKDEV_LVM_TECH_VDO = BlockDevMethod(BLOCKDEV_LVM_VDO) -else: - BLOCKDEV_LVM_TECH_VDO = _UnavailableMethod(error_msg="Installed version of libblockdev doesn't support LVM VDO technology") - -if hasattr(blockdev.LVMTech, "SHARED"): - BLOCKDEV_LVM_SHARED = BlockDevTechInfo(plugin_name="lvm", - check_fn=blockdev.lvm_is_tech_avail, - technologies={blockdev.LVMTech.SHARED: blockdev.LVMTechMode.MODIFY}) # pylint: disable=no-member - BLOCKDEV_LVM_TECH_SHARED = BlockDevMethod(BLOCKDEV_LVM_SHARED) -else: - BLOCKDEV_LVM_TECH_SHARED = _UnavailableMethod(error_msg="Installed version of libblockdev doesn't support shared LVM technology") +BLOCKDEV_LVM_VDO = BlockDevTechInfo(plugin_name="lvm", + check_fn=blockdev.lvm_is_tech_avail, + technologies={blockdev.LVMTech.VDO: (blockdev.LVMTechMode.CREATE | + blockdev.LVMTechMode.REMOVE | + blockdev.LVMTechMode.QUERY)}) +BLOCKDEV_LVM_TECH_VDO = BlockDevMethod(BLOCKDEV_LVM_VDO) + +BLOCKDEV_LVM_SHARED = BlockDevTechInfo(plugin_name="lvm", + check_fn=blockdev.lvm_is_tech_avail, + technologies={blockdev.LVMTech.SHARED: blockdev.LVMTechMode.MODIFY}) +BLOCKDEV_LVM_TECH_SHARED = BlockDevMethod(BLOCKDEV_LVM_SHARED) # libblockdev mdraid plugin required technologies and modes BLOCKDEV_MD_ALL_MODES = (blockdev.MDTechMode.CREATE | @@ -465,6 +508,55 @@ def available_resource(name): technologies={blockdev.SwapTech.SWAP: BLOCKDEV_SWAP_ALL_MODES}) BLOCKDEV_SWAP_TECH = BlockDevMethod(BLOCKDEV_SWAP) +# libblockdev fs plugin required technologies +# no modes, we will check for specific functionality separately +BLOCKDEV_FS = BlockDevTechInfo(plugin_name="fs", + check_fn=blockdev.fs_is_tech_avail, + technologies={blockdev.FSTech.GENERIC: 0, + blockdev.FSTech.MOUNT: 0, + blockdev.FSTech.EXT2: 0, + blockdev.FSTech.EXT3: 0, + blockdev.FSTech.EXT4: 0, + blockdev.FSTech.XFS: 0, + blockdev.FSTech.VFAT: 0, + blockdev.FSTech.NTFS: 0}) +BLOCKDEV_FS_TECH = BlockDevMethod(BLOCKDEV_FS) + + +# libblockdev fs plugin methods +class FSOperation(): + UUID = 0 + LABEL = 1 + RESIZE = 2 + INFO = 3 + MKFS = 4 + + +BLOCKDEV_EXT_UUID = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.UUID, blockdev.fs.can_set_uuid, "ext2")) +BLOCKDEV_XFS_UUID = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.UUID, blockdev.fs.can_set_uuid, "xfs")) +BLOCKDEV_NTFS_UUID = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.UUID, blockdev.fs.can_set_uuid, "ntfs")) + +BLOCKDEV_EXT_LABEL = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.LABEL, blockdev.fs.can_set_label, "ext2")) +BLOCKDEV_XFS_LABEL = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.LABEL, blockdev.fs.can_set_label, "xfs")) +BLOCKDEV_VFAT_LABEL = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.LABEL, blockdev.fs.can_set_label, "vfat")) +BLOCKDEV_NTFS_LABEL = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.LABEL, blockdev.fs.can_set_label, "ntfs")) + +BLOCKDEV_EXT_RESIZE = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.RESIZE, blockdev.fs.can_resize, "ext2")) +BLOCKDEV_XFS_RESIZE = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.RESIZE, blockdev.fs.can_resize, "xfs")) +BLOCKDEV_NTFS_RESIZE = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.RESIZE, blockdev.fs.can_resize, "ntfs")) + +BLOCKDEV_EXT_INFO = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.INFO, blockdev.fs.can_get_size, "ext2")) +BLOCKDEV_XFS_INFO = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.INFO, blockdev.fs.can_get_size, "xfs")) +BLOCKDEV_NTFS_INFO = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.INFO, blockdev.fs.can_get_size, "ntfs")) +BLOCKDEV_VFAT_INFO = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.INFO, blockdev.fs.can_get_size, "vfat")) + +BLOCKDEV_BTRFS_MKFS = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.MKFS, blockdev.fs.can_mkfs, "btrfs")) +BLOCKDEV_EXT_MKFS = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.MKFS, blockdev.fs.can_mkfs, "ext2")) +BLOCKDEV_XFS_MKFS = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.MKFS, blockdev.fs.can_mkfs, "xfs")) +BLOCKDEV_NTFS_MKFS = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.MKFS, blockdev.fs.can_mkfs, "ntfs")) +BLOCKDEV_VFAT_MKFS = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.MKFS, blockdev.fs.can_mkfs, "vfat")) +BLOCKDEV_F2FS_MKFS = blockdev_fs_plugin_operation(BlockDevFSMethod(FSOperation.MKFS, blockdev.fs.can_mkfs, "f2fs")) + # libblockdev plugins # we can't just check if the plugin is loaded, we also need to make sure # that all technologies required by us our supported (some may be missing @@ -481,67 +573,29 @@ def available_resource(name): BLOCKDEV_MDRAID_PLUGIN = blockdev_plugin("libblockdev mdraid plugin", BLOCKDEV_MD_TECH) BLOCKDEV_MPATH_PLUGIN = blockdev_plugin("libblockdev mpath plugin", BLOCKDEV_MPATH_TECH) BLOCKDEV_SWAP_PLUGIN = blockdev_plugin("libblockdev swap plugin", BLOCKDEV_SWAP_TECH) - -# applications with versions -# we need e2fsprogs newer than 1.41 and we are checking the version by running -# the "e2fsck" tool and parsing its ouput for version number -E2FSPROGS_INFO = AppVersionInfo(app_name="e2fsck", - required_version="1.41.0", - version_opt="-V", - version_regex=r"e2fsck ([0-9+\.]+)[\-rc0-9+]* .*") -E2FSPROGS_VERSION = VersionMethod(E2FSPROGS_INFO) - - -# new version of dosftools changed behaviour of many tools -DOSFSTOOLS_INFO = AppVersionInfo(app_name="mkdosfs", - required_version="4.2", - version_opt="--help", - version_regex=r"mkfs\.fat ([0-9+\.]+) .*") -DOSFSTOOLS_VERSION = VersionMethod(DOSFSTOOLS_INFO) +BLOCKDEV_FS_PLUGIN = blockdev_plugin("libblockdev fs plugin", BLOCKDEV_FS_TECH) # applications -DEBUGREISERFS_APP = application("debugreiserfs") -DF_APP = application("df") +# fsck DOSFSCK_APP = application("dosfsck") -DOSFSLABEL_APP = application("dosfslabel") -DUMPE2FS_APP = application_by_version("dumpe2fs", E2FSPROGS_VERSION) -E2FSCK_APP = application_by_version("e2fsck", E2FSPROGS_VERSION) -E2LABEL_APP = application_by_version("e2label", E2FSPROGS_VERSION) +E2FSCK_APP = application("e2fsck") FSCK_HFSPLUS_APP = application("fsck.hfsplus") -HFORMAT_APP = application("hformat") -JFSTUNE_APP = application("jfs_tune") -KPARTX_APP = application("kpartx") -LVMDEVICES = application("lvmdevices") -MKDOSFS_APP = application("mkdosfs") -MKDOSFS_NEW_APP = application_by_version("mkdosfs", DOSFSTOOLS_VERSION) -MKE2FS_APP = application_by_version("mke2fs", E2FSPROGS_VERSION) -MKFS_BTRFS_APP = application("mkfs.btrfs") -MKFS_GFS2_APP = application("mkfs.gfs2") -MKFS_HFSPLUS_APP = application("mkfs.hfsplus") -MKFS_JFS_APP = application("mkfs.jfs") -MKFS_XFS_APP = application("mkfs.xfs") -MKNTFS_APP = application("mkntfs") -MKREISERFS_APP = application("mkreiserfs") -MLABEL_APP = application("mlabel") -MULTIPATH_APP = application("multipath") -NTFSINFO_APP = application("ntfsinfo") -NTFSLABEL_APP = application("ntfslabel") -NTFSRESIZE_APP = application("ntfsresize") -REISERFSTUNE_APP = application("reiserfstune") -RESIZE2FS_APP = application_by_version("resize2fs", E2FSPROGS_VERSION) -TUNE2FS_APP = application_by_version("tune2fs", E2FSPROGS_VERSION) -XFSADMIN_APP = application("xfs_admin") -XFSDB_APP = application("xfs_db") -XFSFREEZE_APP = application("xfs_freeze") -XFSRESIZE_APP = application("xfs_growfs") XFSREPAIR_APP = application("xfs_repair") - FSCK_F2FS_APP = application("fsck.f2fs") -MKFS_F2FS_APP = application("mkfs.f2fs") -MOUNT_APP = application("mount") +# resize (for min size) +NTFSRESIZE_APP = application("ntfsresize") +RESIZE2FS_APP = application("resize2fs") + +# mkfs +MKFS_GFS2_APP = application("mkfs.gfs2") +MKFS_HFSPLUS_APP = application("mkfs.hfsplus") +# other +KPARTX_APP = application("kpartx") +MULTIPATH_APP = application("multipath") STRATISPREDICTUSAGE_APP = application("stratis-predict-usage") +# dbus services STRATIS_SERVICE_METHOD = DBusMethod(STRATIS_SERVICE, STRATIS_PATH) STRATIS_DBUS = dbus_service("stratis", STRATIS_SERVICE_METHOD) diff --git a/blivet/tasks/fsinfo.py b/blivet/tasks/fsinfo.py index b5432e9ce..ea092b0e3 100644 --- a/blivet/tasks/fsinfo.py +++ b/blivet/tasks/fsinfo.py @@ -24,12 +24,15 @@ from six import add_metaclass from ..errors import FSError -from .. import util from . import availability from . import fstask from . import task +import gi +gi.require_version("BlockDev", "3.0") +from gi.repository import BlockDev + @add_metaclass(abc.ABCMeta) class FSInfo(task.BasicApplication, fstask.FSTask): @@ -38,17 +41,9 @@ class FSInfo(task.BasicApplication, fstask.FSTask): description = "filesystem info" - options = abc.abstractproperty( - doc="Options for invoking the application.") - - @property - def _info_command(self): - """ Returns the command for reading filesystem information. - - :returns: a list of appropriate options - :rtype: list of str - """ - return [str(self.ext)] + self.options + [self.fs.device] + @abc.abstractmethod + def _get_info(self): + raise NotImplementedError def do_task(self): # pylint: disable=arguments-differ """ Returns information from the command. @@ -61,41 +56,32 @@ def do_task(self): # pylint: disable=arguments-differ if error_msgs: raise FSError("\n".join(error_msgs)) - error_msg = None try: - (rc, out) = util.run_program_and_capture_output(self._info_command) - if rc: - error_msg = "failed to gather fs info: %s" % rc - except OSError as e: - error_msg = "failed to gather fs info: %s" % e - if error_msg: - raise FSError(error_msg) - return out + info = self._get_info() + except BlockDev.FSError as e: + raise FSError("failed to gather fs info: %s" % e) + return info class Ext2FSInfo(FSInfo): - ext = availability.DUMPE2FS_APP - options = ["-h"] + ext = availability.BLOCKDEV_EXT_INFO - -class JFSInfo(FSInfo): - ext = availability.JFSTUNE_APP - options = ["-l"] + def _get_info(self): + return BlockDev.fs.ext2_get_info(self.fs.device) class NTFSInfo(FSInfo): - ext = availability.NTFSINFO_APP - options = ["-m"] - + ext = availability.BLOCKDEV_NTFS_INFO -class ReiserFSInfo(FSInfo): - ext = availability.DEBUGREISERFS_APP - options = [] + def _get_info(self): + return BlockDev.fs.ntfs_get_info(self.fs.device) class XFSInfo(FSInfo): - ext = availability.XFSDB_APP - options = ["-c", "sb 0", "-c", "p dblocks", "-c", "p blocksize", "-r"] + ext = availability.BLOCKDEV_XFS_INFO + + def _get_info(self): + return BlockDev.fs.xfs_get_info(self.fs.device) class UnimplementedFSInfo(fstask.UnimplementedFSTask): diff --git a/blivet/tasks/fslabeling.py b/blivet/tasks/fslabeling.py index 5bc0111d1..11b2280bb 100644 --- a/blivet/tasks/fslabeling.py +++ b/blivet/tasks/fslabeling.py @@ -23,7 +23,9 @@ from six import add_metaclass -from . import availability +import gi +gi.require_version("BlockDev", "3.0") +from gi.repository import BlockDev @add_metaclass(abc.ABCMeta) @@ -32,9 +34,6 @@ class FSLabeling(object): """An abstract class that represents filesystem labeling actions. """ - default_label = abc.abstractproperty( - doc="Default label set on this filesystem at creation.") - @classmethod @abc.abstractmethod def label_format_ok(cls, label): @@ -46,65 +45,39 @@ def label_format_ok(cls, label): """ raise NotImplementedError - -class Ext2FSLabeling(FSLabeling): - - default_label = "" - - @classmethod - def label_format_ok(cls, label): - return len(label) < 17 - - -class FATFSLabeling(FSLabeling): - - default_label = "" if availability.MKDOSFS_NEW_APP.available else "NO NAME" - @classmethod - def label_format_ok(cls, label): - return len(label) < 12 - + def _blockdev_check_label(cls, fstype, label): + try: + BlockDev.fs.check_label(fstype, label) + except BlockDev.FSError: + return False + else: + return True -class JFSLabeling(FSLabeling): - default_label = "" +class Ext2FSLabeling(FSLabeling): @classmethod def label_format_ok(cls, label): - return len(label) < 17 - + return cls._blockdev_check_label("ext2", label) -class ReiserFSLabeling(FSLabeling): - default_label = "" +class FATFSLabeling(FSLabeling): @classmethod def label_format_ok(cls, label): - return len(label) < 17 + return cls._blockdev_check_label("vfat", label) class XFSLabeling(FSLabeling): - default_label = "" - @classmethod def label_format_ok(cls, label): - return ' ' not in label and len(label) < 13 - - -class HFSLabeling(FSLabeling): - - default_label = "Untitled" - - @classmethod - def label_format_ok(cls, label): - return ':' not in label and len(label) < 28 and len(label) > 0 + return cls._blockdev_check_label("xfs", label) class HFSPlusLabeling(FSLabeling): - default_label = "Untitled" - @classmethod def label_format_ok(cls, label): return ':' not in label and 0 < len(label) < 129 @@ -112,17 +85,13 @@ def label_format_ok(cls, label): class NTFSLabeling(FSLabeling): - default_label = "" - @classmethod def label_format_ok(cls, label): - return len(label) < 129 + return cls._blockdev_check_label("ntfs", label) class F2FSLabeling(FSLabeling): - default_label = "" - @classmethod def label_format_ok(cls, label): - return len(label) < 513 + return cls._blockdev_check_label("f2fs", label) diff --git a/blivet/tasks/fsminsize.py b/blivet/tasks/fsminsize.py index 620b91f73..eac2fe567 100644 --- a/blivet/tasks/fsminsize.py +++ b/blivet/tasks/fsminsize.py @@ -91,15 +91,7 @@ def _extract_block_size(self): if self.fs._current_info is None: return None - block_size = None - for line in (l.strip() for l in self.fs._current_info.splitlines() if l.startswith("Block size:")): - try: - block_size = int(line.split(" ")[-1]) - break - except ValueError: - continue - - return Size(block_size) if block_size else None + return Size(self.fs._current_info.block_size) def _extract_num_blocks(self, info): """ Extract the number of blocks from the resizefs info. diff --git a/blivet/tasks/fsmkfs.py b/blivet/tasks/fsmkfs.py index 408b0ef7a..062d54777 100644 --- a/blivet/tasks/fsmkfs.py +++ b/blivet/tasks/fsmkfs.py @@ -31,6 +31,10 @@ from . import fstask from . import task +import gi +gi.require_version("BlockDev", "3.0") +from gi.repository import BlockDev + @add_metaclass(abc.ABCMeta) class FSMkfsTask(fstask.FSTask): @@ -211,55 +215,6 @@ def do_task(self, options=None, label=False, set_uuid=False, nodiscard=False): raise FSError("format failed: %s" % ret) -class BTRFSMkfs(FSMkfs): - ext = availability.MKFS_BTRFS_APP - label_option = None - nodiscard_option = ["--nodiscard"] - - def get_uuid_args(self, uuid): - return ["-U", uuid] - - @property - def args(self): - return [] - - -class Ext2FSMkfs(FSMkfs): - ext = availability.MKE2FS_APP - label_option = "-L" - nodiscard_option = ["-E", "nodiscard"] - - _opts = [] - - def get_uuid_args(self, uuid): - return ["-U", uuid] - - @property - def args(self): - return self._opts + (["-T", self.fs.fsprofile] if self.fs.fsprofile else []) - - -class Ext3FSMkfs(Ext2FSMkfs): - _opts = ["-t", "ext3"] - - -class Ext4FSMkfs(Ext3FSMkfs): - _opts = ["-t", "ext4"] - - -class FATFSMkfs(FSMkfs): - ext = availability.MKDOSFS_APP - label_option = "-n" - nodiscard_option = None - - def get_uuid_args(self, uuid): - return ["-i", uuid.replace('-', '')] - - @property - def args(self): - return [] - - class GFS2Mkfs(FSMkfs): ext = availability.MKFS_GFS2_APP label_option = None @@ -271,17 +226,6 @@ def args(self): return ["-j", "1", "-p", "lock_nolock", "-O"] -class HFSMkfs(FSMkfs): - ext = availability.HFORMAT_APP - label_option = "-l" - nodiscard_option = None - get_uuid_args = None - - @property - def args(self): - return [] - - class HFSPlusMkfs(FSMkfs): ext = availability.MKFS_HFSPLUS_APP label_option = "-v" @@ -293,67 +237,106 @@ def args(self): return [] -class JFSMkfs(FSMkfs): - ext = availability.MKFS_JFS_APP - label_option = "-L" - nodiscard_option = None - get_uuid_args = None +@add_metaclass(abc.ABCMeta) +class FSBlockDevMkfs(task.BasicApplication, FSMkfsTask): - @property - def args(self): - return ["-q"] + """An abstract class that represents filesystem creation actions. """ + description = "mkfs" + can_nodiscard = False + can_set_uuid = False + can_label = False + fstype = None + def do_task(self, options=None, label=False, set_uuid=False, nodiscard=False): + """Create the format on the device and label if possible and desired. -class NTFSMkfs(FSMkfs): - ext = availability.MKNTFS_APP - label_option = "-L" - nodiscard_option = None - get_uuid_args = None + :param options: any special options, may be None + :type options: list of str or NoneType + :param bool label: whether to label while creating, default is False + :param bool set_uuid: whether to set an UUID while creating, default + is False + """ + # pylint: disable=arguments-differ + error_msgs = self.availability_errors + if error_msgs: + raise FSError("\n".join(error_msgs)) - @property - def args(self): - # -F (force) to allow creating the format on disks, -f (fast) to skip zeroing the device - return ["-F", "-f"] + if set_uuid and self.fs.uuid and not self.fs.uuid_format_ok(self.fs.uuid): + raise FSWriteUUIDError("Choosing not to apply UUID (%s) during" + " creation of filesystem %s. UUID format" + " is unacceptable for this filesystem." + % (self.fs.uuid, self.fs.type)) + if label and self.fs.label and not self.fs.label_format_ok(self.fs.label): + raise FSWriteLabelError("Choosing not to apply label (%s) during" + " creation of filesystem %s. Label format" + " is unacceptable for this filesystem." + % (self.fs.label, self.fs.type)) + try: + bd_options = BlockDev.FSMkfsOptions(label=self.fs.label if label else None, + uuid=self.fs.uuid if set_uuid else None, + no_discard=self.fs._mkfs_nodiscard if nodiscard else False) + BlockDev.fs.mkfs(self.fs.device, self.fstype, bd_options, extra=options or []) + except BlockDev.FSError as e: + raise FSError(str(e)) -class ReiserFSMkfs(FSMkfs): - ext = availability.MKREISERFS_APP - label_option = "-l" - nodiscard_option = None - def get_uuid_args(self, uuid): - return ["-u", uuid] +class BTRFSMkfs(FSBlockDevMkfs): + ext = availability.BLOCKDEV_BTRFS_MKFS + fstype = "btrfs" + can_nodiscard = True + can_set_uuid = True + # XXX btrfs supports labels but we don't really support standalone btrfs + # and use labels as btrfs volume names + can_label = False - @property - def args(self): - return ["-f", "-f"] +class Ext2FSMkfs(FSBlockDevMkfs): + ext = availability.BLOCKDEV_EXT_MKFS + fstype = "ext2" + can_nodiscard = True + can_set_uuid = True + can_label = True -class XFSMkfs(FSMkfs): - ext = availability.MKFS_XFS_APP - label_option = "-L" - nodiscard_option = ["-K"] - def get_uuid_args(self, uuid): - return ["-m", "uuid=" + uuid] +class Ext3FSMkfs(Ext2FSMkfs): + fstype = "ext3" - @property - def args(self): - return ["-f"] +class Ext4FSMkfs(Ext2FSMkfs): + fstype = "ext4" -class F2FSMkfs(FSMkfs): - ext = availability.MKFS_F2FS_APP - label_option = "-l" - nodiscard_option = ["-t", "nodiscard"] - get_uuid_args = None - @property - def args(self): - # Enable the extended node bitmap, this means that we can create more - # files and directories without running out of inodes, even if the - # available space for metadata is limited. - return ["-i"] +class FATFSMkfs(FSBlockDevMkfs): + ext = availability.BLOCKDEV_VFAT_MKFS + fstype = "vfat" + can_nodiscard = False + can_set_uuid = True + can_label = True + + +class NTFSMkfs(FSBlockDevMkfs): + ext = availability.BLOCKDEV_NTFS_MKFS + fstype = "ntfs" + can_nodiscard = False + can_set_uuid = False + can_label = True + + +class XFSMkfs(FSBlockDevMkfs): + ext = availability.BLOCKDEV_XFS_MKFS + fstype = "xfs" + can_nodiscard = True + can_set_uuid = True + can_label = True + + +class F2FSMkfs(FSBlockDevMkfs): + ext = availability.BLOCKDEV_F2FS_MKFS + fstype = "f2fs" + can_nodiscard = True + can_set_uuid = False + can_label = True class UnimplementedFSMkfs(task.UnimplementedTask, FSMkfsTask): diff --git a/blivet/tasks/fsmount.py b/blivet/tasks/fsmount.py index a7f493dd4..6bcefe9d7 100644 --- a/blivet/tasks/fsmount.py +++ b/blivet/tasks/fsmount.py @@ -23,13 +23,16 @@ from ..errors import FSError from ..flags import flags -from .. import util from ..formats import fslib from . import availability from . import fstask from . import task +import gi +gi.require_version("BlockDev", "3.0") +from gi.repository import BlockDev + class FSMount(task.BasicApplication, fstask.FSTask): @@ -40,20 +43,14 @@ class FSMount(task.BasicApplication, fstask.FSTask): # type argument to pass to mount, if different from filesystem type fstype = None - ext = availability.MOUNT_APP + ext = availability.BLOCKDEV_FS_PLUGIN # TASK methods @property def _has_driver(self): """ Is there a filesystem driver in the kernel modules directory. """ - modpath = os.path.realpath(os.path.join("/lib/modules", os.uname()[2])) - if os.path.isdir(modpath): - modname = "%s.ko" % self.mount_type - for _root, _dirs, files in os.walk(modpath): - if any(x.startswith(modname) for x in files): - return True - return False + return BlockDev.utils.have_kernel_module(self.mount_type) @property def _can_mount(self): @@ -100,6 +97,9 @@ def mount_options(self, options): if not options or not isinstance(options, str): options = self.fs.mountopts or ",".join(self.options) + if options is None: + options = "defaults" + return self._modify_options(options) def do_task(self, mountpoint, options=None): @@ -114,20 +114,15 @@ def do_task(self, mountpoint, options=None): if error_msgs: raise FSError("\n".join(error_msgs)) + mountpoint = os.path.normpath(mountpoint) + if not os.path.isdir(mountpoint): + os.makedirs(mountpoint) + try: - rc = util.mount(self.fs.device, mountpoint, - fstype=self.mount_type, - options=self.mount_options(options)) - except OSError as e: + BlockDev.fs.mount(self.fs.device, mountpoint, self.mount_type, self.mount_options(options)) + except BlockDev.FSError as e: raise FSError("mount failed: %s" % e) - if rc: - raise FSError("mount failed: %s" % rc) - - -class AppleBootstrapFSMount(FSMount): - fstype = "hfs" - class BindFSMount(FSMount): diff --git a/blivet/tasks/fsreadlabel.py b/blivet/tasks/fsreadlabel.py index 6f2c8a021..5c6003a36 100644 --- a/blivet/tasks/fsreadlabel.py +++ b/blivet/tasks/fsreadlabel.py @@ -20,12 +20,10 @@ # Red Hat Author(s): Anne Mulhern import abc -import re from six import add_metaclass from ..errors import FSReadLabelError -from .. import util from . import availability from . import fstask @@ -38,36 +36,11 @@ class FSReadLabel(task.BasicApplication, fstask.FSTask): """ An abstract class that represents reading a filesystem's label. """ description = "read filesystem label" - label_regex = abc.abstractproperty( - doc="Matches the string output by the reading application.") - - args = abc.abstractproperty(doc="arguments for reading a label.") - - # IMPLEMENTATION methods - @property - def _read_command(self): - """Get the command to read the filesystem label. - - :return: the command - :rtype: list of str - """ - return [str(self.ext)] + self.args - - def _extract_label(self, labelstr): - """Extract the label from an output string. - - :param str labelstr: the string containing the label information + def depends_on(self): + return [self.fs._info] - :return: the label - :rtype: str - - Raises an FSReadLabelError if the label can not be extracted. - """ - match = re.match(self.label_regex, labelstr) - if match is None: - raise FSReadLabelError("Unknown format for application %s" % self.ext) - return match.group('label') + # IMPLEMENTATION methods def do_task(self): # pylint: disable=arguments-differ """ Get the label. @@ -79,49 +52,26 @@ def do_task(self): # pylint: disable=arguments-differ if error_msgs: raise FSReadLabelError("\n".join(error_msgs)) - (rc, out) = util.run_program_and_capture_output(self._read_command) - if rc != 0: - raise FSReadLabelError("read label failed") - - label = out.strip() + if self.fs._current_info is None: + raise FSReadLabelError("No info available for size computation.") - return label if label == "" else self._extract_label(label) + return self.fs._current_info.label class DosFSReadLabel(FSReadLabel): - ext = availability.DOSFSLABEL_APP - label_regex = r'(?P