From 0cd1a58921cf986f6613480dfa77da09e2329010 Mon Sep 17 00:00:00 2001 From: Adrian Vovk Date: Wed, 3 Jul 2024 17:49:36 -0400 Subject: [PATCH] sysupdate: Add verb to inspect features --- man/systemd-sysupdate.xml | 10 +++ src/sysupdate/sysupdate-transfer.c | 10 +++ src/sysupdate/sysupdate-transfer.h | 4 + src/sysupdate/sysupdate.c | 138 ++++++++++++++++++++++++++++- test/units/TEST-72-SYSUPDATE.sh | 2 + 5 files changed, 163 insertions(+), 1 deletion(-) diff --git a/man/systemd-sysupdate.xml b/man/systemd-sysupdate.xml index f7f1521a5d..778ff2e793 100644 --- a/man/systemd-sysupdate.xml +++ b/man/systemd-sysupdate.xml @@ -106,6 +106,16 @@ + + FEATURE + + If invoked without an argument, enumerates optional features and shows a summarizing + table, including which features are enabled or disabled. If a feature argument is specified, shows + details about the specific feature, including the transfers that are controlled by the feature. + + + + diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index 350a12ee0f..1143c3cf63 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -45,11 +45,16 @@ Transfer* transfer_free(Transfer *t) { t->temporary_path = rm_rf_subvolume_and_free(t->temporary_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); @@ -520,6 +525,7 @@ int transfer_read_definition(Transfer *t, const char *path, const char **dirs, H }; _cleanup_free_ char *filename = NULL; + char *e; int r; assert(path); @@ -545,6 +551,10 @@ int transfer_read_definition(Transfer *t, const char *path, const char **dirs, H 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)) diff --git a/src/sysupdate/sysupdate-transfer.h b/src/sysupdate/sysupdate-transfer.h index 841161846f..505c573d63 100644 --- a/src/sysupdate/sysupdate-transfer.h +++ b/src/sysupdate/sysupdate-transfer.h @@ -15,11 +15,15 @@ typedef struct Transfer Transfer; #include "sysupdate.h" struct Transfer { + char *id; + char *min_version; char **protected_versions; char *current_symlink; bool verify; + char **features; + char **requisite_features; bool enabled; Resource source, target; diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 0cdcb34a58..6b97af111b 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -1201,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; @@ -1517,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" @@ -1729,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 }, diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index 0598ef2c39..e5c62c635e 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -303,6 +303,8 @@ 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" 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" -- 2.25.1