From: Daan De Meyer Date: Fri, 3 Apr 2026 07:13:25 +0000 (+0200) Subject: repart: Use --shrink with mkfs.btrfs to get a minimal filesystem X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4ded480ea535d98a3e0e329b4f7aa8153d362302;p=thirdparty%2Fsystemd.git repart: Use --shrink with mkfs.btrfs to get a minimal filesystem Avoids populating the filesystem twice similar to read-only filesystems. --- diff --git a/man/repart.d.xml b/man/repart.d.xml index d0992830b78..f3ed246b6ec 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -842,9 +842,9 @@ off when false, and best when true). Defaults to off. If set to best, the partition will have the minimal size required to store the sources configured with CopyFiles=. best - is currently only supported for read-only filesystems. If set to guess, the - partition is created at least as big as required to store the sources configured with - CopyFiles=. Note that unless the filesystem is a read-only filesystem, + is currently only supported for read-only filesystems and btrfs. If set to guess, + the partition is created at least as big as required to store the sources configured with + CopyFiles=. Note that unless the filesystem is a read-only filesystem or btrfs, systemd-repart will have to populate the filesystem twice to guess the minimal required size, so enabling this option might slow down repart when populating large partitions. diff --git a/src/repart/repart.c b/src/repart/repart.c index 118c6dfc07e..a21ec10da86 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -2983,9 +2983,13 @@ static int partition_read_definition( return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Minimize= can only be enabled if Format= or Verity=hash are set."); - if (p->minimize == MINIMIZE_BEST && (p->format && !fstype_is_ro(p->format)) && p->verity != VERITY_HASH) + if (p->minimize == MINIMIZE_BEST && + p->format && + !fstype_is_ro(p->format) && + !streq(p->format, "btrfs") && + p->verity != VERITY_HASH) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize=best can only be used with read-only filesystems or Verity=hash."); + "Minimize=best can only be used with read-only filesystems, btrfs, or Verity=hash."); if (partition_needs_populate(p) && !mkfs_supports_root_option(p->format) && geteuid() != 0) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EPERM), @@ -7014,6 +7018,9 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha if (r < 0) return r; } + + if (p->minimize != MINIMIZE_OFF && strv_extend(&sv, "--shrink") < 0) + return log_oom(); } *ret = TAKE_PTR(sv); @@ -9109,6 +9116,8 @@ static int context_minimize(Context *context) { if (!p->format) continue; + bool is_btrfs = streq(p->format, "btrfs"); + if (p->copy_blocks_fd >= 0) continue; @@ -9124,7 +9133,7 @@ static int context_minimize(Context *context) { (void) partition_hint(p, context->node, &hint); - log_info("Pre-populating %s filesystem of partition %s twice to calculate minimal partition size", + log_info("Pre-populating %s filesystem of partition %s to calculate minimal partition size", p->format, strna(hint)); if (!vt) { @@ -9146,7 +9155,9 @@ static int context_minimize(Context *context) { if (fd < 0) return log_error_errno(errno, "Failed to open temporary file %s: %m", temp); - if (fstype_is_ro(p->format)) + if (fstype_is_ro(p->format) || is_btrfs) + /* Read-only filesystems and btrfs (with mkfs.btrfs --shrink) produce a minimal + * filesystem in one pass, so we can use the real UUID directly. */ fs_uuid = p->fs_uuid; else { /* This may seem huge but it will be created sparse so it doesn't take up any space @@ -9168,7 +9179,7 @@ static int context_minimize(Context *context) { return r; } - if (!d || fstype_is_ro(p->format) || (streq_ptr(p->format, "btrfs") && p->compression)) { + if (!d || fstype_is_ro(p->format)) { if (!mkfs_supports_root_option(p->format)) return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "Loop device access is required to populate %s filesystems.", @@ -9198,8 +9209,9 @@ static int context_minimize(Context *context) { return r; /* Read-only filesystems are minimal from the first try because they create and size the - * loopback file for us. */ - if (fstype_is_ro(p->format)) { + * loopback file for us. Similarly, mkfs.btrfs --shrink populates the filesystem from the + * root directory and then shrinks the backing file to the minimal size. */ + if (fstype_is_ro(p->format) || is_btrfs) { fd = safe_close(fd); fd = open(temp, O_RDONLY|O_CLOEXEC|O_NONBLOCK); @@ -9234,10 +9246,8 @@ static int context_minimize(Context *context) { /* Other filesystems need to be provided with a pre-sized loopback file and will adapt to * fully occupy it. Because we gave the filesystem a 1T sparse file, we need to shrink the - * filesystem down to a reasonable size again to fit it in the disk image. While there are - * some filesystems that support shrinking, it doesn't always work properly (e.g. shrinking - * btrfs gives us a 2.0G filesystem regardless of what we put in it). Instead, let's populate - * the filesystem again, but this time, instead of providing the filesystem with a 1T sparse + * filesystem down to a reasonable size again to fit it in the disk image. Let's populate the + * filesystem again, but this time, instead of providing the filesystem with a 1T sparse * loopback file, let's size the loopback file based on the actual data used by the * filesystem in the sparse file after the first attempt. This should be a good guess of the * minimal amount of space needed in the filesystem to fit all the required data. diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index 546df29f44a..fb3dbcfad5d 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -1254,6 +1254,18 @@ Minimize=guess EOF done + if command -v mkfs.btrfs >/dev/null; then + for minimize in guess best; do + tee "$defs/root-btrfs-${minimize}.conf" </dev/null; then tee "$defs/root-squashfs.conf" <