From 6565c64006e7f93cb92e1225aa7d7e0817c1d077 Mon Sep 17 00:00:00 2001 From: Dan Nicholson Date: Tue, 7 Mar 2023 14:22:30 -0700 Subject: [PATCH] Add eos-esp-generator script for mounting ESP This is intended to replace systemd-gpt-auto-generator for mounting the ESP. The various policies we need are difficult to capture there and would be hard to upstream. Instead, this program can capture all the products we support to ensure the ESP is mounted where and how we need it. It creates units in the normal generator directory, which mean they take precedence over the gpt-auto-generator units in the late generator directory. I've tested this on EOS with GPT, EOS with MBR, and PAYG. What's not currently supported is the Windows dual boot configuration where the root disk isn't really the root disk. https://phabricator.endlessm.com/T29930 --- .flake8 | 1 + Makefile.am | 1 + eos-esp-generator | 968 ++++++++++++++++++ tests/Makefile.am | 3 + tests/espgen/README.md | 16 + tests/espgen/grub-gpt-fstab-efi-fstab.json | 18 + tests/espgen/grub-gpt-fstab-efi-kcmdline.json | 10 + .../grub-gpt-fstab-efi-mounts-init.json | 34 + .../grub-gpt-fstab-efi-mounts-reload.json | 50 + .../grub-gpt-fstab-efi-partitions-init.json | 44 + .../grub-gpt-fstab-efi-partitions-reload.json | 51 + tests/espgen/grub-gpt-fstab.json | 10 + tests/espgen/grub-gpt-kcmdline.json | 10 + tests/espgen/grub-gpt-mounts-init.json | 34 + tests/espgen/grub-gpt-mounts-reload.json | 50 + tests/espgen/grub-gpt-partitions-init.json | 44 + tests/espgen/grub-gpt-partitions-reload.json | 51 + tests/espgen/grub-mbr-fstab.json | 10 + tests/espgen/grub-mbr-kcmdline.json | 10 + tests/espgen/grub-mbr-mounts-init.json | 34 + tests/espgen/grub-mbr-mounts-reload.json | 50 + tests/espgen/grub-mbr-partitions-init.json | 35 + tests/espgen/grub-mbr-partitions-reload.json | 42 + tests/espgen/sdboot-fstab.json | 10 + tests/espgen/sdboot-kcmdline.json | 11 + tests/espgen/sdboot-mounts-init.json | 26 + tests/espgen/sdboot-mounts-reload.json | 42 + tests/espgen/sdboot-partitions-init.json | 44 + tests/espgen/sdboot-partitions-reload.json | 51 + tests/espgen/sdboot-xbootldr-fstab.json | 10 + tests/espgen/sdboot-xbootldr-kcmdline.json | 11 + tests/espgen/sdboot-xbootldr-mounts-init.json | 26 + .../espgen/sdboot-xbootldr-mounts-reload.json | 50 + .../sdboot-xbootldr-partitions-init.json | 60 ++ .../sdboot-xbootldr-partitions-reload.json | 67 ++ tests/espgen/windows-fstab.json | 10 + tests/espgen/windows-kcmdline.json | 12 + tests/espgen/windows-mounts-init.json | 34 + tests/espgen/windows-mounts-reload.json | 50 + tests/espgen/windows-partitions-init.json | 103 ++ tests/espgen/windows-partitions-reload.json | 110 ++ tests/test_esp_generator.py | 414 ++++++++ 42 files changed, 2717 insertions(+) create mode 100755 eos-esp-generator create mode 100644 tests/espgen/README.md create mode 100644 tests/espgen/grub-gpt-fstab-efi-fstab.json create mode 100644 tests/espgen/grub-gpt-fstab-efi-kcmdline.json create mode 100644 tests/espgen/grub-gpt-fstab-efi-mounts-init.json create mode 100644 tests/espgen/grub-gpt-fstab-efi-mounts-reload.json create mode 100644 tests/espgen/grub-gpt-fstab-efi-partitions-init.json create mode 100644 tests/espgen/grub-gpt-fstab-efi-partitions-reload.json create mode 100644 tests/espgen/grub-gpt-fstab.json create mode 100644 tests/espgen/grub-gpt-kcmdline.json create mode 100644 tests/espgen/grub-gpt-mounts-init.json create mode 100644 tests/espgen/grub-gpt-mounts-reload.json create mode 100644 tests/espgen/grub-gpt-partitions-init.json create mode 100644 tests/espgen/grub-gpt-partitions-reload.json create mode 100644 tests/espgen/grub-mbr-fstab.json create mode 100644 tests/espgen/grub-mbr-kcmdline.json create mode 100644 tests/espgen/grub-mbr-mounts-init.json create mode 100644 tests/espgen/grub-mbr-mounts-reload.json create mode 100644 tests/espgen/grub-mbr-partitions-init.json create mode 100644 tests/espgen/grub-mbr-partitions-reload.json create mode 100644 tests/espgen/sdboot-fstab.json create mode 100644 tests/espgen/sdboot-kcmdline.json create mode 100644 tests/espgen/sdboot-mounts-init.json create mode 100644 tests/espgen/sdboot-mounts-reload.json create mode 100644 tests/espgen/sdboot-partitions-init.json create mode 100644 tests/espgen/sdboot-partitions-reload.json create mode 100644 tests/espgen/sdboot-xbootldr-fstab.json create mode 100644 tests/espgen/sdboot-xbootldr-kcmdline.json create mode 100644 tests/espgen/sdboot-xbootldr-mounts-init.json create mode 100644 tests/espgen/sdboot-xbootldr-mounts-reload.json create mode 100644 tests/espgen/sdboot-xbootldr-partitions-init.json create mode 100644 tests/espgen/sdboot-xbootldr-partitions-reload.json create mode 100644 tests/espgen/windows-fstab.json create mode 100644 tests/espgen/windows-kcmdline.json create mode 100644 tests/espgen/windows-mounts-init.json create mode 100644 tests/espgen/windows-mounts-reload.json create mode 100644 tests/espgen/windows-partitions-init.json create mode 100644 tests/espgen/windows-partitions-reload.json create mode 100644 tests/test_esp_generator.py diff --git a/.flake8 b/.flake8 index 5cd19e5..daa88e3 100644 --- a/.flake8 +++ b/.flake8 @@ -8,6 +8,7 @@ max-line-length = 88 # Include scripts to check in addition to the default *.py. filename = *.py, + ./eos-esp-generator, ./eos-migrate-chromium-profile, ./eos-migrate-firefox-profile, ./eos-update-flatpak-repos, diff --git a/Makefile.am b/Makefile.am index 186db4e..ab4307e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,6 +38,7 @@ dist_systemduserunit_DATA = \ $(NULL) dist_systemdgenerator_SCRIPTS = \ + eos-esp-generator \ eos-live-boot-generator \ eos-vm-generator \ $(NULL) diff --git a/eos-esp-generator b/eos-esp-generator new file mode 100755 index 0000000..88c7211 --- /dev/null +++ b/eos-esp-generator @@ -0,0 +1,968 @@ +#!/usr/bin/env python3 + +# Copyright © 2023 Endless OS Foundation, LLC +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +"""\ +Endless OS EFI System Partition (ESP) mount generator + +This program is responsible for mounting the EFI System Partition (ESP) on EOS +systems. It's heavily inspired by systemd-gpt-auto-generator with a few policy +changes that are not easily expressed there: + +* Both GPT and MBR partition tables are supported. + +* The EFI LoaderDevicePartUUID variable is preferred but not required. This + allows for usage with bootloaders such as GRUB that do not implement the boot + loader interface. + +* Mounting at /boot is allowed even when the /efi directory exists. This allows + use of a generic OS commit that can be used on systems where /boot data + exists in the ESP or not. + +* When the ESP is mounted at /boot, it is made world readable to allow + unprivileged ostree admin operations to succeed. + +* When the endless.image.device kernel command line argument is set, the + ESP from that disk is mounted. + +The units created with this generator take precedence over +systemd-gpt-auto-generator. Output from the generator can be read with +"journalctl -t eos-esp-generator". When invoked as eos-esp-generator-gather, +the generator will only gather data and store it in a tarball in /run. +""" + +from argparse import ArgumentParser, RawDescriptionHelpFormatter +from dataclasses import dataclass +import json +import logging +from logging.handlers import SysLogHandler +import os +from pathlib import Path +import shlex +import subprocess +import sys +import tarfile +import time +from tempfile import TemporaryDirectory +from textwrap import dedent + +progname = os.path.basename(__file__) +logger = logging.getLogger(progname) + +ESP_GPT_PARTTYPE = 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b' +ESP_MBR_PARTTYPE = '0xef' +XBOOTLDR_GPT_PARTTYPE = 'bc13c2ff-59e6-4262-a352-b275fd6f7172' +LOADER_EFI_VENDOR = '4a67b082-0a4c-41cf-b6c7-440b29bb8c4f' +LOADER_DEVICE_PART_UUID_NAME = 'LoaderDevicePartUUID' +LOADER_DEVICE_PART_UUID_EFIVAR = f'{LOADER_DEVICE_PART_UUID_NAME}-{LOADER_EFI_VENDOR}' + +# Program name used to automatically enable gather mode. +GATHER_MODE_PROGNAME = 'eos-esp-generator-gather' + + +class EspError(Exception): + """ESP generator errors""" + pass + + +class KmsgHandler(logging.StreamHandler): + """Logging Handler using /dev/kmsg""" + PRIORITY_MAP = { + logging.DEBUG: SysLogHandler.LOG_DEBUG, + logging.INFO: SysLogHandler.LOG_INFO, + logging.WARNING: SysLogHandler.LOG_WARNING, + logging.ERROR: SysLogHandler.LOG_ERR, + logging.CRITICAL: SysLogHandler.LOG_CRIT, + } + + def __init__(self): + self.kmsg = open('/dev/kmsg', 'wb', buffering=0) + self.pid = os.getpid() + super().__init__(stream=self.kmsg) + + def close(self): + self.acquire() + try: + self.kmsg.close() + finally: + self.release() + + def emit(self, record): + try: + message = self.format(record) + + # kmsg recognizes a syslog style identifier[pid]: prefix. + priority = self.PRIORITY_MAP.get(record.levelno, SysLogHandler.LOG_INFO) + prefix = f'<{priority:d}>{progname}[{self.pid:d}]: ' + + # kmsg allows a maximum of 1024 bytes per message, so split the + # message up if needed. + size = 1024 - len(prefix) + for pos in range(0, len(message), size): + buf = f'{prefix}{message[pos:pos + size]}'.encode('utf-8') + self.kmsg.write(buf) + except: # noqa: E722 + self.handleError(record) + + +def in_generator_env(): + """Whether the process is executing in a systemd generator environment""" + return 'SYSTEMD_SCOPE' in os.environ + + +def run(cmd, *args, **kwargs): + """Run a command with logging + + By default, subprocess.run is called with check and text set to True and + encoding set to utf-8. In a systemd generator environment, stdout is set to + subprocess.DEVNULL and stderr is set to subprocess.PIPE so that all output + is captured by default. + """ + kwargs.setdefault('check', True) + kwargs.setdefault('text', True) + kwargs.setdefault('encoding', 'utf-8') + if in_generator_env(): + kwargs.setdefault('stdout', subprocess.DEVNULL) + kwargs.setdefault('stderr', subprocess.PIPE) + + logger.debug(f'> {shlex.join(cmd)}') + return subprocess.run(cmd, *args, **kwargs) + + +def _get_root_path(): + """Get the path to the root directory + + For testing, the ESPGEN_ROOT_PATH variable is read. If not set or empty, / + is returned. + """ + return Path(os.getenv('ESPGEN_ROOT_PATH') or '/') + + +def get_mount_data(): + """Gather mounted filesystems + + Returns a list of mounted filesystem dicts. + """ + cmd = ( + 'findmnt', + '--real', + '--json', + '--list', + '--canonicalize', + '--evaluate', + '--nofsroot', + '--output', + 'TARGET,SOURCE,MAJ:MIN,FSTYPE,FSROOT,OPTIONS', + ) + + proc = run(cmd, stdout=subprocess.PIPE) + data = json.loads(proc.stdout).get('filesystems', {}) + logger.debug('mounts:') + for entry in data: + logger.debug(f' {entry}') + return data + + +def get_fstab_data(): + """Gather filesystem mount configuration + + Returns a list of fstab entry dicts. + """ + cmd = ( + 'findmnt', + '--fstab', + '--json', + '--list', + '--canonicalize', + '--evaluate', + '--nofsroot', + '--output', + 'TARGET,SOURCE,MAJ:MIN,FSTYPE,FSROOT,OPTIONS', + ) + + proc = run(cmd, stdout=subprocess.PIPE) + data = json.loads(proc.stdout).get('filesystems', {}) + logger.debug('fstab:') + for entry in data: + logger.debug(f' {entry}') + return data + + +def get_partition_data(): + """Gather disk partition data + + Returns a list of partition dicts. Ideally this would use lsblk --json, but + that just collects udev attributes. Since this will be run as a generator, + udev won't be running yet. In that case, blkid is used to gather partition + data just like udev itself does. + """ + partitions = [] + + # Get the partition devices, stripping any empty lines in the output. + proc = run( + ['blkid', '-d', '-c', '/dev/null', '-o', 'device'], + stdout=subprocess.PIPE, + ) + partition_devs = [line for line in proc.stdout.splitlines() if line] + logger.debug(f'partition devices: {partition_devs}') + + # Probe all the partition devices. + for dev in partition_devs: + try: + proc = run( + ['blkid', '--probe', '-d', '-c', '/dev/null', '-o', 'export', dev], + stdout=subprocess.PIPE, + ) + except subprocess.CalledProcessError as err: + # blkid will exit with code 2 if it can't gather information about + # the device. Just carry on in that case. + if err.returncode == 2: + logger.warning(f'Ignoring blkid --probe exit code 2 for {dev}') + continue + else: + raise + + entry = {} + for line in proc.stdout.splitlines(): + tag, _, value = line.partition('=') + if not tag: + continue + entry[tag] = value + + if entry: + partitions.append(entry) + else: + logger.warning(f'No blkid probe output for {dev}') + + logger.debug('partitions:') + for entry in partitions: + logger.debug(f' {entry}') + return partitions + + +def read_efivar(name): + """Read an EFI variable value + + Reads the data from efivarfs mounted at /sys/firmware/efi/efivars. + Only the variable value is returned, not the attributes. + """ + root = _get_root_path() + varpath = root / 'sys/firmware/efi/efivars' / name + logger.debug(f'Reading EFI variable {varpath}') + try: + with open(varpath, 'rb') as f: + value = f.read() + except FileNotFoundError: + logger.debug(f'EFI variable {name} not set') + return None + + # Skip the first 4 bytes, those are the 32 bit attribute mask. + if len(value) < 4: + logger.warning(f'Invalid EFI variable {name} is less than 4 bytes') + return None + return value[4:] + + +def read_efivar_utf16_string(name): + """Read an EFI variable UTF-16 string + + If the EFI variable doesn't exist, None is returned. Any nul + terminating bytes will be removed. + """ + value = read_efivar(name) + if value is None: + return None + + logger.debug(f'EFI variable {name} contents: {value}') + + # Systemd appends 3 nul bytes for some reason. If there are an odd + # number of bytes, ignore the last one so there are an appropriate + # number of utf16 bytes. + end = len(value) + if end % 2 == 1: + end -= 1 + + # Ignore any trailing nul byte pairs. + while end > 0: + if value[end - 2:end] != b'\0\0': + break + end -= 2 + + return value[:end].decode('utf-16', errors='replace') + + +def parse_kernel_command_line(): + """Read and parse the kernel command line from /proc/cmdline + + Returns a dictionary of arguments names and values. A value of None is used + if the argument has no = sign. + """ + path = _get_root_path() / 'proc/cmdline' + cmdline = path.read_text('utf-8') + + arguments = {} + for arg in shlex.split(cmdline): + name, eq, value = arg.partition('=') + if not eq: + # If there was no = in the argument, use None as the value to + # differentiate from an empty value. + value = None + arguments[name] = value + return arguments + + +def systemd_escape_path(path): + """Escape a path for usage in systemd unit names""" + proc = run( + ('systemd-escape', '--path', str(path)), + stdout=subprocess.PIPE, + ) + return proc.stdout.strip() + + +@dataclass +class EspMount: + """ESP mount specification + + Describes the parameters for mounting the ESP. The write_units() + method can be used to create the systemd units from a generator. + """ + source: str + target: str + type: str = 'vfat' + umask: str = '0077' + + def write_units(self, unit_dir): + source_escaped = systemd_escape_path(self.source) + target_escaped = systemd_escape_path(self.target) + automount_unit = unit_dir / f'{target_escaped}.automount' + mount_unit = unit_dir / f'{target_escaped}.mount' + local_fs_wants = unit_dir / 'local-fs.target.wants' / automount_unit.name + + logger.debug(f'Writing unit {automount_unit}') + automount_unit.write_text(dedent(f"""\ + # Automatically generated by {progname} + + [Unit] + Description=EFI System Partition Automount + + [Automount] + Where={self.target} + TimeoutIdleSec=2min + """)) + + logger.debug(f'Writing unit {mount_unit}') + mount_unit.write_text(dedent(f"""\ + # Automatically generated by {progname} + + [Unit] + Description=EFI System Partition Automount + Requires=systemd-fsck@{source_escaped}.service + After=systemd-fsck@{source_escaped}.service + After=blockdev@{source_escaped}.target + + [Mount] + What={self.source} + Where={self.target} + Type={self.type} + Options=umask={self.umask},noauto,rw + """)) + + # Create a symlink for the automount unit in local-fs.target.wants. + logger.debug(f'Creating {local_fs_wants} symlink') + local_fs_wants.parent.mkdir(parents=True, exist_ok=True) + link = os.path.relpath(automount_unit, local_fs_wants.parent) + os.symlink(link, local_fs_wants) + + +class EspGenerator: + """Generator for mounting the ESP + + Locates the ESP device and determines the path to mount it at. Call + get_esp_mount() to retrieve an EspMount instance describing the + configuration. + """ + def __init__(self): + self.root = _get_root_path() + self.mounts = get_mount_data() + self.fstab = get_fstab_data() + self.partitions = get_partition_data() + self.kcmdline = parse_kernel_command_line() + + self._root_part = self._get_root_partition() + self._endless_image_device, self._endless_image_part = ( + self._get_endless_image_device() + ) + self._loader_device_uuid, self._loader_device_part = self._get_loader_device() + + def get_esp_mount(self): + """Get the ESP mount specification + + Returns an EspMount or None. + """ + # Don't mount units in the initrd. + if os.getenv('SYSTEMD_IN_INITRD', '0') == '1': + logger.info('Skipping ESP mounting in initrd') + return None + + esp_part = self._get_esp_part() + if not esp_part: + logger.info('No ESP partition found, skipping mounting') + return None + + esp_dev = esp_part['DEVNAME'] + logger.info(f'ESP device: {esp_dev}') + + esp_path = self._get_esp_path(esp_part) + if not esp_path: + logger.info('No ESP mount path determined, skipping mounting') + return None + logger.info(f'ESP mount path: {esp_path}') + + # If the ESP is mounted at /boot, it needs to be world readable + # since it's also accessed by ostree and some operations are + # expected to work unprivileged. + umask = '0022' if esp_path == '/boot' else '0077' + + mount = EspMount(source=esp_dev, target=esp_path, umask=umask) + logger.info(f'Created ESP mount instance {mount}') + return mount + + def print_data(self): + """Print gathered device data""" + print('mounts:\n{}'.format(json.dumps(self.mounts, indent=2))) + print('fstab:\n{}'.format(json.dumps(self.fstab, indent=2))) + print('partitions:\n{}'.format(json.dumps(self.partitions, indent=2))) + print('kcmdline:\n{}'.format(json.dumps(self.kcmdline, indent=2))) + + def save_data(self): + """Save gathered data""" + with TemporaryDirectory(dir='/run', prefix='espgen-') as savedir: + logger.debug(f'Writing gathered data to {savedir}') + mounts_path = os.path.join(savedir, 'mounts.json') + with open(mounts_path, 'w') as f: + json.dump(self.mounts, f, indent=2) + f.write('\n') + + fstab_path = os.path.join(savedir, 'fstab.json') + with open(fstab_path, 'w') as f: + json.dump(self.fstab, f, indent=2) + f.write('\n') + + partitions_path = os.path.join(savedir, 'partitions.json') + with open(partitions_path, 'w') as f: + json.dump(self.partitions, f, indent=2) + f.write('\n') + + kcmdline_path = os.path.join(savedir, 'kcmdline.json') + with open(kcmdline_path, 'w') as f: + json.dump(self.kcmdline, f, indent=2) + f.write('\n') + + now = time.strftime('%Y%m%d%H%M%S') + data_path = f'/run/espgen-data-{now}.tar.gz' + logger.info(f'Saving gathered data to {data_path}') + with tarfile.open(data_path, 'x:gz') as tf: + tf.add(savedir, 'espgen-data') + + def _get_esp_part(self): + """Determine the ESP partition to use""" + # If LoaderDevicePartUUID is set, that's always used. + if self._loader_device_uuid: + # If the partition wasn't found, bail out. + if not self._loader_device_part: + return None + + # Make sure it points to an ESP. I'm fairly certain this will only + # be set on GPT disks, but validate MBR, too. + loader_device_dev = self._loader_device_part['DEVNAME'] + loader_device_scheme = self._loader_device_part['PART_ENTRY_SCHEME'] + loader_device_parttype = self._loader_device_part['PART_ENTRY_TYPE'] + if loader_device_scheme == 'gpt': + if loader_device_parttype != ESP_GPT_PARTTYPE: + logger.info( + f'Ignoring LoaderDevicePartUUID device {loader_device_dev} ' + 'since it is not an EFI system partition' + ) + return None + elif loader_device_scheme == 'dos': + if loader_device_parttype != ESP_MBR_PARTTYPE: + logger.info( + f'Ignoring LoaderDevicePartUUID device {loader_device_dev} ' + 'since it is not an EFI system partition' + ) + return None + else: + logger.warning( + f'Unexpected partition type "{loader_device_scheme}" for ' + f'LoaderDevicePartUUID device {loader_device_dev} disk' + ) + return None + + # If it's on the root disk, we're done. + root_dev = self._root_part['DEVNAME'] + loader_on_root = self._partitions_on_same_disk( + self._loader_device_part, + self._root_part, + ) + if loader_on_root: + logger.debug( + f'Using LoaderDevicePartUUID device {loader_device_dev} for ESP ' + f'since it is on the same disk as root device {root_dev}' + ) + return self._loader_device_part + + # If it's on a different disk, it might not be our ESP. Only + # use it if it's on the endless.image.device disk. + if not self._endless_image_part: + return None + + endless_image_dev = self._endless_image_part["DEVNAME"] + + loader_on_endless_image = self._partitions_on_same_disk( + self._loader_device_part, + self._endless_image_part, + ) + if not loader_on_endless_image: + logger.info( + f'Ignoring LoaderDevicePartUUID device {loader_device_dev} since ' + f'it is not on the same disk as root device {root_dev} or ' + f'endless.image.device {endless_image_dev}' + ) + return None + + logger.debug( + f'Using LoaderDevicePartUUID device {loader_device_dev} for ESP since ' + f'it is on the same disk as endless.image.device {endless_image_dev}' + ) + return self._loader_device_part + + # At this point we should be done handling LoaderDevicePartUUID. + assert not self._loader_device_uuid, ( + 'LoaderDevicePartUUID handling did not complete' + ) + + # Look for an appropriate ESP partition. If endless.image.device + # is set, use that disk. Otherwise, use the root partition disk. + if self._endless_image_device: + esp_disk_part = self._endless_image_part + else: + esp_disk_part = self._root_part + + if not esp_disk_part: + # If the partition wasn't found, bail out. + return None + + # Look for the first ESP partition on the disk depending on partition + # type. + esp_part = None + esp_disk_part_dev = esp_disk_part['DEVNAME'] + esp_disk_scheme = esp_disk_part['PART_ENTRY_SCHEME'] + if esp_disk_scheme == 'gpt': + esp_part = self._get_partition( + PART_ENTRY_SCHEME=esp_disk_scheme, + PART_ENTRY_DISK=esp_disk_part['PART_ENTRY_DISK'], + PART_ENTRY_TYPE=ESP_GPT_PARTTYPE, + ) + if not esp_part: + logger.info( + f'No GPT partition with ESP type {ESP_GPT_PARTTYPE} found ' + f'on {esp_disk_part_dev} disk' + ) + return None + elif esp_disk_scheme == 'dos': + esp_part = self._get_partition( + PART_ENTRY_SCHEME=esp_disk_scheme, + PART_ENTRY_DISK=esp_disk_part['PART_ENTRY_DISK'], + PART_ENTRY_TYPE=ESP_MBR_PARTTYPE, + ) + if not esp_part: + logger.info( + f'No MBR partition with ESP type {ESP_MBR_PARTTYPE} found ' + f'on {esp_disk_part_dev} disk' + ) + return None + else: + logger.warning( + f'Unexpected partition type "{esp_disk_scheme}" for ' + f'{esp_disk_part_dev} disk' + ) + return None + + assert esp_part, 'Should have found esp_part' + esp_part_dev = esp_part['DEVNAME'] + + # Don't handle the ESP mount if the disk has an XBOOTLDR partition. We + # don't currently create an XBOOTLDR partition and supporting them + # would provide very little benefit. systemd-gpt-auto-generator should + # mostly DTRT, anyways. + if self._disk_has_xbootldr(esp_part): + logger.info( + f'Ignoring ESP device {esp_part_dev} since the disk has an ' + 'XBOOTLDR partition' + ) + return None + + esp_disk_use = ( + 'endless.image.device' if self._endless_image_device else 'root' + ) + logger.debug( + f'Using device {esp_part_dev} for ESP since it is on the same disk as ' + f'{esp_disk_use} device {esp_disk_part_dev}' + ) + return esp_part + + def _get_esp_path(self, esp_part): + """Determine the path to mount the ESP partition""" + esp_dev = esp_part['DEVNAME'] + boot_mount = self._get_mount(target='/boot') + boot_fstab = self._get_fstab(target='/boot') + efi_mount = self._get_mount(target='/efi') + efi_fstab = self._get_fstab(target='/efi') + + def _use_if_dir_exists(path): + rooted_path = self.root / path.lstrip('/') + if not rooted_path.exists(): + logger.info(f'Would use {path} for mount path, but it does not exist') + return None + return path + + # /boot is preferred unless some other boot directory/partition will be + # mounted there. + path = '/boot' + if boot_fstab: + # If the source is the ESP device, do nothing as the fstab + # generator will create the mount unit. + if boot_fstab['source'] == esp_dev: + logger.info(f'Skipping /boot since it is in fstab using {esp_dev}') + return None + + # Something else is setup to be mounted at /boot. Prefer /efi. + logger.debug( + f'/boot is in fstab using {boot_fstab["source"]}, ' + f'prefer /efi as mount path' + ) + path = '/efi' + + if path == '/boot': + if boot_mount: + # /boot is already mounted but not via fstab configuration. + if boot_mount['source'] == esp_dev: + # If it's mounted using the ESP device, then that probably + # happened from a mount unit created by a previous run of + # this generator. Keep using /boot. + logger.debug( + f'/boot is mounted using ESP device {esp_dev}, ' + 'use it as mount path' + ) + else: + # Some other device is mounted at /boot. Likely this is the + # bind mount created by ostree-prepare-root in the + # initramfs. Regardless, we don't want to mount the ESP + # over it. + logger.debug( + f'/boot is mounted using {boot_mount["source"]}, ' + f'prefer /efi as mount path' + ) + path = '/efi' + + if path == '/boot': + return _use_if_dir_exists(path) + + # At this point we should only be dealing with /efi. + assert path == '/efi', '/boot handling did not complete' + + # Validate potential /efi mount point. + if efi_fstab: + logger.info( + f'Skipping /efi since it is in fstab using {efi_fstab["source"]}' + ) + return None + + if efi_mount: + # /efi is already mounted but not via fstab configuration. + if efi_mount['source'] == esp_dev: + # If it's mounted using the ESP device, then that probably + # happened from a mount unit created by a previous run of this + # generator. Keep using /efi. + logger.debug( + f'/efi is mounted using ESP device {esp_dev}, ' + 'use it as mount path' + ) + else: + # Some other device is mounted at /efi. Don't want to mount the + # ESP over it. + logger.info( + f'Skipping /efi since it is mounted using {efi_mount["source"]}' + ) + return None + + return _use_if_dir_exists(path) + + def _get_root_partition(self): + root_mount = self._get_mount(target='/') + if not root_mount: + raise EspError(f'Could not find / mount in {self.mounts}') + root_dev = root_mount['source'] + root_part = self._get_partition(DEVNAME=root_dev) + if not root_part: + raise EspError(f'Could not find / device {root_dev} in {self.partitions}') + logger.debug(f'Determined root partition {root_part}') + return root_part + + @staticmethod + def _partitions_on_same_disk(a, b): + return a['PART_ENTRY_DISK'] == b['PART_ENTRY_DISK'] + + def _get_endless_image_device(self): + """Find the partition for the endless.image.device command line option + + Looks for the endless.image.device kernel command line option and tries + to find the corresponding partition. Returns a tuple of argument and + partition. + """ + image_device_arg = self.kcmdline.get('endless.image.device') + if not image_device_arg: + logger.debug('endless.image.device kernel command line arg not set') + return None, None + + logger.debug( + f'Found endless.image.device kernel command line arg is {image_device_arg}' + ) + tag, eq, value = image_device_arg.partition('=') + if not eq: + # No = sign, treat the argument as a device path. + part = self._get_partition(DEVNAME=image_device_arg) + else: + # Presumably a tag like UUID=. Make sure it's something we + # can handle. + if tag.upper() not in ('UUID', 'LABEL', 'PARTUUID', 'PARTLABEL'): + logger.warning( + f'Unrecognized tag "{tag}" in endless.image.device argument ' + f'{image_device_arg}' + ) + return image_device_arg, None + + tag = tag.upper() + if tag == 'PARTUUID': + tag = 'PART_ENTRY_UUID' + elif tag == 'PARTLABEL': + tag = 'PART_ENTRY_NAME' + part_kwargs = {tag: value} + part = self._get_partition(**part_kwargs) + + if not part: + logger.warning( + f'Could not locate endless.image.device "{image_device_arg}"' + ) + return image_device_arg, part + + def _get_loader_device(self): + """Find the partition for the LoaderDevicePartUUID EFI variable + + Looks for the LoaderDevicePartUUID EFI variable to find the + corresponding partition. Returns a tuple of value and partition. + """ + loader_part_uuid = read_efivar_utf16_string(LOADER_DEVICE_PART_UUID_EFIVAR) + if not loader_part_uuid: + return None, None + + logger.debug( + f'Found EFI var {LOADER_DEVICE_PART_UUID_NAME} is {loader_part_uuid}' + ) + + uuid = loader_part_uuid.lower() + part = self._get_partition(PART_ENTRY_UUID=uuid) + if not part: + logger.warning( + f'Could not locate {LOADER_DEVICE_PART_UUID_NAME} "{loader_part_uuid}"' + ) + return loader_part_uuid, part + + def _disk_has_xbootldr(self, partition): + """Whether the partition's disk has an XBOOTLDR partition""" + xbootldr_part = self._get_partition( + PART_ENTRY_SCHEME='gpt', + PART_ENTRY_DISK=partition['PART_ENTRY_DISK'], + PART_ENTRY_TYPE=XBOOTLDR_GPT_PARTTYPE, + ) + return bool(xbootldr_part) + + @staticmethod + def _filter_data(data, **kwargs): + """Filter iterable of dict data + + Returns an iterator of dicts where the dict entries match the keyword + arguments. + """ + if not kwargs: + raise ValueError('Must provide filter values') + + def _test(entry): + for name, value in kwargs.items(): + if name not in entry or entry[name] != value: + return False + return True + + return filter(_test, data) + + def _get_mount(self, **kwargs): + return next(self._filter_data(self.mounts, **kwargs), None) + + def _get_fstab(self, **kwargs): + return next(self._filter_data(self.fstab, **kwargs), None) + + def _get_partition(self, **kwargs): + return next(self._filter_data(self.partitions, **kwargs), None) + + +def main(): + """ESP generator main entry point""" + doclines = __doc__.splitlines() + ap = ArgumentParser( + description=doclines[0], + epilog='\n'.join(doclines[2:]), + formatter_class=RawDescriptionHelpFormatter, + ) + ap.add_argument( + 'normal_dir', + metavar='NORMAL', + nargs='?', + help='Normal generator output directory', + ) + ap.add_argument( + 'early_dir', + metavar='EARLY', + nargs='?', + help='Early generator output directory', + ) + ap.add_argument( + 'late_dir', + metavar='LATE', + nargs='?', + help='Late generator output directory', + ) + ap.add_argument( + '-n', + '--dry-run', + action='store_true', + help='only show what would be done', + ) + ap.add_argument( + '-g', + '--gather-data', + action='store_true', + help='only gather device data', + ) + ap.add_argument( + '-s', + '--save-data', + action='store_true', + help='save gathered data', + ) + log_level_group = ap.add_mutually_exclusive_group() + log_level_group.add_argument( + '--quiet', + dest='log_level', + action='store_const', + const=logging.WARNING, + help='only log warning messages', + ) + log_level_group.add_argument( + '--debug', + dest='log_level', + action='store_const', + const=logging.DEBUG, + help='log debug messages', + ) + + # Detect when being run as a generator. + run_as_generator = in_generator_env() + + # Default to debug logging when run as a generator and debug is in the + # kernel command line. + if run_as_generator and 'debug' in parse_kernel_command_line(): + default_log_level = logging.DEBUG + else: + default_log_level = logging.INFO + ap.set_defaults(log_level=default_log_level) + + # Default to gather mode when invoked using the special gather name. + if progname == GATHER_MODE_PROGNAME: + ap.set_defaults(gather_data=True, save_data=True) + + # Generators are run with stdout/stderr connected to /dev/null, so + # reconnect them to /dev/kmsg and use the KmsgHandler logging handler. + if run_as_generator: + with open('/dev/kmsg', 'wb', buffering=0) as kmsg: + kmsg_fd = kmsg.fileno() + stdout_fd = sys.stdout.fileno() + stderr_fd = sys.stderr.fileno() + os.dup2(kmsg_fd, stdout_fd) + os.dup2(kmsg_fd, stderr_fd) + + log_handler = KmsgHandler() + + # Use a format string with just the message since the name, level and + # time are already handled. + log_format = '%(message)s' + else: + # Use a normal stderr handler. + log_handler = logging.StreamHandler(sys.stderr) + log_format = '%(name)s:%(levelname)s:%(message)s' + + args = ap.parse_args() + logging.basicConfig(level=args.log_level, handlers=[log_handler], format=log_format) + + # When not in gather mode, at least the normal unit directory is needed. + if not args.gather_data and not args.normal_dir: + logger.error('Normal generator directory not specified') + sys.exit(1) + + generator = EspGenerator() + if args.gather_data: + # In gather mode, just collect the data and exit. + if args.save_data: + generator.save_data() + else: + generator.print_data() + return + + esp_mount = generator.get_esp_mount() + if esp_mount and not args.dry_run: + # The normal generator directory is used so that we take precedence + # over systemd-gpt-auto-generator. However, this is the same level as + # systemd-fstab-generator, so we need to take care to not override + # fstab entries. + unit_dir = Path(args.normal_dir) + esp_mount.write_units(unit_dir) + + +if __name__ == '__main__': + # Print exceptions through logging so it gets associated to this program in + # kmsg. + try: + main() + except SystemExit: + pass + except subprocess.CalledProcessError as err: + logger.exception(f'Executing {err.cmd[0]} failed:\n{err.stderr}') + sys.exit(err.returncode) + except: # noqa: E722 + logger.exception('Generator failed:') + sys.exit(1) diff --git a/tests/Makefile.am b/tests/Makefile.am index 9efd4f6..57de18d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -19,6 +19,9 @@ EXTRA_DIST = \ __init__.py \ conftest.py \ efivars \ + espgen \ + espgen-test-data.sh \ + test_esp_generator.py \ test_image_boot.py \ test_live_storage.py \ test_migrate_chromium_profile.py \ diff --git a/tests/espgen/README.md b/tests/espgen/README.md new file mode 100644 index 0000000..e0eab11 --- /dev/null +++ b/tests/espgen/README.md @@ -0,0 +1,16 @@ +Test data directory for use with `test_esp_generator.py`. The data here is +created by running `eos-esp-generator` in gather mode. The easiest way to do +that is to install it as +`/etc/systemd/system-generators/eos-esp-generator-gather`. + +Reboot so that it runs as an early boot generator. After booting, run it again +as a generator by calling `systemctl daemon-reload`. There will then be 2 +tarballs at `/run/espgen-data-*.tar.gz`. Unpack the tarballs and add the data +files to this directory with a semi-descriptive prefix. The `mounts.json` and +`partitions.json` data files should be differentiated by adding `-init` or +`-reload` suffixes as appropriate. The `fstab.json` and `kcmdline.json` files +don't need to be differentiated as they won't change between the 2 executions +of the generator. + +Finally, wire up the data in the `ESP_MOUNT_TEST_DATA` dictionary in +`test_esp_generator.py`. The key is the prefix added to the data files above. diff --git a/tests/espgen/grub-gpt-fstab-efi-fstab.json b/tests/espgen/grub-gpt-fstab-efi-fstab.json new file mode 100644 index 0000000..f716579 --- /dev/null +++ b/tests/espgen/grub-gpt-fstab-efi-fstab.json @@ -0,0 +1,18 @@ +[ + { + "target": "/", + "source": "/dev/vda3", + "maj:min": null, + "fstype": "ext4", + "fsroot": null, + "options": "errors=remount-ro" + }, + { + "target": "/efi", + "source": "/dev/vda1", + "maj:min": null, + "fstype": "vfat", + "fsroot": null, + "options": "umask=0077" + } +] diff --git a/tests/espgen/grub-gpt-fstab-efi-kcmdline.json b/tests/espgen/grub-gpt-fstab-efi-kcmdline.json new file mode 100644 index 0000000..ca045bd --- /dev/null +++ b/tests/espgen/grub-gpt-fstab-efi-kcmdline.json @@ -0,0 +1,10 @@ +{ + "BOOT_IMAGE": "(hd0,gpt3)/boot/ostree/eos-078584ee3e2b33ee29896c7ebd1533b40f7f7a6d551dcc39cfecd4d8e8f75d51/vmlinuz-6.5.0-10-generic", + "root": "UUID=cecb0576-cf95-48bf-8b72-aced790e83e9", + "rw": null, + "splash": null, + "plymouth.ignore-serial-consoles": null, + "quiet": null, + "loglevel": "0", + "ostree": "/ostree/boot.1/eos/078584ee3e2b33ee29896c7ebd1533b40f7f7a6d551dcc39cfecd4d8e8f75d51/0" +} diff --git a/tests/espgen/grub-gpt-fstab-efi-mounts-init.json b/tests/espgen/grub-gpt-fstab-efi-mounts-init.json new file mode 100644 index 0000000..575a5fd --- /dev/null +++ b/tests/espgen/grub-gpt-fstab-efi-mounts-init.json @@ -0,0 +1,34 @@ +[ + { + "target": "/", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/03b591bb31c1d4cb69df9e4a7d7097a48012354d4a3777179d90016ec1893738.0", + "options": "ro,relatime" + }, + { + "target": "/boot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/boot", + "options": "ro,relatime" + }, + { + "target": "/usr", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/03b591bb31c1d4cb69df9e4a7d7097a48012354d4a3777179d90016ec1893738.0/usr", + "options": "ro,relatime" + }, + { + "target": "/sysroot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/", + "options": "ro,relatime" + } +] diff --git a/tests/espgen/grub-gpt-fstab-efi-mounts-reload.json b/tests/espgen/grub-gpt-fstab-efi-mounts-reload.json new file mode 100644 index 0000000..39e8577 --- /dev/null +++ b/tests/espgen/grub-gpt-fstab-efi-mounts-reload.json @@ -0,0 +1,50 @@ +[ + { + "target": "/", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/03b591bb31c1d4cb69df9e4a7d7097a48012354d4a3777179d90016ec1893738.0", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/boot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/boot", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/usr", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/03b591bb31c1d4cb69df9e4a7d7097a48012354d4a3777179d90016ec1893738.0/usr", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/sysroot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/var", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/var", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/efi", + "source": "/dev/vda1", + "maj:min": "253:1", + "fstype": "vfat", + "fsroot": "/", + "options": "rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro" + } +] diff --git a/tests/espgen/grub-gpt-fstab-efi-partitions-init.json b/tests/espgen/grub-gpt-fstab-efi-partitions-init.json new file mode 100644 index 0000000..ff16736 --- /dev/null +++ b/tests/espgen/grub-gpt-fstab-efi-partitions-init.json @@ -0,0 +1,44 @@ +[ + { + "DEVNAME": "/dev/vda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "9eaab1f8-c210-2b4e-8656-e8ed0beb1ab2", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "129024", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda3", + "LABEL": "ostree", + "UUID": "cecb0576-cf95-48bf-8b72-aced790e83e9", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "eb219ed1-9e98-4048-9288-1c16db3dea72", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "131072", + "PART_ENTRY_SIZE": "52297695", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "CD28-379D", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "7969ae8c-416d-6f4d-9bff-9e2047c79857", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "126976", + "PART_ENTRY_DISK": "253:0" + } +] diff --git a/tests/espgen/grub-gpt-fstab-efi-partitions-reload.json b/tests/espgen/grub-gpt-fstab-efi-partitions-reload.json new file mode 100644 index 0000000..685b921 --- /dev/null +++ b/tests/espgen/grub-gpt-fstab-efi-partitions-reload.json @@ -0,0 +1,51 @@ +[ + { + "DEVNAME": "/dev/zram0", + "UUID": "1293710f-3872-47e8-a9a8-be9545021994", + "VERSION": "1", + "TYPE": "swap", + "USAGE": "other" + }, + { + "DEVNAME": "/dev/vda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "9eaab1f8-c210-2b4e-8656-e8ed0beb1ab2", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "129024", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda3", + "LABEL": "ostree", + "UUID": "cecb0576-cf95-48bf-8b72-aced790e83e9", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "eb219ed1-9e98-4048-9288-1c16db3dea72", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "131072", + "PART_ENTRY_SIZE": "52297695", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "CD28-379D", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "7969ae8c-416d-6f4d-9bff-9e2047c79857", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "126976", + "PART_ENTRY_DISK": "253:0" + } +] diff --git a/tests/espgen/grub-gpt-fstab.json b/tests/espgen/grub-gpt-fstab.json new file mode 100644 index 0000000..826ab23 --- /dev/null +++ b/tests/espgen/grub-gpt-fstab.json @@ -0,0 +1,10 @@ +[ + { + "target": "/", + "source": "/dev/vda3", + "maj:min": null, + "fstype": "ext4", + "fsroot": null, + "options": "errors=remount-ro" + } +] diff --git a/tests/espgen/grub-gpt-kcmdline.json b/tests/espgen/grub-gpt-kcmdline.json new file mode 100644 index 0000000..ca045bd --- /dev/null +++ b/tests/espgen/grub-gpt-kcmdline.json @@ -0,0 +1,10 @@ +{ + "BOOT_IMAGE": "(hd0,gpt3)/boot/ostree/eos-078584ee3e2b33ee29896c7ebd1533b40f7f7a6d551dcc39cfecd4d8e8f75d51/vmlinuz-6.5.0-10-generic", + "root": "UUID=cecb0576-cf95-48bf-8b72-aced790e83e9", + "rw": null, + "splash": null, + "plymouth.ignore-serial-consoles": null, + "quiet": null, + "loglevel": "0", + "ostree": "/ostree/boot.1/eos/078584ee3e2b33ee29896c7ebd1533b40f7f7a6d551dcc39cfecd4d8e8f75d51/0" +} diff --git a/tests/espgen/grub-gpt-mounts-init.json b/tests/espgen/grub-gpt-mounts-init.json new file mode 100644 index 0000000..575a5fd --- /dev/null +++ b/tests/espgen/grub-gpt-mounts-init.json @@ -0,0 +1,34 @@ +[ + { + "target": "/", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/03b591bb31c1d4cb69df9e4a7d7097a48012354d4a3777179d90016ec1893738.0", + "options": "ro,relatime" + }, + { + "target": "/boot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/boot", + "options": "ro,relatime" + }, + { + "target": "/usr", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/03b591bb31c1d4cb69df9e4a7d7097a48012354d4a3777179d90016ec1893738.0/usr", + "options": "ro,relatime" + }, + { + "target": "/sysroot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/", + "options": "ro,relatime" + } +] diff --git a/tests/espgen/grub-gpt-mounts-reload.json b/tests/espgen/grub-gpt-mounts-reload.json new file mode 100644 index 0000000..39e8577 --- /dev/null +++ b/tests/espgen/grub-gpt-mounts-reload.json @@ -0,0 +1,50 @@ +[ + { + "target": "/", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/03b591bb31c1d4cb69df9e4a7d7097a48012354d4a3777179d90016ec1893738.0", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/boot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/boot", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/usr", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/03b591bb31c1d4cb69df9e4a7d7097a48012354d4a3777179d90016ec1893738.0/usr", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/sysroot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/var", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/var", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/efi", + "source": "/dev/vda1", + "maj:min": "253:1", + "fstype": "vfat", + "fsroot": "/", + "options": "rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro" + } +] diff --git a/tests/espgen/grub-gpt-partitions-init.json b/tests/espgen/grub-gpt-partitions-init.json new file mode 100644 index 0000000..ff16736 --- /dev/null +++ b/tests/espgen/grub-gpt-partitions-init.json @@ -0,0 +1,44 @@ +[ + { + "DEVNAME": "/dev/vda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "9eaab1f8-c210-2b4e-8656-e8ed0beb1ab2", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "129024", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda3", + "LABEL": "ostree", + "UUID": "cecb0576-cf95-48bf-8b72-aced790e83e9", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "eb219ed1-9e98-4048-9288-1c16db3dea72", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "131072", + "PART_ENTRY_SIZE": "52297695", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "CD28-379D", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "7969ae8c-416d-6f4d-9bff-9e2047c79857", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "126976", + "PART_ENTRY_DISK": "253:0" + } +] diff --git a/tests/espgen/grub-gpt-partitions-reload.json b/tests/espgen/grub-gpt-partitions-reload.json new file mode 100644 index 0000000..685b921 --- /dev/null +++ b/tests/espgen/grub-gpt-partitions-reload.json @@ -0,0 +1,51 @@ +[ + { + "DEVNAME": "/dev/zram0", + "UUID": "1293710f-3872-47e8-a9a8-be9545021994", + "VERSION": "1", + "TYPE": "swap", + "USAGE": "other" + }, + { + "DEVNAME": "/dev/vda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "9eaab1f8-c210-2b4e-8656-e8ed0beb1ab2", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "129024", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda3", + "LABEL": "ostree", + "UUID": "cecb0576-cf95-48bf-8b72-aced790e83e9", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "eb219ed1-9e98-4048-9288-1c16db3dea72", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "131072", + "PART_ENTRY_SIZE": "52297695", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "CD28-379D", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "7969ae8c-416d-6f4d-9bff-9e2047c79857", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "126976", + "PART_ENTRY_DISK": "253:0" + } +] diff --git a/tests/espgen/grub-mbr-fstab.json b/tests/espgen/grub-mbr-fstab.json new file mode 100644 index 0000000..27c097d --- /dev/null +++ b/tests/espgen/grub-mbr-fstab.json @@ -0,0 +1,10 @@ +[ + { + "target": "/", + "source": "/dev/vda2", + "maj:min": null, + "fstype": "ext4", + "fsroot": null, + "options": "errors=remount-ro" + } +] diff --git a/tests/espgen/grub-mbr-kcmdline.json b/tests/espgen/grub-mbr-kcmdline.json new file mode 100644 index 0000000..102f70e --- /dev/null +++ b/tests/espgen/grub-mbr-kcmdline.json @@ -0,0 +1,10 @@ +{ + "BOOT_IMAGE": "(hd0,msdos2)/boot/ostree/eos-078584ee3e2b33ee29896c7ebd1533b40f7f7a6d551dcc39cfecd4d8e8f75d51/vmlinuz-6.5.0-10-generic", + "root": "UUID=dde5e094-c22d-4b11-86de-e6c880085f93", + "rw": null, + "splash": null, + "plymouth.ignore-serial-consoles": null, + "quiet": null, + "loglevel": "0", + "ostree": "/ostree/boot.0/eos/078584ee3e2b33ee29896c7ebd1533b40f7f7a6d551dcc39cfecd4d8e8f75d51/0" +} diff --git a/tests/espgen/grub-mbr-mounts-init.json b/tests/espgen/grub-mbr-mounts-init.json new file mode 100644 index 0000000..9c69f2e --- /dev/null +++ b/tests/espgen/grub-mbr-mounts-init.json @@ -0,0 +1,34 @@ +[ + { + "target": "/", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/587b6f5ddcd32337a8b27ac3fe07f6141965fc455200f07f32d2a4ae7c089028.0", + "options": "ro,relatime" + }, + { + "target": "/boot", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/boot", + "options": "ro,relatime" + }, + { + "target": "/usr", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/587b6f5ddcd32337a8b27ac3fe07f6141965fc455200f07f32d2a4ae7c089028.0/usr", + "options": "ro,relatime" + }, + { + "target": "/sysroot", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/", + "options": "ro,relatime" + } +] diff --git a/tests/espgen/grub-mbr-mounts-reload.json b/tests/espgen/grub-mbr-mounts-reload.json new file mode 100644 index 0000000..81eb4af --- /dev/null +++ b/tests/espgen/grub-mbr-mounts-reload.json @@ -0,0 +1,50 @@ +[ + { + "target": "/", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/587b6f5ddcd32337a8b27ac3fe07f6141965fc455200f07f32d2a4ae7c089028.0", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/boot", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/boot", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/usr", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/587b6f5ddcd32337a8b27ac3fe07f6141965fc455200f07f32d2a4ae7c089028.0/usr", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/sysroot", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/var", + "source": "/dev/vda2", + "maj:min": "253:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/var", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/efi", + "source": "/dev/vda1", + "maj:min": "253:1", + "fstype": "vfat", + "fsroot": "/", + "options": "rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro" + } +] diff --git a/tests/espgen/grub-mbr-partitions-init.json b/tests/espgen/grub-mbr-partitions-init.json new file mode 100644 index 0000000..b8fce04 --- /dev/null +++ b/tests/espgen/grub-mbr-partitions-init.json @@ -0,0 +1,35 @@ +[ + { + "DEVNAME": "/dev/vda2", + "LABEL": "ostree", + "UUID": "dde5e094-c22d-4b11-86de-e6c880085f93", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "dos", + "PART_ENTRY_UUID": "680dae45-02", + "PART_ENTRY_TYPE": "0x83", + "PART_ENTRY_FLAGS": "0x80", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "131072", + "PART_ENTRY_SIZE": "88487695", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "6D78-30F7", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "dos", + "PART_ENTRY_UUID": "680dae45-01", + "PART_ENTRY_TYPE": "0xef", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "126976", + "PART_ENTRY_DISK": "253:0" + } +] diff --git a/tests/espgen/grub-mbr-partitions-reload.json b/tests/espgen/grub-mbr-partitions-reload.json new file mode 100644 index 0000000..3b55abf --- /dev/null +++ b/tests/espgen/grub-mbr-partitions-reload.json @@ -0,0 +1,42 @@ +[ + { + "DEVNAME": "/dev/zram0", + "UUID": "dc44bec6-3776-441e-96f5-b77aac2a7505", + "VERSION": "1", + "TYPE": "swap", + "USAGE": "other" + }, + { + "DEVNAME": "/dev/vda2", + "LABEL": "ostree", + "UUID": "dde5e094-c22d-4b11-86de-e6c880085f93", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "dos", + "PART_ENTRY_UUID": "680dae45-02", + "PART_ENTRY_TYPE": "0x83", + "PART_ENTRY_FLAGS": "0x80", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "131072", + "PART_ENTRY_SIZE": "88487695", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "6D78-30F7", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "dos", + "PART_ENTRY_UUID": "680dae45-01", + "PART_ENTRY_TYPE": "0xef", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "126976", + "PART_ENTRY_DISK": "253:0" + } +] diff --git a/tests/espgen/sdboot-fstab.json b/tests/espgen/sdboot-fstab.json new file mode 100644 index 0000000..826ab23 --- /dev/null +++ b/tests/espgen/sdboot-fstab.json @@ -0,0 +1,10 @@ +[ + { + "target": "/", + "source": "/dev/vda3", + "maj:min": null, + "fstype": "ext4", + "fsroot": null, + "options": "errors=remount-ro" + } +] diff --git a/tests/espgen/sdboot-kcmdline.json b/tests/espgen/sdboot-kcmdline.json new file mode 100644 index 0000000..4e50875 --- /dev/null +++ b/tests/espgen/sdboot-kcmdline.json @@ -0,0 +1,11 @@ +{ + "eospayg": null, + "efi_no_storage_paranoia": null, + "rd.shell": "0", + "rw": null, + "splash": null, + "plymouth.ignore-serial-consoles": null, + "quiet": null, + "loglevel": "0", + "ostree": "/ostree/boot.0/eos/de6ecb1835fa999f3d337c4b719b6b1259f829d4e938703a1316949622612ba6/0" +} diff --git a/tests/espgen/sdboot-mounts-init.json b/tests/espgen/sdboot-mounts-init.json new file mode 100644 index 0000000..d8969f8 --- /dev/null +++ b/tests/espgen/sdboot-mounts-init.json @@ -0,0 +1,26 @@ +[ + { + "target": "/sysroot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/", + "options": "rw,relatime" + }, + { + "target": "/", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/d590ffda3b7eebc41121fde0a05c10d5638c5a0facb51ec7437268399dfc51dd.0", + "options": "rw,relatime" + }, + { + "target": "/usr", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/d590ffda3b7eebc41121fde0a05c10d5638c5a0facb51ec7437268399dfc51dd.0/usr", + "options": "ro,relatime" + } +] diff --git a/tests/espgen/sdboot-mounts-reload.json b/tests/espgen/sdboot-mounts-reload.json new file mode 100644 index 0000000..a7d36c0 --- /dev/null +++ b/tests/espgen/sdboot-mounts-reload.json @@ -0,0 +1,42 @@ +[ + { + "target": "/sysroot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/", + "options": "rw,relatime,errors=remount-ro" + }, + { + "target": "/", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/d590ffda3b7eebc41121fde0a05c10d5638c5a0facb51ec7437268399dfc51dd.0", + "options": "rw,relatime,errors=remount-ro" + }, + { + "target": "/usr", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/d590ffda3b7eebc41121fde0a05c10d5638c5a0facb51ec7437268399dfc51dd.0/usr", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/var", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/var", + "options": "rw,relatime,errors=remount-ro" + }, + { + "target": "/boot", + "source": "/dev/vda1", + "maj:min": "253:1", + "fstype": "vfat", + "fsroot": "/", + "options": "rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro" + } +] diff --git a/tests/espgen/sdboot-partitions-init.json b/tests/espgen/sdboot-partitions-init.json new file mode 100644 index 0000000..3f82a69 --- /dev/null +++ b/tests/espgen/sdboot-partitions-init.json @@ -0,0 +1,44 @@ +[ + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "B5E9-EA49", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "0189f938-7e9e-194d-8757-e85edaaca5b3", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "1024000", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "e7fb50ac-d074-5c4d-8d1e-92f91b4f0718", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "1026048", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda3", + "LABEL": "ostree", + "UUID": "2bad2e5e-d97c-4c87-ae37-aa8ed2279c9a", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "b47e35b3-8d0d-7347-b2e1-6303cc628984", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "1028096", + "PART_ENTRY_SIZE": "499090063", + "PART_ENTRY_DISK": "253:0" + } +] diff --git a/tests/espgen/sdboot-partitions-reload.json b/tests/espgen/sdboot-partitions-reload.json new file mode 100644 index 0000000..2a1615a --- /dev/null +++ b/tests/espgen/sdboot-partitions-reload.json @@ -0,0 +1,51 @@ +[ + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "B5E9-EA49", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "0189f938-7e9e-194d-8757-e85edaaca5b3", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "1024000", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "e7fb50ac-d074-5c4d-8d1e-92f91b4f0718", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "1026048", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda3", + "LABEL": "ostree", + "UUID": "2bad2e5e-d97c-4c87-ae37-aa8ed2279c9a", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "b47e35b3-8d0d-7347-b2e1-6303cc628984", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "1028096", + "PART_ENTRY_SIZE": "499090063", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/zram0", + "UUID": "6965bcd9-5f8b-42d2-8c2e-12d0e8d22163", + "VERSION": "1", + "TYPE": "swap", + "USAGE": "other" + } +] diff --git a/tests/espgen/sdboot-xbootldr-fstab.json b/tests/espgen/sdboot-xbootldr-fstab.json new file mode 100644 index 0000000..826ab23 --- /dev/null +++ b/tests/espgen/sdboot-xbootldr-fstab.json @@ -0,0 +1,10 @@ +[ + { + "target": "/", + "source": "/dev/vda3", + "maj:min": null, + "fstype": "ext4", + "fsroot": null, + "options": "errors=remount-ro" + } +] diff --git a/tests/espgen/sdboot-xbootldr-kcmdline.json b/tests/espgen/sdboot-xbootldr-kcmdline.json new file mode 100644 index 0000000..4e50875 --- /dev/null +++ b/tests/espgen/sdboot-xbootldr-kcmdline.json @@ -0,0 +1,11 @@ +{ + "eospayg": null, + "efi_no_storage_paranoia": null, + "rd.shell": "0", + "rw": null, + "splash": null, + "plymouth.ignore-serial-consoles": null, + "quiet": null, + "loglevel": "0", + "ostree": "/ostree/boot.0/eos/de6ecb1835fa999f3d337c4b719b6b1259f829d4e938703a1316949622612ba6/0" +} diff --git a/tests/espgen/sdboot-xbootldr-mounts-init.json b/tests/espgen/sdboot-xbootldr-mounts-init.json new file mode 100644 index 0000000..a61cc79 --- /dev/null +++ b/tests/espgen/sdboot-xbootldr-mounts-init.json @@ -0,0 +1,26 @@ +[ + { + "target": "/sysroot", + "source": "/dev/vda4", + "maj:min": "253:4", + "fstype": "ext4", + "fsroot": "/", + "options": "rw,relatime" + }, + { + "target": "/", + "source": "/dev/vda4", + "maj:min": "253:4", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/d590ffda3b7eebc41121fde0a05c10d5638c5a0facb51ec7437268399dfc51dd.0", + "options": "rw,relatime" + }, + { + "target": "/usr", + "source": "/dev/vda4", + "maj:min": "253:4", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/d590ffda3b7eebc41121fde0a05c10d5638c5a0facb51ec7437268399dfc51dd.0/usr", + "options": "ro,relatime" + } +] diff --git a/tests/espgen/sdboot-xbootldr-mounts-reload.json b/tests/espgen/sdboot-xbootldr-mounts-reload.json new file mode 100644 index 0000000..20eeb90 --- /dev/null +++ b/tests/espgen/sdboot-xbootldr-mounts-reload.json @@ -0,0 +1,50 @@ +[ + { + "target": "/sysroot", + "source": "/dev/vda4", + "maj:min": "253:4", + "fstype": "ext4", + "fsroot": "/", + "options": "rw,relatime,errors=remount-ro" + }, + { + "target": "/", + "source": "/dev/vda4", + "maj:min": "253:4", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/d590ffda3b7eebc41121fde0a05c10d5638c5a0facb51ec7437268399dfc51dd.0", + "options": "rw,relatime,errors=remount-ro" + }, + { + "target": "/usr", + "source": "/dev/vda4", + "maj:min": "253:4", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/d590ffda3b7eebc41121fde0a05c10d5638c5a0facb51ec7437268399dfc51dd.0/usr", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/var", + "source": "/dev/vda4", + "maj:min": "253:4", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/var", + "options": "rw,relatime,errors=remount-ro" + }, + { + "target": "/efi", + "source": "/dev/vda1", + "maj:min": "253:1", + "fstype": "vfat", + "fsroot": "/", + "options": "rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro" + }, + { + "target": "/boot", + "source": "/dev/vda3", + "maj:min": "253:3", + "fstype": "vfat", + "fsroot": "/", + "options": "rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro" + } +] diff --git a/tests/espgen/sdboot-xbootldr-partitions-init.json b/tests/espgen/sdboot-xbootldr-partitions-init.json new file mode 100644 index 0000000..dab31ec --- /dev/null +++ b/tests/espgen/sdboot-xbootldr-partitions-init.json @@ -0,0 +1,60 @@ +[ + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "B5E9-EA49", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "0189f938-7e9e-194d-8757-e85edaaca5b3", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "1024000", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "4c4f3323-e487-f145-9a01-2afa67956dc6", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "part_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "1026048", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda3", + "SEC_TYPE": "msdos", + "UUID": "3C18-31F6", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "c3c8d283-ec36-534c-be18-1b41377f3d40", + "PART_ENTRY_TYPE": "bc13c2ff-59e6-4262-a352-b275fd6f7172", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "1028096", + "PART_ENTRY_SIZE": "409600", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda4", + "LABEL": "ostree", + "UUID": "2bad2e5e-d97c-4c87-ae37-aa8ed2279c9a", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "b47e35b3-8d0d-7347-b2e1-6303cc628984", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "4", + "PART_ENTRY_OFFSET": "1028096", + "PART_ENTRY_SIZE": "499090063", + "PART_ENTRY_DISK": "253:0" + } +] diff --git a/tests/espgen/sdboot-xbootldr-partitions-reload.json b/tests/espgen/sdboot-xbootldr-partitions-reload.json new file mode 100644 index 0000000..746740c --- /dev/null +++ b/tests/espgen/sdboot-xbootldr-partitions-reload.json @@ -0,0 +1,67 @@ +[ + { + "DEVNAME": "/dev/vda1", + "SEC_TYPE": "msdos", + "UUID": "B5E9-EA49", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "0189f938-7e9e-194d-8757-e85edaaca5b3", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "1024000", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "4c4f3323-e487-f145-9a01-2afa67956dc6", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "part_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "1026048", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda3", + "SEC_TYPE": "msdos", + "UUID": "3C18-31F6", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "c3c8d283-ec36-534c-be18-1b41377f3d40", + "PART_ENTRY_TYPE": "bc13c2ff-59e6-4262-a352-b275fd6f7172", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "1028096", + "PART_ENTRY_SIZE": "409600", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/vda4", + "LABEL": "ostree", + "UUID": "2bad2e5e-d97c-4c87-ae37-aa8ed2279c9a", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "b47e35b3-8d0d-7347-b2e1-6303cc628984", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "4", + "PART_ENTRY_OFFSET": "1028096", + "PART_ENTRY_SIZE": "499090063", + "PART_ENTRY_DISK": "253:0" + }, + { + "DEVNAME": "/dev/zram0", + "UUID": "6965bcd9-5f8b-42d2-8c2e-12d0e8d22163", + "VERSION": "1", + "TYPE": "swap", + "USAGE": "other" + } +] diff --git a/tests/espgen/windows-fstab.json b/tests/espgen/windows-fstab.json new file mode 100644 index 0000000..a04ee1d --- /dev/null +++ b/tests/espgen/windows-fstab.json @@ -0,0 +1,10 @@ +[ + { + "target": "/", + "source": "/dev/loop0p3", + "maj:min": null, + "fstype": "ext4", + "fsroot": null, + "options": "errors=remount-ro" + } +] diff --git a/tests/espgen/windows-kcmdline.json b/tests/espgen/windows-kcmdline.json new file mode 100644 index 0000000..209dbd1 --- /dev/null +++ b/tests/espgen/windows-kcmdline.json @@ -0,0 +1,12 @@ +{ + "BOOT_IMAGE": "(loop_img,gpt3)/boot/ostree/eos-05199b9a534975d167eff9621ff44b04342dff52cb692b76ab3f2cd2f5d6bb5e/vmlinuz-6.5.0-10-generic", + "rw": null, + "splash": null, + "plymouth.ignore-serial-consoles": null, + "quiet": null, + "loglevel": "0", + "ostree": "/ostree/boot.1/eos/05199b9a534975d167eff9621ff44b04342dff52cb692b76ab3f2cd2f5d6bb5e/0", + "endless.image.device": "UUID=107EB4247EB4048E", + "endless.image.path": "/endless/endless.img", + "nohibernate": null +} diff --git a/tests/espgen/windows-mounts-init.json b/tests/espgen/windows-mounts-init.json new file mode 100644 index 0000000..fce9f5f --- /dev/null +++ b/tests/espgen/windows-mounts-init.json @@ -0,0 +1,34 @@ +[ + { + "target": "/", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/2b89dbb29d8a02e2fcc96b9812dcb996222f7b38d064e303c899bc04f845cbc3.0", + "options": "ro,relatime" + }, + { + "target": "/boot", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/boot", + "options": "ro,relatime" + }, + { + "target": "/usr", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/2b89dbb29d8a02e2fcc96b9812dcb996222f7b38d064e303c899bc04f845cbc3.0/usr", + "options": "ro,relatime" + }, + { + "target": "/sysroot", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/", + "options": "ro,relatime" + } +] diff --git a/tests/espgen/windows-mounts-reload.json b/tests/espgen/windows-mounts-reload.json new file mode 100644 index 0000000..01685a7 --- /dev/null +++ b/tests/espgen/windows-mounts-reload.json @@ -0,0 +1,50 @@ +[ + { + "target": "/", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/2b89dbb29d8a02e2fcc96b9812dcb996222f7b38d064e303c899bc04f845cbc3.0", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/boot", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/boot", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/usr", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/deploy/2b89dbb29d8a02e2fcc96b9812dcb996222f7b38d064e303c899bc04f845cbc3.0/usr", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/sysroot", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/var", + "source": "/dev/loop0p3", + "maj:min": "259:2", + "fstype": "ext4", + "fsroot": "/ostree/deploy/eos/var", + "options": "ro,relatime,errors=remount-ro" + }, + { + "target": "/efi", + "source": "/dev/sda1", + "maj:min": "8:1", + "fstype": "vfat", + "fsroot": "/", + "options": "rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro" + } +] diff --git a/tests/espgen/windows-partitions-init.json b/tests/espgen/windows-partitions-init.json new file mode 100644 index 0000000..82f8ca8 --- /dev/null +++ b/tests/espgen/windows-partitions-init.json @@ -0,0 +1,103 @@ +[ + { + "DEVNAME": "/dev/loop0p3", + "LABEL": "ostree", + "UUID": "4bdeb166-7be1-40d8-a73c-6555db153bf5", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "cae19154-af25-7b4c-972c-ad7e1bf16501", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "131072", + "PART_ENTRY_SIZE": "33933927", + "PART_ENTRY_DISK": "7:0" + }, + { + "DEVNAME": "/dev/loop0p1", + "SEC_TYPE": "msdos", + "UUID": "AE27-C340", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "f4293fff-3134-2a49-a106-3381175ea360", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "126976", + "PART_ENTRY_DISK": "7:0" + }, + { + "DEVNAME": "/dev/loop0p2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "68b1bc0f-1961-ac4f-81cc-1b22ba659ce0", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "129024", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "7:0" + }, + { + "DEVNAME": "/dev/sda4", + "BLOCK_SIZE": "512", + "UUID": "ECACABC7ACAB8AA2", + "TYPE": "ntfs", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "8236a774-8b07-41fc-b213-c426fe500760", + "PART_ENTRY_TYPE": "de94bba4-06d1-4d40-a16a-bfd50179d6ac", + "PART_ENTRY_FLAGS": "0x8000000000000001", + "PART_ENTRY_NUMBER": "4", + "PART_ENTRY_OFFSET": "103784448", + "PART_ENTRY_SIZE": "1069056", + "PART_ENTRY_DISK": "8:0" + }, + { + "DEVNAME": "/dev/sda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_NAME": "Microsoft reserved partition", + "PART_ENTRY_UUID": "db00106a-064f-46ff-8310-ad78b8830282", + "PART_ENTRY_TYPE": "e3c9e316-0b5c-4db8-817d-f92df00215ae", + "PART_ENTRY_FLAGS": "0x8000000000000000", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "206848", + "PART_ENTRY_SIZE": "32768", + "PART_ENTRY_DISK": "8:0" + }, + { + "DEVNAME": "/dev/sda3", + "BLOCK_SIZE": "512", + "UUID": "107EB4247EB4048E", + "TYPE": "ntfs", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_NAME": "Basic data partition", + "PART_ENTRY_UUID": "6a418dd1-ef6e-420e-8473-c2c081649374", + "PART_ENTRY_TYPE": "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "239616", + "PART_ENTRY_SIZE": "103543269", + "PART_ENTRY_DISK": "8:0" + }, + { + "DEVNAME": "/dev/sda1", + "UUID": "A8B2-3D19", + "VERSION": "FAT32", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_NAME": "EFI system partition", + "PART_ENTRY_UUID": "7e2d5124-6070-4167-8952-6cc10812caa0", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_FLAGS": "0x8000000000000000", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "204800", + "PART_ENTRY_DISK": "8:0" + } +] diff --git a/tests/espgen/windows-partitions-reload.json b/tests/espgen/windows-partitions-reload.json new file mode 100644 index 0000000..c1deb2a --- /dev/null +++ b/tests/espgen/windows-partitions-reload.json @@ -0,0 +1,110 @@ +[ + { + "DEVNAME": "/dev/loop0p3", + "LABEL": "ostree", + "UUID": "4bdeb166-7be1-40d8-a73c-6555db153bf5", + "VERSION": "1.0", + "BLOCK_SIZE": "4096", + "TYPE": "ext4", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "cae19154-af25-7b4c-972c-ad7e1bf16501", + "PART_ENTRY_TYPE": "4f68bce3-e8cd-4db1-96e7-fbcaf984b709", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "131072", + "PART_ENTRY_SIZE": "33933927", + "PART_ENTRY_DISK": "7:0" + }, + { + "DEVNAME": "/dev/loop0p1", + "SEC_TYPE": "msdos", + "UUID": "AE27-C340", + "VERSION": "FAT16", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "f4293fff-3134-2a49-a106-3381175ea360", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "126976", + "PART_ENTRY_DISK": "7:0" + }, + { + "DEVNAME": "/dev/loop0p2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "68b1bc0f-1961-ac4f-81cc-1b22ba659ce0", + "PART_ENTRY_TYPE": "21686148-6449-6e6f-744e-656564454649", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "129024", + "PART_ENTRY_SIZE": "2048", + "PART_ENTRY_DISK": "7:0" + }, + { + "DEVNAME": "/dev/sda4", + "BLOCK_SIZE": "512", + "UUID": "ECACABC7ACAB8AA2", + "TYPE": "ntfs", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_UUID": "8236a774-8b07-41fc-b213-c426fe500760", + "PART_ENTRY_TYPE": "de94bba4-06d1-4d40-a16a-bfd50179d6ac", + "PART_ENTRY_FLAGS": "0x8000000000000001", + "PART_ENTRY_NUMBER": "4", + "PART_ENTRY_OFFSET": "103784448", + "PART_ENTRY_SIZE": "1069056", + "PART_ENTRY_DISK": "8:0" + }, + { + "DEVNAME": "/dev/sda2", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_NAME": "Microsoft reserved partition", + "PART_ENTRY_UUID": "db00106a-064f-46ff-8310-ad78b8830282", + "PART_ENTRY_TYPE": "e3c9e316-0b5c-4db8-817d-f92df00215ae", + "PART_ENTRY_FLAGS": "0x8000000000000000", + "PART_ENTRY_NUMBER": "2", + "PART_ENTRY_OFFSET": "206848", + "PART_ENTRY_SIZE": "32768", + "PART_ENTRY_DISK": "8:0" + }, + { + "DEVNAME": "/dev/sda3", + "BLOCK_SIZE": "512", + "UUID": "107EB4247EB4048E", + "TYPE": "ntfs", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_NAME": "Basic data partition", + "PART_ENTRY_UUID": "6a418dd1-ef6e-420e-8473-c2c081649374", + "PART_ENTRY_TYPE": "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7", + "PART_ENTRY_NUMBER": "3", + "PART_ENTRY_OFFSET": "239616", + "PART_ENTRY_SIZE": "103543269", + "PART_ENTRY_DISK": "8:0" + }, + { + "DEVNAME": "/dev/sda1", + "UUID": "A8B2-3D19", + "VERSION": "FAT32", + "BLOCK_SIZE": "512", + "TYPE": "vfat", + "USAGE": "filesystem", + "PART_ENTRY_SCHEME": "gpt", + "PART_ENTRY_NAME": "EFI system partition", + "PART_ENTRY_UUID": "7e2d5124-6070-4167-8952-6cc10812caa0", + "PART_ENTRY_TYPE": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b", + "PART_ENTRY_FLAGS": "0x8000000000000000", + "PART_ENTRY_NUMBER": "1", + "PART_ENTRY_OFFSET": "2048", + "PART_ENTRY_SIZE": "204800", + "PART_ENTRY_DISK": "8:0" + }, + { + "DEVNAME": "/dev/zram0", + "UUID": "10c3a2ce-da4a-4173-bd6b-b4b2a913dcc6", + "VERSION": "1", + "TYPE": "swap", + "USAGE": "other" + } +] diff --git a/tests/test_esp_generator.py b/tests/test_esp_generator.py new file mode 100644 index 0000000..e7e244d --- /dev/null +++ b/tests/test_esp_generator.py @@ -0,0 +1,414 @@ +# Copyright © 2023 Endless OS Foundation, LLC +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +""" +Tests for eos-esp-generator +""" + +from contextlib import contextmanager +import json +import logging +import os +from pathlib import Path +import pytest +import sys +from textwrap import dedent + +from .util import import_script_as_module, TESTS_PATH + +espgen = import_script_as_module('espgen', 'eos-esp-generator') + +logger = logging.getLogger(__name__) +TESTSDATADIR = TESTS_PATH / 'espgen' + + +@pytest.fixture(autouse=True) +def generator_environment(monkeypatch): + """Generator environment fixture""" + monkeypatch.setenv('SYSTEMD_IN_INITRD', '0') + + +@pytest.fixture +def root_dir(tmp_path, monkeypatch): + """Temporary root directory""" + path = tmp_path / 'root' + monkeypatch.setenv('ESPGEN_ROOT_PATH', str(path)) + path.mkdir() + path.joinpath('boot').mkdir() + path.joinpath('efi').mkdir() + return path + + +@pytest.fixture +def unit_dir(root_dir): + """Temporary systemd generator unit directory""" + path = root_dir / 'run/systemd/generator' + path.mkdir(parents=True) + return path + + +@pytest.fixture +def mock_system_data(monkeypatch, root_dir): + """System data mocking factory fixture + + Provides a function that can be called with the desired mocked data. + This will override the generator calls that use findmnt and blkid. + """ + def _mock_system_data(system, reload=False): + phase = 'reload' if reload else 'init' + + mounts = TESTSDATADIR / f'{system}-mounts-{phase}.json' + with mounts.open() as f: + mounts_data = json.load(f) + logger.debug(f'mounts: {mounts_data}') + + def _fake_mount_data(root=Path('/')): + return mounts_data + + monkeypatch.setattr(espgen, 'get_mount_data', _fake_mount_data) + + fstab = TESTSDATADIR / f'{system}-fstab.json' + with fstab.open() as f: + fstab_data = json.load(f) + logger.debug(f'fstab: {fstab_data}') + + def _fake_fstab_data(): + return fstab_data + + monkeypatch.setattr(espgen, 'get_fstab_data', _fake_fstab_data) + + partitions = TESTSDATADIR / f'{system}-partitions-{phase}.json' + with partitions.open() as f: + partitions_data = json.load(f) + logger.debug(f'partitions: {partitions_data}') + + def _fake_partition_data(): + return partitions_data + + monkeypatch.setattr(espgen, 'get_partition_data', _fake_partition_data) + + kcmdline = TESTSDATADIR / f'{system}-kcmdline.json' + with kcmdline.open() as f: + kcmdline_data = json.load(f) + logger.debug(f'kcmdline: {kcmdline_data}') + + entries = [] + for name, value in kcmdline_data.items(): + if value is not None: + entry = f'{name}={value}' + else: + entry = name + entries.append(entry) + kcmdline_str = ' '.join(entries) + '\n' + kcmdline_path = root_dir / 'proc/cmdline' + kcmdline_path.parent.mkdir(parents=True, exist_ok=True) + with open(kcmdline_path, 'w') as f: + f.write(kcmdline_str) + + return _mock_system_data + + +@contextmanager +def loader_device_part_uuid(value, root): + """LoaderDevicePartUUID EFI variable context manager""" + varpath = root / 'sys/firmware/efi/efivars' / espgen.LOADER_DEVICE_PART_UUID_EFIVAR + varpath.parent.mkdir(parents=True, exist_ok=True) + + # Using the utf-16 codec for writing will add the byte order mark + # (BOM). Select the native endian version. + utf_16 = 'utf-16-le' if sys.byteorder == 'little' else 'utf-16-be' + + with open(varpath, 'wb') as f: + f.write(b'\0\0\0\0') + f.write(value.encode(utf_16)) + + # Add 3 nul byte terminators like systemd-boot does. + f.write(b'\0\0\0') + + try: + yield + finally: + varpath.unlink() + + +# Dictionary of mocked data to use with the test_get_esp_mount() test. The keys +# are names and the values are expected EspMount instances. +ESP_MOUNT_TEST_DATA = { + # Standard EOS using grub with GPT partitioning. /boot gets mounted in the + # initramfs by ostree-prepare-root. + 'grub-gpt': espgen.EspMount( + source='/dev/vda1', + target='/efi', + type='vfat', + umask='0077', + ), + + # Standard EOS using grub with MBR partitioning. /boot gets mounted in the + # initramfs by ostree-prepare-root. + 'grub-mbr': espgen.EspMount( + source='/dev/vda1', + target='/efi', + type='vfat', + umask='0077', + ), + + # Standard EOS using grub with GPT partitioning and /efi in fstab. /boot + # gets mounted in the initramfs by ostree-prepare-root. It's not typical + # that there would be a /efi entry in fstab, but this test ensures no /efi + # mount units are created. + 'grub-gpt-fstab-efi': None, + + # EOS using systemd-boot with GPT partitioning. This represents PAYG + # systemd. There is no /boot mounted by ostree-prepare-root since the root + # partition's /boot directory is empty. The ESP should be mounted at /boot + # world readable so that ostree deployments can be resolved unprivileged. + 'sdboot': espgen.EspMount( + source='/dev/vda1', + target='/boot', + type='vfat', + umask='0022', + ), + + # EOS using systemd-boot with GPT partitioning and an XBOOTLDR partition. + # This is not something we currently do or try to support since it would + # provide little benefit. Mostly this is here to ensure we don't add + # incorrect mounts if the situation starts happening. + 'sdboot-xbootldr': None, + + # Windows dual boot. The Endless disk (including an ESP partition) is a + # file on the NTFS partition that gets loop mounted. The real disk's ESP + # should be mounted instead of the loop disk's ESP partition. The real disk + # is found from the endless.image.device kernel command line parameter. + 'windows': espgen.EspMount( + source='/dev/sda1', + target='/efi', + type='vfat', + umask='0077', + ), +} + + +@pytest.mark.parametrize('system', sorted(ESP_MOUNT_TEST_DATA.keys())) +def test_get_esp_mount(system, root_dir, mock_system_data, monkeypatch): + """EspGenerator.get_esp_mount tests""" + expected_mount = ESP_MOUNT_TEST_DATA[system] + + # Running during init. + mock_system_data(system) + generator = espgen.EspGenerator() + esp_mount = generator.get_esp_mount() + assert esp_mount == expected_mount + + # If the generator is run in the initrd, it should do nothing. + with monkeypatch.context() as mctx: + mctx.setenv('SYSTEMD_IN_INITRD', '1') + generator = espgen.EspGenerator() + esp_mount = generator.get_esp_mount() + assert esp_mount is None + + # If the expected mount directory is /efi but there's no /efi + # directory, nothing should be mounted since there's already a mount + # on /boot. + root_dir.joinpath('efi').rmdir() + generator = espgen.EspGenerator() + esp_mount = generator.get_esp_mount() + if expected_mount is None or expected_mount.target == '/efi': + assert esp_mount is None + else: + assert esp_mount == expected_mount + root_dir.joinpath('efi').mkdir() + + expected_esp_part = {} + if expected_mount: + partitions = espgen.get_partition_data() + expected_esp_part = next( + filter(lambda p: p['DEVNAME'] == expected_mount.source, partitions), + ) + + # If the ESP disk is GPT partitioned, test the LoaderDevicePartUUID + # EFI variable handling. + if expected_esp_part.get('disk_label') == 'gpt': + # Set it to the expected value. + with loader_device_part_uuid(expected_esp_part['uuid'].upper(), root_dir): + generator = espgen.EspGenerator() + esp_mount = generator.get_esp_mount() + assert esp_mount == expected_mount + + # Set it to a different value. This should cause the mount to be + # skipped. + with loader_device_part_uuid('4859CD39-28DC-4C60-B509-C2A63A80483B', root_dir): + generator = espgen.EspGenerator() + esp_mount = generator.get_esp_mount() + assert esp_mount is None + + # Load the post-init mounts data to check that running during a + # reload still creates the mount unit. + mock_system_data(system, reload=True) + generator = espgen.EspGenerator() + esp_mount = generator.get_esp_mount() + assert esp_mount == expected_mount + + +def test_write_units(unit_dir): + """EspMount.write_units tests""" + esp_mount = espgen.EspMount( + source='/dev/vda1', + target='/efi', + type='vfat', + umask='0077', + ) + esp_mount.write_units(unit_dir) + automount_unit = unit_dir / 'efi.automount' + mount_unit = unit_dir / 'efi.mount' + local_fs_wants = unit_dir / 'local-fs.target.wants/efi.automount' + units = set() + for dirpath, dirnames, filenames in os.walk(unit_dir): + units.update([unit_dir / dirpath / f for f in filenames]) + assert units == {automount_unit, mount_unit, local_fs_wants} + assert not os.path.isabs(os.readlink(local_fs_wants)) + assert local_fs_wants.resolve() == automount_unit + + automount_contents = automount_unit.read_text() + assert automount_contents == dedent("""\ + # Automatically generated by eos-esp-generator + + [Unit] + Description=EFI System Partition Automount + + [Automount] + Where=/efi + TimeoutIdleSec=2min + """) + + mount_contents = mount_unit.read_text() + assert mount_contents == dedent("""\ + # Automatically generated by eos-esp-generator + + [Unit] + Description=EFI System Partition Automount + Requires=systemd-fsck@dev-vda1.service + After=systemd-fsck@dev-vda1.service + After=blockdev@dev-vda1.target + + [Mount] + What=/dev/vda1 + Where=/efi + Type=vfat + Options=umask=0077,noauto,rw + """) + + +def test_read_efivar(root_dir, caplog): + """read_efivar tests""" + var = 'Foo-7553c1d3-754b-47f3-a613-b9e2b860e8c1' + varpath = root_dir / 'sys/firmware/efi/efivars' / var + varpath.parent.mkdir(parents=True) + + # The first 32 bits are an attribute mask. Use all 1s so we can + # better detect it leaking into the value. + attr = b'\xff\xff\xff\xff' + + # Missing variable should return None. + varpath.unlink(missing_ok=True) + value = espgen.read_efivar(var) + assert value is None + + # Empty value. + with open(varpath, 'wb') as f: + f.write(attr) + value = espgen.read_efivar(var) + assert value == b'' + + # Actual contents. + with open(varpath, 'wb') as f: + f.write(attr) + f.write(b'hello') + value = espgen.read_efivar(var) + assert value == b'hello' + + # Invalid contents should log a warning and return None. + with open(varpath, 'wb') as f: + pass + caplog.clear() + with caplog.at_level(logging.WARNING): + value = espgen.read_efivar(var) + assert value is None + assert caplog.record_tuples == [( + espgen.logger.name, + logging.WARNING, + f'Invalid EFI variable {var} is less than 4 bytes', + )] + + +def test_efivar_utf16_string(root_dir): + """read_efivar_utf16_string tests""" + var = 'Bar-7553c1d3-754b-47f3-a613-b9e2b860e8c1' + varpath = root_dir / 'sys/firmware/efi/efivars' / var + varpath.parent.mkdir(parents=True) + + # The first 32 bits are an attribute mask. Use all 1s so we can + # better detect it leaking into the value. + attr = b'\xff\xff\xff\xff' + + # Using the utf-16 codec for writing will add the byte order mark + # (BOM). Select the native endian version. + utf_16 = 'utf-16-le' if sys.byteorder == 'little' else 'utf-16-be' + + # Missing variable should return None. + varpath.unlink(missing_ok=True) + value = espgen.read_efivar_utf16_string(var) + assert value is None + + # Empty value. + with open(varpath, 'wb') as f: + f.write(attr) + value = espgen.read_efivar_utf16_string(var) + assert value == '' + + # Actual contents. + with open(varpath, 'wb') as f: + f.write(attr) + f.write('hello'.encode(utf_16)) + value = espgen.read_efivar_utf16_string(var) + assert value == 'hello' + + # Only nul terminator. + with open(varpath, 'wb') as f: + f.write(attr) + f.write(b'\0\0') + value = espgen.read_efivar_utf16_string(var) + assert value == '' + + # Various nul byte terminators. + for n in range(6): + term = b'\0' * n + with open(varpath, 'wb') as f: + f.write(attr) + f.write('hello'.encode(utf_16)) + f.write(term) + value = espgen.read_efivar_utf16_string(var) + assert value == 'hello' + + # Unicode. + uface = '\N{UPSIDE-DOWN FACE}' # 🙃 + for term in (b'', b'\0\0'): + with open(varpath, 'wb') as f: + f.write(attr) + f.write(uface.encode(utf_16)) + f.write(term) + value = espgen.read_efivar_utf16_string(var) + assert value == uface