sysupdate: Split the update verb into two parts internally

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
This commit is contained in:
Philip Withnall
2026-02-09 12:05:05 +00:00
parent 594d0345fa
commit cf7cccf2f7
5 changed files with 177 additions and 9 deletions
+27
View 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,
+1
View 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);
+36
View 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,
+1
View 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);
+112 -9
View 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;