File sdbootutil-1+git20250903.f5a076b.obscpio of Package sdbootutil

07070100000000000081A400000000000000000000000168B8435A00000026000000000000000000000000000000000000003000000000sdbootutil-1+git20250903.f5a076b/.dir-locals.el((sh-mode . ((sh-basic-offset . 8))))
07070100000001000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000002900000000sdbootutil-1+git20250903.f5a076b/.github07070100000002000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000003800000000sdbootutil-1+git20250903.f5a076b/.github/ISSUE_TEMPLATE07070100000003000081A400000000000000000000000168B8435A0000111C000000000000000000000000000000000000004700000000sdbootutil-1+git20250903.f5a076b/.github/ISSUE_TEMPLATE/bug_report.ymlname: "Bug report"
description: "Report a problem with sdbootutil/FDE on openSUSE"
labels: [bug]
title: "[bug]: <short summary>"
body:
  - type: checkboxes
    id: preflight
    attributes:
      label: Pre‑submission checklist
      options:
        - label: I searched existing issues and discussions to avoid duplicates
          required: true
        - label: I tested with the latest released packages or the current git (if applicable)
          required: true
        - label: I can reproduce this on a clean boot or after reboot
          required: false
  - type: input
    id: summary
    attributes:
      label: Short summary
      description: One line that captures the essence of the bug
      placeholder: e.g., "TPM2 unlock fails after kernel update when Secure Boot is enabled"
    validations:
      required: true
  - type: textarea
    id: observed
    attributes:
      label: Observed behavior
      description: What actually happens? Include exact error messages shown on screen or in logs.
      render: text
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: Expected behavior
      description: What should have happened instead?
      render: text
    validations:
      required: true
  - type: textarea
    id: reproduce
    attributes:
      label: Steps to reproduce
      description: Numbered, minimal steps we can follow to reproduce the issue.
      placeholder: |
        1. …
        2. …
        3. …
      render: markdown
    validations:
      required: true
  - type: dropdown
    id: product
    attributes:
      label: Product / distro
      options:
        - MicroOS
        - Tumbleweed
        - Leap
        - Other
    validations:
      required: true
  - type: dropdown
    id: architecture
    attributes:
      label: Architecture
      options:
        - x86_64
        - aarch64
        - s390x
        - ppc64le
        - Other
    validations:
      required: true
  - type: dropdown
    id: platform
    attributes:
      label: Platform
      options:
        - Bare metal
        - VM (QEMU/KVM)
        - VM (other)
    validations:
      required: true
  - type: dropdown
    id: bootloader
    attributes:
      label: Bootloader
      options:
        - systemd-boot
        - grub2-bls
        - Other/unknown
    validations:
      required: true
  - type: input
    id: versions
    attributes:
      label: sdbootutil version
      description: Output of `zypper info sdbootutil | grep Version`
      placeholder: sdbootutil 1+git20250804.8dccab3-1.1
    validations:
      required: false
  - type: textarea
    id: sysinfo
    attributes:
      label: System specs (paste output)
      description: |
        Paste the output of this one‑liner (run as root). It collects common diagnostics without requiring extra tools:

        ```sh
        printf '## os-release\n'; cat /etc/os-release; \
        printf '\n## uname\n'; uname -a; \
        printf '\n## bootctl status\n'; bootctl status --no-pager || true; \
        printf '\n## lsblk\n'; lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS; \
        printf '\n## systemd-detect-virt\n'; systemd-detect-virt || true; \
        printf '\n## sdbootutil version\n'; rpm -q sdbootutil || true
        ```
      render: shell
    validations:
      required: false
  - type: textarea
    id: debuglog
    attributes:
      label: Debug trace excerpts (sanitized)
      description: |
        If you enabled the **opt‑in debug trace** (create `/var/log/sdbootutil.log` and set permissions to 600 before running), please paste only the **relevant, sanitized excerpts** here.

        ⚠️ **Privacy warning:** trace lines may include sensitive data (e.g. passwords typed as command arguments). **You must review and redact** any secrets before sharing. If in doubt, **omit** the trace and instead describe what you observed.

        Tips:
        * Use `sed`/`grep` to keep only the relevant portion (e.g. around the failure time).
        * Replace secrets with placeholders like `<REDACTED>`.
      render: text
    validations:
      required: false
  - type: textarea
    id: extrainfo
    attributes:
      label: Additional context
      description: Any other logs (journal excerpts), config, or details that help diagnose the issue.
      render: text
    validations:
      required: false07070100000004000081A400000000000000000000000168B8435A00000762000000000000000000000000000000000000004C00000000sdbootutil-1+git20250903.f5a076b/.github/ISSUE_TEMPLATE/feature_request.ymlname: "Feature request"
description: "Suggest an enhancement or new capability"
labels: [enhancement]
title: "[feat]: <short description>"
body:
  - type: checkboxes
    id: preflight
    attributes:
      label: Pre‑submission checklist
      options:
        - label: I searched existing issues and discussions to avoid duplicates
          required: true
        - label: This is not a bug report
          required: true
  - type: input
    id: summary
    attributes:
      label: Short description
      placeholder: e.g., "Allow selecting FIDO2 during initial enrollment via CLI flag"
    validations:
      required: true
  - type: textarea
    id: problem
    attributes:
      label: Problem / use case
      description: What problem does this solve? Who benefits?
      render: text
    validations:
      required: true
  - type: textarea
    id: proposal
    attributes:
      label: Proposed solution / expected behavior
      description: Describe how you expect it to work (CLI flags, UI, workflow...).
      render: text
    validations:
      required: true
  - type: textarea
    id: alternatives
    attributes:
      label: Alternatives considered
      description: Other approaches you’ve tried or could work
      render: text
    validations:
      required: false
  - type: input
    id: scope
    attributes:
      label: Affected component(s)
      placeholder: e.g., sdbootutil, enrollment, predictions, installer, docs
    validations:
      required: false
  - type: input
    id: versions
    attributes:
      label: Version(s) you’re using
      placeholder: e.g., "sdbootutil 0.9.0 on Tumbleweed x86_64"
    validations:
      required: false
  - type: textarea
    id: extra
    attributes:
      label: Additional context
      description: Mockups, links, related issues, prior art, etc.
      render: text
    validations:
      required: false07070100000005000081ED00000000000000000000000168B8435A000007D9000000000000000000000000000000000000003700000000sdbootutil-1+git20250903.f5a076b/10-sdbootutil.snapper#!/bin/bash

shopt -s extglob nullglob
set -e

# Check whether it's a transactional system
is_transactional()
{
	findmnt --fstab / -O ro >/dev/null
}

# When creating a snapshot we fetch all bls configs from previous
# snapshot dir, mangle them to contain current snapshot number, then
# install to efi partition.
create_snapshot()
{
	path="$1"
	fs="$2"
	num="$3"

	[ "$fs" = btrfs ] || return 1

	/usr/bin/sdbootutil update --sync "$num" || :

	# If we are in a transactional system we abort here.  The
	# kernel entries are added later via set_default_snapshot,
	# when tukit set via snapper the new snapshot as complete
	is_transactional && return 0

	# The entries are added here only for Tumbleweed
	# (non-transactional systems)
	/usr/bin/sdbootutil add-all-kernels "$num" || :
	# In Tumblweed clean the default snapshot, not the new created
	/usr/bin/sdbootutil cleanup || :
}

delete_snapshot()
{
	local path="$1"
	local fs="$2"
	local num="$3"

	[ "$fs" = btrfs ] || return 1

	/usr/bin/sdbootutil remove-all-kernels "$num" || :
	/usr/bin/sdbootutil cleanup "$num" || :
}

set_default_snapshot()
{
	local path="$1"
	local fs="$2"
	local num="$3"

	[ "$fs" = btrfs ] || return 1

	if is_transactional; then
		# In transactional systems the boot entries are added
		# here, once the transaction is complete and tukit
		# decides to keep it
		/usr/bin/sdbootutil add-all-kernels "$num" || :

		if [ -e /usr/lib/module-init-tools/regenerate-initrd-posttrans ]; then
			/usr/lib/module-init-tools/regenerate-initrd-posttrans
		fi
	fi

	/usr/bin/sdbootutil set-default-snapshot "$num" || :
	/usr/bin/sdbootutil update --sync "$num" || :
}

h()
{
	echo "Available commands:"
	echo "${!commands[@]}"
}

declare -A commands

commands['create-snapshot-post']=create_snapshot
commands['delete-snapshot-pre']=delete_snapshot
commands['set-default-snapshot-post']=set_default_snapshot
commands['help']=h

cmd="$1"
shift
[ -n "$cmd" ] || cmd=help
if [ "${#commands[$cmd]}" -gt 0 ]; then
	${commands[$cmd]} "$@"
fi
07070100000006000081ED00000000000000000000000168B8435A000001B6000000000000000000000000000000000000003500000000sdbootutil-1+git20250903.f5a076b/10-sdbootutil.tukit#!/bin/bash

shopt -s extglob nullglob
set -e

callExt_post()
{
    path="$1"

    if [ -e "$path"/run/regenerate-initrd ]; then
	cp -a "$path"/run/regenerate-initrd /run
    fi
}

h()
{
    echo "Available commands:"
    echo "${!commands[@]}"
}

declare -A commands

commands['callExt-post']=callExt_post
commands['help']=h

cmd="$1"
shift
[ -n "$cmd" ] || cmd=help
if [ "${#commands[$cmd]}" -gt 0 ]; then
    ${commands[$cmd]} "$@"
fi
07070100000007000081ED00000000000000000000000168B8435A00000353000000000000000000000000000000000000003700000000sdbootutil-1+git20250903.f5a076b/50-sdbootutil.install#!/bin/bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2024 SUSE LLC

set -e

COMMAND="${1:?}"
KERNEL_VERSION="${2:?}"
# shellcheck disable=SC2034
ENTRY_DIR_ABS="${3:?}"
# shellcheck disable=SC2034
KERNEL_IMAGE="$4"

[ "$KERNEL_INSTALL_LAYOUT" = "bls" ] || exit 0

MACHINE_ID="${KERNEL_INSTALL_MACHINE_ID:?}"

args=()
[ "$KERNEL_INSTALL_VERBOSE" -lt 1 ] || args+=("-v")
args+=("--esp-path=$KERNEL_INSTALL_BOOT_ROOT" "--entry-token=$KERNEL_INSTALL_ENTRY_TOKEN")

case "$COMMAND" in
	remove)
		exec /usr/bin/sdbootutil "${args[@]}" remove-kernel "$KERNEL_VERSION"
		;;
	add)
		if [ "${KERNEL_IMAGE#/usr/lib/modules/$KERNEL_VERSION}" = "$KERNEL_IMAGE" ]; then
			echo "Unsupported kernel location $KERNEL_IMAGE" >&2
			exit 1
		fi
		exec /usr/bin/sdbootutil "${args[@]}" 'add-kernel' "$KERNEL_VERSION"
		;;
	*)
		exit 0
		;;
esac
07070100000008000081A400000000000000000000000168B8435A00002425000000000000000000000000000000000000003100000000sdbootutil-1+git20250903.f5a076b/ARCHITECTURE.mdsystemd implementation of the bootloader spec
---------------------------------------------

