mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 00:47:10 +09:00
1419 lines
52 KiB
C
1419 lines
52 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <getopt.h>
|
|
#include <unistd.h>
|
|
|
|
#include "build.h"
|
|
#include "bus-error.h"
|
|
#include "bus-locator.h"
|
|
#include "chase.h"
|
|
#include "conf-files.h"
|
|
#include "constants.h"
|
|
#include "dirent-util.h"
|
|
#include "dissect-image.h"
|
|
#include "fd-util.h"
|
|
#include "format-table.h"
|
|
#include "glyph-util.h"
|
|
#include "hexdecoct.h"
|
|
#include "login-util.h"
|
|
#include "main-func.h"
|
|
#include "mount-util.h"
|
|
#include "os-util.h"
|
|
#include "pager.h"
|
|
#include "parse-argument.h"
|
|
#include "parse-util.h"
|
|
#include "path-util.h"
|
|
#include "pretty-print.h"
|
|
#include "set.h"
|
|
#include "sort-util.h"
|
|
#include "string-util.h"
|
|
#include "strv.h"
|
|
#include "sysupdate-transfer.h"
|
|
#include "sysupdate-update-set.h"
|
|
#include "sysupdate.h"
|
|
#include "terminal-util.h"
|
|
#include "utf8.h"
|
|
#include "verbs.h"
|
|
|
|
static char *arg_definitions = NULL;
|
|
bool arg_sync = true;
|
|
uint64_t arg_instances_max = UINT64_MAX;
|
|
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
|
|
static PagerFlags arg_pager_flags = 0;
|
|
static bool arg_legend = true;
|
|
char *arg_root = NULL;
|
|
static char *arg_image = NULL;
|
|
static bool arg_reboot = false;
|
|
static char *arg_component = NULL;
|
|
static int arg_verify = -1;
|
|
static ImagePolicy *arg_image_policy = NULL;
|
|
|
|
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
|
|
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
|
|
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
|
|
STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
|
|
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
|
|
|
|
typedef struct Context {
|
|
Transfer **transfers;
|
|
size_t n_transfers;
|
|
|
|
UpdateSet **update_sets;
|
|
size_t n_update_sets;
|
|
|
|
UpdateSet *newest_installed, *candidate;
|
|
|
|
Hashmap *web_cache; /* Cache for downloaded resources, keyed by URL */
|
|
} Context;
|
|
|
|
static Context *context_free(Context *c) {
|
|
if (!c)
|
|
return NULL;
|
|
|
|
for (size_t i = 0; i < c->n_transfers; i++)
|
|
transfer_free(c->transfers[i]);
|
|
free(c->transfers);
|
|
|
|
for (size_t i = 0; i < c->n_update_sets; i++)
|
|
update_set_free(c->update_sets[i]);
|
|
free(c->update_sets);
|
|
|
|
hashmap_free(c->web_cache);
|
|
|
|
return mfree(c);
|
|
}
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
|
|
|
|
static Context *context_new(void) {
|
|
/* For now, no fields to initialize non-zero */
|
|
return new0(Context, 1);
|
|
}
|
|
|
|
static int context_read_definitions(
|
|
Context *c,
|
|
const char *directory,
|
|
const char *component,
|
|
const char *root,
|
|
const char *node) {
|
|
|
|
_cleanup_strv_free_ char **files = NULL;
|
|
int r;
|
|
|
|
assert(c);
|
|
|
|
if (directory)
|
|
r = conf_files_list_strv(&files, ".conf", NULL, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char**) STRV_MAKE(directory));
|
|
else if (component) {
|
|
_cleanup_strv_free_ char **n = NULL;
|
|
char **l = CONF_PATHS_STRV("");
|
|
size_t k = 0;
|
|
|
|
n = new0(char*, strv_length(l) + 1);
|
|
if (!n)
|
|
return log_oom();
|
|
|
|
STRV_FOREACH(i, l) {
|
|
char *j;
|
|
|
|
j = strjoin(*i, "sysupdate.", component, ".d");
|
|
if (!j)
|
|
return log_oom();
|
|
|
|
n[k++] = j;
|
|
}
|
|
|
|
r = conf_files_list_strv(&files, ".conf", root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char**) n);
|
|
} else
|
|
r = conf_files_list_strv(&files, ".conf", root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char**) CONF_PATHS_STRV("sysupdate.d"));
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to enumerate *.conf files: %m");
|
|
|
|
STRV_FOREACH(f, files) {
|
|
_cleanup_(transfer_freep) Transfer *t = NULL;
|
|
|
|
if (!GREEDY_REALLOC(c->transfers, c->n_transfers + 1))
|
|
return log_oom();
|
|
|
|
t = transfer_new();
|
|
if (!t)
|
|
return log_oom();
|
|
|
|
t->definition_path = strdup(*f);
|
|
if (!t->definition_path)
|
|
return log_oom();
|
|
|
|
r = transfer_read_definition(t, *f);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
c->transfers[c->n_transfers++] = TAKE_PTR(t);
|
|
}
|
|
|
|
if (c->n_transfers == 0) {
|
|
if (arg_component)
|
|
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
|
|
"No transfer definitions for component '%s' found.", arg_component);
|
|
|
|
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
|
|
"No transfer definitions found.");
|
|
}
|
|
|
|
for (size_t i = 0; i < c->n_transfers; i++) {
|
|
r = transfer_resolve_paths(c->transfers[i], root, node);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int context_load_installed_instances(Context *c) {
|
|
int r;
|
|
|
|
assert(c);
|
|
|
|
log_info("Discovering installed instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
|
|
|
for (size_t i = 0; i < c->n_transfers; i++) {
|
|
r = resource_load_instances(
|
|
&c->transfers[i]->target,
|
|
arg_verify >= 0 ? arg_verify : c->transfers[i]->verify,
|
|
&c->web_cache);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int context_load_available_instances(Context *c) {
|
|
int r;
|
|
|
|
assert(c);
|
|
|
|
log_info("Discovering available instances%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
|
|
|
for (size_t i = 0; i < c->n_transfers; i++) {
|
|
assert(c->transfers[i]);
|
|
|
|
r = resource_load_instances(
|
|
&c->transfers[i]->source,
|
|
arg_verify >= 0 ? arg_verify : c->transfers[i]->verify,
|
|
&c->web_cache);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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(c);
|
|
assert(IN_SET(flags, UPDATE_AVAILABLE, UPDATE_INSTALLED));
|
|
|
|
for (;;) {
|
|
bool incomplete = false, exists = 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;
|
|
Resource *rr;
|
|
|
|
assert(t);
|
|
|
|
if (flags == UPDATE_AVAILABLE)
|
|
rr = &t->source;
|
|
else {
|
|
assert(flags == UPDATE_INSTALLED);
|
|
rr = &t->target;
|
|
}
|
|
|
|
for (size_t j = 0; j < rr->n_instances; j++) {
|
|
Instance *i = rr->instances[j];
|
|
|
|
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;
|
|
|
|
if (cursor) {
|
|
if (strverscmp_improved(i->metadata.version, cursor) != 0)
|
|
continue;
|
|
} else {
|
|
cursor = strdup(i->metadata.version);
|
|
if (!cursor)
|
|
return log_oom();
|
|
}
|
|
|
|
cursor_found = true;
|
|
|
|
if (!cursor_instances) {
|
|
cursor_instances = new(Instance*, c->n_transfers);
|
|
if (!cursor_instances)
|
|
return -ENOMEM;
|
|
}
|
|
cursor_instances[k] = i;
|
|
break;
|
|
}
|
|
|
|
if (!cursor) /* No suitable instance beyond the boundary found? Then we are done! */
|
|
break;
|
|
|
|
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 (t->min_version && strverscmp_improved(t->min_version, cursor) > 0)
|
|
extra_flags |= UPDATE_OBSOLETE;
|
|
|
|
if (strv_contains(t->protected_versions, cursor))
|
|
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 */
|
|
continue;
|
|
|
|
/* See if we already have this update set in our table */
|
|
for (size_t i = 0; i < c->n_update_sets; i++) {
|
|
if (strverscmp_improved(c->update_sets[i]->version, cursor) != 0)
|
|
continue;
|
|
|
|
/* We only store the instances we found first, but we remember we also found it again */
|
|
c->update_sets[i]->flags |= flags | extra_flags;
|
|
exists = true;
|
|
newest_found = true;
|
|
break;
|
|
}
|
|
|
|
if (exists)
|
|
continue;
|
|
|
|
/* Doesn't exist yet, let's add it */
|
|
if (!GREEDY_REALLOC(c->update_sets, c->n_update_sets + 1))
|
|
return log_oom();
|
|
|
|
us = new(UpdateSet, 1);
|
|
if (!us)
|
|
return log_oom();
|
|
|
|
*us = (UpdateSet) {
|
|
.flags = flags | (newest_found ? 0 : UPDATE_NEWEST) | extra_flags,
|
|
.version = TAKE_PTR(cursor),
|
|
.instances = TAKE_PTR(cursor_instances),
|
|
.n_instances = c->n_transfers,
|
|
};
|
|
|
|
c->update_sets[c->n_update_sets++] = us;
|
|
|
|
newest_found = true;
|
|
|
|
/* Remember which one is the newest installed */
|
|
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" */
|
|
if ((us->flags & (UPDATE_NEWEST|UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE)) == (UPDATE_NEWEST|UPDATE_AVAILABLE))
|
|
c->candidate = us;
|
|
}
|
|
|
|
/* 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)
|
|
c->candidate = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int context_discover_update_sets(Context *c) {
|
|
int r;
|
|
|
|
assert(c);
|
|
|
|
log_info("Determining installed update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
|
|
|
r = context_discover_update_sets_by_flag(c, UPDATE_INSTALLED);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
log_info("Determining available update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
|
|
|
r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
typesafe_qsort(c->update_sets, c->n_update_sets, update_set_cmp);
|
|
return 0;
|
|
}
|
|
|
|
static const char *update_set_flags_to_string(UpdateSetFlags flags) {
|
|
|
|
switch ((unsigned) flags) {
|
|
|
|
case 0:
|
|
return "n/a";
|
|
|
|
case UPDATE_INSTALLED|UPDATE_NEWEST:
|
|
case UPDATE_INSTALLED|UPDATE_NEWEST|UPDATE_PROTECTED:
|
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST:
|
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
|
return "current";
|
|
|
|
case UPDATE_AVAILABLE|UPDATE_NEWEST:
|
|
case UPDATE_AVAILABLE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
|
return "candidate";
|
|
|
|
case UPDATE_INSTALLED:
|
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE:
|
|
return "installed";
|
|
|
|
case UPDATE_INSTALLED|UPDATE_PROTECTED:
|
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_PROTECTED:
|
|
return "protected";
|
|
|
|
case UPDATE_AVAILABLE:
|
|
case UPDATE_AVAILABLE|UPDATE_PROTECTED:
|
|
return "available";
|
|
|
|
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_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
|
return "current+obsolete";
|
|
|
|
case UPDATE_INSTALLED|UPDATE_OBSOLETE:
|
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE:
|
|
return "installed+obsolete";
|
|
|
|
case UPDATE_INSTALLED|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
|
case UPDATE_INSTALLED|UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
|
return "protected+obsolete";
|
|
|
|
case UPDATE_AVAILABLE|UPDATE_OBSOLETE:
|
|
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_PROTECTED:
|
|
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST:
|
|
case UPDATE_AVAILABLE|UPDATE_OBSOLETE|UPDATE_NEWEST|UPDATE_PROTECTED:
|
|
return "available+obsolete";
|
|
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
}
|
|
|
|
|
|
static int context_show_table(Context *c) {
|
|
_cleanup_(table_unrefp) Table *t = NULL;
|
|
int r;
|
|
|
|
assert(c);
|
|
|
|
t = table_new("", "version", "installed", "available", "assessment");
|
|
if (!t)
|
|
return log_oom();
|
|
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 0), 100);
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 2), 50);
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 3), 50);
|
|
|
|
for (size_t i = 0; i < c->n_update_sets; i++) {
|
|
UpdateSet *us = c->update_sets[i];
|
|
const char *color;
|
|
|
|
color = update_set_flags_to_color(us->flags);
|
|
|
|
r = table_add_many(t,
|
|
TABLE_STRING, update_set_flags_to_glyph(us->flags),
|
|
TABLE_SET_COLOR, color,
|
|
TABLE_STRING, us->version,
|
|
TABLE_SET_COLOR, color,
|
|
TABLE_STRING, special_glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_INSTALLED)),
|
|
TABLE_SET_COLOR, color,
|
|
TABLE_STRING, special_glyph_check_mark_space(FLAGS_SET(us->flags, UPDATE_AVAILABLE)),
|
|
TABLE_SET_COLOR, color,
|
|
TABLE_STRING, update_set_flags_to_string(us->flags),
|
|
TABLE_SET_COLOR, color);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
}
|
|
|
|
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
|
|
}
|
|
|
|
static UpdateSet *context_update_set_by_version(Context *c, const char *version) {
|
|
assert(c);
|
|
assert(version);
|
|
|
|
for (size_t i = 0; i < c->n_update_sets; i++)
|
|
if (streq(c->update_sets[i]->version, version))
|
|
return c->update_sets[i];
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int context_show_version(Context *c, const char *version) {
|
|
bool show_fs_columns = false, show_partition_columns = false,
|
|
have_fs_attributes = false, have_partition_attributes = false,
|
|
have_size = false, have_tries = false, have_no_auto = false,
|
|
have_read_only = false, have_growfs = false, have_sha256 = false;
|
|
_cleanup_(table_unrefp) Table *t = NULL;
|
|
UpdateSet *us;
|
|
int r;
|
|
|
|
assert(c);
|
|
assert(version);
|
|
|
|
us = context_update_set_by_version(c, version);
|
|
if (!us)
|
|
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Update '%s' not found.", version);
|
|
|
|
if (arg_json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
|
|
(void) pager_open(arg_pager_flags);
|
|
|
|
if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
|
|
printf("%s%s%s Version: %s\n"
|
|
" State: %s%s%s\n"
|
|
"Installed: %s%s\n"
|
|
"Available: %s%s\n"
|
|
"Protected: %s%s%s\n"
|
|
" Obsolete: %s%s%s\n\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)" : "",
|
|
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());
|
|
|
|
|
|
t = table_new("type", "path", "ptuuid", "ptflags", "mtime", "mode", "size", "tries-done", "tries-left", "noauto", "ro", "growfs", "sha256");
|
|
if (!t)
|
|
return log_oom();
|
|
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 3), 100);
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 6), 100);
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 7), 100);
|
|
(void) table_set_align_percent(t, table_get_cell(t, 0, 8), 100);
|
|
table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
|
|
|
|
/* Determine if the target will make use of partition/fs attributes for any of the transfers */
|
|
for (size_t n = 0; n < c->n_transfers; n++) {
|
|
Transfer *tr = c->transfers[n];
|
|
|
|
if (tr->target.type == RESOURCE_PARTITION)
|
|
show_partition_columns = true;
|
|
if (RESOURCE_IS_FILESYSTEM(tr->target.type))
|
|
show_fs_columns = true;
|
|
}
|
|
|
|
for (size_t n = 0; n < us->n_instances; n++) {
|
|
Instance *i = us->instances[n];
|
|
|
|
r = table_add_many(t,
|
|
TABLE_STRING, resource_type_to_string(i->resource->type),
|
|
TABLE_PATH, i->path);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.partition_uuid_set) {
|
|
have_partition_attributes = true;
|
|
r = table_add_cell(t, NULL, TABLE_UUID, &i->metadata.partition_uuid);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.partition_flags_set) {
|
|
have_partition_attributes = true;
|
|
r = table_add_cell(t, NULL, TABLE_UINT64_HEX, &i->metadata.partition_flags);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.mtime != USEC_INFINITY) {
|
|
have_fs_attributes = true;
|
|
r = table_add_cell(t, NULL, TABLE_TIMESTAMP, &i->metadata.mtime);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.mode != MODE_INVALID) {
|
|
have_fs_attributes = true;
|
|
r = table_add_cell(t, NULL, TABLE_MODE, &i->metadata.mode);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.size != UINT64_MAX) {
|
|
have_size = true;
|
|
r = table_add_cell(t, NULL, TABLE_SIZE, &i->metadata.size);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.tries_done != UINT64_MAX) {
|
|
have_tries = true;
|
|
r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_done);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.tries_left != UINT64_MAX) {
|
|
have_tries = true;
|
|
r = table_add_cell(t, NULL, TABLE_UINT64, &i->metadata.tries_left);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.no_auto >= 0) {
|
|
bool b;
|
|
|
|
have_no_auto = true;
|
|
b = i->metadata.no_auto;
|
|
r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
if (i->metadata.read_only >= 0) {
|
|
bool b;
|
|
|
|
have_read_only = true;
|
|
b = i->metadata.read_only;
|
|
r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.growfs >= 0) {
|
|
bool b;
|
|
|
|
have_growfs = true;
|
|
b = i->metadata.growfs;
|
|
r = table_add_cell(t, NULL, TABLE_BOOLEAN, &b);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
|
|
if (i->metadata.sha256sum_set) {
|
|
_cleanup_free_ char *formatted = NULL;
|
|
|
|
have_sha256 = true;
|
|
|
|
formatted = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
|
|
if (!formatted)
|
|
return log_oom();
|
|
|
|
r = table_add_cell(t, NULL, TABLE_STRING, formatted);
|
|
} else
|
|
r = table_add_cell(t, NULL, TABLE_EMPTY, NULL);
|
|
if (r < 0)
|
|
return table_log_add_error(r);
|
|
}
|
|
|
|
/* Hide the fs/partition columns if we don't have any data to show there */
|
|
if (!have_fs_attributes)
|
|
show_fs_columns = false;
|
|
if (!have_partition_attributes)
|
|
show_partition_columns = false;
|
|
|
|
if (!show_partition_columns)
|
|
(void) table_hide_column_from_display(t, 2, 3);
|
|
if (!show_fs_columns)
|
|
(void) table_hide_column_from_display(t, 4, 5);
|
|
if (!have_size)
|
|
(void) table_hide_column_from_display(t, 6);
|
|
if (!have_tries)
|
|
(void) table_hide_column_from_display(t, 7, 8);
|
|
if (!have_no_auto)
|
|
(void) table_hide_column_from_display(t, 9);
|
|
if (!have_read_only)
|
|
(void) table_hide_column_from_display(t, 10);
|
|
if (!have_growfs)
|
|
(void) table_hide_column_from_display(t, 11);
|
|
if (!have_sha256)
|
|
(void) table_hide_column_from_display(t, 12);
|
|
|
|
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
|
|
}
|
|
|
|
static int context_vacuum(
|
|
Context *c,
|
|
uint64_t space,
|
|
const char *extra_protected_version) {
|
|
|
|
int r, count = 0;
|
|
|
|
assert(c);
|
|
|
|
if (space == 0)
|
|
log_info("Making room%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
|
else
|
|
log_info("Making room for %" PRIu64 " updates%s", space, special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
|
|
|
for (size_t i = 0; i < c->n_transfers; i++) {
|
|
r = transfer_vacuum(c->transfers[i], space, extra_protected_version);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
count = MAX(count, r);
|
|
}
|
|
|
|
if (count > 0)
|
|
log_info("Removed %i instances.", count);
|
|
else
|
|
log_info("Removed no instances.");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int context_make_offline(Context **ret, const char *node) {
|
|
_cleanup_(context_freep) Context* context = NULL;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
/* Allocates a context object and initializes everything we can initialize offline, i.e. without
|
|
* checking on the update source (i.e. the Internet) what versions are available */
|
|
|
|
context = context_new();
|
|
if (!context)
|
|
return log_oom();
|
|
|
|
r = context_read_definitions(context, arg_definitions, arg_component, arg_root, node);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = context_load_installed_instances(context);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
*ret = TAKE_PTR(context);
|
|
return 0;
|
|
}
|
|
|
|
static int context_make_online(Context **ret, const char *node) {
|
|
_cleanup_(context_freep) Context* context = NULL;
|
|
int r;
|
|
|
|
assert(ret);
|
|
|
|
/* Like context_make_offline(), but also communicates with the update source looking for new
|
|
* versions. */
|
|
|
|
r = context_make_offline(&context, node);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = context_load_available_instances(context);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = context_discover_update_sets(context);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
*ret = TAKE_PTR(context);
|
|
return 0;
|
|
}
|
|
|
|
static int context_apply(
|
|
Context *c,
|
|
const char *version,
|
|
UpdateSet **ret_applied) {
|
|
|
|
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.");
|
|
|
|
if (ret_applied)
|
|
*ret_applied = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
us = c->candidate;
|
|
}
|
|
|
|
if (FLAGS_SET(us->flags, UPDATE_INSTALLED)) {
|
|
log_info("Selected update '%s' is already installed. Skipping update.", us->version);
|
|
|
|
if (ret_applied)
|
|
*ret_applied = NULL;
|
|
|
|
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)
|
|
log_notice("Selected update '%s' is older than newest installed version, proceeding anyway.", us->version);
|
|
|
|
log_info("Selected update '%s' for install.", us->version);
|
|
|
|
(void) sd_notifyf(false,
|
|
"STATUS=Making room for '%s'.", us->version);
|
|
|
|
/* Let's make some room. We make sure for each transfer we have one free space to fill. While
|
|
* removing stuff we'll protect the version we are trying to acquire. Why that? Maybe an earlier
|
|
* download succeeded already, in which case we shouldn't remove it just to acquire it again */
|
|
r = context_vacuum(
|
|
c,
|
|
/* space = */ 1,
|
|
/* extra_protected_version = */ us->version);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (arg_sync)
|
|
sync();
|
|
|
|
(void) sd_notifyf(false,
|
|
"STATUS=Updating to '%s'.\n", 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++) {
|
|
r = transfer_acquire_instance(c->transfers[i], us->instances[i]);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
if (arg_sync)
|
|
sync();
|
|
|
|
for (size_t i = 0; i < c->n_transfers; i++) {
|
|
r = transfer_install_instance(c->transfers[i], us->instances[i], arg_root);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
log_info("%s Successfully installed update '%s'.", special_glyph(SPECIAL_GLYPH_SPARKLES), us->version);
|
|
|
|
if (ret_applied)
|
|
*ret_applied = us;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int reboot_now(void) {
|
|
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
|
_cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL;
|
|
int r;
|
|
|
|
r = sd_bus_open_system(&bus);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to open bus connection: %m");
|
|
|
|
r = bus_call_method(bus, bus_login_mgr, "RebootWithFlags", &error, NULL, "t",
|
|
(uint64_t) SD_LOGIND_ROOT_CHECK_INHIBITORS);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to issue reboot request: %s", bus_error_message(&error, r));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int process_image(
|
|
bool ro,
|
|
char **ret_mounted_dir,
|
|
LoopDevice **ret_loop_device) {
|
|
|
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
|
_cleanup_(umount_and_freep) char *mounted_dir = NULL;
|
|
int r;
|
|
|
|
assert(ret_mounted_dir);
|
|
assert(ret_loop_device);
|
|
|
|
if (!arg_image)
|
|
return 0;
|
|
|
|
assert(!arg_root);
|
|
|
|
r = mount_image_privately_interactively(
|
|
arg_image,
|
|
arg_image_policy,
|
|
(ro ? DISSECT_IMAGE_READ_ONLY : 0) |
|
|
DISSECT_IMAGE_FSCK |
|
|
DISSECT_IMAGE_MKDIR |
|
|
DISSECT_IMAGE_GROWFS |
|
|
DISSECT_IMAGE_RELAX_VAR_CHECK |
|
|
DISSECT_IMAGE_USR_NO_ROOT |
|
|
DISSECT_IMAGE_GENERIC_ROOT |
|
|
DISSECT_IMAGE_REQUIRE_ROOT |
|
|
DISSECT_IMAGE_ALLOW_USERSPACE_VERITY,
|
|
&mounted_dir,
|
|
/* ret_dir_fd= */ NULL,
|
|
&loop_device);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
arg_root = strdup(mounted_dir);
|
|
if (!arg_root)
|
|
return log_oom();
|
|
|
|
*ret_mounted_dir = TAKE_PTR(mounted_dir);
|
|
*ret_loop_device = TAKE_PTR(loop_device);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verb_list(int argc, char **argv, void *userdata) {
|
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
|
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
|
_cleanup_(context_freep) Context* context = NULL;
|
|
const char *version;
|
|
int r;
|
|
|
|
assert(argc <= 2);
|
|
version = argc >= 2 ? argv[1] : NULL;
|
|
|
|
r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = context_make_online(&context, loop_device ? loop_device->node : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (version)
|
|
return context_show_version(context, version);
|
|
else
|
|
return context_show_table(context);
|
|
}
|
|
|
|
static int verb_check_new(int argc, char **argv, void *userdata) {
|
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
|
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
|
_cleanup_(context_freep) Context* context = NULL;
|
|
int r;
|
|
|
|
assert(argc <= 1);
|
|
|
|
r = process_image(/* ro= */ true, &mounted_dir, &loop_device);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = context_make_online(&context, loop_device ? loop_device->node : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!context->candidate) {
|
|
log_debug("No candidate found.");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
puts(context->candidate->version);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
static int verb_vacuum(int argc, char **argv, void *userdata) {
|
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
|
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
|
_cleanup_(context_freep) Context* context = NULL;
|
|
int r;
|
|
|
|
assert(argc <= 1);
|
|
|
|
r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = context_make_offline(&context, loop_device ? loop_device->node : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return context_vacuum(context, 0, NULL);
|
|
}
|
|
|
|
static int verb_update(int argc, char **argv, void *userdata) {
|
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
|
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
|
_cleanup_(context_freep) Context* context = NULL;
|
|
_cleanup_free_ char *booted_version = NULL;
|
|
UpdateSet *applied = NULL;
|
|
const char *version;
|
|
int r;
|
|
|
|
assert(argc <= 2);
|
|
version = argc >= 2 ? argv[1] : NULL;
|
|
|
|
if (arg_reboot) {
|
|
/* If automatic reboot on completion is requested, let's first determine the currently booted image */
|
|
|
|
r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to parse /etc/os-release: %m");
|
|
if (!booted_version)
|
|
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field.");
|
|
}
|
|
|
|
r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = context_make_online(&context, loop_device ? loop_device->node : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = context_apply(context, version, &applied);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (r > 0 && arg_reboot) {
|
|
assert(applied);
|
|
assert(booted_version);
|
|
|
|
if (strverscmp_improved(applied->version, booted_version) > 0) {
|
|
log_notice("Newly installed version is newer than booted version, rebooting.");
|
|
return reboot_now();
|
|
}
|
|
|
|
log_info("Booted version is newer or identical to newly installed version, not rebooting.");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verb_pending_or_reboot(int argc, char **argv, void *userdata) {
|
|
_cleanup_(context_freep) Context* context = NULL;
|
|
_cleanup_free_ char *booted_version = NULL;
|
|
int r;
|
|
|
|
assert(argc == 1);
|
|
|
|
if (arg_image || arg_root)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]);
|
|
|
|
r = context_make_offline(&context, NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
log_info("Determining installed update sets%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS));
|
|
|
|
r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED);
|
|
if (r < 0)
|
|
return r;
|
|
if (!context->newest_installed)
|
|
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions.");
|
|
|
|
r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version);
|
|
if (r < 0) /* yes, arg_root is NULL here, but we have to pass something, and it's a lot more readable
|
|
* if we see what the first argument is about */
|
|
return log_error_errno(r, "Failed to parse /etc/os-release: %m");
|
|
if (!booted_version)
|
|
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION= field.");
|
|
|
|
r = strverscmp_improved(context->newest_installed->version, booted_version);
|
|
if (r > 0) {
|
|
log_notice("Newest installed version '%s' is newer than booted version '%s'.%s",
|
|
context->newest_installed->version, booted_version,
|
|
streq(argv[0], "pending") ? " Reboot recommended." : "");
|
|
|
|
if (streq(argv[0], "reboot"))
|
|
return reboot_now();
|
|
|
|
return EXIT_SUCCESS;
|
|
} else if (r == 0)
|
|
log_info("Newest installed version '%s' matches booted version '%s'.",
|
|
context->newest_installed->version, booted_version);
|
|
else
|
|
log_warning("Newest installed version '%s' is older than booted version '%s'.",
|
|
context->newest_installed->version, booted_version);
|
|
|
|
if (streq(argv[0], "pending")) /* When called as 'pending' tell the caller via failure exit code that there's nothing newer installed */
|
|
return EXIT_FAILURE;
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
static int component_name_valid(const char *c) {
|
|
_cleanup_free_ char *j = NULL;
|
|
|
|
/* See if the specified string enclosed in the directory prefix+suffix would be a valid file name */
|
|
|
|
if (isempty(c))
|
|
return false;
|
|
|
|
if (string_has_cc(c, NULL))
|
|
return false;
|
|
|
|
if (!utf8_is_valid(c))
|
|
return false;
|
|
|
|
j = strjoin("sysupdate.", c, ".d");
|
|
if (!j)
|
|
return -ENOMEM;
|
|
|
|
return filename_is_valid(j);
|
|
}
|
|
|
|
static int verb_components(int argc, char **argv, void *userdata) {
|
|
_cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
|
|
_cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
|
|
_cleanup_set_free_ Set *names = NULL;
|
|
_cleanup_free_ char **z = NULL; /* We use simple free() rather than strv_free() here, since set_free() will free the strings for us */
|
|
char **l = CONF_PATHS_STRV("");
|
|
bool has_default_component = false;
|
|
int r;
|
|
|
|
assert(argc <= 1);
|
|
|
|
r = process_image(/* ro= */ false, &mounted_dir, &loop_device);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
STRV_FOREACH(i, l) {
|
|
_cleanup_closedir_ DIR *d = NULL;
|
|
_cleanup_free_ char *p = NULL;
|
|
|
|
r = chase_and_opendir(*i, arg_root, CHASE_PREFIX_ROOT, &p, &d);
|
|
if (r == -ENOENT)
|
|
continue;
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to open directory '%s': %m", *i);
|
|
|
|
for (;;) {
|
|
_cleanup_free_ char *n = NULL;
|
|
struct dirent *de;
|
|
const char *e, *a;
|
|
|
|
de = readdir_ensure_type(d);
|
|
if (!de) {
|
|
if (errno != 0)
|
|
return log_error_errno(errno, "Failed to enumerate directory '%s': %m", p);
|
|
|
|
break;
|
|
}
|
|
|
|
if (de->d_type != DT_DIR)
|
|
continue;
|
|
|
|
if (dot_or_dot_dot(de->d_name))
|
|
continue;
|
|
|
|
if (streq(de->d_name, "sysupdate.d")) {
|
|
has_default_component = true;
|
|
continue;
|
|
}
|
|
|
|
e = startswith(de->d_name, "sysupdate.");
|
|
if (!e)
|
|
continue;
|
|
|
|
a = endswith(e, ".d");
|
|
if (!a)
|
|
continue;
|
|
|
|
n = strndup(e, a - e);
|
|
if (!n)
|
|
return log_oom();
|
|
|
|
r = component_name_valid(n);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Unable to validate component name: %m");
|
|
if (r == 0)
|
|
continue;
|
|
|
|
r = set_ensure_consume(&names, &string_hash_ops_free, TAKE_PTR(n));
|
|
if (r < 0 && r != -EEXIST)
|
|
return log_error_errno(r, "Failed to add component to set: %m");
|
|
}
|
|
}
|
|
|
|
if (!has_default_component && set_isempty(names)) {
|
|
log_info("No components defined.");
|
|
return 0;
|
|
}
|
|
|
|
z = set_get_strv(names);
|
|
if (!z)
|
|
return log_oom();
|
|
|
|
strv_sort(z);
|
|
|
|
if (has_default_component)
|
|
printf("%s<default>%s\n",
|
|
ansi_highlight(), ansi_normal());
|
|
|
|
STRV_FOREACH(i, z)
|
|
puts(*i);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int verb_help(int argc, char **argv, void *userdata) {
|
|
_cleanup_free_ char *link = NULL;
|
|
int r;
|
|
|
|
r = terminal_urlify_man("systemd-sysupdate", "8", &link);
|
|
if (r < 0)
|
|
return log_oom();
|
|
|
|
printf("%1$s [OPTIONS...] [VERSION]\n"
|
|
"\n%5$sUpdate OS images.%6$s\n"
|
|
"\n%3$sCommands:%4$s\n"
|
|
" list [VERSION] Show installed and available versions\n"
|
|
" check-new Check if there's a new version available\n"
|
|
" update [VERSION] Install new version now\n"
|
|
" vacuum Make room, by deleting old versions\n"
|
|
" pending Report whether a newer version is installed than\n"
|
|
" currently booted\n"
|
|
" reboot Reboot if a newer version is installed than booted\n"
|
|
" components Show list of components\n"
|
|
" -h --help Show this help\n"
|
|
" --version Show package version\n"
|
|
"\n%3$sOptions:%4$s\n"
|
|
" -C --component=NAME Select component to update\n"
|
|
" --definitions=DIR Find transfer definitions in specified directory\n"
|
|
" --root=PATH Operate on an alternate filesystem root\n"
|
|
" --image=PATH Operate on disk image as filesystem root\n"
|
|
" --image-policy=POLICY\n"
|
|
" Specify disk image dissection policy\n"
|
|
" -m --instances-max=INT How many instances to maintain\n"
|
|
" --sync=BOOL Controls whether to sync data to disk\n"
|
|
" --verify=BOOL Force signature verification on or off\n"
|
|
" --reboot Reboot after updating to newer version\n"
|
|
" --no-pager Do not pipe output into a pager\n"
|
|
" --no-legend Do not show the headers and footers\n"
|
|
" --json=pretty|short|off\n"
|
|
" Generate JSON output\n"
|
|
"\nSee the %2$s for details.\n",
|
|
program_invocation_short_name,
|
|
link,
|
|
ansi_underline(),
|
|
ansi_normal(),
|
|
ansi_highlight(),
|
|
ansi_normal());
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_argv(int argc, char *argv[]) {
|
|
|
|
enum {
|
|
ARG_VERSION = 0x100,
|
|
ARG_NO_PAGER,
|
|
ARG_NO_LEGEND,
|
|
ARG_SYNC,
|
|
ARG_DEFINITIONS,
|
|
ARG_JSON,
|
|
ARG_ROOT,
|
|
ARG_IMAGE,
|
|
ARG_IMAGE_POLICY,
|
|
ARG_REBOOT,
|
|
ARG_VERIFY,
|
|
};
|
|
|
|
static const struct option options[] = {
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "version", no_argument, NULL, ARG_VERSION },
|
|
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
|
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
|
|
{ "definitions", required_argument, NULL, ARG_DEFINITIONS },
|
|
{ "instances-max", required_argument, NULL, 'm' },
|
|
{ "sync", required_argument, NULL, ARG_SYNC },
|
|
{ "json", required_argument, NULL, ARG_JSON },
|
|
{ "root", required_argument, NULL, ARG_ROOT },
|
|
{ "image", required_argument, NULL, ARG_IMAGE },
|
|
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
|
|
{ "reboot", no_argument, NULL, ARG_REBOOT },
|
|
{ "component", required_argument, NULL, 'C' },
|
|
{ "verify", required_argument, NULL, ARG_VERIFY },
|
|
{}
|
|
};
|
|
|
|
int c, r;
|
|
|
|
assert(argc >= 0);
|
|
assert(argv);
|
|
|
|
while ((c = getopt_long(argc, argv, "hm:C:", options, NULL)) >= 0) {
|
|
|
|
switch (c) {
|
|
|
|
case 'h':
|
|
return verb_help(0, NULL, NULL);
|
|
|
|
case ARG_VERSION:
|
|
return version();
|
|
|
|
case ARG_NO_PAGER:
|
|
arg_pager_flags |= PAGER_DISABLE;
|
|
break;
|
|
|
|
case ARG_NO_LEGEND:
|
|
arg_legend = false;
|
|
break;
|
|
|
|
case 'm':
|
|
r = safe_atou64(optarg, &arg_instances_max);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg);
|
|
|
|
break;
|
|
|
|
case ARG_SYNC:
|
|
r = parse_boolean_argument("--sync=", optarg, &arg_sync);
|
|
if (r < 0)
|
|
return r;
|
|
break;
|
|
|
|
case ARG_DEFINITIONS:
|
|
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_definitions);
|
|
if (r < 0)
|
|
return r;
|
|
break;
|
|
|
|
case ARG_JSON:
|
|
r = parse_json_argument(optarg, &arg_json_format_flags);
|
|
if (r <= 0)
|
|
return r;
|
|
|
|
break;
|
|
|
|
case ARG_ROOT:
|
|
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
|
|
if (r < 0)
|
|
return r;
|
|
break;
|
|
|
|
case ARG_IMAGE:
|
|
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
|
|
if (r < 0)
|
|
return r;
|
|
break;
|
|
|
|
case ARG_IMAGE_POLICY:
|
|
r = parse_image_policy_argument(optarg, &arg_image_policy);
|
|
if (r < 0)
|
|
return r;
|
|
break;
|
|
|
|
case ARG_REBOOT:
|
|
arg_reboot = true;
|
|
break;
|
|
|
|
case 'C':
|
|
if (isempty(optarg)) {
|
|
arg_component = mfree(arg_component);
|
|
break;
|
|
}
|
|
|
|
r = component_name_valid(optarg);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to determine if component name is valid: %m");
|
|
if (r == 0)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg);
|
|
|
|
r = free_and_strdup_warn(&arg_component, optarg);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
break;
|
|
|
|
case ARG_VERIFY: {
|
|
bool b;
|
|
|
|
r = parse_boolean_argument("--verify=", optarg, &b);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
arg_verify = b;
|
|
break;
|
|
}
|
|
|
|
case '?':
|
|
return -EINVAL;
|
|
|
|
default:
|
|
assert_not_reached();
|
|
}
|
|
}
|
|
|
|
if (arg_image && arg_root)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
|
|
|
|
if ((arg_image || arg_root) && arg_reboot)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --reboot switch may not be combined with --root= or --image=.");
|
|
|
|
if (arg_definitions && arg_component)
|
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int sysupdate_main(int argc, char *argv[]) {
|
|
|
|
static const Verb verbs[] = {
|
|
{ "list", VERB_ANY, 2, VERB_DEFAULT, verb_list },
|
|
{ "components", VERB_ANY, 1, 0, verb_components },
|
|
{ "check-new", VERB_ANY, 1, 0, verb_check_new },
|
|
{ "update", VERB_ANY, 2, 0, verb_update },
|
|
{ "vacuum", VERB_ANY, 1, 0, verb_vacuum },
|
|
{ "reboot", 1, 1, 0, verb_pending_or_reboot },
|
|
{ "pending", 1, 1, 0, verb_pending_or_reboot },
|
|
{ "help", VERB_ANY, 1, 0, verb_help },
|
|
{}
|
|
};
|
|
|
|
return dispatch_verb(argc, argv, verbs, NULL);
|
|
}
|
|
|
|
static int run(int argc, char *argv[]) {
|
|
int r;
|
|
|
|
log_setup();
|
|
|
|
r = parse_argv(argc, argv);
|
|
if (r <= 0)
|
|
return r;
|
|
|
|
return sysupdate_main(argc, argv);
|
|
}
|
|
|
|
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
|