]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
report: implement facts interface
authorYaping Li <202858510+YapingLi04@users.noreply.github.com>
Tue, 3 Mar 2026 06:41:43 +0000 (22:41 -0800)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Wed, 1 Apr 2026 13:34:45 +0000 (15:34 +0200)
src/core/meson.build
src/core/varlink-facts.c [new file with mode: 0644]
src/core/varlink-facts.h [new file with mode: 0644]
src/core/varlink.c
src/report/report.c
src/shared/facts.c [new file with mode: 0644]
src/shared/facts.h [new file with mode: 0644]
src/shared/meson.build
src/shared/varlink-io.systemd.Facts.c [new file with mode: 0644]
src/shared/varlink-io.systemd.Facts.h [new file with mode: 0644]
test/units/TEST-74-AUX-UTILS.report.sh

index 391dc45a6b294a2c294362e5efb1cb0284881387..353854dafd9a8c625dec4143577ebb016608189e 100644 (file)
@@ -70,6 +70,7 @@ libcore_sources = files(
         'varlink-execute.c',
         'varlink-manager.c',
         'varlink-metrics.c',
+        'varlink-facts.c',
         'varlink-unit.c',
 )
 
diff --git a/src/core/varlink-facts.c b/src/core/varlink-facts.c
new file mode 100644 (file)
index 0000000..695080c
--- /dev/null
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/utsname.h>
+
+#include "sd-id128.h"
+#include "sd-json.h"
+#include "sd-varlink.h"
+
+#include "alloc-util.h"
+#include "architecture.h"
+#include "facts.h"
+#include "hostname-setup.h"
+#include "varlink-facts.h"
+#include "virt.h"
+
+static int architecture_generate(FactFamilyContext *context, void *userdata) {
+        assert(context);
+
+        return fact_build_send_string(
+                        context,
+                        /* object= */ NULL,
+                        architecture_to_string(uname_architecture()));
+}
+
+static int boot_id_generate(FactFamilyContext *context, void *userdata) {
+        sd_id128_t id;
+        int r;
+
+        assert(context);
+
+        r = sd_id128_get_boot(&id);
+        if (r < 0)
+                return r;
+
+        return fact_build_send_string(
+                        context,
+                        /* object= */ NULL,
+                        SD_ID128_TO_STRING(id));
+}
+
+static int hostname_generate(FactFamilyContext *context, void *userdata) {
+        _cleanup_free_ char *hostname = NULL;
+        int r;
+
+        assert(context);
+
+        r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &hostname);
+        if (r < 0)
+                return r;
+
+        return fact_build_send_string(
+                        context,
+                        /* object= */ NULL,
+                        hostname);
+}
+
+static int kernel_version_generate(FactFamilyContext *context, void *userdata) {
+        struct utsname u;
+
+        assert(context);
+
+        assert_se(uname(&u) >= 0);
+
+        return fact_build_send_string(
+                        context,
+                        /* object= */ NULL,
+                        u.release);
+}
+
+static int machine_id_generate(FactFamilyContext *context, void *userdata) {
+        sd_id128_t id;
+        int r;
+
+        assert(context);
+
+        r = sd_id128_get_machine(&id);
+        if (r < 0)
+                return r;
+
+        return fact_build_send_string(
+                        context,
+                        /* object= */ NULL,
+                        SD_ID128_TO_STRING(id));
+}
+
+static int virtualization_generate(FactFamilyContext *context, void *userdata) {
+        Virtualization v;
+
+        assert(context);
+
+        v = detect_virtualization();
+        if (v < 0)
+                return v;
+
+        return fact_build_send_string(
+                        context,
+                        /* object= */ NULL,
+                        virtualization_to_string(v));
+}
+
+const FactFamily fact_family_table[] = {
+        /* Keep facts ordered alphabetically */
+        {
+                .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Architecture",
+                .description = "CPU architecture",
+                .generate = architecture_generate,
+        },
+        {
+                .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "BootID",
+                .description = "Current boot ID",
+                .generate = boot_id_generate,
+        },
+        {
+                .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Hostname",
+                .description = "System hostname",
+                .generate = hostname_generate,
+        },
+        {
+                .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "KernelVersion",
+                .description = "Kernel version",
+                .generate = kernel_version_generate,
+        },
+        {
+                .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "MachineID",
+                .description = "Machine ID",
+                .generate = machine_id_generate,
+        },
+        {
+                .name = FACT_IO_SYSTEMD_MANAGER_PREFIX "Virtualization",
+                .description = "Virtualization type",
+                .generate = virtualization_generate,
+        },
+        {}
+};
+
+int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return facts_method_describe(fact_family_table, link, parameters, flags, userdata);
+}
+
+int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return facts_method_list(fact_family_table, link, parameters, flags, userdata);
+}
diff --git a/src/core/varlink-facts.h b/src/core/varlink-facts.h
new file mode 100644 (file)
index 0000000..5c303c8
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "core-forward.h"
+
+#define FACT_IO_SYSTEMD_MANAGER_PREFIX "io.systemd.Manager."
+
+int vl_method_list_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int vl_method_describe_facts(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
index 533d1061b8eb7d583b1635c55576d48275e4b581..e4e598a3dae4628997ac9c1cc5b720450210e464 100644 (file)
@@ -5,6 +5,7 @@
 #include "constants.h"
 #include "errno-util.h"
 #include "manager.h"
+#include "facts.h"
 #include "metrics.h"
 #include "path-util.h"
 #include "pidref.h"
@@ -12,6 +13,7 @@
 #include "unit.h"
 #include "varlink.h"
 #include "varlink-dynamic-user.h"
+#include "varlink-facts.h"
 #include "varlink-io.systemd.ManagedOOM.h"
 #include "varlink-io.systemd.Manager.h"
 #include "varlink-io.systemd.Unit.h"
@@ -433,16 +435,29 @@ int manager_setup_varlink_server(Manager *m) {
 }
 
 int manager_setup_varlink_metrics_server(Manager *m) {
+        int r;
+
         assert(m);
 
         sd_varlink_server_flags_t flags = SD_VARLINK_SERVER_INHERIT_USERDATA;
         if (MANAGER_IS_SYSTEM(m))
                 flags |= SD_VARLINK_SERVER_ACCOUNT_UID;
 
-        return metrics_setup_varlink_server(&m->metrics_varlink_server, flags,
+        r = metrics_setup_varlink_server(&m->metrics_varlink_server, flags,
                                             m->event, EVENT_PRIORITY_IPC,
                                             vl_method_list_metrics, vl_method_describe_metrics,
                                             m);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                /* Server newly created — also register facts interface on it */
+                int q = facts_add_to_varlink_server(m->metrics_varlink_server,
+                                                     vl_method_list_facts, vl_method_describe_facts);
+                if (q < 0)
+                        return q;
+        }
+
+        return r;
 }
 
 static int varlink_server_listen_many_idempotent_sentinel(
index be7b89b8214726a43cc736ef64ff8fece282a075..44ed7535334ff8344177f07fa6f9ebeb118231a2 100644 (file)
@@ -41,6 +41,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep);
 typedef enum Action {
         ACTION_LIST,
         ACTION_DESCRIBE,
+        ACTION_LIST_FACTS,
+        ACTION_DESCRIBE_FACTS,
         _ACTION_MAX,
         _ACTION_INVALID = -EINVAL,
 } Action;
@@ -213,7 +215,7 @@ static Verdict metrics_verdict(LinkInfo *li, sd_json_variant *metric) {
         return VERDICT_MATCH;
 }
 
-static int metrics_on_query_reply(
+static int on_query_reply(
                 sd_varlink *link,
                 sd_json_variant *parameters,
                 const char *error_id,
@@ -288,14 +290,20 @@ static int metrics_call(Context *context, const char *name, const char *path) {
         if (r < 0)
                 return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
 
-        r = sd_varlink_bind_reply(vl, metrics_on_query_reply);
+        r = sd_varlink_bind_reply(vl, on_query_reply);
         if (r < 0)
                 return log_error_errno(r, "Failed to bind reply callback: %m");
 
-        const char *method = context->action == ACTION_LIST ? "io.systemd.Metrics.List" : "io.systemd.Metrics.Describe";
-        r = sd_varlink_observe(vl,
-                               method,
-                               /* parameters= */ NULL);
+        const char *method;
+        switch (context->action) {
+        case ACTION_LIST:           method = "io.systemd.Metrics.List"; break;
+        case ACTION_DESCRIBE:       method = "io.systemd.Metrics.Describe"; break;
+        case ACTION_LIST_FACTS:     method = "io.systemd.Facts.List"; break;
+        case ACTION_DESCRIBE_FACTS: method = "io.systemd.Facts.Describe"; break;
+        default: assert_not_reached();
+        }
+
+        r = sd_varlink_observe(vl, method, /* parameters= */ NULL);
         if (r < 0)
                 return log_error_errno(r, "Failed to issue %s() call: %m", method);
 
@@ -308,7 +316,6 @@ static int metrics_call(Context *context, const char *name, const char *path) {
                 .link = sd_varlink_ref(vl),
                 .name = strdup(name),
         };
-
         if (!li->name)
                 return log_oom();
 
@@ -426,6 +433,105 @@ static int metrics_output_describe(Context *context, Table **ret) {
         return 0;
 }
 
+static int facts_output_list(Context *context, Table **ret) {
+        int r;
+
+        assert(context);
+
+        _cleanup_(table_unrefp) Table *table = table_new("family", "object", "value");
+        if (!table)
+                return log_oom();
+
+        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+        table_set_sort(table, (size_t) 0, (size_t) 1, (size_t) 2);
+
+        FOREACH_ARRAY(m, context->metrics, context->n_metrics) {
+                struct {
+                        const char *name;
+                        const char *object;
+                        sd_json_variant *value;
+                } d = {};
+
+                static const sd_json_dispatch_field dispatch_table[] = {
+                        { "name",   SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string,  voffsetof(d, name),   SD_JSON_MANDATORY },
+                        { "object", SD_JSON_VARIANT_STRING,        sd_json_dispatch_const_string,  voffsetof(d, object), 0                 },
+                        { "value",  _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant_noref, voffsetof(d, value),  SD_JSON_MANDATORY },
+                        {}
+                };
+
+                r = sd_json_dispatch(*m, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &d);
+                if (r < 0) {
+                        _cleanup_free_ char *t = NULL;
+                        int k = sd_json_variant_format(*m, /* flags= */ 0, &t);
+                        if (k < 0)
+                                return log_error_errno(k, "Failed to format JSON: %m");
+
+                        log_warning_errno(r, "Cannot parse fact, skipping: %s", t);
+                        continue;
+                }
+
+                r = table_add_many(
+                                table,
+                                TABLE_STRING,     d.name,
+                                TABLE_STRING,     d.object,
+                                TABLE_JSON,       d.value,
+                                TABLE_SET_WEIGHT, 50U);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        *ret = TAKE_PTR(table);
+        return 0;
+}
+
+static int facts_output_describe(Context *context, Table **ret) {
+        int r;
+
+        assert(context);
+
+        _cleanup_(table_unrefp) Table *table = table_new("family", "description");
+        if (!table)
+                return log_oom();
+
+        table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+        table_set_sort(table, (size_t) 0, (size_t) 1);
+
+        FOREACH_ARRAY(m, context->metrics, context->n_metrics) {
+                struct {
+                        const char *name;
+                        const char *description;
+                } d = {};
+
+                static const sd_json_dispatch_field dispatch_table[] = {
+                        { "name",        SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, name),        SD_JSON_MANDATORY },
+                        { "description", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(d, description), 0                 },
+                        {}
+                };
+
+                r = sd_json_dispatch(*m, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &d);
+                if (r < 0) {
+                        _cleanup_free_ char *t = NULL;
+                        int k = sd_json_variant_format(*m, /* flags= */ 0, &t);
+                        if (k < 0)
+                                return log_error_errno(k, "Failed to format JSON: %m");
+
+                        log_warning_errno(r, "Cannot parse fact description, skipping: %s", t);
+                        continue;
+                }
+
+                r = table_add_many(
+                                table,
+                                TABLE_STRING,     d.name,
+                                TABLE_STRING,     d.description,
+                                TABLE_SET_WEIGHT, 50U);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        *ret = TAKE_PTR(table);
+        return 0;
+}
+
 static int metrics_output(Context *context) {
         int r;
 
@@ -444,8 +550,12 @@ static int metrics_output(Context *context) {
                                 return log_error_errno(r, "Failed to write JSON: %m");
                 }
 
-                if (context->n_metrics == 0 && arg_legend)
-                        log_info("No metrics collected.");
+                if (context->n_metrics == 0 && arg_legend) {
+                        if (IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS))
+                                log_info("No facts collected.");
+                        else
+                                log_info("No metrics collected.");
+                }
 
                 return 0;
         }
@@ -461,6 +571,14 @@ static int metrics_output(Context *context) {
                 r = metrics_output_describe(context, &table);
                 break;
 
+        case ACTION_LIST_FACTS:
+                r = facts_output_list(context, &table);
+                break;
+
+        case ACTION_DESCRIBE_FACTS:
+                r = facts_output_describe(context, &table);
+                break;
+
         default:
                 assert_not_reached();
         }
@@ -474,10 +592,12 @@ static int metrics_output(Context *context) {
         }
 
         if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) {
+                bool is_facts = IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS);
+
                 if (table_isempty(table))
-                        printf("No metrics available.\n");
+                        printf("No %s available.\n", is_facts ? "facts" : "metrics");
                 else
-                        printf("\n%zu metrics listed.\n", table_get_rows(table) - 1);
+                        printf("\n%zu %s listed.\n", table_get_rows(table) - 1, is_facts ? "facts" : "metrics");
         }
 
         return 0;
@@ -659,6 +779,92 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata)
         return 0;
 }
 
