anaconda/anaconda-40.22.3.13/pyanaconda/modules/storage/partitioning/interactive/utils.py

1243 lines
40 KiB
Python
Raw Normal View History

2024-11-14 21:39:56 -08:00
#
# Utilities for the interactive partitioning module
#
# Copyright (C) 2019 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 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 General
# Public License for more details. You should have received a copy of the
# GNU 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 General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import itertools
import re
from blivet import devicefactory
from blivet.devicelibs import crypto, raid
from blivet.devices import LUKSDevice, MDRaidArrayDevice, LVMVolumeGroupDevice
from blivet.errors import StorageError
from blivet.formats import get_format
from blivet.size import Size
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.i18n import _
from pyanaconda.modules.common.errors.configuration import StorageConfigurationError
from pyanaconda.modules.common.errors.storage import UnsupportedDeviceError, UnknownDeviceError
from pyanaconda.modules.common.structures.device_factory import DeviceFactoryRequest, \
DeviceFactoryPermissions
from pyanaconda.modules.storage.disk_initialization import DiskInitializationConfig
from pyanaconda.modules.storage.platform import platform, PLATFORM_MOUNT_POINTS
from pyanaconda.core.product import get_product_name, get_product_version
from pyanaconda.modules.storage.devicetree.root import Root
from pyanaconda.modules.storage.devicetree.utils import get_supported_filesystems, \
is_supported_filesystem
from pyanaconda.core.storage import DEVICE_TEXT_MAP, PARTITION_ONLY_FORMAT_TYPES, \
NAMED_DEVICE_TYPES, CONTAINER_DEVICE_TYPES, SUPPORTED_DEVICE_TYPES
log = get_module_logger(__name__)
def filter_unsupported_disklabel_devices(devices):
"""Return input list minus any devices that exist on an unsupported disklabel."""
return [d for d in devices if not any(
not getattr(p, "disklabel_supported", True) for p in d.ancestors
)]
def collect_used_devices(storage):
"""Collect devices used in existing or new installations.
:param storage: an instance of Blivet
:return: a list of devices
"""
used_devices = []
for root in storage.roots:
for device in root.devices:
if device not in storage.devices:
continue
used_devices.extend(device.ancestors)
for new in [d for d in storage.devicetree.leaves if not d.format.exists]:
if new.format.mountable and not new.format.mountpoint:
continue
used_devices.extend(new.ancestors)
for device in storage.partitions:
if getattr(device, "is_logical", False):
extended = device.disk.format.extended_partition.path
used_devices.append(storage.devicetree.get_device_by_path(extended))
return used_devices
def collect_unused_devices(storage):
"""Collect devices that are not used in existing or new installations.
:param storage: an instance of Blivet
:return: a list of devices
"""
used_devices = set(collect_used_devices(storage))
unused = [
d for d in storage.devices
if d.disks
and d.media_present
and not d.partitioned
and (d.direct or d.isleaf)
and d not in used_devices
]
# Add incomplete VGs and MDs
incomplete = [
d for d in storage.devicetree._devices
if not getattr(d, "complete", True)
]
# Add partitioned devices with unsupported format.
unsupported = [
d for d in storage.partitioned
if not d.format.supported
]
return filter_unsupported_disklabel_devices(unused + incomplete + unsupported)
def collect_bootloader_devices(storage, boot_drive):
"""Collect the bootloader devices.
:param storage: an instance of Blivet
:param boot_drive: a name of the bootloader drive
:return: a list of devices
"""
devices = []
for device in storage.devices:
if device.format.type not in ["biosboot", "prepboot"]:
continue
# Boot drive may not be setup because it IS one of these.
if not boot_drive or boot_drive in (d.name for d in device.disks):
devices.append(device)
return filter_unsupported_disklabel_devices(devices)
def collect_new_devices(storage, boot_drive):
"""Collect new devices.
:param storage: an instance of Blivet
:param boot_drive: a name of the bootloader drive
:return: a list of devices
"""
# A device scheduled for formatting only belongs in the new root.
new_devices = [
d for d in storage.devices
if d.direct
and not d.format.exists
and not d.partitioned
]
# If mount points have been assigned to any existing devices, go ahead
# and pull those in along with any existing swap devices. It doesn't
# matter if the formats being mounted exist or not.
new_mounts = [
d for d in storage.mountpoints.values() if d.exists
]
if new_mounts or new_devices:
new_devices.extend(storage.mountpoints.values())
new_devices.extend(collect_bootloader_devices(storage, boot_drive))
# Remove duplicates, but keep the order.
return filter_unsupported_disklabel_devices(list(dict.fromkeys(new_devices)))
def collect_roots(storage):
"""Collect roots of existing installations.
:param storage: an instance of Blivet
:return: a list of roots
"""
roots = []
supported_devices = set(filter_unsupported_disklabel_devices(storage.devices))
# Get the name of the new installation.
new_root_name = get_new_root_name()
for root in storage.roots:
# Get the name.
name = root.name
# Get the supported devices.
devices = [
d for d in root.devices
if d in supported_devices
and (d.format.exists or root.name == new_root_name)
]
# Get the supported mount points.
mounts = {
m: d for m, d in root.mounts.items()
if d in supported_devices
and (d.format.exists or root.name == new_root_name)
and d.disks
}
if not devices and not mounts:
continue
# Add a root with supported devices.
roots.append(Root(
name=name,
devices=devices,
mounts=mounts,
))
return roots
def get_new_root_name():
"""Get the name of the new installation.
:return: a translated string
"""
return _("New {name} {version} Installation").format(
name=get_product_name(), version=get_product_version()
)
def create_new_root(storage, boot_drive):
"""Create a new root from the given devices.
:param storage: an instance of Blivet
:param boot_drive: a name of the bootloader drive
:return: a new root
"""
devices = collect_new_devices(
storage=storage,
boot_drive=boot_drive
)
mounts = {
d.format.mountpoint: d for d in devices
if getattr(d.format, "mountpoint", None)
}
return Root(
name=get_new_root_name(),
devices=devices,
mounts=mounts,
)
def collect_mount_points():
"""Collect supported mount points.
:return: a list of paths
"""
paths = ["/", "/boot", "/home", "/var"]
# Add the mount point requirements for bootloader stage1 devices.
paths.extend(platform.stage1_constraints[PLATFORM_MOUNT_POINTS])
# Sort the list now so all the real mount points go to the front,
# then add all the pseudo mount points we have.
paths.sort()
paths += ["swap"]
for fmt in ["appleboot", "biosboot", "prepboot"]:
if get_format(fmt).supported:
paths += [fmt]
return paths
def validate_label(label, fmt):
"""Validate the label.
:param str label: a label
:param DeviceFormat fmt: a device format to label
:return: an error message
"""
if not label:
return None
if not fmt.labeling():
return _("Cannot set label on file system.")
if not fmt.label_format_ok(label):
return _("Unacceptable label format for file system.")
return None
def validate_mount_point(path, mount_points):
"""Validate the given path of a mount point.
:param path: a path to validate
:param mount_points: a list of existing mount points
:return: an error message
"""
system_mount_points = ["/dev", "/proc", "/run", "/sys"]
if path in mount_points:
return _("That mount point is already in use. Try something else?")
if not path:
return _("Please enter a valid mount point.")
if path in system_mount_points:
return _("That mount point is invalid. Try something else?")
if ((len(path) > 1 and path.endswith("/")) or
not path.startswith("/") or
" " in path or
re.search(r'/\.*/', path) or
re.search(r'/\.+$', path)):
# - does not end with '/' unless mountpoint _is_ '/'
# - starts with '/' except for "swap", &c
# - does not contain spaces
# - does not contain pairs of '/' enclosing zero or more '.'
# - does not end with '/' followed by one or more '.'
return _("That mount point is invalid. Try something else?")
return None
def validate_container_name(storage, name):
"""Validate the given container name.
:param storage: an instance of Blivet
:param name: a container name
:return: an error message or None
"""
safe_name = storage.safe_device_name(name)
if name != safe_name:
return _("Invalid container name.")
if name in storage.names:
return _("Name is already in use.")
return None
def get_raid_level_by_name(name):
"""Get the RAID level object for the given name.
:param name: a name of the RAID level
:return: an instance of RAIDLevel
"""
if not name:
return None
return raid.get_raid_level(name)
def validate_raid_level(raid_level, num_members):
"""Validate the given raid level.
:param raid_level: a RAID level
:param num_members: a number of members
:return: an error message
"""
if num_members < raid_level.min_members:
return _("The RAID level you have selected ({level}) requires more disks "
"({min}) than you currently have selected ({count}).").format(
level=raid_level,
min=raid_level.min_members,
count=num_members
)
return None
def validate_device_factory_request(storage, request: DeviceFactoryRequest):
"""Validate the given device info.
:param storage: an instance of Blivet
:param request: a device factory request to validate
:return: an error message
"""
device = storage.devicetree.resolve_device(request.device_spec)
device_type = request.device_type
reformat = request.reformat
fs_type = request.format_type
encrypted = request.device_encrypted
raid_level = get_raid_level_by_name(request.device_raid_level)
mount_point = request.mount_point
label = request.label
num_disk = len(request.disks)
changed_label = label != getattr(device.format, "label", "")
changed_fstype = fs_type != device.format.type
if changed_label or changed_fstype:
error = validate_label(
label,
get_format(fs_type)
)
if error:
return error
is_format_mountable = get_format(fs_type).mountable
changed_mount_point = mount_point != getattr(device.format, "mountpoint", "")
if reformat and is_format_mountable and not mount_point:
return _("Please enter a mount point.")
if changed_mount_point and mount_point:
error = validate_mount_point(
mount_point,
storage.mountpoints.keys()
)
if error:
return error
supported_types = (devicefactory.DEVICE_TYPE_PARTITION, devicefactory.DEVICE_TYPE_MD)
if mount_point == "/boot/efi" and device_type not in supported_types:
return _("/boot/efi must be on a device of type {type} or {another}").format(
type=_(DEVICE_TEXT_MAP[devicefactory.DEVICE_TYPE_PARTITION]),
another=_(DEVICE_TEXT_MAP[devicefactory.DEVICE_TYPE_MD])
)
if device_type != devicefactory.DEVICE_TYPE_PARTITION and \
fs_type in PARTITION_ONLY_FORMAT_TYPES:
return _("{fs} must be on a device of type {type}").format(
fs=fs_type,
type=_(DEVICE_TEXT_MAP[devicefactory.DEVICE_TYPE_PARTITION])
)
if mount_point and encrypted and mount_point.startswith("/boot"):
return _("{} cannot be encrypted").format(mount_point)
if encrypted and fs_type in PARTITION_ONLY_FORMAT_TYPES:
return _("{} cannot be encrypted").format(fs_type)
if mount_point == "/" and device.format.exists and not reformat:
return _("You must create a new file system on the root device.")
if (raid_level is not None or device_type == devicefactory.DEVICE_TYPE_MD) and \
raid_level not in get_supported_raid_levels(device_type):
return _("Device does not support RAID level selection {}.").format(raid_level)
if raid_level is not None:
error = validate_raid_level(
raid_level,
num_disk
)
if error:
return error
return None
def suggest_device_name(storage, device):
"""Get a suggestion for a device name.
:param storage: an instance of Blivet
:param device: a device to name
:return:
"""
return storage.suggest_device_name(
swap=bool(device.format.type == "swap"),
mountpoint=getattr(device.format, "mountpoint", None)
)
def revert_reformat(storage, device):
"""Revert reformat of the given device.
:param storage: an instance of Blivet
:param device: a device to reset
"""
# Skip if formats exists.
if device.format.exists and device.raw_device.format.exists:
log.debug("Nothing to revert for %s.", device.name)
return
# Figure out the existing device.
if not device.raw_device.format.exists:
original_device = device.raw_device
else:
original_device = device
# Reset it.
log.debug("Resetting device %s.", original_device.name)
storage.reset_device(original_device)
def resize_device(storage, device, new_size, old_size):
"""Resize the given device.
:param storage: an instance of Blivet
:param device: a device to resize
:param new_size: a new size
:param old_size: an old size
:return: True if the device changed its size, otherwise False
:raise: StorageError if we fail to schedule the device resize
"""
log.debug("Resizing device %s to %s.", device, new_size)
# If a LUKS device is being displayed, adjust the size
# to the appropriate size for the raw device.
use_size = new_size
use_old_size = old_size
if device.raw_device is not device:
use_size = new_size + crypto.LUKS_METADATA_SIZE
use_old_size = device.raw_device.size
# Bound size to boundaries given by the device.
use_size = device.raw_device.align_target_size(use_size)
use_size = bound_size(use_size, device.raw_device, use_old_size)
use_size = device.raw_device.align_target_size(use_size)
# And then we need to re-check that the max size is actually
# different from the current size.
if use_size == device.size or use_size == device.raw_device.size:
# The size hasn't changed.
log.debug("Canceled resize of device %s to %s.", device.raw_device.name, use_size)
return False
if new_size == device.current_size or use_size == device.current_size:
# The size has been set back to its original value.
log.debug("Removing resize of device %s.", device.raw_device.name)
actions = storage.devicetree.actions.find(
action_type="resize",
devid=device.raw_device.id
)
for action in reversed(actions):
storage.devicetree.actions.remove(action)
return bool(actions)
else:
# the size has changed
log.debug("Scheduling resize of device %s to %s.", device.raw_device.name, use_size)
try:
storage.resize_device(device.raw_device, use_size)
except (StorageError, ValueError) as e:
log.exception("Failed to schedule device resize: %s", e)
device.raw_device.size = use_old_size
raise StorageError(str(e)) from None
log.debug(
"Device %s has size: %s (target %s)",
device.raw_device.name,
device.raw_device.size, device.raw_device.target_size
)
return True
def bound_size(size, device, old_size):
"""Returns a size bounded by the maximum and minimum size for the device.
:param size: the candidate size
:type size: :class:`blivet.size.Size`
:param device: the device being displayed
:type device: :class:`blivet.devices.StorageDevice`
:param old_size: the fallback size
:type old_size: :class:`blivet.size.Size`
:returns: a size to which to set the device
:rtype: :class:`blivet.size.Size`
If size is 0, interpreted as set size to maximum possible.
If no maximum size is available, reset size to old_size, but
log a warning.
"""
max_size = device.max_size
min_size = device.min_size
if not size:
if max_size:
log.info("No size specified, using maximum size for "
"this device (%d).", max_size)
size = max_size
else:
log.warning("No size specified and no maximum size available, "
"setting size back to original size (%d).", old_size)
size = old_size
else:
if max_size:
if size > max_size:
log.warning("Size specified (%d) is greater than the maximum "
"size for this device (%d), using maximum size.",
size, max_size)
size = max_size
else:
log.warning("Unknown upper bound on size. Using requested size (%d).",
size)
if size < min_size:
log.warning("Size specified (%d) is less than the minimum size for "
"this device (%d), using minimum size.", size, min_size)
size = min_size
return size
def change_encryption(storage, device, encrypted, luks_version):
"""Change encryption of the given device.
:param storage: an instance of Blivet
:param device: a device to change
:param encrypted: should we encrypt the device?
:param luks_version: a version of LUKS
:return: a LUKS device or a LUKS device parent device
"""
if not encrypted:
log.info("Removing encryption from %s.", device.name)
storage.destroy_device(device)
return device.raw_device
else:
log.info("Applying encryption to %s.", device.name)
luks_version = luks_version or storage.default_luks_version
new_fmt = get_format("luks", device=device.path, luks_version=luks_version)
storage.format_device(device, new_fmt)
luks_dev = LUKSDevice("luks-" + device.name, parents=[device])
storage.create_device(luks_dev)
return luks_dev
def reformat_device(storage, device, fstype, mountpoint, label):
"""Reformat the given device.
:param storage: an instance of Blivet
:param device: a device to reformat
:param fstype: a file system type
:param mountpoint: a mount point
:param label: a label
:raise: StorageError if we fail to format the device
"""
log.info("Scheduling reformat of %s as %s.", device.name, fstype)
old_format = device.format
new_format = get_format(
fstype,
mountpoint=mountpoint,
label=label,
device=device.path
)
try:
storage.format_device(device, new_format)
except (StorageError, ValueError) as e:
log.exception("Failed to register device format action: %s", e)
device.format = old_format
raise StorageError(str(e)) from None
def get_device_luks_version(device):
"""Get the LUKS version of the given device.
:param device: a device
:return: a LUKS version or an empty string
"""
device = device.raw_device
if device.format.type == "luks":
return device.format.luks_version
return ""
def get_container_luks_version(container):
"""Get the LUKS version of the given container.
:param container: a container
:return: a LUKS version or an empty string
"""
for device in itertools.chain([container], container.parents):
luks_version = get_device_luks_version(device)
if luks_version:
return luks_version
return ""
def get_device_raid_level(device):
"""Get the RAID level of the given device.
:param device: a device
:return: a RAID level
"""
device = device.raw_device
if hasattr(device, "level"):
return device.level
if hasattr(device, "data_level"):
return device.data_level
return None
def get_device_raid_level_name(device):
"""Get the RAID level name of the given device."""
raid_level = get_device_raid_level(device)
return raid_level.name if raid_level else ""
def get_container_raid_level(container):
"""Get the RAID level of the given container.
:param container: a container
:return: a RAID level
"""
# Try to get a RAID level of this device.
raid_level = get_device_raid_level(container)
if raid_level:
return raid_level
device = container.raw_device
# Or get a RAID level of the LVM container.
if hasattr(device, "lvs") and len(device.parents) == 1:
return get_container_raid_level(device.parents[0])
return None
def get_container_raid_level_name(device):
"""Get the RAID level name of the given container."""
raid_level = get_container_raid_level(device)
return raid_level.name if raid_level else ""
def collect_file_system_types(device):
"""Collect supported file system types for the given device.
:param device: a device
:return: a list of file system types
"""
# Collect the supported filesystem types.
supported_types = set(get_supported_filesystems())
# Add possibly unsupported but still required file system types:
# Add the device format type.
if device.format.type:
supported_types.add(device.format.type)
# Add the original device format type.
if device.exists and device.original_format.type:
supported_types.add(device.original_format.type)
return sorted(supported_types)
def collect_device_types(device):
"""Collect supported device types for the given device.
:param device: a device
:return: a list of device types
"""
# Collect the supported device types.
supported_types = set(SUPPORTED_DEVICE_TYPES)
supported_types.add(devicefactory.DEVICE_TYPE_MD)
# Include the type of the given device.
supported_types.add(devicefactory.get_device_type(device))
# Include btrfs if it is both allowed and supported.
fmt = get_format("btrfs")
if fmt.supported \
and fmt.formattable \
and device.raw_device.format.type not in PARTITION_ONLY_FORMAT_TYPES + ("swap",):
supported_types.add(devicefactory.DEVICE_TYPE_BTRFS)
return sorted(filter(devicefactory.is_supported_device_type, supported_types))
def get_device_factory_arguments(storage, request: DeviceFactoryRequest, subset=None):
"""Get the device factory arguments for the given request.
:param storage: an instance of Blivet
:param request: a device factory request
:param subset: a subset of argument names to return or None
:return: a dictionary of device factory arguments
"""
args = {
"device_type": request.device_type,
"device": storage.devicetree.get_device_by_name(request.device_spec),
"disks": [storage.devicetree.get_device_by_name(d) for d in request.disks],
"mountpoint": request.mount_point or None,
"fstype": request.format_type or None,
"label": request.label or None,
"luks_version": request.luks_version or storage.default_luks_version,
"device_name": request.device_name or None,
"size": Size(request.device_size) or None,
"raid_level": get_raid_level_by_name(request.device_raid_level),
"encrypted": request.device_encrypted,
"container_name": request.container_name or None,
"container_size": get_container_size_policy_by_number(request.container_size_policy),
"container_raid_level": get_raid_level_by_name(request.container_raid_level),
"container_encrypted": request.container_encrypted,
}
if subset:
args = {name: value for name, value in args.items() if name in subset}
log.debug(
"Generated factory arguments: {\n%s\n}",
",\n".join("{} = {}".format(name, repr(value)) for name, value in args.items())
)
return args
def generate_device_factory_request(storage, device) -> DeviceFactoryRequest:
"""Generate a device info for the given device.
:param storage: an instance of Blivet
:param device: a device
:return: a device factory request
"""
device_type = devicefactory.get_device_type(device)
if device_type is None:
raise UnsupportedDeviceError("Unsupported type of {}.".format(device.name))
# Generate the device data.
request = DeviceFactoryRequest()
request.device_spec = device.name
request.device_name = getattr(device.raw_device, "lvname", device.raw_device.name)
request.device_size = device.size.get_bytes()
request.device_type = device_type
request.reformat = not device.format.exists
request.format_type = device.format.type or ""
request.device_encrypted = isinstance(device, LUKSDevice)
request.luks_version = get_device_luks_version(device)
request.label = getattr(device.format, "label", "") or ""
request.mount_point = getattr(device.format, "mountpoint", "") or ""
request.device_raid_level = get_device_raid_level_name(device)
if hasattr(device, "req_disks") and not device.exists:
disks = device.req_disks
else:
disks = device.disks
request.disks = [d.name for d in disks]
if request.device_type not in CONTAINER_DEVICE_TYPES:
return request
# Generate the container data.
factory = devicefactory.get_device_factory(
storage,
device_type=device_type,
device=device.raw_device
)
container = factory.get_container()
if container:
set_container_data(request, container)
return request
def set_container_data(request: DeviceFactoryRequest, container):
"""Set the container data in the device factory request.
:param request: a device factory request
:param container: a container
"""
request.container_spec = container.name
request.container_name = container.name
request.container_encrypted = container.encrypted
request.container_raid_level = get_container_raid_level_name(container)
request.container_size_policy = get_container_size_policy(container)
if request.container_encrypted:
request.luks_version = get_container_luks_version(container)
def generate_container_data(storage, request: DeviceFactoryRequest):
"""Generate the container data for the device factory request.
:param storage: an instance of Blivet
:param request: a device factory request
"""
# Reset all container data.
request.reset_container_data()
# Check the device type.
if request.device_type not in CONTAINER_DEVICE_TYPES:
return
# Find a container of the requested type.
device = storage.devicetree.resolve_device(request.device_spec)
container = get_container(storage, request.device_type, device.raw_device)
if container:
# Set the request from the found container.
set_container_data(request, container)
else:
# Set the request from a new container.
request.container_name = storage.suggest_container_name()
request.container_raid_level = get_default_container_raid_level_name(
request.device_type
)
def update_container_data(storage, request: DeviceFactoryRequest, container_name):
"""Update the container data in the device factory request.
:param storage: an instance of Blivet
:param request: a device factory request
:param container_name: a container name to apply
"""
# Reset all container data.
request.reset_container_data()
# Check the device type.
if request.device_type not in CONTAINER_DEVICE_TYPES:
raise StorageError("Invalid device type.")
# Find the container in the device tree if any.
container = storage.devicetree.get_device_by_name(container_name)
if container:
# Set the request from the found container.
set_container_data(request, container)
# Use the container's disks.
request.disks = [d.name for d in container.disks]
else:
# Set the request from the new container.
request.container_name = container_name
request.container_raid_level = get_default_container_raid_level_name(
request.device_type
)
def generate_device_factory_permissions(storage, request: DeviceFactoryRequest):
"""Generate permissions for the requested device.
:param storage: an instance of Blivet
:param request: a device factory request
:return: device factory permissions
"""
permissions = DeviceFactoryPermissions()
device = storage.devicetree.resolve_device(request.device_spec)
container = storage.devicetree.resolve_device(request.container_name)
fmt = get_format(request.format_type)
if not device:
raise UnknownDeviceError(request.device_spec)
if device.protected:
return permissions
permissions.device_type = not device.raw_device.exists
permissions.device_raid_level = not device.raw_device.exists
permissions.mount_point = fmt.mountable
permissions.label = \
request.reformat \
and fmt.labeling()
permissions.reformat = \
device.raw_device.exists \
and not device.raw_device.format_immutable \
and is_supported_filesystem(request.format_type)
permissions.device_size = \
device.resizable or (
not device.exists
and request.device_type not in {
devicefactory.DEVICE_TYPE_BTRFS
}
)
permissions.device_name = \
not device.raw_device.exists \
and device.raw_device.type != "btrfs volume" \
and request.device_type in NAMED_DEVICE_TYPES
permissions.format_type = \
request.reformat \
and request.device_type not in {
devicefactory.DEVICE_TYPE_BTRFS
}
permissions.device_encrypted = \
request.reformat \
and not request.container_encrypted \
and request.device_type not in {
devicefactory.DEVICE_TYPE_BTRFS
} \
and not any(
a.format.type == "luks" and a.format.exists
for a in device.raw_device.ancestors if a != device
)
permissions.disks = \
not device.exists \
and not device.raw_device.exists \
and request.device_type not in CONTAINER_DEVICE_TYPES
can_change_container = \
request.device_type in CONTAINER_DEVICE_TYPES \
and not getattr(container, "exists", False)
can_replace_container = \
request.device_type in CONTAINER_DEVICE_TYPES \
and not device.raw_device.exists \
and device.raw_device != container
permissions.container_spec = can_replace_container
permissions.container_name = can_change_container
permissions.container_encrypted = can_change_container
permissions.container_raid_level = can_change_container
permissions.container_size_policy = can_change_container
return permissions
def reset_device(storage, device):
"""Reset the given device in the storage model.
FIXME: Merge with destroy_device.
:param storage: an instance of Blivet
:param device: an instance of a device
:raise: StorageConfigurationError in case of failure
"""
log.debug("Reset device: %s", device.name)
try:
if device.exists:
# Revert changes done to an existing device.
storage.reset_device(device)
else:
# Destroy a non-existing device.
_destroy_device(storage, device)
except (StorageError, ValueError) as e:
log.exception("Failed to reset a device: %s", e)
raise StorageConfigurationError(str(e)) from None
def destroy_device(storage, device):
"""Destroy the given device in the storage model.
:param storage: an instance of Blivet
:param device: an instance of a device
:raise: StorageConfigurationError in case of failure
"""
log.debug("Destroy device: %s", device.name)
try:
_destroy_device(storage, device)
except (StorageError, ValueError) as e:
log.exception("Failed to destroy a device: %s", e)
raise StorageConfigurationError(str(e)) from None
def _destroy_device(storage, device):
"""Destroy the given device in the storage model.
:param storage: an instance of Blivet
:param device: an instance of a device
"""
# Remove the device.
if device.is_disk:
if device.partitioned and not device.format.supported:
storage.recursive_remove(device)
storage.initialize_disk(device)
elif device.direct and not device.isleaf:
# We shouldn't call this method for with non-leaf devices
# except for those which are also directly accessible like
# lvm snapshot origins and btrfs subvolumes that contain
# other subvolumes.
storage.recursive_remove(device)
else:
storage.destroy_device(device)
# Remove empty extended partitions.
if getattr(device, "is_logical", False):
storage.remove_empty_extended_partitions()
# If we've just removed the last partition and the disk label
# is preexisting, reinitialize the disk.
if device.type == "partition" and device.exists and device.disk.format.exists:
config = DiskInitializationConfig()
config.initialize_labels = True
if config.can_initialize(storage, device.disk):
storage.initialize_disk(device.disk)
# Get the device container.
if hasattr(device, "vg"):
container = device.vg
device_type = devicefactory.get_device_type(device)
elif hasattr(device, "volume"):
container = device.volume
device_type = devicefactory.DEVICE_TYPE_BTRFS
else:
container = None
device_type = None
# Adjust container to size of remaining devices, if auto-sized.
if (container and not container.exists and container.children and
container.size_policy == devicefactory.SIZE_POLICY_AUTO):
# Create the device factory.
factory = devicefactory.get_device_factory(
storage,
device_type=device_type,
size=Size(0),
disks=container.disks,
container_name=container.name,
container_encrypted=container.encrypted,
container_raid_level=get_container_raid_level(container),
container_size=container.size_policy,
)
# Configure the factory's devices.
factory.configure()
# Finally, remove empty parents of the device, except for btrfs subvolumes.
for parent in device.parents:
if not parent.children and not parent.is_disk and not parent.type == "btrfs subvolume":
destroy_device(storage, parent)
def rename_container(storage, container, name):
"""Rename the given container.
:param storage: an instance of Blivet
:param container: an instance of a container
:param name: a new name of the container
"""
log.debug("Rename container %s to %s.", container.name, name)
try:
container.name = name
except ValueError as e:
log.exception("Failed to rename container: %s", str(e))
raise StorageError(str(e)) from None
# Fix the btrfs label.
if container.format.type == "btrfs":
container.format.label = name
def get_container(storage, device_type, device=None):
"""Get a container of the given type.
:param storage: an instance of Blivet
:param device_type: a device type
:param device: a defined factory device or None
:return: a container device
"""
if device_type not in CONTAINER_DEVICE_TYPES:
raise StorageError("Invalid device type {}".format(device_type))
if device and devicefactory.get_device_type(device) != device_type:
device = None
factory = devicefactory.get_device_factory(
storage,
device_type=device_type,
size=Size(0),
)
return factory.get_container(device=device)
def get_container_size_policy(container):
"""Get a container size policy."""
size = getattr(container, "size_policy", container.size)
if size is None:
return devicefactory.SIZE_POLICY_AUTO
if size > 0:
return Size(size).get_bytes()
return size
def get_container_size_policy_by_number(number):
"""Get a container size policy by the given number."""
if number <= 0:
return number
return Size(number)
def get_default_container_raid_level_name(device_type):
"""Get the default RAID level for this device type's container type.
:param int device_type: a device_type
:return str: a name of the default RAID level or an empty string
"""
if device_type == devicefactory.DEVICE_TYPE_BTRFS:
return "single"
return ""
def collect_containers(storage, device_type):
"""Collect containers of the given type.
:param storage: an instance of Blivet
:param device_type: a device type
:return: a list of container devices
"""
if device_type == devicefactory.DEVICE_TYPE_BTRFS:
return storage.btrfs_volumes
else:
return storage.vgs
def get_supported_raid_levels(device_type):
"""Get RAID levels for the specified device type.
:param device_type: a type of the device
:return: a list of RAID levels
"""
return devicefactory.get_supported_raid_levels(device_type)
def check_device_completeness(device):
"""Check that the specified device is complete.
:param device: a device to check
:return: an error message or None
"""
if getattr(device, "complete", True):
return None
if isinstance(device, MDRaidArrayDevice):
total = device.member_devices
missing = total - len(device.parents)
return _("This Software RAID array is missing %(missing)d of %(total)d "
"member partitions. You can remove it or select a different "
"device.") % {"missing": missing, "total": total}
if isinstance(device, LVMVolumeGroupDevice):
total = device.pv_count
missing = total - len(device.parents)
return _("This LVM Volume Group is missing %(missingPVs)d of %(totalPVs)d "
"physical volumes. You can remove it or select a different "
"device.") % {"missingPVs": missing, "totalPVs": total}
return _("This %(type)s device is missing member devices. You can remove "
"it or select a different device.") % {"type": device.type}