The kernel-install script shipped with systemd can install kernels in the EFI
partition according to the [bootloader
specification](https://uapi-group.org/specifications/specs/boot_loader_specification/).

Assuming two installed kernels, 1.2.3-1-default and 4.5.6-1-default
one would call

    kernel-install add /lib/modules/1.2.3-1-default/vmlinuz
    kernel-install add /lib/modules/3.4.5-1-default/vmlinuz

Scripts in `/usr/lib/kernel/install.d/` would copy the kernel into
the ESP, generate an initrd and create a config to boot the
specified kernel.

With an entry token (eg machine-id) of 2ceda9f (shortened for
readability) the ESP would look like this:

    ├── 2ceda9f
    │   ├── 1.2.3-1-default
    │   │   ├── initrd
    │   │   └── linux
    │   └── 4.5.6-1-default
    │       ├── initrd
    │       └── linux
    ├── EFI
    │   ├── BOOT
    │   │   └── BOOTX64.EFI
    │   └── systemd
    │       └── systemd-bootx64.efi
    └── loader
        └── entries
            ├── 2ceda9f-1.2.3-1-default.conf
            └── 2ceda9f-4.5.6-1-default.conf

Therefore an entry file might look like this:

    title      openSUSE Tumbleweed
    version    1.2.3-1-default
    machine-id 2ceda9f
    sort-key   opensuse-tumbleweed
    options    root=UUID=abc...
    linux      /2ceda9f/1.2.3-1-default/linux
    initrd     /2ceda9f/1.2.3-1-default/initrd


This scheme is basically designed so a specific instance of an
operating system (in the example 2ceda9f) can be booted with
different kernel versions. It is assumed that a kernel with a given
version is unique and each kernel has one specific initrd. Removing
a kernel (ie kernel-install remove 1.2.3-1-default.conf) would
remove the kernel, initrd and entry file. The root filesystem can
be specified as option or discovered automatically according to the
[discoverable partition specification](https://uapi-group.org/specifications/specs/discoverable_partitions_specification/).


Introducing snapshots
---------------------

With btrfs snapshots, the same root filesystem holds multiple
generations of the operating system. It's necessary to add a
`rootflags=` parameter to the options to tell the kernel which
subvolume to mount. Absence of the parameter would boot the default
subvolume.

So when creating a snapshot, snapper hook scripts could copy the
existing entry files, adding parameters to boot the new snapshot. So
for example if snapshot 15 was created the files would look like
this:

    └── loader
        └── entries
            ├── 2ceda9f-1.2.3-1-default.conf
            ├── 2ceda9f-4.5.6-1-default.conf
            ├── 2ceda9f-1.2.3-1-default-15.conf
            └── 2ceda9f-4.5.6-1-default-15.conf

Since the ESP is FAT, a separator like @ can't be used to add the
snapshot number to the file name.

An entry file might now look like this:

    title      openSUSE Tumbleweed
    version    15@1.2.3-1-default
    machine-id 2ceda9f
    sort-key   opensuse-tumbleweed
    options    root=UUID=abc... rootflags=subvol=@/.snapshots/15/snapshot
    linux      /2ceda9f/1.2.3-1-default/linux
    initrd     /2ceda9f/1.2.3-1-default/initrd

In this example the snapshot number is prepended to the version.
This seems to be the best way right now to get the sorting right.

At this point the entry refers to the same kernel and initrd as used
in the running system. The system may install a different kernel
with same uname or recreate the initrd any time though. That would
potentially break booting the snapshot. So a snapshot entry must
also point to a matching kernel and initrd. FAT has no copy-on-write
features, so somehow those files need to be made unique in a
different way. It would be possible to encode the snapshot number
into the file name but then every snapshot would have it's own copy
of kernel and initrd, even when they could be shared. So a better
option is to use the file's checksum instead.

So that makes an entry look like this

    title      openSUSE Tumbleweed
    version    15@1.2.3-1-default
    machine-id 2ceda9f
    sort-key   opensuse-tumbleweed
    options    root=UUID=abc... rootflags=subvol=@/.snapshots/15/snapshot
    linux      /2ceda9f/1.2.3-1-default/linux-b021b508eb42b2afd06de8f0242b9727aa7dc494
    initrd     /2ceda9f/1.2.3-1-default/initrd-7b200fad3d005285ca914069a4740a5b6874c0ae

To avoid wasting any space, the default entries should also use
checkums and never store plain "linux" and "initrd" files.

With this scheme `kernel-install remove` would no longer work
though. It must especially not delete the kernel version directory
(eg 1.2.3-1-default) as it may contain files needed by other snapshots.
Therefore a `bootctl unlink` and `bootctl cleanup` features were
imlemented (https://github.com/systemd/systemd/pull/26103) to use
reference counting when deleting entries.

Default boot entry
------------------
So far the btrfs default subvolume flag is used to select which
subvolume to boot in absence of boot options. Now this means each
time the default subvolume is changed (ie rollback), the
systemd-boot entries that do not refer to a subvolume need to be
changed to ones that select the correct kernel and initrd combo.

Systemd-boot itself has a mechanism to set a default entry. So
setting the btrfs default subvolume alone actually has no effect.
Therefore snapper also needs to set the systemd-boot default when
changing the default subvolume.

Now creating a snapshot would create an entry with snapshot number
embedded anyway. Therefore the extra step to also create an entry
without snapshot number is not necessary. Instead all entries could
just have the subvolume setting. Selecting the default would only be
done based on the entry, not on the default subvolume. This would
also be the more natural way for MicroOS where no read-write
subvolume exists.

So the ESP would look like this:

    ├── 2ceda9f
    │   ├── 1.2.3-1-default
    │   │   ├── initrd-6272c61615fc18d5158aadda22fb0955c98382c8
    │   │   └── linux-dcebae96db3cab7ffeb8883e78423a99d31d2bfd
    │   └── 4.5.6-1-default
    │       ├── initrd-7b200fad3d005285ca914069a4740a5b6874c0ae
    │       ├── initrd-1b6b609e33104d68bd543a11a0b8a4d354478f46
    │       └── linux-b021b508eb42b2afd06de8f0242b9727aa7dc494
    ├── EFI
    │   ├── BOOT
    │   │   └── BOOTX64.EFI
    │   └── systemd
    │       └── systemd-bootx64.efi
    └── loader
        └── entries
            ├── 2ceda9f-1.2.3-1-default-1.conf
            ├── 2ceda9f-4.5.6-1-default-1.conf
            ├── 2ceda9f-1.2.3-1-default-15.conf
            └── 2ceda9f-4.5.6-1-default-15.conf

Distribution integration
------------------------

The scheme described in this document is implemented by
[sdbootutil](https://github.com/lnussel/sdbootutil). Similar to
`kernel-install` it allows to add and remove kernels to/from the
ESP but is fully snapshot aware. To speed up snapshot creation,
`sdbootutil` also tries to be smart about the initrd and reuses
existing ones from the parent snapshot if possible.
That also means initrds are not updated based on user space
changes at this point. Only kernel changes trigger initrd creation.

`sdbootutil` is called from a [snapper
plugin](https://github.com/lnussel/sdbootutil/blob/main/10-sdbootutil.snapper)
to manage entires on snapshot creation and removal, as well as when
the default subvolume is set.

Kernel package installation and removal also trigger calls to
`sdbootutil` via [file
triggers](https://github.com/lnussel/sdbootutil/blob/main/kernelhooks.lua).
Unfortunately the file trigger method seems to be unreliable with zypper.
Moreover, file triggers can't make an rpm transaction fail
(https://github.com/rpm-software-management/rpm/issues/2581).

Secure boot support
------------------------

Upstream `bootctl` has no support for shim yet
(https://github.com/systemd/systemd/issues/27234). That's required
for secure boot support though. Therefore `sdbootutil` also
implements an `install` feature.

If `/usr/share/efi/$(uname -m)` exists, shim and related files get
installed into the ESP

    └── EFI
        ├── boot
        │   ├── BOOTX64.EFI    <-- shim
        │   ├── MokManager.efi
        │   └── fallback.efi
        └─── systemd
             ├── MokManager.efi
             ├── boot.csv
             ├── grub.efi      <-- actually systemd-boot
             └── shim.efi

Future plans
------------

In general sdbootutil should not exist. It's features should be
implemented in `bootctl`, `kernel-install` or `snapper`.

To simplify handling of the initrd, it would be desirable to switch
to e.g. UKIs that are built on server side. That way no guessing
would be needed as to whether an initrd needs to be updated.
07070100000009000081A400000000000000000000000168B8435A0000042E000000000000000000000000000000000000002900000000sdbootutil-1+git20250903.f5a076b/LICENSEMIT License

Copyright (c) 2023 Ludwig Nussel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
0707010000000A000081A400000000000000000000000168B8435A000000E4000000000000000000000000000000000000002B00000000sdbootutil-1+git20250903.f5a076b/README.mdTool to manage systemd-boot and grub2-bls in a btrfs based, snapper
managed system.

Can install systemd-boot with shim as well as install kernels into
the ESP.

A non-interactive mode can be called from scriptlets or triggers.
0707010000000B000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000002D00000000sdbootutil-1+git20250903.f5a076b/completions0707010000000C000081ED00000000000000000000000168B8435A000009E7000000000000000000000000000000000000003D00000000sdbootutil-1+git20250903.f5a076b/completions/bash_sdbootutil# shellcheck disable=SC2148
shopt -s nullglob

# shellcheck disable=SC2148
err()
{
	:
}

_none()
{
	declare -ga result
	result=()
}

_find_kernels()
{
	local fn kv
	declare -ga result
	result=()

	for fn in /usr/lib/modules/*/"$image"; do
		kv="${fn%/*}"
		kv="${kv##*/}"
		result+=("$kv")
	done
}

_find_entry_ids()
{
	declare -ga result
	result=()

	mapfile -t result < <(bootctl list --json=short | jq -r '.[].id')
}

_arch_name()
{
	declare -ga result
	result=("${!arch_image_map[@]}")
}

_image_name()
{
	declare -ga result
	result=()
	result=("${!arch_image_map[@]}")
}

_method()
{
	declare -ga result
	result=()
	for i in "tpm2" "tpm2+pin" "fido2" "password" "recovery-key"; do
		result+=("$i")
	done
}

_devices()
{
	declare -ga result
	result=()
	mapfile -t result < <(sdbootutil list-devices)
}

_sdbootutil_completion()
{
	eval "$(sdbootutil _print_bash_completion_data)"
	local cur prev words cword image
	COMPREPLY=()
	_get_comp_words_by_ref -n : cur prev words cword

	local command_found=0
	eval_bootctl
	set_image_name
	define_commands

	define_options
	# shellcheck disable=SC2154
	IFS=, read -r -a opts <<< "$opts_long"
	local opts_with_dashes=()
	for opt in "${opts[@]}"; do
		opts_with_dashes+=("--${opt//:/}")
	done

	local i=0

	for word in "${words[@]:1:$cword-1}"; do
		if [ -n "${commands["$word"]+yes}" ]; then
			command_found=1
		fi
		if [ " --arch " == " $word " ]; then
			# shellcheck disable=SC2034
			firmware_arch="${words[i+2]}"
			set_image_name
		fi
		if [ " --image " == " $word " ]; then
			image="${words[i+2]}"
		fi
		((i++))
	done

	# shellcheck disable=SC2154
	local opt_arg_fun="${options_with_arg[${prev#--}]}"
	if [ "${commands["$prev"]}" == "kernel" ]; then
		_find_kernels
		# shellcheck disable=SC2207
		COMPREPLY=( $(compgen -W "${result[*]}" -- "$cur") )
	elif [ "${commands["$prev"]}" == "id" ]; then
		_find_entry_ids
		# shellcheck disable=SC2207
		COMPREPLY=( $(compgen -W "${result[*]}" -- "$cur") )
	elif [ "$opt_arg_fun" == "_path" ]; then
		# shellcheck disable=SC2207
		COMPREPLY=($(compgen -d -- "$cur"))
	elif [ "$opt_arg_fun" ]; then
		eval "$opt_arg_fun"
		# shellcheck disable=SC2207
		COMPREPLY=( $(compgen -W "${result[*]}" -- "$cur") )
	elif [[ $cur == -* ]] || [ $command_found -eq 1 ]; then
		# shellcheck disable=SC2207
		COMPREPLY=( $(compgen -W "${opts_with_dashes[*]}" -- "$cur") )
	elif [ $command_found -eq 0 ]; then
		# shellcheck disable=SC2207
		COMPREPLY=( $(compgen -W "${!commands[*]}" -- "$cur") )
	fi
}

complete -F _sdbootutil_completion sdbootutil
0707010000000D000081A400000000000000000000000168B8435A00001850000000000000000000000000000000000000003700000000sdbootutil-1+git20250903.f5a076b/jeos-firstboot-enroll#!/bin/bash

# shellcheck disable=SC2034
enroll_title=$"Full Disk Encryption Enrollment"
# shellcheck disable=SC2034
enroll_description=$"Enroll a device using TPM2 or FIDO2 key"

crypt_keyid=""
crypt_pw=""
crypt_tpm_pin=""

with_fido2=
with_tpm2=
with_recovery_key=

luks2_devices=()


have_luks2() {
	lsblk --noheadings -o FSTYPE | grep -q crypto_LUKS
}

# exit early without defining any helper functions if there are no luks devices
have_luks2 || return 0

enroll_systemd_firstboot() {
	[ -e /usr/bin/systemd-cryptenroll ] || return 0
	crypt_keyid="$(keyctl id %user:cryptenroll 2> /dev/null)" || return 0
	[ -n "$crypt_keyid" ] || return 0

	welcome_screen_with_console_switch

	local has_fido2=${JEOS_HAS_FIDO2:-}
	local has_tpm2=

	[ -z "$(systemd-cryptenroll --fido2-device=list 2>/dev/null)" ] || has_fido2=1
	[ ! -e '/sys/class/tpm/tpm0' ] || has_tpm2=lock

	while true; do
		local list=()

		if [ -z "$with_recovery_key" ]; then
			list+=('recovery-key' $'Enroll recovery key')
		fi
		if [ -z "$with_fido2" ] && [ -z "$with_tpm2" ] && [ -n "$has_fido2" ]; then
			list+=('FIDO2' $'Enroll FIDO2 token')
		fi
		if [ -z "$with_tpm2" ] && [ -z "$with_fido2" ] && [ -n "$has_tpm2" ]; then
			list+=('TPM2' $'Enroll TPM2 based token' 'TPM2_interactive' 'Enroll TPM2 based token with PIN')
		fi
		if [ -z "$crypt_pw" ]; then
			if [ -n "$password" ]; then
				list+=('root' $'Enroll root password')
			fi
			list+=('password' $'Enroll extra password')
		fi
		[ -n "$list" ] || break

		list+=('done' $'Done')

		d --no-tags --default-item "${list[0]}" --menu $"Disk Encryption" 0 0 "$(menuheight ${#list[@]})" "${list[@]}"
		if [ "$result" = 'done' ]; then
			if [ -z "$with_recovery_key" ] && [ -z "$crypt_pw" ] && [ -z "$with_fido2" ] && [ -z "$with_tpm2" ] && [ -z "$is_jeos_config" ]; then
				d_styled --msgbox $"Can not continue without selecting an enrollment" 5 52
				continue
			fi
			break;
		elif [ "$result" = 'FIDO2' ]; then
			with_fido2=1
		elif [ "$result" = 'TPM2' ]; then
			with_tpm2="$has_tpm2"
		elif [ "$result" = 'TPM2_interactive' ]; then
			while true; do
				d --insecure --passwordbox  $"Enter new PIN (actually just passphrase)" 0 0
				if [ -z "$result" ]; then
					d_styled --yesno $"Retry?" 0 0 || break
					continue
				fi
				crypt_tpm_pin="$result"
				d --insecure --passwordbox  $"Confirm PIN" 0 0
				[ "$crypt_tpm_pin" != "$result" ] || { with_tpm2="$has_tpm2"; break; }
				d --msgbox $"PINs don't match. Try again" 0 0
			done
		elif [ "$result" = 'recovery-key' ]; then
			with_recovery_key=1
		elif [ "$result" = 'root' ]; then
			crypt_pw="$password"
		elif [ "$result" = 'password' ]; then
			while true; do
				d --insecure --passwordbox  $"Enter encryption password" 0 0
				if [ -z "$result" ]; then
					d --aspect 29 --msgbox $"No encryption password set. You can add more keys manually using systemd-cryptenroll." 0 0
					break
				fi
				crypt_pw="$result"
				d --insecure --passwordbox  $"Confirm encryption password" 0 0
				[ "$crypt_pw" != "$result" ] || break
				d --msgbox $"Passwords don't match. Try again" 0 0
			done
		else
			d --msgbox "Error: $result" 0 0
		fi
	done

	return 0
}

write_issue_file() {
	local recovery_key="$1"
	local issuefile="/run/issue.d/90-recovery-key.issue"

	[ -x '/usr/sbin/issue-generator' ] && issuefile="/run/issue.d/90-recovery-key.conf"
	mkdir -p "/run/issue.d"
	echo "$recovery_key" > "$issuefile"
	[ -x '/usr/sbin/issue-generator' ] && run issue-generator
}

enroll_post() {
	[ -e /usr/bin/systemd-cryptenroll ] || return 0
	[ -n "$crypt_keyid" ] || return 0

	do_enroll
}

do_enroll() {
	local out r error=0
	[ -z "$with_recovery_key" ] || {
		# Note that if --no-reuse-initrd is used, then a new
		# initrd will be created and will break the
		# measurement of the initial components if later the
		# TPM2 enrollment is called
		extra=
		if [ -z "$with_tpm2" ] && [ -z "$with_fido2" ]; then
			extra="--no-reuse-initrd"
		fi
		d --infobox "Enrolling recovery-key ..." 3 40
		out="$(run sdbootutil enroll --method=recovery-key "$extra" 2>&1)"
		r="$?"
		if [ $r -ne 0 ]; then
			d --msgbox "Error (recovery-key): $out" 0 0
			error=1
		else
			write_issue_file "$out"
		fi
	}

	[ -z "$crypt_pw" ] || {
		# Note that if --no-reuse-initrd is used, then a new
		# initrd will be created and will break the
		# measurement of the initial components if later the
		# TPM2 enrollment is called
		extra=
		if [ -z "$with_tpm2" ] && [ -z "$with_fido2" ]; then
			extra="--no-reuse-initrd"
		fi
		d --infobox "Enrolling password ..." 3 40
		out="$(PW="$crypt_pw" run sdbootutil enroll --method=password "$extra" 2>&1)"
		r="$?"
		[ $r -eq 0 ] || {
			d --msgbox "Error (password): $out" 0 0
			error=1
		}
	}

	if [ -n "$with_tpm2" ]; then
		if [ -n "$crypt_tpm_pin" ]; then
			d --infobox "Enrolling TPM2+PIN ..." 3 40
			out="$(SDB_ADD_INITIAL_COMPONENT=1 PIN="$crypt_tpm_pin" run sdbootutil enroll --method=tpm2+pin 2>&1)"
			r="$?"
			[ $r -eq 0 ] || {
				d --msgbox "Error (TPM2+PIN): $out" 0 0
				error=1
			}

		else
			d --infobox "Enrolling TPM2 ..." 3 40
			out="$(SDB_ADD_INITIAL_COMPONENT=1 run sdbootutil enroll --method=tpm2 2>&1)"
			r="$?"
			[ $r -eq 0 ] || {
				d --msgbox "Error (TPM2): $out" 0 0
				error=1
			}
		fi
	fi

	[ -z "$with_fido2" ] || {
		d --infobox "Enrolling FIDO2 ..." 3 40
		out="$(run sdbootutil enroll --method=fido2 2>&1)"
		r="$?"
		[ $r -eq 0 ] || {
			d --msgbox "Error (FIDO2): $out" 0 0
			error=1
		}
	}

	if [ "$error" -eq 1 ]; then
		d --msgbox "One or more enrollment methods failed. Resolve the issue and re-try with 'jeos-config enroll'" 0 0
	else
		# Clean the enrollment key.  disk-encryption-tool
		# creates it in the keyslot 0 with the name
		# "enrollment-key", that is showed by
		# systemd-cryptenroll as "other"
		local slots
		while read -r dev; do
			slots=$(systemd-cryptenroll "$dev")
			if grep -q "other" <<<"$slots"; then
				systemd-cryptenroll --wipe-slot=0 "$dev"
			fi
		done < <(sdbootutil list-devices)
	fi
}

enroll_jeos_config() {
	is_jeos_config=1
	d --insecure --passwordbox  $"Enter decryption password" 0 0
	[ -n "$result" ] || return 0
	echo -n "$result" | keyctl padd user cryptenroll @u &> /dev/null

	enroll_systemd_firstboot
	do_enroll
}
0707010000000E000081A400000000000000000000000168B8435A0000001D000000000000000000000000000000000000004500000000sdbootutil-1+git20250903.f5a076b/jeos-firstboot-enroll-override.conf[Service]
KeyringMode=shared
0707010000000F000081A400000000000000000000000168B8435A00000150000000000000000000000000000000000000004000000000sdbootutil-1+git20250903.f5a076b/kernel-install-sdbootutil.conf# disable all kernel-install scripts that are known incompatible
# with sdbootutil.
L /run/kernel/install.d/50-depmod.install - - - - /dev/null
L /run/kernel/install.d/50-dracut.install - - - - /dev/null
L /run/kernel/install.d/51-dracut-rescue.install - - - - /dev/null
L /run/kernel/install.d/90-loaderentry.install - - - - /dev/null
07070100000010000081A400000000000000000000000168B8435A00000743000000000000000000000000000000000000003100000000sdbootutil-1+git20250903.f5a076b/kernelhooks.luakernelhooks = { debug = nil } -- package

kernelhooks.found = {}
kernelhooks.legacy = {} -- entries in /boot

kernelhooks.imagename = "vmlinuz" -- FIXME: arch dependent!

kernelhooks.kernelpattern = string.format("^/usr/lib/modules/(.*)/%s", kernelhooks.imagename)
-- beware of crazy lua patterns, - is similar to * and % is escape character
kernelhooks.legacypattern = string.format("^/boot/%s%%-(.+)", kernelhooks.imagename)

-- for testing outside rpm
if posix == nil then
	posix = require("posix")
end

function _log(msg)
	if kernelhooks.debug then
		print(kernelhooks.debug .. ": " .. msg)
	end
end

function rootpath(path)
	_, _, p = string.find(path, "^/usr(/.*)")
	return p
end

function dirname(path)
	_, _, d, b = string.find(path, "^(.*)/([^/]*)")
	return d
end

function kernelhooks.filter(path)
	_, _, kver = string.find(path, kernelhooks.kernelpattern)
	if kver then
		_log("found kernel " .. kver)
		kernelhooks.found[kver] = path
		return
	end
	_, _, kver = string.find(path, kernelhooks.legacypattern)
	if kver then
		_log("kernel " .. kver .. " in /boot")
		kernelhooks.legacy[kver] = 1
		return
	end
end

function kernelhooks.add()
	for kver in pairs(kernelhooks.found) do
		if kernelhooks.legacy[kver] then
			_log("not adding " .. kver .. " due to legacy /boot location")
		else
			_log("adding " .. kver)
			rpm.execute("/usr/sbin/depmod", "-a", kver)
			rpm.execute("/usr/bin/sdbootutil", "add-kernel", kver)
			rpm.execute("/usr/bin/sdbootutil", "set-default-snapshot")
		end
	end
end

function kernelhooks.remove()
	for kver in pairs(kernelhooks.found) do
		if kernelhooks.legacy[kver] then
			_log("not removing " .. kver .. " due to legacy /boot location")
		else
			_log("removing " .. kver)
			rpm.execute("/usr/bin/sdbootutil", "remove-kernel", kver)
			rpm.execute("/usr/bin/sdbootutil", "set-default-snapshot")
		end
	end
end

07070100000011000081A400000000000000000000000168B8435A0000071F000000000000000000000000000000000000003A00000000sdbootutil-1+git20250903.f5a076b/measure-pcr-generator.sh#!/bin/bash
set -euo pipefail

# For a generator, the first parameter `normal-dir` is not optional
[ -n "$1" ] || { echo "Missing normal-dir parameter"; exit 1; }

[ -f "/etc/crypttab" ] || exit 0;

# Read /etc/crypttab lines that contains tpm2-device and
# tpm2-measure-pcr.  It will order the services as listed in this file
after=""
while read -r name _ _ opts; do
	# Only the entries in /etc/crypttab in the initrd should
	# participate from the extension for now.  The reason is that
	# extensions after the switch root cannot participate in abort
	# the boot process from initrd itself
	[ -f "/etc/initrd-release" ] || continue
	[[ "$name" = \#* ]] && continue
	[[ "$opts" != *"tpm2-device="* ]] && continue
	[[ "$opts" != *"tpm2-measure-pcr="* ]] && continue
	mkdir -p "$1/systemd-cryptsetup@$name.service.d"
	cat > "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf" <<-EOF
	# Automatically generated by measure-pcr-generator

	[Service]
	Environment="SYSTEMD_FORCE_MEASURE=yes"
	EOF
	if [ -n "$after" ]; then
		cat >> "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf" <<-EOF

		[Unit]
		After=systemd-cryptsetup@$after.service
		EOF
	fi
	after="$name"
done < /etc/crypttab

# Do a similar loop for devices that can be unlocked by FIDO2 keys
after=""
while read -r name _ _ opts; do
	[[ "$name" = \#* ]] && continue
	[[ "$opts" != *"fido2-device="* ]] && continue
	mkdir -p "$1/systemd-cryptsetup@$name.service.d"
	[ -f "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf" ] || {
		echo "# Automatically generated by measure-pcr-generator" > "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf"
	}
	if [ -n "$after" ]; then
		cat >> "$1/systemd-cryptsetup@$name.service.d/measure-pcr.conf" <<-EOF

		[Unit]
		After=systemd-cryptsetup@$after.service
		EOF
	fi
	after="$name"
done < /etc/crypttab
07070100000012000081A400000000000000000000000168B8435A000001FA000000000000000000000000000000000000003F00000000sdbootutil-1+git20250903.f5a076b/measure-pcr-validator.service[Unit]
Description=Validate LUKS2 devices
DefaultDependencies=false

FailureAction=poweroff-immediate

Wants=cryptsetup.target
After=cryptsetup.target
Before=initrd-root-device.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/measure-pcr-validator
Environment=TERM=linux
ExecStopPost=/bin/sh -c "/usr/bin/plymouth quit 2>/dev/null || :"
StandardOutput=tty
StandardInput=tty

[Install]
# If we use RequiredBy we trigger its OnFailure=emergency.target
WantedBy=initrd-root-device.target
07070100000013000081A400000000000000000000000168B8435A000008EF000000000000000000000000000000000000003A00000000sdbootutil-1+git20250903.f5a076b/measure-pcr-validator.sh#!/bin/bash
set -euo pipefail

WHITE="\e[1;37m"
LIGHT_BLUE="\e[1;34m"
END="\e[m"

measure_pcr_crypttab() {
	grep -q "tpm2-measure-pcr=yes" /etc/crypttab
}

get_measure_pcr_ignore() {
	(set +eu; . /lib/dracut-lib.sh; getargbool no measure-pcr-validator.ignore)
}

validate_measure_pcr_signature() {
	openssl dgst -sha256 \
		-verify /var/lib/sdbootutil/measure-pcr-public.pem \
		-signature /var/lib/sdbootutil/measure-pcr-prediction.sha256 \
		/var/lib/sdbootutil/measure-pcr-prediction &> /dev/null
}

validate_measure_pcr() {
	if [ ! -e "/sys/class/tpm/tpm0" ]; then
		echo "Error: TPM2 not found in /sys/class/tpm/tpm0"
		return 1
	fi

	local res=1
	for sha in sha1 sha256 sha384 sha512; do
		[ -e "/sys/class/tpm/tpm0/pcr-$sha/15" ] || continue
		read -r expected_pcr_15 < "/sys/class/tpm/tpm0/pcr-$sha/15"
		grep -Fixq "$expected_pcr_15" /var/lib/sdbootutil/measure-pcr-prediction; res="$?"
		break
	done

	return "$res"
}

exit_with_msg() {
	local msg="$1"

	if ! measure_pcr_crypttab; then
		echo "INFO: No PCR 15 validation"

		exit 0
	elif get_measure_pcr_ignore; then
		echo "WARNING: The validation of PCR 15 failed"
		echo "WARNING: $msg"

		exit 0
	else
		echo "ERROR: the validation of PCR 15 failed"

		kill -SIGRTMIN+21 1
		sleep 1
		echo -ne '\n\n\a'
		echo -e "${WHITE}*********************************************************************${END}"
		echo -e "${WHITE}ERROR: $msg${END}"
		echo -e "${WHITE}Use${END} '${LIGHT_BLUE}measure-pcr-validator.ignore=yes${END}' ${WHITE}in cmdline to bypass the check${END}"
		echo -e "${WHITE}*********************************************************************${END}"
		echo
		read -n1 -s -r -t 10 -p $'\e[1;37m*** The system will be halted. Press any key ...\e[0m' || true
		echo
		kill -SIGRTMIN+20 1

		exit 1
	fi
}

[ -f /etc/crypttab ] || exit 0
grep -q 'tpm2-measure-pcr=yes' /etc/crypttab || exit 0
[ -f "/var/lib/sdbootutil/measure-pcr-prediction" ] || exit_with_msg "Missing measure-pcr-prediction file"
[ -f "/var/lib/sdbootutil/measure-pcr-prediction.sha256" ] || exit_with_msg "Missing measure-pcr-prediction.sha256 signature file"
validate_measure_pcr_signature || exit_with_msg "Signature for the prediction file is not valid"
validate_measure_pcr || exit_with_msg "PCR 15 mismatch. Encrypted devices compromised"
07070100000014000081A400000000000000000000000168B8435A000002B7000000000000000000000000000000000000003100000000sdbootutil-1+git20250903.f5a076b/module-setup.sh#!/bin/bash

# Prerequisite check(s) for module.
check() {
    # Return 255 to only include the module, if another module
    # requires it.
    return 0
}

install() {
    inst_multiple grep openssl
    inst_script "$moddir/measure-pcr-generator.sh" "/usr/lib/systemd/system-generators/measure-pcr-generator"
    inst_script "$moddir/measure-pcr-validator.sh" "/usr/bin/measure-pcr-validator"
    inst_simple "$moddir/measure-pcr-validator.service" "$systemdsystemunitdir/measure-pcr-validator.service"
    [ -f "/var/lib/sdbootutil/measure-pcr-public.pem" ] && inst_simple "/var/lib/sdbootutil/measure-pcr-public.pem"
    $SYSTEMCTL -q --root "$initdir" enable measure-pcr-validator.service
}
07070100000015000081ED00000000000000000000000168B8435A0001B38E000000000000000000000000000000000000002C00000000sdbootutil-1+git20250903.f5a076b/sdbootutil#!/bin/bash
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: Copyright 2023 SUSE LLC
set -e
shopt -s nullglob

DEBUG_LOG="/var/log/sdbootutil.log"

if [ -f "$DEBUG_LOG" ]; then
	exec 3>>"$DEBUG_LOG"
	export BASH_XTRACEFD=3
	export PS4='+ \D{%F %T} ${BASH_SOURCE##*/}:${LINENO}:${FUNCNAME[0]:-main}: '
	set -x
fi

verbose=
nl=$'\n'
shimdir="/usr/share/efi/$(uname -m)"
arg_esp_path="$SYSTEMD_ESP_PATH"
arg_entry_token=
arg_arch=
arg_all_entries=
arg_entry_keys=()
arg_no_variables=
arg_no_reuse_initrd=
arg_no_random_seed=
arg_portable=
arg_secure_boot=
arg_sync=
arg_only_default=
arg_default_snapshot=
arg_ask_key_pin_or_pw=
arg_method=
arg_signed_policy=
arg_no_measure_pcr=
arg_measure_pcr=
arg_pcr=
have_snapshots=
# for x in vmlinuz image vmlinux linux bzImage uImage Image zImage; do
image=
unlock_method=

chroot_dir=

color_red=
color_end=
# The shell is interactive and the colors haven't been disabled
# forcefully or the shell is not connected to a terminal but the
# colors have been forcefully enabled
if { [ -t 1 ] \
    && [ "$SYSTEMD_COLORS" != "false" ] && [ "$SYSTEMD_COLORS" != "0" ]; } \
    || { [ ! -t 1 ] \
    && [ "$SYSTEMD_COLORS" = "true" ] || [ "$SYSTEMD_COLORS" = "1" ]; }; then
	color_red="\e[31m"
	color_green="\e[32m"
	color_yellow="\e[33m"
	color_bu="\e[1;4m" # bold underscore
	color_end="\e[m"
fi

# State file for transactional systems
state_file="/var/lib/misc/transactional-update.state"

update_predictions=

tracked_devices=()

rollback=()

declare -g -A eventlog

tmpdir=$(mktemp -d -t sdbootutil.XXXXXX)
cleanup()
{
	local i
	for i in "${rollback[@]}"; do
		if [ -e "$i.bak" ]; then
			info "Restoring $i"
			mv "$i.bak" "$i"
		else
			info "Removing $i"
			rm -f "$i"
		fi
	done
	dbg "Cleaning temporary directory $tmpdir"
	rm -rf "$tmpdir"

	[ -z "$chroot_dir" ] || umount_chroot "$chroot_dir"
}
trap cleanup EXIT

entryfile="$tmpdir/entries.json"
initialentryfile="$tmpdir/initial_entries.json"
snapperfile="$tmpdir/snapper.json"
tmpfile="$tmpdir/tmp"

helpandquit()
{
	# Tabs are removed from the start of the line.  Use spaces to
	# indent
	cat <<-EOF
		Usage: $0 [OPTIONS] [COMMAND]
		OPTIONS:
		  --esp-path		Manually specify path to ESP
		  --arch		Manually set architecture
		  --entry-token		Override entry token
		  --image		Specify Linux kernel file name
		  --all			List all entries (inc. from other systems)
		  --entry-keys		Comma separated list of keys
		  --no-variables	Do not update UEFI variables
		  --no-reuse-initrd	Always regenerate initrd
		  --sync		Synchronize (update, downgrade) the bootloader
		  --portable		Handle bootloader on portable devices
		                        (also --removable possible)
		  --secure-boot		When installing the bootloader, use the shim
		  --only-default	Only list the default entry
		  --default-snapshot	[SNAPSHOT] refers to the default snapshot
		  --ask-key		Ask recovery Key when initial enrollment
		                        (or randomly generated)
		  --ask-pin		Ask recovery PIN for re-enrollment
		                        Ask TPM2 PIN when initial enrollment
		  --ask-pw		Ask password when initial enrollment
		  --method		"tpm2", "tpm2+pin", "fido2", "password", "recovery-key"
		  --signed-policy	Use signed policy for TPM2 enrollment
		  --no-measure-pcr	During enrollment, do not include PCR 15 protection
		  --measure-pcr		Force update of PCR 15 prediction for LUKS2 volume key
		                        Requires LUKS2 password to accessing the volume key
		  --pcr			Comma seperated list of PCRs to enroll
		  --devices		Comma separated list of devices to enroll or unenroll
		                        By default all (not ignored) devices are [un]enrolled
		  -v, --verbose		More verbose output
		  -h, --help		This screen

		COMMAND:
		bootloader [SNAPSHOT]
		           Print the detected bootloader

		add-kernel VERSION [SNAPSHOT]
		           Create boot entry for specified kernel

		add-all-kernels [SNAPSHOT]
		           Create boot entries for all kernels in SNAPSHOT

		mkinitrd [SNAPSHOT]
		           Create boot entries for all kernels in SNAPSHOT,
		           assumes --no-reuse-initrd to regenerate initrds

		remove-kernel VERSION [SNAPSHOT]
		           Remove boot entry for specified kernel

		remove-all-kernels [SNAPSHOT]
		           Remove boot entries for all kernels in SNAPSHOT

		cleanup [SNAPSHOT]
		           Remove boot entries with missing kernels from SNAPSHOT

		list-kernels [SNAPSHOT]
		           List all kernels related to SNAPSHOT

		list-entries [SNAPSHOT]
		           List all entries related to SNAPSHOT

		list-snapshots
		           List all snapshots

		list-devices
		           List encrypted devices that are tracked

		show-entry VERSION [SNAPSHOT]
		           Show fields for an entry with an specified kernel
		           version

		update-entry VERSION [SNAPSHOT]
		           Update "options" field from /etc/kernel/cmdline
		           for an entry

		update-all-entries [SNAPSHOT]
		           Update "options" field from /etc/kernel/cmdline
		           for all entries

		set-default-snapshot [SNAPSHOT]
		           Make SNAPSHOT the default for next boot.
		           Also install all kernels if needed

		is-bootable [SNAPSHOT]
		           Check whether SNAPSHOT has any kernels registered, ie
		           is potentially bootable

		install
		           Install the bootloader and shim into ESP

		needs-update
		           Check whether the bootloader in ESP needs updating

		update
		           Update the bootloader in the ESP if a newer version
		           is available. Passing the --sync option will also
		           allow downgrades, ensuring that the version in the ESP
		           matches the one installed in the system.

		force-update
		           Update the bootloader in any case

		set-default ID
		           Set default boot loader entry

		get-default
		           Get default boot loader entry

		set-timeout SECONDS|menu-disabled|menu-hidden|menu-force
		           Set the menu timeout
		           menu-disabled or menu-hidden=0; menu-force=-1

		get-timeout
		           Get the menu timeout in seconds

		enroll
		           Enroll a TPM2 (+PIN), a FIDO2 key or a password for
		           all devices

		unenroll
		           Unenroll a TPM2 (+PIN), a FIDO2 key or a password for
		           all devices

		update-predictions
		           Update TPM2 predictions

		Variables:
		SYSTEMD_COLORS	Set 0 to disable colored output
		KEY		Recovery key (initial enrollment; %u:sdbootutil)
		PIN		TPM2 PIN (initial enrollment; %u:sdbootutil)
		PIN		Recovery PIN (re-enrollment; %u:sdbootutil)
		PW		Password / Recovery Key (initial enrollment; %u:sdbootutil)
		                (%u:cryptenroll for changes via systemd-cryptenroll)

		Misc:
		Ignoring Devices	A LUKS2 device can be un-tracked (ignored)
		                        by sdbootutil if is present in /etc/crypttab
		                        and has the "x-sdbootutil.ignore" option

	EOF
	exit 0
}

dbg()
{
	[ "${verbose:-0}" -gt 1 ] || return 0
	echo "DEBUG: $*" >&2
}

dbg_var()
{
	[ "${verbose:-0}" -gt 1 ] || return 0
	local v="${1:?}"
	echo "DEBUG: $v: ${!v}" >&2
}

dbg_cat()
{
	[ "${verbose:-0}" -gt 1 ] || return 0
	[ ! -e "$1" ] || { echo "DEBUG: $1" >&2; cat "$1" >&2; }
}

info()
{
	[ "${verbose:-0}" -gt 0 ] || return 0
	echo "$@" >&2
}

warn()
{
	echo "WARNING: $*" >&2
}

err()
{
	echo "ERROR: $*" >&2
	exit 1
}

is_config_file()
{
	[ -e /usr/etc/default/fde-tools ] ||
		[ -e /etc/default/fde-tools ] ||
		[ -e /etc/sysconfig/fde-tools ]
}

load_config_file()
{
	local f
	# Some old installations have /etc/sysconfig/fde-tools
	for f in /usr/etc/default/fde-tools /etc/default/fde-tools /etc/sysconfig/fde-tools; do
		[ ! -e "$f" ] || {
			# shellcheck disable=SC1090
			. "$f"
			info "Loading config file $f"
			dbg_cat "$f"
		}
	done
}

is_secure_boot()
{
	grep -q $'\x01' /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
}

is_sdboot()
{
	local sdboot grub2_bls

	sdboot="$(find_sdboot "${1-$root_snapshot}")"
	grub2_bls="$(find_grub2_bls "${1-$root_snapshot}")"

	# If boot loader is not found, then we check LOADER_TYPE, but
	# if is not present and systemd-boot and grub2-bls are
	# co-installed, we favor grub2-bls in the detection
	if [ ! -e "$sdboot" ] && [ ! -e "$grub2_bls" ]; then
		[ -z "$LOADER_TYPE" ] || [ "$LOADER_TYPE" = "systemd-boot" ]
	else
		[ -e "$sdboot" ] && [ ! -e "$grub2_bls" ]
	fi
}

is_grub2_bls()
{
	local sdboot grub2_bls

	sdboot="$(find_sdboot "${1-$root_snapshot}")"
	grub2_bls="$(find_grub2_bls "${1-$root_snapshot}")"

	# If boot loader is not found, then we check LOADER_TYPE, but
	# if is not present and systemd-boot and grub2-bls are
	# co-installed, we favor grub2-bls in the detection
	if [ ! -e "$sdboot" ] && [ ! -e "$grub2_bls" ]; then
		[ -z "$LOADER_TYPE" ] || [ "$LOADER_TYPE" = "grub2-bls" ]
	else
		[ -e "$grub2_bls" ]
	fi
}

reset_rollback()
{
	for i in "${rollback[@]}"; do
		[ -e "$i.bak" ] || continue
		info "Removing $i.bak"
		rm -f "$i.bak"
	done
	rollback=()
}

is_transactional()
{
	findmnt --fstab / -O ro >/dev/null
}

keyctl_add_with_timeout()
{
	local key="$1"
	local value="$2"

	info "Creating key $key with timeout"
	# When we create a new key with sudo, we do not create a new
	# login session, so the key is not accessible to the owner
	# See https://mjg59.dreamwidth.org/37333.html
	local keyid
	keyid="$(echo -n "$value" | keyctl padd user "$key" @s)"
	# Remove permission from the possesor.  For some reason that I
	# do not understand, this can fail in YaST when called via
	# cheetah
	keyctl setperm "$keyid" 0x003f0000 2> /dev/null || {
		warn "Failed to change the timeout for $key"
		return 0
	}
	keyctl timeout "$keyid" 120
	keyctl link "$keyid" @u
	# systemd tools are doing `keyctl request user "$key"`, and
	# for some reason this means that the key must still be in the
	# session keyring, so we cannot do `keyctl unlink "$keyid" @s`
}

ask_password()
{
	local msg="$1"
	local -n nameref_pw="$2"
	read -r -s -p "$msg: " nameref_pw
	echo >&2
}

ask_new_password()
{
	local msg="$1"
	local -n nameref_pw="$2"
	local pw1 pw2
	ask_password "New $msg" pw1
	ask_password "Re-type $msg" pw2
	while [ "$pw1" != "$pw2" ]; do
		warn "Inputs did't match!"
		ask_password "New $msg" pw1
		ask_password "Re-type $msg" pw2
	done
	# shellcheck disable=SC2034
	nameref_pw="$pw1"
}

subvol_is_ro()
{
	[ -n "$have_snapshots" ] || return 0
	local subvol="${1:?}"

	while read -r line; do
		[ "$line" = "ro=true" ] && return 0
	done < <(btrfs prop get -t s "${subvol#"${subvol_prefix}"}" ro)
	return 1
}

detect_parent()
{
	local subvol="$1"
	[ -n "$have_snapshots" ] || return 0

	local parent_uuid
	parent_uuid="$(btrfs subvol show "${subvol#"${subvol_prefix}"}" | sed -ne 's/\s*Parent UUID:\s*//p')"
	[ "$parent_uuid" != '-' ] || return 0

	local -a parent_subvol_uuid
	# shellcheck disable=SC2207
	parent_subvol_uuid=($(btrfs subvol show -u "$parent_uuid" "${subvol#"${subvol_prefix}"}" 2> /dev/null))
	local btrfs_subvol_status=$?
	[ "$btrfs_subvol_status" = 0 ] || return 0
	parent_subvol="${parent_subvol_uuid[0]}"

	parent_snapshot="${parent_subvol#"${subvol_prefix}"/.snapshots/}"
	if [ "$parent_subvol" = "$parent_snapshot" ]; then
		unset parent_subvol parent_snapshot
	else
		parent_snapshot="${parent_snapshot%/snapshot}"
	fi

	dbg_var "parent_subvol"
	dbg_var "parent_snapshot"
}

sedrootflags()
{
	local subvol="$1"
	# - Delete everything before BOOT_IMAGE= and initrd=
	#   (see https://github.com/openSUSE/sdbootutil/issues/182)
	# - Delete BOOT_IMAGE= and initrd=
	# - Replace or add root= to refers to UUID or mapped device
	#   (if encrypted)
	# - Replace or add rootflags to point at correct subvolume
	# - Replace or add systemd.machine-id to match current
	#   machine-id
	#
	# From the sed manual:
	# ‘t’
	#     branch conditionally (that is: jump to a label) _only
	#     if_ a ‘s///’ command has succeeded since the last input
	#     line was read or another conditional branch was taken.
	#
	# We use the t command to jump over an expression that appends
	# a parameter if replacing the parameter succeeded (ie it was
	# already there). Since we always operate on the same line,
	# "empty" t jumps are used to reset the condition after very
	# s///.
	local root_param="UUID=$root_uuid"
	[ -z "$root_device_is_crypt" ] || root_param="$root_device"
	local sed_arguments=("-e s/[ \t]\+/ /g"
		"-e s/^.*\(initrd=[^ ]*\|BOOT_IMAGE=[^ ]*\)\s*/\1 /"
		"-e s/\<\(BOOT_IMAGE\|initrd\)=[^ ]* \?//"
		"-e s/\$//;ta;:a"
		"-e s,\<root=[^ ]*,root=$root_param,;tb;s,\$, root=$root_param,;tc;:c;:b")
	[ -z "$have_snapshots" ] || sed_arguments+=("-e s,\<rootflags=subvol=[^ ]*,rootflags=subvol=$subvol,;td;s,\$, rootflags=subvol=$subvol,;te;:e;:d")
	[ -z "$machine_id" ] || sed_arguments+=("-e s,\<systemd.machine_id=[^ ]*,systemd.machine_id=$machine_id,;tf;s,\$, systemd.machine_id=$machine_id,;tg;:g;:f")
	sed "${sed_arguments[@]}"
}

entry_filter=("cat")
update_entries()
{
	[ -z "$1" ] || entry_filter=("$@")
	bootctl list --json=short | "${entry_filter[@]}" > "$entryfile"
	dbg "Entry filter: ${entry_filter[*]}"
	dbg_cat "$entryfile"
}

update_entries_for_subvol()
{
	local subvol="$1"
	local ext="${2:-}"

	[ -z "$ext" ] || ext="|$ext"
	update_entries jq "[.[]|select(has(\"options\"))|select(.options|test(\"root=(?:UUID=$root_uuid|$root_device) .*rootflags=subvol=$subvol\")$ext)]"
}

update_entries_for_snapshot()
{
	local n="$1"
	update_entries_for_subvol "${subvol_prefix}/.snapshots/$n/snapshot"
}

update_entries_for_snapshot_invert()
{
	local n="$1"
	update_entries_for_subvol "${subvol_prefix}/.snapshots/$n/snapshot" "not"
}

update_entries_for_this_system()
{
	update_entries jq "[.[]|select(has(\"options\"))|select(.options|test(\"root=(?:UUID=$root_uuid|$root_device)\"))]"
}

entry_conf_file()
{
	local kernel_version="${1:?}"
	local snapshot="$2"
	local tries="$3"

	# GRUB2 with the BLS patches does not follow the expected
	# ordering rules, using only rpmvercmp() with the entry
	# filename.  To provide an order we add a prefix ("system")
	# for entries that are not RO, making it newer that any other
	# entry name that start with the "snapper" prefix: ("system" >
	# "snapper").
	local prefix=""
	local subvol=""
	[ -z "$have_snapshots" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
	if ! is_transactional && is_grub2_bls; then
		if ! subvol_is_ro "$subvol"; then
			prefix="system"
		else
			prefix="snapper"
		fi
	fi

	echo "${prefix:+$prefix-}$entry_token-$kernel_version${snapshot:+-$snapshot}${tries:++$tries}.conf"
}

find_conf_file()
{
	local kernel_version="${1:?}"
	local snapshot="$2"
	local id
	id="$(entry_conf_file "$kernel_version" "$snapshot")"

	update_entries_for_snapshot "$snapshot"

	while IFS= read -r path; do
		if [ -f "$path" ]; then
			echo "$path"
			return 0
		fi
	done < <(jq -r --arg id "$id" '.[] | select(.id == $id) | .path' < "$entryfile")

	return 1
}

settle_entry_token()
{
	local snapshot="$1"
	set_os_release "$snapshot"
	set_machine_id "$snapshot"
	case "$arg_entry_token" in
		""|auto)
			if [ -s '/etc/kernel/entry-token' ]; then
				read -r entry_token < '/etc/kernel/entry-token'
			else
				local var
				for var in machine_id os_release_IMAGE_ID os_release_ID; do
					entry_token="${!var}"
					[ -z "$entry_token" ] || break
				done
			fi
			;;
		machine-id)
			[ -n "$machine_id" ] || err "Couldn't determine machine-id"
			entry_token="$machine_id"
			;;
		os-id)
			# shellcheck disable=SC2154
			entry_token="$os_release_ID"
			[ -n "$entry_token" ] || err "Missing ID"
			;;
		os-image)
			# shellcheck disable=SC2154
			entry_token="$os_release_IMAGE_ID"
			[ -n "$entry_token" ] || err "Missing IMAGE_ID"
			;;
		literal:*)
			entry_token="${arg_entry_token#literal:}"
			;;
		*) err "Unexpected parameter for --entry-token=: $arg_entry_token" ;;
	esac
	[ -n "$entry_token" ] || err "Can't determine entry-token"

	dbg_var "entry_token"
	return 0
}

remove_kernel()
{
	local snapshot="$1"
	local kernel_version="$2"
	[ -n "$kernel_version" ] || err "Missing kernel version"

	info "Removing kernel $kernel_version"
	dbg_var "snapshot"

	settle_entry_token "${snapshot}"
	local id
	id="$(entry_conf_file "$kernel_version" "$snapshot")"
	info "Removing boot entry $id"
	bootctl unlink "$id"

	# This action will require to update the PCR predictions
	update_predictions=1
}

install_with_rollback()
{
	local src="${1:?}"
	local dst="${2:?}"

	if [ -e "$dst" ]; then
		if cmp -s "$src" "$dst"; then
			info "$dst unchanged"
			return 0
		fi
		mv "$dst" "$dst.bak" || return "$?"
	fi
	rollback+=("$dst")
	install -p -m 0644 "$src" "$dst" || return "$?"
	chown root:root "$dst" 2>/dev/null || :
	info "Installed $dst"
}

update_snapper()
{
	snapper --jsonout --no-dbus list --disable-used-space > "$snapperfile"
	dbg_cat "$snapperfile"
}

set_snapper_title_and_sortkey()
{
	[ -n "$have_snapshots" ] || return 0
	snapshot="${1:?}"
	local type date desc important pre_num
	local snapshot_info

	[ -s "$snapperfile" ] || update_snapper

	# shellcheck disable=SC2046
	IFS="|" read -r type date desc important pre_num <<<\
		$(jq -r --arg snapshot "$snapshot" \
		'.["root"][]|select(.number==($snapshot|tonumber))|[.type,.date,(.description|gsub("\\|";"_")),.userdata.important,."pre-number"//""]|join("|")'\
		< "$snapperfile")

	if [ -z "$desc" ] && [ "$type" = "post" ] && [ -n "$pre_num" ]; then
		read -r desc <<<"$(jq -r --arg snapshot "$pre_num" '.["root"][]|select(.number==($snapshot|tonumber))|.description' < "$snapperfile")"
	fi

	if [ "$important" = "yes" ]; then important="*"; else important=""; fi
	[ "$type" = "single" ] && type=""
	snapshot_info="$snapshot,$kernel_version,$date${type:+, $type}${desc:+, $desc}"

	# shellcheck disable=SC2154
	title="Snapper: ${important}$title ($snapshot_info)"
	sort_key="snapper-$sort_key"
}

set_os_release()
{
	local snapshot="$1"
	local subvol=""
	[ -z "$snapshot" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
	os_release_files=(
		"${subvol#"${subvol_prefix}"}/usr/lib/os-release"
		"${subvol#"${subvol_prefix}"}/etc/os-release"
	)

	for file in "${os_release_files[@]}"; do
		[ -f "$file" ] || continue
		eval "$(sed -ne '/^[A-Z_]\+=/s/^/os_release_/p' < "$file")"
		break
	done
}

set_machine_id()
{
	local snapshot="$1"
	local subvol=""
	[ -z "$snapshot" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
	machine_id_files=()
	if is_transactional && [ -z "$TRANSACTIONAL_UPDATE" ]; then
		[ -n "$snapshot" ] && machine_id_files+=("/var/lib/overlay/$snapshot/etc/machine-id")
	fi
	machine_id_files+=("${subvol#"${subvol_prefix}"}/etc/machine-id")

	for file in "${machine_id_files[@]}"; do
		if [ -s "$file" ]; then
			read -r machine_id < "$file"
			break
		fi
	done
}

reuse_initrd()
{
	local snapshot="$1"
	local subvol="$2"
	local kernel_version="${3:?}"
	local conf

	[ -z "$arg_no_reuse_initrd" ] || return 1
	settle_entry_token "${snapshot}"

	conf="$(find_conf_file "$kernel_version" "${snapshot}")"
	local find_conf_status=$?

	if [ $find_conf_status -ne 0 ]; then
		# check if we can reuse the initrd from the parent
		# to avoid expensive regeneration
		detect_parent "$subvol"
		if [ -n "$parent_subvol" ]; then
			settle_entry_token "$parent_snapshot"
			conf="$(find_conf_file "$kernel_version" "$parent_snapshot")"
			find_conf_status=$?
		fi
	fi

	if [ "$find_conf_status" -eq 0 ]; then
		local k v
		while read -r k v; do
			[ "$k" = 'initrd' ] || continue
			[ -f "${boot_root}$v" ] || continue
			info "Found existing initrd $v"
			dstinitrd+=("$v")
		done < "$conf"
		[ "${#dstinitrd[@]}" -eq 0 ] || return 0
	fi

	return 1
}

mount_chroot()
{
	local snapshot_dir="$1"

	# We include the rootfs (the first line usually), as is needed
	# to appear in the mounts under the chroot, allowing dracut to
	# properly detect the fs type and load the relevant module.
	findmnt -o TARGET,FSTYPE -Rv --pairs / > "$tmpdir/mounts"
	mount --bind "$snapshot_dir" "$snapshot_dir"
	while read -r line; do
		eval "$line"
		# shellcheck disable=SC2153
		[ "$FSTYPE" = "btrfs" ] || [ "$FSTYPE" = "vfat" ] || [ "$FSTYPE" = "xfs" ] || [[ "$FSTYPE" == ext* ]] || continue
		[ "$TARGET" != "/" ] || continue
		[ "$TARGET" = "/etc" ] && [ "$FSTYPE" = "btrfs" ] && continue
		[[ "$TARGET" != /.snapshots* ]] || continue
		mountpoint --quiet "$snapshot_dir$TARGET" || mount --bind "$TARGET" "$snapshot_dir$TARGET"
	done < "$tmpdir/mounts"
	rm "$tmpdir/mounts"

	mount -t tmpfs -o size=10m tmpfs "$snapshot_dir/run"
	if [ -e /run/systemd/journal ]; then
		mkdir -p "$snapshot_dir/run/systemd/journal"
		mount --bind /run/systemd/journal "$snapshot_dir/run/systemd/journal"
	fi
	for i in proc dev sys tmp; do
		mount --bind "/$i" "$snapshot_dir/$i"
	done
	chroot_dir="$snapshot_dir"
}

umount_chroot()
{
	local snapshot_dir="$1"

	umount -R "$snapshot_dir"
	chroot_dir=
}

mount_etc()
{
	local snapshot_dir="$1"

	# Don't mount if we are within a transactional-update shell
	[ -z "$TRANSACTIONAL_UPDATE" ] || return 0

	# Only overlayfs needs special treatment
	[ "$(findmnt --tab-file "${snapshot_dir}/etc/fstab" --noheadings --nofsroot --output FSTYPE /etc)" = "overlay" ] || return 0

	IFS=',' read -ra fields <<<\
	   "$(findmnt --tab-file "${snapshot_dir}/etc/fstab" --noheadings --nofsroot --output OPTIONS /etc | sed 's#/sysroot##g' | sed 's#:/etc,#:'"${snapshot_dir}"'/etc,#g')"

	local lower=""
	local upper=""
	for element in "${fields[@]}"; do
		IFS='=' read -r key value <<<"$element"
		[ "$key" = "lowerdir" ] && lower="$value"
		[ "$key" = "upperdir" ] && upper="$value"
	done

	mount overlay -t overlay -o ro,"lowerdir=${upper}:${lower}" "${snapshot_dir}/etc"
}

add_version_to_title()
{
	# TW pretty name does not include the version
	# shellcheck disable=SC2154
	[ -n "$os_release_VERSION" ] || title="$title $os_release_VERSION_ID"
}

add_kernel_version_to_title()
{
	# grub2-bls does not show the `version` field
	title="$title ($snapshot@$kernel_version)"
}

pending_kernel_size()
{
	echo $(($(stat -c %s "$1") / 1024 + 1))
}

pending_initrds_size()
{
	local size=0
	local i=0
	while [ -e "$1/initrd-$i" ]; do
		size=$((size + $(stat -c %s "$1/initrd-$i")))
		((++i))
	done
	echo $((size / 1024 + 1))
}

boot_free_space()
{
	echo $(($(findmnt -n -b -o AVAIL --target "$boot_root" | head -n 1) / 1024))
}

regex_snapshot_ids_for_free_space()
{
	local snapshot="$1"

	[ -s "$snapperfile" ] || update_snapper

	# Select the default and the active snapshots.  Also include
	# one extra snapshot if it is passed as a parameter, that
	# refers to the snapshot from where we are installing the
	# kernel
	declare -A snapshots
	[ -z "$snapshot" ] || snapshots["$snapshot"]=1
	[ -z "$root_snapshot" ] || snapshots["$root_snapshot"]=1
	local id
	while read -r id; do
		snapshots[$id]=1
	done < <(jq -r '.root[]|select(.active==true or .default==true)|.number' "$snapperfile")

	local re
	if [ "${#snapshots[@]}" = 1 ]; then
		re="${!snapshots[*]}"
	else
		IFS='|' eval re='"(:?${!snapshots[*]})"'
	fi

	echo "$re"
}

select_entries_for_free_space()
{
	local snapshot="$1"

	# Extend the entry file with a "priority" field, based on the
	# ordered snapshot IDs, and the "kernel" field, based on the
	# kernel version.  Note that the textual suffix (-default,
	# -slowroll) is removed and does not participate in the
	# ordering
	#
	# The final list is a join of two list.  One that contains all
	# the entries that belong to the safe-to-delete snapshots, and
	# another one that contain the entries from the default
	# snapshot, that is not the default entry.  This last sub-list
	# has the higher priority value, so when sorted get the last
	# order
	#
	# Expected order for removal:
	#
	# MicroOS
	#   - The default / active snapshot is always the last one,
	#     except when we do a rollback
	#   - The default boot entry should never be removed
	#   - The removal order is sort(snapshot_id, kernel), except
	#     for entries that belong to the default snapshot.  Those
	#     should be removed as a last resort
	#   - If there are snapshots after the default and active one,
	#     they are remainigs from a rollback, and they can be
	#     removed in the same order (ideally early)
	#
	# Tumbleweed
	#   - The default / active snapshot is always the first one,
	#     except when we do a rollback
	#   - The default boot entry should never be removed
	#   - The removal order is sort(snapshot_id, kernel), the same
	#     as in MicroOS
	#   - If there are snapshot before the default and active one,
	#     they are remainings from a rollback and they can be
	#     removed in the same order

	# Select the entries that are safe to remove, i.e. the ones
	# that are not the default, the active nor the one from where
	# we are extracting the kernel (that usually is the default or
	# the active one)
	update_entries_for_snapshot_invert "$(regex_snapshot_ids_for_free_space "$snapshot")"
	jq 'map(. + {"priority": .version | scan("(\\d+)@") | .[] | tonumber, "kernel": .version | scan(".*@(?:(\\d+).(\\d+).(\\d+)-(\\d+))") | map(. | tonumber)})' < "$entryfile" > "${entryfile}.ext-1"

	# The name is confusing.  The maximum priority is the minimal
	# one once is sorted by priority
	local max_priority
	read -r max_priority <<<"$(jq -r 'max_by(.priority)|.priority' < "${entryfile}.ext-1")"
	((++max_priority))

	# From the no-safe ones, we can remove some entries from the
	# default entry (the active is the one under development in
	# MicroOS)
	update_entries_for_snapshot "$root_snapshot"
	jq --arg max_priority "$max_priority" 'map(. + {"priority": $max_priority | tonumber, "kernel": .version | scan(".*@(?:(\\d+).(\\d+).(\\d+)-(\\d+))") | map(. | tonumber)})' < "$entryfile" > "${entryfile}.ext-2"

	# The default boot entry should be in the second list, but
	# puting the filter here guarantees that it will not appears
	# in the final list
	jq --slurp 'add|[.[]|select(.isDefault==false)]' "${entryfile}.ext-1" "${entryfile}.ext-2" > "$entryfile"
	dbg "Added priority and kernel version to entry file (for free space)"
	dbg_cat "$entryfile"
}

make_free_space()
{
	local snapshot="$1"
	local required_size="$2"

	info "Required free space in ESP: $required_size KB"

	# If there is already free space, shortcut the code
	free_space="$(boot_free_space)"
	[ "$required_size" -gt "$free_space" ] || return 0

	# "Cleaning /boot/efi" message is presented via stderr
	dbg "Calling bootctl cleanup"
	bootctl -q cleanup 2> /dev/null

	select_entries_for_free_space "$snapshot"

	local id
	while read -r id; do
		free_space="$(boot_free_space)"
		dbg "Free space in the ESP: $free_space KB"
		if [ "$required_size" -gt "$free_space" ]; then
			info "Removing boot entry $id"
			bootctl unlink "$id"
		else
			return 0
		fi
	done < <(jq -r 'sort_by(.priority, .kernel) | .[] | .id' < "$entryfile")

	free_space="$(boot_free_space)"
	dbg "Free space in the ESP after deallocation: $free_space KB"
	[ "$required_size" -lt "$free_space" ]
}

make_free_space_for_kernel()
{
	local snapshot="$1"

	# Calculate the free space and the required size.  All sizes
	# are in Kb to avoid big numbers
	local free_space total_size
	total_size=$(($(pending_kernel_size "$src") + $(pending_initrds_size "$tmpdir")))

	make_free_space "$snapshot" "$total_size"
}

create_boot_options() {
	local subvol="$1"
	local boot_options=
	for i in "${subvol:1}/etc/kernel/cmdline" "${subvol:1}/usr/lib/kernel/cmdline" /proc/cmdline; do
		[ -f "$i" ] || continue
		dbg_cat "$i"
		boot_options="$(sedrootflags "$subvol" < "$i")"
		break
	done
	echo "$boot_options"
}

install_kernel()
{
	local snapshot="$1"
	local subvol=""
	[ -z "$have_snapshots" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
	local kernel_version="$2"
	local dstinitrd=()
	local src="${subvol#"${subvol_prefix}"}/lib/modules/$kernel_version/$image"
	local initrddir="${subvol#"${subvol_prefix}"}/usr/lib/initrd"
	[ -n "$kernel_version" ] || err "Missing kernel version"
	[ -e "$src" ] || err "Can't find $src"

	info "Installing kernel $kernel_version"
	dbg_var "snapshot"

	calc_chksum "$src"
	settle_entry_token "${snapshot}"
	local dst="/$entry_token/$kernel_version/linux-$chksum"

	local initrd="${src%/*}/initrd"

	mkdir -p "$boot_root${dst%/*}"

	if [ -e "$initrd" ]; then
		ln -s "$initrd" "$tmpdir/initrd-0"
	elif [ -d "$initrddir" ] && [ -x "/usr/bin/mkmoduleinitrd" ]; then
		local f i
		i=0
		for f in "$initrddir"/*; do
			ln -s "$f" "$tmpdir/initrd-$i"
			((++i))
		done
		/usr/bin/mkmoduleinitrd "${subvol#"${subvol_prefix}"}" "$kernel_version" "$tmpdir/initrd-$i"
	elif ! reuse_initrd "$snapshot" "$subvol" "$kernel_version"; then
		local snapshot_dir="/.snapshots/$snapshot/snapshot"
		local dracut_args=(
			'--reproducible'
			'--force'
			'--tmpdir' '/var/tmp'
		)
		if [ "${verbose:-0}" -le 1 ]; then
			dracut_args+=('--quiet')
		fi

		info "Generating new initrd"

		if [ "$subvol" != "$root_subvol" ] && [ -n "$have_snapshots" ]; then
			mount_chroot "${snapshot_dir}"
			# In MicroOS we need to be sure to have the same /etc
			# inside the snapshot.  For example, /etc/crypttab can
			# have modifications in the overlay that will be
			# visible once the snapshot is active, but the version
			# in /.snapshots is still the unmodified base
			is_transactional && mount_etc "${snapshot_dir}"
			chroot "${snapshot_dir}" dracut "${dracut_args[@]}" "$tmpdir/initrd-0" "$kernel_version"
			umount_chroot "${snapshot_dir}"
		else
			dracut "${dracut_args[@]}" "$tmpdir/initrd-0" "$kernel_version"
		fi
	fi

	make_free_space_for_kernel "$snapshot" || err "No free space in $boot_root for new kernel"

	local boot_options
	boot_options="$(create_boot_options "$subvol")"

	if [ "${#dstinitrd[@]}" -eq 0 ] && [ -e "$tmpdir/initrd-0" ]; then
		i=0
		while [ -e "$tmpdir/initrd-$i" ]; do
			calc_chksum "$tmpdir/initrd-$i"
			dstinitrd+=("${dst%/*}/initrd-$chksum")
			((++i))
		done
	fi

	title="${os_release_PRETTY_NAME:-Linux $kernel_version}"
	# shellcheck disable=SC2154
	sort_key="$os_release_ID"

	add_version_to_title
	if ! is_transactional && subvol_is_ro "$subvol"; then
		set_snapper_title_and_sortkey "$snapshot"
	elif is_grub2_bls; then
		add_kernel_version_to_title
	fi

	local entry_machine_id=
	[ "$entry_token" = "$machine_id" ] && entry_machine_id="$machine_id"

	cat > "$tmpdir/entry.conf" <<-EOF
	# Boot Loader Specification type#1 entry
	title      $title
	version    $snapshot@$kernel_version${entry_machine_id:+${nl}machine-id $entry_machine_id}${sort_key:+${nl}sort-key   $sort_key}
	options    $boot_options
	linux      $dst
	EOF
	for i in "${dstinitrd[@]}"; do
		echo "initrd     $i" >> "$tmpdir/entry.conf"
	done
	dbg "Generated new boot entry"
	dbg_cat "$tmpdir/entry.conf"

	local failed=
	if [ ! -e "$boot_root$dst" ]; then
		install_with_rollback "$src" "$boot_root$dst" || failed=kernel
	else
		info "Reusing $boot_root$dst"
	fi
	if [ -z "$failed" ] && [ -e "$tmpdir/initrd-0" ]; then
		i=0
		while [ -e "$tmpdir/initrd-$i" ]; do
			if [ ! -e "$boot_root${dstinitrd[$i]}" ]; then
				install_with_rollback "$tmpdir/initrd-$i" "$boot_root${dstinitrd[$i]}" || { failed=initrd; break; }
				rm -f "$tmpdir/initrd-$i"
			fi
			((++i))
		done
	fi
	if [ -z "$failed" ]; then
		local tries
		if [ -f /etc/kernel/tries ]; then
			read -r tries < /etc/kernel/tries
		fi

		if ! [[ "$tries" =~ ^[0-9]+$ ]]; then
			tries=
		fi

		loader_entry="$boot_root/loader/entries/$(entry_conf_file "$kernel_version" "$snapshot" "$tries")"
		install_with_rollback "$tmpdir/entry.conf" "$loader_entry" || failed="bootloader entry"
		rm -f "$tmpdir/entry.conf"
	fi
	[ -z "$failed" ] || err "Failed to install $failed"
	reset_rollback

	# Do a final cleanup, as sometimes we are replacing an old
	# initrd
	bootctl -q cleanup 2> /dev/null

	# This action will require to update the PCR predictions
	update_predictions=1
}

install_all_kernels()
{
	local snapshot="$1"

	info "Installing all kernels"
	dbg_var "snapshot"
	dbg_var "arg_no_reuse_initrd"

	find_kernels "$snapshot"
	for kv in "${!found_kernels[@]}"; do
		install_kernel "${snapshot}" "$kv"
	done
}

remove_all_kernels()
{
	local snapshot="$1"

	info "Removing all kernels"
	dbg_var "snapshot"

	find_kernels "$snapshot"
	for kv in "${!found_kernels[@]}"; do
		remove_kernel "${snapshot}" "$kv"
	done
}

cleanup_entries()
{
	info "Cleaning up boot entries"

	if [ ! -s "$entryfile" ]; then
		if [ -n "$1" ]; then
			update_entries_for_snapshot "$1"
		else
			update_entries_for_this_system
		fi
	fi

	local id path snapshot kernel_version subvol src
	while read -r id; do
		read -r path
		read -r snapshot
		read -r kernel_version
		subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"
		src="${subvol#"${subvol_prefix}"}/lib/modules/$kernel_version/$image"
		[ -e "$src" ] || {
			info "Cleaning boot entry $id"
			rm "$path"
		}
	done < <(jq -r '.[] | .id, .path, (.version | scan("(.*)@(.*)") | .[])' "$entryfile")

	dbg "Calling bootctl cleanup"
	bootctl -q cleanup 2> /dev/null
}

list_entries()
{
	info "Listing boot entries"

	if [ ! -s "$entryfile" ]; then
		if [ -n "$1" ]; then
			update_entries_for_snapshot "$1"
		elif [ -n "$arg_all_entries" ]; then
			update_entries
		else
			update_entries_for_this_system
		fi
	fi

	local isdefault isreported type id root conf title
	while read -r isdefault isreported type id root conf title; do
		color=
		if [ "$isdefault" = "true" ]; then
			color="$color_bu"
		elif [ -n "$arg_only_default" ]; then
			continue
		fi
		if [ "$isreported" = "false" ]; then
			color="$color${color_green}"
		fi
		if [ "$type" = "loader" ]; then
			color="$color${color_yellow}"
		fi

		local errors=()
		if [ -n "$verbose" ] && [ -n "$conf" ] && [ -e "$conf" ]; then
			local k
			local v
			while read -r k v; do
				if [ "$k" = 'linux' ] || [ "$k" = 'initrd' ]; then
					if [ ! -e "$root$v" ]; then
						errors+=("$root/$v does not exist")
					fi
				fi
				[ -n "$have_snapshots" ] || break
				if [ "$k" = 'options' ]; then
					local snapshot
					# shellcheck disable=SC2001
					read -r snapshot <<<"$(echo "$v" | sed -e "s,.*rootflags=subvol=${subvol_prefix}/.snapshots/\([0-9]\+\)/snapshot.*,\1,")"
					if [ ! -d "/.snapshots/$snapshot/snapshot" ]; then
						errors+=("/.snapshot/$snapshot/snapshot does not exist")
					fi
				fi
			done < "$conf"
		fi
		if [ "${#errors[@]}" -gt 0 ]; then
			echo -e "  ${color_red}${errors[*]}${color_end}" >&2
		fi
		echo -e "$color$id${verbose:+: $title}${color_end}"
	done < <(jq '.[]|[.isDefault, if has("isReported") then .isReported else 0 end, if has("type") then .type else "unknown" end, .id, .root, .path, .showTitle]|join(" ")' -r < "$entryfile")
}

show_entry_fields()
{
	local snapshot="$1"
	local kernel_version="$2"
	[ -n "$kernel_version" ] || err "Missing kernel version"
	settle_entry_token "${snapshot}"
	local id
	id="$(entry_conf_file "$kernel_version" "$snapshot")"

	local conf
	conf="$(find_conf_file "$kernel_version" "$snapshot")"

	[ -z "$verbose" ] || echo -e "ID\t$id"
	local k
	local v
	while read -r k v; do
		case "$k" in
			title|version|sort-key|options|linux|initrd) ;;
			*) continue ;;
		esac

		if [ "${#arg_entry_keys[@]}" -eq 0 ] || [[ ${arg_entry_keys[*]} == *"all"* ]] || [[ ${arg_entry_keys[*]} == *"$k"* ]]; then
			echo -e "$k\t$v"
		fi
	done < "$conf"
}

update_entry_conf()
{
	local conf="$1"
	local snapshot="$2"

	local subvol=""
	[ -z "$have_snapshots" ] || subvol="${subvol_prefix}/.snapshots/${snapshot}/snapshot"

	local boot_options
	boot_options="$(create_boot_options "$subvol")"

	cp "$conf" "$tmpdir/entry.conf"
	sed -i "s|^options\s*.*$|options    $boot_options|g" "$tmpdir/entry.conf"
	cp "$tmpdir/entry.conf" "$conf"
}

update_entry()
{
	local snapshot="$1"
	local kernel_version="$2"

	settle_entry_token "${snapshot}"
	local id
	id="$(entry_conf_file "$kernel_version" "$snapshot")"

	local conf
	conf="$(find_conf_file "$kernel_version" "$snapshot")"
	[ -f "$conf" ] || return 0

	info "Updating boot entry $id"
	update_entry_conf "$conf" "$snapshot"

	# This action will require to update the PCR predictions
	update_predictions=1
}

update_all_entries()
{
	local snapshot="$1"

	make_free_space "$snapshot" 1024

	update_entries_for_snapshot "$1"
	while read -r conf; do
		update_entry_conf "$conf" "$snapshot"
	done < <(jq -r '.[]|.path' < "$entryfile")

	# This action will require to update the PCR predictions
	update_predictions=1
}

list_snapshots()
{
	[ -n "$have_snapshots"  ] || { info "System does not support snapshots."; return 0; }
	[ -s "$snapperfile" ] || update_snapper 2>"$tmpfile" || err "$(cat "$tmpfile")"

	info "Listing snapshots"

	local n=0
	while read -r n isdefault title; do
		[ "$n" != "0" ] || continue
		local id="$n"
		if [ "$isdefault" = "true" ]; then
			id="$color_bu$id$color_end"
		fi
		update_kernels "$n"
		[ "$is_bootable" = 1 ] || id="!$id"
		echo -e "$id $title"
	done < <(jq '.root|.[]|[.number, .default, .description]|join(" ")' -r < "$snapperfile")
}

calc_chksum()
{
	# shellcheck disable=SC2046
	set -- $(sha1sum "$1")
	chksum="$1"
}

# map with kernel version as key and checksum as value
declare -A found_kernels
find_kernels()
{
	local subvol=""
	[ -z "$have_snapshots" ] || subvol="${subvol_prefix}/.snapshots/${1:?}/snapshot"
	local fn kv
	found_kernels=()

	for fn in "${subvol#"${subvol_prefix}"}"/usr/lib/modules/*/"$image"; do
		kv="${fn%/*}"
		kv="${kv##*/}"
		calc_chksum "$fn"
		found_kernels["$kv"]="$chksum"
		info "Found kernel $kv = $chksum"
	done
}

# Map that uses expected path on the ESP for each installed kernel as
# key.  The value is the entry id if an entry exists.
declare -A installed_kernels
# Map of ESP path to id of kernels that are not in the subvol
declare -A stale_kernels
is_bootable=
update_kernels()
{
	local snapshot="$1"
	local path id
	installed_kernels=()
	stale_kernels=()
	is_bootable=
	find_kernels "$snapshot"
	settle_entry_token "${snapshot}"
	for kv in "${!found_kernels[@]}"; do
		installed_kernels["/$entry_token/$kv/linux-${found_kernels[$kv]}"]=''
	done
	update_entries_for_snapshot "$snapshot"

	# XXX: maybe we should parse the actual path in the entry
	while read -r path id; do
		if [ "${installed_kernels[$path]+none}" = 'none' ]; then
			installed_kernels["$path"]="$id"
			is_bootable=1
		else
			# kernel in ESP that is not installed
			stale_kernels["$path"]="$id"
		fi
	done < <(jq -r '.[]|select(has("linux"))|[.linux,.id]|join(" ")'< "$entryfile")
}

list_kernels()
{
	local snapshot=""
	[ -z "$have_snapshots" ] || snapshot="${1:?}"

	info "Listing kernels"

	update_kernels "$snapshot"
	local kernelfiles=("${!installed_kernels[@]}")
	for k in "${kernelfiles[@]}"; do
		local id="${installed_kernels[$k]}"
		local kv="${k%/*}"
		kv="${kv##*/}"
		if [ -z "$id" ]; then
			echo -e "${color_yellow}missing /lib/modules/$kv/$image${color_end}"
		else
			echo -e "${color_green}ok /lib/modules/$kv/$image -> $id${color_end}"
		fi
	done
	kernelfiles=("${!stale_kernels[@]}")
	for k in "${kernelfiles[@]}"; do
		local id="${stale_kernels[$k]}"
		printf "${color_red}stale %s${color_end}\n" "$id"
	done
}

list_devices()
{
	info "Listing devices"

	detect_tracked_devices

	for dev in "${tracked_devices[@]}"; do
		echo "$dev"
	done
}

is_shim_installed()
{
	# Check if shim is installed in the ESP
	# In installed systems there should be a shim.efi
	[ ! -f "${boot_root}${boot_dst}/shim.efi" ] || return 0
	# In removable media there is only BOOT${arch}.EFI
	! grep -q "UEFI SHIM" "${boot_root}/EFI/BOOT/BOOT${firmware_arch^^}.EFI" || return 0
	return 1
}

is_bootable()
{
	local snapshot="$1"
	update_kernels "$snapshot"

	[ "$is_bootable" = 1 ] || return 1
	return 0
}

bootloader_version()
{
	local fn="$1"
	if [ -z "$1" ]; then
		if [ -e "$shimdir/shim.efi" ]; then
			fn="$boot_root$boot_dst/grub.efi"
		else
			local bootloader
			bootloader="$(find_bootloader)"
			fn="$boot_root$boot_dst/${bootloader##*/}"
		fi
	fi
	[ -e "$fn" ] || return 1
	if is_sdboot; then
		read -r _ _ _ v _ < <(grep -ao '#### LoaderInfo: systemd-boot [^#]\+ ####' "$fn")
	else
		# Useless as it reports mayor.minor, so append the
		# last update time until the minutes, as the FAT store
		# dates differently than other filesystems
		read -r _ _ _ v _ < <(grep -aoP 'GNU GRUB  version %s\x00[^\x00]+\x00' "$fn")
		v="${v:2}-$(date -r "$fn" +'%Y%m%d%H%M')"
	fi
	[ -n "$v" ] || return 1

	dbg "Bootloader version $v"

	echo "$v"
}

is_installed()
{
	info "Checking if the bootloader is installed"
	bootloader_version > /dev/null && [ -e "$boot_root/$boot_dst/installed_by_sdbootutil" ]
}

find_sdboot()
{
	local prefix=""
	[ -z "$have_snapshots" ] || prefix="/.snapshots/${1-$root_snapshot}/snapshot"
	# XXX: this is a hack in case we need to inject a signed
	# systemd-boot from a separate package
	local sdboot="$prefix/usr/lib/systemd-boot/systemd-boot$firmware_arch.efi"
	[ -e "$sdboot" ] || sdboot="$prefix/usr/lib/systemd/boot/efi/systemd-boot$firmware_arch.efi"
	echo "$sdboot"
}

find_grub2_bls()
{
	local prefix=""
	[ -z "$have_snapshots" ] || prefix="/.snapshots/${1-$root_snapshot}/snapshot"

	local grub2_arch
	grub2_arch="$(uname -m)"
	case "$grub2_arch" in
		i[[3456]]86) grub2_arch=i386 ;;
		x86_64) grub2_arch=x86_64 ;;
		amd64) grub2_arch=x86_64 ;;
		sparc) grub2_arch=sparc64 ;;
		mipsel|mips64el) grub2_arch=mipsel ;;
		mips|mips64) grub2_arch=mips ;;
		arm*) grub2_arch=arm ;;
		aarch64*) grub2_arch=arm64 ;;
		loongarch64) grub2_arch=loongarch64 ;;
		riscv32*) grub2_arch=riscv32 ;;
		riscv64*) grub2_arch=riscv64 ;;
	esac

	# The old grub.efi will contain the BLS patches, but we cannot
	# use it because we also dropped the process of creating the
	# configuration file and installing bli.mod
	echo "$prefix/usr/share/grub2/${grub2_arch}-efi/grubbls.efi"
}

