]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: Split the update verb into two parts internally
authorPhilip Withnall <pwithnall@gnome.org>
Wed, 31 Dec 2025 00:46:25 +0000 (00:46 +0000)
committerPhilip Withnall <pwithnall@gnome.org>
Mon, 9 Feb 2026 12:05:05 +0000 (12:05 +0000)
An ‘acquire’ (download) part, and an ‘install’ (apply) part.

Following commits will expose these as separate verbs and D-Bus methods,
but this commit is the one which rearranges the internals.

If doing an ‘install’, a mirror version of the ‘acquire’ has to happen
first to make sure the transfer’s internal state is correct.

‘Acquire’ can require an internet connection, but ‘install’ will always
work with `--offline` specified.

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

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

index cd85db5236e9faf29eafc447aaf87279e793a2fb..237c157831f0c85e7986734bd1ae058bd4c48daf 100644 (file)
@@ -16,6 +16,33 @@ void partition_info_destroy(PartitionInfo *p) {
         p->device = mfree(p->device);
 }
 
+int partition_info_copy(PartitionInfo *dest, const PartitionInfo *src) {
+        int r;
+
+        assert(dest);
+        assert(src);
+
+        r = free_and_strdup_warn(&dest->label, src->label);
+        if (r < 0)
+                return r;
+
+        r = free_and_strdup_warn(&dest->device, src->device);
+        if (r < 0)
+                return r;
+
+        dest->partno = src->partno;
+        dest->start = src->start;
+        dest->size = src->size;
+        dest->flags = src->flags;
+        dest->type = src->type;
+        dest->uuid = src->uuid;
+        dest->no_auto = src->no_auto;
+        dest->read_only = src->read_only;
+        dest->growfs = src->growfs;
+
+        return 0;
+}
+
 int read_partition_info(
                 struct fdisk_context *c,
                 struct fdisk_table *t,
index 21277962e4147a9650113dfa9b78d28ec8aab112..fbda14de6f59d27f7aea3b142c0d4aef0cad8757 100644 (file)
@@ -36,6 +36,7 @@ typedef struct PartitionInfo {
         }
 
 void partition_info_destroy(PartitionInfo *p);
+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);
 
index 5af2f4cf301fac52280b9099e78853688a6d591e..a13f659e360448446f7b96795a5a8737aa61c0d1 100644 (file)
@@ -1599,6 +1599,42 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
         return 0;
 }
 
+int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
+        InstanceMetadata f;
+        Instance *existing;
+        int r;
+
+        assert(t);
+        assert(i);
+
+        log_debug("transfer_process_partial_and_pending_instance %s", i->path);
+
+        /* Does this instance already exist in the target but isn’t pending? */
+        existing = resource_find_instance(&t->target, i->metadata.version);
+        if (existing && !existing->is_pending)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to acquire '%s', instance is already in the target but is not pending.", i->path);
+
+        /* All we need to do is compute the temporary paths. We don’t need to do any of the other work in
+         * transfer_acquire_instance(). */
+        r = transfer_compute_temporary_paths(t, i, &f);
+        if (r < 0)
+                return r;
+
+        /* 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#`. */
+        if (t->target.type == RESOURCE_PARTITION) {
+                assert(i->resource == &t->target);
+                assert(i->is_pending);
+
+                r = partition_info_copy(&t->partition_info, &i->partition_info);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 int transfer_install_instance(
                 Transfer *t,
                 Instance *i,
index 92c19227953abae580632a7aff054d53da867065..f5976f0477f32bcffa2e5666eb05ba33c651f9c7 100644 (file)
@@ -67,5 +67,6 @@ int transfer_resolve_paths(Transfer *t, const char *root, const char *node);
 int transfer_vacuum(Transfer *t, uint64_t space, const char *extra_protected_version);
 
 int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, void *userdata);
+int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i);
 
 int transfer_install_instance(Transfer *t, Instance *i, const char *root);
