]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: Use partition types for pending/partial partitions
authorDaan De Meyer <daan@amutable.com>
Tue, 17 Feb 2026 19:57:01 +0000 (20:57 +0100)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 18 Feb 2026 02:15:04 +0000 (11:15 +0900)
Fixes #40658

src/sysupdate/sysupdate-partition.c
src/sysupdate/sysupdate-partition.h
src/sysupdate/sysupdate-resource.c
src/sysupdate/sysupdate-transfer.c
src/sysupdate/sysupdate-transfer.h

index 237c157831f0c85e7986734bd1ae058bd4c48daf..94a69850af57cc114f25df1d0899ebe3489ab25f 100644 (file)
@@ -9,6 +9,20 @@
 #include "string-util.h"
 #include "sysupdate-partition.h"
 
+/* App-specific IDs used as HMAC keys to derive "partial" and "pending" partition type UUIDs from the
+ * original partition type UUID. This way we can indicate sysupdate transfer state via a separate GPT
+ * partition type UUID instead of using a label prefix, saving precious label space. */
+#define GPT_SYSUPDATE_PARTIAL_APP_ID SD_ID128_MAKE(ac,cf,a0,c2,da,24,46,0a,9f,c9,0b,b8,fc,78,52,19)
+#define GPT_SYSUPDATE_PENDING_APP_ID SD_ID128_MAKE(80,f3,d6,1e,23,83,43,b9,81,f5,ce,37,93,f4,7d,4c)
+
+int gpt_partition_type_uuid_for_sysupdate_partial(sd_id128_t type, sd_id128_t *ret) {
+        return sd_id128_get_app_specific(type, GPT_SYSUPDATE_PARTIAL_APP_ID, ret);
+}
+
+int gpt_partition_type_uuid_for_sysupdate_pending(sd_id128_t type, sd_id128_t *ret) {
+        return sd_id128_get_app_specific(type, GPT_SYSUPDATE_PENDING_APP_ID, ret);
+}
+
 void partition_info_destroy(PartitionInfo *p) {
         assert(p);
 
@@ -242,6 +256,22 @@ int patch_partition(
                         return log_error_errno(r, "Failed to update partition UUID: %m");
         }
 
+        if (change & PARTITION_TYPE) {
+                _cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *pt = NULL;
+
+                pt = fdisk_new_parttype();
+                if (!pt)
+                        return log_oom();
+
+                r = fdisk_parttype_set_typestr(pt, SD_ID128_TO_UUID_STRING(info->type));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to initialize partition type: %m");
+
+                r = fdisk_partition_set_type(pa, pt);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to update partition type: %m");
+        }
+
         type = gpt_partition_type_from_uuid(info->type);
 
         /* Tweak the read-only flag, but only if supported by the partition type */
index fbda14de6f59d27f7aea3b142c0d4aef0cad8757..5ae8c571ddf57514612b1e21512d5bb7dec67b0f 100644 (file)
@@ -12,7 +12,8 @@ typedef enum PartitionChange {
         PARTITION_GROWFS          = 1 << 3,
         PARTITION_UUID            = 1 << 4,
         PARTITION_LABEL           = 1 << 5,
-        _PARTITION_CHANGE_MAX     = (1 << 6) - 1, /* all of the above */
+        PARTITION_TYPE            = 1 << 6,
+        _PARTITION_CHANGE_MAX     = (1 << 7) - 1, /* all of the above */
         _PARTITION_CHANGE_INVALID = -EINVAL,
 } PartitionChange;
 
@@ -40,5 +41,8 @@ int partition_info_copy(PartitionInfo *dest, const PartitionInfo *src);
 
 int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret);
 
+int gpt_partition_type_uuid_for_sysupdate_partial(sd_id128_t type, sd_id128_t *ret);
+int gpt_partition_type_uuid_for_sysupdate_pending(sd_id128_t type, sd_id128_t *ret);
+
 int find_suitable_partition(const char *device, uint64_t space, sd_id128_t *partition_type, PartitionInfo *ret);
 int patch_partition(const char *device, const PartitionInfo *info, PartitionChange change);
index 821854cda8ea8720d922172f55fe3a4c1fdcff6e..3be0943e4c0a3dd59fd1384b90330b22605fe2dc 100644 (file)
@@ -31,6 +31,7 @@
 #include "strv.h"
 #include "sysupdate-cache.h"
 #include "sysupdate-instance.h"
+#include "sysupdate-partition.h"
 #include "sysupdate-pattern.h"
 #include "sysupdate-resource.h"
 #include "time-util.h"
