From: Lennart Poettering Date: Wed, 24 Jun 2026 15:09:19 +0000 (+0200) Subject: sysupdate: add a "suggests" concept to features and components X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=40e727c93b5582ed5e1e550c1cd2b5c35683e10e;p=thirdparty%2Fsystemd.git sysupdate: add a "suggests" concept to features and components Let's make it possible to "suggest" that certain features or components are enabled under some conditions. For this, both features and components gain two things: 1. A Suggested= field which takes a boolean. If true the feature/component will be suggested for installation, if false it will not. 2. A set of SuggestedOnXYZ= settings are modelled after ConditionXYZ= in unit files (and implement a subset of them), will suggest some component/feature under specific conditions. The result of the condition is shown in the various output tools. --- diff --git a/man/sysupdate.features.xml b/man/sysupdate.features.xml index 606292e2694..d3a7a638101 100644 --- a/man/sysupdate.features.xml +++ b/man/sysupdate.features.xml @@ -109,6 +109,53 @@ + + + Suggest= + + Takes a boolean argument. If true, this feature is suggested for enablement, if + false it is not. This does not enable the feature on its own, but is a hint surfaced by the update + tools (for example via the switch of + systemd-sysupdate8) + so that the system administrator or higher-level tooling may act on it. + + If this setting is not specified, the SuggestOn…= conditions described below + are evaluated instead to determine whether the feature is suggested. If neither this setting nor any + SuggestOn…= condition is specified, the feature is not suggested. + + + + + + SuggestOnArchitecture= + SuggestOnFirmware= + SuggestOnVirtualization= + SuggestOnHost= + SuggestOnFraction= + SuggestOnKernelCommandLine= + SuggestOnVersion= + SuggestOnCredential= + SuggestOnSecurity= + SuggestOnOSRelease= + SuggestOnMachineTag= + + Suggest this feature for enablement depending on system properties. These settings + take the same arguments and implement the same semantics — including the leading ! + for negation — as the identically-named + ConditionArchitecture=, ConditionFirmware=, + ConditionVirtualization=, ConditionHost=, + ConditionFraction=, ConditionKernelCommandLine=, + ConditionVersion=, ConditionCredential=, + ConditionSecurity=, ConditionOSRelease= and + ConditionMachineTag= settings for unit files, which are documented in + systemd.unit5. + The feature is suggested if all specified conditions apply. As with the Condition…= + settings, assigning an empty string to one of these resets the list. + + These conditions are only evaluated if Suggest= is not specified. + + + diff --git a/src/shared/shared-forward.h b/src/shared/shared-forward.h index 751a6f71dc3..a9c7101e558 100644 --- a/src/shared/shared-forward.h +++ b/src/shared/shared-forward.h @@ -19,6 +19,7 @@ typedef enum BusPrintPropertyFlags BusPrintPropertyFlags; typedef enum BusTransport BusTransport; typedef enum CatFlags CatFlags; typedef enum CertificateSourceType CertificateSourceType; +typedef enum ConditionType ConditionType; typedef enum DnsAnswerFlags DnsAnswerFlags; typedef enum DnsCacheMode DnsCacheMode; typedef enum DnsOverTlsMode DnsOverTlsMode; diff --git a/src/sysupdate/sysupdate-config.c b/src/sysupdate/sysupdate-config.c index 7f553a95f0b..45794b5a212 100644 --- a/src/sysupdate/sysupdate-config.c +++ b/src/sysupdate/sysupdate-config.c @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "condition.h" #include "log.h" #include "specifier.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "sysupdate-config.h" @@ -48,6 +50,22 @@ int config_parse_url_specifiers( return free_and_replace(*s, resolved); } +static const char* const suggest_on_type_table[_CONDITION_TYPE_MAX] = { + [CONDITION_ARCHITECTURE] = "SuggestOnArchitecture", + [CONDITION_FIRMWARE] = "SuggestOnFirmware", + [CONDITION_VIRTUALIZATION] = "SuggestOnVirtualization", + [CONDITION_HOST] = "SuggestOnHost", + [CONDITION_FRACTION] = "SuggestOnFraction", + [CONDITION_KERNEL_COMMAND_LINE] = "SuggestOnKernelCommandLine", + [CONDITION_VERSION] = "SuggestOnVersion", + [CONDITION_CREDENTIAL] = "SuggestOnCredential", + [CONDITION_SECURITY] = "SuggestOnSecurity", + [CONDITION_OS_RELEASE] = "SuggestOnOSRelease", + [CONDITION_MACHINE_TAG] = "SuggestOnMachineTag", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(suggest_on_type, ConditionType); + int config_parse_url_specifiers_many( const char *unit, const char *filename, @@ -90,3 +108,58 @@ int config_parse_url_specifiers_many( return 0; } + +int config_parse_condition( + 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) { + + const char *root = userdata; + ConditionType cond = ltype; + Condition **list = data, *c; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + *list = condition_free_list_type(*list, cond); + return 0; + } + + bool trigger = rvalue[0] == '|'; + if (trigger) { + rvalue++; + rvalue += strspn(rvalue, WHITESPACE); + } + + bool negate = rvalue[0] == '!'; + if (negate) { + rvalue++; + rvalue += strspn(rvalue, WHITESPACE); + } + + _cleanup_free_ char *resolved = NULL; + r = specifier_printf(rvalue, SIZE_MAX, system_and_tmp_specifier_table, root, /* userdata= */ 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; + } + + c = condition_new(cond, resolved, trigger, negate); + if (!c) + return log_oom(); + + LIST_PREPEND(conditions, *list, c); + return 0; +} diff --git a/src/sysupdate/sysupdate-config.h b/src/sysupdate/sysupdate-config.h index bd972d598d5..271430c73d5 100644 --- a/src/sysupdate/sysupdate-config.h +++ b/src/sysupdate/sysupdate-config.h @@ -2,6 +2,10 @@ #pragma once #include "conf-parser-forward.h" +#include "shared-forward.h" CONFIG_PARSER_PROTOTYPE(config_parse_url_specifiers); CONFIG_PARSER_PROTOTYPE(config_parse_url_specifiers_many); +CONFIG_PARSER_PROTOTYPE(config_parse_condition); + +DECLARE_STRING_TABLE_LOOKUP_TO_STRING(suggest_on_type, ConditionType); diff --git a/src/sysupdate/sysupdate-feature.c b/src/sysupdate/sysupdate-feature.c index 49c2e77988b..ef284f7f701 100644 --- a/src/sysupdate/sysupdate-feature.c +++ b/src/sysupdate/sysupdate-feature.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "alloc-util.h" +#include "condition.h" #include "conf-parser.h" #include "hash-funcs.h" #include "path-util.h" @@ -18,6 +21,8 @@ static Feature *feature_free(Feature *f) { free(f->documentation); free(f->appstream); + condition_free_list(f->suggest_on); + return mfree(f); } @@ -30,6 +35,7 @@ Feature *feature_new(void) { *f = (Feature) { .n_ref = 1, + .suggest = -1, }; return f; @@ -45,10 +51,22 @@ int feature_read_definition(Feature *f, const char *root, const char *path, cons 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 }, + { "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 }, + { "Feature", "Suggest", config_parse_tristate, 0, &f->suggest }, + { "Feature", "SuggestOnArchitecture", config_parse_condition, CONDITION_ARCHITECTURE, &f->suggest_on }, + { "Feature", "SuggestOnFirmware", config_parse_condition, CONDITION_FIRMWARE, &f->suggest_on }, + { "Feature", "SuggestOnVirtualization", config_parse_condition, CONDITION_VIRTUALIZATION, &f->suggest_on }, + { "Feature", "SuggestOnHost", config_parse_condition, CONDITION_HOST, &f->suggest_on }, + { "Feature", "SuggestOnFraction", config_parse_condition, CONDITION_FRACTION, &f->suggest_on }, + { "Feature", "SuggestOnKernelCommandLine", config_parse_condition, CONDITION_KERNEL_COMMAND_LINE, &f->suggest_on }, + { "Feature", "SuggestOnVersion", config_parse_condition, CONDITION_VERSION, &f->suggest_on }, + { "Feature", "SuggestOnCredential", config_parse_condition, CONDITION_CREDENTIAL, &f->suggest_on }, + { "Feature", "SuggestOnSecurity", config_parse_condition, CONDITION_SECURITY, &f->suggest_on }, + { "Feature", "SuggestOnOSRelease", config_parse_condition, CONDITION_OS_RELEASE, &f->suggest_on }, + { "Feature", "SuggestOnMachineTag", config_parse_condition, CONDITION_MACHINE_TAG, &f->suggest_on }, {} }; @@ -82,3 +100,15 @@ int feature_read_definition(Feature *f, const char *root, const char *path, cons return 0; } + +int feature_is_suggested(Feature *f) { + assert(f); + + if (f->suggest >= 0) + return f->suggest; + + if (!f->suggest_on) /* no condition → false */ + return false; + + return condition_test_list(f->suggest_on, environ, suggest_on_type_to_string, /* logger= */ NULL, /* userdata= */ NULL); +} diff --git a/src/sysupdate/sysupdate-feature.h b/src/sysupdate/sysupdate-feature.h index 7d7495087c5..3f46ded7f9e 100644 --- a/src/sysupdate/sysupdate-feature.h +++ b/src/sysupdate/sysupdate-feature.h @@ -13,6 +13,9 @@ typedef struct Feature { char *appstream; bool enabled; + + int suggest; + Condition *suggest_on; } Feature; Feature *feature_new(void); @@ -23,3 +26,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Feature*, feature_unref); extern const struct hash_ops feature_hash_ops; int feature_read_definition(Feature *f, const char *root, const char *path, const char *const *conf_file_dirs); + +int feature_is_suggested(Feature *f); diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 56f02811627..793a54d4cab 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -9,6 +9,7 @@ #include "build.h" #include "bus-polkit.h" +#include "condition.h" #include "conf-files.h" #include "conf-parser.h" #include "constants.h" @@ -38,6 +39,7 @@ #include "pretty-print.h" #include "runtime-scope.h" #include "sort-util.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "sysupdate.h" @@ -87,6 +89,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep); .cleanup = -1, \ .installdb_fd = -EBADF, \ .target_identifier.class = _TARGET_CLASS_INVALID, \ + .component_suggest = -1, \ } void context_done(Context *c) { @@ -126,6 +129,7 @@ void context_done(Context *c) { c->transfer_source = mfree(c->transfer_source); target_identifier_done(&c->target_identifier); + condition_free_list(c->component_suggest_on); } static int context_from_cmdline(Context *ret) { @@ -346,9 +350,21 @@ static int read_component(Context *c) { return log_oom(); ConfigTableItem table[] = { - { "Component", "Description", config_parse_string, 0, &c->component_description }, - { "Component", "Documentation", config_parse_url_specifiers_many, 0, &c->component_documentation }, - { "Component", "Enabled", config_parse_bool, 0, &c->component_enabled }, + { "Component", "Description", config_parse_string, 0, &c->component_description }, + { "Component", "Documentation", config_parse_url_specifiers_many, 0, &c->component_documentation }, + { "Component", "Enabled", config_parse_bool, 0, &c->component_enabled }, + { "Component", "Suggest", config_parse_tristate, 0, &c->component_suggest }, + { "Component", "SuggestOnArchitecture", config_parse_condition, CONDITION_ARCHITECTURE, &c->component_suggest_on }, + { "Component", "SuggestOnFirmware", config_parse_condition, CONDITION_FIRMWARE, &c->component_suggest_on }, + { "Component", "SuggestOnVirtualization", config_parse_condition, CONDITION_VIRTUALIZATION, &c->component_suggest_on }, + { "Component", "SuggestOnHost", config_parse_condition, CONDITION_HOST, &c->component_suggest_on }, + { "Component", "SuggestOnFraction", config_parse_condition, CONDITION_FRACTION, &c->component_suggest_on }, + { "Component", "SuggestOnKernelCommandLine", config_parse_condition, CONDITION_KERNEL_COMMAND_LINE, &c->component_suggest_on }, + { "Component", "SuggestOnVersion", config_parse_condition, CONDITION_VERSION, &c->component_suggest_on }, + { "Component", "SuggestOnCredential", config_parse_condition, CONDITION_CREDENTIAL, &c->component_suggest_on }, + { "Component", "SuggestOnSecurity", config_parse_condition, CONDITION_SECURITY, &c->component_suggest_on }, + { "Component", "SuggestOnOSRelease", config_parse_condition, CONDITION_OS_RELEASE, &c->component_suggest_on }, + { "Component", "SuggestOnMachineTag", config_parse_condition, CONDITION_MACHINE_TAG, &c->component_suggest_on }, {} }; @@ -1876,6 +1892,20 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata if (r < 0) return table_log_add_error(r); + r = feature_is_suggested(f); + if (r < 0) { + errno = -r; /* Let's make %m below show this error */ + _cleanup_free_ char *k = asprintf_safe("error (%m)"); + r = table_add_many(table, + TABLE_FIELD, "Suggested", + TABLE_STRING, k); + } else + r = table_add_many(table, + TABLE_FIELD, "Suggested", + TABLE_BOOLEAN, r); + 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) @@ -1908,14 +1938,28 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata 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"); + table = table_new("enabled", "suggested", "feature", "description", "documentation"); if (!table) return log_oom(); + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + 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_SET_COLOR, ansi_highlight_green_red(f->enabled)); + if (r < 0) + return table_log_add_error(r); + + r = feature_is_suggested(f); + if (r < 0) + r = table_add_many(table, TABLE_EMPTY); + else + r = table_add_many(table, TABLE_BOOLEAN_CHECKMARK, r); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many(table, TABLE_STRING, f->id, TABLE_STRING, f->description, TABLE_STRING, f->documentation, @@ -2435,16 +2479,30 @@ static int context_list_components(Context *context, char ***ret_component_names return 0; } +static int context_component_is_suggested(Context *c) { + assert(c); + + /* Only applies to components, not to the main system */ + if (!c->component) + return -ENOTTY; + + if (c->component_suggest >= 0) + return c->component_suggest; + + if (!c->component_suggest_on) /* no condition → false */ + return false; + + return condition_test_list(c->component_suggest_on, environ, suggest_on_type_to_string, /* logger= */ NULL, /* userdata= */ NULL); +} + VERB_NOARG(verb_components, "components", "Show list of components"); static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(context_done) Context context = CONTEXT_NULL; - _cleanup_strv_free_ char **component_names = NULL; - bool has_default_component = false; int r; assert(argc <= 1); + _cleanup_(context_done) Context context = CONTEXT_NULL; r = context_from_cmdline(&context); if (r < 0) return r; @@ -2459,6 +2517,8 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda if (r < 0) return r; + _cleanup_strv_free_ char **component_names = NULL; + bool has_default_component = false; r = context_list_components(&context, &component_names, &has_default_component); if (r < 0) return log_error_errno(r, "Failed to enumerate components: %m"); @@ -2469,7 +2529,7 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda return 0; } - _cleanup_(table_unrefp) Table *t = table_new("", "component", "description", "documentation"); + _cleanup_(table_unrefp) Table *t = table_new("enabled", "suggested", "component", "description", "documentation"); if (!t) return log_oom(); @@ -2479,6 +2539,7 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda r = table_add_many( t, TABLE_EMPTY, + TABLE_EMPTY, TABLE_STRING, "", TABLE_SET_COLOR, ansi_highlight(), TABLE_STRING, "Default Component", @@ -2506,12 +2567,25 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda if (r < 0) continue; + r = table_add_many( + t, + TABLE_BOOLEAN_CHECKMARK, cc.component_enabled, + TABLE_SET_COLOR, ansi_highlight_green_red(cc.component_enabled)); + if (r < 0) + return table_log_add_error(r); + + r = context_component_is_suggested(&cc); + if (r < 0) + r = table_add_many(t, TABLE_EMPTY); + else + r = table_add_many(t, TABLE_BOOLEAN_CHECKMARK, r); + if (r < 0) + return table_log_add_error(r); + const char *doc = cc.component_documentation ? cc.component_documentation[0] : NULL; r = table_add_many( t, - TABLE_BOOLEAN_CHECKMARK, cc.component_enabled, - TABLE_SET_COLOR, ansi_highlight_green_red(cc.component_enabled), TABLE_STRING, *i, TABLE_STRING, cc.component_description, TABLE_STRING, doc, @@ -2850,10 +2924,16 @@ static int vl_server(void) { static int run(int argc, char *argv[]) { int r; + LIBAPPARMOR_NOTE(recommended); + LIBAUDIT_NOTE(recommended); LIBBLKID_NOTE(recommended); LIBCRYPTO_NOTE(suggested); LIBCRYPTSETUP_NOTE(suggested); LIBMOUNT_NOTE(recommended); + LIBSELINUX_NOTE(recommended); + LIBTSS2_ESYS_NOTE(suggested); + LIBTSS2_MU_NOTE(suggested); + LIBTSS2_RC_NOTE(suggested); log_setup(); diff --git a/src/sysupdate/sysupdate.h b/src/sysupdate/sysupdate.h index 6088bde669b..f75f142984b 100644 --- a/src/sysupdate/sysupdate.h +++ b/src/sysupdate/sysupdate.h @@ -28,6 +28,9 @@ typedef struct Context { char **component_documentation; bool component_enabled; + int component_suggest; + Condition *component_suggest_on; + Transfer **transfers; size_t n_transfers;