repart: Keep new partitions after existing same-type partitions

When a partition is deleted from a table containing multiple partitions
of the same type, filling the resulting partition-number gap can place
a new partition before an existing partition matched to an earlier
definition. This changes definition matching on the next invocation and
breaks idempotency.

Keep new partitions after existing partitions of the same type. If no
partition table slot is available after them, fall back to the first
unused slot.

Which existing partition is matched to which definition is still
determined by partition type and partition number order, as documented,
and is unchanged by this.

Fixes #43449
This commit is contained in:
quanyeyang
2026-08-26 19:43:03 +01:00
committed by Luca Boccassi
parent 9bb06d5cd6
commit a9ff124143
3 changed files with 112 additions and 9 deletions
+8 -6
View File
@@ -97,12 +97,14 @@
<listitem><para>Partitions that shall be created are now allocated on the disk, taking the size
constraints and weights declared in the configuration files into account. Free space is used within the
limits set by size and padding requests. In addition, existing partitions that should be grown are
grown. New partitions are always appended to the end of the partition table, taking the first partition
table slot whose index is greater than the indexes of all existing partitions. Partitions are never
reordered and thus partition numbers remain stable. When partitions are created, they are placed in the
smallest area of free space that is large enough to satisfy the size and padding limits. This means
that partitions might have different order on disk than in the partition table. Note that this
allocation happens in memory only, the partition table on disk is not updated yet.</para></listitem>
grown. When possible, newly created partitions are assigned partition table slots after existing
partitions of the same type, as those are matched to configuration files in partition number order
and a new partition inserted before an existing one would change that matching on the next
invocation. If no such slot is available, the first unused slot is used. Partitions are never
reordered and thus partition numbers remain stable. When partitions are created, they are placed
in the smallest area of free space that is large enough to satisfy the size and padding limits.
This means that partitions might have different order on disk than in the partition table. Note
that this allocation happens in memory only, the partition table on disk is not updated yet.</para></listitem>
<listitem><para>All existing partitions for which configuration files exist and which currently have no
GPT partition label set will be assigned a label, either explicitly configured in the configuration or
+29 -3
View File
@@ -1766,16 +1766,42 @@ static int context_grow_partitions(Context *context) {
return 0;
}
static uint64_t find_first_unused_partno(Context *context) {
static uint64_t find_unused_partno(Context *context, const Partition *partition) {
uint64_t partno = 0;
size_t nents;
assert(context);
assert(partition);
/* Keep new partitions after existing partitions of the same type, as those are matched
* to definitions in partition number order. */
LIST_FOREACH(partitions, p, context->partitions)
if (PARTITION_EXISTS(p) && p->partno != UINT64_MAX &&
sd_id128_equal(p->type.uuid, partition->type.uuid))
partno = MAX(partno, p->partno + 1);
nents = sym_fdisk_get_npartitions(context->fdisk_context);
/* Prefer a free slot after all existing partitions of the same type. */
for (; partno < nents; partno++) {
bool found = false;
LIST_FOREACH(partitions, p, context->partitions)
if (p->partno != UINT64_MAX && p->partno == partno) {
found = true;
break;
}
if (!found)
return partno;
}
/* Fall back to the first unused slot if there is no such slot. */
for (partno = 0;; partno++) {
bool found = false;
LIST_FOREACH(partitions, p, context->partitions)
if (p->partno != UINT64_MAX && p->partno == partno)
if (p->partno != UINT64_MAX && p->partno == partno) {
found = true;
break;
}
if (!found)
break;
}
@@ -1811,7 +1837,7 @@ static void context_place_partitions(Context *context) {
continue;
p->offset = start;
p->partno = find_first_unused_partno(context);
p->partno = find_unused_partno(context, p);
assert(left >= p->new_size);
start += p->new_size;
+75
View File
@@ -3161,6 +3161,81 @@ EOF
assert_in "$imgs/leftover2.img3 : start= 26624, size= 24536, type=$esp_guid," "$output"
}
testcase_partition_number_gap() {
local defs imgs image output before after
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"
image="$imgs/gap.img"
tee "$defs/10-a.conf" <<EOF
[Partition]
Type=linux-generic
Label=a
SizeMinBytes=20M
SizeMaxBytes=20M
EOF
tee "$defs/20-b.conf" <<EOF
[Partition]
Type=linux-generic
Label=b
SizeMinBytes=10M
SizeMaxBytes=10M
EOF
# Initially create two partitions with consecutive partition numbers.
systemd-repart --offline="$OFFLINE" \
--definitions="$defs" \
--seed="$seed" \
--empty=create \
--size=64M \
--dry-run=no \
"$image"
output="$(sfdisk -d "$image")"
assert_in "${image}1 :" "$output"
assert_in "${image}2 :" "$output"
# Remove the first partition, leaving partition number 1 unused while
# partition number 2 remains occupied.
sfdisk --delete "$image" 1
output="$(sfdisk -d "$image")"
assert_not_in "${image}1 :" "$output"
assert_in "${image}2 :" "$output"
# Repart should append the new partition after the highest existing
# partition of the same type instead of filling the lower-numbered gap.
systemd-repart --offline="$OFFLINE" \
--definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$image"
output="$(sfdisk -d "$image")"
assert_not_in "${image}1 :" "$output"
assert_in "${image}2 : start= 43008, size= 40960, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4," "$output"
assert_in "${image}3 : start= 2048, size= 20480, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4," "$output"
# A subsequent invocation should not rematch the definitions and modify
# the partition table again.
before="$output"
systemd-repart --offline="$OFFLINE" \
--definitions="$defs" \
--seed="$seed" \
--dry-run=no \
"$image"
after="$(sfdisk -d "$image")"
assert_eq "$after" "$before"
}
OFFLINE="yes"
run_testcases