@@ -244,9 +245,7 @@ static int resource_load_from_blockdev(Resource *rr) {
                 _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
                 _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
                 Instance *instance;
-                const char *pinfo_label_stripped;
                 bool is_partial = false, is_pending = false;
-                const char *stripped;
 
                 r = read_partition_info(c, t, i, &pinfo);
                 if (r < 0)
@@ -254,9 +253,28 @@ static int resource_load_from_blockdev(Resource *rr) {
                 if (r == 0) /* not assigned */
                         continue;
 
-                /* Check if partition type matches */
-                if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type.uuid))
-                        continue;
+                /* Check if partition type matches, either directly or via derived partial/pending type
+                 * UUIDs. The derived UUIDs are computed from the configured partition type by hashing it
+                 * with a fixed app-specific ID, so we can detect the state without relying on label
+                 * prefixes. */
+                if (rr->partition_type_set) {
+                        sd_id128_t partial_type, pending_type;
+
+                        r = gpt_partition_type_uuid_for_sysupdate_partial(rr->partition_type.uuid, &partial_type);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
+
+                        r = gpt_partition_type_uuid_for_sysupdate_pending(rr->partition_type.uuid, &pending_type);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
+
+                        if (sd_id128_equal(pinfo.type, partial_type))
+                                is_partial = true;
+                        else if (sd_id128_equal(pinfo.type, pending_type))
+                                is_pending = true;
+                        else if (!sd_id128_equal(pinfo.type, rr->partition_type.uuid))
+                                continue;
+                }
 
                 /* A label of "_empty" means "not used so far" for us */
                 if (streq_ptr(pinfo.label, "_empty")) {
@@ -264,18 +282,7 @@ static int resource_load_from_blockdev(Resource *rr) {
                         continue;
                 }
 
-                /* Match the label with any partial/pending prefix removed so the user’s existing patterns
-                 * match regardless of the instance’s state. */
-                if ((stripped = startswith(pinfo.label, "PRT#"))) {
-                        pinfo_label_stripped = stripped;
-                        is_partial = true;
-                } else if ((stripped = startswith(pinfo.label, "PND#"))) {
-                        pinfo_label_stripped = stripped;
-                        is_pending = true;
-                } else
-                        pinfo_label_stripped = pinfo.label;
-
-                r = pattern_match_many(rr->patterns, pinfo_label_stripped, &extracted_fields);
+                r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
                 if (r < 0)
                         return log_error_errno(r, "Failed to match pattern: %m");
                 if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
index 868917b00993cd0fcfdce13e2405fdada9b4561e..46f3129f5d9937046570b0677570fbad0fcc0e36 100644 (file)
@@ -67,8 +67,6 @@ Transfer* transfer_free(Transfer *t) {
         strv_free(t->appstream);
 
         partition_info_destroy(&t->partition_info);
-        free(t->temporary_partial_partition_label);
-        free(t->temporary_pending_partition_label);
         free(t->final_partition_label);
 
         resource_destroy(&t->source);
@@ -766,12 +764,21 @@ static int transfer_instance_vacuum(
 
         case RESOURCE_PARTITION: {
                 PartitionInfo pinfo = instance->partition_info;
+                PartitionChange change = PARTITION_LABEL;
 
                 /* label "_empty" means "no contents" for our purposes */
                 pinfo.label = (char*) "_empty";
 
-                log_debug("Relabelling partition '%s' to '%s'.", pinfo.device, pinfo.label);
-                r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
+                /* If the partition had a derived partial/pending type UUID, restore the original
+                 * partition type so that the slot is properly recognized as empty in subsequent
+                 * scans. */
+                if ((instance->is_partial || instance->is_pending) && t->target.partition_type_set) {
+                        pinfo.type = t->target.partition_type.uuid;
+                        change |= PARTITION_TYPE;
+                }
+
+                log_debug("Resetting partition '%s' to empty.", pinfo.device);
+                r = patch_partition(t->target.path, &pinfo, change);
                 if (r < 0)
                         return r;
 
@@ -1172,7 +1179,7 @@ static int run_callout(
  * and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
  * skipped). */
 int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
-        _cleanup_free_ char *formatted_pattern = NULL, *formatted_partial_pattern = NULL, *formatted_pending_pattern = NULL;
+        _cleanup_free_ char *formatted_pattern = NULL;
         int r;
 
         assert(t);
@@ -1182,8 +1189,6 @@ int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata
         assert(!t->temporary_partial_path);
         assert(!t->temporary_pending_path);
         assert(!t->final_partition_label);
-        assert(!t->temporary_partial_partition_label);
-        assert(!t->temporary_pending_partition_label);
         assert(!strv_isempty(t->target.patterns));
 
         /* Format the target name using the first pattern specified */
@@ -1234,25 +1239,18 @@ int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata
                 if (!r)
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
 
-                if (!strprepend(&formatted_partial_pattern, "PRT#", formatted_pattern))
-                        return log_oom();
-                r = gpt_partition_label_valid(formatted_partial_pattern);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_partial_pattern);
-                if (!r)
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_partial_pattern);
-
-                free_and_replace(t->temporary_partial_partition_label, formatted_partial_pattern);
+                if (!t->target.partition_type_set)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition type must be set for partition targets.");
 
-                if (!strprepend(&formatted_pending_pattern, "PND#", formatted_pattern))
-                        return log_oom();
-                r = gpt_partition_label_valid(formatted_pending_pattern);
+                /* Derive temporary partition type UUIDs for partial/pending states from the configured
+                 * partition type. This avoids the need for label prefixes. */
+                r = gpt_partition_type_uuid_for_sysupdate_partial(t->target.partition_type.uuid, &t->partition_type_partial);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pending_pattern);
-                if (!r)
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pending_pattern);
+                        return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
 
-                free_and_replace(t->temporary_pending_partition_label, formatted_pending_pattern);
+                r = gpt_partition_type_uuid_for_sysupdate_pending(t->target.partition_type.uuid, &t->partition_type_pending);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
 
                 t->final_partition_label = TAKE_PTR(formatted_pattern);
         }
@@ -1312,13 +1310,18 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
 
                 where = t->partition_info.device;
 
-                /* Rename the partition to `PRT#<VERSION>` to indicate that a transfer to it is in progress. */
-                r = free_and_strdup_warn(&t->partition_info.label, t->temporary_partial_partition_label);
+                /* Set the partition label and change the partition type to the derived "partial" type UUID
+                 * to indicate that a transfer to it is in progress. */
+                r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
                 if (r < 0)
                         return r;
-                t->partition_change = PARTITION_LABEL;
+                t->partition_info.type = t->partition_type_partial;
+                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
 
-                log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
+                log_debug("Marking partition '%s' as partial (label='%s', type=%s).",
+                          t->partition_info.device,
+                          t->partition_info.label,
+                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
                 r = patch_partition(
                                 t->target.path,
                                 &t->partition_info,
@@ -1545,12 +1548,10 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
         }
 
         if (t->target.type == RESOURCE_PARTITION) {
-                /* Now rename the partition again to `PND#<VERSION>` to indicate that the acquire is complete
-                 * and the partition is ready for install. */
-                r = free_and_strdup_warn(&t->partition_info.label, t->temporary_pending_partition_label);
-                if (r < 0)
-                        return r;
-                t->partition_change = PARTITION_LABEL;
+                /* Now change the partition type to the derived "pending" type UUID to indicate that the
+                 * acquire is complete and the partition is ready for install. */
+                t->partition_info.type = t->partition_type_pending;
+                t->partition_change = PARTITION_TYPE;
 
                 if (f->partition_uuid_set) {
                         t->partition_info.uuid = f->partition_uuid;
@@ -1577,7 +1578,9 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
                         t->partition_change |= PARTITION_GROWFS;
                 }
 
-                log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
+                log_debug("Marking partition '%s' as pending (type=%s).",
+                          t->partition_info.device,
+                          SD_ID128_TO_UUID_STRING(t->partition_info.type));
                 r = patch_partition(
                                 t->target.path,
                                 &t->partition_info,
@@ -1617,7 +1620,7 @@ int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
 
         /* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
          * already happened in the acquire phase, the target should already have that information and it
-         * should already have been claimed as `PND#`. */
+         * should already have been claimed with the pending partition type UUID. */
         if (t->target.type == RESOURCE_PARTITION) {
                 assert(i->resource == &t->target);
                 assert(i->is_pending);
@@ -1665,14 +1668,17 @@ int transfer_install_instance(
                 t->temporary_pending_path = mfree(t->temporary_pending_path);
         }
 
-        if (t->temporary_pending_partition_label) {
+        if (t->final_partition_label) {
                 assert(t->target.type == RESOURCE_PARTITION);
-                assert(t->final_partition_label);
+                assert(t->target.partition_type_set);
 
                 r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
                 if (r < 0)
                         return r;
-                t->partition_change = PARTITION_LABEL;
+
+                /* Restore the original partition type UUID now that the partition is fully installed. */
+                t->partition_info.type = t->target.partition_type.uuid;
+                t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
 
                 r = patch_partition(
                                 t->target.path,
index d6dee0234aad8745bf4317fc460096644a747d0d..64af8c2fc31877658965d55008202509049bce63 100644 (file)
@@ -48,8 +48,11 @@ typedef struct Transfer {
         PartitionInfo partition_info;
         PartitionChange partition_change;
         char *final_partition_label;
-        char *temporary_partial_partition_label;
-        char *temporary_pending_partition_label;
+
+        /* Derived partition type UUIDs used to indicate partial/pending state on the partition type level,
+         * instead of polluting the partition label with prefixes */
+        sd_id128_t partition_type_partial;
+        sd_id128_t partition_type_pending;
 
         Context *context;
 } Transfer;