find_bootloader()
{
	if is_sdboot "${1-$root_snapshot}"; then
		find_sdboot "${1-$root_snapshot}"
	elif is_grub2_bls "${1-$root_snapshot}"; then
		find_grub2_bls "${1-$root_snapshot}"
	else
		err "Bootloader not detected"
	fi
}

bootloader_needs_update()
{
	local prefix=""
	local snapshot=""
	if [ -n "$have_snapshots" ]; then
		snapshot="${1-$root_snapshot}"
		prefix="/.snapshots/${snapshot}/snapshot"
	fi

	info "Checking if bootloader needs update"

	local bldr_name
	local v nv
	v="$(bootloader_version)"
	[ -n "$v" ] || return 1
	info "Deployed version $v"
	nv="$(bootloader_version "$(find_bootloader "$snapshot")")"
	[ -n "$nv" ] || return 1
	info "System version $nv"
	systemd-analyze compare-versions "$v" "$nv" 2>/dev/null
	local status="$?"
	bldr_name=$(bootloader_name "$snapshot")
	if [ "$status" = "11" ]; then
		info "$bldr_name is newer than system bootloader"
		return 2
	elif [ "$status" = "12" ]; then
		info "$bldr_name needs to be updated"
		return 0
	fi
	info "$bldr_name is already up-to-date"
	return 1
}

install_bootloader()
{
	local snapshot=""
	local prefix=""
	if [ -n "$have_snapshots" ]; then
		snapshot="${1:-$root_snapshot}"
		prefix="/.snapshots/${root_snapshot}/snapshot"
	fi

	info "Installing bootloader"
	dbg_var "$snapshot"

	local bootloader bldr_name blkpart drive partno
	settle_entry_token "${snapshot}"

	bootloader=$(find_bootloader "$snapshot")
	bldr_name=$(bootloader_name "$snapshot")
	dbg_var "bootloader"
	dbg_var "bldr_name"

	mkdir -p "$boot_root/loader/entries"

	# The ESP presence was checked early
	blkpart="$(findmnt -nvo SOURCE "$boot_root")"
	[ -L "/sys/class/block/${blkpart##*/}" ] || err "$blkpart is not a partition"
	drive="$(readlink -f "/sys/class/block/${blkpart##*/}")"
	drive="${drive%/*}"
	drive="/dev/${drive##*/}"
	read -r partno < "/sys/class/block/${blkpart##*/}"/partition

	if [ -n "$arg_secure_boot" ] && [ ! -e "$prefix$shimdir/shim.efi" ]; then
		warn "A secure boot installation cannot be done. The shim package is not installed"
	fi

	if [ -n "$arg_secure_boot" ] && [ -e "$prefix$shimdir/shim.efi" ]; then
		info "Installing $bldr_name with shim into $boot_root"
		entry="$boot_dst/shim.efi"
		for i in MokManager shim; do
			[ -n "$arg_portable" ] || install -p -D "$prefix$shimdir/$i.efi" "$boot_root$boot_dst/$i.efi"
		done
		install -p -D "$bootloader" "$boot_root$boot_dst/grub.efi"

		# boot entry point
		install -p -D "$prefix$shimdir/MokManager.efi" "$boot_root/EFI/BOOT/MokManager.efi"
		[ -n "$arg_portable" ] || install -p -D "$prefix$shimdir/fallback.efi" "$boot_root/EFI/BOOT/fallback.efi"
		install -p -D "$prefix$shimdir/shim.efi" "$boot_root/EFI/BOOT/BOOT${firmware_arch^^}.EFI"
	else
		info "Installing $bldr_name without shim into $boot_root"
		entry="$boot_dst/${bootloader##*/}"
		[ -n "$arg_portable" ] || install -p -D "$bootloader" "$boot_root$entry"
		install -p -D "$bootloader" "$boot_root/EFI/BOOT/BOOT${firmware_arch^^}.EFI"
	fi

	# shellcheck disable=SC2154
	[ -n "${os_release_NAME}" ] || set_os_release "${snapshot}"
	local split
	IFS=" " read -r -a split <<<"${os_release_NAME}"
	local boot_manager
	boot_manager="${split[0]} Boot Manager"

	# This is for shim to create the entry if missing
	[ -n "$arg_portable" ] || echo "${entry##*/},$boot_manager" | { echo -ne "\xff\xfe"; iconv -f ascii -t ucs-2le; } > "$boot_root$boot_dst/boot.csv"

	mkdir -p "$boot_root/$entry_token"
	echo "$entry_token" > "$boot_root$boot_dst/installed_by_sdbootutil"
	mkdir -p "/etc/kernel"
	[ -s /etc/kernel/entry-token ] || echo "$entry_token" > /etc/kernel/entry-token
	update_random_seed

	if is_sdboot "$snapshot"; then
		[ -s "$boot_root/loader/entries.srel" ] || echo type1 > "$boot_root/loader/entries.srel"
		[ -e "$boot_root/loader/loader.conf" ] || echo -e "#timeout 3\n#console-mode keep\n" > "$boot_root/loader/loader.conf"
	fi

	# Create boot menu entry if it does not exist
	local escaped_entry="${entry//\//\\\\}"
	[ -n "$arg_no_variables" ] || [ -n "$arg_portable" ] || efibootmgr | grep -q "Boot.*${boot_manager}.*${escaped_entry}" || efibootmgr -q --create --disk "$drive" --part "$partno" --label "${boot_manager} ($bldr_name)" --loader "$entry" || true

	# Make it the first option
	if [ -z "$arg_no_variables" ] && [ -z "$arg_portable" ]; then
		local boot_order
		boot_order="$(efibootmgr | grep BootOrder)"
		boot_order="${boot_order#BootOrder: }"

		local boot_entry
		boot_entry="$(efibootmgr | grep "Boot.*${boot_manager}.*${escaped_entry}")"
		boot_entry="${boot_entry%\* *}"
		boot_entry="${boot_entry#Boot}"

		efibootmgr -q -D -o "$boot_entry,$boot_order" || true
	fi

	# This action will require to update the PCR predictions
	update_predictions=1
}

bootloader_update()
{
	local status=0

	info "Updating bootloader"

	bootloader_needs_update "${1:-$root_snapshot}" || status=$?
	if [ $status -eq 0 ]; then
		info "The bootloader needs to be updated"
		install_bootloader "${1:-$root_snapshot}"
	elif [ -n "$arg_sync" ] && [ $status -eq 2 ]; then
		info "The bootloader will be downgraded"
		install_bootloader "${1:-$root_snapshot}"
	fi
}

hex_to_binary()
{
	local s="$1"
	local i
	for ((i=0;i<${#s};i+=2)); do echo -ne "\x${s:$i:2}"; done
}

update_random_seed()
{
	[ -z "$arg_no_random_seed" ] || return 0
	local s _p
	read -r s _p < <({ dd if=/dev/urandom bs=32 count=1 status=none; [ -e "$boot_root/loader/random-seed" ] && dd if="$boot_root/loader/random-seed" bs=32 count=1 status=none; } | sha256sum)
	[ "${#s}" = 64 ] || { warn "Invalid random seed"; return 0; }
	hex_to_binary "$s" > "$boot_root/loader/random-seed.new"
	mv "$boot_root/loader/random-seed.new" "$boot_root/loader/random-seed"
}

bli_efi_var_get()
{
	# BLI uses this vendor UUID
	local efi_var="/sys/firmware/efi/efivars/${1:?}-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
	if [ -e "$efi_var" ]; then
		dd "if=$efi_var" bs=2 skip=2 conv=lcase status=none | tr -d '\0'
	fi
}

loader_conf_set()
{
	local key="${1:?}"
	local value="${2:?}"

	[ -e "${boot_root}/loader/loader.conf" ] || touch "${boot_root}/loader/loader.conf"

	if grep -q "^$key " "${boot_root}/loader/loader.conf"; then
		sed -i -e "s/^$key .*/$key $value/" "${boot_root}/loader/loader.conf"
	else
		echo "$key $value" >> "${boot_root}/loader/loader.conf"
	fi
}

loader_conf_get()
{
	local key="${1:?}"
	if [ -e "${boot_root}/loader/loader.conf" ]; then
		IFS=' ' read -r key value < <(grep "^$key " "${boot_root}/loader/loader.conf")
		echo -n "$value"
	fi
}

grubenv_set()
{
	local key="${1:?}"
	local value="${2:?}"

	[ -e "${boot_root}${boot_dst}/grubenv" ] || touch "${boot_root}${boot_dst}/grubenv"

	grubenv="$(mktemp -t grubenv.XXXXXX)"
	echo "# GRUB Environment Block" > "$grubenv"
	while read -r line; do
		[[ "$line" == '#'* ]] && continue
		[[ "$line" == "$key"=* ]] && continue
		echo "$line" >> "$grubenv"
	done < "${boot_root}${boot_dst}/grubenv"
	echo "$key=$value" >> "$grubenv"

	local filler
	filler=$((1024 - $(stat -c %s "$grubenv")))
	printf '#%.0s' $(seq 1 $filler) >> "$grubenv"

	mv "$grubenv" "${boot_root}${boot_dst}/grubenv"
}

grubenv_get()
{
	local key="${1:?}"

	if [ -e "${boot_root}${boot_dst}/grubenv" ]; then
		IFS='=' read -r key value < <(grep "^$key=" "${boot_root}${boot_dst}/grubenv")
		echo -n "$value"
	fi
}

set_default_sdboot()
{
	local id="${1:?}"
	if ! bootctl set-default "$id" > "$tmpfile" 2>&1; then
		if grep -q "Failed to update EFI variable" "$tmpfile" ||
				grep -q "Not booted with a supported boot loader" "$tmpfile" ||
				grep -q "Not booted with UEFI" "$tmpfile"; then
			loader_conf_set "default" "$id"
		else
			err "$(cat "$tmpfile")"
		fi
	fi
}

set_default_grub2_bls()
{
	local id="${1:?}"
	set_default_sdboot "$id"
	grubenv_set "default" "$id"
}

set_default_entry()
{
	local id="${1:?}"

	info "Setting default entry $id"

	if [ ! -f "${boot_root}/loader/entries/$id" ] && [ ! -f "${boot_root}/loader/entries/$id.conf" ]; then
		update_entries
		while read -r path; do
			if [ ! -f "$path" ]; then
				err "Boot loader entry $id not found"
 			fi
		done < <(jq -r --arg id "$id" '.[] | select(.id == $id) | .path' < "$entryfile")
	fi
	if is_sdboot; then
		set_default_sdboot "$id"
	elif is_grub2_bls; then
		set_default_grub2_bls "$id"
	else
		err "Bootloader not detected"
	fi

	# Setting a different boot entry invalidates "$entryfile"
	rm -f "$entryfile"

	# This action will require to update the PCR predictions
	update_predictions=1
}

get_default_sdboot()
{
	local val
	val="$(bli_efi_var_get "LoaderEntryDefault")"
	[ -n "$val" ] || val="$(loader_conf_get "default")"
	[ -n "$val" ] || {
		update_entries_for_this_system
		jq -r '.[] | select(.isDefault == true) | .id' < "$entryfile"
	}
	[ -z "$val" ] || echo "$val"
}

get_default_grub2_bls()
{
	local val
	val="$(grubenv_get "default")"
	[ -n "$val" ] || val="$(get_default_sdboot "default")"
	[ -z "$val" ] || echo "$val"
}

get_default_entry()
{
	if is_sdboot; then
		get_default_sdboot
	elif is_grub2_bls; then
		get_default_grub2_bls
	else
		err "Bootloader not detected"
	fi
}

set_timeout_sdboot()
{
	local timeout="${1:?}"
	[ "$timeout" = "-1" ] && timeout="menu-force"
	if ! bootctl set-timeout "$timeout" > "$tmpfile" 2>&1; then
		if grep -q "Failed to update EFI variable" "$tmpfile" ||
				grep -q "Not booted with a supported boot loader" "$tmpfile" ||
				grep -q "Not booted with UEFI" "$tmpfile"; then
			loader_conf_set "timeout" "$timeout"
		else
			err "$(cat "$tmpfile")"
		fi
	fi
}

set_timeout_grub2_bls()
{
	local timeout="${1:?}"
	set_timeout_sdboot "$timeout"
	[ "$timeout" = "menu-disabled" ] || [ "$timeout" = "menu-hidden" ] && timeout=0
	[ "$timeout" = "menu-force" ] && timeout=-1
	grubenv_set "timeout" "$timeout"
}

set_timeout()
{
	local timeout="${1:?}"
	info "Setting timeout $timeout"
	if is_sdboot; then
		set_timeout_sdboot "$timeout"
	elif is_grub2_bls; then
		set_timeout_grub2_bls "$timeout"
	else
		err "Bootloader not detected"
	fi

	# This action will require to update the PCR predictions
	update_predictions=1
}

get_timeout_sdboot()
{
	local val
	val="$(bli_efi_var_get "LoaderConfigTimeout")"
	[ -n "$val" ] || val="$(loader_conf_get "timeout")"
	if [ "$val" = 4294967295 ]; then
		val=-1
	fi
	[ -z "$val" ] || echo "$val"
}

get_timeout_grub2_bls()
{
	local val
	val="$(grubenv_get "timeout")"
	[ -z "$val" ] || echo "$val"
}

get_timeout()
{
	if is_sdboot; then
		get_timeout_sdboot
	elif is_grub2_bls; then
		get_timeout_grub2_bls
	else
		err "Bootloader not detected"
	fi
}

set_default_snapshot()
{
	[ -n "$have_snapshots" ] || { info "System does not support snapshots."; return 0; }
	local num="${1:?}"
	local configs

	info "Setting default snapshot $num"

	update_entries_for_snapshot "$num"
	mapfile configs < <(jq '.[]|[.id]|join(" ")' -r < "$entryfile")
	configs=("${configs[@]%$nl}")
	if [ -z "${configs[0]}" ]; then
		info "Snapshot $num has no configs, trying to create them..."
		install_all_kernels "$num"
		update_entries_for_snapshot "$num"
		mapfile configs < <(jq '.[]|[.id]|join(" ")' -r < "$entryfile")
		configs=("${configs[@]%$nl}")
		if [ -z "${configs[0]}" ]; then
			err "snapshot $num has no kernels"
		fi
	fi

	# The default snapshot is not the criteria used for the
	# bootloader to select the default boot entry, but for
	# coherence we synchronize it here, invalidating any previous
	# "$snapperfile"
	#
	# We do not use snapper, to avoid recursion and do not trigger
	# any plugin
	local subvolume_id
	read -r _ subvolume_id _ < <(btrfs subvolume list -o /.snapshots | grep "${subvol_prefix}/.snapshots/$num/snapshot")
	btrfs subvolume set-default "${subvolume_id}" /.snapshots
	rm -f "$snapperfile"

	set_default_entry "${configs[0]}"
}

have_pcrlock()
{
	[ -e /usr/bin/systemd-pcrlock ] || [ -e /usr/lib/systemd/systemd-pcrlock ]
}

have_pcr_oracle()
{
	[ -e /usr/bin/pcr-oracle ]
}

pcrlock()
{
	dbg "systemd-pcrlock $*"
	local pcrlock_cmd="/usr/bin/systemd-pcrlock"
	[ -e "$pcrlock_cmd" ] || pcrlock_cmd="/usr/lib/systemd/systemd-pcrlock"
	SYSTEMD_LOG_LEVEL="${SYSTEMD_LOG_LEVEL:-warning}" "$pcrlock_cmd" "$@"
}

is_pcr_oracle()
{
	[ -e /etc/systemd/tpm2-pcr-public-key.pem ] && \
	    [ -e /etc/systemd/tpm2-pcr-private-key.pem ] && \
	    have_pcr_oracle
}

snapshot_ids_for_prediction()
{
	# Select the ID of the snapshots that participate in the
	# prediction.  The order is important, so the most relevant
	# should be presented first

	# Get the numbers for the last three snapshots
	[ -s "$snapperfile" ] || update_snapper

	# Select the default and the active snapshots.
	local -A snapshots
	local -a snapshot_ids
	local id
	if [ -n "$root_snapshot" ]; then
		[ -n "${snapshots[$root_snapshot]}" ] || snapshot_ids+=("$root_snapshot")
		snapshots["$root_snapshot"]=1
	fi
	while read -r id; do
		[ -n "${snapshots[$id]}" ] || snapshot_ids+=("$id")
		snapshots[$id]=1
	done < <(jq -r '.root[]|select(.default==true)|.number' "$snapperfile")
	while read -r id; do
		[ -n "${snapshots[$id]}" ] || snapshot_ids+=("$id")
		snapshots[$id]=1
	done < <(jq -r '.root[]|select(.active==true)|.number' "$snapperfile")

	if is_transactional && [ -e "${state_file}" ]; then
		# shellcheck disable=SC1090
		. "${state_file}"
		for id in $LAST_WORKING_SNAPSHOTS; do
			if [ "${#snapshots[@]}" -lt 3 ]; then
				[ -n "${snapshots[$id]}" ] || snapshot_ids+=("$id")
				snapshots[$id]=1
			fi
		done
	else
		while read -r id; do
			if [ "${#snapshots[@]}" -lt 3 ]; then
				[ -n "${snapshots[$id]}" ] || snapshot_ids+=("$id")
				snapshots[$id]=1
			fi
		done < <(jq -r '.root|sort_by(.date)[-2:]|reverse|.[]|.number' "$snapperfile")
	fi

	echo "${snapshot_ids[@]}"
}

regex_snapshot_ids_for_prediction()
{
	local re
	re="$(snapshot_ids_for_prediction)"
	re="${re// /|}"
	echo "(:?$re)"
}

select_entries_for_prediction()
{
	# The regex of ids is ordered by relevance, but the returns
	# set is not.  We need to add a "priority" and "kernel"
	# (version) field that can be used to order the set
	local ids
	ids="$(snapshot_ids_for_prediction)"

	update_entries_for_snapshot "$(regex_snapshot_ids_for_prediction)"

	# Extend the entry file with a "priority" field, based on the
	# ordered snapshot IDs, and "kernel" field, based on the
	# kernel version.  Note that the textual suffix (-default,
	# -slowroll) is removed and does not participate in the
	# ordering
	#
	# Expected order for prediction:
	#
	# MicroOS
	#   - The default snapshot usually has the top priority (0),
	#     then the active (1).  The order is set by
	#     `snapshot_ids_for_prediction`
	#   - Last working snapshots are added after those.  Also set
	#     by `snapshot_ids_for_prediction`
	#   - If the snapshot contains multiple kernel, the higher
	#     version has more priority
	#
	# Tumbleweed
	#   - Same than MicroOS (`snapshot_ids_for_prediction`)
	#   - Newest working snapshot are added after those.  Also set
	#     by `snapshot_ids_for_prediction`
	#   - If the snapshot contains multiple kernel, the higher
	#     version has more priority
	#
	jq --arg ids "$ids" 'def priority(id): id as $id | $ids | split(" ") | index($id); map(. + {"priority": priority(.version | scan("(\\d+)@") | .[]), "kernel": .version | scan(".*@(?:(\\d+).(\\d+).(\\d+)-(\\d+))") | map(. | tonumber)})' < "$entryfile" > "${entryfile}.ext"
	mv "${entryfile}.ext" "$entryfile"
	dbg "Added priority and kernel version to entry file (for prediction)"
	dbg_cat "$entryfile"
}

backup_initial_components()
{
	select_entries_for_prediction
	mv "$entryfile" "$initialentryfile"
	cp -a "$boot_root/." "$tmpdir"
}

parse_eventlog()
{
	[ "${#eventlog[@]}" -eq 0 ] || return 0

	while read -r line; do
		eventlog["$line"]=1
	done < <(pcrlock log --json=short | jq -r '.log | .[] | .sha256' | sort -u)
}

find_matching_variation()
{
	local component="$1"

	local hashes=()
	for variation in /var/lib/pcrlock.d/"$component".pcrlock.d/*.pcrlock; do
		mapfile -t hashes < <(jq -r '.records | .[] | .digests | .[] | select(.hashAlg == "sha256") | .digest' < "$variation")
		for h in "${hashes[@]}"; do
			[ "${eventlog["$h"]+_}" ] || continue 2
		done
		echo "$variation"
		break
	done
}

shift_component()
{
	local component="$1"

	parse_eventlog

	dbg "Shifting component $component"
	[ -d "/var/lib/pcrlock.d/$component.pcrlock.d" ] || {
		dbg "The component is not a directory or is missing"
		return 0
	}

	# Instead of moving all the variations of a component (as this
	# will increase the amount of combinations, reaching early the
	# PolicyOR limitation), we keep the one that matches the
	# current eventlog.  If the variation is also duplicated, it
	# will be dropped by `systemd-pcrlock`
	local variation vname
	variation="$(find_matching_variation "$component")"
	if [ -n "$variation" ]; then
		vname="$(basename "$variation")"
		dbg "$variation found in the eventlog"
		[[ "$vname" == shift-* ]] || {
			# Drop any previous shifted variation, as
			# there is no guarantee that the new one will
			# share the name
			find /var/lib/pcrlock.d/"$component".pcrlock.d -name 'shift-*.pcrlock' -delete
			mv "$variation" "/var/lib/pcrlock.d/$component.pcrlock.d/shift-$vname"
		}
	else
		dbg "No matching variation found for $component"
		return 0
	fi

	# Remove the rest of the variations
	find /var/lib/pcrlock.d/"$component".pcrlock.d -name '*.pcrlock' ! -name 'shift-*.pcrlock' -delete
}

pcrlock_manual_raw()
{
	local pcr="$1"
	local pcrlock="$2"
	local element="$3"

	echo -n '{"records":[{"pcr":'"$pcr"',"digests":[' > "$pcrlock"
	local separator=0
	local digest
	for dgst in sha1 sha256 sha384 sha512; do
		[ "$separator" = "0" ] || echo -n "," >> "$pcrlock"
		separator=1
		hash "${dgst}sum" || continue
		read -r digest _ < <("${dgst}sum" "$element")
		echo -n '{"hashAlg":"'"$dgst"'","digest":"'"$digest"'"}' >> "$pcrlock"
	done
	echo ']}]}' >> "$pcrlock"
}

pcrlock_sdboot_cmdline_initrd()
{
	local cmdline="$1"
	local initrd="$2"
	local suffix="$3"

	# 710-kernel-cmdline-initrd-entry.pcrlock.d is not part of the
	# pcrlock standards
	echo "$cmdline" > "$tmpdir/cmdline"
	pcrlock \
		lock-kernel-cmdline \
		--pcrlock="$tmpdir/cmdline.pcrlock" \
		"$tmpdir/cmdline"
	pcrlock \
		lock-kernel-initrd \
		--pcrlock="$tmpdir/initrd.pcrlock" \
		"$initrd" 2> /dev/null || pcrlock_manual_raw 9 "$tmpdir/initrd.pcrlock" "$initrd"
	mkdir -p /var/lib/pcrlock.d/710-kernel-cmdline-initrd-entry.pcrlock.d
	jq --slurp '{"records": [.[].records[0]]}' \
	   "$tmpdir/cmdline.pcrlock" \
	   "$tmpdir/initrd.pcrlock" \
	   > "/var/lib/pcrlock.d/710-kernel-cmdline-initrd-entry.pcrlock.d/cmdline-initrd-$suffix.pcrlock"
	rm "$tmpdir/cmdline"
	rm "$tmpdir/cmdline.pcrlock"
	rm "$tmpdir/initrd.pcrlock"

	# 710-kernel-cmdline-boot-loader.pcrlock.d is not part of the
	# pcrlock standards
	echo -ne "$cmdline\0" > "$tmpdir/cmdline"
	iconv -t UTF-16LE -o "$tmpdir/cmdline.utf16" "$tmpdir/cmdline"
	pcrlock \
		lock-raw \
		--pcr=12 \
		--pcrlock="/var/lib/pcrlock.d/710-kernel-cmdline-boot-loader.pcrlock.d/cmdline-$suffix.pcrlock" \
		"$tmpdir/cmdline.utf16"
	rm "$tmpdir/cmdline.utf16"
}

pcrlock_grub2_bls_kernel_initrd()
{
	local linux="$1"
	local initrd="$2"
	local suffix="$3"

	local elements=("$linux" "$initrd")
	local locks=()
	local n=0
	for element in "${elements[@]}"; do
		n=$((n+1))
		pcrlock \
			lock-raw \
			--pcr=9 \
			--pcrlock "$tmpdir/element-$n.pcrlock" \
			"$element" 2> /dev/null || pcrlock_manual_raw 9 "$tmpdir/element-$n.pcrlock" "$element"
		locks+=("$tmpdir/element-$n.pcrlock")
	done
	mkdir -p /var/lib/pcrlock.d/710-grub2-bls-kernel-initrd-entry.pcrlock.d
	jq --slurp '{"records": [.[].records[0]]}' \
	   "${locks[@]}" \
	   > "/var/lib/pcrlock.d/710-grub2-bls-kernel-initrd-entry.pcrlock.d/kernel-initrd-$suffix.pcrlock"
	rm "${locks[@]}"
}

pcrlock_grub2_bls_cmdline()
{
	local linux="$1"
	local cmdline="$2"
	local initrd="$3"
	local suffix="$4"
	local lines=("$linux" "$cmdline" "$initrd")

	local locks=()
	local n=0
	for line in "${lines[@]}"; do
		n=$((n+1))
		echo -n "$line" > "$tmpdir/line"
		pcrlock \
			lock-raw \
			--pcr=8 \
			--pcrlock "$tmpdir/line-$n.pcrlock" \
			"$tmpdir/line"
		locks+=("$tmpdir/line-$n.pcrlock")
		rm "$tmpdir/line"
	done
	mkdir -p /var/lib/pcrlock.d/650-grub2-bls-entry-cmdline.pcrlock.d
	jq --slurp '{"records": [.[].records[0]]}' \
	   "${locks[@]}" \
	   > "/var/lib/pcrlock.d/650-grub2-bls-entry-cmdline.pcrlock.d/cmdline-$suffix.pcrlock"
	rm "${locks[@]}"
}

pcrlock_grub2_bls_entry_files()
{
	local suffix="${1:+-$1}"
	local base="${2:-$boot_root}"
	local locks=()
	local n=0
	for i in "$base"/loader/entries/*.conf; do
		n=$((n+1))
		pcrlock \
			lock-raw \
			--pcr=9 \
			--pcrlock="$tmpdir/entry-$n.pcrlock" \
			"$i"
		locks+=("$tmpdir/entry-$n.pcrlock")
	done
	mkdir -p /var/lib/pcrlock.d/643-grub2-bls-entry-files.pcrlock.d
	jq --slurp '{"records": [.[].records[0]]}' \
	   "${locks[@]}" \
	   > "/var/lib/pcrlock.d/643-grub2-bls-entry-files.pcrlock.d/generated$suffix.pcrlock"
	rm "${locks[@]}"
}

pcrlock_sdboot()
{
	info "Generating TPM2 predictions with systemd-pcrlock (systemd-boot)"

	# 641-sdboot-loader-conf.pcrlock is not part of the pcrlock
	# standards
	if [ -e "${boot_root}/loader/loader.conf" ]; then
		shift_component 641-sdboot-loader-conf
		pcrlock \
			lock-raw "${boot_root}/loader/loader.conf" \
			--pcr=5 \
			--pcrlock=/var/lib/pcrlock.d/641-sdboot-loader-conf.pcrlock.d/generated.pcrlock
	fi

	# 650-kernel-efi-application.pcrlock is not part of the
	# pcrlock standards
	# TODO: move to kernel-TYPE-pcrlock.rpm
	shift_component 650-kernel-efi-application
	local n=0
	local -A kernels
	while read -r linux; do
		[ -f "${boot_root}$linux" ] || {
			info "Missing ${boot_root}$linux, ignoring entry for prediction"
			continue
		}
		[ -z "${kernels["$linux"]}" ] || continue
		kernels["$linux"]=1
		n=$((n+1))
		# Limit to 4 because of the separator
		[ "$n" -le 4 ] || {
			info "More than 4 variations for 650-kernel-efi-application"
			continue
		}
		pcrlock \
			lock-pe \
			--pcrlock=/var/lib/pcrlock.d/650-kernel-efi-application.pcrlock.d/linux-"$n".pcrlock \
			"${boot_root}/$linux"
	done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | map(.linux) | .[]' "$entryfile")

	# Join the cmdline and the initrd in a single component
	shift_component 710-kernel-cmdline-initrd-entry
	shift_component 710-kernel-cmdline-boot-loader
	n=0
	while read -r cmdline; do
		read -r initrd
		[ -f "${boot_root}$initrd" ] || {
			info "Missing ${boot_root}$initrd, ignoring entry for prediction"
			continue
		}
		n=$((n+1))
		[ "$n" -le 8 ] || {
			info "More than 8 variations for 710-kernel-cmdline-initrd-entry"
			continue
		}
		pcrlock_sdboot_cmdline_initrd "initrd=$cmdline" "${boot_root}$initrd" "$n"
	done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | .[] | ([(.initrd[0] | sub("/"; "\\"; "g")), .options] | join(" ")), .initrd[0]' "$entryfile")

	# Generate variation for 710-kernel-cmdline-initrd-entry
	# component that contains the current cmdline and the current
	# initrd, even if this will never be used again.  This is
	# required because disk-encryption-tool generates a new initrd
	# during the first boot, making the event log impossible to
	# align for systemd-pcrlock
	n=0
	if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
		while read -r cmdline; do
			read -r initrd
			n=$((n+1))
			pcrlock_sdboot_cmdline_initrd "initrd=$cmdline" "${tmpdir}$initrd" "0-$n"
		done < <(jq --raw-output '.[] | ([(.initrd[0] | sub("/"; "\\"; "g")), .options] | join(" ")), .initrd[0]' "$initialentryfile")
	fi
}

pcrlock_grub2_bls()
{
	info "Generating TPM2 predictions with systemd-pcrlock (grub2-bls)"

	# 641-grub2-bls-grubenv.pcrlock is not part of the pcrlock
	# standards
	if [ -e "${boot_root}${boot_dst}/grubenv" ]; then
		shift_component 641-grub2-bls-grubenv
		pcrlock \
			lock-raw "${boot_root}${boot_dst}/grubenv" \
			--pcr=9 \
			--pcrlock=/var/lib/pcrlock.d/641-grub2-bls-grubenv.pcrlock.d/generated.pcrlock
	fi

	# 643-grub2-bls-entry-files.pcrlock is not part of the pcrlock
	# standards
	shift_component 643-grub2-bls-entry-files
	pcrlock_grub2_bls_entry_files
	if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
		pcrlock_grub2_bls_entry_files "0" "$tmpdir"
	fi

	blkpart="$(findmnt -nvo SOURCE "$boot_root")"
	read -r partno < "/sys/class/block/${blkpart##*/}"/partition
	# Once we are out of the BIOS / EFI, the numeration cannot be
	# done without device.map.  It is safe to assume that the ESP
	# is always the first disk (hd0)
	grub2_bls_drive="(hd0,gpt$partno)"

	# Join linux, initrd and cmdline in a single pcrlock file
	shift_component 650-grub2-bls-entry-cmdline
	n=0
	while read -r options; do
		read -r linux
		read -r initrd
		[ -f "${boot_root}$linux" ] || {
			info "Missing ${boot_root}$linux, ignoring entry for prediction"
			continue
		}
		[ -f "${boot_root}$initrd" ] || {
			info "Missing ${boot_root}$initrd, ignoring entry for prediction"
			continue
		}
		n=$((n+1))
		[ "$n" -le 8 ] || {
			info "More than 8 variations for 650-grub2-bls-entry-cmdline"
			continue
		}
		pcrlock_grub2_bls_cmdline "linux ${grub2_bls_drive}$linux $options" \
					  "${grub2_bls_drive}$linux $options" \
					  "initrd ${grub2_bls_drive}$initrd" "$n"
	done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | .[] | .options, .linux, .initrd[0]' "$entryfile")

	# Generate variation for 650-grub2-bls-entry-cmdline component
	# that contains the current cmdline and the current initrd,
	# even if this will never be used again.  This is required
	# because disk-encryption-tool generates a new initrd during
	# the first boot, making the event log impossible to align for
	# systemd-pcrlock
	n=0
	if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
		while read -r options; do
			read -r linux
			read -r initrd
			n=$((n+1))
			pcrlock_grub2_bls_cmdline "linux ${grub2_bls_drive}$linux $options" \
						  "${grub2_bls_drive}$linux $options" \
						  "initrd ${grub2_bls_drive}$initrd" "0-$n"
		done < <(jq --raw-output '.[] | .options, .linux, .initrd[0]' "$initialentryfile")
	fi

	# With secure boot, grub2-bls invokes shim to extend PCR4
	if is_secure_boot; then
		# 650-kernel-efi-application.pcrlock is not part of
		# the pcrlock standards
		# TODO: move to kernel-TYPE-pcrlock.rpm
		shift_component 650-kernel-efi-application
		local n=0
		local -A kernels
		while read -r linux; do
			[ -f "${boot_root}$linux" ] || {
				info "Missing ${boot_root}$linux, ignoring entry for prediction"
				continue
			}
			[ -z "${kernels["$linux"]}" ] || continue
			kernels["$linux"]=1
			n=$((n+1))
			# Limit to 4 because of the separator
			[ "$n" -le 4 ] || {
				info "More than 4 variations for 650-kernel-efi-application"
				continue
			}
			pcrlock \
				lock-pe \
				--pcrlock=/var/lib/pcrlock.d/650-kernel-efi-application.pcrlock.d/linux-"$n".pcrlock \
				"${boot_root}/$linux"
		done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | map(.linux) | .[]' "$entryfile")
	fi

	# Join the kernel and the initrd in a single component
	shift_component 710-grub2-bls-kernel-initrd-entry
	n=0
	while read -r linux; do
		read -r initrd
		[ -f "${boot_root}$linux" ] || continue
		[ -f "${boot_root}$initrd" ] || continue
		n=$((n+1))
		[ "$n" -le 8 ] || continue
		pcrlock_grub2_bls_kernel_initrd "${boot_root}$linux" "${boot_root}$initrd" "$n"
	done < <(jq --raw-output 'sort_by(.priority, (.kernel | map(-.))) | .[] | .linux, .initrd[0]' "$entryfile")

	# Generate variation for 710-grub2-bls-kernel-initrd-entry for the
	# same reason than before.
	n=0
	if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
		while read -r linux; do
			read -r initrd
			n=$((n+1))
			pcrlock_grub2_bls_kernel_initrd "${tmpdir}$linux" "${tmpdir}$initrd" "0-$n"
		done < <(jq --raw-output '.[] | .linux, .initrd[0]' "$initialentryfile")
	fi
}

clean_pcrlock_d()
{
	[ -d /var/lib/pcrlock.d ] || return 0

	# Remove the shifted measurements since the last reboot.  They
	# are used to link the current components with the event log,
	# so pcrlock can work with the aligments.  For example, if a
	# file gets replaced (loader.conf) the new measurement cannot
	# be found in the event log, as contains the old hash, making
	# the aligment fail.
	local btime
	read -r _ btime < <(grep btime /proc/stat)
	local minutes=$((1 + ($(date +%s) - btime) / 60))
	dbg "Cleaning shifted measurements older than $minutes minutes"
	find /var/lib/pcrlock.d -name 'shift-*.pcrlock' -type f -cmin +"$minutes" -delete

	# Remove older (1 week) generated measurements.  This will
	# keep the predictions at minimum and decrease the
	# combinations.  Removing all can be a problem in certain
	# conditions.  For example, after the first boot some pcrlock
	# files contain hashes for the original ESP assets, that are
	# required for the event log aligment.
	find /var/lib/pcrlock.d -name '*.pcrlock' -type f -mtime +7 -delete

	# Sometimes, like in tests, the user will generate new entries
	# and reboot in a short period of time
	if [ "$(find /var/lib/pcrlock.d -type f -name '*-7.pcrlock' | wc -l)" -gt 0 ]; then
		rm -fr /var/lib/pcrlock.d
	fi
}

generate_tpm2_predictions_pcrlock()
{
	local pcrs="$FDE_SEAL_PCR_LIST"

	info "Generating TPM2 predictions with systemd-pcrlock"

	# Select the affected entries
	select_entries_for_prediction

	clean_pcrlock_d

	shift_component 250-firmware-code-early
	shift_component 550-firmware-code-late
	pcrlock lock-firmware-code

	shift_component 250-firmware-config-early
	shift_component 550-firmware-config-late
	pcrlock lock-firmware-config

	# If secure boot is disabled, this can fail.  There is patch
	# for the policy generation, and for the authority is planned
	shift_component 240-secureboot-policy
	pcrlock lock-secureboot-policy &> /dev/null || true
	shift_component 620-secureboot-authority
	pcrlock lock-secureboot-authority &> /dev/null || true

	# Uses / by default, but firmware measures GPT of the disk a
	# boot application was loaded from, which is effectively the
	# disk where our ESP is located.
	shift_component 600-gpt
	pcrlock lock-gpt "$boot_root"

	# Measure the boot loader.  Combinations:
	#   - Removable media with shim: BOOTX64.EFI, grub.efi
	#   - Removable media w/out shim: BOOTX64.EFI
	#   - Installed system with shim: shim.efi, grub.efi
	#   - Installed system w/out shim and grub2-bls: grub.efi
	#   - Installed system w/out shim and systemd-boot: systemd-bootx64.efi
	local shim_path bootloader_path
	if is_shim_installed; then
		if [ -n "$arg_portable" ]; then
			shim_path="${boot_root}/EFI/BOOT/BOOT${firmware_arch^^}.EFI"
			bootloader_path="${boot_root}/EFI/BOOT/grub.efi"
		else
			shim_path="${boot_root}${boot_dst}/shim.efi"
			bootloader_path="${boot_root}${boot_dst}/grub.efi"
		fi
	else
		local bootloader_filename
		if is_sdboot; then
			bootloader_filename="systemd-boot${firmware_arch,,}.efi"
		elif is_grub2_bls; then
			bootloader_filename="grub.efi"
		fi
		if [ -n "$arg_portable" ]; then
			bootloader_path="${boot_root}/EFI/BOOT/${bootloader_filename}"
		else
			bootloader_path="${boot_root}${boot_dst}/${bootloader_filename}"
		fi
	fi

	if [ -n "$shim_path" ]; then
		# 630-shim-efi-application is not part of the pcrlock
		# standards
		# TODO: move to shim-pcrlock.rpm
		shift_component 630-shim-efi-application
		pcrlock \
			lock-pe \
			--pcrlock=/var/lib/pcrlock.d/630-shim-efi-application.pcrlock.d/generated.pcrlock \
			"${shim_path}"
	fi

	# 640-boot-loader-efi-application is not part of the
	# pcrlock standards
	# This is measuring the grub / systemd-boot EFI binary
	# TODO: move to systemd-boot-pcrlock.rpm / grub2-bls.rpm
	shift_component 640-boot-loader-efi-application
	pcrlock \
		lock-pe \
		--pcrlock=/var/lib/pcrlock.d/640-boot-loader-efi-application.pcrlock.d/generated.pcrlock \
		"${bootloader_path}"

	if is_sdboot; then
		pcrlock_sdboot
	elif is_grub2_bls; then
		pcrlock_grub2_bls
	fi

	# If the prediction fails, the system will ask for a password,
	# but we can do a re-enrollment using the recovery PIN.  To
	# register a recovery PIN the installer (sdbootutil-enroll,
	# YaST) will call this script deploying in the %u keyring
	# "sdbootutil[-pin]" entry.  For re-enrollments we can use the
	# same entry, the PIN environment variable, or the
	# --ask-{key,pin,pw} parameter.
	local pin
	local extra=()
	local keyid keyid_int
	keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
	keyid_int="$(keyctl id %user:sdbootutil-pin 2> /dev/null)" || true
	if [ -n "$arg_ask_key_pin_or_pw" ]; then
		ask_password "Recovery PIN" pin
		extra=("--recovery-pin=yes")
	elif [ -n "$PIN" ]; then
		pin="$PIN"
		extra=("--recovery-pin=yes")
	elif [ -n "$keyid_int" ]; then
		pin="$(keyctl pipe "$keyid_int")"
		extra=("--recovery-pin=yes")
	elif [ -n "$keyid" ]; then
		pin="$(keyctl pipe "$keyid")"
		extra=("--recovery-pin=yes")
	else
		# No PIN was provided, systemd-pcrlock will generate
		# one
		extra=("--recovery-pin=show")
	fi

	local output
	output="$(PIN="$pin" pcrlock --pcr="$pcrs" "${extra[@]}" make-policy)"
	local pcrlock_status=$?

	if [ $pcrlock_status -ne 0 ]; then
		echo "Error creating the systemd-pcrlock policy!"
		return 1
	elif echo "$output" | grep -q "recovery PIN"; then
		local split
		IFS=":" read -r -a split <<<"$output"
		pin="${split[1]}"
		pin="${pin## }"
		pin="${pin%% }"

		echo "Recovery PIN: $pin"
		if [ -x /usr/bin/qrencode ]; then
			echo "You can also scan it with your mobile phone:"
			qrencode -t utf8i "$pin"
		fi

		# Add the generated recovery PIN to the kernel
		# keyring, so that it is available to `sdbootutil
		# enroll --method=recovery-key`
		keyctl_add_with_timeout "sdbootutil-pin" "$pin"
	fi

	# Publish the assets in the ESP, so can be imported by
	# dracut-pcr-signature
	[ -e /var/lib/systemd/pcrlock.json ] && \
		cp /var/lib/systemd/pcrlock.json "${boot_root}${boot_dst}" && {
			echo "NVIndex policy created"
		}
}

get_pcrs()
{
	local pcrs
	local jq_pcr='.tokens[]|select(.type == "systemd-tpm2")|."tpm2_pubkey_pcrs"|join(",")'
	# We can have multiple devices, each one of them with
	# different PCRs
	while read -r dev; do
		pcrs=$(cryptsetup luksDump --dump-json-metadata "$dev" | jq -r "$jq_pcr")
		[ -z "$pcrs" ] || echo "$pcrs"
	done <<<"$(blkid -t TYPE=crypto_LUKS -o device)"
}

generate_tpm2_predictions_pcr_oracle()
{
	local entry
	local all_pcrs

	info "Generating TPM2 predictions with pcr-oracle"

	# Select the affected entries
	select_entries_for_prediction

	all_pcrs=$(get_pcrs)
	[ -n "$all_pcrs" ] || all_pcrs="$FDE_SEAL_PCR_LIST"

	rm -f /etc/systemd/tpm2-pcr-signature.json

	# We make as many predictions as |all_pcrs| * |entries| to
	# cover all the combinations.  pcr-oracle is smart to include
	# the entry only one time, so we will not have duplications.
	# This is a step for multi device configurations.
	local -a entries
	mapfile -t entries < <(jq -r '.[]|.id' "$entryfile")
	if [ -z "${entries[0]}" ]; then
		err "No bootloader entries found"
	fi
	for pcrs in $all_pcrs; do
		for entry in "${entries[@]}"; do
			info "Generate prediction for $entry with PCRs $pcrs"
			if ! pcr-oracle \
			     --private-key /etc/systemd/tpm2-pcr-private-key.pem \
			     --from eventlog \
			     --output /etc/systemd/tpm2-pcr-signature.json \
			     --target-platform=systemd \
			     --boot-entry "${entry}" \
			     sign "$pcrs"; then
				err "Failed to install TPM predictions for ${entry}"
			fi
		done
	done

	# Publish the assets in the ESP, so can be imported by
	# dracut-pcr-signature
	cp /etc/systemd/tpm2-pcr-public-key.pem "${boot_root}${boot_dst}"
	[ -e /etc/systemd/tpm2-pcr-signature.json ] && \
		cp /etc/systemd/tpm2-pcr-signature.json "${boot_root}${boot_dst}" && {
			echo "Signed policy created"
		}
}

get_volume_password()
{
	local dev="$1"
	local pw keyid
	# If we are enrolling for the first time, sdbootutil-pin can
	# contain the recovery PIN, that cannot be used to unlock the
	# device (unless later it is done an enrollment of a new
	# recovery key)
	keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
	keyid_ce="$(keyctl id %user:cryptenroll 2> /dev/null)" || true
	if [ -n "$keyid_ce" ]; then
		pw="$(keyctl pipe "$keyid_ce")"
	elif [ -n "$keyid" ]; then
		pw="$(keyctl pipe "$keyid")"
	else
		ask_password "Password for $dev" pw
		# If the key was missing for all the keyrings put back
		# into the cryptenroll keyring, as there is a chance
		# that this is part or a re-enrollment
		keyctl_add_with_timeout "cryptenroll" "$pw"
	fi
	echo "$pw"
}

get_volume_key()
{
	local dev="$1"
	local pw out
	pw="$(get_volume_password "$dev")"
	out="$(cryptsetup luksDump --batch-mode --dump-master-key "$dev" <<<"$pw")" || {
		# If luksDump fails, remove the password from the
		# keyring.  Can be that the password was wrong, and
		# systemd-cryptenroll ask later for the password.
		# Both passwords can appear in the keyring separated
		# by NULL
		keyctl revoke %user:cryptenroll &> /dev/null || true
		keyctl reap &> /dev/null || true

		# Try one more time
		pw="$(get_volume_password "$dev")"
		out="$(cryptsetup luksDump --batch-mode --dump-master-key "$dev" <<<"$pw")" || return 1
	}
	echo "$out" | sed -n '/MK dump:/,$p' | sed -E 's/MK dump:|[[:blank:]]+//g' | sed -z 's/\n//g'
}

extend_pcr()
{
	local dgst="$1"
	local pcr="$2"
	local val="$3"
	local digest

	hash "${dgst}sum" || return
	hex_to_binary "$pcr$val" > "$tmpdir/pcr"
	read -r digest _ < <("${dgst}sum" "$tmpdir/pcr")
	echo "$digest"
}

generate_tpm2_predictions_pcr_15()
{
	local devs=()
	local msgs=()
	local vks=()
	local name dev opts extra uuid pw

	grep -q "tpm2-measure-pcr=yes" /etc/crypttab || return

	info "Generating predictions for PCR15"

	# Read /etc/crypttab lines that contains tpm2-device and
	# tpm2-measure-pcr.  This code is the similar from
	# measure-pcr-generator.sh, so we guarantee the same ordering
	# for PCR 15 extension
	while read -r name dev _ opts; do
		# Only the entries in /etc/crypttab in the initrd
		# (marked with x-initrd.attach) should participate
		# from the extension for now.  The reason is that
		# extensions after the switch root cannot participate
		# in abort the boot process from initrd itself
		#
		# Note that dracut will add the cr_swap partition even
		# if it is not marked as x-initrd.attach
		[[ "$name" = \#* ]] && continue
		[[ "$opts" != *"tpm2-device="* ]] && continue
		[[ "$opts" != *"tpm2-measure-pcr="* ]] && continue

		# If the device name is UUID= convert as a real device
		# name, and if not, retrieve the UUID
		if [[ "$dev" = "UUID="* ]]; then
			uuid="${dev#"UUID="}"
			dev="$(blkid --uuid "$uuid")"
		else
			uuid="$(blkid "$dev" -o value -s UUID)"
		fi

		# Get the FSTYPE of the real device (crypto_LUKS) and
		# the slave / holder one (btrfs, swap, etc).  Also get
		# the mount point so we can identify /etc or /var.
		#
		# According to https://systemd.io/MOUNT_REQUIREMENTS/
		# /etc is mounted in initrd, and /var is mounted after
		# initrd, but in MicroOS (via microos-tools) is adding
		# /var to be mounted in the initrd stage too because
		# of selinux (98selinux-microos).
		extra="$(lsblk --noheadings -o FSTYPE,MOUNTPOINT "$dev")"
		if [[ "$extra" != *"swap"* ]] && [[ "$extra" != *"/etc"* ]] && { [[ "$extra" != *"/var"* ]] || ! is_transactional; }; then
			[[ "$opts" != *"x-initrd.attach"* ]] && continue
		fi

		dbg "Adding $dev (cryptsetup:$name:$uuid) for PCR15"

		devs+=("$dev")
		msgs+=("cryptsetup:$name:$uuid")
	done < /etc/crypttab
	# We need to separate this into a different loop because we
	# cannot nest two reads (one for crypttab and another for the
	# password)
	for dev in "${devs[@]}"; do
		local vk
		vk="$(get_volume_key "$dev")"
		[ -n "$vk" ] || { warn "Volume key cannot be extracted. Dropping PCR 15"; return 0; }
		vks+=("$vk")
	done

	rm -f /var/lib/sdbootutil/measure-pcr-prediction
	rm -f /var/lib/sdbootutil/measure-pcr-prediction.sha256
	local dgsts=("sha1" "sha256" "sha384" "sha512")
	local sizes=(40 64 96 128)
	local pcr15 hmac

	for i in "${!dgsts[@]}"; do
		pcr15="$(printf '0%.0s' $(seq 1 "${sizes[i]}"))"
		# Add the 0x00...0 predictions as a valid one.
		# Workaround the systemd-cryptsetup issue for LVM
		# devices
		echo "$pcr15" >> "/var/lib/sdbootutil/measure-pcr-prediction"
		for j in "${!msgs[@]}"; do
			hmac="$(echo -ne "${msgs[j]}" | /usr/libexec/sdbootutil/uhmac "${dgsts[i]}" <(echo "${vks[j]}"))"
			pcr15="$(extend_pcr "${dgsts[i]}" "$pcr15" "$hmac")"
			dbg "${msgs[j]} (${dgsts[i]}): $hmac"
		done
		echo "$pcr15" >> "/var/lib/sdbootutil/measure-pcr-prediction"
	done

	if [ -f "/var/lib/sdbootutil/measure-pcr-prediction" ] && [ -f "/var/lib/sdbootutil/measure-pcr-private.pem" ]; then
		openssl dgst -sha256 \
			-sign /var/lib/sdbootutil/measure-pcr-private.pem \
			-out /var/lib/sdbootutil/measure-pcr-prediction.sha256 \
			/var/lib/sdbootutil/measure-pcr-prediction
	fi

	# Register the hash of the parsed crypttab
	local crypttab_sha1
	read -r crypttab_sha1 _ < <(sha1sum /etc/crypttab)
	echo "$crypttab_sha1" > /var/lib/sdbootutil/crypttab.sha1

	# Publish the assets in the ESP, so can be imported by
	# dracut-pcr-signature
	[ ! -e /var/lib/sdbootutil/measure-pcr-prediction ] || \
		cp /var/lib/sdbootutil/measure-pcr-prediction "${boot_root}${boot_dst}"
	[ ! -e /var/lib/sdbootutil/measure-pcr-prediction.sha256 ] || \
		cp /var/lib/sdbootutil/measure-pcr-prediction.sha256 "${boot_root}${boot_dst}"
}

updated_crypttab()
{
	local crypttab_sha1
	read -r crypttab_sha1 _ < <(sha1sum /etc/crypttab)
	grep -Fixq "$crypttab_sha1" /var/lib/sdbootutil/crypttab.sha1 2> /dev/null
}

generate_tpm2_predictions()
{
	[ -e /etc/crypttab ] || return 0
	grep -q "tpm2-device" /etc/crypttab || return 0
	! in_lockout || err "The TPM2 is in lockout. Use 'tpm2_dictionarylockout -c [ -p passwd ]' to clear the DA lockout and re-try the sdbootutil command"

	# The PCR list is used by both models (pcr-oracle,
	# systemd-pcrlock).  The first one will try first to get the
	# PCRs from the LUKS2 header, that will succeed in the normal
	# situation but fail during the initial enrollment.  This is
	# because we generate the prediction first and later we do the
	# enrollment
	is_config_file || err "/etc/sysconfig/fde-tools not found"
	load_config_file
	[ -z "$arg_pcr" ] || FDE_SEAL_PCR_LIST="$arg_pcr"

	if is_pcr_oracle; then
		generate_tpm2_predictions_pcr_oracle
	elif have_pcrlock; then
		generate_tpm2_predictions_pcrlock || return 1
	fi

	# Generate a PCR 15 prediction only in certain cases, as for
	# now this will ask the password (can be resolved by an
	# external tool that extract the password from the TPM2 if the
	# policy is still valid)
	updated_crypttab || { generate_tpm2_predictions_pcr_15; arg_measure_pcr=; }
	# shellcheck disable=SC2015
	[ -f "/var/lib/sdbootutil/measure-pcr-prediction" ] && [ -z "$arg_measure_pcr" ] || generate_tpm2_predictions_pcr_15
	# The user can remove measure-pcr-prediction file from the
	# ESP, but generate_tpm2_predictions_pcr_15 will not be called
	[ -f "${boot_root}${boot_dst}/measure-prediction" ] || {
		[ ! -e /var/lib/sdbootutil/measure-pcr-prediction ] || \
			cp /var/lib/sdbootutil/measure-pcr-prediction "${boot_root}${boot_dst}"
		[ ! -e /var/lib/sdbootutil/measure-pcr-prediction.sha256 ] || \
			cp /var/lib/sdbootutil/measure-pcr-prediction.sha256 "${boot_root}${boot_dst}"
	}
}

have_tracked_devices()
{
	[ "${#tracked_devices[@]}" -gt 0 ]
}

detect_tracked_devices()
{
	# A LUKS2 device can be un-tracked (ignored) by sdbootutil if
	# is present in /etc/crypttab and has the
	# "x-sdbootutil.ignore" option
	local dev fstype uuid
	! have_tracked_devices || return 0

	dbg_cat "/etc/crypttab"

	while read -r dev fstype uuid; do
		[ "$fstype" = 'crypto_LUKS' ] || continue
		cryptsetup isLuks --type luks2 "$dev" || continue
		[ -e /etc/crypttab ] && grep -E -q "${dev}[[:space:]].*x-sdbootutil.ignore" /etc/crypttab && continue
		[ -e /etc/crypttab ] && grep -E -q "${uuid}[[:space:]].*x-sdbootutil.ignore" /etc/crypttab && continue
		dbg "Tracking encrypted device $dev"
		tracked_devices+=("$dev")
	done < <(lsblk --noheadings -o PATH,FSTYPE,UUID)
	have_tracked_devices
}

have_tpm2()
{
	[ -n "$(systemd-cryptenroll --tpm2-device=list 2>/dev/null)" ]
}

have_fido2()
{
	[ -n "$(systemd-cryptenroll --fido2-device=list 2>/dev/null)" ]
}

have_slot()
{
	local dev="${1:?}"
	local kind="${2:?}"
	grep -q "$kind" < <(systemd-cryptenroll "$dev")
}

in_lockout()
{
	hash tpm2_getcap &> /dev/null || { warn "tpm2_getcap not found"; return 1; }
	tpm2_getcap properties-variable | grep -q 'inLockout: *1'
}

add_crypttab_option()
{
	# This version will share the same options for all crypto_LUKS
	# devices.  This imply that all of them will be unlocked by
	# the same TPM2, or the same FIDO2 key.
	#
	# The ones ignored will be untouched.
	local option="$1"

	dbg "Adding \"$option\" to /etc/crypttab"
	dbg_cat "/etc/crypttab"

	local crypttab
	crypttab="$(mktemp -t crypttab.XXXXXX)"
	echo "# File created by sdbootutil.  Comments will be removed" > "$crypttab"
	echo "# Add the 'x-sdbootutil.ignore' option to un-track a device" >> "$crypttab"

	local name
	local device
	local key
	local opts
	while read -r name device key opts; do
		[[ "$name" = \#* ]] && continue
		if [[ "$opts" != *"x-sdbootutil.ignore"* ]] && [[ "$opts" != *"$option"* ]]; then
			[ -z "$opts" ] && opts="$option" || opts="$opts,$option"
			# crypttab has changed so initrd needs to be
			# updated
			arg_no_reuse_initrd=1
		fi
		echo "$name $device ${key:-none} $opts" >> "$crypttab"
	done < /etc/crypttab

	mv -Z "$crypttab" /etc/crypttab
	chmod 644 /etc/crypttab

	dbg "Added \"$option\" to /etc/crypttab"
	dbg_cat "/etc/crypttab"
}

remove_crypttab_option()
{
	# Similar to add_crypttab_option, the option will be removed
	# from all the entries.
	#
	# The ones ignored will be untouched.
	local option="$1"

	dbg "Removing \"$option\" to /etc/crypttab"
	dbg_cat "/etc/crypttab"

	local crypttab
	crypttab="$(mktemp -t crypttab.XXXXXX)"
	echo "# File created by sdbootutil.  Comments will be removed" > "$crypttab"
	echo "# Add the 'x-sdbootutil.ignore' option to un-track a device" >> "$crypttab"

	local name
	local device
	local key
	local opts
	while read -r name device key opts; do
		[[ "$name" = \#* ]] && continue
		if [[ "$opts" != *"x-sdbootutil.ignore"* ]] && [[ "$opts" = *"$option"* ]]; then
			opts="${opts#"$option",}"
			opts="${opts//,"$option"}"
			opts="${opts//"$option"}"
			# crypttab has changed so initrd needs to be
			# updated
			arg_no_reuse_initrd=1
		fi
		[ -n "$opts" ] && echo "$name $device ${key:-none} $opts" >> "$crypttab"
		[ -z "$opts" ] && echo "$name $device ${key:-none}" >> "$crypttab"
	done < /etc/crypttab

	mv -Z "$crypttab" /etc/crypttab
	chmod 644 /etc/crypttab

	dbg "Removed \"$option\" to /etc/crypttab"
	dbg_cat "/etc/crypttab"
}

generate_rsa_key()
{
	pcr-oracle \
		--rsa-generate-key \
		--private-key /etc/systemd/tpm2-pcr-private-key.pem \
		--public-key /etc/systemd/tpm2-pcr-public-key.pem \
		store-public-key
}

set_unlock_method()
{
	local dev="$1"

	unlock_method=

	# If %user:cryptenroll is set, use it as an automatic
	# unlocker, if not, try the TPM2 or the FIDO2 key
	local keyid
	keyid="$(keyctl id %user:cryptenroll 2> /dev/null)" || true
	[ -z "$keyid" ] || return 0

	# Do not use TPM2 slot for enrolling TPM2
	if [ "$arg_method" != "tpm2" ] && [ "$arg_method" != "tpm2+pin" ] && have_slot "$dev" "tpm2"; then
		info "Unlocking using TPM2"
		unlock_method="--unlock-tpm2-device=auto"
	# Same for FIDO2
	elif [ "$arg_method" != "fido2" ] && have_slot "$dev" "fido2"; then
		info "Unlocking using FIDO2"
		unlock_method="--unlock-fido2-device=auto"
	fi
}

enroll_pcrlock()
{
	local dev="$1"
	local tpm2_pin="$2"
	local extra_args=()

	if [ -z "$tpm2_pin" ]; then
		info "Enrolling with TPM2 (pcrlock): $dev"
	else
		info "Enrolling with TPM2+PIN (pcrlock): $dev"
		extra_args+=(--tpm2-with-pin=1)
	fi

	if [ ! -f /var/lib/systemd/pcrlock.json ]; then
		warn "Could not find /var/lib/systemd/pcrlock.json"
	fi

	set_unlock_method "$dev"
	if [ -n "$unlock_method" ]; then
		extra_args+=("$unlock_method")
	fi

	# Note that the PCRs are now not stored in the LUKS2 header
	if NEWPIN="$tpm2_pin" systemd-cryptenroll \
		 --wipe-slot=tpm2 \
		 --tpm2-device=auto \
		 "${extra_args[@]}" \
		 --tpm2-pcrlock=/var/lib/systemd/pcrlock.json \
		 "$dev"; then
		# systemd-cryptenroll exits successfully even if the
		# token was not enrolled.  Manually check if the
		# device has a tpm2 slot enrolled
		systemd-cryptenroll "$dev" | grep -q "tpm2"
	else
		return 1
	fi

	# Not all PCR predictions match the current system state and
	# will be excluded from the policy. Trigger an update on the
	# next boot which should include them
	systemctl --quiet enable sdbootutil-update-predictions.service || true
}

enroll_pcroracle()
{
	local dev="$1"
	local tpm_pin="$2"
	local extra_args=()

	if [ -z "$tpm_pin" ]; then
		info "Enrolling with TPM2 (pcr-oracle): $dev"
	else
		info "Enrolling with TPM2+PIN (pcr-oracle): $dev"
		extra_args+=(--tpm2-with-pin=1)
	fi

	if [ ! -f /etc/systemd/tpm2-pcr-signature.json ]; then
		warn "Could not find /etc/systemd/tpm2-pcr-signature.json"
	fi

	set_unlock_method "$dev"
	if [ -n "$unlock_method" ]; then
		extra_args+=("$unlock_method")
	fi

	# Note that the PCRs are now not stored in the LUKS2 header
	if NEWPIN="$tpm_pin" systemd-cryptenroll \
		 --wipe-slot=tpm2 \
		 --tpm2-device=auto \
		 "${extra_args[@]}" \
		 --tpm2-public-key=/etc/systemd/tpm2-pcr-public-key.pem \
		 --tpm2-public-key-pcrs="${FDE_SEAL_PCR_LIST}" \
		 "$dev"; then
		# systemd-cryptenroll exits successfully even if the
		# token was not enrolled.  Manually check if the
		# device has a tpm2 slot enrolled
		systemd-cryptenroll "$dev" | grep -q "tpm2"
	else
		return 1
	fi
}

enroll_fido2()
{
	local dev="$1"

	info "Enrolling with FIDO2: $dev"

	local extra_args=()
	set_unlock_method "$dev"
	if [ -n "$unlock_method" ]; then
		extra_args+=("$unlock_method")
	fi

	systemd-cryptenroll --wipe-slot=fido2 --fido2-device=auto "${extra_args[@]}" "$dev"
}

enroll_password()
{
	local dev="$1"
	local pw="$2"

	info "Enrolling with password: $dev"

	local extra_args=()
	set_unlock_method "$dev"
	if [ -n "$unlock_method" ]; then
		extra_args+=("$unlock_method")
	fi

	NEWPASSWORD="$pw" systemd-cryptenroll --wipe-slot=password --password "${extra_args[@]}" "$dev"
}

enroll_recovery_key()
{
	local dev="$1"

	info "Enrolling with recovery key: $dev"

	local extra_args=()
	set_unlock_method "$dev"
	if [ -n "$unlock_method" ]; then
		extra_args+=("$unlock_method")
	fi

	# If no recovery key is provided, systemd-cryptenroll will
	# generate one
	local key keyid keyid_int
	keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
	keyid_int="$(keyctl id %user:sdbootutil-pin 2> /dev/null)" || true
	if [ -n "$arg_ask_key_pin_or_pw" ]; then
		ask_new_password "recovery key" key
	elif [ -n "$KEY" ]; then
		key="$KEY"
	elif [ -n "$keyid_int" ]; then
		key="$(keyctl pipe "$keyid_int")"
	elif [ -n "$keyid" ]; then
		key="$(keyctl pipe "$keyid")"
	fi

	local generated_key=
	# This function will be called for every device.  Let systemd
	# generate a secure recovery key only the first time if there
	# is no recovery pin selected (%user:sdbootutil[-pin])
	if [ -z "$key" ]; then
		# systemd-cryptenroll will put in stdout the recovery
		# key, and the rest of the information in stderr
		key="$(systemd-cryptenroll --wipe-slot=recovery --recovery-key "${extra_args[@]}" "$dev" 2> /dev/null)"
		generated_key=1
	else
		# A recovery key has already been generated, use it
		# for all the devices.  systemd-cryptenroll always
		# generates a random recovery key, but we want $key.
		# Replace it by using cryptsetup, so we can still use
		# %u:cryptenroll in systemd-cryptenroll, and the
		# temporary recovery key in cryptsetup, to avoid
		# requesting a password
		local tmp_key
		tmp_key="$(systemd-cryptenroll --wipe-slot=recovery --recovery-key "${extra_args[@]}" "$dev" 2> /dev/null)"
		local split
		read -r -a split < <(systemd-cryptenroll "$dev" | grep recovery)
		local keyslot="${split[0]}"
		# cryptsetup can only read the new passphrase from a
		# keyfile
		local tmp_key_file
		tmp_key_file="$(mktemp -t key_file.XXXXXX)"
		echo -n "$key" > "$tmp_key_file"
		cryptsetup luksChangeKey --key-slot "$keyslot" --force-password "$dev" "$tmp_key_file" <<<"$tmp_key"
		shred "$tmp_key_file"
	fi

	# If we enroll a recovery key first, we can use the generated
	# key as a recovery PIN later when we enroll a TPM2[+PIN]
	# (note that the recovery PIN is not the same PIN for the
	# tpm2+pin method).
	#
	# If we enroll the recovery key after the TPM2 enrollment and
	# we send the recovery PIN via the keyring, then we can make
	# the recovery key the same as the recovery PIN.  But if the
	# PIN is missing from the keyring, then we missed the
	# synchronization and the key and the PIN are different.
	if [ -z "$keyid_int" ] && have_slot "$dev" "tpm2"; then
		warn "There is already a recovery PIN for the TPM2"
		warn "The recovery key and the recovery PIN are now different"
	fi

	if [ -n "$generated_key" ]; then
		echo "Recovery key: $key"
		if [ -x /usr/bin/qrencode ]; then
			echo "You can also scan it with your mobile phone:"
			qrencode -t utf8i "$key"
		fi
	fi

	# Make sure that the registered recovery key is in the kernel
	# keyring, so that it is available to systemd-cryptenroll
	keyid="$(keyctl id %user:cryptenroll 2> /dev/null)" || true
	if [ -z "$keyid" ] && [ -z "$unlock_method" ]; then
		keyctl_add_with_timeout "cryptenroll" "$key"
	fi
	# ... and to --method=tpm2[+pin] via a private keyring
	keyctl_add_with_timeout "sdbootutil-pin" "$key"
}

enroll_device()
{
	local dev="$1"
	local pin_or_pw="$2"

	case "$arg_method" in
		"tpm2"|"tpm2+pin")
			if ! is_pcr_oracle && have_pcrlock; then
				enroll_pcrlock "$dev" "$pin_or_pw" || {
					warn "Enrollment with systemd-pcrlock failed"
					warn "Re-trying with pcr-oracle"
					unenroll_pcrlock
					# This function generates
					# /etc/systemd/tpm2-pcr-public-key.pem
					# which is needed by
					# enroll_pcroracle
					generate_rsa_key
					enroll_pcroracle "$dev" "$pin_or_pw"
				}
			elif have_pcr_oracle; then
				enroll_pcroracle "$dev" "$pin_or_pw"
			else
				info "No TMP2 enrollment mechanism found"
			fi
			;;

		"fido2")
			enroll_fido2 "$dev"
			;;

		"password")
			enroll_password "$dev" "$pin_or_pw"
			;;

		"recovery-key")
			enroll_recovery_key "$dev"
			;;

		*)
			err "Unexpected parameter for --method=: $arg_method"
			;;
	esac
}

enroll()
{
	[ -e /etc/crypttab ] || { info "/etc/crypttab not found. No encrypted devices?"; return 0; }
	[ -e /usr/bin/systemd-cryptenroll ] || { info "systemd-cryptenroll not found"; return 0; }
	detect_tracked_devices || { info "No LUKS2 devices found"; return 0; }

	info "Enrolling devices ($arg_method): ${tracked_devices[*]}"

	have_pcrlock || have_pcr_oracle || err "Not systemd-pcrlock nor pcr-oracle found"

	# Prepare /etc/crypttab and update initrd if required
	case "$arg_method" in
		"tpm2"|"tpm2+pin")
			have_tpm2 || err "No TPM2 found found"
			! in_lockout || err "The TPM2 is in lockout. Use 'tpm2_dictionarylockout -c [ -p passwd ]' to continue"
			add_crypttab_option 'tpm2-device=auto'
			[ -n "$arg_no_measure_pcr" ] || add_crypttab_option 'tpm2-measure-pcr=yes'
			;;

		"fido2")
			have_fido2 || err "No FIDO2 key found"
			add_crypttab_option 'fido2-device=auto'
			;;
	esac

	# For predicting PCR 15 we need to sign a file.  Create the
	# public and private key if missing.  This (as the
	# /etc/crypttab change) can require a new initrd
	if [ "$arg_method" = "tpm2" ] || [ "$arg_method" = "tpm2+pin" ]; then
		local private="/var/lib/sdbootutil/measure-pcr-private.pem"
		local public="/var/lib/sdbootutil/measure-pcr-public.pem"
		[ -f "$private" ] || openssl genrsa -out "$private" 4096
		# Writes "writing RSA key" in stderr and -noout is not
		# doing what I was expecting
		[ -f "$public" ] || openssl rsa -in "$private" -pubout -out "$public" 2> /dev/null
	fi

	# If the crypttab file changed (that is expected), we need to
	# generate a new initrd
	if [ "$arg_no_reuse_initrd" = "1" ]; then
		install_all_kernels "$root_snapshot"
		# Avoid the call of generate_tpm2_predictions at the
		# end of the script
		update_predictions=
	fi

	# If systemd-pcrlock is missing then we need to use
	# pcr-oracle.  For this we need to generate the RSA keys
	# early.  Also we need to set the PCRs used to assest the
	# system status
	if [ "$arg_method" = "tpm2" ] || [ "$arg_method" = "tpm2+pin" ]; then
		load_config_file
		[ -z "$arg_pcr" ] || FDE_SEAL_PCR_LIST="$arg_pcr"

		if [ -z "${FDE_SEAL_PCR_LIST}" ]; then
			if systemd-detect-virt -q; then
				info "Virtualized systemd detected ($(systemd-detect-virt)). Dropping PCR0 and PCR2"
				FDE_SEAL_PCR_LIST=""
			else
				FDE_SEAL_PCR_LIST="0,2,"
			fi
			if is_sdboot; then
				FDE_SEAL_PCR_LIST+="4,7,9"
			elif is_grub2_bls; then
				FDE_SEAL_PCR_LIST+="4,7,8,9"
			else
				err "Bootloader not detected"
			fi
		fi

		is_config_file || echo "FDE_SEAL_PCR_LIST=${FDE_SEAL_PCR_LIST}" >> /etc/default/fde-tools

		if { ! have_pcrlock || [ -n "$arg_signed_policy" ]; } && have_pcr_oracle; then
			# Once the RSA key is present, then
			# is_pcr_oracle is true
			generate_rsa_key
		fi

		# During the initial enrollment it is expected that
		# for systemd-pcrlock the recovery PIN will be
		# extracted from the %u keyring "sdbootutil[-pin]"
		# entry
		generate_tpm2_predictions || {
			# If the generation of prediction fails and we
			# are trying with systemd-pcrlock, we try
			# again with pcr-oracle
			if ! is_pcr_oracle; then
				echo "Predictions with systemd-pcrlock failed"
				echo "Re-trying with pcr-oracle"
				unenroll_pcrlock
				# This function generates
				# /etc/systemd/tpm2-pcr-public-key.pem
				# which is needed by
				# generate_tpm2_predictions
				generate_rsa_key
				generate_tpm2_predictions
			fi
		}
	fi

	# For the PIN (tpm2+pin) or password (password), we can get it
	# from the %u keyring "sdbootutil" entry, the PIN or PW
	# environment variable, or introduced by the user
	local pin_or_pw keyid
	if [ "$arg_method" = "tpm2+pin" ]; then
		keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
		if [ -n "$arg_ask_key_pin_or_pw" ]; then
			ask_new_password "TPM2 PIN" pin_or_pw
		elif [ -n "$PIN" ]; then
			pin_or_pw="$PIN"
		elif [ -n "$keyid" ]; then
			pin_or_pw="$(keyctl pipe "$keyid")"
		else
			err "Use %u:sdbootutil, PIN or --ask-pin to provide the TPM2 PIN"
		fi
	elif [ "$arg_method" = "password" ]; then
		keyid="$(keyctl id %user:sdbootutil 2> /dev/null)" || true
		if [ -n "$arg_ask_key_pin_or_pw" ]; then
			ask_new_password "password" pin_or_pw
		elif [ -n "$PW" ]; then
			pin_or_pw="$PW"
		elif [ -n "$keyid" ]; then
			pin_or_pw="$(keyctl pipe "$keyid")"
		else
			err "Use %u:sdbootutil, PW or --ask-pw to provide the password"
		fi
	fi

	for dev in "${tracked_devices[@]}"; do
		enroll_device "$dev" "$pin_or_pw"
	done
}

unenroll_pcrlock()
{
	systemctl --quiet disable sdbootutil-update-predictions.service || true
	pcrlock remove-policy &> /dev/null || true
	rm -fr /var/lib/pcrlock.d
	rm -f /var/lib/systemd/pcrlock.json
	rm -f "${boot_root}${boot_dst}/pcrlock.json"
	rm -f /var/lib/sdbootutil/measure-pcr-prediction
	rm -f /var/lib/sdbootutil/measure-pcr-prediction.sha256
	rm -f "${boot_root}${boot_dst}/measure-pcr-prediction"
	rm -f "${boot_root}${boot_dst}/measure-pcr-prediction.sha256"
}

unenroll_pcr_oracle()
{
	rm -f /etc/systemd/tpm2-pcr-private-key.pem
	rm -f /etc/systemd/tpm2-pcr-public-key.pem
	rm -f /etc/systemd/tpm2-pcr-signature.json
	rm -f "${boot_root}${boot_dst}/tpm2-pcr-public-key.pem"
	rm -f "${boot_root}${boot_dst}/tpm2-pcr-signature.json"
	rm -f /var/lib/sdbootutil/measure-pcr-prediction
	rm -f /var/lib/sdbootutil/measure-pcr-prediction.sha256
	rm -f "${boot_root}${boot_dst}/measure-pcr-prediction"
	rm -f "${boot_root}${boot_dst}/measure-pcr-prediction.sha256"
}

unenroll_device()
{
	local dev="$1"

	case "$arg_method" in
		"tpm2"|"tpm2+pin")
			systemd-cryptenroll \
				--wipe-slot=tpm2 \
				"$dev"
			;;

		"fido2")
			systemd-cryptenroll \
				--wipe-slot=fido2 \
				"$dev"
			;;

		"password")
			systemd-cryptenroll \
				--wipe-slot=password \
				"$dev"
			;;

		"recovery-key")
			systemd-cryptenroll \
				--wipe-slot=recovery \
				"$dev"
			;;

		*)
			err "Unexpected parameter for --method=: $arg_method"
			;;
	esac
}

unenroll()
{
	[ -e /etc/crypttab ] || { info "/etc/crypttab not found. No encrypted devices?"; return 0; }
	[ -e /usr/bin/systemd-cryptenroll ] || { info "systemd-cryptenroll not found"; return 0; }
	detect_tracked_devices || { info "No LUKS2 devices found"; return 0; }

	info "Unenrolling devices ($arg_method): ${tracked_devices[*]}"

	# Prepare /etc/crypttab and update initrd if required
	case "$arg_method" in
		"tpm2"|"tpm2+pin")
			have_tpm2 || err "No TPM2 found found"
			remove_crypttab_option 'tpm2-device=auto'
			remove_crypttab_option 'tpm2-measure-pcr=yes'
			;;

		"fido2")
			have_fido2 || err "No FIDO2 key found"
			remove_crypttab_option 'fido2-device=auto'
			;;
	esac
	if [ "$arg_no_reuse_initrd" = "1" ]; then
		install_all_kernels "$root_snapshot"
		# Avoid the call of generate_tpm2_predictions at the
		# end of the script
		update_predictions=
	fi

	if [ "$arg_method" = "tpm2" ] || [ "$arg_method" = "tpm2+pin" ]; then
		unenroll_pcrlock
		unenroll_pcr_oracle
	fi

	for dev in "${tracked_devices[@]}"; do
		unenroll_device "$dev"
	done
}

eval_bootctl()
{
	# XXX: bootctl should have json output for that too
	# shellcheck disable=SC2016
	eval "$(bootctl 2>/dev/null | sed -ne 's/Firmware Arch: *\(\w\+\)/firmware_arch="\1"/p;s/ *token: *\(\w\+\)/entry_token="\1"/p;s, *\$BOOT: *\([^ ]\+\).*,boot_root="\1",p')"
}

bootloader_name()
{
	info "Checking the bootloader name"

	if is_sdboot "${1:-$root_snapshot}"; then
		echo "systemd-boot"
	elif is_grub2_bls "${1:-$root_snapshot}"; then
		echo "grub2-bls"
	else
		err "Bootloader not detected"
	fi
}

set_image_name() {
	[ -z "$image" ] || return

	declare -gA arch_image_map=(
		[x64]="vmlinuz"
		[aa64]="Image"
		[riscv64]="Image"
	)

	if [ -n "${arch_image_map[$firmware_arch]}" ]; then
		image="${arch_image_map[$firmware_arch]}"
	else
		err "Unsupported architecture $firmware_arch"
	fi
}

define_commands() {
	declare -gA commands=(
		[install]=""
		[needs-update]=""
		[update]=""
		[force-update]=""
		[add-kernel]="kernel"
		[remove-kernel]="kernel"
		[cleanup]=""
		[set-default-snapshot]=""
		[add-all-kernels]=""
		[mkinitrd]=""
		[remove-all-kernels]=""
		[is-installed]=""
		[list-snapshots]=""
		[list-entries]=""
		[list-kernels]=""
		[list-devices]=""
		[show-entry]="kernel"
		[update-entry]="kernel"
		[update-all-entries]=""
		[is-bootable]=""
		[set-default]="id"
		[get-default]=""
		[set-timeout]="seconds"
		[get-timeout]=""
		[enroll]=""
		[unenroll]=""
		[update-predictions]=""
		[bootloader]=""
	)
}

define_options() {
	declare -gA options_with_arg=(
		[help]=""
		[verbose]=""
		[esp-path]="_path"
		[entry-token]="_path"
		[arch]="_arch_name"
		[image]="_image_name"
		[entry-keys]="_find_kernels"
		[no-variables]=""
		[no-reuse-initrd]=""
		[no-random-seed]=""
		[all]=""
		[sync]=""
		[portable]=""
		[secure-boot]=""
		[removable]=""
		[only-default]=""
		[default-snapshot]=""
		[ask-key]=""
		[ask-pin]=""
		[ask-pw]=""
		[method]="_method"
		[signed-policy]=""
		[no-measure-pcr]=""
		[measure-pcr]=""
		[pcr]="_none"
		[devices]="_devices"
	)
	opts_long=""
	for opt in "${!options_with_arg[@]}"; do
		if [ "${options_with_arg[$opt]}" ]; then
			opts_long+="${opt}:,"
		else
			opts_long+="${opt},"
		fi
	done
	opts_long="${opts_long%,}"
}

####### main #######

if [ "$1" = "_print_bash_completion_data" ]; then
	declare -f set_image_name
	declare -f eval_bootctl
	declare -f define_commands
	declare -f define_options
	exit 0
fi

define_options
getopt_tmp=$(getopt -o hv --long "$opts_long" -n "${0##*/}" -- "$@")
eval set -- "$getopt_tmp"

while true ; do
	case "$1" in
		-h|--help) helpandquit ;;
		-v|--verbose) verbose=$((++verbose)); shift ;;
		--esp-path) arg_esp_path="$2"; shift 2 ;;
		--arch) arg_arch="$2"; shift 2 ;;
		--entry-token) arg_entry_token="$2"; shift 2 ;;
		--image) image="$2"; shift 2 ;;
		--entry-keys) IFS=',' read -r -a arg_entry_keys <<<"$2"; shift 2 ;;
		--no-variables) arg_no_variables=1; shift ;;
		--no-reuse-initrd) arg_no_reuse_initrd=1; shift ;;
		--no-random-seed) arg_no_random_seed=1; shift ;;
		--all) arg_all_entries=1; shift ;;
		--sync) arg_sync=1; shift ;;
		--portable) arg_portable=1; shift ;;
		--removable) arg_portable=1; shift ;;
		--secure-boot) arg_secure_boot=1; shift ;;
		--only-default) arg_only_default=1; shift ;;
		--default-snapshot) arg_default_snapshot=1; shift ;;
		--ask-key|--ask-pin|--ask-pw) arg_ask_key_pin_or_pw=1; shift ;;
		--method) arg_method="$2"; shift 2 ;;
		--signed-policy) arg_signed_policy=1; shift ;;
		--no-measure-pcr) arg_no_measure_pcr=1; shift ;;
		--measure-pcr) arg_measure_pcr=1; shift ;;
		--pcr) arg_pcr="$2"; shift 2 ;;
		--devices) IFS=',' read -r -a tracked_devices <<<"$2"; shift 2 ;;
		--) shift ; break ;;
		*) echo "Internal error!" ; exit 1 ;;
	esac
done

if [ -z "$SYSTEMD_LOG_LEVEL" ] && [ "${verbose:-0}" -gt 1 ]; then
	if [ "$verbose" -gt 2 ]; then
		SYSTEMD_LOG_LEVEL=debug
	else
		SYSTEMD_LOG_LEVEL=info
	fi
	export SYSTEMD_LOG_LEVEL
fi

define_commands
if [ -z "$1" ]; then
	helpandquit
elif [ -z ${commands["$1"]+yes} ]; then
	err "unknown command $1"
fi

[ -n "$arg_esp_path" ] && export SYSTEMD_ESP_PATH="$arg_esp_path"

eval_bootctl
read -r root_uuid root_device < <(findmnt / -v -r -n -o UUID,SOURCE)
root_device_is_crypt=
[ "$(lsblk --noheadings -o TYPE "$root_device")" = "crypt" ] && root_device_is_crypt=1
root_subvol=""
subvol_prefix=""
if [ "$(stat -f -c %T /)" = "btrfs" ] && [ -d /.snapshots ]; then
	have_snapshots=1
	root_subvol=$(btrfs subvol show / 2>/dev/null|head -1)
	subvol_prefix="${root_subvol%/.snapshots/*}"
fi
root_snapshot=""
if [ -n "$have_snapshots" ]; then
	if [ -n "$arg_default_snapshot" ]; then
		[ -s "$snapperfile" ] || update_snapper
		read -r root_snapshot <<<"$(jq -r '.root[]|select(.default==true)|.number' < "$snapperfile")"
	else
		root_snapshot="${root_subvol#"${subvol_prefix}"/.snapshots/}"
		root_snapshot="${root_snapshot%/snapshot}"
	fi
fi

if [ -n "$arg_esp_path" ] && [ "$boot_root" != "$arg_esp_path" ]; then
	err "mismatch of esp path"
fi
[ -n "$arg_arch" ] && firmware_arch="$arg_arch"

[ -n "$boot_root" ] || err "No ESP detected. Legacy system?"
[ -n "$root_uuid" ] || err "Can't determine root UUID"
[ -n "$root_subvol" ] || [ -z "$have_snapshots" ] || err "Can't determine root subvolume"
[ -n "$root_device" ] || err "Can't determine root device"
[ -n "$firmware_arch" ] || err "Can't determine firmware arch"
set_image_name

mountpoint -q "$boot_root" || err "$boot_root is not a valid mountpoint"

dbg_var "root_snapshot"
dbg_var "boot_root"

# shellcheck disable=SC1091
[ -e /etc/sysconfig/bootloader ] && . /etc/sysconfig/bootloader

if is_sdboot; then
	boot_dst="/EFI/systemd"
elif is_grub2_bls; then
	set_os_release "${root_snapshot}"
	# shellcheck disable=SC2154
	read -r -a name <<<"${os_release_NAME,,}"
	boot_dst="/EFI/${name[0]}"
else
	msg="Bootloader not detected"
	[ -z "$LOADER_TYPE" ] || msg+=". /etc/sysconfig/bootloader has LOADER_TYPE=\"$LOADER_TYPE\", but only \"systemd-boot\" or \"grub2-bls\" are recognized."
	err "$msg"
fi

# Removable media layout is described in
# https://github.com/rhboot/shim/blob/main/README.fallback
if [ -n "$arg_portable" ]; then
	if [ -d "${boot_root}${boot_dst}" ]; then
		err "Bootloader is already installed permanently"
	fi
	boot_dst="/EFI/BOOT"
fi

# When we are doing an operation different from installation, the
# boot_dst should be already present.  We can use it to identify a
# removable installation
if [ "$1" != "install" ] && [ ! -d "${boot_root}${boot_dst}" ]; then
	info "Removable installation detected"
	boot_dst="/EFI/BOOT"
	arg_portable=1
fi

dbg_var "boot_dst"

if [ "$SECURE_BOOT" = "yes" ] || is_shim_installed; then
	arg_secure_boot=1
fi

if [ "$UPDATE_NVRAM" = "no" ]; then
	arg_no_variables=1
fi

# Keep initial components before they are replaced by some actions
# (new initrd, new entry, etc)
if [ "$SDB_ADD_INITIAL_COMPONENT" = "1" ]; then
	backup_initial_components
fi

case "$1" in
	install)
		install_bootloader "${2:-$root_snapshot}" ;;
	needs-update)
		bootloader_needs_update "${2:-$root_snapshot}" ;;
	update)
		bootloader_update "${2:-$root_snapshot}" ;;
	force-update)
		if is_installed; then
			install_bootloader "${2:-$root_snapshot}"
		else
			:
		fi ;;
	bootloader)
		bootloader_name "${2:-$root_snapshot}" ;;
	add-kernel)
		install_kernel "${3:-$root_snapshot}" "$2" ;;
	add-all-kernels)
		install_all_kernels "${2:-$root_snapshot}" ;;
	mkinitrd)
		arg_no_reuse_initrd=1
		install_all_kernels "${2:-$root_snapshot}" ;;
	remove-kernel)
		remove_kernel "${3:-$root_snapshot}" "$2" ;;
	remove-all-kernels)
		remove_all_kernels "${2:-$root_snapshot}" ;;
	cleanup)
		cleanup_entries "${2:-}" ;;
	set-default-snapshot)
		set_default_snapshot "${2:-$root_snapshot}" ;;
	is-installed)
		if is_installed; then
			info "systemd-boot was installed using sdbootutil"
			exit 0
		else
			info "not installed using this tool"
			exit 1
		fi ;;
	list-kernels)
		list_kernels "${2:-$root_snapshot}" ;;
	list-entries)
		list_entries "${2:-}" ;;
	list-snapshots)
		list_snapshots ;;
	list-devices)
		list_devices ;;
	show-entry)
		show_entry_fields "${3:-$root_snapshot}" "$2" ;;
	update-entry)
		update_entry "${3:-$root_snapshot}" "$2" ;;
	update-all-entries)
		update_all_entries "${2:-$root_snapshot}" ;;
	is-bootable)
		is_bootable "${2:-$root_snapshot}" ;;
	set-default)
		set_default_entry "$2" ;;
	get-default)
		get_default_entry "$2" ;;
	set-timeout)
		set_timeout "$2" ;;
	get-timeout)
		get_timeout "$2" ;;
	enroll)
		enroll ;;
	unenroll)
		unenroll ;;
	update-predictions)
		update_predictions=1 ;;
	*)
		helpandquit ;;
esac

[ -z "$update_predictions" ] || generate_tpm2_predictions
07070100000016000081ED00000000000000000000000168B8435A00000BCB000000000000000000000000000000000000003300000000sdbootutil-1+git20250903.f5a076b/sdbootutil-enroll#!/bin/bash

get_credential() {
	local var="${1:?}"
	local name="${2:?}"
	local keyid
	keyid="$(keyctl id %user:"$name" 2> /dev/null)" || true

	if [ -n "$CREDENTIALS_DIRECTORY" ] && [ -e "$CREDENTIALS_DIRECTORY/$name" ]; then
		read -r "$var" < "$CREDENTIALS_DIRECTORY/$name"
	elif [ -n "$keyid" ]; then
		read -r "$var" <<<"$(keyctl pipe "$keyid")"
	fi
}

have_luks2() {
	lsblk --noheadings -o FSTYPE | grep -q crypto_LUKS
}

write_issue_file()
{
	local recovery_key="$1"
	local issuefile="/run/issue.d/90-recovery-key.issue"

	[ -x '/usr/sbin/issue-generator' ] && issuefile="/run/issue.d/90-recovery-key.conf"
	mkdir -p "/run/issue.d"
	echo "$recovery_key" > "$issuefile"
	[ -x '/usr/sbin/issue-generator' ] && issue-generator
}


[ ! -e "/var/lib/YaST2/reconfig_system" ] || exit 0
have_luks2 || exit 0

# disk-encryption-tool-dracut uses "cryptenroll" for the keyring
# parameter, if we use a different one we need to move it back to
# "cryptenroll", to do the enrollment via sdbootutil without
# requesting a password
enroll_keyid="$(keyctl id %user:cryptenroll 2> /dev/null)" || exit 0
[ -n "$enroll_keyid" ] || {
	echo "Enrollment key not registered in the keyring. Aborting" > /dev/stderr
	exit 1
}

# Proceed with the enrollment
rk=
get_credential rk "sdbootutil-enroll.rk"

pw=
get_credential pw "sdbootutil-enroll.pw"

tpm2_pin=
get_credential tpm2_pin "sdbootutil-enroll.tpm2+pin"

tpm2=
get_credential tpm2 "sdbootutil-enroll.tpm2"

fido2=
get_credential fido2 "sdbootutil-enroll.fido2"

[ -z "$rk" ] || {
	echo "Enrolling recovery key"
	# Note that if --no-reuse-initrd is used, then a new initrd
	# will be created and will break the measurement of the
	# initial components if later the TPM2 enrollment is called
	extra=
	if [ -z "$tpm2_pin" ] && [ -z "$tpm2" ] && [ -z "$fido2" ]; then
		extra="--no-reuse-initrd"
	fi
	recovery_key="$(sdbootutil enroll --method=recovery-key "$extra")"
	write_issue_file "$recovery_key"
}

[ -z "$pw" ] || {
	echo "Enrolling password"
	# Note that if --no-reuse-initrd is used, then a new initrd
	# will be created and will break the measurement of the
	# initial components if later the TPM2 enrollment is called
	extra=
	if [ -z "$tpm2_pin" ] && [ -z "$tpm2" ] && [ -z "$fido2" ]; then
		extra="--no-reuse-initrd"
	fi
	PW="$pw" sdbootutil enroll --method=password "$extra"
}

if [ -n "$tpm2_pin" ]; then
	echo "Enrolling TPM2 with PIN"
	SDB_ADD_INITIAL_COMPONENT=1 PIN="$crypt_tpm_pin" sdbootutil enroll --method=tpm2+pin
elif [ -n "$tpm2" ]; then
	echo "Enrolling TPM2"
	SDB_ADD_INITIAL_COMPONENT=1 sdbootutil enroll --method=tpm2
fi

[ -z "$fido2" ] || {
	echo "Enrolling a FIDO2 key"
	sdbootutil enroll --method=fido2
}

# Clean the enrollment key.  disk-encryption-tool creates it in the
# keyslot 0 with the name "enrollment-key", that is showed by
# systemd-cryptenroll as "other"
while read -r dev; do
	slots=$(systemd-cryptenroll "$dev")
	if grep -q "other" <<<"$slots"; then
		systemd-cryptenroll --wipe-slot=0 "$dev"
	fi
done < <(sdbootutil list-devices)
07070100000017000081A400000000000000000000000168B8435A00000144000000000000000000000000000000000000003B00000000sdbootutil-1+git20250903.f5a076b/sdbootutil-enroll.service[Unit]
Description=Enroll encrypted root disk 
DefaultDependencies=false

After=jeos-firstboot.service
#ConditionPathExists=/var/lib/YaST2/enroll_system

[Service]
Type=oneshot
RemainAfterExit=yes
KeyringMode=shared
ExecStart=/usr/bin/sdbootutil-enroll
ImportCredential=sdbootutil-enroll.*

[Install]
WantedBy=default.target07070100000018000081A400000000000000000000000168B8435A00000105000000000000000000000000000000000000004700000000sdbootutil-1+git20250903.f5a076b/sdbootutil-update-predictions.service[Unit]
Description=Update TPM predictions
ConditionSecurity=tpm2

[Service]
Type=oneshot
RemainAfterExit=yes
KeyringMode=shared
ExecStart=/usr/bin/sdbootutil update-predictions
ImportCredential=sdbootutil-update-predictions.*

[Install]
WantedBy=default.target
07070100000019000081A400000000000000000000000168B8435A0000228C000000000000000000000000000000000000003100000000sdbootutil-1+git20250903.f5a076b/sdbootutil.spec#
# spec file for package sdbootutil
#
# Copyright (c) 2025 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.

# Please submit bugfixes or comments via https://bugs.opensuse.org/
#


%global rustflags '-Clink-arg=-Wl,-z,relro,-z,now'
Name:           sdbootutil
Version:        0
Release:        0
Summary:        bootctl wrapper for BLS boot loaders
License:        MIT
URL:            https://github.com/openSUSE/sdbootutil
Source:         %{name}-%{version}.tar
Source1:        vendor.tar.zst
Source2:        config.toml
BuildRequires:  cargo
BuildRequires:  cargo-packaging
BuildRequires:  libopenssl-devel
BuildRequires:  systemd-rpm-macros
Requires:       %{name}-dracut-measure-pcr
Requires:       dialog
Requires:       dracut-pcr-signature
Requires:       efibootmgr
Requires:       jq
Requires:       keyutils
Requires:       openssl
Requires:       pcr-oracle
Requires:       qrencode
Requires:       sed
Requires:       (%{name}-snapper if (snapper and btrfsprogs))
Requires:       (%{name}-tukit if read-only-root-fs)
# While systemd-pcrlock is in experimental
Requires:       systemd-experimental
# something needs to require it. Can be us.
Requires:       tpm2.0-tools
# While bootctl is in udev
Requires:       udev
Supplements:    (grub2-x86_64-efi-bls and shim)
Supplements:    (systemd-boot and shim)
# Because uhmac it is not a noarch package
# BuildArch:      noarch
ExclusiveArch:  aarch64 riscv64 x86_64
%{?systemd_requires}

%description
bootctl wrapper for BLS boot loaders, like systemd-boot and grub2-bls.
Implements also the life cycle of a full disk encryption installation,
based on systemd.

%package snapper
Summary:        plugin script for snapper
Requires:       %{name} = %{version}
Requires:       btrfsprogs
Requires:       snapper
BuildArch:      noarch

%description snapper
Plugin scripts for snapper to handle BLS config files

%package tukit
Summary:        plugin script for tukit
Requires:       %{name} = %{version}
Requires:       tukit
BuildArch:      noarch

%description tukit
Plugin scripts for tukit to handle BLS config files

%package kernel-install
Summary:        Hook script for kernel-install
Requires:       %{name} = %{version}
# While kernel-install is in udev
Requires:       udev
BuildArch:      noarch

%description kernel-install
Plugin script for kernel-install. Note: installation of this
package may disable other plugin scripts that are incompatible.

%package enroll
Summary:        Full disk encryption enrollment
Requires:       %{name} = %{version}
BuildArch:      noarch

%description enroll
Systemd service and script for full disk encryption enrollment.

%package jeos-firstboot-enroll
Summary:        JEOS module for full disk encryption enrollment
Requires:       %{name} = %{version}
Requires:       %{name}-enroll = %{version}
Requires:       jeos-firstboot
BuildArch:      noarch

%description jeos-firstboot-enroll
JEOS module for full disk encryption enrollment. The module
present the different options and delegate into sdbootutil-enroll
service the effective enrollment.

%package bash-completion
Summary:        Bash completions for sdbootutil
Requires:       %{name} = %{version}
Requires:       bash
Requires:       bash-completion
BuildArch:      noarch

%description bash-completion
Bash completions script for sdbootutil.
Allows the user to press TAB to see available commands,
options and parameters.

%package dracut-measure-pcr
Summary:        Dracut module to measure PCR 15
BuildRequires:  pkgconfig
BuildRequires:  rpm-config-SUSE
BuildRequires:  pkgconfig(dracut)
BuildArch:      noarch

%description dracut-measure-pcr
Dracut module from sdbootutil to measure PCR 15 in non-UKIs systems

%prep
%autosetup -a1 -p1
mv vendor uhmac
cd uhmac
mkdir .cargo
install -D -m 644 %{SOURCE2} .cargo/config.toml

%build
cd uhmac
%{cargo_build}

%install
install -D -m 755 %{name} %{buildroot}%{_bindir}/%{name}

# Install uhmac binary
pushd uhmac
%{cargo_install}
install -D -m 755 %{buildroot}%{_bindir}/uhmac %{buildroot}%{_libexecdir}/%{name}/uhmac
rm %{buildroot}%{_bindir}/uhmac
popd

# Update prediction service
install -D -m 644 %{name}-update-predictions.service \
	%{buildroot}%{_unitdir}/%{name}-update-predictions.service

# Enrollment service
install -m 755 %{name}-enroll %{buildroot}%{_bindir}/%{name}-enroll
install -D -m 644 %{name}-enroll.service %{buildroot}/%{_unitdir}/%{name}-enroll.service

# Jeos module
install -D -m 644 jeos-firstboot-enroll-override.conf \
	%{buildroot}%{_prefix}/lib/systemd/system/jeos-firstboot.service.d/jeos-firstboot-enroll-override.conf
install -D -m 644 jeos-firstboot-enroll %{buildroot}%{_datadir}/jeos-firstboot/modules/enroll

# Snapper
install -D -m 755 10-%{name}.snapper %{buildroot}%{_prefix}/lib/snapper/plugins/10-%{name}.snapper

# Tukit
install -D -m 755 10-%{name}.tukit %{buildroot}%{_prefix}/lib/tukit/plugins/10-%{name}.tukit

# kernel-install
install -D -m 755 50-%{name}.install %{buildroot}%{_prefix}/lib/kernel/install.d/50-%{name}.install

# Bash completions
install -D -m 755 completions/bash_sdbootutil %{buildroot}%{_datadir}/bash-completion/completions/sdbootutil

# Dracut module
install -D -m 755 module-setup.sh %{buildroot}%{_prefix}/lib/dracut/modules.d/50measure-pcr/module-setup.sh
install -D -m 755 measure-pcr-generator.sh %{buildroot}%{_prefix}/lib/dracut/modules.d/50measure-pcr/measure-pcr-generator.sh
install -D -m 755 measure-pcr-validator.sh %{buildroot}%{_prefix}/lib/dracut/modules.d/50measure-pcr/measure-pcr-validator.sh
install -D -m 644 measure-pcr-validator.service %{buildroot}/%{_prefix}/lib/dracut/modules.d/50measure-pcr/measure-pcr-validator.service

install -d -m 700 %{buildroot}%{_sharedstatedir}/%{name}

# tmpfiles
install -D -m 755 kernel-install-%{name}.conf \
	%{buildroot}%{_prefix}/lib/tmpfiles.d/kernel-install-%{name}.conf

%transfiletriggerin -- %{_prefix}/lib/systemd/boot/efi %{_datadir}/grub2/%{_build_arch}-efi %{_datadir}/efi/%{_build_arch}
cat > /dev/null || :
[ "$YAST_IS_RUNNING" != 'instsys' ] || exit 0
[ -e /sys/firmware/efi/efivars ] || exit 0
[ -z "$TRANSACTIONAL_UPDATE" ] || exit 0
[ -z "$VERBOSE_FILETRIGGERS" ] || echo "%{name}-%{version}-%{release}: updating bootloader"
if [ -e /etc/sysconfig/bootloader ]; then
	. /etc/sysconfig/bootloader &> /dev/null
	if [ "$LOADER_TYPE" = "grub2-bls" ] || [ "$LOADER_TYPE" = "systemd-boot" ]; then
		sdbootutil update
	fi
else
	sdbootutil update
fi

%preun
%service_del_preun %{name}-update-predictions.service

%postun
%service_del_postun %{name}-update-predictions.service

%pre
%service_add_pre %{name}-update-predictions.service

%post
%service_add_post %{name}-update-predictions.service

%preun enroll
%service_del_preun %{name}-enroll.service

%postun enroll
%service_del_postun %{name}-enroll.service

%pre enroll
%service_add_pre %{name}-enroll.service

%post enroll
%service_add_post %{name}-enroll.service

%posttrans kernel-install
%tmpfiles_create kernel-install-%{name}.conf

%post dracut-measure-pcr
%{?regenerate_initrd_post}

%posttrans dracut-measure-pcr
%{?regenerate_initrd_posttrans}

%postun dracut-measure-pcr
%{?regenerate_initrd_post}

%files
%license LICENSE
%dir %{_sharedstatedir}/%{name}
%{_bindir}/%{name}
%{_unitdir}/%{name}-update-predictions.service
%dir %{_libexecdir}/%{name}
%{_libexecdir}/%{name}/uhmac

%files snapper
%dir %{_prefix}/lib/snapper
%dir %{_prefix}/lib/snapper/plugins
%{_prefix}/lib/snapper/plugins/*

%files tukit
%dir %{_prefix}/lib/tukit
%dir %{_prefix}/lib/tukit/plugins
%{_prefix}/lib/tukit/plugins/*

%files kernel-install
%dir %{_prefix}/lib/kernel
%dir %{_prefix}/lib/kernel/install.d
%{_prefix}/lib/kernel/install.d/*
%{_prefix}/lib/tmpfiles.d/kernel-install-%{name}.conf

%files enroll
%{_bindir}/%{name}-enroll
%{_unitdir}/%{name}-enroll.service

%files jeos-firstboot-enroll
%dir %{_datadir}/jeos-firstboot
%dir %{_datadir}/jeos-firstboot/modules
%{_datadir}/jeos-firstboot/modules/enroll
%dir %{_unitdir}/jeos-firstboot.service.d
%{_unitdir}/jeos-firstboot.service.d/jeos-firstboot-enroll-override.conf

%files bash-completion
%dir %{_datadir}/bash-completion
%dir %{_datadir}/bash-completion/completions
%{_datadir}/bash-completion/completions/sdbootutil

%files dracut-measure-pcr
%dir %{_prefix}/lib/dracut
%dir %{_prefix}/lib/dracut/modules.d
%{_prefix}/lib/dracut/modules.d/50measure-pcr

%changelog
0707010000001A000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000002700000000sdbootutil-1+git20250903.f5a076b/uhmac0707010000001B000081A400000000000000000000000168B8435A00002638000000000000000000000000000000000000003200000000sdbootutil-1+git20250903.f5a076b/uhmac/Cargo.lock# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
 "anstyle",
 "anstyle-parse",
 "anstyle-query",
 "anstyle-wincon",
 "colorchoice",
 "is_terminal_polyfill",
 "utf8parse",
]

[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"

[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
 "utf8parse",
]

[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
 "windows-sys",
]

[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
 "anstyle",
 "once_cell_polyfill",
 "windows-sys",
]

[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"

[[package]]
name = "cc"
version = "1.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
dependencies = [
 "shlex",
]

[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"

[[package]]
name = "clap"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
dependencies = [
 "clap_builder",
 "clap_derive",
]

[[package]]
name = "clap_builder"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
dependencies = [
 "anstream",
 "anstyle",
 "clap_lex",
 "strsim",
]

[[package]]
name = "clap_derive"
version = "4.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
dependencies = [
 "heck",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"

[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"

[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
 "foreign-types-shared",
]

[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"

[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"

[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"

[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"

[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"

[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"

[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"

[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
 "bitflags",
 "cfg-if",
 "foreign-types",
 "libc",
 "once_cell",
 "openssl-macros",
 "openssl-sys",
]

[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "openssl-sys"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
 "cc",
 "libc",
 "pkg-config",
 "vcpkg",
]

[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"

[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"

[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"

[[package]]
name = "syn"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "uhmac"
version = "0.1.0"
dependencies = [
 "clap",
 "hex",
 "openssl",
]

[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"

[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"

[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"

[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
 "windows-targets",
]

[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
 "windows_aarch64_gnullvm",
 "windows_aarch64_msvc",
 "windows_i686_gnu",
 "windows_i686_gnullvm",
 "windows_i686_msvc",
 "windows_x86_64_gnu",
 "windows_x86_64_gnullvm",
 "windows_x86_64_msvc",
]

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"

[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"

[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"

[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"

[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"

[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"

[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
0707010000001C000081A400000000000000000000000168B8435A000000A3000000000000000000000000000000000000003200000000sdbootutil-1+git20250903.f5a076b/uhmac/Cargo.toml[package]
name = "uhmac"
version = "0.1.0"
edition = "2024"

[dependencies]
clap = { version = "4.5.38", features = ["derive"] }
hex = "0.4.3"
openssl = "0.10.72"
0707010000001D000041ED00000000000000000000000268B8435A00000000000000000000000000000000000000000000002B00000000sdbootutil-1+git20250903.f5a076b/uhmac/src0707010000001E000081A400000000000000000000000168B8435A0000071E000000000000000000000000000000000000003300000000sdbootutil-1+git20250903.f5a076b/uhmac/src/main.rsuse std::fs;
use std::io;
use std::io::Read;
use std::path::PathBuf;

use clap::{Parser, ValueEnum};
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use openssl::sign::Signer;

#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
    /// Digest used to calculate the HMAC
    digest: Digest,

    /// File with the secret key as an hex string
    key: PathBuf,
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
enum Digest {
    SHA1,
    SHA256,
    SHA384,
    SHA512,
}

fn main() -> io::Result<()> {
    let cli = Cli::parse();

    let key = PKey::hmac(
        &hex::decode(fs::read_to_string(&cli.key)?.trim()).expect("valid hexadecimal string"),
    )
    .expect("valid HMAC key");
    let mut message = String::new();
    io::stdin().read_to_string(&mut message)?;

    let mut signer;
    let result = match cli.digest {
        Digest::SHA1 => {
            signer = Signer::new(MessageDigest::sha1(), &key).unwrap();
            signer.update(message.as_bytes()).unwrap();
            hex::encode(signer.sign_to_vec().unwrap())
        }
        Digest::SHA256 => {
            signer = Signer::new(MessageDigest::sha256(), &key).unwrap();
            signer.update(message.as_bytes()).unwrap();
            hex::encode(signer.sign_to_vec().unwrap())
        }
        Digest::SHA384 => {
            signer = Signer::new(MessageDigest::sha384(), &key).unwrap();
            signer.update(message.as_bytes()).unwrap();
            hex::encode(signer.sign_to_vec().unwrap())
        }
        Digest::SHA512 => {
            signer = Signer::new(MessageDigest::sha512(), &key).unwrap();
            signer.update(message.as_bytes()).unwrap();
            hex::encode(signer.sign_to_vec().unwrap())
        }
    };

    println!("{result}");

    Ok(())
}
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!347 blocks
openSUSE Build Service is sponsored by