sysupdate: Add ListFeatures() and ListTargets() varlink methods (#42900)
Following on from adding the basic varlink scaffolding to sysupdate, let’s varlinkify a couple of the D-Bus methods. Because varlink doesn’t have a concept of object paths, the D-Bus path structure which allows a target to be selected has been squashed down to a target argument for each relevant method. Varlinkify the way to list targets, and also the way to list features because that was simple to do at the same time. More methods need varlinkifying in the future, but let’s do it in small and manageable chunks.
This commit is contained in:
@@ -25,6 +25,33 @@ static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
SD_VARLINK_FIELD_COMMENT("Name of the target, unique within a class."),
|
||||
SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
Target,
|
||||
SD_VARLINK_FIELD_COMMENT("Identifier for the target."),
|
||||
SD_VARLINK_DEFINE_FIELD_BY_TYPE(id, TargetIdentifier, 0));
|
||||
|
||||
static SD_VARLINK_DEFINE_STRUCT_TYPE(
|
||||
Feature,
|
||||
SD_VARLINK_FIELD_COMMENT("Identifier for the feature."),
|
||||
SD_VARLINK_DEFINE_FIELD(id, SD_VARLINK_STRING, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("A short human readable description of the feature."),
|
||||
SD_VARLINK_DEFINE_FIELD(description, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("URLs to documentation for the feature."),
|
||||
SD_VARLINK_DEFINE_FIELD(documentation, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
|
||||
SD_VARLINK_FIELD_COMMENT("A URL to an AppStream catalog XML file describing the feature."),
|
||||
SD_VARLINK_DEFINE_FIELD(appstream, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Whether the feature is enabled."),
|
||||
SD_VARLINK_DEFINE_FIELD(isEnabled, SD_VARLINK_BOOL, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("Array of IDs of the transfers (including currently disabled ones) which are controlled by this feature."),
|
||||
SD_VARLINK_DEFINE_FIELD(transfers, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
ListFeatures,
|
||||
SD_VARLINK_FIELD_COMMENT("Target to list features for."),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(target, TargetIdentifier, 0),
|
||||
SD_VARLINK_FIELD_COMMENT("The configured features."),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(features, Feature, SD_VARLINK_ARRAY));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
CheckNew,
|
||||
SD_VARLINK_FIELD_COMMENT("Target to check for updates for."),
|
||||
@@ -33,6 +60,11 @@ static SD_VARLINK_DEFINE_METHOD(
|
||||
SD_VARLINK_FIELD_COMMENT("The version string for the new version which is available."),
|
||||
SD_VARLINK_DEFINE_OUTPUT(available, SD_VARLINK_STRING, 0));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
ListTargets,
|
||||
SD_VARLINK_FIELD_COMMENT("The configured targets."),
|
||||
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(targets, Target, SD_VARLINK_ARRAY));
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(NoUpdateNeeded);
|
||||
static SD_VARLINK_DEFINE_ERROR(NoSuchTarget);
|
||||
|
||||
@@ -42,14 +74,22 @@ SD_VARLINK_DEFINE_INTERFACE(
|
||||
SD_VARLINK_INTERFACE_COMMENT("APIs to manage system updates"),
|
||||
|
||||
/* Methods */
|
||||
SD_VARLINK_SYMBOL_COMMENT("Show optional features"),
|
||||
&vl_method_ListFeatures,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Check if there’s a new version available"),
|
||||
&vl_method_CheckNew,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Show targets"),
|
||||
&vl_method_ListTargets,
|
||||
|
||||
/* Types */
|
||||
SD_VARLINK_SYMBOL_COMMENT("Class of a Target."),
|
||||
&vl_type_TargetClass,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Identifier for a component of the system (i.e. the host itself, a sysext, a confext, etc.) that can be updated by systemd-sysupdate(8)."),
|
||||
&vl_type_TargetIdentifier,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Type containing a configured sysupdate target."),
|
||||
&vl_type_Target,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Type containing a configured sysupdate feature."),
|
||||
&vl_type_Feature,
|
||||
|
||||
/* Errors */
|
||||
SD_VARLINK_SYMBOL_COMMENT("Error indicating that no update is currently available to update to."),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "macro.h"
|
||||
#include "string-table.h"
|
||||
#include "sysupdate-target.h"
|
||||
|
||||
@@ -14,9 +16,39 @@ static const char* const target_class_table[_TARGET_CLASS_MAX] = {
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(target_class, TargetClass);
|
||||
|
||||
int target_identifier_new(TargetClass class, const char *name, TargetIdentifier **ret) {
|
||||
_cleanup_(target_identifier_freep) TargetIdentifier *t = NULL;
|
||||
|
||||
assert(name);
|
||||
assert(ret);
|
||||
|
||||
t = new(TargetIdentifier, 1);
|
||||
if (!t)
|
||||
return -ENOMEM;
|
||||
|
||||
*t = (TargetIdentifier) {
|
||||
.class = class,
|
||||
.name = strdup(name),
|
||||
};
|
||||
|
||||
if (!t->name)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = TAKE_PTR(t);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void target_identifier_done(TargetIdentifier *t) {
|
||||
assert(t);
|
||||
|
||||
t->class = _TARGET_CLASS_INVALID;
|
||||
t->name = mfree(t->name);
|
||||
}
|
||||
|
||||
TargetIdentifier *target_identifier_free(TargetIdentifier *t) {
|
||||
if (!t)
|
||||
return NULL;
|
||||
|
||||
target_identifier_done(t);
|
||||
return mfree(t);
|
||||
}
|
||||
|
||||
@@ -29,4 +29,8 @@ typedef struct TargetIdentifier {
|
||||
char *name;
|
||||
} TargetIdentifier;
|
||||
|
||||
int target_identifier_new(TargetClass class, const char *name, TargetIdentifier **ret);
|
||||
void target_identifier_done(TargetIdentifier *t);
|
||||
TargetIdentifier *target_identifier_free(TargetIdentifier *t);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(TargetIdentifier*, target_identifier_free);
|
||||
|
||||
+377
-32
@@ -38,6 +38,8 @@
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "runtime-scope.h"
|
||||
#include "set.h"
|
||||
#include "siphash24.h"
|
||||
#include "sort-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
@@ -467,7 +469,7 @@ static int context_load_installed_instances(Context *c) {
|
||||
|
||||
assert(c);
|
||||
|
||||
log_info("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
|
||||
log_debug("Discovering installed instances%s", glyph(GLYPH_ELLIPSIS));
|
||||
|
||||
FOREACH_ARRAY(tr, c->transfers, c->n_transfers) {
|
||||
Transfer *t = *tr;
|
||||
@@ -1268,14 +1270,146 @@ static int context_load_paths_from_image(Context *context, Image *image) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Load a Context to point to the target given by the TargetIdentifier. The TargetIdentifier will have been
|
||||
* syntactically validated by dispatch_target_identifier(), but might still point to components which don’t
|
||||
* exist, images which the user isn’t privileged to access, etc. This function validates the TargetIdentifier
|
||||
* against an enumerated list of known targets, which are safe to update without additional permissions. */
|
||||
static int context_load_online_from_target(
|
||||
Context *context,
|
||||
ProcessImageFlags process_image_flags,
|
||||
ReadDefinitionsFlags read_definitions_flags) {
|
||||
static void target_identifier_hash_func(const TargetIdentifier *t, struct siphash *state) {
|
||||
assert(t);
|
||||
|
||||
siphash24_compress_typesafe(t->class, state);
|
||||
siphash24_compress_string(t->name, state);
|
||||
}
|
||||
|
||||
static int target_identifier_compare_func(const TargetIdentifier *x, const TargetIdentifier *y) {
|
||||
int r;
|
||||
|
||||
assert(x);
|
||||
assert(y);
|
||||
|
||||
r = CMP(x->class, y->class);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
return strcmp(x->name, y->name);
|
||||
}
|
||||
|
||||
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(target_identifier_hash_ops,
|
||||
TargetIdentifier, target_identifier_hash_func, target_identifier_compare_func,
|
||||
TargetIdentifier, target_identifier_free);
|
||||
|
||||
static int enumerate_image_class(RuntimeScope runtime_scope, TargetClass class, Set **targets) {
|
||||
_cleanup_hashmap_free_ Hashmap *images = NULL;
|
||||
Image *image;
|
||||
int r;
|
||||
|
||||
r = image_discover(runtime_scope, (ImageClass) class, NULL, &images);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
HASHMAP_FOREACH(image, images) {
|
||||
_cleanup_(target_identifier_freep) TargetIdentifier *t = NULL;
|
||||
bool have = false;
|
||||
_cleanup_(context_done) Context image_context = CONTEXT_NULL;
|
||||
|
||||
if (image_is_host(image))
|
||||
continue; /* We already enroll the host ourselves */
|
||||
|
||||
if (!image_type_can_sysupdate(image->type))
|
||||
continue;
|
||||
|
||||
r = context_load_paths_from_image(&image_context, image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Load the components in a separate Context specific to the given Image before
|
||||
* committing to loading that state to the main Context. */
|
||||
r = context_load_offline(
|
||||
&image_context,
|
||||
PROCESS_IMAGE_READ_ONLY,
|
||||
/* read_definitions_flags= */ 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = context_list_components(&image_context, /* ret_component_names= */ NULL, &have);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!have) {
|
||||
log_debug("Skipping %s because it has no default component", image->path);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = target_identifier_new(class, image->name, &t);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = set_ensure_consume(targets, &target_identifier_hash_ops, TAKE_PTR(t));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int context_enumerate_components(Context *context, Set **targets) {
|
||||
_cleanup_strv_free_ char **component_names = NULL;
|
||||
bool have_default_component;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
|
||||
r = context_list_components(context, &component_names, &have_default_component);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (have_default_component) {
|
||||
_cleanup_(target_identifier_freep) TargetIdentifier *t = NULL;
|
||||
|
||||
r = target_identifier_new(TARGET_HOST, "host", &t);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = set_ensure_consume(targets, &target_identifier_hash_ops, TAKE_PTR(t));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
STRV_FOREACH(component, component_names) {
|
||||
_cleanup_(target_identifier_freep) TargetIdentifier *t = NULL;
|
||||
|
||||
r = target_identifier_new(TARGET_COMPONENT, *component, &t);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = set_ensure_consume(targets, &target_identifier_hash_ops, TAKE_PTR(t));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int context_enumerate_targets(Context *context, Set **targets) {
|
||||
static const TargetClass discoverable_classes[] = {
|
||||
TARGET_MACHINE,
|
||||
TARGET_PORTABLE,
|
||||
TARGET_SYSEXT,
|
||||
TARGET_CONFEXT,
|
||||
};
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
|
||||
FOREACH_ARRAY(class, discoverable_classes, ELEMENTSOF(discoverable_classes)) {
|
||||
r = enumerate_image_class(RUNTIME_SCOPE_SYSTEM, *class, targets);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = context_enumerate_components(context, targets);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int context_load_paths_from_target(Context *context) {
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
@@ -1366,9 +1500,45 @@ static int context_load_online_from_target(
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Load a Context to point to the target given by the TargetIdentifier. The TargetIdentifier will have been
|
||||
* syntactically validated by dispatch_target_identifier(), but might still point to components which don’t
|
||||
* exist, images which the user isn’t privileged to access, etc. This function validates the TargetIdentifier
|
||||
* against an enumerated list of known targets, which are safe to update without additional permissions. */
|
||||
static int context_load_online_from_target(
|
||||
Context *context,
|
||||
ProcessImageFlags process_image_flags,
|
||||
ReadDefinitionsFlags read_definitions_flags) {
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
assert(context->target_identifier.class != _TARGET_CLASS_INVALID);
|
||||
|
||||
r = context_load_paths_from_target(context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return context_load_online(context, process_image_flags, read_definitions_flags);
|
||||
}
|
||||
|
||||
static int context_load_offline_from_target(
|
||||
Context *context,
|
||||
ProcessImageFlags process_image_flags,
|
||||
ReadDefinitionsFlags read_definitions_flags) {
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
assert(context->target_identifier.class != _TARGET_CLASS_INVALID);
|
||||
|
||||
r = context_load_paths_from_target(context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return context_load_offline(context, process_image_flags, read_definitions_flags);
|
||||
}
|
||||
|
||||
static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
|
||||
const Context *c = ASSERT_PTR(t->context);
|
||||
size_t i, n = c->n_transfers;
|
||||
@@ -1703,6 +1873,37 @@ static int context_install(
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int context_get_transfers_for_feature(Context *context, const char *feature_id, char ***ret_transfers) {
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
assert(feature_id);
|
||||
|
||||
FOREACH_ARRAY(tr, context->transfers, context->n_transfers) {
|
||||
Transfer *t = *tr;
|
||||
|
||||
if (!strv_contains(t->features, feature_id) && !strv_contains(t->requisite_features, feature_id))
|
||||
continue;
|
||||
|
||||
r = strv_extend(ret_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, feature_id) && !strv_contains(t->requisite_features, feature_id))
|
||||
continue;
|
||||
|
||||
r = strv_extend(ret_transfers, t->id);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static JSON_DISPATCH_ENUM_DEFINE(dispatch_target_class, TargetClass, target_class_from_string);
|
||||
|
||||
static int dispatch_target_identifier(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
|
||||
@@ -1869,27 +2070,9 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata
|
||||
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 = context_get_transfers_for_feature(&context, f->id, &transfers);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = table_add_many(table,
|
||||
TABLE_FIELD, "Name",
|
||||
@@ -1975,7 +2158,16 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata
|
||||
return table_log_add_error(r);
|
||||
}
|
||||
|
||||
return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
|
||||
r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_legend) {
|
||||
if (table_isempty(table))
|
||||
log_info("No features.");
|
||||
else
|
||||
printf("\n%zu features listed.\n", table_get_rows(table) - 1);
|
||||
}
|
||||
} else {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||
_cleanup_strv_free_ char **features = NULL;
|
||||
@@ -2219,6 +2411,96 @@ static int verb_enable_feature(int argc, char *argv[], uintptr_t _data, void *us
|
||||
}
|
||||
}
|
||||
|
||||
static int feature_to_json(Context *context, const Feature *f, sd_json_variant **ret) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
_cleanup_strv_free_ char **transfers = NULL;
|
||||
const char *documentation_strv[2] = { NULL, };
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
assert(f);
|
||||
assert(ret);
|
||||
|
||||
r = context_get_transfers_for_feature(context, f->id, &transfers);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* FIXME: Long term we’d like to support an array of documentation, but currently the D-Bus interface
|
||||
* doesn’t support that and neither do the internals of sysupdate. So just expose 0 or 1 URLs for now. */
|
||||
documentation_strv[0] = f->documentation;
|
||||
|
||||
r = sd_json_variant_merge_objectbo(
|
||||
&v,
|
||||
SD_JSON_BUILD_PAIR_STRING("id", f->id),
|
||||
JSON_BUILD_PAIR_STRING_NON_EMPTY("description", f->description),
|
||||
JSON_BUILD_PAIR_STRV_NON_EMPTY("documentation", (char **) documentation_strv),
|
||||
JSON_BUILD_PAIR_STRING_NON_EMPTY("appstream", f->appstream),
|
||||
SD_JSON_BUILD_PAIR_BOOLEAN("isEnabled", f->enabled),
|
||||
JSON_BUILD_PAIR_STRV_NON_EMPTY("transfers", transfers));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
*ret = TAKE_PTR(v);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int vl_method_list_features(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
_cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||
Feature *f;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "target", SD_JSON_VARIANT_OBJECT, dispatch_target_identifier, voffsetof(context, target_identifier), SD_JSON_MANDATORY },
|
||||
{},
|
||||
};
|
||||
|
||||
r = sd_varlink_dispatch(link, parameters, dispatch_table, &context);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
/* Listing features doesn’t require a polkit check */
|
||||
|
||||
if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
|
||||
context.verify = 0;
|
||||
|
||||
/* ListFeatures is always offline */
|
||||
context.offline = true;
|
||||
|
||||
r = context_load_offline_from_target(
|
||||
&context,
|
||||
PROCESS_IMAGE_READ_ONLY,
|
||||
READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
|
||||
if (r == -ENOENT)
|
||||
return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *l = NULL;
|
||||
|
||||
HASHMAP_FOREACH(f, context.features) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
|
||||
r = feature_to_json(&context, f, &v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_json_variant_append_array(&l, v);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!l) {
|
||||
r = sd_json_variant_new_array(&l, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return sd_varlink_replybo(link,
|
||||
SD_JSON_BUILD_PAIR_VARIANT("features", l));
|
||||
}
|
||||
|
||||
VERB_NOARG(verb_check_new, "check-new",
|
||||
"Check if there's a new version available");
|
||||
static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
@@ -2770,6 +3052,67 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int vl_method_list_targets(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
_cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||
_cleanup_set_free_ Set *targets = NULL;
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
r = sd_varlink_dispatch(link, parameters, NULL, NULL);
|
||||
if (r != 0)
|
||||
return r;
|
||||
|
||||
/* Listing targets doesn’t require a polkit check */
|
||||
|
||||
if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0)
|
||||
context.verify = 0;
|
||||
|
||||
/* ListTargets is always offline */
|
||||
context.offline = true;
|
||||
|
||||
r = context_load_offline(
|
||||
&context,
|
||||
PROCESS_IMAGE_READ_ONLY,
|
||||
/* read_definitions_flags= */ 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *l = NULL;
|
||||
|
||||
r = context_enumerate_targets(&context, &targets);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Sort to ensure consistent ordering */
|
||||
size_t n;
|
||||
_cleanup_free_ TargetIdentifier **sorted = NULL;
|
||||
r = set_dump_sorted(targets, (void***) &sorted, &n);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
FOREACH_ARRAY(p, sorted, n) {
|
||||
TargetIdentifier *target_identifier = *p;
|
||||
const char *name = (target_identifier->class != TARGET_HOST) ? target_identifier->name : NULL;
|
||||
|
||||
r = sd_json_variant_append_arraybo(&l,
|
||||
SD_JSON_BUILD_PAIR_OBJECT("id",
|
||||
JSON_BUILD_PAIR_ENUM("class", target_class_to_string(target_identifier->class)),
|
||||
SD_JSON_BUILD_PAIR_STRING("name", name)));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (!l) {
|
||||
r = sd_json_variant_new_array(&l, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return sd_varlink_replybo(link,
|
||||
SD_JSON_BUILD_PAIR_VARIANT("targets", l));
|
||||
}
|
||||
|
||||
VERB(verb_enable_component, "enable-component", "COMPONENT…", 1, VERB_ANY, 0,
|
||||
"Enable component");
|
||||
VERB(verb_enable_component, "disable-component", "COMPONENT…", 1, VERB_ANY, 0,
|
||||
@@ -3160,7 +3503,9 @@ static int vl_server(void) {
|
||||
|
||||
r = sd_varlink_server_bind_method_many(
|
||||
varlink_server,
|
||||
"io.systemd.SysUpdate.CheckNew", vl_method_check_new);
|
||||
"io.systemd.SysUpdate.CheckNew", vl_method_check_new,
|
||||
"io.systemd.SysUpdate.ListFeatures", vl_method_list_features,
|
||||
"io.systemd.SysUpdate.ListTargets", vl_method_list_targets);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind Varlink method: %m");
|
||||
|
||||
|
||||
@@ -1380,7 +1380,7 @@ static int target_method_list_features(sd_bus_message *msg, void *userdata, sd_b
|
||||
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);
|
||||
r = sysupdate_run_simple(&json, t, "--offline", "features", NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ verify_version_current() {
|
||||
verify_object_fields() {
|
||||
local updatectl_output="${1:?}"
|
||||
|
||||
[[ "${updatectl_output}" != *"Unrecognized object field"* ]] || exit 1
|
||||
[[ "${updatectl_output}" != *"Unrecognized object field"* ]]
|
||||
}
|
||||
|
||||
for sector_size in "${SECTOR_SIZES[@]}"; do
|
||||
@@ -461,8 +461,16 @@ EOF
|
||||
verify_version_current "$blockdev" "$sector_size" v5 2
|
||||
|
||||
# Now let's try enabling an optional feature
|
||||
"$SYSUPDATE" features | grep "optional"
|
||||
"$SYSUPDATE" features optional | grep "99-optional"
|
||||
if [[ "$client" == "sysupdate-cli" ]]; then
|
||||
"$SYSUPDATE" features | grep "optional"
|
||||
"$SYSUPDATE" features optional | grep "99-optional"
|
||||
elif [[ "$client" == "varlink" ]]; then
|
||||
[[ $(varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListFeatures '{"target":{"class":"host"}}' | jq -r '.features[] | select(.id=="optional") | .description') == "Optional Feature" ]]
|
||||
varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListFeatures '{"target":{"class":"host"}}' | jq -r '.features[] | select(.id=="optional") | .transfers' | grep "99-optional"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v5.efi.extra.d/optional.efi"
|
||||
mkdir "$CONFIGDIR/optional.feature.d"
|
||||
echo -e "[Feature]\nEnabled=true" > "$CONFIGDIR/optional.feature.d/enable.conf"
|
||||
@@ -639,8 +647,11 @@ MatchPattern=some-component_@v
|
||||
CurrentSymlink=some-component
|
||||
EOF
|
||||
"$SYSUPDATE" --json=short components | grep -F '{"default":false,"components":["some-component"]}' >/dev/null
|
||||
varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListTargets | jq -e '.targets | all(.id.class != "host")' >/dev/null
|
||||
mkdir /run/sysupdate.d
|
||||
"$SYSUPDATE" --json=short components | grep -F '{"default":false,"components":["some-component"]}' >/dev/null
|
||||
varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListTargets | jq -e '.targets | all(.id.class != "host")' >/dev/null
|
||||
[[ $(varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListTargets | jq -r '.targets[0].id.name') == "some-component" ]]
|
||||
|
||||
# Regression test for https://github.com/systemd/systemd/issues/42330 — the
|
||||
# 'pending'/'reboot' verbs and the '--reboot' switch compare the newest installed
|
||||
@@ -674,8 +685,15 @@ Path=$WORKDIR/blobs
|
||||
MatchPattern=tiny-@v.bin
|
||||
InstancesMax=1
|
||||
EOF
|
||||
|
||||
"$SYSUPDATE" --verify=no update
|
||||
cmp "$WORKDIR/source/tiny-v1.bin" "$WORKDIR/blobs/tiny-v1.bin"
|
||||
|
||||
# Test that listing features when none are configured gives an empty list.
|
||||
"$SYSUPDATE" features |& grep "No features." >/dev/null
|
||||
[[ $(varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListFeatures '{"target":{"class":"host"}}' | jq -r '.features') == "[]" ]]
|
||||
|
||||
# Cleanup
|
||||
rm "$CONFIGDIR/01-tiny-url.transfer"
|
||||
rm "$WORKDIR/source/tiny-v1.bin"
|
||||
rm "$WORKDIR/source/SHA256SUMS"
|
||||
@@ -1426,4 +1444,8 @@ cmp "$WORKDIR/source/pack-v2" "$WORKDIR/blobs/pack-v2"
|
||||
rm -rf "$CONFIGDIR/01-manifest-yes-and-retry.transfer" \
|
||||
"$WORKDIR/source/pack-v1" "$WORKDIR/source/pack-v2"
|
||||
|
||||
# Test that listing components/targets when none are configured gives an empty list.
|
||||
"$SYSUPDATE" components |& grep "No components defined." >/dev/null
|
||||
[[ $(varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.ListTargets | jq -r '.targets') == "[]" ]]
|
||||
|
||||
touch /testok
|
||||
|
||||
Reference in New Issue
Block a user