diff --git a/meta-avocado-nxp/stone/stone-imx95-frdm.json b/meta-avocado-nxp/stone/stone-imx95-frdm.json index 3a3c8c6f..79516ad5 100644 --- a/meta-avocado-nxp/stone/stone-imx95-frdm.json +++ b/meta-avocado-nxp/stone/stone-imx95-frdm.json @@ -132,8 +132,6 @@ { "name": "var", "image": "var", - "size": 512, - "size_unit": "mebibytes", "expand": "true" } ] diff --git a/meta-avocado/recipes-core/cryptsetup-var/files/cryptsetup-var.sh b/meta-avocado/recipes-core/cryptsetup-var/files/cryptsetup-var.sh index 0ef4fcb0..82391e81 100644 --- a/meta-avocado/recipes-core/cryptsetup-var/files/cryptsetup-var.sh +++ b/meta-avocado/recipes-core/cryptsetup-var/files/cryptsetup-var.sh @@ -217,13 +217,45 @@ maybe_resize() { # Interrupted mid-way (power cut), LUKS2 records the reencryption in its # metadata; open_var resumes it before opening. A filesystem whose size cannot # be read gets the whole-partition reencryption instead - slower, still correct. +# +# A /var that has already been GROWN to fill the partition (a deployed device +# whose OTA turns encryption on) has no free tail for the header. btrfs can +# shrink online, so give the header its 32 MiB back first: mount, resize by +# the deficit, unmount, then reencrypt as usual. Anything else about the +# migration is the same; it just starts from a filesystem that no longer ends +# at the partition's last sector. +btrfs_total_bytes() { + btrfs inspect-internal dump-super "$VAR_DEV" 2>/dev/null | awk '/^total_bytes/{print $2}' +} + +shrink_btrfs_for_header() { + shrink_mib="$1" + mnt=/run/cryptsetup-var-shrink + echo "cryptsetup-var: btrfs on $VAR_DEV fills the partition - shrinking it by ${shrink_mib} MiB to make room for the LUKS2 header" + mkdir -p "$mnt" + if ! mount -t btrfs "$VAR_DEV" "$mnt"; then + echo "cryptsetup-var: cannot mount $VAR_DEV to shrink it - cannot encrypt in place" >&2 + exit 1 + fi + if ! btrfs filesystem resize "-${shrink_mib}M" "$mnt"; then + umount "$mnt" + echo "cryptsetup-var: btrfs refused to shrink by ${shrink_mib} MiB (tail in use or too full) - cannot encrypt in place" >&2 + exit 1 + fi + umount "$mnt" +} + encrypt_in_place() { fstype="$1" part_bytes=$(blockdev --getsize64 "$VAR_DEV") fs_bytes="" if [ "$fstype" = "btrfs" ] && command -v btrfs >/dev/null 2>&1; then - fs_bytes=$(btrfs inspect-internal dump-super "$VAR_DEV" 2>/dev/null \ - | awk '/^total_bytes/{print $2}') + fs_bytes=$(btrfs_total_bytes) + if [ -n "$fs_bytes" ] && [ "$fs_bytes" -gt 0 ] 2>/dev/null \ + && [ $(( part_bytes - fs_bytes )) -lt 33554432 ]; then + shrink_btrfs_for_header $(( (33554432 - (part_bytes - fs_bytes) + 1048575) / 1048576 )) + fs_bytes=$(btrfs_total_bytes) + fi fi size_opt="" if [ -n "$fs_bytes" ] && [ "$fs_bytes" -gt 0 ] 2>/dev/null; then @@ -237,8 +269,18 @@ encrypt_in_place() { echo "cryptsetup-var: $fstype on $VAR_DEV leaves no 32 MiB for a LUKS header - cannot encrypt in place" >&2 exit 1 fi + # Tell whoever is watching the console what is about to happen: a seeded + # /var can be tens of GiB, so this step can run for many minutes with + # nothing else on screen. It is a one-time migration and resumable after a + # power cut (resume_reencrypt). Periodic progress needs cryptsetup >= 2.4. + if [ -n "$size_opt" ]; then work_mib=$want_mib; else work_mib=$(( part_bytes / 1048576 )); fi + progress_opt="" + if cryptsetup --help 2>&1 | grep -q -- --progress-frequency; then + progress_opt="--progress-frequency 30" + fi echo "cryptsetup-var: first boot - encrypting existing $fstype on $VAR_DEV in place${size_opt:+ ($size_opt)}" - # shellcheck disable=SC2086 # size_opt is two words on purpose + echo "cryptsetup-var: ${work_mib} MiB to convert - one-time migration, resumes after a power cut${progress_opt:+, progress every 30 s}" + # shellcheck disable=SC2086 # size_opt / progress_opt are two words on purpose cryptsetup reencrypt --encrypt \ --type luks2 \ --cipher aes-xts-plain64 \ @@ -246,6 +288,7 @@ encrypt_in_place() { --hash sha256 \ --reduce-device-size 32M \ $size_opt \ + $progress_opt \ --key-file "$KEY_FILE" \ --batch-mode \ "$VAR_DEV" diff --git a/meta-avocado/recipes-core/cryptsetup-var/tests/test-cryptsetup-var-inplace.sh b/meta-avocado/recipes-core/cryptsetup-var/tests/test-cryptsetup-var-inplace.sh index 3b82f580..0abf0325 100755 --- a/meta-avocado/recipes-core/cryptsetup-var/tests/test-cryptsetup-var-inplace.sh +++ b/meta-avocado/recipes-core/cryptsetup-var/tests/test-cryptsetup-var-inplace.sh @@ -27,6 +27,7 @@ cat > "$work/bin/cryptsetup" <<'S' #!/bin/sh echo "cryptsetup $*" >> "$LOG" case "$1" in + --help) echo " --progress-frequency=secs" ;; isLuks) [ -e "$STATE/luks" ] ;; luksDump) cat "$STATE/dump" 2>/dev/null; exit 0 ;; luksFormat) touch "$STATE/luks" ;; @@ -46,8 +47,13 @@ S cat > "$work/bin/btrfs" <<'S' #!/bin/sh echo "btrfs $*" >> "$LOG" -case "$1" in inspect-internal) printf 'total_bytes\t\t%s\n' "$(cat "$STATE/fsbytes")" ;; esac +case "$1" in + inspect-internal) printf 'total_bytes\t\t%s\n' "$(cat "$STATE/fsbytes")" ;; + filesystem) [ "$2" = resize ] && { n="${3#-}"; n="${n%M}"; echo $(( $(cat "$STATE/fsbytes") - n * 1048576 )) > "$STATE/fsbytes"; } ;; +esac S +printf '#!/bin/sh\necho "mount $*" >> "$LOG"\n' > "$work/bin/mount" +printf '#!/bin/sh\necho "umount $*" >> "$LOG"\n' > "$work/bin/umount" printf '#!/bin/sh\necho "0 4194304 crypt aes-xts-plain64 :64:logon:x 0 %s 32768 1 allow_discards"\n' "8:0" > "$work/bin/dmsetup" printf '#!/bin/sh\nexit 0\n' > "$work/bin/modprobe" printf '#!/bin/sh\nexit 0\n' > "$work/bin/systemd-cryptenroll" @@ -66,8 +72,8 @@ run() { # run # --- Case 1: flashed 128 MiB btrfs -> in-place reencrypt confined to fs+32M --- s="$work/c1"; mkdir -p "$s"; echo btrfs > "$s/fstype"; echo 134217728 > "$s/fsbytes" run "$s" || bad "case 1 script exit" -if grep -q "^cryptsetup reencrypt --encrypt --type luks2 --cipher aes-xts-plain64 --key-size 512 --hash sha256 --reduce-device-size 32M --device-size 160M --key-file .* --batch-mode $blk\$" "$s/log"; then - ok "plaintext btrfs is re-encrypted in place, confined to fs + 32 MiB (160M)" +if grep -q "^cryptsetup reencrypt --encrypt --type luks2 --cipher aes-xts-plain64 --key-size 512 --hash sha256 --reduce-device-size 32M --device-size 160M --progress-frequency 30 --key-file .* --batch-mode $blk\$" "$s/log"; then + ok "plaintext btrfs is re-encrypted in place, confined to fs + 32 MiB (160M), with progress" else bad "unexpected reencrypt command: $(grep reencrypt "$s/log" || echo none)"; fi grep -q "^cryptsetup luksFormat" "$s/log" && bad "luksFormat ran over a flashed filesystem" || ok "luksFormat never touches a flashed filesystem" grep -q "^cryptsetup luksOpen --key-file" "$s/log" && ok "container opened with the recovery key afterwards" || bad "container not opened" @@ -85,4 +91,22 @@ if grep -q "^cryptsetup reencrypt --resume-only" "$s/log" && [ "$(grep -n 'reenc ok "interrupted reencryption is resumed before the container is opened" else bad "resume not attempted first: $(grep -n 'reencrypt\|luksOpen' "$s/log")"; fi +# --- Case 4: btrfs grown to fill the 2 GiB partition (deployed device, OTA turns +# encryption on) -> shrink it by the 32 MiB header deficit first, then reencrypt --- +s="$work/c4"; mkdir -p "$s"; echo btrfs > "$s/fstype"; echo 2147483648 > "$s/fsbytes" +run "$s" || bad "case 4 script exit" +if grep -q "^btrfs filesystem resize -32M /run/cryptsetup-var-shrink$" "$s/log"; then + ok "grown btrfs is shrunk by exactly the 32 MiB header deficit" +else bad "no/unexpected shrink: $(grep 'btrfs filesystem' "$s/log" || echo none)"; fi +seq_ok=1 +m=$(grep -n '^mount -t btrfs' "$s/log" | head -1 | cut -d: -f1); r=$(grep -n '^btrfs filesystem resize' "$s/log" | head -1 | cut -d: -f1) +u=$(grep -n '^umount' "$s/log" | head -1 | cut -d: -f1); e=$(grep -n '^cryptsetup reencrypt --encrypt' "$s/log" | head -1 | cut -d: -f1) +{ [ -n "$m" ] && [ -n "$r" ] && [ -n "$u" ] && [ -n "$e" ] && [ "$m" -lt "$r" ] && [ "$r" -lt "$u" ] && [ "$u" -lt "$e" ]; } || seq_ok=0 +[ "$seq_ok" = 1 ] && ok "shrink is mount -> resize -> umount, all before the reencrypt" || bad "wrong shrink ordering: $(grep -n 'mount\|resize\|reencrypt --encrypt' "$s/log")" +grep -q "^cryptsetup reencrypt --encrypt .*--device-size 2048M " "$s/log" && ok "reencrypt then covers the shrunk fs + header (2048M = whole partition)" || bad "unexpected device-size after shrink: $(grep 'reencrypt --encrypt' "$s/log")" +grep -q "MiB to convert" "$s/out" && ok "operator is told how much is being converted" || bad "no size/progress line on the console" + +# --- Case 5: flashed small btrfs (case 1 shape) must NOT be shrunk --- +grep -q "^btrfs filesystem resize" "$work/c1/log" && bad "small flashed btrfs was shrunk needlessly" || ok "a filesystem with free tail is left alone" + echo; echo "passed: $pass failed: $fail"; [ "$fail" -eq 0 ]