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.
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);
/* 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
/* 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;
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;
}
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;
}
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) {
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;
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,
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);
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;
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;
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;