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();
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:
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();
}
}
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;
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;
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 */
/* We only store the instances we found first, but we remember we also found it again */
u->flags |= flags | extra_flags;
- exists = true;
+ skip = true;
newest_found = true;
break;
}
- if (exists)
+ if (skip)
continue;
/* Doesn't exist yet, let's add it */
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);
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());
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)
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);
{ "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 },
{},
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)