mirror of
https://github.com/systemd/systemd
synced 2024-10-15 12:34:37 +00:00
ukify: rework option parsing to support a config file
In some ways this is similar to mkosi: we have a argparse.ArgumentParser() with a bunch of options, and a configparser.ConfigParser() with an overlapping set of options. Many options are settable in both places, but not all. In mkosi, we define this in three places (a dataclass, and a function for argparse, and a function for configparser). Here, we have one huge list of ConfigItem instances. Each instance specifies the full metadata for both parsers. Argparse generates a --help string for all the options, and we also append a config file sample to --help based on the ConfigItem data: $ python src/ukify/ukify.py --help|tail -n 25 config file: [UKI] Linux = LINUX Initrd = INITRD… Cmdline = TEXT|@PATH OSRelease = TEXT|@PATH DeviceTree = PATH Splash = BMP PCRPKey = KEY Uname = VERSION EFIArch = ia32|x64|arm|aa64|riscv64 Stub = STUB PCRBanks = BANK… SigningEngine = ENGINE SecureBootPrivateKey = SB_KEY SecureBootCertificate = SB_CERT SignKernel = SIGN_KERNEL [PCRSignature:NAME] PCRPrivateKey = PATH PCRPublicKey = PATH Phases = PHASE-PATH… While writing this I needed to check the argument parsing, so I added a --summary switch. It just pretty-prints the resulting option dictionary: $ python src/ukify/ukify.py /efi//3a9d668b4db749398a4a5e78a03bffa5/6.2.11-300.fc38.x86_64/linux /efi//3a9d668b4db749398a4a5e78a03bffa5/6.2.11-300.fc38.x86_64/initrd --pcr-private-key=PRIV.key --pcr-public-key=PUB.key --config=man/ukify-example.conf --summary Host arch 'x86_64', EFI arch 'x64' {'_groups': [0, 'initrd', 'system'], 'cmdline': 'A1 B2 C3', 'config': 'man/ukify-example.conf', 'devicetree': None, 'efi_arch': 'x64', 'initrd': [PosixPath('initrd1'), PosixPath('initrd2'), PosixPath('initrd3'), PosixPath('/efi/3a9d668b4db749398a4a5e78a03bffa5/6.2.11-300.fc38.x86_64/initrd')], 'linux': PosixPath('/efi/3a9d668b4db749398a4a5e78a03bffa5/6.2.11-300.fc38.x86_64/linux'), 'measure': None, 'os_release': PosixPath('/etc/os-release'), 'output': 'linux.efi', 'pcr_banks': ['sha1', 'sha384'], 'pcr_private_keys': [PosixPath('PRIV.key'), PosixPath('pcr-private-initrd-key.pem'), PosixPath('pcr-private-system-key.pem')], 'pcr_public_keys': [PosixPath('PUB.key'), PosixPath('pcr-public-initrd-key.pem'), PosixPath('pcr-public-system-key.pem')], 'pcrpkey': None, 'phase_path_groups': [None, ['enter-initrd'], ['enter-initrd:leave-initrd', 'enter-initrd:leave-initrd:sysinit', 'enter-initrd:leave-initrd:sysinit:ready']], 'sb_cert': PosixPath('mkosi.secure-boot.crt'), 'sb_key': PosixPath('mkosi.secure-boot.key'), 'sections': [], 'sign_kernel': None, 'signing_engine': None, 'splash': None, 'stub': PosixPath('/usr/lib/systemd/boot/efi/linuxx64.efi.stub'), 'summary': True, 'tools': None, 'uname': None} With --summary, existence of input paths is not checked. I think we'll want to show them, instead of throwing an error, but in red, similarly to 'bootctl list'. This also fixes tests which were failing with e.g. E FileNotFoundError: [Errno 2] No such file or directory: '/ARG1' =========================== short test summary info ============================ FAILED ../src/ukify/test/test_ukify.py::test_parse_args_minimal - FileNotFoun... FAILED ../src/ukify/test/test_ukify.py::test_parse_args_many - FileNotFoundEr... FAILED ../src/ukify/test/test_ukify.py::test_parse_sections - FileNotFoundErr... =================== 3 failed, 10 passed, 3 skipped in 1.51s ====================
This commit is contained in:
parent
3f7e77fae1
commit
5143a47a81
|
@ -22,6 +22,7 @@
|
|||
# pylint: disable=too-many-branches,fixme
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import collections
|
||||
import dataclasses
|
||||
import fnmatch
|
||||
|
@ -29,10 +30,12 @@ import itertools
|
|||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import pprint
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import typing
|
||||
|
||||
|
@ -84,18 +87,6 @@ def shell_join(cmd):
|
|||
return ' '.join(shlex.quote(str(x)) for x in cmd)
|
||||
|
||||
|
||||
def path_is_readable(s: typing.Optional[str]) -> typing.Optional[pathlib.Path]:
|
||||
"""Convert a filename string to a Path and verify access."""
|
||||
if s is None:
|
||||
return None
|
||||
p = pathlib.Path(s)
|
||||
try:
|
||||
p.open().close()
|
||||
except IsADirectoryError:
|
||||
pass
|
||||
return p
|
||||
|
||||
|
||||
def round_up(x, blocksize=4096):
|
||||
return (x + blocksize - 1) // blocksize * blocksize
|
||||
|
||||
|
@ -337,11 +328,13 @@ def check_inputs(opts):
|
|||
if name in {'output', 'tools'}:
|
||||
continue
|
||||
|
||||
if not isinstance(value, pathlib.Path):
|
||||
continue
|
||||
|
||||
if isinstance(value, pathlib.Path):
|
||||
# Open file to check that we can read it, or generate an exception
|
||||
value.open().close()
|
||||
elif isinstance(value, list):
|
||||
for item in value:
|
||||
if isinstance(item, pathlib.Path):
|
||||
item.open().close()
|
||||
|
||||
check_splash(opts.splash)
|
||||
|
||||
|
@ -668,157 +661,412 @@ def make_uki(opts):
|
|||
print(f"Wrote {'signed' if opts.sb_key else 'unsigned'} {opts.output}")
|
||||
|
||||
|
||||
def parse_args(args=None):
|
||||
p = argparse.ArgumentParser(
|
||||
description='Build and sign Unified Kernel Images',
|
||||
allow_abbrev=False,
|
||||
usage='''\
|
||||
ukify [options…] [LINUX INITRD…]
|
||||
ukify -h | --help
|
||||
''')
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class ConfigItem:
|
||||
@staticmethod
|
||||
def config_list_prepend(namespace, group, dest, value):
|
||||
"Prepend value to namespace.<dest>"
|
||||
|
||||
# Suppress printing of usage synopsis on errors
|
||||
p.error = lambda message: p.exit(2, f'{p.prog}: error: {message}\n')
|
||||
assert not group
|
||||
|
||||
p.add_argument('linux',
|
||||
old = getattr(namespace, dest, [])
|
||||
setattr(namespace, dest, value + old)
|
||||
|
||||
@staticmethod
|
||||
def config_set_if_unset(namespace, group, dest, value):
|
||||
"Set namespace.<dest> to value only if it was None"
|
||||
|
||||
assert not group
|
||||
|
||||
if getattr(namespace, dest) is None:
|
||||
setattr(namespace, dest, value)
|
||||
|
||||
@staticmethod
|
||||
def config_set_group(namespace, group, dest, value):
|
||||
"Set namespace.<dest>[idx] to value, with idx derived from group"
|
||||
|
||||
if group not in namespace._groups:
|
||||
namespace._groups += [group]
|
||||
idx = namespace._groups.index(group)
|
||||
|
||||
old = getattr(namespace, dest, None)
|
||||
if old is None:
|
||||
old = []
|
||||
setattr(namespace, dest,
|
||||
old + ([None] * (idx - len(old))) + [value])
|
||||
|
||||
@staticmethod
|
||||
def parse_boolean(s: str) -> bool:
|
||||
"Parse 1/true/yes/y/t/on as true and 0/false/no/n/f/off/None as false"
|
||||
s_l = s.lower()
|
||||
if s_l in {'1', 'true', 'yes', 'y', 't', 'on'}:
|
||||
return True
|
||||
if s_l in {'0', 'false', 'no', 'n', 'f', 'off'}:
|
||||
return False
|
||||
raise ValueError('f"Invalid boolean literal: {s!r}')
|
||||
|
||||
# arguments for argparse.ArgumentParser.add_argument()
|
||||
name: typing.Union[str, typing.List[str]]
|
||||
dest: str = None
|
||||
metavar: str = None
|
||||
type: typing.Callable = None
|
||||
nargs: str = None
|
||||
action: typing.Callable = None
|
||||
default: typing.Any = None
|
||||
version: str = None
|
||||
choices: typing.List[str] = None
|
||||
help: str = None
|
||||
|
||||
# metadata for config file parsing
|
||||
config_key: str = None
|
||||
config_push: typing.Callable[..., ...] = config_set_if_unset
|
||||
|
||||
def _names(self) -> typing.Tuple[str]:
|
||||
return self.name if isinstance(self.name, tuple) else (self.name,)
|
||||
|
||||
def argparse_dest(self) -> str:
|
||||
# It'd be nice if argparse exported this, but I don't see that in the API
|
||||
if self.dest:
|
||||
return self.dest
|
||||
return self._names()[0].lstrip('-').replace('-', '_')
|
||||
|
||||
def add_to(self, parser: argparse.ArgumentParser):
|
||||
kwargs = { key:val
|
||||
for key in dataclasses.asdict(self)
|
||||
if (key not in ('name', 'config_key', 'config_push') and
|
||||
(val := getattr(self, key)) is not None) }
|
||||
args = self._names()
|
||||
parser.add_argument(*args, **kwargs)
|
||||
|
||||
def apply_config(self, namespace, section, group, key, value) -> None:
|
||||
assert f'{section}/{key}' == self.config_key
|
||||
dest = self.argparse_dest()
|
||||
|
||||
if self.action == argparse.BooleanOptionalAction:
|
||||
# We need to handle this case separately: the options are called
|
||||
# --foo and --no-foo, and no argument is parsed. But in the config
|
||||
# file, we have Foo=yes or Foo=no.
|
||||
conv = self.parse_boolean
|
||||
elif self.type:
|
||||
conv = self.type
|
||||
else:
|
||||
conv = lambda s:s
|
||||
|
||||
if self.nargs == '*':
|
||||
value = [conv(v) for v in value.split()]
|
||||
else:
|
||||
value = conv(value)
|
||||
|
||||
self.config_push(namespace, group, dest, value)
|
||||
|
||||
def config_example(self) -> typing.Tuple[typing.Optional[str]]:
|
||||
if not self.config_key:
|
||||
return None, None, None
|
||||
section_name, key = self.config_key.split('/', 1)
|
||||
if section_name.endswith(':'):
|
||||
section_name += 'NAME'
|
||||
if self.choices:
|
||||
value = '|'.join(self.choices)
|
||||
else:
|
||||
value = self.metavar or self.argparse_dest().upper()
|
||||
return (section_name, key, value)
|
||||
|
||||
|
||||
CONFIG_ITEMS = [
|
||||
ConfigItem(
|
||||
'--version',
|
||||
action = 'version',
|
||||
version = f'ukify {__version__}',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--summary',
|
||||
help = 'print parsed config and exit',
|
||||
action = 'store_true',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'linux',
|
||||
metavar = 'LINUX',
|
||||
type = pathlib.Path,
|
||||
nargs="?",
|
||||
help='vmlinuz file [.linux section]')
|
||||
p.add_argument('initrd',
|
||||
nargs = '?',
|
||||
help = 'vmlinuz file [.linux section]',
|
||||
config_key = 'UKI/Linux',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'initrd',
|
||||
metavar = 'INITRD…',
|
||||
type = pathlib.Path,
|
||||
nargs = '*',
|
||||
help='initrd files [.initrd section]')
|
||||
help = 'initrd files [.initrd section]',
|
||||
config_key = 'UKI/Initrd',
|
||||
config_push = ConfigItem.config_list_prepend,
|
||||
),
|
||||
|
||||
p.add_argument('--cmdline',
|
||||
ConfigItem(
|
||||
('--config', '-c'),
|
||||
metavar = 'PATH',
|
||||
help = 'configuration file',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--cmdline',
|
||||
metavar = 'TEXT|@PATH',
|
||||
help='kernel command line [.cmdline section]')
|
||||
help = 'kernel command line [.cmdline section]',
|
||||
config_key = 'UKI/Cmdline',
|
||||
),
|
||||
|
||||
p.add_argument('--os-release',
|
||||
ConfigItem(
|
||||
'--os-release',
|
||||
metavar = 'TEXT|@PATH',
|
||||
help='path to os-release file [.osrel section]')
|
||||
help = 'path to os-release file [.osrel section]',
|
||||
config_key = 'UKI/OSRelease',
|
||||
),
|
||||
|
||||
p.add_argument('--devicetree',
|
||||
ConfigItem(
|
||||
'--devicetree',
|
||||
metavar = 'PATH',
|
||||
type = pathlib.Path,
|
||||
help='Device Tree file [.dtb section]')
|
||||
p.add_argument('--splash',
|
||||
help = 'Device Tree file [.dtb section]',
|
||||
config_key = 'UKI/DeviceTree',
|
||||
),
|
||||
ConfigItem(
|
||||
'--splash',
|
||||
metavar = 'BMP',
|
||||
type = pathlib.Path,
|
||||
help='splash image bitmap file [.splash section]')
|
||||
p.add_argument('--pcrpkey',
|
||||
help = 'splash image bitmap file [.splash section]',
|
||||
config_key = 'UKI/Splash',
|
||||
),
|
||||
ConfigItem(
|
||||
'--pcrpkey',
|
||||
metavar = 'KEY',
|
||||
type = pathlib.Path,
|
||||
help='embedded public key to seal secrets to [.pcrpkey section]')
|
||||
p.add_argument('--uname',
|
||||
help = 'embedded public key to seal secrets to [.pcrpkey section]',
|
||||
config_key = 'UKI/PCRPKey',
|
||||
),
|
||||
ConfigItem(
|
||||
'--uname',
|
||||
metavar='VERSION',
|
||||
help='"uname -r" information [.uname section]')
|
||||
help='"uname -r" information [.uname section]',
|
||||
config_key = 'UKI/Uname',
|
||||
),
|
||||
|
||||
p.add_argument('--efi-arch',
|
||||
ConfigItem(
|
||||
'--efi-arch',
|
||||
metavar = 'ARCH',
|
||||
choices = ('ia32', 'x64', 'arm', 'aa64', 'riscv64'),
|
||||
help='target EFI architecture')
|
||||
help = 'target EFI architecture',
|
||||
config_key = 'UKI/EFIArch',
|
||||
),
|
||||
|
||||
p.add_argument('--stub',
|
||||
ConfigItem(
|
||||
'--stub',
|
||||
type = pathlib.Path,
|
||||
help='path to the sd-stub file [.text,.data,… sections]')
|
||||
help = 'path to the sd-stub file [.text,.data,… sections]',
|
||||
config_key = 'UKI/Stub',
|
||||
),
|
||||
|
||||
p.add_argument('--section',
|
||||
ConfigItem(
|
||||
'--section',
|
||||
dest = 'sections',
|
||||
metavar = 'NAME:TEXT|@PATH',
|
||||
type = Section.parse_arg,
|
||||
action = 'append',
|
||||
default = [],
|
||||
help='additional section as name and contents [NAME section]')
|
||||
help = 'additional section as name and contents [NAME section]',
|
||||
),
|
||||
|
||||
p.add_argument('--pcr-private-key',
|
||||
ConfigItem(
|
||||
'--pcr-banks',
|
||||
metavar = 'BANK…',
|
||||
type = parse_banks,
|
||||
config_key = 'UKI/PCRBanks',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--signing-engine',
|
||||
metavar = 'ENGINE',
|
||||
help = 'OpenSSL engine to use for signing',
|
||||
config_key = 'UKI/SigningEngine',
|
||||
),
|
||||
ConfigItem(
|
||||
'--secureboot-private-key',
|
||||
dest = 'sb_key',
|
||||
help = 'path to key file or engine-specific designation for SB signing',
|
||||
config_key = 'UKI/SecureBootPrivateKey',
|
||||
),
|
||||
ConfigItem(
|
||||
'--secureboot-certificate',
|
||||
dest = 'sb_cert',
|
||||
help = 'path to certificate file or engine-specific designation for SB signing',
|
||||
config_key = 'UKI/SecureBootCertificate',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--sign-kernel',
|
||||
action = argparse.BooleanOptionalAction,
|
||||
help = 'Sign the embedded kernel',
|
||||
config_key = 'UKI/SignKernel',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--pcr-private-key',
|
||||
dest = 'pcr_private_keys',
|
||||
metavar = 'PATH',
|
||||
type = pathlib.Path,
|
||||
action = 'append',
|
||||
help='private part of the keypair for signing PCR signatures')
|
||||
p.add_argument('--pcr-public-key',
|
||||
help = 'private part of the keypair for signing PCR signatures',
|
||||
config_key = 'PCRSignature:/PCRPrivateKey',
|
||||
config_push = ConfigItem.config_set_group,
|
||||
),
|
||||
ConfigItem(
|
||||
'--pcr-public-key',
|
||||
dest = 'pcr_public_keys',
|
||||
metavar = 'PATH',
|
||||
type = pathlib.Path,
|
||||
action = 'append',
|
||||
help='public part of the keypair for signing PCR signatures')
|
||||
p.add_argument('--phases',
|
||||
help = 'public part of the keypair for signing PCR signatures',
|
||||
config_key = 'PCRSignature:/PCRPublicKey',
|
||||
config_push = ConfigItem.config_set_group,
|
||||
),
|
||||
ConfigItem(
|
||||
'--phases',
|
||||
dest = 'phase_path_groups',
|
||||
metavar = 'PHASE-PATH…',
|
||||
type = parse_phase_paths,
|
||||
action = 'append',
|
||||
help='phase-paths to create signatures for')
|
||||
help = 'phase-paths to create signatures for',
|
||||
config_key = 'PCRSignature:/Phases',
|
||||
config_push = ConfigItem.config_set_group,
|
||||
),
|
||||
|
||||
p.add_argument('--pcr-banks',
|
||||
metavar='BANK…',
|
||||
type=parse_banks)
|
||||
|
||||
p.add_argument('--signing-engine',
|
||||
metavar='ENGINE',
|
||||
help='OpenSSL engine to use for signing')
|
||||
p.add_argument('--secureboot-private-key',
|
||||
dest='sb_key',
|
||||
help='path to key file or engine-specific designation for SB signing')
|
||||
p.add_argument('--secureboot-certificate',
|
||||
dest='sb_cert',
|
||||
help='path to certificate file or engine-specific designation for SB signing')
|
||||
|
||||
p.add_argument('--sign-kernel',
|
||||
action=argparse.BooleanOptionalAction,
|
||||
help='Sign the embedded kernel')
|
||||
|
||||
p.add_argument('--tools',
|
||||
ConfigItem(
|
||||
'--tools',
|
||||
type = pathlib.Path,
|
||||
action = 'append',
|
||||
help='Directories to search for tools (systemd-measure, ...)')
|
||||
help = 'Directories to search for tools (systemd-measure, …)',
|
||||
),
|
||||
|
||||
p.add_argument('--output', '-o',
|
||||
ConfigItem(
|
||||
('--output', '-o'),
|
||||
type = pathlib.Path,
|
||||
help='output file path')
|
||||
help = 'output file path',
|
||||
),
|
||||
|
||||
p.add_argument('--measure',
|
||||
ConfigItem(
|
||||
'--measure',
|
||||
action = argparse.BooleanOptionalAction,
|
||||
help='print systemd-measure output for the UKI')
|
||||
help = 'print systemd-measure output for the UKI',
|
||||
),
|
||||
]
|
||||
|
||||
p.add_argument('--version',
|
||||
action='version',
|
||||
version=f'ukify {__version__}')
|
||||
CONFIGFILE_ITEMS = { item.config_key:item
|
||||
for item in CONFIG_ITEMS
|
||||
if item.config_key }
|
||||
|
||||
opts = p.parse_args(args)
|
||||
|
||||
if opts.linux is not None:
|
||||
path_is_readable(opts.linux)
|
||||
for initrd in opts.initrd or ():
|
||||
path_is_readable(initrd)
|
||||
path_is_readable(opts.devicetree)
|
||||
path_is_readable(opts.pcrpkey)
|
||||
for key in opts.pcr_private_keys or ():
|
||||
path_is_readable(key)
|
||||
for key in opts.pcr_public_keys or ():
|
||||
path_is_readable(key)
|
||||
def apply_config(namespace, filename=None):
|
||||
if filename is None:
|
||||
filename = namespace.config
|
||||
if filename is None:
|
||||
return
|
||||
|
||||
# Fill in ._groups based on --pcr-public-key=, --pcr-private-key=, and --phases=.
|
||||
assert '_groups' not in namespace
|
||||
n_pcr_priv = len(namespace.pcr_private_keys or ())
|
||||
namespace._groups = list(range(n_pcr_priv))
|
||||
|
||||
cp = configparser.ConfigParser(
|
||||
comment_prefixes='#',
|
||||
inline_comment_prefixes='#',
|
||||
delimiters='=',
|
||||
empty_lines_in_values=False,
|
||||
interpolation=None,
|
||||
strict=False)
|
||||
# Do not make keys lowercase
|
||||
cp.optionxform = lambda option: option
|
||||
|
||||
cp.read(filename)
|
||||
|
||||
for section_name, section in cp.items():
|
||||
idx = section_name.find(':')
|
||||
if idx >= 0:
|
||||
section_name, group = section_name[:idx+1], section_name[idx+1:]
|
||||
if not section_name or not group:
|
||||
raise ValueError('Section name components cannot be empty')
|
||||
if ':' in group:
|
||||
raise ValueError('Section name cannot contain more than one ":"')
|
||||
else:
|
||||
group = None
|
||||
for key, value in section.items():
|
||||
if item := CONFIGFILE_ITEMS.get(f'{section_name}/{key}'):
|
||||
item.apply_config(namespace, section_name, group, key, value)
|
||||
else:
|
||||
print(f'Unknown config setting [{section_name}] {key}=')
|
||||
|
||||
|
||||
def config_example():
|
||||
prev_section = None
|
||||
for item in CONFIG_ITEMS:
|
||||
section, key, value = item.config_example()
|
||||
if section:
|
||||
if prev_section != section:
|
||||
if prev_section:
|
||||
yield ''
|
||||
yield f'[{section}]'
|
||||
prev_section = section
|
||||
yield f'{key} = {value}'
|
||||
|
||||
|
||||
def create_parser():
|
||||
p = argparse.ArgumentParser(
|
||||
description='Build and sign Unified Kernel Images',
|
||||
allow_abbrev=False,
|
||||
usage='''\
|
||||
ukify [options…] [LINUX INITRD…]
|
||||
''',
|
||||
epilog='\n '.join(('config file:', *config_example())),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
|
||||
for item in CONFIG_ITEMS:
|
||||
item.add_to(p)
|
||||
|
||||
# Suppress printing of usage synopsis on errors
|
||||
p.error = lambda message: p.exit(2, f'{p.prog}: error: {message}\n')
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def finalize_options(opts):
|
||||
if opts.cmdline and opts.cmdline.startswith('@'):
|
||||
opts.cmdline = path_is_readable(opts.cmdline[1:])
|
||||
opts.cmdline = pathlib.Path(opts.cmdline[1:])
|
||||
elif opts.cmdline:
|
||||
# Drop whitespace from the commandline. If we're reading from a file,
|
||||
# we copy the contents verbatim. But configuration specified on the commandline
|
||||
# or in the config file may contain additional whitespace that has no meaning.
|
||||
opts.cmdline = ' '.join(opts.cmdline.split())
|
||||
|
||||
if opts.os_release is not None and opts.os_release.startswith('@'):
|
||||
opts.os_release = path_is_readable(opts.os_release[1:])
|
||||
elif opts.os_release is None and opts.linux is not None:
|
||||
if opts.os_release and opts.os_release.startswith('@'):
|
||||
opts.os_release = pathlib.Path(opts.os_release[1:])
|
||||
elif not opts.os_release and opts.linux:
|
||||
p = pathlib.Path('/etc/os-release')
|
||||
if not p.exists():
|
||||
p = path_is_readable('/usr/lib/os-release')
|
||||
p = pathlib.Path('/usr/lib/os-release')
|
||||
opts.os_release = p
|
||||
|
||||
if opts.efi_arch is None:
|
||||
opts.efi_arch = guess_efi_arch()
|
||||
|
||||
if opts.stub is None:
|
||||
opts.stub = path_is_readable(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub')
|
||||
opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub')
|
||||
|
||||
if opts.signing_engine is None:
|
||||
opts.sb_key = path_is_readable(opts.sb_key) if opts.sb_key else None
|
||||
opts.sb_cert = path_is_readable(opts.sb_cert) if opts.sb_cert else None
|
||||
if opts.sb_key:
|
||||
opts.sb_key = pathlib.Path(opts.sb_key)
|
||||
if opts.sb_cert:
|
||||
opts.sb_cert = pathlib.Path(opts.sb_cert)
|
||||
|
||||
if bool(opts.sb_key) ^ bool(opts.sb_cert):
|
||||
raise ValueError('--secureboot-private-key= and --secureboot-certificate= must be specified together')
|
||||
|
@ -826,14 +1074,6 @@ ukify [options…] [LINUX INITRD…]
|
|||
if opts.sign_kernel and not opts.sb_key:
|
||||
raise ValueError('--sign-kernel requires --secureboot-private-key= and --secureboot-certificate= to be specified')
|
||||
|
||||
n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys)
|
||||
n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys)
|
||||
n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups)
|
||||
if n_pcr_pub is not None and n_pcr_pub != n_pcr_priv:
|
||||
raise ValueError('--pcr-public-key= specifications must match --pcr-private-key=')
|
||||
if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv:
|
||||
raise ValueError('--phases= specifications must match --pcr-private-key=')
|
||||
|
||||
if opts.output is None:
|
||||
if opts.linux is None:
|
||||
raise ValueError('--output= must be specified when building a PE addon')
|
||||
|
@ -843,6 +1083,30 @@ ukify [options…] [LINUX INITRD…]
|
|||
for section in opts.sections:
|
||||
section.check_name()
|
||||
|
||||
if opts.summary:
|
||||
# TODO: replace pprint() with some fancy formatting.
|
||||
pprint.pprint(vars(opts))
|
||||
sys.exit()
|
||||
|
||||
|
||||
def parse_args(args=None):
|
||||
p = create_parser()
|
||||
opts = p.parse_args(args)
|
||||
|
||||
# Check that --pcr-public-key=, --pcr-private-key=, and --phases=
|
||||
# have either the same number of arguments are are not specified at all.
|
||||
n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys)
|
||||
n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys)
|
||||
n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups)
|
||||
if n_pcr_pub is not None and n_pcr_pub != n_pcr_priv:
|
||||
raise ValueError('--pcr-public-key= specifications must match --pcr-private-key=')
|
||||
if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv:
|
||||
raise ValueError('--phases= specifications must match --pcr-private-key=')
|
||||
|
||||
apply_config(opts)
|
||||
|
||||
finalize_options(opts)
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue