From: Philip Withnall Date: Mon, 1 Jun 2026 17:35:54 +0000 (+0100) Subject: sysupdate: Add varlink ListFeatures() method X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3e34ccf6df88069fb4f06eae29538cd7583a4b40;p=thirdparty%2Fsystemd.git sysupdate: Add varlink ListFeatures() method And add integration tests for it using `jq`. --- diff --git a/src/shared/varlink-io.systemd.SysUpdate.c b/src/shared/varlink-io.systemd.SysUpdate.c index e80142f9c51..94a7180a509 100644 --- a/src/shared/varlink-io.systemd.SysUpdate.c +++ b/src/shared/varlink-io.systemd.SysUpdate.c @@ -25,6 +25,28 @@ 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( + 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("A URL to documentation for the feature."), + SD_VARLINK_DEFINE_FIELD(documentation, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + 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."), @@ -42,6 +64,8 @@ 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, @@ -50,6 +74,8 @@ SD_VARLINK_DEFINE_INTERFACE( &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 feature."), + &vl_type_Feature, /* Errors */ SD_VARLINK_SYMBOL_COMMENT("Error indicating that no update is currently available to update to."), diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 19fd136931a..9ed5d8db464 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -2241,6 +2241,91 @@ 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; + int r; + + assert(context); + assert(f); + assert(ret); + + r = context_get_transfers_for_feature(context, f->id, &transfers); + if (r < 0) + return r; + + 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_STRING_NON_EMPTY("documentation", f->documentation), + 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 online */ + context.offline = false; + + r = context_load_online_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) { @@ -3182,7 +3267,8 @@ 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); if (r < 0) return log_error_errno(r, "Failed to bind Varlink method: %m"); diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index d3a8a4bc61f..6e3e3603b0c 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -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" @@ -674,8 +682,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"