index a40e94da6c878016b5e5eaadcd016d87a5986e2a..700962819fc112edbbc48660fad1ac072648f0ce 100644 (file)
@@ -397,7 +397,7 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
                                 assert(flags == UPDATE_INSTALLED);
 
                                 match = resource_find_instance(&t->target, cursor);
-                                if (!match) {
+                                if (!match && !(extra_flags & (UPDATE_PARTIAL|UPDATE_PENDING))) {
                                         /* When we're looking for installed versions, let's be robust and treat
                                          * an incomplete installation as an installation. Otherwise, there are
                                          * situations that can lead to sysupdate wiping the currently booted OS.
@@ -413,6 +413,14 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
 
                         if (strv_contains(t->protected_versions, cursor))
                                 extra_flags |= UPDATE_PROTECTED;
+
+                        /* Partial or pending updates by definition are not incomplete, they’re
+                         * partial/pending instead */
+                        if (match && match->is_partial)
+                                extra_flags = (extra_flags | UPDATE_PARTIAL) & ~UPDATE_INCOMPLETE;
+
+                        if (match && match->is_pending)
+                                extra_flags = (extra_flags | UPDATE_PENDING) & ~UPDATE_INCOMPLETE;
                 }
 
                 r = free_and_strdup_warn(&boundary, cursor);
@@ -431,7 +439,9 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
 
                         /* Merge in what we've learned and continue onto the next version */
 
-                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE)) {
+                        if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE) ||
+                            FLAGS_SET(u->flags, UPDATE_PARTIAL) ||
+                            FLAGS_SET(u->flags, UPDATE_PENDING)) {
                                 assert(u->n_instances == c->n_transfers);
 
                                 /* Incomplete updates will have picked NULL instances for the transfers that
@@ -450,7 +460,7 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
 
                         /* If this is the newest installed version, that is incomplete and just became marked
                          * as available, and if there is no other candidate available, we promote this to be
-                         * the candidate. */
+                         * the candidate. Ignore partial or pending status on the update set. */
                         if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
                             !c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
                                 c->candidate = u;
@@ -486,7 +496,8 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
                 if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED)) == (UPDATE_NEWEST|UPDATE_INSTALLED))
                         c->newest_installed = us;
 
-                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate" */
+                /* Remember which is the newest non-obsolete, available (and not installed) version, which we declare the "candidate".
+                 * It may be partial or pending. */
                 if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
                         c->candidate = us;
         }
@@ -496,6 +507,11 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
             c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
                 c->candidate = NULL;
 
+        /* Newest installed is still pending and no candidate is set? Then it becomes the candidate. */
+        if (c->newest_installed && FLAGS_SET(c->newest_installed->flags, UPDATE_PENDING) &&
+            !c->candidate)
+                c->candidate = c->newest_installed;
+
         return 0;
 }
 
@@ -986,6 +1002,8 @@ static int context_on_acquire_progress(const Transfer *t, const Instance *inst,
                                               overall, n - i, i, inst->metadata.version, overall);
 }
 