+static int verb_facts(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        Action action;
+        int r;
+
+        assert(argc >= 1);
+        assert(argv);
+
+        /* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */
+        arg_json_format_flags |= SD_JSON_FORMAT_SEQ;
+
+        if (streq_ptr(argv[0], "facts"))
+                action = ACTION_LIST_FACTS;
+        else {
+                assert(streq_ptr(argv[0], "describe-facts"));
+                action = ACTION_DESCRIBE_FACTS;
+        }
+
+        r = parse_metrics_matches(argv + 1);
+        if (r < 0)
+                return r;
+
+        _cleanup_(context_done) Context context = {
+                .action = action,
+        };
+        size_t n_skipped_sources = 0;
+
+        _cleanup_free_ DirectoryEntries *de = NULL;
+        _cleanup_free_ char *sources_path = NULL;
+        r = readdir_sources(&sources_path, &de);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                r = sd_event_default(&context.event);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get event loop: %m");
+
+                r = sd_event_set_signal_exit(context.event, true);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to enable exit on SIGINT/SIGTERM: %m");
+
+                FOREACH_ARRAY(i, de->entries, de->n_entries) {
+                        struct dirent *d = *i;
+
+                        if (set_size(context.link_infos) >= METRICS_LINKS_MAX) {
+                                n_skipped_sources++;
+                                break;
+                        }
+
+                        _cleanup_free_ char *p = path_join(sources_path, d->d_name);
+                        if (!p)
+                                return log_oom();
+
+                        (void) metrics_call(&context, d->d_name, p);
+                }
+        }
+
+        if (set_isempty(context.link_infos)) {
+                if (arg_legend)
+                        log_info("No facts sources found.");
+        } else {
+                assert(context.event);
+
+                r = sd_event_loop(context.event);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to run event loop: %m");
+
+                r = metrics_output(&context);
+                if (r < 0)
+                        return r;
+        }
+
+        if (n_skipped_sources > 0)
+                return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN),
+                                         "Too many facts sources, only %u sources contacted, %zu sources skipped.",
+                                         set_size(context.link_infos), n_skipped_sources);
+        if (context.n_invalid_metrics > 0)
+                return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN),
+                                         "%zu facts are not valid.",
+                                         context.n_invalid_metrics);
+        if (context.n_skipped_metrics > 0)
+                return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN),
+                                         "Too many facts, only %zu facts collected, %zu facts skipped.",
+                                         context.n_metrics, context.n_skipped_metrics);
+        return 0;
+}
+
 static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *userdata) {
         int r;
 
@@ -723,11 +929,14 @@ static int help(void) {
                 return log_oom();
 
         printf("%1$s [OPTIONS...] COMMAND ...\n"
-               "\n%5$sAcquire metrics from local sources.%6$s\n"
+               "\n%5$sAcquire metrics and facts from local sources.%6$s\n"
                "\n%3$sCommands:%4$s\n"
                "  metrics [MATCH...]    Acquire list of metrics and their values\n"
                "  describe-metrics [MATCH...]\n"
                "                        Describe available metrics\n"
+               "  facts [MATCH...]      Acquire list of facts and their values\n"
+               "  describe-facts [MATCH...]\n"
+               "                        Describe available facts\n"
                "  list-sources          Show list of known metrics sources\n"
                "\n%3$sOptions:%4$s\n"
                "  -h --help             Show this help\n"
@@ -831,6 +1040,8 @@ static int report_main(int argc, char *argv[]) {
                 { "help",             VERB_ANY, 1,        0, verb_help                                },
                 { "metrics",          VERB_ANY, VERB_ANY, 0, verb_metrics,      ACTION_LIST           },
                 { "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics,      ACTION_DESCRIBE       },
+                { "facts",            VERB_ANY, VERB_ANY, 0, verb_facts,        ACTION_LIST_FACTS     },
+                { "describe-facts",   VERB_ANY, VERB_ANY, 0, verb_facts,        ACTION_DESCRIBE_FACTS },
                 { "list-sources",     VERB_ANY, 1,        0, verb_list_sources                        },
                 {}
         };
diff --git a/src/shared/facts.c b/src/shared/facts.c
new file mode 100644 (file)
index 0000000..5a6882c
--- /dev/null
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "facts.h"
+#include "json-util.h"
+#include "log.h"
+#include "varlink-io.systemd.Facts.h"
+#include "varlink-util.h"
+
+int facts_add_to_varlink_server(
+                sd_varlink_server *server,
+                sd_varlink_method_t vl_method_list_cb,
+                sd_varlink_method_t vl_method_describe_cb) {
+
+        int r;
+
+        assert(server);
+        assert(vl_method_list_cb);
+        assert(vl_method_describe_cb);
+
+        r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_Facts);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to add varlink facts interface to varlink server: %m");
+
+        r = sd_varlink_server_bind_method_many(
+                        server,
+                        "io.systemd.Facts.List",     vl_method_list_cb,
+                        "io.systemd.Facts.Describe", vl_method_describe_cb);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to register varlink facts methods: %m");
+
+        return 0;
+}
+
+static int fact_family_build_json(const FactFamily *ff, sd_json_variant **ret) {
+        assert(ff);
+
+        return sd_json_buildo(
+                        ret,
+                        SD_JSON_BUILD_PAIR_STRING("name", ff->name),
+                        SD_JSON_BUILD_PAIR_STRING("description", ff->description));
+}
+
+int facts_method_describe(
+                const FactFamily fact_family_table[],
+                sd_varlink *link,
+                sd_json_variant *parameters,
+                sd_varlink_method_flags_t flags,
+                void *userdata) {
+
+        int r;
+
+        assert(fact_family_table);
+        assert(link);
+        assert(parameters);
+        assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE));
+
+        r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL);
+        if (r != 0)
+                return r;
+
+        r = sd_varlink_set_sentinel(link, "io.systemd.Facts.NoSuchFact");
+        if (r < 0)
+                return r;
+
+        for (const FactFamily *ff = fact_family_table; ff && ff->name; ff++) {
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+
+                r = fact_family_build_json(ff, &v);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to describe fact family '%s': %m", ff->name);
+
+                r = sd_varlink_reply(link, v);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to send varlink reply: %m");
+        }
+
+        return 0;
+}
+
+int facts_method_list(
+                const FactFamily fact_family_table[],
+                sd_varlink *link,
+                sd_json_variant *parameters,
+                sd_varlink_method_flags_t flags,
+                void *userdata) {
+
+        int r;
+
+        assert(fact_family_table);
+        assert(link);
+        assert(parameters);
+        assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE));
+
+        r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL);
+        if (r != 0)
+                return r;
+
+        r = sd_varlink_set_sentinel(link, "io.systemd.Facts.NoSuchFact");
+        if (r < 0)
+                return r;
+
+        FactFamilyContext ctx = { .link = link };
+        for (const FactFamily *ff = fact_family_table; ff && ff->name; ff++) {
+                assert(ff->generate);
+
+                ctx.fact_family = ff;
+                r = ff->generate(&ctx, userdata);
+                if (r < 0)
+                        return log_debug_errno(
+                                        r, "Failed to list facts for fact family '%s': %m", ff->name);
+        }
+
+        return 0;
+}
+
+static int fact_build_send(FactFamilyContext *context, const char *object, sd_json_variant *value) {
+        assert(context);
+        assert(value);
+        assert(context->link);
+        assert(context->fact_family);
+
+        return sd_varlink_replybo(context->link,
+                        SD_JSON_BUILD_PAIR_STRING("name", context->fact_family->name),
+                        JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object),
+                        SD_JSON_BUILD_PAIR("value", SD_JSON_BUILD_VARIANT(value)));
+}
+
+int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        int r;
+
+        assert(value);
+
+        r = sd_json_variant_new_string(&v, value);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to allocate JSON string: %m");
+
+        return fact_build_send(context, object, v);
+}
+
+int fact_build_send_unsigned(FactFamilyContext *context, const char *object, uint64_t value) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+        int r;
+
+        r = sd_json_variant_new_unsigned(&v, value);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to allocate JSON unsigned: %m");
+
+        return fact_build_send(context, object, v);
+}
diff --git a/src/shared/facts.h b/src/shared/facts.h
new file mode 100644 (file)
index 0000000..8a8a94c
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "shared-forward.h"
+
+typedef struct FactFamily FactFamily;
+
+typedef struct FactFamilyContext {
+        const FactFamily *fact_family;
+        sd_varlink *link;
+} FactFamilyContext;
+
+typedef int (*fact_family_generate_func_t)(FactFamilyContext *ffc, void *userdata);
+
+typedef struct FactFamily {
+        const char *name;
+        const char *description;
+        fact_family_generate_func_t generate;
+} FactFamily;
+
+/* Add io.systemd.Facts interface + methods to an existing varlink server */
+int facts_add_to_varlink_server(
+                sd_varlink_server *server,
+                sd_varlink_method_t vl_method_list_cb,
+                sd_varlink_method_t vl_method_describe_cb);
+
+int facts_method_describe(const FactFamily fact_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+int facts_method_list(const FactFamily fact_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+
+int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value);
+int fact_build_send_unsigned(FactFamilyContext *context, const char *object, uint64_t value);
index cdbe763d0137d5307f2ff552240926b77623fb9b..e2c1501adb86b9c90013468edef04fd14355f293 100644 (file)
@@ -77,6 +77,7 @@ shared_sources = files(
         'exit-status.c',
         'extension-util.c',
         'factory-reset.c',
+        'facts.c',
         'fdset.c',
         'fido2-util.c',
         'find-esp.c',
@@ -205,6 +206,7 @@ shared_sources = files(
         'varlink-io.systemd.BootControl.c',
         'varlink-io.systemd.Credentials.c',
         'varlink-io.systemd.FactoryReset.c',
+        'varlink-io.systemd.Facts.c',
         'varlink-io.systemd.Hostname.c',
         'varlink-io.systemd.Import.c',
         'varlink-io.systemd.InstanceMetadata.c',
diff --git a/src/shared/varlink-io.systemd.Facts.c b/src/shared/varlink-io.systemd.Facts.c
new file mode 100644 (file)
index 0000000..c7cf102
--- /dev/null
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-varlink-idl.h"
+
+#include "varlink-io.systemd.Facts.h"
+
+static SD_VARLINK_DEFINE_ERROR(NoSuchFact);
+
+static SD_VARLINK_DEFINE_METHOD_FULL(
+                List,
+                SD_VARLINK_REQUIRES_MORE,
+                SD_VARLINK_FIELD_COMMENT("Fact family name, e.g. io.systemd.Manager.Hostname"),
+                SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
+                SD_VARLINK_FIELD_COMMENT("Fact object name"),
+                SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+                SD_VARLINK_FIELD_COMMENT("Fact value"),
+                SD_VARLINK_DEFINE_OUTPUT(value, SD_VARLINK_ANY, 0));
+
+static SD_VARLINK_DEFINE_METHOD_FULL(
+                Describe,
+                SD_VARLINK_REQUIRES_MORE,
+                SD_VARLINK_FIELD_COMMENT("Fact family name"),
+                SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
+                SD_VARLINK_FIELD_COMMENT("Fact family description"),
+                SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, 0));
+
+SD_VARLINK_DEFINE_INTERFACE(
+                io_systemd_Facts,
+                "io.systemd.Facts",
+                SD_VARLINK_INTERFACE_COMMENT("Facts APIs"),
+                SD_VARLINK_SYMBOL_COMMENT("Method to get a list of facts and their values"),
+                &vl_method_List,
+                SD_VARLINK_SYMBOL_COMMENT("Method to get the fact families"),
+                &vl_method_Describe,
+                SD_VARLINK_SYMBOL_COMMENT("No such fact found"),
+                &vl_error_NoSuchFact);
diff --git a/src/shared/varlink-io.systemd.Facts.h b/src/shared/varlink-io.systemd.Facts.h
new file mode 100644 (file)
index 0000000..ce07de3
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-varlink-idl.h"
+
+extern const sd_varlink_interface vl_interface_io_systemd_Facts;
index 876c5b2cad49eb2680b450764b7f621512d4743b..4edb67a3150635e744f268a4f2b547b7a0585b0b 100755 (executable)
@@ -35,3 +35,23 @@ varlinkctl info /run/systemd/report/io.systemd.Network
 varlinkctl list-methods /run/systemd/report/io.systemd.Network
 varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {}
 varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {}
+
+# Test facts verbs
+"$REPORT" facts
+"$REPORT" facts -j
+"$REPORT" facts --no-legend
+"$REPORT" describe-facts
+"$REPORT" describe-facts -j
+"$REPORT" describe-facts --no-legend
+
+# Test facts with match filters
+"$REPORT" facts io
+"$REPORT" facts io.systemd piff
+"$REPORT" facts piff
+"$REPORT" describe-facts io
+"$REPORT" describe-facts io.systemd piff
+"$REPORT" describe-facts piff
+
+# Test facts via direct Varlink call on existing socket
+varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Facts.List {}
+varlinkctl --more call /run/systemd/report/io.systemd.Manager io.systemd.Facts.Describe {}