Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package sdbootutil for openSUSE:Factory 
checked in at 2026-09-28 10:36:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/sdbootutil (Old)
 and      /work/SRC/openSUSE:Factory/.sdbootutil.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "sdbootutil"

Mon Sep 28 10:36:48 2026 rev:111 rq:1380261 version:1+git20260924.2b7b94e

Changes:
--------
--- /work/SRC/openSUSE:Factory/sdbootutil/sdbootutil.changes    2026-09-09 
16:21:39.164279909 +0200
+++ /work/SRC/openSUSE:Factory/.sdbootutil.new.383539/sdbootutil.changes        
2026-09-28 10:37:29.045415164 +0200
@@ -1,0 +2,27 @@
+Thu Sep 24 20:05:03 UTC 2026 - Alberto Planas Dominguez <[email protected]>
+
+- Update to version 1+git20260924.2b7b94e:
+  * Improve detection of encrypted device when RAID is used
+  * Support btrfs RAID1 configurations
+  * Move the service from oneshot to exec to avoid the wait
+  * Drop shift variations already present as a component
+  * Fix Supplement use of 'if' instead of 'and'
+  * Hide the warning for entries that uses @
+  * Accept _ instead of @ as snapshot prefix for version
+  * Drop chown and set ownership via install
+  * Use bootctl to generate the random seed
+  * Start the validation with the strongest bank
+  * Parse the JSON output of findmnt
+  * Use stdin for qrencode
+  * Fix log file permissions
+  * Improve PCR15 diagnosis in status command
+  * Avoid abrmd TCTI error message
+  * Do not fail if pcrlock lock verb cannot reproduce the event log
+  * The completion subpackage supplements the main one
+  * Detect when grubenv is full
+  * Use systemd-analyze to compare versions in status
+  * Fix bootcounter in GRUB2 EFI variable
+  * Drop lowercase in dd
+  * Fix loader_conf_set for paths
+
+-------------------------------------------------------------------

Old:
----
  sdbootutil-1+git20260909.7cfa1f0.obscpio

New:
----
  sdbootutil-1+git20260924.2b7b94e.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ sdbootutil.spec ++++++
--- /var/tmp/diff_new_pack.QvX9Cm/_old  2026-09-28 10:37:29.953453134 +0200
+++ /var/tmp/diff_new_pack.QvX9Cm/_new  2026-09-28 10:37:29.955453218 +0200
@@ -55,7 +55,7 @@
 %{nil}
 
 Name:           sdbootutil
-Version:        1+git20260909.7cfa1f0
+Version:        1+git20260924.2b7b94e
 Release:        0
 Summary:        Bootctl wrapper for BLS boot loaders
 License:        MIT
@@ -151,6 +151,7 @@
 Requires:       %{name} = %{version}
 Requires:       bash
 Requires:       bash-completion
+Supplements:    (%{name} and bash-completion)
 BuildArch:      noarch
 
 %description bash-completion

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.QvX9Cm/_old  2026-09-28 10:37:29.998455016 +0200
+++ /var/tmp/diff_new_pack.QvX9Cm/_new  2026-09-28 10:37:30.001455142 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param 
name="url">https://github.com/openSUSE/sdbootutil.git</param>
-              <param 
name="changesrevision">7cfa1f0ab1bba2c808ef5ff63aff22b26aa42d08</param></service></servicedata>
+              <param 
name="changesrevision">2b7b94e0792520e76bfdf84650365aa4d251560c</param></service></servicedata>
 (No newline at EOF)
 

++++++ sdbootutil-1+git20260909.7cfa1f0.obscpio -> 
sdbootutil-1+git20260924.2b7b94e.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sdbootutil-1+git20260909.7cfa1f0/.github/ISSUE_TEMPLATE/bug_report.yml 
new/sdbootutil-1+git20260924.2b7b94e/.github/ISSUE_TEMPLATE/bug_report.yml
--- old/sdbootutil-1+git20260909.7cfa1f0/.github/ISSUE_TEMPLATE/bug_report.yml  
2026-09-09 13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/.github/ISSUE_TEMPLATE/bug_report.yml  
2026-09-24 22:03:19.000000000 +0200
@@ -121,7 +121,7 @@
     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.
+        If you enabled the **opt‑in debug trace** (`sdbootutil 
--start-trace-code`, which creates `/var/log/sdbootutil.log` with mode 600), 
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.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdbootutil-1+git20260909.7cfa1f0/ARCHITECTURE.md 
new/sdbootutil-1+git20260924.2b7b94e/ARCHITECTURE.md
--- old/sdbootutil-1+git20260909.7cfa1f0/ARCHITECTURE.md        2026-09-09 
13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/ARCHITECTURE.md        2026-09-24 
22:03:19.000000000 +0200
@@ -83,7 +83,7 @@
 An entry file might now look like this:
 
     title      openSUSE Tumbleweed
-    version    [email protected]
+    version    15_1.2.3-1-default
     machine-id 2ceda9f
     sort-key   opensuse-tumbleweed
     options    root=UUID=abc... rootflags=subvol=@/.snapshots/15/snapshot
@@ -107,7 +107,7 @@
 So that makes an entry look like this
 
     title      openSUSE Tumbleweed
-    version    [email protected]
+    version    15_1.2.3-1-default
     machine-id 2ceda9f
     sort-key   opensuse-tumbleweed
     options    root=UUID=abc... rootflags=subvol=@/.snapshots/15/snapshot
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdbootutil-1+git20260909.7cfa1f0/CLAUDE.md 
new/sdbootutil-1+git20260924.2b7b94e/CLAUDE.md
--- old/sdbootutil-1+git20260909.7cfa1f0/CLAUDE.md      2026-09-09 
13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/CLAUDE.md      2026-09-24 
22:03:19.000000000 +0200
@@ -185,7 +185,7 @@
 
 ```
 title      openSUSE Tumbleweed
-version    [email protected]
+version    15_6.2.1-1-default
 machine-id 2ceda9f
 sort-key   opensuse-tumbleweed
 options    root=UUID=... rootflags=subvol=@/.snapshots/15/snapshot
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sdbootutil-1+git20260909.7cfa1f0/measure-pcr-validator.sh 
new/sdbootutil-1+git20260924.2b7b94e/measure-pcr-validator.sh
--- old/sdbootutil-1+git20260909.7cfa1f0/measure-pcr-validator.sh       
2026-09-09 13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/measure-pcr-validator.sh       
2026-09-24 22:03:19.000000000 +0200
@@ -27,7 +27,8 @@
        fi
 
        local res=1
-       for sha in sha1 sha256 sha384 sha512; do
+       # Strongest bank first
+       for sha in sha512 sha384 sha256 sha1; 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="$?"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdbootutil-1+git20260909.7cfa1f0/sdbootutil 
new/sdbootutil-1+git20260924.2b7b94e/sdbootutil
--- old/sdbootutil-1+git20260909.7cfa1f0/sdbootutil     2026-09-09 
13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/sdbootutil     2026-09-24 
22:03:19.000000000 +0200
@@ -33,7 +33,10 @@
 DEBUG_LOG="/var/log/sdbootutil.log"
 verbose=
 
-if [[ "$*" =~ "--start-trace-code" ]] && ! touch "$DEBUG_LOG" 2>/dev/null; then
+# The trace can record secrets (passwords and PINs passed as command
+# line arguments to the tools that this script calls), so the log is
+# created 0600 and never with whatever the umask happens to be
+if [[ "$*" =~ "--start-trace-code" ]] && ! (umask 077; touch "$DEBUG_LOG") 
2>/dev/null; then
        echo "Cannot enable the code trace, $DEBUG_LOG is not writable" >&2
        exit 1
 fi
@@ -43,9 +46,12 @@
 fi
 # The trace is only enabled if the log can be opened for writing, so a
 # trace left behind by root does not break the tool for the other users.
-# The completion data is also excluded, as the messages and the trace
-# itself interfere with the shell completion
-if [ "$1" != "_print_bash_completion_data" ] && [ -f "$DEBUG_LOG" ] && { exec 
3>>"$DEBUG_LOG"; } 2>/dev/null; then
+# The mode is reasserted on every run, so a log created by hand or by an
+# older version is tightened before anything is written to it, and the
+# trace stays off if it cannot be.  The completion data is also
+# excluded, as the messages and the trace itself interfere with the
+# shell completion
+if [ "$1" != "_print_bash_completion_data" ] && [ -f "$DEBUG_LOG" ] && { chmod 
0600 "$DEBUG_LOG" && exec 3>>"$DEBUG_LOG"; } 2>/dev/null; then
        verbose=3
        echo "The trace of the code is being stored in $DEBUG_LOG" >&2
        echo "Remove the file or use --stop-trace-code to stop tracing the 
code" >&2
@@ -853,7 +859,7 @@
 {
        local device
        read -r device < <(findmnt / -v -n -o SOURCE 2> /dev/null)
-       [ -n "$device" ] && [ "$(lsblk --noheadings -o TYPE "$device" 2> 
/dev/null)" = "crypt" ]
+       [ -n "$device" ] && lsblk --noheadings --list --inverse -o TYPE 
"$device" 2> /dev/null | grep -qx crypt
 }
 
 get_rootfs()
@@ -865,7 +871,9 @@
                label) read -r rootfs_data < <(findmnt / -v -n -o LABEL 2> 
/dev/null) ;;
                partuuid) read -r rootfs_data < <(findmnt / -v -n -o PARTUUID 
2> /dev/null) ;;
                partlabel) read -r rootfs_data < <(findmnt / -v -n -o PARTLABEL 
2> /dev/null) ;;
-               device) read -r rootfs_data < <(findmnt / -v -n -o SOURCE 2> 
/dev/null) ;;
+               # "/dev/mapper/NAME", as "/dev/dm-N" depends on the
+               # order in which the devices were opened
+               device) read -r rootfs_data < <(lsblk --noheadings --nodeps -o 
PATH "$(findmnt / -v -n -o SOURCE 2> /dev/null)" 2> /dev/null) ;;
                *)
                        info "Can't determine rootfs ($ROOTFS). Using UUID as 
default"
                        ROOTFS="uuid"
@@ -883,12 +891,18 @@
 
 get_all_rootfs()
 {
-       local rootfs rootfs_data
+       local rootfs rootfs_data kname path
 
        read -r rootfs_data < <(findmnt / -v -n -o SOURCE)
        rootfs="$rootfs_data"
 
+       # Every device of the file system (btrfs can span several), by
+       # both names, as older entries can use "/dev/dm-N"
        read -r rootfs_data < <(findmnt / -v -n -o UUID)
+       [ -z "$rootfs_data" ] || while read -r kname path; do
+               rootfs="$rootfs|/dev/$kname|$path"
+       done < <(lsblk --noheadings --raw -o KNAME,PATH -Q "UUID == 
\"$rootfs_data\"" 2> /dev/null)
+
        [ -z "$rootfs_data" ] || rootfs="$rootfs|UUID=$rootfs_data"
 
        read -r rootfs_data < <(findmnt / -v -n -o LABEL)
@@ -938,12 +952,28 @@
        sed "${sed_arguments[@]}"
 }
 
+# Entries written before the "N_" prefix say "N@", and systemd >= v262
+# warns about the "@" on every parse.  Only the warning, the entry is
+# accepted and sorts the same
+bootctl_noise="^.*: Version string '[0-9]+@[^']*' is not a valid version, 
accepting anyway\.$"
+
+# `bootctl` for the commands that parse the entries.  The noise is
+# kept when the user asked for more output, with `--verbose` or with
+# SYSTEMD_LOG_LEVEL
+bootctl_entries()
+{
+       if [ -n "$verbose" ] || [ -n "$SYSTEMD_LOG_LEVEL" ]; then
+               bootctl "$@"
+               return
+       fi
+       bootctl "$@" 2> >(grep -Ev "$bootctl_noise" >&2)
+}
 
 entry_filter=("cat")
 update_entries()
 {
        [ -z "$1" ] || entry_filter=("$@")
-       bootctl list --json=short | "${entry_filter[@]}" > "$entryfile"
+       bootctl_entries list --json=short | "${entry_filter[@]}" > "$entryfile"
        dbg "Entry filter: ${entry_filter[*]}"
        dbg_cat "$entryfile"
 }
@@ -971,7 +1001,7 @@
        local root
        root="$(get_all_rootfs)"
 
-       update_entries jq 
"[.[]|select(has(\"options\"))|select(.options|test(\"root=(?:$root)\"))]"
+       update_entries jq 
"[.[]|select(has(\"options\"))|select(.options|test(\"root=(?:$root)(?: 
|$)\"))]"
 }
 
 update_entries_for_extra()
@@ -1012,6 +1042,24 @@
        echo 
"${prefix:+$prefix-}$entry_token-$kernel_version${snapshot:+-$snapshot}${tries:++$tries}.conf"
 }
 
+# An entry name reduced to what identifies it, so that a name from
+# `bootctl` and one from a boot loader can be compared.
+#
+# `bootctl` reports the ID with its ".conf" suffix and without the boot
+# counter that "entry_conf_file" puts in the file name.  grub2-bls
+# writes "LoaderEntrySelected" the other way around: no suffix, and the
+# counter still there, because it records the name before
+# "systemd-bless-boot" renames the file
+entry_key()
+{
+       # "+3" until the loader counts a boot, "+2-1" afterwards
+       local name="${1%.conf}"
+
+       [[ ! "$name" =~ ^(.+)\+[0-9]+(-[0-9]+)?$ ]] || name="${BASH_REMATCH[1]}"
+
+       echo "$name"
+}
+
 find_conf_file()
 {
        local kernel_version="${1:?}"
@@ -1167,7 +1215,7 @@
                       | select(.type? == "type1")
                       | select(.path != null)
                       | .id, .path,
-                        ((.version // "") | 
capture("((?<snapshot>[0-9]*)@)?(?<kernel>.*)")) as $v
+                        ((.version // "") | 
capture("((?<snapshot>[0-9]*)[@_])?(?<kernel>.*)")) as $v
                       | $v.snapshot, $v.kernel')
 }
 
@@ -1304,7 +1352,7 @@
        local id
        id="$(entry_conf_file "$kernel_version" "$snapshot")"
        info "Removing boot entry $id"
-       bootctl unlink "$id"
+       bootctl_entries unlink "$id"
 
        # If we remove the default entry, `bootctl` will mark a new
        # default, but we still need to update the EFI var (or the
@@ -1339,8 +1387,7 @@
                mv "$old" "$old.bak" || return "$?"
        fi
        rollback+=("$old")
-       install -p -m 0644 "$src" "$dst" || return "$?"
-       chown root:root "$dst" 2> /dev/null || true
+       install -p -m 0644 -o root -g root "$src" "$dst" || return "$?"
        info "Installed $dst"
 }
 
@@ -1459,38 +1506,36 @@
        # 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,FSROOT -Rv --pairs / > "$tmpdir/mounts"
+       findmnt -o TARGET,FSTYPE,FSROOT -Rv --json / > "$tmpdir/mounts"
        mount --bind "$snapshot_dir" "$snapshot_dir"
        # Register the chroot before mounting anything else, so a
        # failure in the middle of the loop is unwound by `cleanup`
        chroot_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
-               [[ "$TARGET" != /run/media/* ]] || continue
+       local target fstype fsroot
+       while IFS=$'\t' read -r target fstype fsroot; do
+               [ "$fstype" = "btrfs" ] || [ "$fstype" = "vfat" ] || [ 
"$fstype" = "xfs" ] || [[ "$fstype" == ext* ]] || continue
+               [ "$target" != "/" ] || continue
+               [ "$target" = "/etc" ] && [ "$fstype" = "btrfs" ] && continue
+               [[ "$target" != /.snapshots* ]] || continue
+               [[ "$target" != /run/media/* ]] || continue
                # After a `transactional-update apply` the running
                # system has /usr and /boot bind mounted from the new
                # default snapshot.  Those belong to a different
                # snapshot and must not shadow the ones that
                # "$snapshot_dir" provides
-               # shellcheck disable=SC2153
-               [ -z "$(snapshot_from_fsroot "$FSROOT")" ] || continue
+               [ -z "$(snapshot_from_fsroot "$fsroot")" ] || continue
                # Not every mount point of the running system is present
                # in the snapshot.  For example the directory that
                # `transactional-update` mounts under /tmp while a
                # transaction is open, or any external media.  They are
                # not needed to generate the initrd, and the snapshot can
                # be read-only, so the directory cannot be created
-               if [ ! -d "$snapshot_dir$TARGET" ]; then
-                       dbg "Skipping $TARGET, not present in $snapshot_dir"
+               if [ ! -d "$snapshot_dir$target" ]; then
+                       dbg "Skipping $target, not present in $snapshot_dir"
                        continue
                fi
-               mountpoint --quiet "$snapshot_dir$TARGET" || mount --bind 
"$TARGET" "$snapshot_dir$TARGET"
-       done < "$tmpdir/mounts"
+               mountpoint --quiet "$snapshot_dir$target" || mount --bind 
"$target" "$snapshot_dir$target"
+       done < <(jq -r 
'..|objects|select(has("target"))|[.target,.fstype,.fsroot]|@tsv' 
"$tmpdir/mounts")
        rm "$tmpdir/mounts"
 
        mount -t tmpfs -o size=10m tmpfs "$snapshot_dir/run"
@@ -1663,12 +1708,12 @@
        jq -r --arg in_use "${in_use[*]}" --arg last "$root_snapshot" '
                def snapshot_of:
                        ((.options // "") | capture("rootflags=subvol=[^ 
]*/\\.snapshots/(?<n>[0-9]+)/snapshot") | .n)
-                       // ((.version // "") | capture("^(?<n>[0-9]+)@") | .n)
+                       // ((.version // "") | capture("^(?<n>[0-9]+)[@_]") | 
.n)
                        // ((.id // "") | 
capture("-(?<n>[0-9]+)(\\+[0-9]+(-[0-9]+)?)?\\.conf$") | .n)
                        // "";
                def kernel_of:
                        ((.linux // "") | capture("^/[^/]+/(?<k>[^/]+)/[^/]+$") 
| .k)
-                       // ((.version // "") | capture("@(?<k>.+)$") | .k)
+                       // ((.version // "") | capture("^[0-9]+[@_](?<k>.+)$") 
| .k)
                        // "";
 
                ($in_use | split(" ") | map(select(. != ""))) as $in_use
@@ -2044,10 +2089,18 @@
        local entry_machine_id=
        [ "$entry_token" = "$machine_id" ] && entry_machine_id="$machine_id"
 
+       # The snapshot number goes in front of the kernel version, so
+       # that the boot loader sorts the entries by snapshot first.  The
+       # separator is "_": it is one of the characters that
+       # `strverscmp_improved()` drops as a plain segment separator,
+       # like the "@" used before, so both sort identically.  "@" is
+       # outside the UAPI.10 charset and systemd v262 warns about it on
+       # every entry it parses.  Entries written with "@" are still on
+       # disk, and every reader accepts both
        cat > "$tmpdir/entry.conf" <<-EOF
        # Boot Loader Specification type#1 entry
        title      $title
-       version    
${snapshot:+$snapshot@}$kernel_version${entry_machine_id:+${nl}machine-id 
$entry_machine_id}${sort_key:+${nl}sort-key   $sort_key}
+       version    
${snapshot:+${snapshot}_}$kernel_version${entry_machine_id:+${nl}machine-id 
$entry_machine_id}${sort_key:+${nl}sort-key   $sort_key}
        options    $boot_options
        linux      $dst${devicetree_dst:+${nl}devicetree ${devicetree_dst}}
        EOF
@@ -2206,7 +2259,7 @@
                        info "Cleaning boot entry $id"
                        rm "$path"
                }
-       done < <(jq -r '.[] | .id, .path, (.version | 
capture("((?<snapshot>[0-9]*)@)?(?<kernel>.*)")) as $v | $v.snapshot, 
$v.kernel' "$entryfile")
+       done < <(jq -r '.[] | .id, .path, (.version | 
capture("((?<snapshot>[0-9]*)[@_])?(?<kernel>.*)")) as $v | $v.snapshot, 
$v.kernel' "$entryfile")
 
        # The loop above drops the entry whose kernel is gone.  The
        # opposite case -- the kernel is still there and what went missing
@@ -2260,13 +2313,12 @@
        fi
 
        # `bootctl` builds "isSelected" by matching the entry ID against
-       # "LoaderEntrySelected", but grub2-bls writes the variable without
-       # the ".conf" suffix, so nothing matches there and "isSelected" is
-       # always null.  Read the variable to compare it here too, both
-       # spellings, until grub2-bls is fixed.  Note that the value is
-       # lower-cased by `bli_efi_var_get`
+       # "LoaderEntrySelected", but grub2-bls spells the variable
+       # differently and nothing matches there, so "isSelected" is always
+       # null.  Compare it here too, through "entry_key", until grub2-bls
+       # is fixed
        local selected=
-       [ -z "$interactive" ] || selected="$(bli_efi_var_get 
"LoaderEntrySelected")"
+       [ -z "$interactive" ] || selected="$(entry_key "$(bli_efi_var_get 
"LoaderEntrySelected")")"
 
        local isdefault isselected isreported type id root conf title marker 
booted
        while read -r isdefault isselected isreported type id root conf title; 
do
@@ -2283,8 +2335,7 @@
                marker=
                if [ -n "$interactive" ]; then
                        booted=
-                       if [ "$isselected" = "true" ] || [ "${id,,}" = 
"$selected" ] \
-                           || [ "${id,,}" = "$selected.conf" ]; then
+                       if [ "$isselected" = "true" ] || [ "${id%.conf}" = 
"$selected" ]; then
                                booted=1
                        fi
                        if [ "$isdefault" = "true" ]; then
@@ -3055,14 +3106,20 @@
        for ((i=0;i<${#s};i+=2)); do echo -ne "\x${s:$i:2}"; done
 }
 
+# `bootctl random-seed` hashes fresh entropy together with the seed
+# that is already in the ESP, it also writes the "LoaderSystemToken"
+# EFI variable.
 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 
"${esp_root}/loader/random-seed" ] && dd if="${esp_root}/loader/random-seed" 
bs=32 count=1 status=none; } | sha256sum)
-       [ "${#s}" = 64 ] || { warn "Invalid random seed"; return 0; }
-       hex_to_binary "$s" > "${esp_root}/loader/random-seed.new"
-       mv "${esp_root}/loader/random-seed.new" "${esp_root}/loader/random-seed"
+
+       local extra=()
+       [ -z "$arg_no_variables" ] && [ -z "$arg_portable" ] && mountpoint -q 
"$esp_root" || extra=("--no-variables")
+
+       local output
+       output="$(bootctl "${extra[@]}" random-seed 2>&1)" || \
+               warn "Failed to update the random seed${output:+: $output}"
+       return 0
 }
 
 has_efivars()
@@ -3074,7 +3131,8 @@
 {
        # BLI uses this vendor UUID
        local 
efi_var="/sys/firmware/efi/efivars/${1:?}-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
-       [ ! -e "$efi_var" ] || dd "if=$efi_var" bs=2 skip=2 conv=lcase 
status=none | tr -d '\0'
+       # 4 bytes of attributes, then the value as UTF-16LE
+       [ ! -e "$efi_var" ] || dd "if=$efi_var" bs=2 skip=2 status=none | tr -d 
'\0'
 }
 
 bli_efi_var_set()
@@ -3096,7 +3154,8 @@
        [ -e "${esp_root}/loader/loader.conf" ] || touch 
"${esp_root}/loader/loader.conf"
 
        if grep -q "^$key " "${esp_root}/loader/loader.conf"; then
-               sed -i -e "s/^$key .*/$key $value/" 
"${esp_root}/loader/loader.conf"
+               # "|" as the delimiter, as a value can contain a path
+               sed -i -e "s|^$key .*|$key $value|" 
"${esp_root}/loader/loader.conf"
        else
                echo "$key $value" >> "${esp_root}/loader/loader.conf"
        fi
@@ -3127,9 +3186,15 @@
        done < "${esp_root}${esp_dst}/grubenv"
        echo "$key=$value" >> "$grubenv"
 
+       # GRUB2 reads a block of exactly 1024 bytes, and what is not a
+       # variable is padding.  One `printf` for the whole padding: the
+       # old `seq 1 $filler` loop printed nothing for a full block, and
+       # `printf` writes its format once when it has no arguments, so a
+       # block that was already full got a 1025th byte
        local filler
        filler=$((1024 - $(stat -c %s "$grubenv")))
-       printf '#%.0s' $(seq 1 $filler) >> "$grubenv"
+       [ "$filler" -ge 0 ] || err "grubenv has no room left for $key"
+       printf '%*s' "$filler" "" | tr ' ' '#' >> "$grubenv"
 
        mv "$grubenv" "${esp_root}${esp_dst}/grubenv"
 }
@@ -3299,19 +3364,14 @@
 # The snapshot that an entry describes, read from the entry list in
 # stdin.  Empty when the entry is not in the list or describes no
 # snapshot
-#
-# The ID is matched case insensitively: when it comes from
-# "LoaderEntryDefault" it has been through `bli_efi_var_get`, which
-# lower-cases what it reads, and an entry token is not always lower
-# case (same workaround as in "list_entries")
 entry_snapshot()
 {
        local id="${1:?}"
 
        jq -r --arg id "$id" '.[]
-               | select((.id | ascii_downcase) == ($id | ascii_downcase))
+               | select(.id == $id)
                | ((.options // "") | capture("rootflags=subvol=[^ 
]*/\\.snapshots/(?<n>[0-9]+)/snapshot") | .n)
-                 // ((.version // "") | capture("^(?<n>[0-9]+)@") | .n)
+                 // ((.version // "") | capture("^(?<n>[0-9]+)[@_]") | .n)
                  // empty'
 }
 
@@ -3547,6 +3607,22 @@
        return "$status"
 }
 
+# `systemd-pcrlock` refuses to generate a component when the event
+# log does not replay to the current value of one of the PCRs that
+# the component describes, and it checks all of them, not only the
+# ones that the policy is going to seal.  Two cases are routine:
+# PCR 7 when secure boot is disabled, and PCR 0 under a firmware
+# that does not log everything that it measures, like the vTPM of
+# VMware Workstation.  Neither is a reason to stop, as a PCR left
+# without a component is dropped by `predict` and reported by
+# `get_final_pcrs`
+pcrlock_lock()
+{
+       local err status=0
+       err="$(pcrlock "$@" 2>&1)" || status=$?
+       [ "$status" -eq 0 ] || dbg "No component for '$1': ${err:-exit $status}"
+}
+
 is_pcr_oracle()
 {
        [ -e /etc/systemd/tpm2-pcr-public-key.pem ] && \
@@ -3687,9 +3763,9 @@
        #     version has more priority
        #
        # Without snapshots
-       #   - The version of the entry has no "N@" prefix, so there is
-       #     no snapshot to order by and every entry shares the same
-       #     priority.  Only the kernel version separates them, the
+       #   - The version of the entry has no "N_" (or "N@") prefix, so
+       #     there is no snapshot to order by and every entry shares the
+       #     same priority.  Only the kernel version separates them, the
        #     higher one first
        #
        local filter
@@ -3697,7 +3773,7 @@
        # and by "as", so the filter has to reach jq unexpanded
        # shellcheck disable=SC2016
        if [ -n "$have_snapshots" ]; then
-               filter='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)})'
+               filter='def priority(id): id as $id | $ids | split(" ") | 
index($id); map(. + {"priority": priority(.version | scan("^(\\d+)[@_]") | 
.[]), "kernel": .version | scan("^\\d+[@_](?:(\\d+).(\\d+).(\\d+)-(\\d+))") | 
map(. | tonumber)})'
        else
                filter='map(([.version // "" | 
scan("(\\d+)\\.(\\d+)\\.(\\d+)-(\\d+)")] | first) as $kernel | . + {"priority": 
0, "kernel": (($kernel // []) | map(tonumber))})'
        fi
@@ -3777,6 +3853,26 @@
        find /var/lib/pcrlock.d/"$component".pcrlock.d -name '*.pcrlock' ! 
-name 'shift-*.pcrlock' -delete
 }
 
+# The shifted variation matches the current event log, and when the
+# component did not change it is identical to the one that was just
+# generated.  `systemd-pcrlock` drops the duplicated values from the
+# prediction, but only after walking every combination of variations,
+# so each copy doubles the cost of `predict` and `make-policy`.
+drop_duplicated_shifts()
+{
+       local shifted variation
+
+       for shifted in /var/lib/pcrlock.d/*.pcrlock.d/shift-*.pcrlock; do
+               for variation in "${shifted%/*}"/*.pcrlock; do
+                       [[ "$(basename "$variation")" != shift-* ]] || continue
+                       cmp -s "$shifted" "$variation" || continue
+                       dbg "Dropping $shifted, identical to $variation"
+                       rm "$shifted"
+                       break
+               done
+       done
+}
+
 uint64_le()
 {
        # 64 bit little endian representation of a number, as escape
@@ -4417,6 +4513,10 @@
 
 get_predicted_hashes()
 {
+       # Nothing generated any component yet, which is a valid state
+       # and not a reason to print a `find` error
+       [ -d /var/lib/pcrlock.d ] || return 0
+
        find /var/lib/pcrlock.d/ -name "*.pcrlock" -type f -exec jq -r 
'.records[].digests[] | select(.hashAlg == "sha256") | .digest' {} + | sort -u
 }
 
@@ -4926,18 +5026,16 @@
 
        shift_component 250-firmware-code-early
        shift_component 550-firmware-code-late
-       pcrlock lock-firmware-code
+       pcrlock_lock lock-firmware-code
 
        shift_component 250-firmware-config-early
        shift_component 550-firmware-config-late
-       pcrlock lock-firmware-config
+       pcrlock_lock 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
+       pcrlock_lock lock-secureboot-policy
        shift_component 620-secureboot-authority
-       pcrlock lock-secureboot-authority &> /dev/null || true
+       pcrlock_lock lock-secureboot-authority
        # Generates 620-secureboot-authority when the verb cannot
        pcrlock_secureboot_sbatlevel
 
@@ -5003,6 +5101,8 @@
                pcrlock_grub2_bls
        fi
 
+       drop_duplicated_shifts
+
        # The copy in the ESP is imported by `dracut-pcr-signature`,
        # and can be missing after a new ESP installation.  Both copies
        # are identical, so a missing one can be restored from the
@@ -5181,7 +5281,7 @@
                echo "Recovery PIN: $pin"
                if [ -x /usr/bin/qrencode ]; then
                        echo "You can also scan it with your mobile phone:"
-                       qrencode -t utf8i "$pin"
+                       echo -n "$pin" | qrencode -t utf8i
                fi
 
                # Add the generated recovery PIN to the kernel
@@ -5793,7 +5893,21 @@
 in_lockout()
 {
        command -v tpm2_getcap &> /dev/null || { warn "tpm2_getcap not found"; 
return 1; }
-       tpm2_getcap properties-variable | grep -q 'inLockout: *1'
+
+       # `tpm2-tools` probes a fixed list of TCTIs and `tabrmd` comes
+       # before the device.  A system that has `libtss2-tcti-tabrmd0`
+       # installed but no `tpm2-abrmd` running loads the library, fails
+       # to reach the service on the bus, and prints a GDBus warning
+       # plus "Could not initialize TCTI file" before falling back to
+       # /dev/tpmrm0.  Nothing is broken by that, but it is the first
+       # thing on stderr, and an installer that collects stderr reports
+       # it as the reason for whatever fails next.  Naming the device
+       # skips the probe.
+       #
+       # This is the same device that every `systemd-cryptenroll` and
+       # `systemd-pcrlock` call here already talks to: `tpm2_context_new`
+       # hardcodes it, and for the same reason
+       TPM2TOOLS_TCTI="device:/dev/tpmrm0" tpm2_getcap properties-variable | 
grep -q 'inLockout: *1'
 }
 
 is_same_device()
@@ -6178,7 +6292,7 @@
                echo "Recovery key: $key"
                if [ -x /usr/bin/qrencode ]; then
                        echo "You can also scan it with your mobile phone:"
-                       qrencode -t utf8i "$key"
+                       echo -n "$key" | qrencode -t utf8i
                fi
        fi
 
@@ -6610,6 +6724,7 @@
 status_tpm2_device=
 status_pin_reachable=
 status_dangling=
+status_pcr15_halt=
 
 # Whether "$status_dangling" holds this entry.  The names in it carry
 # the ".conf" that the boot loader interface variables leave out, so
@@ -6984,11 +7099,27 @@
        fi
        status_row "Enabled" "yes"
 
+       # The same three checks that `measure-pcr-validator` makes at
+       # boot, in the same order.  Any of them failing ends in
+       # `FailureAction=poweroff-immediate`, so this is not a detail of
+       # the report but the answer to "why did the machine power off"
+       if [ ! -e "$prediction" ]; then
+               status_pcr15_halt="there is no prediction file"
+       elif [ ! -e "$prediction.sha256" ]; then
+               status_pcr15_halt="the prediction is not signed"
+       elif ! openssl dgst -sha256 \
+                       -verify /var/lib/sdbootutil/measure-pcr-public.pem \
+                       -signature "$prediction.sha256" \
+                       "$prediction" &> /dev/null; then
+               status_pcr15_halt="the signature of the prediction is not valid"
+       fi
+
        if [ -n "$arg_full" ]; then
-               local present="MISSING" signed=", NOT SIGNED"
-               [ ! -e "$prediction" ] || present="present"
-               [ ! -e "$prediction.sha256" ] || signed=", signed"
-               status_row "Prediction" "$present$signed"
+               if [ -n "$status_pcr15_halt" ]; then
+                       status_row "Prediction" "UNUSABLE, $status_pcr15_halt"
+               else
+                       status_row "Prediction" "present, signed"
+               fi
 
                if [ -e /var/lib/sdbootutil/measure-pcr-public.pem ]; then
                        status_row "Public key" "yes"
@@ -7201,6 +7332,28 @@
        EOF
 }
 
+# One row for a binary in the ESP against the one that the system ships,
+# decided the same way that `bootloader_needs_update` decides
+status_version_row()
+{
+       local label="$1" deployed="$2" system="$3"
+
+       if [ -z "$system" ]; then
+               status_row "$label" "$deployed, the version of the system 
cannot be read"
+               return
+       fi
+
+       # The comparison goes to stdout ("261.2 == 261.2"), and only the
+       # exit status is wanted here
+       local status=0
+       systemd-analyze compare-versions "$deployed" "$system" > /dev/null 2>&1 
|| status="$?"
+       case "$status" in
+               11) status_row "$label" "$deployed, newer than the $system of 
the system" ;;
+               12) status_row "$label" "OUTDATED: $deployed in the ESP, 
$system in the system" ;;
+               *)  status_row "$label" "up to date ($deployed)" ;;
+       esac
+}
+
 # What the machine booted, and what it will boot next
 status_boot()
 {
@@ -7220,18 +7373,19 @@
 
        if [ -z "$deployed" ]; then
                status_row "In the ESP" "no bootloader found in the ESP"
-       elif [ -z "$system" ]; then
-               status_row "In the ESP" "$deployed, the version of the system 
cannot be read"
        else
-               # The comparison goes to stdout ("261.2 == 261.2"), and
-               # only the exit status is wanted here
-               local cmp=0
-               systemd-analyze compare-versions "$deployed" "$system" > 
/dev/null 2>&1 || cmp="$?"
-               case "$cmp" in
-                       11) status_row "In the ESP" "$deployed, newer than the 
$system of the system" ;;
-                       12) status_row "In the ESP" "OUTDATED: $deployed in the 
ESP, $system in the system" ;;
-                       *)  status_row "In the ESP" "up to date ($deployed)" ;;
-               esac
+               status_version_row "In the ESP" "$deployed" "$system"
+       fi
+
+       # `install_bootloader` deploys the shim and the bootloader
+       # together, so a shim that is not the one of the system is also a
+       # reason to run it, the way `bootloader_needs_update` counts it.
+       # Without a shim in the ESP there is nothing to compare
+       local deployed_shim="" system_shim=""
+       deployed_shim="$(shim_version 2> /dev/null)" || deployed_shim=""
+       if [ -n "$deployed_shim" ]; then
+               system_shim="$(shim_version "$(find_shim)" 2> /dev/null)" || 
system_shim=""
+               status_version_row "Shim" "$deployed_shim" "$system_shim"
        fi
 
        # Its own copy of the entry list, and not "update_entries": that
@@ -7242,17 +7396,16 @@
        [ -n "$entries" ] || entries="[]"
 
        # `bootctl` builds "isSelected" by matching the entry ID against
-       # "LoaderEntrySelected", but grub2-bls writes the variable without
-       # the ".conf" suffix, so nothing matches there and "isSelected" is
-       # always null.  Compare both spellings against the variable, which
-       # `bli_efi_var_get` returns lower-cased (same workaround as in
-       # "list_entries")
-       local selected="" booted=""
+       # "LoaderEntrySelected", but grub2-bls spells the variable
+       # differently and nothing matches there, so "isSelected" is always
+       # null.  Compare it here too, through "entry_key" (same workaround
+       # as in "list_entries")
+       local selected="" key="" booted=""
        selected="$(bli_efi_var_get "LoaderEntrySelected" 2> /dev/null)" || 
selected=""
-       booted="$(jq -r --arg s "$selected" \
+       key="$(entry_key "$selected")"
+       booted="$(jq -r --arg k "$key" \
                     'first(.[] | select(.isSelected == true
-                                        or (.id | ascii_downcase) == $s
-                                        or (.id | ascii_downcase) == ($s + 
".conf"))
+                                        or (.id | sub("\\.conf$"; "")) == $k)
                           | .id) // empty' <<<"$entries")"
        # No entry claims it, so report the raw variable: on a machine
        # that booted something the ESP no longer offers, the name is the
@@ -7416,6 +7569,16 @@
                warn "Run 'sdbootutil cleanup --repair' to write them again 
from the kernels that are still installed"
        }
 
+       # The validator runs on every boot and powers the machine off
+       # when the prediction it needs is not usable.  Nothing else in
+       # the report says that the next boot does not finish, and the
+       # rows that carry the two halves of it are behind `--full`
+       [ -z "$status_pcr15_halt" ] || {
+               echo
+               warn "/etc/crypttab asks to measure PCR 15, but 
$status_pcr15_halt. 'measure-pcr-validator' powers the machine off at the next 
boot"
+               warn "Run 'sdbootutil update-predictions' to write it, or boot 
once with 'measure-pcr-validator.ignore=yes' in the cmdline"
+       }
+
        # A policy with no device behind it is not an error, but it is
        # never what the reader assumes when they see "Backend: pcrlock".
        # It is what a partial unenroll leaves, and it makes the whole
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sdbootutil-1+git20260909.7cfa1f0/sdbootutil-update-predictions.service 
new/sdbootutil-1+git20260924.2b7b94e/sdbootutil-update-predictions.service
--- old/sdbootutil-1+git20260909.7cfa1f0/sdbootutil-update-predictions.service  
2026-09-09 13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/sdbootutil-update-predictions.service  
2026-09-24 22:03:19.000000000 +0200
@@ -3,7 +3,7 @@
 ConditionSecurity=tpm2
 
 [Service]
-Type=oneshot
+Type=exec
 KeyringMode=shared
 PrivateTmp=yes
 # Predictions are only needed when a device is unlocked with the TPM2.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdbootutil-1+git20260909.7cfa1f0/sdbootutil.spec 
new/sdbootutil-1+git20260924.2b7b94e/sdbootutil.spec
--- old/sdbootutil-1+git20260909.7cfa1f0/sdbootutil.spec        2026-09-09 
13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/sdbootutil.spec        2026-09-24 
22:03:19.000000000 +0200
@@ -151,6 +151,7 @@
 Requires:       %{name} = %{version}
 Requires:       bash
 Requires:       bash-completion
+Supplements:    (%{name} and bash-completion)
 BuildArch:      noarch
 
 %description bash-completion
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdbootutil-1+git20260909.7cfa1f0/tests/README.md 
new/sdbootutil-1+git20260924.2b7b94e/tests/README.md
--- old/sdbootutil-1+git20260909.7cfa1f0/tests/README.md        2026-09-09 
13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/tests/README.md        2026-09-24 
22:03:19.000000000 +0200
@@ -61,6 +61,7 @@
 | `update-all-entries` | editing an entry in place is not a silent no-op, and 
is deterministic |
 | `boot-counter` | a `+N` counter is kept, and does not hide the entry from 
removal |
 | `repair-entry` | `cleanup --repair` restores the file and keeps a 
hand-edited command line |
+| `set-default` | the default entry is written where the same loader reads it 
back |
 
 ### What it reports today
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/sdbootutil-1+git20260909.7cfa1f0/tests/scenarios/set-default 
new/sdbootutil-1+git20260924.2b7b94e/tests/scenarios/set-default
--- old/sdbootutil-1+git20260909.7cfa1f0/tests/scenarios/set-default    
1970-01-01 01:00:00.000000000 +0100
+++ new/sdbootutil-1+git20260924.2b7b94e/tests/scenarios/set-default    
2026-09-24 22:03:19.000000000 +0200
@@ -0,0 +1,36 @@
+#!/bin/bash
+# scenario: set-default
+# Set the default boot entry and read it back
+#
+# The default is written to a different place on each loader --
+# loader.conf for systemd-boot, grubenv for grub2-bls -- and the reader
+# has to look in the same one. It fails silently: the write lands
+# somewhere nothing reads, get-default keeps answering with bootctl's
+# guess, and the machine boots the entry it booted before.
+#
+# --no-variables is what puts the on-disk path under test, and is also
+# what keeps this out of the real EFI variables.
+sdb()
+{
+       "$SDBOOTUTIL" --esp-path "$ESP" --no-variables --disable-predictions 
"$@"
+}
+
+# Prefer an entry that is not the default already, so that the write has
+# something to change, and settle for the only one there is otherwise.
+# `first` over an empty stream prints nothing rather than "null", so the
+# preference has to be expressed inside jq: a guard out here would read
+# the empty output as an answer and skip the guest
+target="$(bootctl list --json=short | jq -r '
+       [.[] | select(.type == "type1")] as $entries
+       | (first($entries[] | select(.isDefault != true))
+          // first($entries[]) // empty) | .id')"
+[ -n "$target" ] || exit 77
+
+sdb set-default "$target"
+
+got="$(sdb get-default)"
+[ "$got" = "$target" ] || {
+       echo "get-default returned '$got', expected '$target'"
+       exit 1
+}
+echo "default round trip: $target"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdbootutil-1+git20260909.7cfa1f0/tests/unit 
new/sdbootutil-1+git20260924.2b7b94e/tests/unit
--- old/sdbootutil-1+git20260909.7cfa1f0/tests/unit     2026-09-09 
13:40:14.000000000 +0200
+++ new/sdbootutil-1+git20260924.2b7b94e/tests/unit     2026-09-24 
22:03:19.000000000 +0200
@@ -2,8 +2,10 @@
 # SPDX-License-Identifier: MIT
 # SPDX-FileCopyrightText: Copyright 2026 SUSE LLC
 #
-# Unit tests for the routing of the secrets: which source wins, what is
-# published to the kernel keyring, and which warnings are printed.
+# Unit tests for the library half of sdbootutil: the routing of the
+# secrets -- which source wins, what is published to the keyring, and
+# which warnings are printed -- the entry names the boot loaders spell
+# differently, the versions the status report compares, and `grubenv`.
 #
 # These are decisions the tool takes *before* it writes anything to the
 # TPM2, so they do not need one, and they do not need a guest either: a
@@ -41,6 +43,9 @@
 KEYS=(cryptenroll sdbootutil sdbootutil-key sdbootutil-pw sdbootutil-pin
       sdbootutil-recovery-pin sdbootutil-tpm2-pin)
 
+# A boot entry ID as `bootctl` reports it, for the entry_key cases
+ENTRY="opensuse-microos-7.2.3-1-default-10"
+
 [ "${1:-}" != "-h" ] && [ "${1:-}" != "--help" ] || {
        sed -n '4,26p' "${BASH_SOURCE[0]}" | sed 's/^# \?//'
        exit 0
@@ -333,6 +338,130 @@
        no_key sdbootutil-recovery-pin
 }
 
+# entry_key: the spellings a boot loader can leave in
+# "LoaderEntrySelected", all reduced to the ID that `bootctl` reports.
+# systemd-boot writes that ID as it is; grub2-bls drops the ".conf"
+# suffix and keeps the boot counter, which it records before
+# "systemd-bless-boot" renames the file
+
+t_ek_systemd_boot()
+{
+       load
+
+       is "the ID itself" "$ENTRY" "$(entry_key "$ENTRY.conf")"
+}
+
+t_ek_no_suffix()
+{
+       load
+
+       is "no .conf suffix" "$ENTRY" "$(entry_key "$ENTRY")"
+}
+
+t_ek_counter()
+{
+       load
+
+       is "counter alone" "$ENTRY" "$(entry_key "$ENTRY+3")"
+       is "counter and suffix" "$ENTRY" "$(entry_key "$ENTRY+3.conf")"
+}
+
+t_ek_counted_boot()
+{
+       load
+
+       is "one attempt counted" "$ENTRY" "$(entry_key "$ENTRY+2-1")"
+}
+
+t_ek_not_a_counter()
+{
+       load
+
+       # Only "+" followed by digits is a counter, so an entry token
+       # that carries one keeps it
+       is "a + in the name" "my+entry-1" "$(entry_key "my+entry-1.conf")"
+       is "nothing to reduce" "" "$(entry_key "")"
+}
+
+# status_version_row: the three answers that the report can give about a
+# binary in the ESP, the same ones that `bootloader_needs_update` gives
+
+t_svr_current()
+{
+       load
+
+       says "row" "$(status_version_row "In the ESP" 261.2 261.2)" "up to date 
(261.2)"
+}
+
+t_svr_outdated()
+{
+       load
+
+       says "row" "$(status_version_row "In the ESP" 258.4 261.2)" \
+            "OUTDATED: 258.4 in the ESP, 261.2 in the system"
+}
+
+t_svr_newer()
+{
+       load
+
+       says "row" "$(status_version_row "In the ESP" 262 261.2)" \
+            "262, newer than the 261.2 of the system"
+}
+
+# The shim reports a major.minor that does not change between builds, so
+# the modification time appended to it is what decides
+t_svr_shim_mtime()
+{
+       load
+
+       says "older" "$(status_version_row Shim 16.1-202506170714 
16.1-202601011200)" "OUTDATED"
+       says "newer" "$(status_version_row Shim 16.1-202601011200 
16.1-202506170714)" "newer than"
+}
+
+t_svr_no_system()
+{
+       load
+
+       says "row" "$(status_version_row Shim 16.1-202506170714 "")" \
+            "the version of the system cannot be read"
+}
+
+# grubenv_set: GRUB2 reads a block of exactly 1024 bytes, so the size is
+# the whole of what the padding has to get right
+
+t_grubenv_size()
+{
+       load
+       # shellcheck disable=SC2034  # both are read by the sourced library
+       esp_root="$workdir" esp_dst=""
+
+       grubenv_set default opensuse-tumbleweed-7.2.3-1-default-10
+       is "size" 1024 "$(stat -c %s "$workdir/grubenv")"
+
+       grubenv_set timeout 5
+       is "size after a second key" 1024 "$(stat -c %s "$workdir/grubenv")"
+
+       is "default" opensuse-tumbleweed-7.2.3-1-default-10 "$(grubenv_get 
default)"
+       is "timeout" 5 "$(grubenv_get timeout)"
+}
+
+# A block whose variables already fill it needs no padding at all, and
+# that is where one byte used to be written anyway
+t_grubenv_full()
+{
+       load
+       # shellcheck disable=SC2034  # both are read by the sourced library
+       esp_root="$workdir" esp_dst=""
+
+       # 25 bytes of header, and a "default=" line that takes the rest
+       printf '%s\n' "# GRUB Environment Block" > "$workdir/grubenv"
+       printf 'default=%s\n' "$(printf '%*s' 990 "" | tr ' ' x)" >> 
"$workdir/grubenv"
+
+       grubenv_set default "$(printf '%*s' 990 "" | tr ' ' y)"
+       is "size" 1024 "$(stat -c %s "$workdir/grubenv")"
+}
+
 ####### the runner #######
 
 run_case()
@@ -380,6 +509,18 @@
        t_erk_recovery_pin_env      enroll_recovery_key enrolls the presented 
recovery PIN as the key
        t_erk_diverging             enroll_recovery_key reports a key that 
differs from the PIN
        t_erk_unreachable_pin       enroll_recovery_key publishes nothing when 
a PIN it cannot reach exists
+       t_ek_systemd_boot           entry_key takes the ID that systemd-boot 
writes
+       t_ek_no_suffix              entry_key takes the ID without the ".conf" 
suffix
+       t_ek_counter                entry_key drops the boot counter
+       t_ek_counted_boot           entry_key drops a counter with the attempts 
done
+       t_ek_not_a_counter          entry_key keeps a "+" that is not a boot 
counter
+       t_svr_current               status_version_row reports a version that 
is the one of the system
+       t_svr_outdated              status_version_row reports a version older 
than the one of the system
+       t_svr_newer                 status_version_row reports a version newer 
than the one of the system
+       t_svr_shim_mtime            status_version_row compares the shim by its 
modification time
+       t_svr_no_system             status_version_row reports a system version 
that cannot be read
+       t_grubenv_size              grubenv_set writes a block of exactly 1024 
bytes
+       t_grubenv_full              grubenv_set does not pad a block that is 
already full
 EOF
 
 echo

++++++ sdbootutil.obsinfo ++++++
--- /var/tmp/diff_new_pack.QvX9Cm/_old  2026-09-28 10:37:30.127460411 +0200
+++ /var/tmp/diff_new_pack.QvX9Cm/_new  2026-09-28 10:37:30.129460494 +0200
@@ -1,5 +1,5 @@
 name: sdbootutil
-version: 1+git20260909.7cfa1f0
-mtime: 1788954014
-commit: 7cfa1f0ab1bba2c808ef5ff63aff22b26aa42d08
+version: 1+git20260924.2b7b94e
+mtime: 1790280199
+commit: 2b7b94e0792520e76bfdf84650365aa4d251560c
 

Reply via email to