From: Philip Withnall Date: Tue, 30 Dec 2025 23:49:47 +0000 (+0000) Subject: sysupdate: Allow instances to be partial or pending X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fdc661116d47797804b8042db91effc0c1e8fdd0;p=thirdparty%2Fsystemd.git sysupdate: Allow instances to be partial or pending 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 Helps: https://github.com/systemd/systemd/issues/34814 --- diff --git a/src/sysupdate/sysupdate-instance.h b/src/sysupdate/sysupdate-instance.h index 42ba1f1ba92..05b42ac6105 100644 --- a/src/sysupdate/sysupdate-instance.h +++ b/src/sysupdate/sysupdate-instance.h @@ -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); diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index b2688418995..821854cda8e 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -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; } }