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:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user