Improve partition type detection, assignment, and misconfig handling (#3336)

This commit is contained in:
mintsuki 2025-04-04 01:03:02 +02:00 committed by GitHub
parent e6b0ebb5f3
commit fa581021eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 80 additions and 53 deletions

View File

@ -95,13 +95,19 @@ class PartitioningList(ListManager):
'assign_mountpoint': str(_('Assign mountpoint')), 'assign_mountpoint': str(_('Assign mountpoint')),
'mark_formatting': str(_('Mark/Unmark to be formatted (wipes data)')), 'mark_formatting': str(_('Mark/Unmark to be formatted (wipes data)')),
'mark_bootable': str(_('Mark/Unmark as bootable')), 'mark_bootable': str(_('Mark/Unmark as bootable')),
'mark_xbootldr': str(_('Mark/Unmark as XBOOTLDR')), }
if self._using_gpt:
self._actions.update({
'mark_esp': str(_('Mark/Unmark as ESP')),
'mark_xbootldr': str(_('Mark/Unmark as XBOOTLDR'))
})
self._actions.update({
'set_filesystem': str(_('Change filesystem')), 'set_filesystem': str(_('Change filesystem')),
'btrfs_mark_compressed': str(_('Mark/Unmark as compressed')), # btrfs only 'btrfs_mark_compressed': str(_('Mark/Unmark as compressed')), # btrfs only
'btrfs_mark_nodatacow': str(_('Mark/Unmark as nodatacow')), # btrfs only 'btrfs_mark_nodatacow': str(_('Mark/Unmark as nodatacow')), # btrfs only
'btrfs_set_subvolumes': str(_('Set subvolumes')), # btrfs only 'btrfs_set_subvolumes': str(_('Set subvolumes')), # btrfs only
'delete_partition': str(_('Delete partition')) 'delete_partition': str(_('Delete partition'))
} })
device_partitions = [] device_partitions = []
@ -239,8 +245,14 @@ class PartitioningList(ListManager):
# how do we know it was the original one? # how do we know it was the original one?
not_filter += [ not_filter += [
self._actions['set_filesystem'], self._actions['set_filesystem'],
self._actions['mark_bootable'], self._actions['mark_bootable']
self._actions['mark_xbootldr'], ]
if self._using_gpt:
not_filter += [
self._actions['mark_esp'],
self._actions['mark_xbootldr']
]
not_filter += [
self._actions['btrfs_mark_compressed'], self._actions['btrfs_mark_compressed'],
self._actions['btrfs_mark_nodatacow'], self._actions['btrfs_mark_nodatacow'],
self._actions['btrfs_set_subvolumes'] self._actions['btrfs_set_subvolumes']
@ -287,23 +299,46 @@ class PartitioningList(ListManager):
action_key = [k for k, v in self._actions.items() if v == action][0] action_key = [k for k, v in self._actions.items() if v == action][0]
match action_key: match action_key:
case 'assign_mountpoint': case 'assign_mountpoint':
partition.mountpoint = self._prompt_mountpoint() new_mountpoint = self._prompt_mountpoint()
if partition.mountpoint == Path('/boot'): if not partition.is_swap():
partition.set_flag(PartitionFlag.BOOT) if partition.is_home():
if self._using_gpt: partition.invert_flag(PartitionFlag.LINUX_HOME)
partition.set_flag(PartitionFlag.ESP) partition.mountpoint = new_mountpoint
if partition.is_root():
partition.flags = []
if partition.is_boot():
partition.flags = []
partition.set_flag(PartitionFlag.BOOT)
if self._using_gpt:
partition.set_flag(PartitionFlag.ESP)
if partition.is_home():
partition.flags = []
partition.set_flag(PartitionFlag.LINUX_HOME)
case 'mark_formatting': case 'mark_formatting':
self._prompt_formatting(partition) self._prompt_formatting(partition)
case 'mark_bootable': case 'mark_bootable':
partition.invert_flag(PartitionFlag.BOOT) if not partition.is_swap():
if self._using_gpt: partition.invert_flag(PartitionFlag.BOOT)
case 'mark_esp':
if not partition.is_root() and not partition.is_home() and not partition.is_swap():
if PartitionFlag.XBOOTLDR in partition.flags:
partition.invert_flag(PartitionFlag.XBOOTLDR)
partition.invert_flag(PartitionFlag.ESP) partition.invert_flag(PartitionFlag.ESP)
case 'mark_xbootldr': case 'mark_xbootldr':
partition.invert_flag(PartitionFlag.XBOOTLDR) if not partition.is_root() and not partition.is_home() and not partition.is_swap():
if PartitionFlag.ESP in partition.flags:
partition.invert_flag(PartitionFlag.ESP)
partition.invert_flag(PartitionFlag.XBOOTLDR)
case 'set_filesystem': case 'set_filesystem':
fs_type = self._prompt_partition_fs_type() fs_type = self._prompt_partition_fs_type()
if fs_type: if fs_type:
if partition.is_swap():
partition.invert_flag(PartitionFlag.SWAP)
partition.fs_type = fs_type partition.fs_type = fs_type
if partition.is_swap():
partition.mountpoint = None
partition.flags = []
partition.set_flag(PartitionFlag.SWAP)
# btrfs subvolumes will define mountpoints # btrfs subvolumes will define mountpoints
if fs_type == FilesystemType.Btrfs: if fs_type == FilesystemType.Btrfs:
partition.mountpoint = None partition.mountpoint = None
@ -390,7 +425,6 @@ class PartitioningList(ListManager):
def _prompt_mountpoint(self) -> Path: def _prompt_mountpoint(self) -> Path:
header = str(_('Partition mount-points are relative to inside the installation, the boot would be /boot as an example.')) + '\n' header = str(_('Partition mount-points are relative to inside the installation, the boot would be /boot as an example.')) + '\n'
header += str(_('If mountpoint /boot is set, then the partition will also be marked as bootable.')) + '\n'
prompt = str(_('Mountpoint')) prompt = str(_('Mountpoint'))
mountpoint = prompt_dir(prompt, header, validate=False, allow_skip=False) mountpoint = prompt_dir(prompt, header, validate=False, allow_skip=False)
@ -520,6 +554,8 @@ class PartitioningList(ListManager):
if self._using_gpt: if self._using_gpt:
partition.set_flag(PartitionFlag.ESP) partition.set_flag(PartitionFlag.ESP)
elif partition.is_swap(): elif partition.is_swap():
partition.mountpoint = None
partition.flags = []
partition.set_flag(PartitionFlag.SWAP) partition.set_flag(PartitionFlag.SWAP)
return partition return partition

View File

@ -425,21 +425,40 @@ class GlobalMenu(AbstractMenu):
shim if necessary. shim if necessary.
""" """
bootloader = self._item_group.find_by_key('bootloader').value bootloader = self._item_group.find_by_key('bootloader').value
root_partition: PartitionModification | None = None
boot_partition: PartitionModification | None = None boot_partition: PartitionModification | None = None
efi_partition: PartitionModification | None = None
if disk_config := self._item_group.find_by_key('disk_config').value: if disk_config := self._item_group.find_by_key('disk_config').value:
for layout in disk_config.device_modifications:
if root_partition := layout.get_root_partition():
break
for layout in disk_config.device_modifications: for layout in disk_config.device_modifications:
if boot_partition := layout.get_boot_partition(): if boot_partition := layout.get_boot_partition():
break break
if SysInfo.has_uefi():
for layout in disk_config.device_modifications:
if efi_partition := layout.get_efi_partition():
break
else: else:
return "No disk layout selected" return "No disk layout selected"
if root_partition is None:
return "Root partition not found"
if boot_partition is None: if boot_partition is None:
return "Boot partition not found" return "Boot partition not found"
if SysInfo.has_uefi():
if efi_partition is None:
return "EFI system partition (ESP) not found"
if efi_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]:
return "ESP must be formatted as a FAT filesystem"
if bootloader == Bootloader.Limine: if bootloader == Bootloader.Limine:
if boot_partition.fs_type != FilesystemType.Fat32: if boot_partition.fs_type not in [FilesystemType.Fat12, FilesystemType.Fat16, FilesystemType.Fat32]:
return "Limine does not support booting without a FAT boot partition" return "Limine does not support booting with a non-FAT boot partition"
return None return None

View File

@ -871,9 +871,6 @@ class PartitionModification:
partuuid: str | None = None partuuid: str | None = None
uuid: str | None = None uuid: str | None = None
_efi_indicator_flags = (PartitionFlag.BOOT, PartitionFlag.ESP)
_boot_indicator_flags = (PartitionFlag.BOOT, PartitionFlag.XBOOTLDR)
def __post_init__(self) -> None: def __post_init__(self) -> None:
# needed to use the object as a dictionary key due to hash func # needed to use the object as a dictionary key due to hash func
if not hasattr(self, '_obj_id'): if not hasattr(self, '_obj_id'):
@ -951,26 +948,16 @@ class PartitionModification:
raise ValueError('Mountpoint is not specified') raise ValueError('Mountpoint is not specified')
def is_efi(self) -> bool: def is_efi(self) -> bool:
return ( return PartitionFlag.ESP in self.flags
any(set(self.flags) & set(self._efi_indicator_flags))
and (
self.fs_type == FilesystemType.Fat12
or self.fs_type == FilesystemType.Fat16
or self.fs_type == FilesystemType.Fat32
)
and PartitionFlag.XBOOTLDR not in self.flags
)
def is_boot(self) -> bool: def is_boot(self) -> bool:
""" if self.mountpoint is not None:
Returns True if any of the boot indicator flags are found in self.flags return self.mountpoint == Path('/boot')
or if the partition is mounted on /boot return False
"""
return any(set(self.flags) & set(self._boot_indicator_flags)) or Path('/boot') == self.mountpoint
def is_root(self) -> bool: def is_root(self) -> bool:
if self.mountpoint is not None: if self.mountpoint is not None:
return Path('/') == self.mountpoint return self.mountpoint == Path('/')
else: else:
for subvol in self.btrfs_subvols: for subvol in self.btrfs_subvols:
if subvol.is_root(): if subvol.is_root():
@ -979,10 +966,9 @@ class PartitionModification:
return False return False
def is_home(self) -> bool: def is_home(self) -> bool:
return ( if self.mountpoint is not None:
self.mountpoint == Path('/home') return self.mountpoint == Path('/home')
or PartitionFlag.LINUX_HOME in self.flags return False
)
def is_swap(self) -> bool: def is_swap(self) -> bool:
return self.fs_type == FilesystemType.LinuxSwap return self.fs_type == FilesystemType.LinuxSwap
@ -1377,26 +1363,12 @@ class DeviceModification:
self.partitions.append(partition) self.partitions.append(partition)
def get_efi_partition(self) -> PartitionModification | None: def get_efi_partition(self) -> PartitionModification | None:
"""
Similar to get_boot_partition() but excludes XBOOTLDR partitions from it's candidates.
"""
filtered = filter(lambda x: x.is_efi() and x.mountpoint, self.partitions) filtered = filter(lambda x: x.is_efi() and x.mountpoint, self.partitions)
return next(filtered, None) return next(filtered, None)
def get_boot_partition(self) -> PartitionModification | None: def get_boot_partition(self) -> PartitionModification | None:
""" filtered = filter(lambda x: x.is_boot() and x.mountpoint, self.partitions)
Returns the first partition marked as XBOOTLDR (PARTTYPE id of bc13c2ff-...) or Boot and has a mountpoint. return next(filtered, None)
Only returns XBOOTLDR if separate EFI is detected using self.get_efi_partition()
Will return None if no suitable partition is found.
"""
if efi_partition := self.get_efi_partition():
filtered = filter(lambda x: x.is_boot() and x != efi_partition and x.mountpoint, self.partitions)
if boot_partition := next(filtered, None):
return boot_partition
return efi_partition
else:
filtered = filter(lambda x: x.is_boot() and x.mountpoint, self.partitions)
return next(filtered, None)
def get_root_partition(self) -> PartitionModification | None: def get_root_partition(self) -> PartitionModification | None:
filtered = filter(lambda x: x.is_root(), self.partitions) filtered = filter(lambda x: x.is_root(), self.partitions)