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:
committed by
Luca Boccassi
parent
9bb06d5cd6
commit
a9ff124143
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user