]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: Repair incomplete versions in-place
authorAdrian Vovk <adrianvovk@gmail.com>
Thu, 20 Jun 2024 01:05:04 +0000 (21:05 -0400)
committerAdrian Vovk <adrianvovk@gmail.com>
Thu, 22 Aug 2024 20:00:46 +0000 (16:00 -0400)
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

man/org.freedesktop.sysupdate1.xml
src/sysupdate/sysupdate-transfer.c
src/sysupdate/sysupdate.c

index 3acae3d701ccffe4683ec7daf214c1920ce79779..79f718f93c4e2c61d581272b1b497ad839c0a696 100644 (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>
index 69bba00cabb5e1b6263cbbfb491b0f1534fa31c6..f7d9a043fc68e84aae7d26de324431eca8fd6cba 100644 (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 */
index 9b56bf1a8f29c69791af4e0323f419b033fe9fcc..04fd003299ea2495a04d3eeb93cc840dc97dedc0 100644 (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.");
         }