index : archinstall32 | |
Archlinux32 installer | gitolite user |
summaryrefslogtreecommitdiff |
-rw-r--r-- | archinstall/lib/disk.py | 203 | ||||
-rw-r--r-- | archinstall/lib/general.py | 3 | ||||
-rw-r--r-- | archinstall/lib/user_interaction.py | 287 |
diff --git a/archinstall/lib/disk.py b/archinstall/lib/disk.py index de39bafd..e0d9b423 100644 --- a/archinstall/lib/disk.py +++ b/archinstall/lib/disk.py @@ -2,7 +2,6 @@ import glob import pathlib import re import time -from collections import OrderedDict from typing import Optional from .general import * @@ -30,11 +29,11 @@ class BlockDevice: self.path = path self.info = info self.keep_partitions = True - self.part_cache = OrderedDict() + self.part_cache = {} + # TODO: Currently disk encryption is a BIT misleading. # It's actually partition-encryption, but for future-proofing this # I'm placing the encryption password on a BlockDevice level. - self.encryption_password = None def __repr__(self, *args, **kwargs): return f"BlockDevice({self.device})" @@ -48,6 +47,12 @@ class BlockDevice: raise KeyError(f'{self} does not contain information: "{key}"') return self.info[key] + def __len__(self): + return len(self.partitions) + + def __lt__(self, left_comparitor): + return self.path < left_comparitor.path + def json(self): """ json() has precedence over __dump__, so this is a way @@ -61,12 +66,21 @@ class BlockDevice: def __dump__(self): return { - 'path': self.path, - 'info': self.info, - 'partition_cache': self.part_cache + self.path : { + 'partuuid' : self.uuid, + 'wipe' : self.info.get('wipe', None), + 'partitions' : [part.__dump__() for part in self.partitions.values()] + } } @property + def partition_type(self): + output = json.loads(SysCommand(f"lsblk --json -o+PTTYPE {self.path}").decode('UTF-8')) + + for device in output['blockdevices']: + return device['pttype'] + + @property def device(self): """ Returns the actual device-endpoint of the BlockDevice. @@ -78,7 +92,7 @@ class BlockDevice: raise DiskError(f'Could not locate backplane info for "{self.path}"') if self.info['type'] == 'loop': - for drive in json.loads(b''.join(SysCommand(['losetup', '--json'])).decode('UTF_8'))['loopdevices']: + for drive in json.loads(SysCommand(['losetup', '--json']).decode('UTF_8'))['loopdevices']: if not drive['name'] == self.path: continue @@ -100,18 +114,17 @@ class BlockDevice: @property def partitions(self): - o = b''.join(SysCommand(['partprobe', self.path])) + SysCommand(['partprobe', self.path]) - # o = b''.join(sys_command('/usr/bin/lsblk -o name -J -b {dev}'.format(dev=dev))) - o = b''.join(SysCommand(['/usr/bin/lsblk', '-J', self.path])) + result = SysCommand(['/usr/bin/lsblk', '-J', self.path]) - if b'not a block device' in o: + if b'not a block device' in result: raise DiskError(f'Can not read partitions off something that isn\'t a block device: {self.path}') - if not o[:1] == b'{': + if not result[:1] == b'{': raise DiskError('Error getting JSON output from:', f'/usr/bin/lsblk -J {self.path}') - r = json.loads(o.decode('UTF-8')) + r = json.loads(result.decode('UTF-8')) if len(r['blockdevices']) and 'children' in r['blockdevices'][0]: root_path = f"/dev/{r['blockdevices'][0]['name']}" for part in r['blockdevices'][0]['children']: @@ -140,10 +153,18 @@ class BlockDevice: This is more reliable than relying on /dev/disk/by-partuuid as it doesn't seam to be able to detect md raid partitions. """ - lsblk = b''.join(SysCommand(f'lsblk -J -o+UUID {self.path}')) - for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']: + for partition in json.loads(SysCommand(f'lsblk -J -o+UUID {self.path}').decode('UTF-8'))['blockdevices']: return partition.get('uuid', None) + @property + def size(self): + output = json.loads(SysCommand(f"lsblk --json -o+SIZE {self.path}").decode('UTF-8')) + + for device in output['blockdevices']: + assert device['size'][-1] == 'G' # Make sure we're counting in Gigabytes, otherwise the next logic fails. + + return float(device['size'][:-1]) + def has_partitions(self): return len(self.partitions) @@ -154,7 +175,7 @@ class BlockDevice: return False def flush_cache(self): - self.part_cache = OrderedDict() + self.part_cache = {} class Partition: @@ -210,6 +231,77 @@ class Partition: else: return f'Partition(path={self.path}, size={self.size}, fs={self.filesystem}{mount_repr})' + def __dump__(self): + return { + 'type' : 'primary', + 'PARTUUID' : self.uuid, + 'wipe' : self.allow_formatting, + 'boot' : self.boot, + 'ESP' : self.boot, + 'mountpoint' : self.target_mountpoint, + 'encrypted' : self._encrypted, + 'start' : self.start, + 'size' : self.end, + 'filesystem' : { + 'format' : get_filesystem_type(self.path) + } + } + + @property + def sector_size(self): + output = json.loads(SysCommand(f"lsblk --json -o+LOG-SEC {self.path}").decode('UTF-8')) + + for device in output['blockdevices']: + return device.get('log-sec', None) + + @property + def start(self): + output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) + + for partition in output.get('partitiontable', {}).get('partitions', []): + if partition['node'] == self.path: + return partition['start']# * self.sector_size + + @property + def end(self): + # TODO: Verify that the logic holds up, that 'size' is the size without 'start' added to it. + output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) + + for partition in output.get('partitiontable', {}).get('partitions', []): + if partition['node'] == self.path: + return partition['size']# * self.sector_size + + @property + def boot(self): + output = json.loads(SysCommand(f"sfdisk --json {self.block_device.path}").decode('UTF-8')) + + # Get the bootable flag from the sfdisk output: + # { + # "partitiontable": { + # "label":"dos", + # "id":"0xd202c10a", + # "device":"/dev/loop0", + # "unit":"sectors", + # "sectorsize":512, + # "partitions": [ + # {"node":"/dev/loop0p1", "start":2048, "size":10483712, "type":"83", "bootable":true} + # ] + # } + # } + + for partition in output.get('partitiontable', {}).get('partitions', []): + if partition['node'] == self.path: + return partition.get('bootable', False) + + return False + + @property + def partition_type(self): + lsblk = json.loads(SysCommand(f"lsblk --json -o+PTTYPE {self.path}").decode('UTF-8')) + + for device in lsblk['blockdevices']: + return device['pttype'] + @property def uuid(self) -> Optional[str]: """ @@ -217,8 +309,8 @@ class Partition: This is more reliable than relying on /dev/disk/by-partuuid as it doesn't seam to be able to detect md raid partitions. """ - lsblk = b''.join(SysCommand(f'lsblk -J -o+PARTUUID {self.path}')) - for partition in json.loads(lsblk.decode('UTF-8'))['blockdevices']: + lsblk = json.loads(SysCommand(f'lsblk -J -o+PARTUUID {self.path}').decode('UTF-8')) + for partition in lsblk['blockdevices']: return partition.get('partuuid', None) return None @@ -237,7 +329,7 @@ class Partition: @property def real_device(self): - for blockdevice in json.loads(b''.join(SysCommand('lsblk -J')).decode('UTF-8'))['blockdevices']: + for blockdevice in json.loads(SysCommand('lsblk -J').decode('UTF-8'))['blockdevices']: if parent := self.find_parent_of(blockdevice, os.path.basename(self.path)): return f"/dev/{parent}" # raise DiskError(f'Could not find appropriate parent for encrypted partition {self}') @@ -331,30 +423,29 @@ class Partition: log(f'Formatting {path} -> {filesystem}', level=logging.INFO) if filesystem == 'btrfs': - o = b''.join(SysCommand(f'/usr/bin/mkfs.btrfs -f {path}')) - if b'UUID' not in o: - raise DiskError(f'Could not format {path} with {filesystem} because: {o}') + if b'UUID' not in (mkfs := SysCommand(f'/usr/bin/mkfs.btrfs -f {path}')): + raise DiskError(f'Could not format {path} with {filesystem} because: {mkfs}') self.filesystem = 'btrfs' elif filesystem == 'vfat': - o = b''.join(SysCommand(f'/usr/bin/mkfs.vfat -F32 {path}')) - if (b'mkfs.fat' not in o and b'mkfs.vfat' not in o) or b'command not found' in o: - raise DiskError(f'Could not format {path} with {filesystem} because: {o}') + mkfs = SysCommand(f'/usr/bin/mkfs.vfat -F32 {path}') + if (b'mkfs.fat' not in mkfs and b'mkfs.vfat' not in mkfs) or b'command not found' in mkfs: + raise DiskError(f"Could not format {path} with {filesystem} because: {mkfs}") self.filesystem = 'vfat' elif filesystem == 'ext4': if (handle := SysCommand(f'/usr/bin/mkfs.ext4 -F {path}')).exit_code != 0: - raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') + raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") self.filesystem = 'ext4' elif filesystem == 'xfs': if (handle := SysCommand(f'/usr/bin/mkfs.xfs -f {path}')).exit_code != 0: - raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') + raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") self.filesystem = 'xfs' elif filesystem == 'f2fs': if (handle := SysCommand(f'/usr/bin/mkfs.f2fs -f {path}')).exit_code != 0: - raise DiskError(f'Could not format {path} with {filesystem} because: {b"".join(handle)}') + raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") self.filesystem = 'f2fs' elif filesystem == 'crypto_LUKS': @@ -393,9 +484,9 @@ class Partition: try: if options: - SysCommand(f'/usr/bin/mount -o {options} {self.path} {target}') + SysCommand(f"/usr/bin/mount -o {options} {self.path} {target}") else: - SysCommand(f'/usr/bin/mount {self.path} {target}') + SysCommand(f"/usr/bin/mount {self.path} {target}") except SysCallError as err: raise err @@ -404,7 +495,7 @@ class Partition: def unmount(self): try: - exit_code = SysCommand(f'/usr/bin/umount {self.path}').exit_code + SysCommand(f"/usr/bin/umount {self.path}") except SysCallError as err: exit_code = err.exit_code @@ -476,17 +567,29 @@ class Filesystem: # TODO: https://stackoverflow.com/questions/28157929/how-to-safely-handle-an-exception-inside-a-context-manager if len(args) >= 2 and args[1]: raise args[1] - b''.join(SysCommand('sync')) + SysCommand('sync') return True + def load_layout(self, layout :dict): + for partition in layout: + # We don't want to re-add an existing partition (those containing a UUID already) + if 'UUID' not in partition: + self.add_partition(partition.get('type', 'primary'), + start=partition.get('start', '1MiB'), # TODO: Revisit sane block starts (4MB for memorycards for instance) + end=partition.get('size', '100%'), + partition_format=partition.get('filesystem', {}).get('format', 'btrfs')) + + + + exit(0) + def find_partition(self, mountpoint): for partition in self.blockdevice: if partition.target_mountpoint == mountpoint or partition.mountpoint == mountpoint: return partition def raw_parted(self, string: str): - x = SysCommand(f'/usr/bin/parted -s {string}') - return x + return SysCommand(f'/usr/bin/parted -s {string}') def parted(self, string: str): """ @@ -582,9 +685,10 @@ def device_state(name, *args, **kwargs): # lsblk --json -l -n -o path def all_disks(*args, **kwargs): kwargs.setdefault("partitions", False) - drives = OrderedDict() - # for drive in json.loads(sys_command(f'losetup --json', *args, **lkwargs, hide_from_log=True)).decode('UTF_8')['loopdevices']: - for drive in json.loads(b''.join(SysCommand('lsblk --json -l -n -o path,size,type,mountpoint,label,pkname,model')).decode('UTF_8'))['blockdevices']: + drives = {} + + lsblk = json.loads(SysCommand('lsblk --json -l -n -o path,size,type,mountpoint,label,pkname,model').decode('UTF_8')) + for drive in lsblk['blockdevices']: if not kwargs['partitions'] and drive['type'] == 'part': continue @@ -617,12 +721,10 @@ def harddrive(size=None, model=None, fuzzy=False): def get_mount_info(path) -> dict: try: - output = SysCommand(f'/usr/bin/findmnt --json {path}') + output = SysCommand(f'/usr/bin/findmnt --json {path}').decode('UTF-8') except SysCallError: return {} - output = output.decode('UTF-8') - if not output: return {} @@ -636,14 +738,12 @@ def get_mount_info(path) -> dict: def get_partitions_in_use(mountpoint) -> list: try: - output = SysCommand(f'/usr/bin/findmnt --json -R {mountpoint}') + output = SysCommand(f"/usr/bin/findmnt --json -R {mountpoint}").decode('UTF-8') except SysCallError: return [] mounts = [] - output = output.decode('UTF-8') - if not output: return [] @@ -659,16 +759,25 @@ def get_partitions_in_use(mountpoint) -> list: def get_filesystem_type(path): try: - handle = SysCommand(f"blkid -o value -s TYPE {path}") - return b''.join(handle).strip().decode('UTF-8') + return SysCommand(f"blkid -o value -s TYPE {path}").decode('UTF-8').strip() except SysCallError: return None def disk_layouts(): try: - handle = SysCommand("lsblk -f -o+TYPE,SIZE -J") - return json.loads(b''.join(handle).decode('UTF-8')) + return json.loads(SysCommand("lsblk -f -o+TYPE,SIZE -J").decode('UTF-8')) except SysCallError as err: log(f"Could not return disk layouts: {err}") return None + + +def encrypted_partitions(blockdevices :dict) -> bool: + for partition in blockdevices.values(): + if partition.get('encrypted', False): + yield partition + +def find_partition_by_mountpoint(partitions, relative_mountpoint :str): + for partition in partitions: + if partition.get('mountpoint', None) == relative_mountpoint: + return partition
\ No newline at end of file diff --git a/archinstall/lib/general.py b/archinstall/lib/general.py index b9dc66ab..4d3257ba 100644 --- a/archinstall/lib/general.py +++ b/archinstall/lib/general.py @@ -71,6 +71,8 @@ def locate_binary(name): raise RequirementError(f"Binary {name} does not exist.") +def json_dumps(*args, **kwargs): + return json.dumps(*args, **{**kwargs, 'cls': JSON}) class JsonEncoder: def _encode(obj): @@ -113,7 +115,6 @@ class JSON(json.JSONEncoder, json.JSONDecoder): def encode(self, obj): return super(JSON, self).encode(self._encode(obj)) - class SysCommandWorker: def __init__(self, cmd, callbacks=None, peak_output=False, environment_vars=None, logfile=None, working_directory='./'): if not callbacks: diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 07b675a4..4f99820d 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -9,6 +9,7 @@ import signal import sys import time +from .disk import BlockDevice from .exceptions import * from .general import SysCommand from .hardware import AVAILABLE_GFX_DRIVERS, has_uefi @@ -17,7 +18,6 @@ from .networking import list_interfaces from .output import log from .profiles import Profile, list_profiles - # TODO: Some inconsistencies between the selection processes. # Some return the keys from the options, some the values? @@ -186,8 +186,24 @@ def generic_multi_select(options, text="Select one or more of the options above except RequirementError as e: log(f" * {e} * ", fg='red') + sys.stdout.write('\n') + sys.stdout.flush() return selected_options +def select_encrypted_partitions(blockdevices :dict) -> dict: + print(blockdevices[0]) + + if len(blockdevices) == 1: + if len(blockdevices[0]['partitions']) == 2: + root = find_partition_by_mountpoint(blockdevices[0]['partitions'], '/') + blockdevices[0]['partitions'][root]['encrypted'] = True + return True + + options = [] + for partition in blockdevices.values(): + options.append({key: val for key, val in partition.items() if val}) + + print(generic_multi_select(options, f"Choose which partitions to encrypt (leave blank when done): ")) class MiniCurses: def __init__(self, width, height): @@ -535,6 +551,275 @@ def generic_select(options, input_text="Select one of the above by index or abso return selected_option +def select_partition_layout(block_device): + return { + BlockDevice("/dev/sda"): { # Block Device level + "wipe": False, # Safety flags + "partitions" : [ # Affected / New partitions + { + "PARTUUID" : "654bb317-1b73-4339-9a00-7222792f4ba9", # If existing partition + "wipe" : False, # Safety flags + "boot" : True, # Safety flags / new flags + "ESP" : True, # Safety flags / new flags + "mountpoint" : "/mnt/boot" + } + ] + }, + BlockDevice("/dev/sdb") : { + "wipe" : True, + "partitions" : [ + { + # No PARTUUID required here since it's a new partition + "type" : "primary", # parted options + "size" : "100%", + "filesystem" : { + "encrypted" : True, # TODO: Not sure about this here + "format": "btrfs", # mkfs options + }, + "mountpoint" : "/mnt" + } + ] + } + } + +def valid_fs_type(fstype :str) -> bool: + # https://www.gnu.org/software/parted/manual/html_node/mkpart.html + + return fstype in [ + "ext2", + "fat16", "fat32", + "hfs", "hfs+", "hfsx", + "linux-swap", + "NTFS", + "reiserfs", + "ufs", + "btrfs", + ] + +def valid_parted_position(pos :str): + if not len(pos): + return False + + if pos.isdigit(): + return True + + if pos[-1] == '%' and pos[:-1].isdigit(): + return True + + if pos[-3:].lower() in ['mib', 'kib', 'b', 'tib'] and pos[:-3].isdigit(): + return True + + return False + +def partition_overlap(partitions :list, start :str, end :str) -> bool: + # TODO: Implement sanity check + return False + +def get_default_partition_layout(block_devices): + if len(block_devices) == 1: + return { + block_devices[0] : [ + { # Boot + "type" : "primary", + "start" : "0MiB", + "size" : "513MiB", + "boot" : True, + "mountpoint" : "/boot", + "filesystem" : { + "format" : "fat32" + } + }, + { # Root + "type" : "primary", + "start" : "513MiB", + "encrypted" : True, + "size" : f"{max(block_devices[0].size*0.2, 20)*1024}MiB", # Warning: Won't work on small where max size is 16GB for instance. + "mountpoint" : "/", + "filesystem" : { + "format" : "btrfs" + } + }, + { # Home + "type" : "primary", + "encrypted" : True, + "start" : f"{513 + (max(block_devices[0].size*0.2, 20)*1024)}MiB", + "size" : "100%", + "mountpoint" : "/home", + "filesystem" : { + "format" : "btrfs" + } + } + ] + } + # TODO: Implement sane generic layout for 2+ drives + +def wipe_and_create_partitions(block_device :BlockDevice) -> dict: + if hasUEFI(): + partition_type = 'gpt' + else: + partition_type = 'msdos' + + partitions_result = [] # Test code: [part.__dump__() for part in block_device.partitions.values()] + suggested_layout = [ + { # Boot + "type" : "primary", + "start" : "0MiB", + "size" : "513MiB", + "boot" : True, + "mountpoint" : "/boot", + "filesystem" : { + "format" : "fat32" + } + }, + { # Root + "type" : "primary", + "start" : "513MiB", + "encrypted" : True, + "size" : f"{max(block_device.size*0.2, 20)}GiB", + "mountpoint" : "", + "filesystem" : { + "format" : "btrfs" + } + }, + { # Home + "type" : "primary", + "encrypted" : True, + "start" : f"{max(block_device.size*0.2, 20)}GiB", + "size" : "100%", + "mountpoint" : "/home", + "filesystem" : { + "format" : "btrfs" + } + } + ] + # TODO: Squeeze in BTRFS subvolumes here + + while True: + modes = [ + "Create new partition", + "Suggest partition layout", + "Delete partition" if len(partitions_result) else "", + "Assign mount-point for partition" if len(partitions_result) else "", + "Mark/Unmark a partition as encrypted" if len(partitions_result) else "", + "Mark/Unmark a partition as bootable (automatic for /boot)" if len(partitions_result) else "" + ] + + # Print current partition layout: + if len(partitions_result): + print('Current partition layout:') + for partition in partitions_result: + print({key: val for key, val in partition.items() if val}) + print() + + task = generic_select(modes, + input_text=f"Select what to do with {block_device} (leave blank when done): ") + + if task == 'Create new partition': + if partition_type == 'gpt': + # https://www.gnu.org/software/parted/manual/html_node/mkpart.html + # https://www.gnu.org/software/parted/manual/html_node/mklabel.html + name = input("Enter a desired name for the partition: ").strip() + fstype = input("Enter a desired filesystem type for the partition: ").strip() + start = input("Enter the start sector of the partition (percentage or block number, ex: 0%): ").strip() + end = input("Enter the end sector of the partition (percentage or block number, ex: 100%): ").strip() + + if valid_parted_position(start) and valid_parted_position(end) and valid_fs_type(fstype): + if partition_overlap(partitions_result, start, end): + log(f"This partition overlaps with other partitions on the drive! Ignoring this partition creation.", fg="red") + continue + + partitions_result.append({ + "type" : "primary", # Strictly only allowed under MSDOS, but GPT accepts it so it's "safe" to inject + "start" : start, + "size" : end, + "mountpoint" : None, + "filesystem" : { + "format" : fstype + } + }) + else: + log(f"Invalid start, end or fstype for this partition. Ignoring this partition creation.", fg="red") + continue + elif task == "Suggest partition layout": + if len(partitions_result): + if input(f"{block_device} contains queued partitions, this will remove those, are you sure? y/N: ").strip().lower() in ('', 'n'): + continue + + partitions_result = [*suggested_layout] + elif task is None: + return { + block_device : partitions_result + } + else: + for index, partition in enumerate(partitions_result): + print(f"{index}: Start: {partition['start']}, End: {partition['size']} ({partition['filesystem']['format']}{', mounting at: '+partition['mountpoint'] if partition['mountpoint'] else ''})") + + if task == "Delete partition": + if (partition := generic_select(partitions_result, 'Select which partition to delete: ', options_output=False)): + del(partitions_result[partitions_result.index(partition)]) + elif task == "Assign mount-point for partition": + if (partition := generic_select(partitions_result, 'Select which partition to mount where: ', options_output=False)): + print(' * Partition mount-points are relative to inside the installation, the boot would be /boot as an example.') + mountpoint = input('Select where to mount partition (leave blank to remove mountpoint): ').strip() + + if len(mountpoint): + partitions_result[partitions_result.index(partition)]['mountpoint'] = mountpoint + if mountpoint == '/boot': + log(f"Marked partition as bootable because mountpoint was set to /boot.", fg="yellow") + partitions_result[partitions_result.index(partition)]['boot'] = True + else: + del(partitions_result[partitions_result.index(partition)]['mountpoint']) + + elif task == "Mark/Unmark a partition as encrypted": + if (partition := generic_select(partitions_result, 'Select which partition to mark as encrypted: ', options_output=False)): + # Negate the current encryption marking + partitions_result[partitions_result.index(partition)]['encrypted'] = not partitions_result[partitions_result.index(partition)].get('encrypted', False) + + elif task == "Mark/Unmark a partition as bootable (automatic for /boot)": + if (partition := generic_select(partitions_result, 'Select which partition to mark as bootable: ', options_output=False)): + partitions_result[partitions_result.index(partition)]['boot'] = not partitions_result[partitions_result.index(partition)].get('boot', False) + + return { + block_device : partitions_result + } + +def select_individual_blockdevice_usage(block_devices :list): + result = {} + + for device in block_devices: + log(f'Select what to do with {device}', fg="yellow") + modes = [ + "Wipe and create new partitions", + "Re-use partitions" + ] + + device_mode = generic_select(modes) + + if device_mode == "Re-use partitions": + layout = select_partition_layout(device) + elif device_mode == "Wipe and create new partitions": + layout = wipe_and_create_partitions(device) + else: + continue + + result[device] = layout + + return result + + +def select_disk_layout(block_devices :list): + modes = [ + "Wipe all selected drives and use a best-effort default partition layout", + "Select what to do with each individual drive (followed by partition usage)" + ] + + mode = generic_select(modes, input_text=f"Select what you wish to do with the selected block devices: ") + + if mode == 'Wipe all selected drives and use a best-effort default partition layout': + return get_default_partition_layout(block_devices) + else: + return select_individual_blockdevice_usage(block_devices) + def select_disk(dict_o_disks): """ |