From 0c96ae049dd188c0df247c29ea36715e92428d3a Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Mon, 22 Nov 2021 11:22:51 -0500 Subject: NTFS Root Filesystem Support (#748) * For fun, allow NTFS as a root filesystem type Add ability to format a filesystem as NTFS Try to force filesystem type Fix FAT mounting * Split out mount fs type method * Handle rootfstype on non-GRUB bootloaders * Add -Q to mkfs.ntfs command line for quick formatting * I believe this will fix GRUB with NTFS root * Remove the fsck hook if NTFS is used as the root partition * Looks like the string is ntfs3 not ntfs so this logic wasn't running --- archinstall/lib/disk/partition.py | 48 +++++++++++++++++++++++++------------ archinstall/lib/installer.py | 38 +++++++++++++++++++---------- archinstall/lib/user_interaction.py | 3 ++- 3 files changed, 61 insertions(+), 28 deletions(-) (limited to 'archinstall') diff --git a/archinstall/lib/disk/partition.py b/archinstall/lib/disk/partition.py index e2083649..4aea3832 100644 --- a/archinstall/lib/disk/partition.py +++ b/archinstall/lib/disk/partition.py @@ -13,6 +13,7 @@ from ..exceptions import DiskError, SysCallError, UnknownFilesystemFormat 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): if not part_id: @@ -71,17 +72,17 @@ class Partition: def __dump__(self): return { - 'type' : 'primary', - 'PARTUUID' : self._safe_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) + 'type': 'primary', + 'PARTUUID': self._safe_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) } } @@ -98,7 +99,7 @@ class Partition: for partition in output.get('partitiontable', {}).get('partitions', []): if partition['node'] == self.path: - return partition['start']# * self.sector_size + return partition['start'] # * self.sector_size @property def end(self): @@ -107,7 +108,7 @@ class Partition: for partition in output.get('partitiontable', {}).get('partitions', []): if partition['node'] == self.path: - return partition['size']# * self.sector_size + return partition['size'] # * self.sector_size @property def boot(self): @@ -301,6 +302,13 @@ class Partition: raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") self.filesystem = filesystem + elif filesystem == 'ntfs': + options = ['-f'] + options + + if (handle := SysCommand(f"/usr/bin/mkfs.ntfs -Q {' '.join(options)} {path}")).exit_code != 0: + raise DiskError(f"Could not format {path} with {filesystem} because: {handle.decode('UTF-8')}") + self.filesystem = filesystem + elif filesystem == 'crypto_LUKS': # from ..luks import luks2 # encrypted_partition = luks2(self, None, None) @@ -333,13 +341,15 @@ class Partition: raise DiskError(f'Need to format (or define) the filesystem on {self} before mounting.') fs = self.filesystem + fs_type = get_mount_fs_type(fs) + pathlib.Path(target).mkdir(parents=True, exist_ok=True) try: if options: - mnt_handle = SysCommand(f"/usr/bin/mount -o {options} {self.path} {target}") + mnt_handle = SysCommand(f"/usr/bin/mount -t {fs_type} -o {options} {self.path} {target}") else: - mnt_handle = SysCommand(f"/usr/bin/mount {self.path} {target}") + mnt_handle = SysCommand(f"/usr/bin/mount -t {fs_type} {self.path} {target}") # TODO: Should be redundant to check for exit_code if mnt_handle.exit_code != 0: @@ -382,3 +392,11 @@ class Partition: except UnknownFilesystemFormat as err: raise err return True + + +def get_mount_fs_type(fs): + if fs == 'ntfs': + return 'ntfs3' # Needed to use the Paragon R/W NTFS driver + elif fs == 'fat32': + return 'vfat' # This is the actual type used for fat32 mounting. + return fs diff --git a/archinstall/lib/installer.py b/archinstall/lib/installer.py index fff62201..676070f6 100644 --- a/archinstall/lib/installer.py +++ b/archinstall/lib/installer.py @@ -19,6 +19,7 @@ from .storage import storage from .output import log from .profiles import Profile from .disk.btrfs import create_subvolume, mount_subvolume +from .disk.partition import get_mount_fs_type from .exceptions import DiskError, ServiceException, RequirementError, HardwareIncompatibilityError # Any package that the Installer() is responsible for (optional and the default ones) @@ -40,16 +41,17 @@ class InstallationFile: def __exit__(self, *args): self.fh.close() self.installation.chown(self.owner, self.filename) - - def write(self, data :Union[str, bytes]): + + def write(self, data: Union[str, bytes]): return self.fh.write(data) - + def read(self, *args): return self.fh.read(*args) - + def poll(self, *args): return self.fh.poll(*args) + class Installer: """ `Installer()` is the wrapper for most basic installation steps. @@ -165,7 +167,7 @@ class Installer: return True - def mount_ordered_layout(self, layouts :dict): + def mount_ordered_layout(self, layouts: dict): from .luks import luks2 mountpoints = {} @@ -295,7 +297,7 @@ class Installer: def activate_time_syncronization(self): self.log('Activating systemd-timesyncd for time synchronization using Arch Linux and ntp.org NTP servers.', level=logging.INFO) self.enable_service('systemd-timesyncd') - + with open(f"{self.target}/etc/systemd/timesyncd.conf", "w") as fh: fh.write("[Time]\n") fh.write("NTP=0.arch.pool.ntp.org 1.arch.pool.ntp.org 2.arch.pool.ntp.org 3.arch.pool.ntp.org\n") @@ -446,6 +448,11 @@ class Installer: self.MODULES.append('btrfs') if '/usr/bin/btrfs-progs' not in self.BINARIES: self.BINARIES.append('/usr/bin/btrfs') + + # There is not yet an fsck tool for NTFS. If it's being used for the root filesystem, the hook should be removed. + if partition.filesystem == 'ntfs3' and partition.mountpoint == self.target: + if 'fsck' in self.HOOKS: + self.HOOKS.remove('fsck') if self.detect_encryption(partition): if 'encrypt' not in self.HOOKS: @@ -499,7 +506,7 @@ class Installer: if kind == 'zram': self.log(f"Setting up swap on zram") self.pacstrap('zram-generator') - + # We could use the default example below, but maybe not the best idea: https://github.com/archlinux/archinstall/pull/678#issuecomment-962124813 # zram_example_location = '/usr/share/doc/zram-generator/zram-generator.conf.example' # shutil.copy2(f"{self.target}{zram_example_location}", f"{self.target}/usr/lib/systemd/zram-generator.conf") @@ -521,11 +528,14 @@ class Installer: boot_partition = None root_partition = None + root_partition_fs = None for partition in self.partitions: if partition.mountpoint == self.target + '/boot': boot_partition = partition elif partition.mountpoint == self.target: root_partition = partition + root_partition_fs = partition.filesystem + root_fs_type = get_mount_fs_type(root_partition_fs) if boot_partition is None and root_partition is None: raise ValueError(f"Could not detect root (/) or boot (/boot) in {self.target} based on: {self.partitions}") @@ -597,10 +607,10 @@ class Installer: # TODO: We need to detect if the encrypted device is a whole disk encryption, # or simply a partition encryption. Right now we assume it's a partition (and we always have) log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG) - entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}\n') + entry.write(f'options cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}\n') else: log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG) - entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}\n') + entry.write(f'options root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}\n') self.helper_flags['bootloader'] = bootloader @@ -610,12 +620,16 @@ class Installer: if real_device := self.detect_encryption(root_partition): root_uuid = SysCommand(f"blkid -s UUID -o value {real_device.path}").decode().rstrip() _file = "/etc/default/grub" - add_to_CMDLINE_LINUX = f"sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"cryptdevice=UUID={root_uuid}:cryptlvm\"/'" + add_to_CMDLINE_LINUX = f"sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"cryptdevice=UUID={root_uuid}:cryptlvm rootfstype={root_fs_type}\"/'" enable_CRYPTODISK = "sed -i 's/#GRUB_ENABLE_CRYPTODISK=y/GRUB_ENABLE_CRYPTODISK=y/'" log(f"Using UUID {root_uuid} of {real_device} as encrypted root identifier.", level=logging.INFO) SysCommand(f"/usr/bin/arch-chroot {self.target} {add_to_CMDLINE_LINUX} {_file}") SysCommand(f"/usr/bin/arch-chroot {self.target} {enable_CRYPTODISK} {_file}") + else: + _file = "/etc/default/grub" + add_to_CMDLINE_LINUX = f"sed -i 's/GRUB_CMDLINE_LINUX=\"\"/GRUB_CMDLINE_LINUX=\"rootfstype={root_fs_type}\"/'" + SysCommand(f"/usr/bin/arch-chroot {self.target} {add_to_CMDLINE_LINUX} {_file}") log(f"GRUB uses {boot_partition.path} as the boot partition.", level=logging.INFO) if has_uefi(): @@ -664,10 +678,10 @@ class Installer: # TODO: We need to detect if the encrypted device is a whole disk encryption, # or simply a partition encryption. Right now we assume it's a partition (and we always have) log(f"Identifying root partition by PART-UUID on {real_device}: '{real_device.uuid}'.", level=logging.DEBUG) - kernel_parameters.append(f'cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}') + kernel_parameters.append(f'cryptdevice=PARTUUID={real_device.uuid}:luksdev root=/dev/mapper/luksdev rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}') else: log(f"Identifying root partition by PART-UUID on {root_partition}, looking for '{root_partition.uuid}'.", level=logging.DEBUG) - kernel_parameters.append(f'root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp {" ".join(self.KERNEL_PARAMS)}') + kernel_parameters.append(f'root=PARTUUID={root_partition.uuid} rw intel_pstate=no_hwp rootfstype={root_fs_type} {" ".join(self.KERNEL_PARAMS)}') SysCommand(f'efibootmgr --disk {boot_partition.path[:-1]} --part {boot_partition.path[-1]} --create --label "{label}" --loader {loader} --unicode \'{" ".join(kernel_parameters)}\' --verbose') diff --git a/archinstall/lib/user_interaction.py b/archinstall/lib/user_interaction.py index 05cba3ca..82113ba0 100644 --- a/archinstall/lib/user_interaction.py +++ b/archinstall/lib/user_interaction.py @@ -486,7 +486,8 @@ def ask_for_main_filesystem_format(): 'btrfs': 'btrfs', 'ext4': 'ext4', 'xfs': 'xfs', - 'f2fs': 'f2fs' + 'f2fs': 'f2fs', + 'ntfs': 'ntfs' } value = generic_select(options, "Select which filesystem your main partition should use (by number or name): ", allow_empty_input=False) -- cgit v1.2.3-54-g00ecf