Skip to content

41_snapshots-btrfs aborts on LUKS-on-Btrfs with detached header & external /boot (grub-probe: cannot get cryptodisk …) #377

@PapiJalopy

Description

@PapiJalopy

Environment

Item Value
Distro Arch Linux
grub-btrfs 4.13-yabsnap_info_support-2024-03-06
GRUB grub-improved-luks2-git 2.12.r303.g86e8f2c4b-1
btrfs-progs 6.15
Snapper 0.12.2
Layout single encrypted container on SSD, no partitions, formatted with Btrfs → LUKS (header on USB) →
same USB stick holds /boot (vfat)
Devices /dev/disk/by-id/<REDACTED>/dev/mapper/enc (UUID =REDACTED)
/dev/sdb1 (USB, UUID =REDACTED) → /boot & header
[user@hostname ~]$ lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
sda           8:0    0 953.9G  0 disk
└─enc       254:0    0 953.9G  0 crypt /.snapshots
                                       /var
                                       /home
                                       /pkg
                                       /swap
                                       /
sdb           8:16   1  29.1G  0 disk
└─sdb1        8:17   1     1G  0 part  /boot

Symptom

Running

sudo grub-mkconfig -o /boot/grub/grub.cfg

terminates during execution of 41_snapshots-btrfs with only

Detecting snapshots ...

When set -x tracing is enabled the last command executed is

root_uuid=$(${grub_probe} --device ${root_device} --target=fs_uuid)

which fails with