+static int context_process_partial_and_pending(Context *c, const char *version);
+
 static int context_acquire(
                 Context *c,
                 const char *version) {
@@ -1011,7 +1029,15 @@ static int context_acquire(
 
         if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
                 log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
-        else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
+        else if (FLAGS_SET(us->flags, UPDATE_PARTIAL)) {
+                log_info("Selected update '%s' is already acquired and partially installed. Vacuum it to try installing again.", us->version);
+
+                return 0;
+        } else if (FLAGS_SET(us->flags, UPDATE_PENDING)) {
+                log_info("Selected update '%s' is already acquired and pending installation.", us->version);
+
+                return context_process_partial_and_pending(c, version);
+        } else if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
                 log_info("Selected update '%s' is already installed. Skipping update.", us->version);
 
                 return 0;
@@ -1075,6 +1101,70 @@ static int context_acquire(
         return 1;
 }
 
+/* Check to see if we have an update set acquired and pending installation. */
+static int context_process_partial_and_pending(
+                Context *c,
+                const char *version) {
+
+        UpdateSet *us = NULL;
+        int r;
+
+        assert(c);
+
+        if (version) {
+                us = context_update_set_by_version(c, version);
+                if (!us)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
+        } else {
+                if (!c->candidate) {
+                        log_info("No update needed.");
+
+                        return 0;
+                }
+
+                us = c->candidate;
+        }
+
+        if (FLAGS_SET(us->flags, UPDATE_INCOMPLETE))
+                log_info("Selected update '%s' is already installed, but incomplete. Repairing.", us->version);
+        else if ((us->flags & (UPDATE_PARTIAL|UPDATE_PENDING|UPDATE_INSTALLED)) == UPDATE_INSTALLED) {
+                log_info("Selected update '%s' is already installed. Skipping update.", us->version);
+
+                return 0;
+        }
+
+        if (FLAGS_SET(us->flags, UPDATE_PARTIAL))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is only partially downloaded, refusing.", us->version);
+        if (!FLAGS_SET(us->flags, UPDATE_PENDING))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not pending installation, refusing.", us->version);
+
+        if (FLAGS_SET(us->flags, UPDATE_OBSOLETE))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is obsolete, refusing.", us->version);
+
+        if (!FLAGS_SET(us->flags, UPDATE_NEWEST))
+                log_notice("Selected update '%s' is not the newest, proceeding anyway.", us->version);
+        if (c->newest_installed && strverscmp_improved(c->newest_installed->version, us->version) > 0)
+                log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
+
+        log_info("Selected update '%s' for install.", us->version);
+
+        /* There should now be one instance picked for each transfer, and the order is the same */
+        assert(us->n_instances == c->n_transfers);
+
+        for (size_t i = 0; i < c->n_transfers; i++) {
+                Instance *inst = us->instances[i];
+                Transfer *t = c->transfers[i];
+
+                assert(inst);
+
+                r = transfer_process_partial_and_pending_instance(t, inst);
+                if (r < 0)
+                        return r;
+        }
+
+        return 1;
+}
+
 static int context_install(
                 Context *c,
                 const char *version,
@@ -1106,7 +1196,8 @@ static int context_install(
                 Instance *inst = us->instances[i];
                 Transfer *t = c->transfers[i];
 
-                if (inst->resource == &t->target)
+                if (inst->resource == &t->target &&
+                    !inst->is_pending)
                         continue;
 
                 r = transfer_install_instance(t, inst, arg_root);
@@ -1436,7 +1527,12 @@ static int verb_vacuum(int argc, char **argv, void *userdata) {
         return context_vacuum(context, 0, NULL);
 }
 
-static int verb_update(int argc, char **argv, void *userdata) {
+typedef enum {
+        UPDATE_ACTION_ACQUIRE = 1 << 0,
+        UPDATE_ACTION_INSTALL = 1 << 1,
+} UpdateActionFlags;
+
+static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) {
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
         _cleanup_(context_freep) Context* context = NULL;
@@ -1470,11 +1566,14 @@ static int verb_update(int argc, char **argv, void *userdata) {
         if (r < 0)
                 return r;
 
-        r = context_acquire(context, version);
+        if (action_flags & UPDATE_ACTION_ACQUIRE)
+                r = context_acquire(context, version);
+        else
+                r = context_process_partial_and_pending(context, version);
         if (r < 0)
                 return r;  /* error */
 
-        if (r > 0)  /* update needed */
+        if (action_flags & UPDATE_ACTION_INSTALL && r > 0)  /* update needed */
                 r = context_install(context, version, &applied);
         if (r < 0)
                 return r;
@@ -1500,6 +1599,10 @@ static int verb_update(int argc, char **argv, void *userdata) {
         return 0;
 }
 
+static int verb_update(int argc, char **argv, void *userdata) {
+        return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE | UPDATE_ACTION_INSTALL);
+}
+
 static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
         _cleanup_(context_freep) Context* context = NULL;
         _cleanup_free_ char *booted_version = NULL;