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

320 lines
11 KiB
Python
Raw Normal View History

2024-11-14 21:39:56 -08:00
#
# Copyright (C) 2020 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.
#
from blivet.errors import StorageError, InconsistentPVSectorSize
from blivet.size import Size
from dasbus.structure import compare_data
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.structures.device_factory import DeviceFactoryRequest
from pyanaconda.modules.common.task import Task
from pyanaconda.modules.storage.constants import INCONSISTENT_SECTOR_SIZES_SUGGESTIONS
from pyanaconda.modules.storage.partitioning.interactive.utils import destroy_device, \
get_device_factory_arguments, revert_reformat, resize_device, reformat_device, \
validate_label, change_encryption, rename_container
log = get_module_logger(__name__)
__all__ = ["ChangeDeviceTask"]
class ChangeDeviceTask(Task):
"""A task for changing a device in the device tree."""
def __init__(self, storage, device, request: DeviceFactoryRequest,
original_request: DeviceFactoryRequest):
"""Create a task.
FIXME: Remove device and original request from the arguments.
:param storage: an instance of Blivet
:param device: a device to change
:param request: a device factory request
:param original_request: an original device factory request
"""
super().__init__()
self._storage = storage
self._device = device
self._request = request
self._original_request = original_request
@property
def name(self):
"""Name of this task."""
return "Change a device"
def run(self):
"""Change a device in the device tree.
:raise: StorageConfigurationError if the device cannot be changed
"""
log.debug("Change device: %s", self._request)
# Nothing to do. Skip.
if compare_data(self._request, self._original_request):
log.debug("Nothing to change.")
return
try:
# Change the container.
self._rename_container()
# Change or replace the device.
if not self._device.raw_device.exists:
self._replace_device()
else:
self._change_device()
except InconsistentPVSectorSize as e:
self._handle_storage_error(e, "\n\n".join([
_("Failed to change a device."),
str(e).strip(),
_(INCONSISTENT_SECTOR_SIZES_SUGGESTIONS)
]))
except StorageError as e:
self._handle_storage_error(e, str(e))
def _handle_storage_error(self, exception, message):
"""Handle the storage error."""
log.error("Failed to change a device: %s", message)
raise StorageConfigurationError(message) from exception
def _rename_container(self):
"""Rename the existing container."""
container_spec = self._request.container_spec
container_name = self._request.container_name
# Nothing to do.
if not container_spec or container_spec == container_name:
return
container = self._storage.devicetree.resolve_device(container_spec)
# Container doesn't exist.
if not container:
return
log.debug("Changing container name: %s", container_name)
try:
rename_container(self._storage, container, container_name)
except StorageError as e:
log.error("Invalid container name: %s", e)
raise StorageError(str(e)) from e
def _replace_device(self):
"""Replace the nonexistent device with a new one.
If something has changed but the device does not exist,
there is no need to schedule actions on the device. It
is only necessary to create a new device object which
reflects the current choices.
"""
log.debug("Replacing a nonexistent device.")
device = self._device
if self._should_remove_device():
# Remove the current device.
destroy_device(self._storage, device)
# We don't want to pass the device if we removed it.
self._request.device_spec = ""
# Create a new device.
log.debug("Creating a new device.")
arguments = get_device_factory_arguments(self._storage, self._request)
device = self._storage.factory_device(**arguments)
# Update the device.
self._device = device
def _should_remove_device(self):
"""Should we remove the current device?"""
new_request = self._request
old_request = self._original_request
# Check the device type.
if old_request.device_type != new_request.device_type:
return True
# Check the container name.
if old_request.container_name and new_request.container_name != old_request.container_name:
return True
return False
def _change_device(self):
"""Change the configuration of the existing device."""
log.debug("Modifying an existing device.")
self._revert_device_reformat()
self._change_device_size()
if self._should_reformat_device():
self._change_device_encryption()
self._change_device_format()
else:
self._change_device_label()
self._change_device_mount_point()
self._change_device_name()
def _revert_device_reformat(self):
"""Revert reformat of the device."""
if self._request.reformat:
return
log.debug("Reverting device reformat.")
revert_reformat(self._storage, self._device)
def _change_device_size(self):
"""Resize the device."""
size = Size(self._request.device_size)
original_size = Size(self._original_request.device_size)
if size == original_size:
return
log.debug("Changing device size: %s", size)
resize_device(self._storage, self._device, size, original_size)
def _should_reformat_device(self):
"""Should we reformat the device?
:return: True of False
"""
if not self._request.reformat:
return False
if self._device.format.exists:
return True
if self._original_request.device_encrypted != self._request.device_encrypted:
return True
if self._original_request.luks_version != self._request.luks_version:
return True
if self._original_request.format_type != self._request.format_type:
return True
return False
def _change_device_encryption(self):
"""Change the device encryption."""
storage = self._storage
device = self._device
encrypted = self._request.device_encrypted
luks_version = self._request.luks_version
if self._original_request.device_encrypted != encrypted:
log.debug("Changing device encryption: %s", encrypted)
device = change_encryption(
storage=storage,
device=device,
encrypted=encrypted,
luks_version=luks_version
)
elif encrypted and self._original_request.luks_version != luks_version:
log.debug("Changing LUKS version: %s", luks_version)
# LUKS version cannot be easily changed,
# so remove the current LUKS device.
device = change_encryption(
storage=storage,
device=device,
encrypted=False,
luks_version=luks_version
)
# And create a new one with the requested
# LUKS version.
device = change_encryption(
storage=storage,
device=device,
encrypted=True,
luks_version=luks_version
)
self._device = device
def _change_device_format(self):
"""Change the device format."""
log.debug("Changing device format: %s", self._request.format_type)
reformat_device(
storage=self._storage,
device=self._device,
fstype=self._request.format_type,
mountpoint=self._request.mount_point,
label=self._request.label
)
def _change_device_label(self):
"""Change the device label."""
label = self._request.label
if self._original_request.label == label:
return
if not hasattr(self._device.format, "label"):
log.warning("Cannot set a label to the current format.")
return
if self._device.format.exists:
log.warning("Cannot relabel already existing file system.")
return
if validate_label(label, self._device.format):
log.warning("Cannot set an invalid label.")
return
log.debug("Changing device label: %s", label)
self._device.format.label = label
def _change_device_mount_point(self):
"""Change the device mount point."""
mount_point = self._request.mount_point
if not mount_point:
return
if self._original_request.mount_point == mount_point:
return
log.debug("Changing device mount point: %s", mount_point)
self._device.format.mountpoint = mount_point
def _change_device_name(self):
"""Change the device name."""
name = self._request.device_name
original_name = self._original_request.device_name
if name == original_name:
return
log.debug("Changing device name: %s", name)
try:
self._device.raw_device.name = name
except ValueError as e:
log.error("Invalid device name: %s", e)
raise StorageError(str(e)) from e