repart: Place new partitions at beginning of free area rather than at end

When placing new partitions and there's space left because the new partitions
aren't occupying the whole free space, context_grow_partitions_on_free_area()
is supposed to distribute the free space between the new partitions. If still
no partition wants the free space, the free space ends up becoming padding.

Currently that padding is allocated to the partition preceding the FreeArea
(ie. a->after). This obviously means that the new partitions now end up at
the *end* of the free area rather than at the beginning, which is somewhat
unexpected given how partition placement usually is done.

Fix it by finding the last partition that belongs to the free area, and then
allocating the padding to that partition, so that the new partitions end up
getting aligned with the beginning of the free area, not the end.

Because the span might not be rounded to grain if there's a pre-allocated
a->after partition before the free area, we need to round it down ourselves
(otherwise the "left >= p->new_padding" assertion in context_place_partitions()
is going to fail).

Also ensure the fix works as expected by adding a test.
This commit is contained in:
Jonas Dreßler
2026-06-20 15:18:20 +02:00
committed by Lennart Poettering
parent cbe3a9ff11
commit 959c3286d0
2 changed files with 61 additions and 8 deletions
+15 -4
View File
@@ -1719,10 +1719,21 @@ static int context_grow_partitions_on_free_area(Context *context, FreeArea *a) {
break;
}
/* Yuck, still no one? Then make it padding */
if (span > 0 && a->after) {
assert(a->after->new_padding != UINT64_MAX);
a->after->new_padding += span;
/* Partitions didn't want all the space? Then make it padding */
if (span > 0) {
Partition *last_partition = NULL;
LIST_FOREACH(partitions, p, context->partitions)
if (p->allocated_to_area == a)
last_partition = p;
if (last_partition) {
assert(last_partition->new_padding != UINT64_MAX);
last_partition->new_padding += round_down_size(span, context->grain_size);
} else if (a->after) {
assert(a->after->new_padding != UINT64_MAX);
a->after->new_padding += round_down_size(span, context->grain_size);
}
}
return 0;
+46 -4
View File
@@ -500,8 +500,8 @@ EOF
"raw_size" : 33554432,
"size" : "-> 32M",
"old_padding" : 0,
"raw_padding" : 0,
"padding" : "-> 0B",
"raw_padding" : 70234112,
"padding" : "-> 66.9M",
"activity" : "create",
"drop-in_files" : [
"$defs/root.conf.d/override1.conf",
@@ -577,8 +577,8 @@ EOF
"raw_size" : 33554432,
"size" : "-> 32M",
"old_padding" : 0,
"raw_padding" : 0,
"padding" : "-> 0B",
"raw_padding" : 36679680,
"padding" : "-> 34.9M",
"activity" : "create"
}
]
@@ -2397,6 +2397,48 @@ EOF
grep -q tada "${btrfs_mntpoint_encrypted}/magic-encrypted"
}
testcase_insert_into_gap() {
local defs imgs output
defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '$defs' '$imgs'" RETURN
chmod 0755 "$defs"
echo "*** Inserting a new partition into a gap between two existing partitions ***"
truncate -s 71M "$imgs/gap.img"
sfdisk "$imgs/gap.img" <<EOF
label: gpt
size=10M, type=${esp_guid}, name="part-a",
start=60M, size=10M, type=${root_guid}, name="part-c",
EOF
tee "$defs/new.conf" <<EOF
[Partition]
Type=usr
Label=part-b
SizeMinBytes=10M
SizeMaxBytes=10M
EOF
systemd-repart --offline="$OFFLINE" \
--definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$imgs/gap.img"
output=$(sfdisk --dump "$imgs/gap.img")
assert_in "$imgs/gap.img1 : start= 2048, size= 20480," "$output"
assert_in "$imgs/gap.img2 : start= 122880, size= 20480," "$output"
# New partition B must start at the beginning of the gap (after A), not at
# the end (before C).
assert_in "$imgs/gap.img3 : start= 22528, size= 20480, type=$usr_guid, uuid=$usr_uuid, name=\"part-b\"" "$output"
}
OFFLINE="yes"
run_testcases