From 8f114904fc0ee5b3d4b12cfb0231d90d5142f9c0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 11:38:30 +0100 Subject: [PATCH 1/2] analyze: add verb for showing system's CHIDs We have the code already, expose it in systemd-analyze too. This should make it easier to debug the CHID use in the UKIs with onboard tooling. --- man/systemd-analyze.xml | 36 +++++ shell-completion/bash/systemd-analyze | 2 +- src/analyze/analyze-chid.c | 224 ++++++++++++++++++++++++++ src/analyze/analyze-chid.h | 4 + src/analyze/analyze.c | 3 + src/analyze/meson.build | 1 + src/fundamental/chid-fundamental.c | 2 +- src/fundamental/chid-fundamental.h | 2 + test/units/TEST-65-ANALYZE.sh | 5 + 9 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 src/analyze/analyze-chid.c create mode 100644 src/analyze/analyze-chid.h diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index 68d006a1ce..cf005db70b 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -205,6 +205,11 @@ OPTIONS smbios11 + + systemd-analyze + OPTIONS + chid + @@ -1084,6 +1089,37 @@ io.systemd.credential:vmm.notify_socket=vsock-stream:2:254570042 + + <command>systemd-analyze chid</command> + + Shows a list of Computer Hardware IDs (CHIDs) of the local system. These IDs identify the + system's computer hardware, based on SMBIOS data. See Using Computer + Hardware IDs (CHIDs) for details about CHIDs. + + + Example output + $ systemd-analyze chid +TYPE INPUT CHID + 3 MFPSmp 520537c0-3b59-504f-b062-9682ea236b21 + 4 MFPS-- edf05dc8-a53d-5b2c-8023-630bca2a2463 + 5 MFP--- ebc6a4d9-ec48-537a-916b-c69fa4fdd814 + 6 M--Smp 5ebe4bba-f598-5e90-9ff2-9fd0d3211465 + 7 M--S-- 1a3fb835-b42a-5f9c-a38c-eff5bfd5c41d + 8 M-P-mp 2a831dce-8163-5bad-8406-435b8c752dd8 + 9 M-P--- 7c21c878-4a75-50f7-9816-21e811588da0 + 10 MF--mp 9a003537-bcc5-500e-b10a-8d8892e4fc64 + 11 MF---- bb9122bb-8a5c-50d2-a742-a85beb719909 + 13 M---mp bfc36935-5032-5987-a0a3-6311f01de33a + +LEGEND: M → sys_vendor (LENOVO) ┄ F → product_family (ThinkPad X1 Carbon Gen 9) ┄ P → product_name (20XW0055GE) + S → product_sku (LENOVO_MT_20XW_BU_Think_FM_ThinkPad X1 Carbon Gen 9) ┄ m → board_vendor (LENOVO) + p → board_name (20XW0055GE) + + + + + diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index caec77e718..41a2151e12 100644 --- a/shell-completion/bash/systemd-analyze +++ b/shell-completion/bash/systemd-analyze @@ -67,7 +67,7 @@ _systemd_analyze() { ) local -A VERBS=( - [STANDALONE]='time blame unit-files unit-paths exit-status compare-versions calendar timestamp timespan pcrs srk has-tpm2 smbios11' + [STANDALONE]='time blame unit-files unit-paths exit-status compare-versions calendar timestamp timespan pcrs srk has-tpm2 smbios11 chid' [CRITICAL_CHAIN]='critical-chain' [DOT]='dot' [DUMP]='dump' diff --git a/src/analyze/analyze-chid.c b/src/analyze/analyze-chid.c new file mode 100644 index 0000000000..5bb50f54d8 --- /dev/null +++ b/src/analyze/analyze-chid.c @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "analyze.h" +#include "analyze-chid.h" +#include "chid-fundamental.h" +#include "efi-api.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-table.h" +#include "parse-util.h" +#include "strv.h" +#include "utf8.h" +#include "virt.h" + +static int parse_chid_type(const char *s, size_t *ret) { + unsigned u; + int r; + + assert(s); + + r = safe_atou(s, &u); + if (r < 0) + return r; + if (u >= CHID_TYPES_MAX) + return -ERANGE; + + if (ret) + *ret = u; + + return 0; +} + +static const char chid_smbios_fields_char[_CHID_SMBIOS_FIELDS_MAX] = { + [CHID_SMBIOS_MANUFACTURER] = 'M', + [CHID_SMBIOS_FAMILY] = 'F', + [CHID_SMBIOS_PRODUCT_NAME] = 'P', + [CHID_SMBIOS_PRODUCT_SKU] = 'S', + [CHID_SMBIOS_BASEBOARD_MANUFACTURER] = 'm', + [CHID_SMBIOS_BASEBOARD_PRODUCT] = 'p', +}; + +static char *chid_smbios_fields_string(uint32_t combination) { + _cleanup_free_ char *s = NULL; + + for (ChidSmbiosFields f = 0; f < _CHID_SMBIOS_FIELDS_MAX; f++) { + char c; + + c = (combination & (UINT32_C(1) << f)) ? chid_smbios_fields_char[f] : '-'; + + if (!strextend(&s, CHAR_TO_STR(c))) + return NULL; + } + + return TAKE_PTR(s); +} + +static int add_chid(Table *table, const EFI_GUID guids[static CHID_TYPES_MAX], size_t t) { + int r; + + assert(table); + assert(guids); + assert(t < CHID_TYPES_MAX); + + sd_id128_t id = efi_guid_to_id128(guids + t); + + if (sd_id128_is_null(id)) + return 0; + + _cleanup_free_ char *flags = chid_smbios_fields_string(chid_smbios_table[t]); + if (!flags) + return log_oom(); + + r = table_add_many(table, + TABLE_UINT, (unsigned) t, + TABLE_STRING, flags, + TABLE_UUID, id); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +static void smbios_fields_free(char16_t *(*fields)[_CHID_SMBIOS_FIELDS_MAX]) { + assert(fields); + + FOREACH_ARRAY(i, *fields, _CHID_SMBIOS_FIELDS_MAX) + free(*i); +} + +int verb_chid(int argc, char *argv[], void *userdata) { + + static const char *const smbios_files[_CHID_SMBIOS_FIELDS_MAX] = { + [CHID_SMBIOS_MANUFACTURER] = "sys_vendor", + [CHID_SMBIOS_FAMILY] = "product_family", + [CHID_SMBIOS_PRODUCT_NAME] = "product_name", + [CHID_SMBIOS_PRODUCT_SKU] = "product_sku", + [CHID_SMBIOS_BASEBOARD_MANUFACTURER] = "board_vendor", + [CHID_SMBIOS_BASEBOARD_PRODUCT] = "board_name", + }; + + _cleanup_(table_unrefp) Table *table = NULL; + int r; + + if (detect_container() > 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Container environments do not have SMBIOS."); + + table = table_new("type", "input", "chid"); + if (!table) + return log_oom(); + + (void) table_set_align_percent(table, table_get_cell(table, 0, 0), 100); + (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 50); + + _cleanup_close_ int smbios_fd = open("/sys/class/dmi/id", O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (smbios_fd < 0) + return log_error_errno(errno, "Failed to open SMBIOS sysfs object: %m"); + + _cleanup_(smbios_fields_free) char16_t* smbios_fields[_CHID_SMBIOS_FIELDS_MAX] = {}; + for (ChidSmbiosFields f = 0; f < _CHID_SMBIOS_FIELDS_MAX; f++) { + _cleanup_free_ char *buf = NULL; + size_t size; + + r = read_virtual_file_at(smbios_fd, smbios_files[f], SIZE_MAX, &buf, &size); + if (r < 0) + return log_error_errno(r, "Failed to read SMBIOS field '%s': %m", smbios_files[f]); + + if (size < 1 || buf[size-1] != '\n') + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected SMBIOS field '%s' to end in newline, but it doesn't, refusing.", smbios_files[f]); + + size--; + + smbios_fields[f] = utf8_to_utf16(buf, size); + if (!smbios_fields[f]) + return log_oom(); + } + + EFI_GUID chids[CHID_TYPES_MAX] = {}; + chid_calculate((const char16_t* const*) smbios_fields, chids); + + if (strv_isempty(strv_skip(argv, 1))) + for (size_t t = 0; t < CHID_TYPES_MAX; t++) { + r = add_chid(table, chids, t); + if (r < 0) + return r; + } + else { + STRV_FOREACH(as, strv_skip(argv, 1)) { + size_t t; + r = parse_chid_type(*as, &t); + if (r < 0) + return log_error_errno(r, "Failed to pare CHID type: %s", *as); + + r = add_chid(table, chids, t); + if (r < 0) + return r; + } + + (void) table_set_sort(table, (size_t) 0); + } + + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return log_error_errno(r, "Failed to output table: %m"); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_free_ char *legend = NULL; + bool separator = false; + size_t w = 0; + + legend = strjoin(ansi_grey(), "LEGEND: ", ansi_normal()); + if (!legend) + return log_oom(); + + for (ChidSmbiosFields f = 0; f < _CHID_SMBIOS_FIELDS_MAX; f++) { + _cleanup_free_ char *c = utf16_to_utf8(smbios_fields[f], SIZE_MAX); + if (!c) + return log_oom(); + + if (!strextend(&legend, + ansi_grey(), + separator ? " " : "", + separator ? special_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED) : "", + separator ? " " : "", + ansi_normal(), + CHAR_TO_STR(chid_smbios_fields_char[f]), + ansi_grey(), + " ", + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + " ", + ansi_normal(), + smbios_files[f], + ansi_grey(), + " (", + ansi_highlight(), + c, + ansi_grey(), + ")", + ansi_normal())) + return log_oom(); + + w += separator * 3 + + 4 + + utf8_console_width(smbios_files[f]) + + 2 + + utf8_console_width(c) + + 1; + + if (w > 79) { + if (!strextend(&legend, "\n ")) + return log_oom(); + + separator = false; + w = 8; + } else + separator = true; + + } + + putchar('\n'); + puts(legend); + } + + return EXIT_SUCCESS; +} diff --git a/src/analyze/analyze-chid.h b/src/analyze/analyze-chid.h new file mode 100644 index 0000000000..a3f40c6013 --- /dev/null +++ b/src/analyze/analyze-chid.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int verb_chid(int argc, char *argv[], void *userdata); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index e21f12c65e..ad3dd446ab 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -18,6 +18,7 @@ #include "analyze-calendar.h" #include "analyze-capability.h" #include "analyze-cat-config.h" +#include "analyze-chid.h" #include "analyze-compare-versions.h" #include "analyze-condition.h" #include "analyze-critical-chain.h" @@ -219,6 +220,7 @@ static int help(int argc, char *argv[], void *userdata) { " filesystems [NAME...] List known filesystems\n" " architectures [NAME...] List known architectures\n" " smbios11 List strings passed via SMBIOS Type #11\n" + " chid List local CHIDs\n" "\n%3$sExpression Evaluation:%4$s\n" " condition CONDITION... Evaluate conditions and asserts\n" " compare-versions VERSION1 [OP] VERSION2\n" @@ -691,6 +693,7 @@ static int run(int argc, char *argv[]) { { "srk", VERB_ANY, 1, 0, verb_srk }, { "architectures", VERB_ANY, VERB_ANY, 0, verb_architectures }, { "smbios11", VERB_ANY, 1, 0, verb_smbios11 }, + { "chid", VERB_ANY, VERB_ANY, 0, verb_chid }, {} }; diff --git a/src/analyze/meson.build b/src/analyze/meson.build index c42db1a633..5a7c6c9285 100644 --- a/src/analyze/meson.build +++ b/src/analyze/meson.build @@ -6,6 +6,7 @@ systemd_analyze_sources = files( 'analyze-calendar.c', 'analyze-capability.c', 'analyze-cat-config.c', + 'analyze-chid.c', 'analyze-compare-versions.c', 'analyze-condition.c', 'analyze-critical-chain.c', diff --git a/src/fundamental/chid-fundamental.c b/src/fundamental/chid-fundamental.c index 01045176f5..9c719a334d 100644 --- a/src/fundamental/chid-fundamental.c +++ b/src/fundamental/chid-fundamental.c @@ -61,7 +61,7 @@ static void get_chid(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIE ret_chid->Data4[0] = (ret_chid->Data4[0] & UINT8_C(0x3f)) | UINT8_C(0x80); } -static const uint32_t chid_smbios_table[CHID_TYPES_MAX] = { +const uint32_t chid_smbios_table[CHID_TYPES_MAX] = { [3] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) | (UINT32_C(1) << CHID_SMBIOS_FAMILY) | (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME) | diff --git a/src/fundamental/chid-fundamental.h b/src/fundamental/chid-fundamental.h index 1e582932fd..41f7be337d 100644 --- a/src/fundamental/chid-fundamental.h +++ b/src/fundamental/chid-fundamental.h @@ -23,5 +23,7 @@ typedef enum ChidSmbiosFields { _CHID_SMBIOS_FIELDS_MAX, } ChidSmbiosFields; +extern const uint32_t chid_smbios_table[CHID_TYPES_MAX]; + /* CHID (also called HWID by fwupd) is described at https://github.com/fwupd/fwupd/blob/main/docs/hwids.md */ void chid_calculate(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], EFI_GUID ret_chids[static CHID_TYPES_MAX]); diff --git a/test/units/TEST-65-ANALYZE.sh b/test/units/TEST-65-ANALYZE.sh index 2fa368a678..ac39c42da4 100755 --- a/test/units/TEST-65-ANALYZE.sh +++ b/test/units/TEST-65-ANALYZE.sh @@ -990,6 +990,11 @@ systemd-analyze architectures uname systemd-analyze smbios11 systemd-analyze smbios11 -q +if test -f /sys/class/dmi/id/board_vendor && ! systemd-detect-virt --container ; then + systemd-analyze chid + systemd-analyze chid --json=pretty +fi + systemd-analyze condition --instance=tmp --unit=systemd-growfs@.service systemd-analyze verify --instance=tmp --man=no systemd-growfs@.service systemd-analyze security --instance=tmp systemd-growfs@.service From 0e5a83f5102716dc44f15e92a8fb76029652f696 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 15 Nov 2024 11:40:44 +0100 Subject: [PATCH 2/2] analyze: drop conditioning of --no-legend and --json= on specific verbs First of all, the list of verbs was badly out of date, in particular for --no-legend. But second if it, I think such minor switches that alter some detail of the output should not result in failure when the specific tweak does not apply on some command. It should be fine for scripts and suchlike to dumbly always pass --no-legend to all invocations of our tools without having to consider if a specific subtool of ours actually supports it or not. --- src/analyze/analyze.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index ad3dd446ab..d1619dbb63 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -595,10 +595,6 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --offline= requires one or more units to perform a security review."); - if (sd_json_format_enabled(arg_json_format_flags) && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot", "fdstore", "pcrs", "architectures", "capability", "exit-status")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Option --json= is only supported for security, inspect-elf, plot, fdstore, pcrs, architectures, capability, exit-status right now."); - if (arg_threshold != 100 && !streq_ptr(argv[optind], "security")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --threshold= is only supported for security right now."); @@ -633,10 +629,6 @@ static int parse_argv(int argc, char *argv[]) { if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used."); - if ((!arg_legend && !STRPTR_IN_SET(argv[optind], "plot", "architectures")) || - (streq_ptr(argv[optind], "plot") && !arg_legend && !arg_table && !sd_json_format_enabled(arg_json_format_flags))) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --no-legend is only supported for plot with either --table or --json=."); - if (arg_table && !streq_ptr(argv[optind], "plot")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --table is only supported for plot right now.");