/usr/bin/grub-probe: error: cannot get cryptodisk from source disk `hostdisk//dev/sda'.
Internal error: Unreleased memory pool(s) found.

No grub-btrfs.cfg is produced, so the snapshot submenu never appears

Why grub-probe --target=fs_uuid can fail

I would first like to explain why grub-probe --target=fs_uuid can fail in this scenario

  • grub-probe --target=fs_uuid tries to parse the LUKS header in-place on the disk.

  • When it finds the disk it expects to find a LUKS header in the
    first sector (inline header).
    With a detached header that sector is just random data, so parsing fails
    and grub-probe exits.

My solution to grub-probe --target=fs_uuid failing

# 1. Try the original, fast path first
root_uuid=$(${grub_probe} --device "${root_device}" --target=fs_uuid 2>/dev/null) || true
#          └──────────────────┬───────────────────────────────────────────────┘
#               may fail → we ignore the exit code with `|| true`

# 2. Fallback 1: blkid
[ -z "$root_uuid" ] && root_uuid=$(blkid -s UUID -o value "${root_device}")

# 3. Fallback 2: lsblk   (rarely needed, but covers busybox systems without blkid)
[ -z "$root_uuid" ] && root_uuid=$(lsblk -no UUID "${root_device}")

# 4. Hard stop if we *still* have nothing
[ -z "$root_uuid" ] && print_error "Cannot determine UUID of ${root_device}"

Same block is applied to boot_uuid.
This does not change the underlying logic so script will continue on both inline-header and detached-header systems.

What this achieves

  • Normal systems (inline header) still use GRUB’s own probe

  • Detached-header or exotic file-system configurations fall back to kernel knowledge

  • Script exits with a error message only if all methods fail

Continuing the debugging.

After applying the above patch I re-ran 41_snapshots-btrfs with the set -x flag to continue monitoring execution tracing.
I was then caught up on this command

list_insmods+=("cryptomount -u $(echo $GRUB_CMDLINE_LINUX_DEFAULT | grep -o -P '(?<=cryptdevice=UUID=).*(?=:cryptdev)')")

It was failing to grep the UUID from GRUB_CMDLINE_LINUX_DEFAULT string in /etc/default/grub because it didnt exist there. GRUB_CMDLINE_LINUX_DEFAULT on my system is only loglevel=3 quiet so it has no cryptdevice= string.
My cryptdevice= string is part of GRUB_CMDLINE_LINUX.

also with a detached header Arch recommends:

cryptdevice=/dev/disk/by-id/<SSD>:enc:header

so grep '(?<=cryptdevice=UUID=)…' would return nothing anyways and grep exits so script
dies again.

You may see why this caused incompatibility with the script so the following patch block was made.

Cryptomount patch block and changes

The following code block snippet

# Enable LUKS encrypted devices support
case "$(echo "$GRUB_BTRFS_ENABLE_CRYPTODISK" | tr '[:upper:]' '[:lower:]')" in
    true)
            list_insmods=()
            list_insmods+=("insmod gzio")
            list_insmods+=("insmod part_gpt")
            list_insmods+=("insmod cryptodisk")
            list_insmods+=("insmod luks")
            list_insmods+=("insmod gcry_rijndael")
            list_insmods+=("insmod gcry_rijndael")
            list_insmods+=("insmod gcry_sha256")
            list_insmods+=("insmod ${boot_fs}")
            list_insmods+=("cryptomount -u $(echo $GRUB_CMDLINE_LINUX_DEFAULT | grep -o -P '(?<=cryptdevice=UUID=).*(?=:cryptdev)')")
            ;;
    *)
            list_insmods=("insmod ${boot_fs}")
            ;;
esac

is replaced with

## Enable LUKS encrypted devices support
case "$(echo "$GRUB_BTRFS_ENABLE_CRYPTODISK" | tr '[:upper:]' '[:lower:]')" in
    true)
        list_insmods=(
            "insmod gzio"
            "insmod part_gpt"
            "insmod cryptodisk"
            "insmod luks"
            "insmod gcry_rijndael"
            "insmod gcry_rijndael"
            "insmod gcry_sha256"
            "insmod ${boot_fs}"
        )

        #  Extract the <source> field of cryptdevice=<source>:<name>[:header]
        crypt_source="$(printf '%s %s\n' "$GRUB_CMDLINE_LINUX_DEFAULT" "$GRUB_CMDLINE_LINUX" \
                          | grep -o -P 'cryptdevice=\K[^:]+' || true)"

        # Turn the source into a UUID that cryptomount -u understands
        crypt_uuid=""
        if [[ "$crypt_source" =~ ^UUID=.* ]]; then            # already UUID=…
            crypt_uuid="${crypt_source#UUID=}"
        elif [[ "$crypt_source" == /dev/* ]]; then            # path → resolve → blkid
            real_dev=$(readlink -f "$crypt_source" 2>/dev/null || true)
            [ -b "$real_dev" ] && crypt_uuid=$(blkid -s UUID -o value "$real_dev" 2>/dev/null || true)
        fi

        # Exec the proper cryptomount command
        if [[ "$crypt_uuid" =~ ^[0-9a-fA-F-]{36}$ ]]; then
            list_insmods+=("cryptomount -u ${crypt_uuid}")
        else
            # last-resort: scan all crypto containers (works but a bit slower)
            list_insmods+=("cryptomount -a")
        fi
        ;;
    *)
        list_insmods=("insmod ${boot_fs}")
        ;;
esac

This section -

        crypt_source="$(printf '%s %s\n' "$GRUB_CMDLINE_LINUX_DEFAULT" \
                                          "$GRUB_CMDLINE_LINUX" \
                        | grep -o -P 'cryptdevice=\K[^:]+' || true)"
  • Extracts the <source> part of cryptdevice=

  • Combines both GRUB cmdline variables (DEFAULT and LINUX).

  • grep -o -P 'cryptdevice=\K[^:]+' keeps everything between
    cryptdevice= and the next :.
    Matches both forms:

    • cryptdevice=UUID=1111-2222-…: → returns UUID=1111-2222-…

    • cryptdevice=/dev/disk/by-id/nvme-SN123: → returns /dev/disk/by-id/…

  • || true prevents set -e from killing the script if no match is found.

For this snippet -

        crypt_uuid=""
        if [[ "$crypt_source" =~ ^UUID=.* ]]; then
            crypt_uuid="${crypt_source#UUID=}"       # strip leading “UUID=”
        elif [[ "$crypt_source" == /dev/* ]]; then
            real_dev=$(readlink -f "$crypt_source" 2>/dev/null || true)  # resolve symlinks
            [ -b "$real_dev" ] &&                               # ensure block device
              crypt_uuid=$(blkid -s UUID -o value "$real_dev" 2>/dev/null || true)
        fi

We try to extract a usable UUID that grub can understand.

For instance,

  1. We resolved a UUID=… so we just remove the prefix.

  2. we've resolved a device path (/dev/disk/by-id/…):
    check symlinks and ask blkid for its file-system UUID..

  3. If neither yields a UUID, crypt_uuid remains empty.

Next we have -

        if [[ "$crypt_uuid" =~ ^[0-9a-fA-F-]{36}$ ]]; then
            list_insmods+=("cryptomount -u ${crypt_uuid}")  # fast, direct
        else
            list_insmods+=("cryptomount -a")                # fallback autoscan
        fi

Here we append the correct cryptomount command depending on if we found a UUID or not.

  • cryptomount -u <uuid> if we found a UUID
    or
  • cryptomount -a if we didn't.

cryptomount -a scans every disk for LUKS headers. This is kinda like a last resort

Outcome

Boom

[user@hostname ~]$ sudo grub-mkconfig -o /boot/grub/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-linux-hardened
Found initrd image: /boot/initramfs-linux-hardened.img
Found fallback initrd image(s) in /boot: initramfs-linux-hardened-fallback.img
Warning: os-prober will be executed to detect other bootable partitions.
Its output will be used to detect bootable binaries on them and create new boot entries.
Found Windows Boot Manager on /dev/nvme0n1p1@/efi/Microsoft/Boot/bootmgfw.efi
Adding boot menu entry for UEFI Firmware Settings ...
Detecting snapshots ...
/usr/bin/grub-probe: error: cannot get cryptodisk from source disk `hostdisk//dev/sda'.
You have a memory leak (not released memory pool):
 [0x78b0c2866b0] dtree
