diff --git a/po/POTFILES.in b/po/POTFILES.in index 77047529..29032463 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -33,6 +33,7 @@ subiquity/common/api/tests/test_endtoend.py subiquity/common/api/tests/test_server.py subiquity/common/errorreport.py subiquity/common/filesystem/actions.py +subiquity/common/filesystem/boot.py subiquity/common/filesystem/__init__.py subiquity/common/filesystem/labels.py subiquity/common/filesystem/manipulator.py @@ -106,6 +107,7 @@ subiquity/__main__.py subiquity/models/filesystem.py subiquity/models/identity.py subiquity/models/__init__.py +subiquity/models/kernel.py subiquity/models/keyboard.py subiquity/models/locale.py subiquity/models/mirror.py @@ -126,6 +128,7 @@ subiquity/server/controllers/filesystem.py subiquity/server/controllers/identity.py subiquity/server/controllers/__init__.py subiquity/server/controllers/install.py +subiquity/server/controllers/kernel.py subiquity/server/controllers/keyboard.py subiquity/server/controllers/locale.py subiquity/server/controllers/mirror.py diff --git a/subiquity/common/filesystem/actions.py b/subiquity/common/filesystem/actions.py index 0f6a55b6..33b2e792 100644 --- a/subiquity/common/filesystem/actions.py +++ b/subiquity/common/filesystem/actions.py @@ -18,7 +18,7 @@ import functools from subiquitycore.gettext38 import pgettext -from subiquity.common.filesystem import labels +from subiquity.common.filesystem import boot, labels from subiquity.models.filesystem import ( Bootloader, Disk, @@ -292,7 +292,7 @@ def _can_delete_partition(partition): if partition.device._has_preexisting_partition(): return _("Cannot delete a single partition from a device that " "already has partitions.") - if partition.is_bootloader_partition: + if boot.is_bootloader_partition(partition): return _("Cannot delete required bootloader partition") return _can_delete_generic(partition) @@ -341,12 +341,12 @@ _can_toggle_boot = make_checker(DeviceAction.TOGGLE_BOOT) @_can_toggle_boot.register(Disk) def _can_toggle_boot_disk(disk): - if disk._is_boot_device(): - for disk2 in disk._m.all_disks(): - if disk2 is not disk and disk2._is_boot_device(): + if boot.is_boot_device(disk): + for disk2 in boot.all_boot_devices(disk._m): + if disk2 is not disk: return True return False elif disk._fs is not None or disk._constructed_device is not None: return False else: - return disk._can_be_boot_disk() + return boot.can_be_boot_device(disk) diff --git a/subiquity/common/filesystem/boot.py b/subiquity/common/filesystem/boot.py new file mode 100644 index 00000000..4001cff3 --- /dev/null +++ b/subiquity/common/filesystem/boot.py @@ -0,0 +1,109 @@ +# Copyright 2021 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import functools + +from subiquity.models.filesystem import ( + Disk, + Bootloader, + Partition, + ) + + +@functools.singledispatch +def is_boot_device(device): + """Is `device` a boot device?""" + return False + + +@is_boot_device.register(Disk) +def _is_boot_device_disk(disk): + bl = disk._m.bootloader + if bl == Bootloader.NONE: + return False + elif bl == Bootloader.BIOS: + return disk.grub_device + elif bl in [Bootloader.PREP, Bootloader.UEFI]: + return any(p.grub_device for p in disk._partitions) + + +@functools.singledispatch +def can_be_boot_device(device, *, with_reformatting=False): + """Can `device` be made into a boot device? + + If with_reformatting=True, return true if the device can be made + into a boot device after reformatting. + """ + return False + + +@can_be_boot_device.register(Disk) +def _can_be_boot_device_disk(disk, *, with_reformatting=False): + bl = disk._m.bootloader + if disk._has_preexisting_partition() and not with_reformatting: + if bl == Bootloader.BIOS: + if disk.ptable == "msdos": + return True + else: + return disk._partitions[0].flag == "bios_grub" + elif bl == Bootloader.UEFI: + return any(is_esp(p) for p in disk._partitions) + elif bl == Bootloader.PREP: + return any(p.flag == "prep" for p in disk._partitions) + else: + return True + + +@functools.singledispatch +def is_esp(device): + """Is `device` a UEFI ESP?""" + return False + + +@is_esp.register(Partition) +def _is_esp_partition(partition): + if not can_be_boot_device(partition.device, with_reformatting=True): + return False + if partition.device.ptable == "gpt": + return partition.flag == "boot" + else: + blockdev_raw = partition._m._probe_data['blockdev'].get( + partition._path()) + if blockdev_raw is None: + return False + typecode = blockdev_raw.get("ID_PART_ENTRY_TYPE") + if typecode is None: + return False + try: + return int(typecode, 0) == 0xef + except ValueError: + # In case there was garbage in the udev entry... + return False + + +def all_boot_devices(model): + """Return all current boot devices for `model`.""" + return [disk for disk in model.all_disks() if is_boot_device(disk)] + + +def is_bootloader_partition(partition): + if partition._m.bootloader == Bootloader.BIOS: + return partition.flag == "bios_grub" + elif partition._m.bootloader == Bootloader.UEFI: + return is_esp(partition) + elif partition._m.bootloader == Bootloader.PREP: + return partition.flag == "prep" + else: + return False diff --git a/subiquity/common/filesystem/labels.py b/subiquity/common/filesystem/labels.py index 6b92cc67..0e574a4f 100644 --- a/subiquity/common/filesystem/labels.py +++ b/subiquity/common/filesystem/labels.py @@ -16,6 +16,7 @@ import functools from subiquity.common import types +from subiquity.common.filesystem import boot from subiquity.models.filesystem import ( Disk, LVM_LogicalVolume, @@ -65,7 +66,7 @@ def _annotations_partition(partition): else: # boot loader partition r.append(_("unconfigured")) - elif partition.is_esp: + elif boot.is_esp(partition): if partition.fs() and partition.fs().mount(): r.append(_("primary ESP")) elif partition.grub_device: @@ -194,7 +195,7 @@ def _usage_labels_generic(device): if m: # A filesytem r.append(_("mounted at {path}").format(path=m.path)) - elif not getattr(device, 'is_esp', False): + elif not boot.is_esp(device): # A filesytem r.append(_("not mounted")) elif fs.preserve: diff --git a/subiquity/common/filesystem/manipulator.py b/subiquity/common/filesystem/manipulator.py index 4b98c237..e24a7166 100644 --- a/subiquity/common/filesystem/manipulator.py +++ b/subiquity/common/filesystem/manipulator.py @@ -15,9 +15,7 @@ import logging -from subiquity.common.filesystem.actions import ( - DeviceAction, - ) +from subiquity.common.filesystem import boot from subiquity.common.types import Bootloader from subiquity.models.filesystem import ( align_up, @@ -40,9 +38,8 @@ class FilesystemManipulator: mount = self.model.add_mount(fs, spec['mount']) if self.model.needs_bootloader_partition(): vol = fs.volume - if vol.type == "partition" and vol.device.type == "disk": - if vol.device._can_be_boot_disk(): - self.add_boot_disk(vol.device) + if vol.type == "partition" and boot.can_be_boot_device(vol.device): + self.add_boot_disk(vol.device) return mount def delete_mount(self, mount): @@ -216,7 +213,7 @@ class FilesystemManipulator: needs_boot = self.model.needs_bootloader_partition() log.debug('model needs a bootloader partition? {}'.format(needs_boot)) - can_be_boot = DeviceAction.TOGGLE_BOOT in DeviceAction.supported(disk) + can_be_boot = boot.can_be_boot_device(disk) if needs_boot and len(disk.partitions()) == 0 and can_be_boot: part = self._create_boot_partition(disk) @@ -299,7 +296,7 @@ class FilesystemManipulator: boot_disk.grub_device = False partitions = [ p for p in boot_disk.partitions() - if p.is_bootloader_partition + if boot.is_bootloader_partition(p) ] remount = False if boot_disk.preserve: @@ -338,16 +335,15 @@ class FilesystemManipulator: def add_boot_disk(self, new_boot_disk): bootloader = self.model.bootloader if not self.supports_resilient_boot: - for disk in self.model.all_disks(): - if disk._is_boot_device(): - self.remove_boot_disk(disk) + for disk in boot.all_boot_devices(self.model): + self.remove_boot_disk(disk) if new_boot_disk._has_preexisting_partition(): if bootloader == Bootloader.BIOS: new_boot_disk.grub_device = True elif bootloader == Bootloader.UEFI: should_mount = self.model._mount_for_path('/boot/efi') is None for p in new_boot_disk.partitions(): - if p.is_esp: + if boot.is_esp(p): p.grub_device = True if should_mount: self._mount_esp(p) diff --git a/subiquity/models/filesystem.py b/subiquity/models/filesystem.py index 76b579ec..8c31fb31 100644 --- a/subiquity/models/filesystem.py +++ b/subiquity/models/filesystem.py @@ -615,30 +615,6 @@ class Disk(_Device): def dasd(self): return self._m._one(type='dasd', device_id=self.device_id) - def _can_be_boot_disk(self): - bl = self._m.bootloader - if self._has_preexisting_partition(): - if bl == Bootloader.BIOS: - if self.ptable == "msdos": - return True - else: - return self._partitions[0].flag == "bios_grub" - elif bl == Bootloader.UEFI: - return any(p.is_esp for p in self._partitions) - elif bl == Bootloader.PREP: - return any(p.flag == "prep" for p in self._partitions) - else: - return True - - def _is_boot_device(self): - bl = self._m.bootloader - if bl == Bootloader.NONE: - return False - elif bl == Bootloader.BIOS: - return self.grub_device - elif bl in [Bootloader.PREP, Bootloader.UEFI]: - return any(p.grub_device for p in self._partitions) - @property def ok_for_raid(self): if self._fs is not None: @@ -701,39 +677,10 @@ class Partition(_Formattable): def _path(self): return partition_kname(self.device.path, self._number) - @property - def is_esp(self): - if self.device.type != "disk": - return False - if self.device.ptable == "gpt": - return self.flag == "boot" - else: - blockdev_raw = self._m._probe_data['blockdev'].get(self._path()) - if blockdev_raw is None: - return False - typecode = blockdev_raw.get("ID_PART_ENTRY_TYPE") - if typecode is None: - return False - try: - return int(typecode, 0) == 0xef - except ValueError: - # In case there was garbage in the udev entry... - return False - - @property - def is_bootloader_partition(self): - if self._m.bootloader == Bootloader.BIOS: - return self.flag == "bios_grub" - elif self._m.bootloader == Bootloader.UEFI: - return self.is_esp - elif self._m.bootloader == Bootloader.PREP: - return self.flag == "prep" - else: - return False - @property def ok_for_raid(self): - if self.is_bootloader_partition: + from subiquity.common.filesystem import boot + if boot.is_bootloader_partition(self): return False if self._fs is not None: if self._fs.preserve: @@ -841,10 +788,6 @@ class LVM_LogicalVolume(_Formattable): def flag(self): return None # hack! - @property - def is_esp(self): - return False # another hack! - ok_for_raid = False ok_for_lvm_vg = False @@ -916,6 +859,7 @@ class Mount: spec = attr.ib(default=None) def can_delete(self): + from subiquity.common.filesystem import boot # Can't delete mount of /boot/efi or swap, anything else is fine. if not self.path: # swap mount @@ -923,7 +867,7 @@ class Mount: if not isinstance(self.device.volume, Partition): # Can't be /boot/efi if volume is not a partition return True - if self.device.volume.is_esp: + if boot.is_esp(self.device.volume): # /boot/efi return False return True @@ -1348,6 +1292,7 @@ class FilesystemModel(object): def add_partition(self, device, size, flag="", wipe=None, grub_device=None): + from subiquity.common.filesystem import boot if size > device.free_for_partitions: raise Exception("%s > %s", size, device.free_for_partitions) real_size = align_up(size) @@ -1357,7 +1302,7 @@ class FilesystemModel(object): p = Partition( m=self, device=device, size=real_size, flag=flag, wipe=wipe, grub_device=grub_device) - if p.is_bootloader_partition: + if boot.is_bootloader_partition(p): device._partitions.insert(0, device._partitions.pop()) device.ptable = device.ptable_for_new_partition() dasd = device.dasd() diff --git a/subiquity/models/tests/test_filesystem.py b/subiquity/models/tests/test_filesystem.py index 3edb928c..f1534d0f 100644 --- a/subiquity/models/tests/test_filesystem.py +++ b/subiquity/models/tests/test_filesystem.py @@ -285,26 +285,6 @@ class TestFilesystemModel(unittest.TestCase): self.assertFalse(lv.ok_for_raid) self.assertFalse(lv.ok_for_lvm_vg) - def test_is_esp(self): - model = make_model(Bootloader.UEFI) - gpt_disk = make_disk(model, ptable='gpt') - not_gpt_esp = make_partition(model, gpt_disk) - self.assertFalse(not_gpt_esp.is_esp) - gpt_esp = make_partition(model, gpt_disk, flag='boot') - self.assertTrue(gpt_esp.is_esp) - - dos_disk = make_disk(model, ptable='msdos') - not_dos_esp = make_partition(model, dos_disk) - dos_esp = make_partition(model, dos_disk) - model._probe_data = { - 'blockdev': { - dos_esp._path(): {'ID_PART_ENTRY_TYPE': '0xef'}, - not_dos_esp._path(): {'ID_PART_ENTRY_TYPE': '0x83'}, - } - } - self.assertFalse(not_dos_esp.is_esp) - self.assertTrue(dos_esp.is_esp) - def fake_up_blockdata_disk(disk, **kw): model = disk._m diff --git a/subiquity/server/controllers/filesystem.py b/subiquity/server/controllers/filesystem.py index 586d1ebe..a2e1d711 100644 --- a/subiquity/server/controllers/filesystem.py +++ b/subiquity/server/controllers/filesystem.py @@ -40,7 +40,7 @@ from subiquity.common.errorreport import ErrorReportKind from subiquity.common.filesystem.actions import ( DeviceAction, ) -from subiquity.common.filesystem import labels +from subiquity.common.filesystem import boot, labels from subiquity.common.filesystem.manipulator import FilesystemManipulator from subiquity.common.types import ( Bootloader, @@ -226,8 +226,9 @@ class FilesystemController(SubiquityController, FilesystemManipulator): status=ProbeStatus.DONE, error_report=self.full_probe_error(), disks=[ - labels.for_client(d, min_size=min_size) - for d in self.model._all(type='disk') + labels.for_client(device, min_size=min_size) + for device in self.model._actions + if boot.can_be_boot_device(device, with_reformatting=True) ]) async def guided_POST(self, choice: Optional[GuidedChoice]) \ diff --git a/subiquity/ui/views/filesystem/filesystem.py b/subiquity/ui/views/filesystem/filesystem.py index 26b0d9d5..72a98fab 100644 --- a/subiquity/ui/views/filesystem/filesystem.py +++ b/subiquity/ui/views/filesystem/filesystem.py @@ -63,7 +63,7 @@ from subiquitycore.view import BaseView from subiquity.common.filesystem.actions import ( DeviceAction, ) -from subiquity.common.filesystem import labels +from subiquity.common.filesystem import boot, labels from subiquity.models.filesystem import ( humanize_size, ) @@ -286,7 +286,7 @@ class DeviceList(WidgetWrap): self.parent.refresh_model_inputs() def _disk_TOGGLE_BOOT(self, disk): - if disk._is_boot_device(): + if boot.is_boot_device(disk): self.parent.controller.remove_boot_disk(disk) else: self.parent.controller.add_boot_disk(disk) @@ -329,13 +329,12 @@ class DeviceList(WidgetWrap): ptype=device.ptable_for_new_partition().upper()) def _label_TOGGLE_BOOT(self, action, device): - if device._is_boot_device(): + if boot.is_boot_device(device): return _("Stop Using As Boot Device") else: if self.parent.controller.supports_resilient_boot: - for other in self.parent.model.all_disks(): - if other._is_boot_device(): - return _("Add As Another Boot Device") + if boot.all_boot_devices(self.parent.model): + return _("Add As Another Boot Device") return _("Use As Boot Device") def _action_menu_for_device(self, device): diff --git a/subiquity/ui/views/filesystem/partition.py b/subiquity/ui/views/filesystem/partition.py index b004b019..10609e59 100644 --- a/subiquity/ui/views/filesystem/partition.py +++ b/subiquity/ui/views/filesystem/partition.py @@ -37,7 +37,7 @@ from subiquitycore.ui.container import Pile from subiquitycore.ui.stretchy import Stretchy from subiquitycore.ui.utils import rewrap -from subiquity.common.filesystem import labels +from subiquity.common.filesystem import boot, labels from subiquity.models.filesystem import ( align_up, Disk, @@ -189,7 +189,7 @@ class PartitionForm(Form): self.form_pile.contents[i] = (self.use_swap._table, o) elif w is self.use_swap._table and not show_use: self.form_pile.contents[i] = (self.mount._table, o) - if not getattr(self.device, 'is_esp', False): + if not boot.is_esp(self.device): fstype_for_check = fstype if fstype_for_check is None: fstype_for_check = self.existing_fs_type @@ -381,14 +381,14 @@ class PartitionStretchy(Stretchy): if partition.flag in ["bios_grub", "prep"]: label = None initial['mount'] = None - elif partition.is_esp and not partition.grub_device: + elif boot.is_esp(partition) and not partition.grub_device: label = None else: label = _("Save") initial['size'] = humanize_size(self.partition.size) max_size += self.partition.size - if not partition.is_esp: + if not boot.is_esp(partition): initial.update(initial_data_for_fs(self.partition.fs())) else: if partition.fs() and partition.fs().mount(): @@ -422,7 +422,7 @@ class PartitionStretchy(Stretchy): self.form.buttons.base_widget[0].set_label(_("OK")) if partition is not None: - if partition.is_esp: + if boot.is_esp(partition): if partition.original_fstype(): opts = [ Option(( @@ -465,7 +465,7 @@ class PartitionStretchy(Stretchy): rows = [] focus_index = 0 if partition is not None: - if self.partition.is_esp: + if boot.is_esp(self.partition): if self.partition.grub_device: desc = _(configured_boot_partition_description) if self.partition.preserve: @@ -536,7 +536,7 @@ class PartitionStretchy(Stretchy): def done(self, form): log.debug("Add Partition Result: {}".format(form.as_data())) data = form.as_data() - if self.partition is not None and self.partition.is_esp: + if self.partition is not None and boot.is_esp(self.partition): if self.partition.original_fstype() is None: data['fstype'] = self.partition.fs().fstype if self.partition.fs().mount() is not None: