From cf7cccf2f7949736e47dd035d022554cd6f1609e Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Wed, 31 Dec 2025 00:46:25 +0000 Subject: [PATCH] sysupdate: Split the update verb into two parts internally MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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 Helps: https://github.com/systemd/systemd/issues/34814 --- src/sysupdate/sysupdate-partition.c | 27 +++++++ src/sysupdate/sysupdate-partition.h | 1 + src/sysupdate/sysupdate-transfer.c | 36 +++++++++ src/sysupdate/sysupdate-transfer.h | 1 + src/sysupdate/sysupdate.c | 121 +++++++++++++++++++++++++--- 5 files changed, 177 insertions(+), 9 deletions(-) diff --git a/src/sysupdate/sysupdate-partition.c b/src/sysupdate/sysupdate-partition.c index cd85db5236e..237c157831f 100644 --- a/src/sysupdate/sysupdate-partition.c +++ b/src/sysupdate/sysupdate-partition.c @@ -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, diff --git a/src/sysupdate/sysupdate-partition.h b/src/sysupdate/sysupdate-partition.h index 21277962e41..fbda14de6f5 100644 --- a/src/sysupdate/sysupdate-partition.h +++ b/src/sysupdate/sysupdate-partition.h @@ -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); diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 5af2f4cf301..a13f659e360 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -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, diff --git a/src/sysupdate/sysupdate-transfer.h b/src/sysupdate/sysupdate-transfer.h index 92c19227953..f5976f0477f 100644 --- a/src/sysupdate/sysupdate-transfer.h +++ b/src/sysupdate/sysupdate-transfer.h @@ -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); diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index a40e94da6c8..700962819fc 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -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; -- 2.47.3