Internal error: Unreleased memory pool(s) found.
Found snapshot: 2025-07-08 03:00:41 | @snapshots/17/snapshot                          | single   | timeline                                      |
Found snapshot: 2025-07-08 02:00:01 | @snapshots/16/snapshot                          | single   | timeline                                      |
Found snapshot: 2025-07-08 01:00:46 | @snapshots/15/snapshot                          | single   | timeline                                      |
Found snapshot: 2025-07-08 00:00:57 | @snapshots/14/snapshot                          | single   | timeline                                      |
Found snapshot: 2025-07-07 23:00:27 | @snapshots/13/snapshot                          | single   | timeline                                      |
Found snapshot: 2025-07-07 22:00:27 | @snapshots/12/snapshot                          | single   | timeline                                      |
Found snapshot: 2025-07-07 21:50:35 | @snapshots/11/snapshot                          | single   | timeline                                      |
Found snapshot: 2025-07-07 16:00:01 | @snapshots/10/snapshot                          | single   | timeline                                      |
Found snapshot: 2025-07-07 15:00:26 | @snapshots/9/snapshot                           | single   | timeline                                      |
Found snapshot: 2025-07-07 14:00:04 | @snapshots/8/snapshot                           | single   | timeline                                      |
Found snapshot: 2025-07-07 13:00:15 | @snapshots/7/snapshot                           | single   | timeline                                      |
Found snapshot: 2025-07-07 12:00:37 | @snapshots/6/snapshot                           | single   | timeline                                      |
Found snapshot: 2025-07-07 11:00:07 | @snapshots/5/snapshot                           | single   | timeline                                      |
Found snapshot: 2025-07-07 10:00:08 | @snapshots/4/snapshot                           | single   | timeline                                      |
Found snapshot: 2025-07-07 09:33:14 | timeshift-btrfs/snapshots/2025-07-07_09-33-14/@ | ondemand | {timeshift-autosnap} {created before upgrade} |
Found snapshot: 2025-07-07 09:12:08 | @snapshots/3/snapshot                           | single   | test snapshot 2                               |
Found snapshot: 2025-07-07 09:00:10 | @snapshots/2/snapshot                           | single   | timeline                                      |
Found snapshot: 2025-07-07 08:59:43 | @snapshots/1/snapshot                           | single   | test snapshot                                 |
Found 18 snapshot(s)
Unmount /tmp/grub-btrfs.tAroDMMCMK .. Success
done

Menu generation succeeds, 18 snapshot entries appear.
grub-btrfs.cfg is created and passed script checks.
Im sure you've noticed there's that same grub-probe error from before but its no longer an issue because we extract the UUID using blkid in my scenario, for normal users with inline-header that command wont fail the check and that error wont appear.

I've been struggling with this issue for a couple years now and finally learned enough to remedy the problem. I'll be making a pull request soon but knowing it might not get merged soon or even at all I've decided to make this issue to hopefully help the next person or somebody that's facing a similar issue.

41_snapshots-btrfs.txt

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions