repart: Correctly handle btrfs compression (#39597)

systemd-repart is incorrectly choosing the loop-mount
code path to copy files after formatting, instead of using the --rootdir
path, which is required by mkfs.btrfs to apply compression (since it's
on files, not the fs).

So two fixes (and an integ test):

1. If Btrfs compression is requested without a root directory (e.g.,
Compression= without CopyFiles=), we now log a warning and skip the
--compress flag. This prevents the mkfs.btrfs failure, and it's
meaningless anyway without any files.
2. The logic in repart now uses the --rootdir code path whenever the
partition is btrfs and compression is requested. Otherwise it still
won't work even in the legitimate case because use the loop mounting
code, which is too late to use --compress.

Fixes: https://github.com/systemd/systemd/issues/39584
This commit is contained in:
Yu Watanabe
2025-11-10 01:52:19 +09:00
committed by GitHub
3 changed files with 86 additions and 10 deletions

View File

@@ -1685,6 +1685,76 @@ EOF
grep -q 'UUID=[0-9a-f-]* /home btrfs discard,rw,nodev,suid,exec,subvol=@home,zstd:1,noatime,lazytime 0 1' "$root"/etc/fstab
}
testcase_btrfs_compression() {
local defs imgs loop output
if ! systemd-analyze compare-versions "$(btrfs --version | head -n 1 | awk '{ print $2 }')" ge v6.13; then
echo "btrfs-progs is not installed or older than v6.13, skipping test."
return 0
fi
defs="$(mktemp -d)"
imgs="$(mktemp -d)"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
chmod 0755 "$defs"
echo "*** testcase for btrfs compression with CopyFiles (OFFLINE=$OFFLINE) ***"
# Must not be in tmpfs due to exclusions. It also must be large and
# compressible so that the compression check succeeds later.
src=/etc/test-source-file
dd if=/dev/zero of="$src" bs=1M count=1 2>/dev/null
tee "$defs/btrfs-compressed.conf" <<EOF
[Partition]
Type=linux-generic
Format=btrfs
Compression=zstd
CopyFiles=$src:/test-file
SizeMinBytes=100M
SizeMaxBytes=100M
EOF
systemd-repart --offline="$OFFLINE" \
--definitions="$defs" \
--empty=create \
--size=auto \
--dry-run=no \
--seed="$seed" \
"$imgs/btrfs-compressed.img" 2>&1 | tee "$imgs/repart-output.txt"
rm "$src"
output=$(cat "$imgs/repart-output.txt")
assert_in "Rootdir from:" "$output"
assert_in "Compress:" "$output"
if [[ "$OFFLINE" == "yes" ]] || systemd-detect-virt --quiet --container; then
echo "Skipping mount verification (requires loop devices)"
return 0
fi
loop="$(losetup -P --show --find "$imgs/btrfs-compressed.img")"
# shellcheck disable=SC2064
trap "umount '$imgs/mount' 2>/dev/null || true; losetup -d '$loop' 2>/dev/null || true; rm -rf '$defs' '$imgs'" RETURN
echo "Loop device: $loop"
udevadm wait --timeout=60 --settle "${loop:?}p1"
mkdir -p "$imgs/mount"
mount -t btrfs "${loop:?}p1" "$imgs/mount"
[[ -f "$imgs/mount/test-file" ]]
[[ "$(stat -c%s "$imgs/mount/test-file")" == "1048576" ]]
if command -v compsize &>/dev/null; then
output=$(compsize "$imgs/mount/test-file" 2>&1)
assert_in "zstd" "$output"
fi
umount "$imgs/mount"
losetup -d "$loop"
}
testcase_varlink_list_devices() {
REPART="$(which systemd-repart)"
varlinkctl introspect "$REPART"