diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index 9370766b9a..096eb0c53d 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -97,12 +97,14 @@
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.
+ 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.
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
diff --git a/src/repart/repart.c b/src/repart/repart.c
index 821c8393e7..7d8433921b 100644
--- a/src/repart/repart.c
+++ b/src/repart/repart.c
@@ -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;
diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh
index 3df437ea4f..d2b0e95970 100755
--- a/test/units/TEST-58-REPART.sh
+++ b/test/units/TEST-58-REPART.sh
@@ -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" <