Merge pull request #33398 from AdrianVovk/sysupdate-optional

sysupdate: Add support for optional features
This commit is contained in:
Lennart Poettering
2024-10-21 12:36:43 +02:00
committed by GitHub
21 changed files with 1723 additions and 162 deletions
+1
View File
@@ -2,6 +2,7 @@
systemd_sysupdate_sources = files(
'sysupdate-cache.c',
'sysupdate-feature.c',
'sysupdate-instance.c',
'sysupdate-partition.c',
'sysupdate-pattern.c',
@@ -78,6 +78,18 @@
send_interface="org.freedesktop.sysupdate1.Target"
send_member="GetVersion"/>
<allow send_destination="org.freedesktop.sysupdate1"
send_interface="org.freedesktop.sysupdate1.Target"
send_member="ListFeatures"/>
<allow send_destination="org.freedesktop.sysupdate1"
send_interface="org.freedesktop.sysupdate1.Target"
send_member="DescribeFeature"/>
<allow send_destination="org.freedesktop.sysupdate1"
send_interface="org.freedesktop.sysupdate1.Target"
send_member="SetFeatureEnabled"/>
<allow send_destination="org.freedesktop.sysupdate1"
send_interface="org.freedesktop.sysupdate1.Job"
send_member="Cancel"/>
@@ -71,4 +71,14 @@
</defaults>
</action>
<action id="org.freedesktop.sysupdate1.manage-features">
<description gettext-domain="systemd">Manage optional features</description>
<message gettext-domain="systemd">Authentication is required to manage optional features</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
+121
View File
@@ -0,0 +1,121 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "conf-parser.h"
#include "hash-funcs.h"
#include "path-util.h"
#include "sysupdate-feature.h"
#include "sysupdate.h"
#include "web-util.h"
static Feature *feature_free(Feature *f) {
if (!f)
return NULL;
free(f->id);
free(f->description);
free(f->documentation);
free(f->appstream);
return mfree(f);
}
Feature *feature_new(void) {
Feature *f;
f = new(Feature, 1);
if (!f)
return NULL;
*f = (Feature) {
.n_ref = 1,
};
return f;
}
DEFINE_TRIVIAL_REF_UNREF_FUNC(Feature, feature, feature_free);
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(feature_hash_ops,
char, string_hash_func, string_compare_func,
Feature, feature_unref);
static int config_parse_url_specifiers(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
char **s = ASSERT_PTR(data);
_cleanup_free_ char *resolved = NULL;
int r;
assert(rvalue);
if (isempty(rvalue)) {
*s = mfree(*s);
return 0;
}
r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue);
return 0;
}
if (!http_url_is_valid(resolved)) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
"%s= URL is not valid, ignoring: %s", lvalue, rvalue);
return 0;
}
return free_and_replace(*s, resolved);
}
int feature_read_definition(Feature *f, const char *path, const char *const *dirs) {
assert(f);
ConfigTableItem table[] = {
{ "Feature", "Description", config_parse_string, 0, &f->description },
{ "Feature", "Documentation", config_parse_url_specifiers, 0, &f->documentation },
{ "Feature", "AppStream", config_parse_url_specifiers, 0, &f->appstream },
{ "Feature", "Enabled", config_parse_bool, 0, &f->enabled },
{}
};
_cleanup_free_ char *filename = NULL;
int r;
assert(path);
assert(dirs);
r = path_extract_filename(path, &filename);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
r = config_parse_many(
STRV_MAKE_CONST(path),
dirs,
strjoina(filename, ".d"),
arg_root,
"Feature\0",
config_item_table_lookup, table,
CONFIG_PARSE_WARN,
/* userdata= */ NULL,
/* stats_by_path= */ NULL,
/* drop_in_files= */ NULL);
if (r < 0)
return r;
*ASSERT_PTR(endswith(filename, ".feature")) = 0; /* Remove the file extension */
f->id = TAKE_PTR(filename);
return 0;
}
+27
View File
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "hash-funcs.h"
#include "macro.h"
#include "sysupdate-transfer.h"
typedef struct Feature {
unsigned n_ref;
char *id;
char *description;
char *documentation;
char *appstream;
bool enabled;
} Feature;
Feature *feature_new(void);
Feature *feature_ref(Feature *f);
Feature *feature_unref(Feature *f);
DEFINE_TRIVIAL_CLEANUP_FUNC(Feature*, feature_unref);
extern const struct hash_ops feature_hash_ops;
int feature_read_definition(Feature *f, const char *path, const char *const *conf_file_dirs);
+71 -20
View File
@@ -28,6 +28,7 @@
#include "stdio-util.h"
#include "strv.h"
#include "sync-util.h"
#include "sysupdate-feature.h"
#include "sysupdate-pattern.h"
#include "sysupdate-resource.h"
#include "sysupdate-transfer.h"
@@ -44,12 +45,16 @@ Transfer* transfer_free(Transfer *t) {
t->temporary_path = rm_rf_subvolume_and_free(t->temporary_path);
free(t->definition_path);
free(t->id);
free(t->min_version);
strv_free(t->protected_versions);
free(t->current_symlink);
free(t->final_path);
strv_free(t->features);
strv_free(t->requisite_features);
strv_free(t->changelog);
strv_free(t->appstream);
@@ -93,12 +98,6 @@ Transfer* transfer_new(Context *ctx) {
return t;
}
static const Specifier specifier_table[] = {
COMMON_SYSTEM_SPECIFIERS,
COMMON_TMP_SPECIFIERS,
{}
};
static int config_parse_protect_version(
const char *unit,
const char *filename,
@@ -466,11 +465,33 @@ static int config_parse_partition_flags(
return 0;
}
int transfer_read_definition(Transfer *t, const char *path) {
int r;
static bool transfer_decide_if_enabled(Transfer *t, Hashmap *known_features) {
assert(t);
/* Requisite feature disabled -> transfer disabled */
STRV_FOREACH(id, t->requisite_features) {
Feature *f = hashmap_get(known_features, *id);
if (!f || !f->enabled) /* missing features are implicitly disabled */
return false;
}
/* No features defined -> transfer implicitly enabled */
if (strv_isempty(t->features))
return true;
/* At least one feature enabled -> transfer enabled */
STRV_FOREACH(id, t->features) {
Feature *f = hashmap_get(known_features, *id);
if (f && f->enabled)
return true;
}
/* All listed features disabled -> transfer disabled */
return false;
}
int transfer_read_definition(Transfer *t, const char *path, const char **dirs, Hashmap *known_features) {
assert(t);
assert(path);
ConfigTableItem table[] = {
{ "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version },
@@ -478,6 +499,8 @@ int transfer_read_definition(Transfer *t, const char *path) {
{ "Transfer", "Verify", config_parse_bool, 0, &t->verify },
{ "Transfer", "ChangeLog", config_parse_url_specifiers, 0, &t->changelog },
{ "Transfer", "AppStream", config_parse_url_specifiers, 0, &t->appstream },
{ "Transfer", "Features", config_parse_strv, 0, &t->features },
{ "Transfer", "RequisiteFeatures", config_parse_strv, 0, &t->requisite_features },
{ "Source", "Type", config_parse_resource_type, 0, &t->source.type },
{ "Source", "Path", config_parse_resource_path, 0, &t->source },
{ "Source", "PathRelativeTo", config_parse_resource_path_relto, 0, &t->source.path_relative_to },
@@ -501,17 +524,39 @@ int transfer_read_definition(Transfer *t, const char *path) {
{}
};
r = config_parse(NULL, path, NULL,
"Transfer\0"
"Source\0"
"Target\0",
config_item_table_lookup, table,
CONFIG_PARSE_WARN,
t,
NULL);
_cleanup_free_ char *filename = NULL;
char *e;
int r;
assert(path);
assert(dirs);
r = path_extract_filename(path, &filename);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", path);
r = config_parse_many(
STRV_MAKE_CONST(path),
dirs,
strjoina(filename, ".d"),
arg_root,
"Transfer\0"
"Source\0"
"Target\0",
config_item_table_lookup, table,
CONFIG_PARSE_WARN,
/* userdata= */ NULL,
/* stats_by_path= */ NULL,
/* drop_in_files= */ NULL);
if (r < 0)
return r;
e = ASSERT_PTR(endswith(filename, ".transfer") ?: endswith(filename, ".conf"));
*e = 0; /* Remove the file extension */
t->id = TAKE_PTR(filename);
t->enabled = transfer_decide_if_enabled(t, known_features);
if (!RESOURCE_IS_SOURCE(t->source.type))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Source Type= must be one of url-file, url-tar, tar, regular-file, directory, subvolume.");
@@ -704,6 +749,8 @@ int transfer_vacuum(
assert(instances_max >= 1);
if (instances_max == UINT64_MAX) /* Keep infinite instances? */
limit = UINT64_MAX;
else if (space == UINT64_MAX) /* forcibly delete all instances? */
limit = 0;
else if (space > instances_max)
return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
"Asked to delete more instances than total maximum allowed number of instances, refusing.");
@@ -713,7 +760,7 @@ int transfer_vacuum(
else
limit = instances_max - space;
if (t->target.type == RESOURCE_PARTITION) {
if (t->target.type == RESOURCE_PARTITION && space != UINT64_MAX) {
uint64_t rm, remain;
/* If we are looking at a partition table, we also have to take into account how many
@@ -767,7 +814,11 @@ int transfer_vacuum(
assert(oldest->resource);
log_info("%s Removing old '%s' (%s).", special_glyph(SPECIAL_GLYPH_RECYCLING), oldest->path, resource_type_to_string(oldest->resource->type));
log_info("%s Removing %s '%s' (%s).",
special_glyph(SPECIAL_GLYPH_RECYCLING),
space == UINT64_MAX ? "disabled" : "old",
oldest->path,
resource_type_to_string(oldest->resource->type));
switch (t->target.type) {
+7 -3
View File
@@ -15,12 +15,17 @@ typedef struct Transfer Transfer;
#include "sysupdate.h"
struct Transfer {
char *definition_path;
char *id;
char *min_version;
char **protected_versions;
char *current_symlink;
bool verify;
char **features;
char **requisite_features;
bool enabled;
Resource source, target;
uint64_t instances_max;
@@ -55,11 +60,10 @@ struct Transfer {
typedef int (*TransferProgress)(const Transfer *t, const Instance *inst, unsigned percentage);
Transfer* transfer_new(Context *ctx);
Transfer* transfer_free(Transfer *t);
DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_free);
int transfer_read_definition(Transfer *t, const char *path);
int transfer_read_definition(Transfer *t, const char *path, const char **dirs, Hashmap *features);
int transfer_resolve_paths(Transfer *t, const char *root, const char *node);
+296 -54
View File
@@ -25,9 +25,11 @@
#include "set.h"
#include "signal-util.h"
#include "sort-util.h"
#include "specifier.h"
#include "string-util.h"
#include "strv.h"
#include "sysupdate.h"
#include "sysupdate-feature.h"
#include "sysupdate-transfer.h"
#include "sysupdate-update-set.h"
#include "sysupdate-util.h"
@@ -57,10 +59,21 @@ STATIC_DESTRUCTOR_REGISTER(arg_component, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
const Specifier specifier_table[] = {
COMMON_SYSTEM_SPECIFIERS,
COMMON_TMP_SPECIFIERS,
{}
};
typedef struct Context {
Transfer **transfers;
size_t n_transfers;
Transfer **disabled_transfers;
size_t n_disabled_transfers;
Hashmap *features; /* Defined features, keyed by ID */
UpdateSet **update_sets;
size_t n_update_sets;
@@ -77,6 +90,12 @@ static Context* context_free(Context *c) {
transfer_free(*tr);
free(c->transfers);
FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers)
transfer_free(*tr);
free(c->disabled_transfers);
hashmap_free(c->features);
FOREACH_ARRAY(us, c->update_sets, c->n_update_sets)
update_set_free(*us);
free(c->update_sets);
@@ -93,83 +112,145 @@ static Context* context_new(void) {
return new0(Context, 1);
}
static int context_read_definitions(
static void free_transfers(Transfer **array, size_t n) {
FOREACH_ARRAY(t, array, n)
transfer_free(*t);
free(array);
}
static int read_definitions(
Context *c,
const char *directory,
const char *component,
const char *root,
const char **dirs,
const char *suffix,
const char *node) {
_cleanup_strv_free_ char **files = NULL;
Transfer **transfers = NULL, **disabled = NULL;
size_t n_transfers = 0, n_disabled = 0;
int r;
CLEANUP_ARRAY(transfers, n_transfers, free_transfers);
CLEANUP_ARRAY(disabled, n_disabled, free_transfers);
assert(c);
assert(dirs);
assert(suffix);
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"));
r = conf_files_list_strv(&files, suffix, arg_root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, dirs);
if (r < 0)
return log_error_errno(r, "Failed to enumerate *.conf files: %m");
return log_error_errno(r, "Failed to enumerate sysupdate.d/*%s definitions: %m", suffix);
STRV_FOREACH(f, files) {
STRV_FOREACH(p, files) {
_cleanup_(transfer_freep) Transfer *t = NULL;
if (!GREEDY_REALLOC(c->transfers, c->n_transfers + 1))
return log_oom();
Transfer **appended;
t = transfer_new(c);
if (!t)
return log_oom();
t->definition_path = strdup(*f);
if (!t->definition_path)
return log_oom();
r = transfer_read_definition(t, *f);
r = transfer_read_definition(t, *p, dirs, c->features);
if (r < 0)
return r;
c->transfers[c->n_transfers++] = TAKE_PTR(t);
r = transfer_resolve_paths(t, arg_root, node);
if (r < 0)
return r;
if (t->enabled)
appended = GREEDY_REALLOC_APPEND(transfers, n_transfers, &t, 1);
else
appended = GREEDY_REALLOC_APPEND(disabled, n_disabled, &t, 1);
if (!appended)
return log_oom();
TAKE_PTR(t);
}
c->transfers = TAKE_PTR(transfers);
c->n_transfers = n_transfers;
c->disabled_transfers = TAKE_PTR(disabled);
c->n_disabled_transfers = n_disabled;
return 0;
}
static int context_read_definitions(Context *c, const char* node) {
_cleanup_strv_free_ char **dirs = NULL, **files = NULL;
int r;
assert(c);
if (arg_definitions)
dirs = strv_new(arg_definitions);
else if (arg_component) {
char **l = CONF_PATHS_STRV("");
size_t i = 0;
dirs = new0(char*, strv_length(l) + 1);
if (!dirs)
return log_oom();
STRV_FOREACH(dir, l) {
char *j;
j = strjoin(*dir, "sysupdate.", arg_component, ".d");
if (!j)
return log_oom();
dirs[i++] = j;
}
} else
dirs = strv_new(CONF_PATHS("sysupdate.d"));
if (!dirs)
return log_oom();
r = conf_files_list_strv(&files,
".feature",
arg_root,
CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
(const char**) dirs);
if (r < 0)
return log_error_errno(r, "Failed to enumerate sysupdate.d/*.feature definitions: %m");
STRV_FOREACH(p, files) {
_cleanup_(feature_unrefp) Feature *f = NULL;
f = feature_new();
if (!f)
return log_oom();
r = feature_read_definition(f, *p, (const char**) dirs);
if (r < 0)
return r;
r = hashmap_ensure_put(&c->features, &feature_hash_ops, f->id, f);
if (r < 0)
return log_error_errno(r, "Failed to insert feature '%s' into map: %m", f->id);
TAKE_PTR(f);
}
r = read_definitions(c, (const char**) dirs, ".transfer", node);
if (r < 0)
return r;
if (c->n_transfers + c->n_disabled_transfers == 0) {
/* Backwards-compat: If no .transfer defs are found, fall back to trying .conf! */
r = read_definitions(c, (const char**) dirs, ".conf", node);
if (r < 0)
return r;
if (c->n_transfers + c->n_disabled_transfers > 0)
log_warning("As of v257, transfer definitions should have the '.transfer' extension.");
}
if (c->n_transfers == 0) {
if (arg_component)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No transfer definitions for component '%s' found.", arg_component);
"No transfer definitions for component '%s' found.",
arg_component);
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No transfer definitions found.");
}
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
Transfer *t = *tr;
r = transfer_resolve_paths(t, root, node);
if (r < 0)
return r;
}
return 0;
}
@@ -191,6 +272,17 @@ static int context_load_installed_instances(Context *c) {
return r;
}
FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
Transfer *t = *tr;
r = resource_load_instances(
&t->target,
arg_verify >= 0 ? arg_verify : t->verify,
&c->web_cache);
if (r < 0)
return r;
}
return 0;
}
@@ -203,7 +295,6 @@ static int context_load_available_instances(Context *c) {
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
Transfer *t = *tr;
assert(t);
r = resource_load_instances(
&t->source,
@@ -750,6 +841,7 @@ static int context_vacuum(
uint64_t space,
const char *extra_protected_version) {
size_t disabled_count = 0;
int r, count = 0;
assert(c);
@@ -773,15 +865,29 @@ static int context_vacuum(
count = MAX(count, r);
}
FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) {
r = transfer_vacuum(*tr, UINT64_MAX /* wipe all instances */, NULL);
if (r < 0)
return r;
if (r > 0)
disabled_count++;
}
if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
if (count > 0)
if (count > 0 && disabled_count > 0)
log_info("Removed %i instances, and %zu disabled transfers.", count, disabled_count);
else if (count > 0)
log_info("Removed %i instances.", count);
else if (disabled_count > 0)
log_info("Removed %zu disabled transfers.", disabled_count);
else
log_info("Removed no instances.");
log_info("Found nothing to remove.");
} else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_INTEGER("removed", count));
r = sd_json_buildo(&json,
SD_JSON_BUILD_PAIR_INTEGER("removed", count),
SD_JSON_BUILD_PAIR_UNSIGNED("disabledTransfers", disabled_count));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
@@ -806,7 +912,7 @@ static int context_make_offline(Context **ret, const char *node) {
if (!context)
return log_oom();
r = context_read_definitions(context, arg_definitions, arg_component, arg_root, node);
r = context_read_definitions(context, node);
if (r < 0)
return r;
@@ -1095,6 +1201,140 @@ static int verb_list(int argc, char **argv, void *userdata) {
}
}
static int verb_features(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_(table_unrefp) Table *table = NULL;
const char *feature_id;
Feature *f;
int r;
assert(argc <= 2);
feature_id = argc >= 2 ? argv[1] : NULL;
r = process_image(/* ro= */ true, &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;
if (feature_id) {
_cleanup_strv_free_ char **transfers = NULL;
f = hashmap_get(context->features, feature_id);
if (!f)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"Optional feature not found: %s",
feature_id);
table = table_new_vertical();
if (!table)
return log_oom();
FOREACH_ARRAY(tr, context->transfers, context->n_transfers) {
Transfer *t = *tr;
if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
continue;
r = strv_extend(&transfers, t->id);
if (r < 0)
return log_oom();
}
FOREACH_ARRAY(tr, context->disabled_transfers, context->n_disabled_transfers) {
Transfer *t = *tr;
if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id))
continue;
r = strv_extend(&transfers, t->id);
if (r < 0)
return log_oom();
}
r = table_add_many(table,
TABLE_FIELD, "Name",
TABLE_STRING, f->id,
TABLE_FIELD, "Enabled",
TABLE_BOOLEAN, f->enabled);
if (r < 0)
return table_log_add_error(r);
if (f->description) {
r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f->description);
if (r < 0)
return table_log_add_error(r);
}
if (f->documentation) {
r = table_add_many(table,
TABLE_FIELD, "Documentation",
TABLE_STRING, f->documentation,
TABLE_SET_URL, f->documentation);
if (r < 0)
return table_log_add_error(r);
}
if (f->appstream) {
r = table_add_many(table,
TABLE_FIELD, "AppStream",
TABLE_STRING, f->appstream,
TABLE_SET_URL, f->appstream);
if (r < 0)
return table_log_add_error(r);
}
if (!strv_isempty(transfers)) {
r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, transfers);
if (r < 0)
return table_log_add_error(r);
}
return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
} else if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_OFF)) {
table = table_new("", "feature", "description", "documentation");
if (!table)
return log_oom();
HASHMAP_FOREACH(f, context->features) {
r = table_add_many(table,
TABLE_BOOLEAN_CHECKMARK, f->enabled,
TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled),
TABLE_STRING, f->id,
TABLE_STRING, f->description,
TABLE_STRING, f->documentation,
TABLE_SET_URL, f->documentation);
if (r < 0)
return table_log_add_error(r);
}
return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
} else {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
_cleanup_strv_free_ char **features = NULL;
HASHMAP_FOREACH(f, context->features) {
r = strv_extend(&features, f->id);
if (r < 0)
return log_oom();
}
r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRV("features", features));
if (r < 0)
return log_error_errno(r, "Failed to create JSON: %m");
r = sd_json_variant_dump(json, arg_json_format_flags, stdout, NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON: %m");
}
return 0;
}
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;
@@ -1411,6 +1651,7 @@ static int verb_help(int argc, char **argv, void *userdata) {
"\n%5$sUpdate OS images.%6$s\n"
"\n%3$sCommands:%4$s\n"
" list [VERSION] Show installed and available versions\n"
" features [FEATURE] Show optional features\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"
@@ -1623,8 +1864,9 @@ 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 },
{ "features", VERB_ANY, 2, 0, verb_features },
{ "check-new", VERB_ANY, 1, 0, verb_check_new },
{ "update", VERB_ANY, 2, 0, verb_update },
{ "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 },
+4
View File
@@ -4,6 +4,8 @@
#include <inttypes.h>
#include <stdbool.h>
#include "specifier.h"
/* Forward declare this type so that Transfers can point at it */
typedef struct Context Context;
@@ -11,3 +13,5 @@ extern bool arg_sync;
extern uint64_t arg_instances_max;
extern char *arg_root;
extern char *arg_transfer_source;
extern const Specifier specifier_table[];
+228 -20
View File
@@ -13,6 +13,7 @@
#include "bus-util.h"
#include "common-signal.h"
#include "discover-image.h"
#include "dropin.h"
#include "env-util.h"
#include "escape.h"
#include "event-util.h"
@@ -31,6 +32,8 @@
#include "string-table.h"
#include "sysupdate-util.h"
#define FEATURES_DROPIN_NAME "systemd-sysupdate-enabled"
typedef struct Manager {
sd_event *event;
sd_bus *bus;
@@ -85,6 +88,7 @@ typedef enum JobType {
JOB_CHECK_NEW,
JOB_UPDATE,
JOB_VACUUM,
JOB_DESCRIBE_FEATURE,
_JOB_TYPE_MAX,
_JOB_TYPE_INVALID = -EINVAL,
} JobType;
@@ -104,6 +108,7 @@ struct Job {
JobType type;
bool offline;
char *version; /* Passed into sysupdate for JOB_DESCRIBE and JOB_UPDATE */
char *feature; /* Passed into sysupdate for JOB_DESCRIBE_FEATURE */
unsigned progress_percent;
@@ -131,11 +136,12 @@ static const char* const target_class_table[_TARGET_CLASS_MAX] = {
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass);
static const char* const job_type_table[_JOB_TYPE_MAX] = {
[JOB_LIST] = "list",
[JOB_DESCRIBE] = "describe",
[JOB_CHECK_NEW] = "check-new",
[JOB_UPDATE] = "update",
[JOB_VACUUM] = "vacuum",
[JOB_LIST] = "list",
[JOB_DESCRIBE] = "describe",
[JOB_CHECK_NEW] = "check-new",
[JOB_UPDATE] = "update",
[JOB_VACUUM] = "vacuum",
[JOB_DESCRIBE_FEATURE] = "describe-feature",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(job_type, JobType);
@@ -149,6 +155,7 @@ static Job *job_free(Job *j) {
free(j->object_path);
free(j->version);
free(j->feature);
sd_json_variant_unref(j->json);
@@ -440,8 +447,8 @@ static int job_start(Job *j) {
NULL, /* maybe --verify=no */
NULL, /* maybe --component=, --root=, or --image= */
NULL, /* maybe --offline */
NULL, /* list, check-new, update, vacuum */
NULL, /* maybe version (for list, update) */
NULL, /* list, check-new, update, vacuum, features */
NULL, /* maybe version (for list, update), maybe feature (features) */
NULL
};
size_t k = 2;
@@ -493,6 +500,12 @@ static int job_start(Job *j) {
cmd[k++] = "vacuum";
break;
case JOB_DESCRIBE_FEATURE:
cmd[k++] = "features";
assert(!isempty(j->feature));
cmd[k++] = j->feature;
break;
default:
assert_not_reached();
}
@@ -573,20 +586,26 @@ static int job_method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *
action = "org.freedesktop.sysupdate1.vacuum";
break;
case JOB_DESCRIBE_FEATURE:
action = NULL;
break;
default:
assert_not_reached();
}
r = bus_verify_polkit_async(
msg,
action,
/* details= */ NULL,
&j->manager->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
if (action) {
r = bus_verify_polkit_async(
msg,
action,
/* details= */ NULL,
&j->manager->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
}
r = job_cancel(j);
if (r < 0)
@@ -918,6 +937,8 @@ static int target_method_describe_finish(
_cleanup_free_ char *text = NULL;
int r;
/* NOTE: This is also reused by target_method_describe_feature */
assert(json);
r = sd_json_variant_format(json, 0, &text);
@@ -1132,7 +1153,7 @@ static int target_method_vacuum_finish(
sd_bus_error *error) {
sd_json_variant *v;
uint64_t instances;
uint64_t instances, disabled;
assert(json);
@@ -1144,7 +1165,15 @@ static int target_method_vacuum_finish(
instances = sd_json_variant_unsigned(v);
assert(instances <= UINT32_MAX);
return sd_bus_reply_method_return(msg, "u", (uint32_t) instances);
v = sd_json_variant_by_key(json, "disabledTransfers");
if (!v)
return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Missing key 'disabledTransfers'");
if (!sd_json_variant_is_unsigned(v))
return log_sysupdate_bad_json(SYNTHETIC_ERRNO(EPROTO), "vacuum", "Key 'disabledTransfers' should be an unsigned int");
disabled = sd_json_variant_unsigned(v);
assert(disabled <= UINT32_MAX);
return sd_bus_reply_method_return(msg, "uu", (uint32_t) instances, (uint32_t) disabled);
}
static int target_method_vacuum(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
@@ -1247,6 +1276,167 @@ static int target_method_get_appstream(sd_bus_message *msg, void *userdata, sd_b
return sd_bus_send(NULL, reply, NULL);
}
static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
_cleanup_strv_free_ char **features = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
Target *t = ASSERT_PTR(userdata);
sd_json_variant *v;
uint64_t flags;
int r;
assert(msg);
r = sd_bus_message_read(msg, "t", &flags);
if (r < 0)
return r;
if (flags != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
r = sysupdate_run_simple(&json, t, "features", NULL);
if (r < 0)
return r;
v = sd_json_variant_by_key(json, "features");
if (!v)
return -EINVAL;
r = sd_json_variant_strv(v, &features);
if (r < 0)
return r;
r = sd_bus_message_new_method_return(msg, &reply);
if (r < 0)
return r;
r = sd_bus_message_append_strv(reply, features);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
static int target_method_describe_feature(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
Target *t = ASSERT_PTR(userdata);
_cleanup_(job_freep) Job *j = NULL;
const char *feature;
uint64_t flags;
int r;
assert(msg);
r = sd_bus_message_read(msg, "st", &feature, &flags);
if (r < 0)
return r;
if (isempty(feature))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Feature must be specified");
if (flags != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
r = job_new(JOB_DESCRIBE_FEATURE, t, msg, target_method_describe_finish, &j);
if (r < 0)
return r;
j->feature = strdup(feature);
if (!j->feature)
return log_oom();
r = job_start(j);
if (r < 0)
return sd_bus_error_set_errnof(error, r, "Failed to start job: %m");
TAKE_PTR(j); /* Avoid job from being killed & freed */
return 1;
}
static bool feature_name_is_valid(const char *name) {
if (isempty(name))
return false;
if (!ascii_is_valid(name))
return false;
if (!filename_is_valid(strjoina(name, ".feature.d")))
return false;
return true;
}
static int target_method_set_feature_enabled(sd_bus_message *msg, void *userdata, sd_bus_error *error) {
_cleanup_free_ char *feature_ext = NULL;
Target *t = ASSERT_PTR(userdata);
const char *feature;
uint64_t flags;
int32_t enabled;
int r;
assert(msg);
if (t->class != TARGET_HOST)
return sd_bus_reply_method_errorf(msg,
SD_BUS_ERROR_NOT_SUPPORTED,
"For now, features can only be managed on the host system.");
r = sd_bus_message_read(msg, "sit", &feature, &enabled, &flags);
if (r < 0)
return r;
if (!feature_name_is_valid(feature))
return sd_bus_reply_method_errorf(msg,
SD_BUS_ERROR_INVALID_ARGS,
"The specified feature is invalid");
if (flags != 0)
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be 0");
if (!endswith(feature, ".feature")) {
feature_ext = strjoin(feature, ".feature");
if (!feature_ext)
return -ENOMEM;
feature = feature_ext;
}
const char *details[] = {
"class", target_class_to_string(t->class),
"name", t->name,
"feature", feature,
"enabled", enabled >= 0 ? true_false(enabled) : "unset",
NULL
};
r = bus_verify_polkit_async(
msg,
"org.freedesktop.sysupdate1.manage-features",
details,
&t->manager->polkit_registry,
error);
if (r < 0)
return r;
if (r == 0)
return 1; /* Will call us back */
/* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
if (enabled < 0) { /* Reset -> delete the drop-in file */
_cleanup_free_ char *path = NULL;
r = drop_in_file(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME, NULL, &path);
if (r < 0)
return r;
if (unlink(path) < 0)
return -errno;
} else { /* otherwise, create the drop-in with the right settings */
r = write_drop_in_format(SYSCONF_DIR "/sysupdate.d", feature, 50, FEATURES_DROPIN_NAME,
"# Generated via org.freedesktop.sysupdate1 D-Bus interface\n\n"
"[Feature]\n"
"Enabled=%s\n",
yes_no(enabled));
if (r < 0)
return r;
}
return sd_bus_reply_method_return(msg, NULL);
}
static int target_list_components(Target *t, char ***ret_components, bool *ret_have_default) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
_cleanup_strv_free_ char **components = NULL;
@@ -1397,7 +1587,7 @@ static const sd_bus_vtable target_vtable[] = {
SD_BUS_METHOD_WITH_ARGS("Vacuum",
SD_BUS_NO_ARGS,
SD_BUS_RESULT("u", count),
SD_BUS_RESULT("u", instances, "u", disabled_transfers),
target_method_vacuum,
SD_BUS_VTABLE_UNPRIVILEGED),
@@ -1413,6 +1603,24 @@ static const sd_bus_vtable target_vtable[] = {
target_method_get_version,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("ListFeatures",
SD_BUS_ARGS("t", flags),
SD_BUS_RESULT("as", features),
target_method_list_features,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("DescribeFeature",
SD_BUS_ARGS("s", feature, "t", flags),
SD_BUS_RESULT("s", json),
target_method_describe_feature,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD_WITH_ARGS("SetFeatureEnabled",
SD_BUS_ARGS("s", feature, "i", enabled, "t", flags),
SD_BUS_NO_RESULT,
target_method_set_feature_enabled,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END
};
+330 -24
View File
@@ -12,12 +12,18 @@
#include "bus-locator.h"
#include "bus-map-properties.h"
#include "bus-util.h"
#include "conf-files.h"
#include "conf-parser.h"
#include "errno-list.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "fs-util.h"
#include "json-util.h"
#include "main-func.h"
#include "os-util.h"
#include "pager.h"
#include "path-util.h"
#include "pretty-print.h"
#include "strv.h"
#include "sysupdate-update-set-flags.h"
@@ -29,9 +35,11 @@ static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
static bool arg_reboot = false;
static bool arg_offline = false;
static bool arg_now = false;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static char *arg_host = NULL;
#define SYSUPDATE_HOST_PATH "/org/freedesktop/sysupdate1/target/host"
#define SYSUPDATE_TARGET_INTERFACE "org.freedesktop.sysupdate1.Target"
typedef struct Version {
@@ -1044,21 +1052,19 @@ static int update_started(sd_bus_message *reply, void *userdata, sd_bus_error *r
return 0;
}
static int verb_update(int argc, char **argv, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
static int do_update(sd_bus *bus, char **targets) {
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
_cleanup_(sd_event_source_unrefp) sd_event_source *render_exit = NULL;
_cleanup_ordered_hashmap_free_ OrderedHashmap *map = NULL;
_cleanup_strv_free_ char **targets = NULL, **versions = NULL, **target_paths = NULL;
_cleanup_strv_free_ char **versions = NULL, **target_paths = NULL;
size_t n;
unsigned remaining = 0;
void *p;
bool did_anything = false;
int r;
r = ensure_targets(bus, argv + 1, &targets);
if (r < 0)
return log_error_errno(r, "Could not find targets: %m");
assert(bus);
assert(targets);
r = parse_targets(targets, &n, &target_paths, &versions);
if (r < 0)
@@ -1140,18 +1146,64 @@ static int verb_update(int argc, char **argv, void *userdata) {
did_anything = true;
}
if (arg_reboot) {
if (did_anything)
return reboot_now();
log_info("Nothing was updated... skipping reboot.");
}
return did_anything ? 1 : 0;
}
static int verb_update(int argc, char **argv, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
_cleanup_strv_free_ char **targets = NULL;
bool did_anything = false;
int r;
r = ensure_targets(bus, argv + 1, &targets);
if (r < 0)
return log_error_errno(r, "Could not find targets: %m");
r = do_update(bus, targets);
if (r < 0)
return r;
if (r > 0)
did_anything = true;
if (!arg_reboot)
return 0;
if (did_anything)
return reboot_now();
log_info("Nothing was updated... skipping reboot.");
return 0;
}
static int do_vacuum(sd_bus *bus, const char *target, const char *path) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
uint32_t count, disabled;
int r;
r = sd_bus_call_method(bus, bus_sysupdate_mgr->destination, path, SYSUPDATE_TARGET_INTERFACE, "Vacuum", &error, &reply, NULL);
if (r < 0)
return log_bus_error(r, &error, target, "call Vacuum");
r = sd_bus_message_read(reply, "uu", &count, &disabled);
if (r < 0)
return bus_log_parse_error(r);
if (count > 0 && disabled > 0)
log_info("Deleted %u instance(s) and %u disabled transfer(s) of %s.",
count, disabled, target);
else if (count > 0)
log_info("Deleted %u instance(s) of %s.", count, target);
else if (disabled > 0)
log_info("Deleted %u disabled transfer(s) of %s.", disabled, target);
else
log_info("Found nothing to delete for %s.", target);
return count + disabled > 0 ? 1 : 0;
}
static int verb_vacuum(int argc, char **argv, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_strv_free_ char **targets = NULL, **target_paths = NULL;
size_t n;
int r;
@@ -1165,19 +1217,260 @@ static int verb_vacuum(int argc, char **argv, void *userdata) {
return log_error_errno(r, "Failed to parse targets: %m");
for (size_t i = 0; i < n; i++) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
uint32_t count;
r = sd_bus_call_method(bus, bus_sysupdate_mgr->destination, target_paths[i], SYSUPDATE_TARGET_INTERFACE, "Vacuum", &error, &reply, NULL);
r = do_vacuum(bus, targets[i], target_paths[i]);
if (r < 0)
return log_bus_error(r, &error, targets[i], "call Vacuum");
return r;
}
return 0;
}
r = sd_bus_message_read(reply, "u", &count);
typedef struct Feature {
char *name;
char *description;
bool enabled;
char *documentation;
char **transfers;
} Feature;
static void feature_done(Feature *f) {
assert(f);
f->name = mfree(f->name);
f->description = mfree(f->description);
f->documentation = mfree(f->documentation);
f->transfers = strv_free(f->transfers);
}
static int describe_feature(sd_bus *bus, const char *feature, Feature *ret) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_(feature_done) Feature f = {};
char *json;
int r;
static const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, name), SD_JSON_MANDATORY },
{ "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, description), 0 },
{ "enabled", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Feature, enabled), SD_JSON_MANDATORY },
{ "documentationUrl", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Feature, documentation), 0 },
{ "transfers", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(Feature, transfers), 0 },
{}
};
assert(bus);
assert(feature);
assert(ret);
r = sd_bus_call_method(bus,
bus_sysupdate_mgr->destination,
SYSUPDATE_HOST_PATH,
SYSUPDATE_TARGET_INTERFACE,
"DescribeFeature",
&error,
&reply,
"st",
feature,
UINT64_C(0));
if (r < 0)
return log_bus_error(r, &error, "host", "lookup feature");
r = sd_bus_message_read_basic(reply, 's', &json);
if (r < 0)
return bus_log_parse_error(r);
r = sd_json_parse(json, 0, &v, NULL, NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse JSON: %m");
r = sd_json_dispatch(v, dispatch_table, 0, &f);
if (r < 0)
return log_error_errno(r, "Failed to dispatch JSON: %m");
*ret = TAKE_STRUCT(f);
return 0;
}
static int list_features(sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_strv_free_ char **features = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
int r;
assert(bus);
table = table_new("", "feature", "description");
if (!table)
return log_oom();
r = sd_bus_call_method(bus,
bus_sysupdate_mgr->destination,
SYSUPDATE_HOST_PATH,
SYSUPDATE_TARGET_INTERFACE,
"ListFeatures",
&error,
&reply,
"t",
UINT64_C(0));
if (r < 0)
return log_bus_error(r, &error, "host", "lookup feature");
r = sd_bus_message_read_strv(reply, &features);
if (r < 0)
return bus_log_parse_error(r);
STRV_FOREACH(feature, features) {
_cleanup_(feature_done) Feature f = {};
_cleanup_free_ char *name_link = NULL;
r = describe_feature(bus, *feature, &f);
if (r < 0)
return r;
if (urlify_enabled() && f.documentation) {
name_link = strjoin(f.name, special_glyph(SPECIAL_GLYPH_EXTERNAL_LINK));
if (!name_link)
return log_oom();
}
r = table_add_many(table,
TABLE_BOOLEAN_CHECKMARK, f.enabled,
TABLE_SET_COLOR, ansi_highlight_green_red(f.enabled),
TABLE_STRING, name_link ?: f.name,
TABLE_SET_URL, f.documentation,
TABLE_STRING, f.description);
if (r < 0)
return table_log_add_error(r);
}
return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, arg_legend);
}
static int verb_features(int argc, char **argv, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_(feature_done) Feature f = {};
int r;
if (argc == 1)
return list_features(bus);
table = table_new_vertical();
if (!table)
return log_oom();
r = describe_feature(bus, argv[1], &f);
if (r < 0)
return r;
r = table_add_many(table,
TABLE_FIELD, "Name",
TABLE_STRING, f.name,
TABLE_FIELD, "Enabled",
TABLE_BOOLEAN, f.enabled);
if (r < 0)
return table_log_add_error(r);
if (f.description) {
r = table_add_many(table, TABLE_FIELD, "Description", TABLE_STRING, f.description);
if (r < 0)
return table_log_add_error(r);
}
if (f.documentation) {
r = table_add_many(table,
TABLE_FIELD, "Documentation",
TABLE_STRING, f.documentation,
TABLE_SET_URL, f.documentation);
if (r < 0)
return table_log_add_error(r);
}
if (!strv_isempty(f.transfers)) {
r = table_add_many(table, TABLE_FIELD, "Transfers", TABLE_STRV_WRAPPED, f.transfers);
if (r < 0)
return table_log_add_error(r);
}
return table_print_with_pager(table, SD_JSON_FORMAT_OFF, arg_pager_flags, false);
}
static int verb_enable(int argc, char **argv, void *userdata) {
sd_bus *bus = ASSERT_PTR(userdata);
bool did_anything = false, enable;
char **features;
int r;
enable = streq(argv[0], "enable");
features = strv_skip(argv, 1);
STRV_FOREACH(feature, features) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
r = sd_bus_call_method(bus,
bus_sysupdate_mgr->destination,
SYSUPDATE_HOST_PATH,
SYSUPDATE_TARGET_INTERFACE,
"SetFeatureEnabled",
&error,
/* reply= */ NULL,
"sbt",
*feature,
(int) enable,
UINT64_C(0));
if (r < 0)
return log_bus_error(r, &error, "host", "call SetFeatureEnabled");
}
if (!arg_now) /* We weren't asked to apply the changes, so we're done! */
return 0;
if (enable) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_free_ char *target = NULL;
char *version = NULL;
/* We're downloading the new feature into the "current" version, which is either going to be
* the currently booted version or it's going to be a pending update that has already been
* installed and is just waiting for us to reboot into it. */
r = sd_bus_call_method(bus,
bus_sysupdate_mgr->destination,
SYSUPDATE_HOST_PATH,
SYSUPDATE_TARGET_INTERFACE,
"GetVersion",
&error,
&reply,
NULL);
if (r < 0)
return log_bus_error(r, &error, "host", "get current version");
r = sd_bus_message_read_basic(reply, 's', &version);
if (r < 0)
return bus_log_parse_error(r);
log_info("Deleted %u instance(s) of %s.\n", count, targets[i]);
}
target = strjoin("host@", version);
if (!target)
return log_oom();
r = do_update(bus, STRV_MAKE(target));
} else
r = do_vacuum(bus, "host", SYSUPDATE_HOST_PATH);
if (r < 0)
return r;
if (r > 0)
did_anything = true;
if (arg_reboot && did_anything)
return reboot_now();
else if (did_anything)
log_info("Feature(s) %s.", enable ? "downloaded" : "deleted");
else
log_info("Nothing %s%s.",
enable ? "downloaded" : "deleted",
arg_reboot ? ", skipping reboot" :"");
return 0;
}
@@ -1196,11 +1489,15 @@ static int help(void) {
" check [TARGET...] Check for updates\n"
" update [TARGET[@VERSION]...] Install updates\n"
" vacuum [TARGET...] Clean up old updates\n"
" features [FEATURE] List and inspect optional features on host OS\n"
" enable FEATURE... Enable optional feature on host OS\n"
" disable FEATURE... Disable optional feature on host OS\n"
" -h --help Show this help\n"
" --version Show package version\n"
"\n%3$sOptions:%4$s\n"
" --reboot Reboot after updating to newer version\n"
" --offline Do not fetch metadata from the network\n"
" --now Download/delete resources immediately\n"
" -H --host=[USER@]HOST Operate on remote host\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
@@ -1222,6 +1519,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_NO_LEGEND,
ARG_REBOOT,
ARG_OFFLINE,
ARG_NOW,
};
static const struct option options[] = {
@@ -1232,6 +1530,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "host", required_argument, NULL, 'H' },
{ "reboot", no_argument, NULL, ARG_REBOOT },
{ "offline", no_argument, NULL, ARG_OFFLINE },
{ "now", no_argument, NULL, ARG_NOW },
{}
};
@@ -1270,6 +1569,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_offline = true;
break;
case ARG_NOW:
arg_now = true;
break;
case '?':
return -EINVAL;
@@ -1286,10 +1589,13 @@ static int run(int argc, char *argv[]) {
int r;
static const Verb verbs[] = {
{ "list", VERB_ANY, 2, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list },
{ "check", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_check },
{ "update", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_update },
{ "vacuum", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_vacuum },
{ "list", VERB_ANY, 2, VERB_DEFAULT|VERB_ONLINE_ONLY, verb_list },
{ "check", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_check },
{ "update", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_update },
{ "vacuum", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, verb_vacuum },
{ "features", VERB_ANY, 2, VERB_ONLINE_ONLY, verb_features },
{ "enable", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_enable },
{ "disable", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_enable },
{}
};