From 7ac06d75d0785da07d270dc38a7581d74c285cc5 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 22 Oct 2021 20:43:01 +0200 Subject: Restructured disk.py into lib/disk/.py instead. Shouldn't be any broken links as we expose all the functions through __init__.py - but you never know so I'll keep an eye for issues with this. --- archinstall/lib/disk/helpers.py | 178 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 archinstall/lib/disk/helpers.py (limited to 'archinstall/lib/disk/helpers.py') diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py new file mode 100644 index 00000000..d37dfd9b --- /dev/null +++ b/archinstall/lib/disk/helpers.py @@ -0,0 +1,178 @@ +import re +import json +from ..exceptions import SysCallError +from ..general import SysCommand +from ..output import log + +ROOT_DIR_PATTERN = re.compile('^.*?/devices') + +def sort_block_devices_based_on_performance(block_devices): + result = {device: 0 for device in block_devices} + + for device, weight in result.items(): + if device.spinning: + weight -= 10 + else: + weight += 5 + + if device.bus_type == 'nvme': + weight += 20 + elif device.bus_type == 'sata': + weight += 10 + + result[device] = weight + + return result + +def filter_disks_below_size_in_gb(devices, gigabytes): + for disk in devices: + if disk.size >= gigabytes: + yield disk + +def select_largest_device(devices, gigabytes, filter_out=None): + if not filter_out: + filter_out = [] + + copy_devices = [*devices] + for filter_device in filter_out: + if filter_device in copy_devices: + copy_devices.pop(copy_devices.index(filter_device)) + + copy_devices = list(filter_disks_below_size_in_gb(copy_devices, gigabytes)) + + if not len(copy_devices): + return None + + return max(copy_devices, key=(lambda device : device.size)) + +def select_disk_larger_than_or_close_to(devices, gigabytes, filter_out=None): + if not filter_out: + filter_out = [] + + copy_devices = [*devices] + for filter_device in filter_out: + if filter_device in copy_devices: + copy_devices.pop(copy_devices.index(filter_device)) + + if not len(copy_devices): + return None + + return min(copy_devices, key=(lambda device : abs(device.size - gigabytes))) + +def convert_to_gigabytes(string): + unit = string.strip()[-1] + size = float(string.strip()[:-1]) + + if unit == 'M': + size = size / 1024 + elif unit == 'T': + size = size * 1024 + + return size + +def device_state(name, *args, **kwargs): + # Based out of: https://askubuntu.com/questions/528690/how-to-get-list-of-all-non-removable-disk-device-names-ssd-hdd-and-sata-ide-onl/528709#528709 + if os.path.isfile('/sys/block/{}/device/block/{}/removable'.format(name, name)): + with open('/sys/block/{}/device/block/{}/removable'.format(name, name)) as f: + if f.read(1) == '1': + return + + path = ROOT_DIR_PATTERN.sub('', os.readlink('/sys/block/{}'.format(name))) + hotplug_buses = ("usb", "ieee1394", "mmc", "pcmcia", "firewire") + for bus in hotplug_buses: + if os.path.exists('/sys/bus/{}'.format(bus)): + for device_bus in os.listdir('/sys/bus/{}/devices'.format(bus)): + device_link = ROOT_DIR_PATTERN.sub('', os.readlink('/sys/bus/{}/devices/{}'.format(bus, device_bus))) + if re.search(device_link, path): + return + return True + +# lsblk --json -l -n -o path +def all_disks(*args, **kwargs): + kwargs.setdefault("partitions", False) + 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 + + drives[drive['path']] = BlockDevice(drive['path'], drive) + + return drives + + +def harddrive(size=None, model=None, fuzzy=False): + collection = all_disks() + for drive in collection: + if size and convert_to_gigabytes(collection[drive]['size']) != size: + continue + if model and (collection[drive]['model'] is None or collection[drive]['model'].lower() != model.lower()): + continue + + return collection[drive] + + +def get_mount_info(path) -> dict: + try: + output = SysCommand(f'/usr/bin/findmnt --json {path}').decode('UTF-8') + except SysCallError: + return {} + + if not output: + return {} + + output = json.loads(output) + if 'filesystems' in output: + if len(output['filesystems']) > 1: + raise DiskError(f"Path '{path}' contains multiple mountpoints: {output['filesystems']}") + + return output['filesystems'][0] + + +def get_partitions_in_use(mountpoint) -> list: + try: + output = SysCommand(f"/usr/bin/findmnt --json -R {mountpoint}").decode('UTF-8') + except SysCallError: + return [] + + mounts = [] + + if not output: + return [] + + output = json.loads(output) + for target in output.get('filesystems', []): + mounts.append(Partition(target['source'], None, filesystem=target.get('fstype', None), mountpoint=target['target'])) + + for child in target.get('children', []): + mounts.append(Partition(child['source'], None, filesystem=child.get('fstype', None), mountpoint=child['target'])) + + return mounts + + +def get_filesystem_type(path): + try: + return SysCommand(f"blkid -o value -s TYPE {path}").decode('UTF-8').strip() + except SysCallError: + return None + + +def disk_layouts(): + try: + 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(block_devices, relative_mountpoint :str): + for device in block_devices: + for partition in block_devices[device]['partitions']: + if partition.get('mountpoint', None) == relative_mountpoint: + return partition \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 2ef793b76af3173c3bbe7fd9942263df6d84d464 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 22 Oct 2021 20:55:00 +0200 Subject: Forgot some imports that didn't show up on a static run without going through a few of the menu's --- archinstall/lib/disk/blockdevice.py | 2 ++ archinstall/lib/disk/helpers.py | 1 + 2 files changed, 3 insertions(+) (limited to 'archinstall/lib/disk/helpers.py') diff --git a/archinstall/lib/disk/blockdevice.py b/archinstall/lib/disk/blockdevice.py index e2fcdae5..422f35aa 100644 --- a/archinstall/lib/disk/blockdevice.py +++ b/archinstall/lib/disk/blockdevice.py @@ -1,4 +1,6 @@ +import json from ..output import log +from ..general import SysCommand class BlockDevice: def __init__(self, path, info=None): diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index d37dfd9b..8b372f73 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -1,5 +1,6 @@ import re import json +from .blockdevice import BlockDevice from ..exceptions import SysCallError from ..general import SysCommand from ..output import log -- cgit v1.2.3-70-g09d2 From 7149b76f3bd3163938fe7413546e5f678f98851f Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Fri, 22 Oct 2021 21:54:16 +0200 Subject: Forgot some imports that didn't show up on a static run without going through a few of the menu's --- archinstall/lib/disk/__init__.py | 2 +- archinstall/lib/disk/blockdevice.py | 3 +++ archinstall/lib/disk/filesystem.py | 6 +++++- archinstall/lib/disk/helpers.py | 2 ++ archinstall/lib/disk/partition.py | 4 ++++ archinstall/lib/disk/user_guides.py | 4 ++-- examples/guided.py | 6 +++--- 7 files changed, 20 insertions(+), 7 deletions(-) (limited to 'archinstall/lib/disk/helpers.py') diff --git a/archinstall/lib/disk/__init__.py b/archinstall/lib/disk/__init__.py index 8237f774..352d04b9 100644 --- a/archinstall/lib/disk/__init__.py +++ b/archinstall/lib/disk/__init__.py @@ -1,7 +1,7 @@ from .btrfs import * from .helpers import * from .blockdevice import BlockDevice -from .filesystem import Filesystem +from .filesystem import Filesystem, MBR, GPT from .partition import * from .user_guides import * from .validators import * \ No newline at end of file diff --git a/archinstall/lib/disk/blockdevice.py b/archinstall/lib/disk/blockdevice.py index daa65323..57cbcfa6 100644 --- a/archinstall/lib/disk/blockdevice.py +++ b/archinstall/lib/disk/blockdevice.py @@ -1,3 +1,4 @@ +import os import json import logging from ..output import log @@ -94,6 +95,7 @@ class BlockDevice: @property def partitions(self): + from .filesystem import Partition SysCommand(['partprobe', self.path]) result = SysCommand(['/usr/bin/lsblk', '-J', self.path]) @@ -123,6 +125,7 @@ class BlockDevice: @property def partition_table_type(self): + from .filesystem import GPT return GPT @property diff --git a/archinstall/lib/disk/filesystem.py b/archinstall/lib/disk/filesystem.py index b53d8451..28846764 100644 --- a/archinstall/lib/disk/filesystem.py +++ b/archinstall/lib/disk/filesystem.py @@ -1,7 +1,11 @@ import time +import logging +import json from .partition import Partition from .blockdevice import BlockDevice +from ..general import SysCommand from ..output import log +from ..storage import storage GPT = 0b00000001 MBR = 0b00000010 @@ -58,7 +62,7 @@ class Filesystem: return index def load_layout(self, layout :dict): - from .luks import luks2 + from ..luks import luks2 # If the layout tells us to wipe the drive, we do so if layout.get('wipe', False): diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 8b372f73..65abdea2 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -132,6 +132,8 @@ def get_mount_info(path) -> dict: def get_partitions_in_use(mountpoint) -> list: + from .partition import Partition + try: output = SysCommand(f"/usr/bin/findmnt --json -R {mountpoint}").decode('UTF-8') except SysCallError: diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index 30151583..3bb2982b 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -2,9 +2,13 @@ import glob import pathlib import time import logging +import json +import os from typing import Optional from .blockdevice import BlockDevice +from .helpers import get_mount_info, get_filesystem_type from ..output import log +from ..general import SysCommand class Partition: def __init__(self, path: str, block_device: BlockDevice, part_id=None, size=-1, filesystem=None, mountpoint=None, encrypted=False, autodetect_filesystem=True): diff --git a/archinstall/lib/disk/user_guides.py b/archinstall/lib/disk/user_guides.py index 0a975149..79b9d48f 100644 --- a/archinstall/lib/disk/user_guides.py +++ b/archinstall/lib/disk/user_guides.py @@ -3,7 +3,7 @@ from ..output import log def suggest_single_disk_layout(block_device, default_filesystem=None): if not default_filesystem: - from .user_interaction import ask_for_main_filesystem_format + from ..user_interaction import ask_for_main_filesystem_format default_filesystem = ask_for_main_filesystem_format() MIN_SIZE_TO_ALLOW_HOME_PART = 40 # Gb @@ -76,7 +76,7 @@ def suggest_single_disk_layout(block_device, default_filesystem=None): def suggest_multi_disk_layout(block_devices, default_filesystem=None): if not default_filesystem: - from .user_interaction import ask_for_main_filesystem_format + from ..user_interaction import ask_for_main_filesystem_format default_filesystem = ask_for_main_filesystem_format() # Not really a rock solid foundation of information to stand on, but it's a start: diff --git a/examples/guided.py b/examples/guided.py index b7c75b30..2efb4972 100644 --- a/examples/guided.py +++ b/examples/guided.py @@ -140,7 +140,7 @@ def ask_user_questions(): # Ask for a root password (optional, but triggers requirement for super-user if skipped) if not archinstall.arguments.get('!root-password', None): - archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (Recommendation: leave blank to leave root disabled): ') + archinstall.arguments['!root-password'] = archinstall.get_password(prompt='Enter root password (leave blank to disable disabled & create superuser): ') # Ask for additional users (super-user if root pw was not set) @@ -245,9 +245,9 @@ def perform_filesystem_operations(): Setup the blockdevice, filesystem (and optionally encryption). Once that's done, we'll hand over to perform_installation() """ - mode = archinstall.GPT + mode = archinstall.disk.GPT if has_uefi() is False: - mode = archinstall.MBR + mode = archinstall.disk.MBR for drive in archinstall.arguments['harddrives']: with archinstall.Filesystem(drive, mode) as fs: -- cgit v1.2.3-70-g09d2 From 68b891837c6174d1f75babf42ee6657d4726576b Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 27 Oct 2021 13:13:10 +0000 Subject: Finalized the create_subvolume and mount_subvolume functions. Remaining is to call these functions during the disk setup process to create the subvolumes and mount them in place, rather than doing the normal steps. --- archinstall/lib/disk/btrfs.py | 36 ++++++++++++++++++++++++++++++++++-- archinstall/lib/disk/helpers.py | 4 +++- 2 files changed, 37 insertions(+), 3 deletions(-) (limited to 'archinstall/lib/disk/helpers.py') diff --git a/archinstall/lib/disk/btrfs.py b/archinstall/lib/disk/btrfs.py index d6758b3f..558a249e 100644 --- a/archinstall/lib/disk/btrfs.py +++ b/archinstall/lib/disk/btrfs.py @@ -1,4 +1,36 @@ +import pathlib, glob +from typing import Union +from .helpers import get_mount_info +from ..exceptions import DiskError from ..general import SysCommand -def create_subvolume(installation): - SysCommand(f"btrfs subvolume create {installation.target}/@") \ No newline at end of file +def mount_subvolume(installation, location :Union[pathlib.Path, str], force=False) -> bool: + """ + This function uses mount to mount a subvolume on a given device, at a given location with a given subvolume name. + + @installation: archinstall.Installer instance + @location: a localized string or path inside the installation / or /boot for instance without specifying /mnt/boot + @force: overrides the check for weither or not the subvolume mountpoint is empty or not + """ + # Set up the required physical structure + if type(location) == str: + location = pathlib.Path(location) + + if not location.exists(): + location.mkdir(parents=True) + + if glob.glob(str(installation.target/location/'*')) and force is False: + raise DiskError(f"Cannot mount subvolume to {installation.target/location} because it contains data (non-empty folder target)") + + # Mount the logical volume to the physical structure + return SysCommand(f"mount {get_mount_info(installation.target/location)['source']} {installation.target}/{str(location)} -o subvol=@/{str(location)}").exit_code == 0 + +def create_subvolume(installation, location :Union[pathlib.Path, str]) -> bool: + """ + This function uses btrfs to create a subvolume. + + @installation: archinstall.Installer instance + @location: a localized string or path inside the installation / or /boot for instance without specifying /mnt/boot + """ + + SysCommand(f"btrfs subvolume create {installation.target}/{str(location)}") \ No newline at end of file diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 65abdea2..2e84a657 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -1,5 +1,7 @@ import re import json +import pathlib +from typing import Union from .blockdevice import BlockDevice from ..exceptions import SysCallError from ..general import SysCommand @@ -114,7 +116,7 @@ def harddrive(size=None, model=None, fuzzy=False): return collection[drive] -def get_mount_info(path) -> dict: +def get_mount_info(path :Union[pathlib.Path, str]) -> dict: try: output = SysCommand(f'/usr/bin/findmnt --json {path}').decode('UTF-8') except SysCallError: -- cgit v1.2.3-70-g09d2 From 6be071b6e0d43a2fded8a3fd773525fb1216aa91 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 30 Oct 2021 16:16:43 +0200 Subject: Enhanced get_mount_info() to recursively get the information --- archinstall/lib/disk/helpers.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'archinstall/lib/disk/helpers.py') diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 2e84a657..0111d327 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -117,10 +117,13 @@ def harddrive(size=None, model=None, fuzzy=False): def get_mount_info(path :Union[pathlib.Path, str]) -> dict: - try: - output = SysCommand(f'/usr/bin/findmnt --json {path}').decode('UTF-8') - except SysCallError: - return {} + for traversal in list(map(str, [str(path)] + list(pathlib.Path(str(path)).parents))): + try: + output = SysCommand(f'/usr/bin/findmnt --json {traversal}').decode('UTF-8') + if output: + break + except SysCallError: + pass if not output: return {} @@ -132,6 +135,8 @@ def get_mount_info(path :Union[pathlib.Path, str]) -> dict: return output['filesystems'][0] + return {} + def get_partitions_in_use(mountpoint) -> list: from .partition import Partition -- cgit v1.2.3-70-g09d2 From c4f688ce4c9ce89748e11bd0565c9b3720e66b74 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 30 Oct 2021 16:28:55 +0200 Subject: Added some error handling to mount points not getting mounted properly. --- archinstall/lib/disk/btrfs.py | 2 +- archinstall/lib/disk/helpers.py | 5 ++++- archinstall/lib/installer.py | 7 +++++++ 3 files changed, 12 insertions(+), 2 deletions(-) (limited to 'archinstall/lib/disk/helpers.py') diff --git a/archinstall/lib/disk/btrfs.py b/archinstall/lib/disk/btrfs.py index b56430b8..cf60095f 100644 --- a/archinstall/lib/disk/btrfs.py +++ b/archinstall/lib/disk/btrfs.py @@ -25,7 +25,7 @@ def mount_subvolume(installation, location :Union[pathlib.Path, str], force=Fals raise DiskError(f"Cannot mount subvolume to {installation.target/location} because it contains data (non-empty folder target)") log(f"Mounting {location} as a subvolume", level=logging.INFO) - print(get_mount_info(installation.target/location)) + print(get_mount_info(installation.target/location, traverse=True)) # Mount the logical volume to the physical structure mount_location = get_mount_info(installation.target/location)['source'] SysCommand(f"umount {mount_location}") diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 0111d327..8a6d5a48 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -116,7 +116,7 @@ def harddrive(size=None, model=None, fuzzy=False): return collection[drive] -def get_mount_info(path :Union[pathlib.Path, str]) -> dict: +def get_mount_info(path :Union[pathlib.Path, str], traverse=False) -> dict: for traversal in list(map(str, [str(path)] + list(pathlib.Path(str(path)).parents))): try: output = SysCommand(f'/usr/bin/findmnt --json {traversal}').decode('UTF-8') @@ -125,6 +125,9 @@ def get_mount_info(path :Union[pathlib.Path, str]) -> dict: except SysCallError: pass + if not traverse: + break + if not output: return {} diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index 41d918a1..b0a7c5f1 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -1,11 +1,14 @@ +import time from .disk import * from .hardware import * from .locale_helpers import verify_keyboard_layout, verify_x11_keyboard_layout +from .helpers import get_mount_info from .mirrors import * from .plugins import plugins from .storage import storage from .user_interaction import * from .disk.btrfs import create_subvolume, mount_subvolume +from .exceptions import DiskError, ServiceException # Any package that the Installer() is responsible for (optional and the default ones) __packages__ = ["base", "base-devel", "linux-firmware", "linux", "linux-lts", "linux-zen", "linux-hardened"] @@ -144,6 +147,10 @@ class Installer: else: mountpoints[mountpoint]['device_instance'].mount(f"{self.target}{mountpoint}") + time.sleep(1) + if not get_mount_info(f"{self.target}{mountpoint}", traverse=False): + raise DiskError(f"Target {self.target}{mountpoint} never got mounted properly.") + if (subvolumes := mountpoints[mountpoint].get('btrfs', {}).get('subvolumes', {})): for name, location in subvolumes.items(): create_subvolume(self, location) -- cgit v1.2.3-70-g09d2 From c2596be1f10cb766b9bd06a0ab637468b90b07e4 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 30 Oct 2021 16:32:55 +0200 Subject: Added more logging (will change INFO to DEBUG later) --- archinstall/lib/disk/btrfs.py | 3 +-- archinstall/lib/disk/helpers.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'archinstall/lib/disk/helpers.py') diff --git a/archinstall/lib/disk/btrfs.py b/archinstall/lib/disk/btrfs.py index cf60095f..bf24f88b 100644 --- a/archinstall/lib/disk/btrfs.py +++ b/archinstall/lib/disk/btrfs.py @@ -25,9 +25,8 @@ def mount_subvolume(installation, location :Union[pathlib.Path, str], force=Fals raise DiskError(f"Cannot mount subvolume to {installation.target/location} because it contains data (non-empty folder target)") log(f"Mounting {location} as a subvolume", level=logging.INFO) - print(get_mount_info(installation.target/location, traverse=True)) # Mount the logical volume to the physical structure - mount_location = get_mount_info(installation.target/location)['source'] + mount_location = get_mount_info(installation.target/location, traverse=True)['source'] SysCommand(f"umount {mount_location}") return SysCommand(f"mount {mount_location} {installation.target}/{str(location)} -o subvol=@/{str(location)}").exit_code == 0 diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 8a6d5a48..78bf08ed 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -1,5 +1,6 @@ import re import json +import logging import pathlib from typing import Union from .blockdevice import BlockDevice @@ -119,6 +120,7 @@ def harddrive(size=None, model=None, fuzzy=False): def get_mount_info(path :Union[pathlib.Path, str], traverse=False) -> dict: for traversal in list(map(str, [str(path)] + list(pathlib.Path(str(path)).parents))): try: + log(f"Getting mount information at location {traversal}", level=logging.INFO) output = SysCommand(f'/usr/bin/findmnt --json {traversal}').decode('UTF-8') if output: break -- cgit v1.2.3-70-g09d2 From 3ee1a5c18e4bf248b54e8da9be6a88cfe042a2b9 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Sat, 30 Oct 2021 17:15:33 +0200 Subject: Tweaked get_mount_info() and mount_subvolume(). mount info now returns the path it found after traversal. mount_subvolume will no longer assume installation.target is of pathlib.Path and converts it if it isn't. --- archinstall/lib/disk/btrfs.py | 33 +++++++++++++++++++++------------ archinstall/lib/disk/helpers.py | 17 +++++++++++++---- 2 files changed, 34 insertions(+), 16 deletions(-) (limited to 'archinstall/lib/disk/helpers.py') diff --git a/archinstall/lib/disk/btrfs.py b/archinstall/lib/disk/btrfs.py index f682cd7c..a71fb1f4 100644 --- a/archinstall/lib/disk/btrfs.py +++ b/archinstall/lib/disk/btrfs.py @@ -6,29 +6,38 @@ from ..exceptions import DiskError from ..general import SysCommand from ..output import log -def mount_subvolume(installation, location :Union[pathlib.Path, str], force=False) -> bool: +def mount_subvolume(installation, subvolume_location :Union[pathlib.Path, str], force=False) -> bool: """ This function uses mount to mount a subvolume on a given device, at a given location with a given subvolume name. @installation: archinstall.Installer instance - @location: a localized string or path inside the installation / or /boot for instance without specifying /mnt/boot + @subvolume_location: a localized string or path inside the installation / or /boot for instance without specifying /mnt/boot @force: overrides the check for weither or not the subvolume mountpoint is empty or not """ + + installation_mountpoint = installation.target + if type(installation_mountpoint) == str: + installation_mountpoint = pathlib.Path(installation_mountpoint) # Set up the required physical structure - if type(location) == str: - location = pathlib.Path(location) + if type(subvolume_location) == str: + subvolume_location = pathlib.Path(subvolume_location) - if not (installation.target/location).exists(): - (installation.target/location).mkdir(parents=True) + target = installation_mountpoint / subvolume_location.relative_to(subvolume_location.anchor) - if glob.glob(str(installation.target/location/'*')) and force is False: - raise DiskError(f"Cannot mount subvolume to {installation.target/location} because it contains data (non-empty folder target)") + if not (target).exists(): + (target).mkdir(parents=True) + + if glob.glob(str(target/'*')) and force is False: + raise DiskError(f"Cannot mount subvolume to {target} because it contains data (non-empty folder target)") - log(f"Mounting {installation.target/location} as a subvolume", level=logging.INFO) + log(f"Mounting {target} as a subvolume", level=logging.INFO) # Mount the logical volume to the physical structure - mount_location = get_mount_info(installation.target/location, traverse=True)['source'] - SysCommand(f"umount {mount_location}") - return SysCommand(f"mount {mount_location} {installation.target}/{str(location)} -o subvol=@/{str(location)}").exit_code == 0 + mountpoint_device, mountpoint_device_real_path = get_mount_info(target, traverse=True, return_real_path=True)['source'] + if mountpoint_device_real_path == str(target): + log(f"Unmounting non-subvolume {mountpoint_device} previously mounted at {target}") + SysCommand(f"umount {mountpoint_device}") + + return SysCommand(f"mount {mountpoint_device} {target} -o subvol=@{subvolume_location}").exit_code == 0 def create_subvolume(installation, location :Union[pathlib.Path, str]) -> bool: """ diff --git a/archinstall/lib/disk/helpers.py b/archinstall/lib/disk/helpers.py index 78bf08ed..341b732f 100644 --- a/archinstall/lib/disk/helpers.py +++ b/archinstall/lib/disk/helpers.py @@ -117,7 +117,7 @@ def harddrive(size=None, model=None, fuzzy=False): return collection[drive] -def get_mount_info(path :Union[pathlib.Path, str], traverse=False) -> dict: +def get_mount_info(path :Union[pathlib.Path, str], traverse=False, return_real_path=False) -> dict: for traversal in list(map(str, [str(path)] + list(pathlib.Path(str(path)).parents))): try: log(f"Getting mount information at location {traversal}", level=logging.INFO) @@ -131,16 +131,25 @@ def get_mount_info(path :Union[pathlib.Path, str], traverse=False) -> dict: break if not output: - return {} + if return_real_path: + return {}, None + else: + return {} output = json.loads(output) if 'filesystems' in output: if len(output['filesystems']) > 1: raise DiskError(f"Path '{path}' contains multiple mountpoints: {output['filesystems']}") - return output['filesystems'][0] + if return_real_path: + return output['filesystems'][0], traversal + else: + return output['filesystems'][0] - return {} + if return_real_path: + return {}, traversal + else: + return {} def get_partitions_in_use(mountpoint) -> list: -- cgit v1.2.3-70-g09d2