1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

sysupdate: Repair incomplete versions in-place

A previous commit made sysupdate recognize installed versions where some
transfers are missing. This commit teaches sysupdate how to correctly
repair these incomplete versions.

Previously, if you had a incomplete installation of the OS booted, and
ran sysupdate in an attempt to repair it, sysupdate would make things
worse by creating copies of the currently-booted partitions in the
inactive slots. Then at boot you have two identical partitions, with
identical labels an UUIDs, and end up with a mess.

With this commit, sysupdate is able to recognize situations where it can
simply download the missing transfers and leave the rest of the system
undistrubed.

Partial fix for https://github.com/systemd/systemd/issues/33339
This commit is contained in:
Adrian Vovk 2024-06-19 21:05:04 -04:00
parent 57ada07e7a
commit 631803cccd
No known key found for this signature in database
GPG Key ID: 90A7B546533E15FB
3 changed files with 63 additions and 11 deletions

View File

@ -226,7 +226,8 @@ node /org/freedesktop/sysupdate1/target/host {
<term><literal>incomplete</literal></term>
<listitem><para>A boolean indicating whether this version is incomplete, which means that it is
missing some file. Note that only installed incomplete versions will be offered by the service;
versions that are incomplete on the server-side are completely ignored.</para></listitem>
versions that are incomplete on the server-side are completely ignored. Incomplete versions can
be repaired in-place by calling <function>Update()</function> on that version.</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -1075,8 +1075,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, TransferProgress cb, voi
assert(t);
assert(i);
assert(i->resource);
assert(t == container_of(i->resource, Transfer, source));
assert(i->resource == &t->source);
assert(cb);
/* Does this instance already exist in the target? Then we don't need to acquire anything */

View File

@ -325,11 +325,36 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
/* See if we already have this update set in our table */
FOREACH_ARRAY(update_set, c->update_sets, c->n_update_sets) {
UpdateSet *u = *update_set;
if (strverscmp_improved(u->version, cursor) != 0)
continue;
/* We only store the instances we found first, but we remember we also found it again */
/* Merge in what we've learned and continue onto the next version */
if (FLAGS_SET(u->flags, UPDATE_INCOMPLETE)) {
assert(u->n_instances == c->n_transfers);
/* Incomplete updates will have picked NULL instances for the transfers that
* are missing. Now we have more information, so let's try to fill them in. */
for (size_t j = 0; j < u->n_instances; j++) {
if (!u->instances[j])
u->instances[j] = cursor_instances[j];
/* Make sure that the list is full if the update is AVAILABLE */
assert(flags != UPDATE_AVAILABLE || u->instances[j]);
}
}
u->flags |= flags | extra_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. */
if (FLAGS_SET(u->flags, UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_AVAILABLE) &&
!c->candidate && !FLAGS_SET(u->flags, UPDATE_OBSOLETE))
c->candidate = u;
skip = true;
newest_found = true;
break;
@ -367,7 +392,8 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
}
/* Newest installed is newer than or equal to candidate? Then suppress the candidate */
if (c->newest_installed && c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
if (c->newest_installed && !FLAGS_SET(c->newest_installed->flags, UPDATE_INCOMPLETE) &&
c->candidate && strverscmp_improved(c->newest_installed->version, c->candidate->version) >= 0)
c->candidate = NULL;
return 0;
@ -736,6 +762,10 @@ static int context_vacuum(
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
Transfer *t = *tr;
/* Don't bother clearing out space if we're not going to be downloading anything */
if (extra_protected_version && resource_find_instance(&t->target, extra_protected_version))
continue;
r = transfer_vacuum(t, space, extra_protected_version);
if (r < 0)
return r;
@ -866,7 +896,9 @@ static int context_apply(
us = c->candidate;
}
if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
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)) {
log_info("Selected update '%s' is already installed. Skipping update.", us->version);
if (ret_applied)
@ -874,13 +906,12 @@ static int context_apply(
return 0;
}
if (!FLAGS_SET(us->flags, UPDATE_AVAILABLE))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected update '%s' is not available, 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);
assert((us->flags & (UPDATE_AVAILABLE|UPDATE_INSTALLED|UPDATE_OBSOLETE)) == UPDATE_AVAILABLE);
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)
@ -913,8 +944,17 @@ static int context_apply(
assert(us->n_instances == c->n_transfers);
for (size_t i = 0; i < c->n_transfers; i++) {
r = transfer_acquire_instance(c->transfers[i], us->instances[i],
context_on_acquire_progress, c);
Instance *inst = us->instances[i];
Transfer *t = c->transfers[i];
assert(inst); /* ditto */
if (inst->resource == &t->target) { /* a present transfer in an incomplete installation */
assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
continue;
}
r = transfer_acquire_instance(t, inst, context_on_acquire_progress, c);
if (r < 0)
return r;
}
@ -926,7 +966,13 @@ static int context_apply(
"STATUS=Installing '%s'.", us->version);
for (size_t i = 0; i < c->n_transfers; i++) {
r = transfer_install_instance(c->transfers[i], us->instances[i], arg_root);
Instance *inst = us->instances[i];
Transfer *t = c->transfers[i];
if (inst->resource == &t->target)
continue;
r = transfer_install_instance(t, inst, arg_root);
if (r < 0)
return r;
}
@ -1160,6 +1206,12 @@ static int verb_update(int argc, char **argv, void *userdata) {
return reboot_now();
}
if (strverscmp_improved(applied->version, booted_version) == 0 &&
FLAGS_SET(applied->flags, UPDATE_INCOMPLETE)) {
log_notice("Currently booted version was incomplete and has been repaired, rebooting.");
return reboot_now();
}
log_info("Booted version is newer or identical to newly installed version, not rebooting.");
}