]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: Allow instances to be partial or pending
authorPhilip Withnall <pwithnall@gnome.org>
Tue, 30 Dec 2025 23:49:47 +0000 (23:49 +0000)
committerPhilip Withnall <pwithnall@gnome.org>
Mon, 9 Feb 2026 11:54:40 +0000 (11:54 +0000)
If we allow target instances to be partial or pending, we can build on
top of this to allow updates to be split into two phases: ‘acquire’ (which
takes an available source instance and copies it (temporarily partial) to
a pending target instance; and ‘install’ (which takes a pending target
instance and installs it as an installed target instance).

This commit introduces a file/directory and partition prefix naming
scheme to identify partial and pending instances.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>
Helps: https://github.com/systemd/systemd/issues/34814

src/sysupdate/sysupdate-instance.h
src/sysupdate/sysupdate-resource.c

index 42ba1f1ba92763e6f9a6c703afbafc04851b9700..05b42ac610585372f03be6c3279dd0742d2391b0 100644 (file)
@@ -45,8 +45,11 @@ struct Instance {
         InstanceMetadata metadata;
 
         /* Where we found the instance */
-        char *path;
+        char *path;  /* includes the `.sysupdate.partial.` (etc.) prefix, if applicable */
         PartitionInfo partition_info;
+
+        bool is_partial;
+        bool is_pending;
 };
 
 void instance_metadata_destroy(InstanceMetadata *m);
index b26884189959bf4f690efd54ef82154af571ad43..821854cda8ea8720d922172f55fe3a4c1fdcff6e 100644 (file)
@@ -80,15 +80,22 @@ static int resource_load_from_directory_recursive(
                 Resource *rr,
                 DIR* d,
                 const char* relpath,
-                mode_t m) {
+                const char* relpath_for_matching,
+                mode_t m,
+                bool ancestor_is_partial,
+                bool ancestor_is_pending) {
         int r;
 
         for (;;) {
                 _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
                 _cleanup_free_ char *joined = NULL, *rel_joined = NULL;
+                _cleanup_free_ char *rel_joined_for_matching = NULL;
                 Instance *instance;
                 struct dirent *de;
+                const char *de_d_name_stripped;
                 struct stat st;
+                bool is_partial = ancestor_is_partial, is_pending = ancestor_is_pending;
+                const char *stripped;
 
                 errno = 0;
                 de = readdir_no_dot(d);
@@ -128,11 +135,26 @@ static int resource_load_from_directory_recursive(
                 if (!(S_ISDIR(st.st_mode) && S_ISREG(m)) && ((st.st_mode & S_IFMT) != m))
                         continue;
 
+                if ((stripped = startswith(de->d_name, ".sysupdate.partial."))) {
+                        de_d_name_stripped = stripped;
+                        is_partial = true;
+                } else if ((stripped = startswith(de->d_name, ".sysupdate.pending."))) {
+                        de_d_name_stripped = stripped;
+                        is_pending = true;
+                } else
+                        de_d_name_stripped = de->d_name;
+
                 rel_joined = path_join(relpath, de->d_name);
                 if (!rel_joined)
                         return log_oom();
 
-                r = pattern_match_many(rr->patterns, rel_joined, &extracted_fields);
+                /* Match against the filename with any `.sysupdate.partial.` (etc.) prefix stripped, so the
+                 * user’s patterns still apply. But don’t use the stripped version in any paths or recursion. */
+                rel_joined_for_matching = path_join(relpath_for_matching, de_d_name_stripped);
+                if (!rel_joined_for_matching)
+                        return log_oom();
+
+                r = pattern_match_many(rr->patterns, rel_joined_for_matching, &extracted_fields);
                 if (r == PATTERN_MATCH_RETRY) {
                         _cleanup_closedir_ DIR *subdir = NULL;
 
@@ -140,7 +162,7 @@ static int resource_load_from_directory_recursive(
                         if (!subdir)
                                 continue;
 
-                        r = resource_load_from_directory_recursive(rr, subdir, rel_joined, m);
+                        r = resource_load_from_directory_recursive(rr, subdir, rel_joined, rel_joined_for_matching, m, is_partial, is_pending);
                         if (r < 0)
                                 return r;
                         if (r == 0)
@@ -168,6 +190,9 @@ static int resource_load_from_directory_recursive(
 
                 if (instance->metadata.mode == MODE_INVALID)
                         instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */
+
+                instance->is_partial = is_partial;
+                instance->is_pending = is_pending;
         }
 
         return 0;
@@ -192,7 +217,7 @@ static int resource_load_from_directory(
                 return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path);
         }
 
-        return resource_load_from_directory_recursive(rr, d, NULL, m);
+        return resource_load_from_directory_recursive(rr, d, NULL, NULL, m, false, false);
 }
 
 static int resource_load_from_blockdev(Resource *rr) {
@@ -219,6 +244,9 @@ 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)
@@ -236,7 +264,18 @@ static int resource_load_from_blockdev(Resource *rr) {
                         continue;
                 }
 
-                r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
+                /* 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);
                 if (r < 0)
                         return log_error_errno(r, "Failed to match pattern: %m");
                 if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
@@ -262,6 +301,9 @@ static int resource_load_from_blockdev(Resource *rr) {
 
                 if (instance->metadata.read_only < 0)
                         instance->metadata.read_only = instance->partition_info.read_only;
+
+                instance->is_partial = is_partial;
+                instance->is_pending = is_pending;
         }
 
         return 0;
@@ -539,6 +581,11 @@ static int resource_load_from_web(
                                         memcpy(instance->metadata.sha256sum, h.iov_base, h.iov_len);
                                         instance->metadata.sha256sum_set = true;
                                 }
+
+                                /* Web resources can only be a source, not a target, so
+                                 * can never be partial or pending. */
+                                instance->is_partial = false;
+                                instance->is_pending = false;
                         }
                 }