mirror of
https://github.com/systemd/systemd.git
synced 2025-01-10 05:18:17 +03:00
Merge pull request #33570 from AdrianVovk/sysupdate-incomplete
sysupdate: Handle incomplete versions
This commit is contained in:
commit
88261bcf3b
@ -222,6 +222,14 @@ node /org/freedesktop/sysupdate1/target/host {
|
||||
<function>Vacuum()</function> operation.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<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. Incomplete versions can
|
||||
be repaired in-place by calling <function>Update()</function> on that version.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>changelog_urls</literal></term>
|
||||
<listitem><para>A list of strings that contain user-presentable URLs to ChangeLogs associated with
|
||||
|
@ -257,9 +257,9 @@
|
||||
<term><option>--instances-max=</option></term>
|
||||
<term><option>-m</option></term>
|
||||
|
||||
<listitem><para>Takes a decimal integer greater than or equal to 2. Controls how many versions to
|
||||
keep at any time. This option may also be configured inside the transfer files, via the
|
||||
<varname>InstancesMax=</varname> setting, see
|
||||
<listitem><para>Takes a decimal integer greater than or equal to 2 while updating or 1 while vacuuming.
|
||||
Controls how many versions to keep at any time. This option may also be configured inside the transfer
|
||||
files, via the <varname>InstancesMax=</varname> setting, see
|
||||
<citerefentry><refentrytitle>sysupdate.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details.</para>
|
||||
|
||||
|
@ -527,13 +527,25 @@ int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int instance_version_match(Instance *const*a, Instance *const*b) {
|
||||
assert(a);
|
||||
assert(b);
|
||||
assert(*a);
|
||||
assert(*b);
|
||||
assert((*a)->metadata.version);
|
||||
assert((*b)->metadata.version);
|
||||
|
||||
/* List is sorted newest-to-oldest */
|
||||
return -strverscmp_improved((*a)->metadata.version, (*b)->metadata.version);
|
||||
}
|
||||
|
||||
Instance* resource_find_instance(Resource *rr, const char *version) {
|
||||
Instance key = {
|
||||
.metadata.version = (char*) version,
|
||||
}, *k = &key;
|
||||
|
||||
Instance **found;
|
||||
found = typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_cmp);
|
||||
found = typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_version_match);
|
||||
if (!found)
|
||||
return NULL;
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -10,6 +10,9 @@ const char* update_set_flags_to_color(UpdateSetFlags flags) {
|
||||
if (flags == 0 || (flags & UPDATE_OBSOLETE))
|
||||
return (flags & UPDATE_NEWEST) ? ansi_highlight_grey() : ansi_grey();
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_INCOMPLETE))
|
||||
return ansi_highlight_yellow();
|
||||
|
||||
if (FLAGS_SET(flags, UPDATE_INSTALLED|UPDATE_NEWEST))
|
||||
return ansi_highlight();
|
||||
|
||||
@ -68,6 +71,27 @@ const char* update_set_flags_to_string(UpdateSetFlags flags) {
|
||||
case UPDATE_AVAILABLE|UPDATE_PROTECTED:
|
||||
return "available";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_INCOMPLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_INCOMPLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "current+incomplete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_INCOMPLETE:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_INCOMPLETE:
|
||||
return "installed+incomplete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_INCOMPLETE|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_INCOMPLETE|UPDATE_PROTECTED:
|
||||
return "protected+incomplete";
|
||||
|
||||
case UPDATE_AVAILABLE|UPDATE_INCOMPLETE:
|
||||
case UPDATE_AVAILABLE|UPDATE_INCOMPLETE|UPDATE_PROTECTED:
|
||||
case UPDATE_AVAILABLE|UPDATE_INCOMPLETE|UPDATE_NEWEST:
|
||||
case UPDATE_AVAILABLE|UPDATE_INCOMPLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
/* We must never offer an update as available for download if it's incomplete */
|
||||
assert_not_reached();
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
||||
@ -88,6 +112,26 @@ const char* update_set_flags_to_string(UpdateSetFlags flags) {
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "available+obsolete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_NEWEST:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
return "current+obsolete+incomplete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_INCOMPLETE:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_INCOMPLETE:
|
||||
return "installed+obsolete+incomplete";
|
||||
|
||||
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_PROTECTED:
|
||||
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_PROTECTED:
|
||||
return "protected+obsolete+incomplete";
|
||||
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_INCOMPLETE:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_PROTECTED:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_NEWEST:
|
||||
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_INCOMPLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
||||
assert_not_reached();
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
@ -2,11 +2,12 @@
|
||||
#pragma once
|
||||
|
||||
typedef enum UpdateSetFlags {
|
||||
UPDATE_NEWEST = 1 << 0,
|
||||
UPDATE_AVAILABLE = 1 << 1,
|
||||
UPDATE_INSTALLED = 1 << 2,
|
||||
UPDATE_OBSOLETE = 1 << 3,
|
||||
UPDATE_PROTECTED = 1 << 4,
|
||||
UPDATE_NEWEST = 1 << 0,
|
||||
UPDATE_AVAILABLE = 1 << 1,
|
||||
UPDATE_INSTALLED = 1 << 2,
|
||||
UPDATE_OBSOLETE = 1 << 3,
|
||||
UPDATE_PROTECTED = 1 << 4,
|
||||
UPDATE_INCOMPLETE = 1 << 5,
|
||||
} UpdateSetFlags;
|
||||
|
||||
const char* update_set_flags_to_color(UpdateSetFlags flags);
|
||||
|
@ -215,7 +215,6 @@ static int context_load_available_instances(Context *c) {
|
||||
}
|
||||
|
||||
static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags) {
|
||||
_cleanup_free_ Instance **cursor_instances = NULL;
|
||||
_cleanup_free_ char *boundary = NULL;
|
||||
bool newest_found = false;
|
||||
int r;
|
||||
@ -224,64 +223,91 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
||||
assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
|
||||
|
||||
for (;;) {
|
||||
bool incomplete = false, exists = false;
|
||||
_cleanup_free_ Instance **cursor_instances = NULL;
|
||||
bool skip = false;
|
||||
UpdateSetFlags extra_flags = 0;
|
||||
_cleanup_free_ char *cursor = NULL;
|
||||
UpdateSet *us = NULL;
|
||||
|
||||
for (size_t k = 0; k < c->n_transfers; k++) {
|
||||
Transfer *t = c->transfers[k];
|
||||
bool cursor_found = false;
|
||||
/* First, let's find the newest version that's older than the boundary. */
|
||||
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
|
||||
Resource *rr;
|
||||
|
||||
assert(t);
|
||||
assert(*tr);
|
||||
|
||||
if (flags == UPDATE_AVAILABLE)
|
||||
rr = &t->source;
|
||||
rr = &(*tr)->source;
|
||||
else {
|
||||
assert(flags == UPDATE_INSTALLED);
|
||||
rr = &t->target;
|
||||
rr = &(*tr)->target;
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(inst, rr->instances, rr->n_instances) {
|
||||
Instance *i = *inst;
|
||||
Instance *i = *inst; /* Sorted newest-to-oldest */
|
||||
|
||||
assert(i);
|
||||
|
||||
/* Is the instance we are looking at equal or newer than the boundary? If so, we
|
||||
* already checked this version, and it wasn't complete, let's ignore it. */
|
||||
if (boundary && strverscmp_improved(i->metadata.version, boundary) >= 0)
|
||||
continue;
|
||||
continue; /* Not older than the boundary */
|
||||
|
||||
if (cursor) {
|
||||
if (strverscmp_improved(i->metadata.version, cursor) != 0)
|
||||
continue;
|
||||
} else {
|
||||
cursor = strdup(i->metadata.version);
|
||||
if (!cursor)
|
||||
return log_oom();
|
||||
}
|
||||
if (cursor && strverscmp(i->metadata.version, cursor) <= 0)
|
||||
break; /* Not newer than the cursor. The same will be true for all
|
||||
* subsequent instances (due to sorting) so let's skip to the
|
||||
* next transfer. */
|
||||
|
||||
cursor_found = true;
|
||||
if (free_and_strdup(&cursor, i->metadata.version) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (!cursor_instances) {
|
||||
cursor_instances = new(Instance*, c->n_transfers);
|
||||
if (!cursor_instances)
|
||||
return -ENOMEM;
|
||||
}
|
||||
cursor_instances[k] = i;
|
||||
break;
|
||||
break; /* All subsequent instances will be older than this one */
|
||||
}
|
||||
|
||||
if (!cursor) /* No suitable instance beyond the boundary found? Then we are done! */
|
||||
break;
|
||||
if (flags == UPDATE_AVAILABLE && !cursor)
|
||||
break; /* This transfer didn't have a version older than the boundary,
|
||||
* so any older-than-boundary version that might exist in a different
|
||||
* transfer must always be incomplete. For reasons described below,
|
||||
* we don't include incomplete versions for AVAILABLE updates. So we
|
||||
* are completely done looking. */
|
||||
}
|
||||
|
||||
if (!cursor_found) {
|
||||
/* Hmm, we didn't find the version indicated by 'cursor' among the instances
|
||||
* of this transfer, let's skip it. */
|
||||
incomplete = true;
|
||||
break;
|
||||
if (!cursor) /* We didn't find anything older than the boundary, so we're done. */
|
||||
break;
|
||||
|
||||
cursor_instances = new0(Instance*, c->n_transfers);
|
||||
if (!cursor_instances)
|
||||
return log_oom();
|
||||
|
||||
/* Now let's find all the instances that match the version of the cursor, if we have them */
|
||||
for (size_t k = 0; k < c->n_transfers; k++) {
|
||||
Transfer *t = c->transfers[k];
|
||||
Instance *match = NULL;
|
||||
|
||||
assert(t);
|
||||
|
||||
if (flags == UPDATE_AVAILABLE) {
|
||||
match = resource_find_instance(&t->source, cursor);
|
||||
if (!match) {
|
||||
/* When we're looking for updates to download, we don't offer
|
||||
* incomplete versions at all. The server wants to send us an update
|
||||
* with parts of the OS missing. For robustness sake, let's not do
|
||||
* that. */
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
assert(flags == UPDATE_INSTALLED);
|
||||
|
||||
match = resource_find_instance(&t->target, cursor);
|
||||
if (!match) {
|
||||
/* 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.
|
||||
* See https://github.com/systemd/systemd/issues/33339 */
|
||||
extra_flags |= UPDATE_INCOMPLETE;
|
||||
}
|
||||
}
|
||||
|
||||
cursor_instances[k] = match;
|
||||
|
||||
if (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
|
||||
extra_flags |= UPDATE_OBSOLETE;
|
||||
|
||||
@ -289,30 +315,52 @@ static int context_discover_update_sets_by_flag(Context *c, UpdateSetFlags flags
|
||||
extra_flags |= UPDATE_PROTECTED;
|
||||
}
|
||||
|
||||
if (!cursor) /* EOL */
|
||||
break;
|
||||
|
||||
r = free_and_strdup_warn(&boundary, cursor);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (incomplete) /* One transfer was missing this version, ignore the whole thing */
|
||||
if (skip)
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
exists = true;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
if (exists)
|
||||
if (skip)
|
||||
continue;
|
||||
|
||||
/* Doesn't exist yet, let's add it */
|
||||
@ -344,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;
|
||||
@ -504,6 +553,12 @@ static int context_show_version(Context *c, const char *version) {
|
||||
|
||||
FOREACH_ARRAY(inst, us->instances, us->n_instances) {
|
||||
Instance *i = *inst;
|
||||
|
||||
if (!i) {
|
||||
assert(FLAGS_SET(us->flags, UPDATE_INCOMPLETE));
|
||||
continue;
|
||||
}
|
||||
|
||||
r = table_add_many(t,
|
||||
TABLE_STRING, resource_type_to_string(i->resource->type),
|
||||
TABLE_PATH, i->path);
|
||||
@ -641,13 +696,14 @@ static int context_show_version(Context *c, const char *version) {
|
||||
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
|
||||
printf("%s%s%s Version: %s\n"
|
||||
" State: %s%s%s\n"
|
||||
"Installed: %s%s\n"
|
||||
"Installed: %s%s%s%s%s\n"
|
||||
"Available: %s%s\n"
|
||||
"Protected: %s%s%s\n"
|
||||
" Obsolete: %s%s%s\n",
|
||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_glyph(us->flags), ansi_normal(), us->version,
|
||||
strempty(update_set_flags_to_color(us->flags)), update_set_flags_to_string(us->flags), ansi_normal(),
|
||||
yes_no(us->flags & UPDATE_INSTALLED), FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_NEWEST) ? " (newest)" : "",
|
||||
FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? ansi_highlight_yellow() : "", FLAGS_SET(us->flags, UPDATE_INCOMPLETE) ? " (incomplete)" : "", ansi_normal(),
|
||||
yes_no(us->flags & UPDATE_AVAILABLE), (us->flags & (UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST)) == (UPDATE_AVAILABLE|UPDATE_NEWEST) ? " (newest)" : "",
|
||||
FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED) ? ansi_highlight() : "", yes_no(FLAGS_SET(us->flags, UPDATE_INSTALLED|UPDATE_PROTECTED)), ansi_normal(),
|
||||
us->flags & UPDATE_OBSOLETE ? ansi_highlight_red() : "", yes_no(us->flags & UPDATE_OBSOLETE), ansi_normal());
|
||||
@ -675,6 +731,7 @@ static int context_show_version(Context *c, const char *version) {
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("installed", FLAGS_SET(us->flags, UPDATE_INSTALLED)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("obsolete", FLAGS_SET(us->flags, UPDATE_OBSOLETE)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("protected", FLAGS_SET(us->flags, UPDATE_PROTECTED)),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("incomplete", FLAGS_SET(us->flags, UPDATE_INCOMPLETE)),
|
||||
SD_JSON_BUILD_PAIR_STRV("changelog_urls", changelog_urls),
|
||||
SD_JSON_BUILD_PAIR_VARIANT("contents", t_json));
|
||||
if (r < 0)
|
||||
@ -705,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;
|
||||
@ -835,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)
|
||||
@ -843,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)
|
||||
@ -882,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;
|
||||
}
|
||||
@ -895,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;
|
||||
}
|
||||
@ -1067,6 +1144,10 @@ static int verb_vacuum(int argc, char **argv, void *userdata) {
|
||||
|
||||
assert(argc <= 1);
|
||||
|
||||
if (arg_instances_max < 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"The --instances-max argument must be >= 1 while vacuuming");
|
||||
|
||||
r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1090,6 +1171,10 @@ static int verb_update(int argc, char **argv, void *userdata) {
|
||||
assert(argc <= 2);
|
||||
version = argc >= 2 ? argv[1] : NULL;
|
||||
|
||||
if (arg_instances_max < 2)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"The --instances-max argument must be >= 2 while updating");
|
||||
|
||||
if (arg_reboot) {
|
||||
/* If automatic reboot on completion is requested, let's first determine the currently booted image */
|
||||
|
||||
@ -1121,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.");
|
||||
}
|
||||
|
||||
|
@ -324,7 +324,8 @@ static int parse_describe(sd_bus_message *reply, Version *ret) {
|
||||
Version v = {};
|
||||
char *version_json = NULL;
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL, *contents_json = NULL;
|
||||
bool newest = false, available = false, installed = false, obsolete = false, protected = false;
|
||||
bool newest = false, available = false, installed = false, obsolete = false, protected = false,
|
||||
incomplete = false;
|
||||
int r;
|
||||
|
||||
assert(reply);
|
||||
@ -348,6 +349,7 @@ static int parse_describe(sd_bus_message *reply, Version *ret) {
|
||||
{ "installed", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, PTR_TO_SIZE(&installed), 0 },
|
||||
{ "obsolete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, PTR_TO_SIZE(&obsolete), 0 },
|
||||
{ "protected", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, PTR_TO_SIZE(&protected), 0 },
|
||||
{ "incomplete", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, PTR_TO_SIZE(&incomplete), 0 },
|
||||
{ "changelog_urls", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, PTR_TO_SIZE(&v.changelog), 0 },
|
||||
{ "contents", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, PTR_TO_SIZE(&contents_json), 0 },
|
||||
{},
|
||||
@ -362,6 +364,7 @@ static int parse_describe(sd_bus_message *reply, Version *ret) {
|
||||
SET_FLAG(v.flags, UPDATE_INSTALLED, installed);
|
||||
SET_FLAG(v.flags, UPDATE_OBSOLETE, obsolete);
|
||||
SET_FLAG(v.flags, UPDATE_PROTECTED, protected);
|
||||
SET_FLAG(v.flags, UPDATE_INCOMPLETE, incomplete);
|
||||
|
||||
r = sd_json_variant_format(contents_json, 0, &v.contents_json);
|
||||
if (r < 0)
|
||||
|
@ -53,6 +53,10 @@ at_exit() {
|
||||
|
||||
trap at_exit EXIT
|
||||
|
||||
update_checksums() {
|
||||
(cd "$WORKDIR/source" && sha256sum uki* part* dir-*.tar.gz >SHA256SUMS)
|
||||
}
|
||||
|
||||
new_version() {
|
||||
local sector_size="${1:?}"
|
||||
local version="${2:?}"
|
||||
@ -74,7 +78,7 @@ new_version() {
|
||||
echo $RANDOM >"$WORKDIR/source/dir-$version/bar.txt"
|
||||
tar --numeric-owner -C "$WORKDIR/source/dir-$version/" -czf "$WORKDIR/source/dir-$version.tar.gz" .
|
||||
|
||||
(cd "$WORKDIR/source" && sha256sum uki* part* dir-*.tar.gz >SHA256SUMS)
|
||||
update_checksums
|
||||
}
|
||||
|
||||
update_now() {
|
||||
@ -91,9 +95,9 @@ verify_version() {
|
||||
local sector_size="${2:?}"
|
||||
local version="${3:?}"
|
||||
local part1_number="${4:?}"
|
||||
local part2_number="${5:?}"
|
||||
local gpt_reserved_sectors part1_offset part2_offset
|
||||
local gpt_reserved_sectors part2_number part1_offset part2_offset
|
||||
|
||||
part2_number=$(( part1_number + 2 ))
|
||||
gpt_reserved_sectors=$((1024 * 1024 / sector_size))
|
||||
part1_offset=$(((part1_number - 1) * 2048 + gpt_reserved_sectors))
|
||||
part2_offset=$(((part2_number - 1) * 2048 + gpt_reserved_sectors))
|
||||
@ -108,6 +112,12 @@ verify_version() {
|
||||
|
||||
# Check the extra efi
|
||||
cmp "$WORKDIR/source/uki-extra-$version.efi" "$WORKDIR/xbootldr/EFI/Linux/uki_$version.efi.extra.d/extra.addon.efi"
|
||||
}
|
||||
|
||||
verify_version_current() {
|
||||
local version="${3:?}"
|
||||
|
||||
verify_version "$@"
|
||||
|
||||
# Check the directories
|
||||
cmp "$WORKDIR/source/dir-$version/foo.txt" "$WORKDIR/dirs/current/foo.txt"
|
||||
@ -225,24 +235,50 @@ EOF
|
||||
# Install initial version and verify
|
||||
new_version "$sector_size" v1
|
||||
update_now
|
||||
verify_version "$blockdev" "$sector_size" v1 1 3
|
||||
verify_version_current "$blockdev" "$sector_size" v1 1
|
||||
|
||||
# Create second version, update and verify that it is added
|
||||
new_version "$sector_size" v2
|
||||
update_now
|
||||
verify_version "$blockdev" "$sector_size" v2 2 4
|
||||
verify_version "$blockdev" "$sector_size" v1 1
|
||||
verify_version_current "$blockdev" "$sector_size" v2 2
|
||||
|
||||
# Create third version, update and verify it replaced the first version
|
||||
new_version "$sector_size" v3
|
||||
update_now
|
||||
verify_version "$blockdev" "$sector_size" v3 1 3
|
||||
verify_version_current "$blockdev" "$sector_size" v3 1
|
||||
verify_version "$blockdev" "$sector_size" v2 2
|
||||
test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v1+3-0.efi"
|
||||
test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v1.efi.extra.d/extra.addon.efi"
|
||||
test ! -d "$WORKDIR/xbootldr/EFI/Linux/uki_v1.efi.extra.d"
|
||||
|
||||
# Create fourth version, update using updatectl and verify it replaced the
|
||||
# second version
|
||||
# Create fourth version, but make it be incomplete (i.e. missing some files)
|
||||
# on the server-side. Verify that it's not offered as an update.
|
||||
new_version "$sector_size" v4
|
||||
rm "$WORKDIR/source/uki-extra-v4.efi"
|
||||
update_checksums
|
||||
(! "$SYSUPDATE" --verify=no check-new)
|
||||
|
||||
# Create a fifth version, that's complete on the server side. We should
|
||||
# completely skip the incomplete v4 and install v5 instead.
|
||||
new_version "$sector_size" v5
|
||||
update_now
|
||||
verify_version "$blockdev" "$sector_size" v3 1
|
||||
verify_version_current "$blockdev" "$sector_size" v5 2
|
||||
|
||||
# Make the local installation of v5 incomplete by deleting a file, then make
|
||||
# sure that sysupdate still recognizes the installation and can complete it
|
||||
# in place
|
||||
rm -r "$WORKDIR/xbootldr/EFI/Linux/uki_v5.efi.extra.d"
|
||||
"$SYSUPDATE" --offline list v5 | grep -q "incomplete"
|
||||
update_now
|
||||
"$SYSUPDATE" --offline list v5 | grep -qv "incomplete"
|
||||
verify_version "$blockdev" "$sector_size" v3 1
|
||||
verify_version_current "$blockdev" "$sector_size" v5 2
|
||||
|
||||
# Create sixth version, update using updatectl and verify it replaced the
|
||||
# correct version
|
||||
new_version "$sector_size" v6
|
||||
if [[ -x "$SYSUPDATED" ]] && command -v updatectl; then
|
||||
systemctl start systemd-sysupdated
|
||||
"$SYSUPDATE" --verify=no check-new
|
||||
@ -254,7 +290,8 @@ EOF
|
||||
# User-facing updatectl returns 0 if there's no updates, so use the low-level
|
||||
# utility to make sure we did upgrade
|
||||
(! "$SYSUPDATE" --verify=no check-new )
|
||||
verify_version "$blockdev" "$sector_size" v4 2 4
|
||||
verify_version_current "$blockdev" "$sector_size" v6 1
|
||||
verify_version "$blockdev" "$sector_size" v5 2
|
||||
|
||||
# Next, let's run updatectl's various inspection commands. We're not
|
||||
# testing for specific output, but this will at least catch obvious crashes
|
||||
@ -265,18 +302,18 @@ EOF
|
||||
cp "$CONFIGDIR/01-first.conf" /run/sysupdate.test.d/01-first.conf
|
||||
updatectl list
|
||||
updatectl list host
|
||||
updatectl list host@v4
|
||||
updatectl list host@v6
|
||||
updatectl check
|
||||
rm -r /run/sysupdate.test.d
|
||||
fi
|
||||
|
||||
# Create fifth version, and update through a file:// URL. This should be
|
||||
# Create seventh version, and update through a file:// URL. This should be
|
||||
# almost as good as testing HTTP, but is simpler for us to set up. file:// is
|
||||
# abstracted in curl for us, and since our main goal is to test our own code
|
||||
# (and not curl) this test should be quite good even if not comprehensive. This
|
||||
# will test the SHA256SUMS logic at least (we turn off GPG validation though,
|
||||
# see above)
|
||||
new_version "$sector_size" v5
|
||||
new_version "$sector_size" v7
|
||||
|
||||
cat >"$CONFIGDIR/02-second.conf" <<EOF
|
||||
[Source]
|
||||
@ -306,7 +343,8 @@ InstancesMax=3
|
||||
EOF
|
||||
|
||||
update_now
|
||||
verify_version "$blockdev" "$sector_size" v5 1 3
|
||||
verify_version "$blockdev" "$sector_size" v6 1
|
||||
verify_version_current "$blockdev" "$sector_size" v7 2
|
||||
|
||||
# Cleanup
|
||||
[[ -b "$blockdev" ]] && losetup --detach "$blockdev"
|
||||
|
Loading…
Reference in New Issue
Block a user