1
0
mirror of https://github.com/systemd/systemd synced 2026-04-06 23:24:52 +02:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Yu Watanabe
445b7073c7
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
2025-11-10 01:52:19 +09:00
Chris Down
2091caddb8 test: Add integration test for btrfs compression in repart
Add testcase_btrfs_compression() to verify that btrfs partitions with
Compression= and CopyFiles= directives work correctly.

The test verifies the fix for issue #39584, where mkfs.btrfs would fail
with "ERROR: --compression must be used with --rootdir" when repart
tried to create compressed btrfs filesystems.

The test creates a partition definition with Format=btrfs,
Compression=zstd, and CopyFiles=, then validates:

1. systemd-repart output shows "Rootdir from:" and "Compress:",
   confirming that the --rootdir code path is used
2. mkfs.btrfs is invoked with both --compress and --rootdir options
3. The file is successfully copied to the filesystem
4. Compression is actually applied (verified via compsize output
   containing "zstd")
2025-11-09 21:14:22 +08:00
Chris Down
f30a29245d repart: Force --rootdir population for btrfs with compression
When a btrfs partition is configured with both Compression= and
CopyFiles=, we need to ensure files are copied during filesystem
creation using mkfs.btrfs --rootdir, rather than copying files
afterwards via loop device mounting.

This is required because mkfs.btrfs can only apply compression settings
when files are provided via --rootdir during filesystem creation. If we
format the filesystem first and then mount it to copy files, the
compression setting is meaningless.

Modify the partition_needs_populate() condition to force the --rootdir
code path when the format is btrfs and compression is requested.

This ensures that partition_populate_directory() runs and creates a
temporary directory with the files, which is then passed to
make_filesystem() as the root parameter, allowing mkfs.btrfs to create
the filesystem with compression applied.

Fixes: https://github.com/systemd/systemd/issues/39584
2025-11-07 18:17:08 +08:00
Chris Down
adf88771ff mkfs-util: Ignore btrfs compression when there is no dir to copy
mkfs.btrfs requires that the --compress option be used together with
--rootdir, as compression only makes sense in that context (because
compression is not a persistent setting).

Right now, If --compress is specified without --rootdir, mkfs.btrfs
fails with:

  ERROR: --compression must be used with --rootdir

This can occur when repart is configured with Compression= but the
partition populate logic doesn't use the --rootdir code path (eg. when
using loop device mounting to copy files after mkfs).

Add a defensive check to skip compression and emit a user-friendly
warning when compression is requested but no root directory is
provided. The warning message references the repart directive names
(Compression= and CopyFiles=) rather than low-level mkfs options to
help users understand the requirement.

This prevents crashes but doesn't enable compression, that requires
ensuring the --rootdir code path is used, which it currently is not and
will be addressed in the next patch.

Fixes: https://github.com/systemd/systemd/issues/39584
2025-11-07 18:17:08 +08:00
3 changed files with 86 additions and 10 deletions

View File

@ -6785,7 +6785,8 @@ static int context_mkfs(Context *context) {
* have to populate using the filesystem's mkfs's --root= (or equivalent) option. To do that,
* we need to set up the final directory tree beforehand. */
if (partition_needs_populate(p) && (!t->loop || fstype_is_ro(p->format))) {
if (partition_needs_populate(p) &&
(!t->loop || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression))) {
if (!mkfs_supports_root_option(p->format))
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Loop device access is required to populate %s filesystems.",
@ -8533,7 +8534,7 @@ static int context_minimize(Context *context) {
return r;
}
if (!d || fstype_is_ro(p->format)) {
if (!d || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression)) {
if (!mkfs_supports_root_option(p->format))
return log_error_errno(SYNTHETIC_ERRNO(ENODEV),
"Loop device access is required to populate %s filesystems.",

View File

@ -466,17 +466,22 @@ int make_filesystem(
return log_oom();
if (compression) {
_cleanup_free_ char *c = NULL;
if (!root)
log_warning("Btrfs compression setting ignored because no files are being copied. "
"Compression= can only be applied when CopyFiles= is also specified.");
else {
_cleanup_free_ char *c = NULL;
c = strdup(compression);
if (!c)
return log_oom();
c = strdup(compression);
if (!c)
return log_oom();
if (compression_level && !strextend(&c, ":", compression_level))
return log_oom();
if (compression_level && !strextend(&c, ":", compression_level))
return log_oom();
if (strv_extend_many(&argv, "--compress", c) < 0)
return log_oom();
if (strv_extend_many(&argv, "--compress", c) < 0)
return log_oom();
}
}
/* mkfs.btrfs unconditionally warns about several settings changing from v5.15 onwards which

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"