report: implement facts interface (#41117)
This is a rebased version of #40936: - the first few commits are #41003, which is greenlighted for merging after the release - then there's #40923 - and some changes on top
This commit is contained in:
@@ -6,4 +6,13 @@ executables += [
|
||||
'public' : true,
|
||||
'sources' : files('report.c'),
|
||||
},
|
||||
|
||||
libexec_template + {
|
||||
'name' : 'systemd-report-basic',
|
||||
'public' : true,
|
||||
'sources' : files(
|
||||
'report-basic-server.c',
|
||||
'report-basic.c',
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "ansi-color.h"
|
||||
#include "build.h"
|
||||
#include "facts.h"
|
||||
#include "format-table.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "report-basic.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
static int vl_server(void) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *vs = NULL;
|
||||
int r;
|
||||
|
||||
r = varlink_server_new(&vs, /* flags= */ 0, /* userdata= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate Varlink server: %m");
|
||||
|
||||
r = facts_add_to_varlink_server(vs, vl_method_list_facts, vl_method_describe_facts);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to register Facts varlink interface: %m");
|
||||
|
||||
r = sd_varlink_server_loop_auto(vs);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run Varlink event loop: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("%s [OPTIONS...]\n"
|
||||
"\n%sGenerate a report describing the current system%s\n"
|
||||
"\n%sOptions:%s\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
ansi_underline(),
|
||||
ansi_normal());
|
||||
table_print(options, stdout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
OptionParser state = { argc, argv };
|
||||
|
||||
FOREACH_OPTION(&state, c, /* ret_a= */ NULL, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
}
|
||||
|
||||
if (state.optind < argc)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"This program takes no arguments.");
|
||||
|
||||
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"This program can only run as a Varlink service.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return vl_server();
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
@@ -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 "report-basic.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));
|
||||
}
|
||||
|
||||
static const FactFamily fact_family_table[] = {
|
||||
/* Keep facts ordered alphabetically */
|
||||
{
|
||||
.name = FACT_IO_SYSTEMD_BASIC "Architecture",
|
||||
.description = "CPU architecture",
|
||||
.generate = architecture_generate,
|
||||
},
|
||||
{
|
||||
.name = FACT_IO_SYSTEMD_BASIC "BootID",
|
||||
.description = "Current boot ID",
|
||||
.generate = boot_id_generate,
|
||||
},
|
||||
{
|
||||
.name = FACT_IO_SYSTEMD_BASIC "Hostname",
|
||||
.description = "System hostname",
|
||||
.generate = hostname_generate,
|
||||
},
|
||||
{
|
||||
.name = FACT_IO_SYSTEMD_BASIC "KernelVersion",
|
||||
.description = "Kernel version",
|
||||
.generate = kernel_version_generate,
|
||||
},
|
||||
{
|
||||
.name = FACT_IO_SYSTEMD_BASIC "MachineID",
|
||||
.description = "Machine ID",
|
||||
.generate = machine_id_generate,
|
||||
},
|
||||
{
|
||||
.name = FACT_IO_SYSTEMD_BASIC "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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "shared-forward.h"
|
||||
|
||||
#define FACT_IO_SYSTEMD_BASIC "io.systemd.Basic."
|
||||
|
||||
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);
|
||||
+248
-35
@@ -20,14 +20,15 @@
|
||||
#include "runtime-scope.h"
|
||||
#include "set.h"
|
||||
#include "sort-util.h"
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "time-util.h"
|
||||
#include "varlink-idl-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
#define METRICS_MAX 1024U
|
||||
#define METRICS_LINKS_MAX 128U
|
||||
#define METRICS_OR_FACTS_MAX 1024U
|
||||
#define METRICS_OR_FACTS_LINKS_MAX 128U
|
||||
#define TIMEOUT_USEC (30 * USEC_PER_SEC) /* 30 seconds */
|
||||
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
@@ -39,17 +40,21 @@ static char **arg_matches = NULL;
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep);
|
||||
|
||||
typedef enum Action {
|
||||
ACTION_LIST,
|
||||
ACTION_DESCRIBE,
|
||||
ACTION_LIST_METRICS,
|
||||
ACTION_DESCRIBE_METRICS,
|
||||
ACTION_LIST_FACTS,
|
||||
ACTION_DESCRIBE_FACTS,
|
||||
_ACTION_MAX,
|
||||
_ACTION_INVALID = -EINVAL,
|
||||
} Action;
|
||||
|
||||
/* The structure for collected "metrics" or "facts". The fields
|
||||
* are prefixed with just "metrics" for brevity. */
|
||||
typedef struct Context {
|
||||
Action action;
|
||||
sd_event *event;
|
||||
Set *link_infos;
|
||||
sd_json_variant **metrics; /* Collected metrics for sorting */
|
||||
sd_json_variant **metrics; /* Collected metrics or facts for sorting */
|
||||
size_t n_metrics, n_skipped_metrics, n_invalid_metrics;
|
||||
} Context;
|
||||
|
||||
@@ -83,6 +88,15 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
|
||||
void, trivial_hash_func, trivial_compare_func,
|
||||
LinkInfo, link_info_free);
|
||||
|
||||
static const char* const action_method_table[] = {
|
||||
[ACTION_LIST_METRICS] = "io.systemd.Metrics.List",
|
||||
[ACTION_DESCRIBE_METRICS] = "io.systemd.Metrics.Describe",
|
||||
[ACTION_LIST_FACTS] = "io.systemd.Facts.List",
|
||||
[ACTION_DESCRIBE_FACTS] = "io.systemd.Facts.Describe",
|
||||
};
|
||||
|
||||
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(action_method, Action);
|
||||
|
||||
static int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b) {
|
||||
const char *name_a, *name_b, *object_a, *object_b;
|
||||
sd_json_variant *fields_a, *fields_b;
|
||||
@@ -213,7 +227,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,
|
||||
@@ -226,7 +240,10 @@ static int metrics_on_query_reply(
|
||||
Context *context = ASSERT_PTR(li->context);
|
||||
|
||||
if (error_id) {
|
||||
if (streq(error_id, SD_VARLINK_ERROR_DISCONNECTED))
|
||||
if (STR_IN_SET(error_id, SD_VARLINK_ERROR_METHOD_NOT_FOUND,
|
||||
SD_VARLINK_ERROR_METHOD_NOT_IMPLEMENTED))
|
||||
log_debug("Ignoring Varlink endpoint '%s': %s", li->name, error_id);
|
||||
else if (streq(error_id, SD_VARLINK_ERROR_DISCONNECTED))
|
||||
log_warning("Varlink connection to '%s' disconnected prematurely, ignoring.", li->name);
|
||||
else if (streq(error_id, SD_VARLINK_ERROR_TIMEOUT))
|
||||
log_warning("Varlink connection to '%s' timed out, ignoring.", li->name);
|
||||
@@ -236,7 +253,7 @@ static int metrics_on_query_reply(
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (context->n_metrics >= METRICS_MAX) {
|
||||
if (context->n_metrics >= METRICS_OR_FACTS_MAX) {
|
||||
context->n_skipped_metrics++;
|
||||
goto finish;
|
||||
}
|
||||
@@ -266,7 +283,7 @@ finish:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int metrics_call(Context *context, const char *name, const char *path) {
|
||||
static int call_collect(Context *context, const char *name, const char *path) {
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
int r;
|
||||
|
||||
@@ -285,14 +302,13 @@ 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 = ASSERT_PTR(action_method_to_string(context->action));
|
||||
|
||||
r = sd_varlink_observe(vl, method, /* parameters= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue %s() call: %m", method);
|
||||
|
||||
@@ -305,7 +321,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();
|
||||
|
||||
@@ -318,7 +333,7 @@ static int metrics_call(Context *context, const char *name, const char *path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int metrics_output_list(Context *context, Table **ret) {
|
||||
static int output_collected_list(Context *context, Table **ret) {
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
@@ -372,7 +387,7 @@ static int metrics_output_list(Context *context, Table **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int metrics_output_describe(Context *context, Table **ret) {
|
||||
static int output_collected_describe(Context *context, Table **ret) {
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
@@ -423,7 +438,106 @@ static int metrics_output_describe(Context *context, Table **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int metrics_output(Context *context) {
|
||||
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 output_collected(Context *context) {
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
@@ -441,8 +555,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;
|
||||
}
|
||||
@@ -450,12 +568,20 @@ static int metrics_output(Context *context) {
|
||||
_cleanup_(table_unrefp) Table *table = NULL;
|
||||
switch(context->action) {
|
||||
|
||||
case ACTION_LIST:
|
||||
r = metrics_output_list(context, &table);
|
||||
case ACTION_LIST_METRICS:
|
||||
r = output_collected_list(context, &table);
|
||||
break;
|
||||
|
||||
case ACTION_DESCRIBE:
|
||||
r = metrics_output_describe(context, &table);
|
||||
case ACTION_DESCRIBE_METRICS:
|
||||
r = output_collected_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:
|
||||
@@ -471,10 +597,12 @@ static int metrics_output(Context *context) {
|
||||
}
|
||||
|
||||
if (arg_legend && !sd_json_format_enabled(arg_json_format_flags)) {
|
||||
const char *type = IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS) ? "facts" : "metrics";
|
||||
|
||||
if (table_isempty(table))
|
||||
printf("No metrics available.\n");
|
||||
printf("No %s available.\n", type);
|
||||
else
|
||||
printf("\n%zu metrics listed.\n", table_get_rows(table) - 1);
|
||||
printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -582,7 +710,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata)
|
||||
|
||||
assert(argc >= 1);
|
||||
assert(argv);
|
||||
assert(IN_SET(action, ACTION_LIST, ACTION_DESCRIBE));
|
||||
assert(IN_SET(action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS));
|
||||
|
||||
/* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */
|
||||
arg_json_format_flags |= SD_JSON_FORMAT_SEQ;
|
||||
@@ -613,7 +741,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata)
|
||||
FOREACH_ARRAY(i, de->entries, de->n_entries) {
|
||||
struct dirent *d = *i;
|
||||
|
||||
if (set_size(context.link_infos) >= METRICS_LINKS_MAX) {
|
||||
if (set_size(context.link_infos) >= METRICS_OR_FACTS_LINKS_MAX) {
|
||||
n_skipped_sources++;
|
||||
break;
|
||||
}
|
||||
@@ -622,7 +750,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata)
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
(void) metrics_call(&context, d->d_name, p);
|
||||
(void) call_collect(&context, d->d_name, p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,7 +764,7 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata)
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run event loop: %m");
|
||||
|
||||
r = metrics_output(&context);
|
||||
r = output_collected(&context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -656,6 +784,86 @@ 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 = data;
|
||||
int r;
|
||||
|
||||
assert(argc >= 1);
|
||||
assert(argv);
|
||||
assert(IN_SET(action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS));
|
||||
|
||||
/* Enable JSON-SEQ mode here, since we'll dump a large series of JSON objects */
|
||||
arg_json_format_flags |= SD_JSON_FORMAT_SEQ;
|
||||
|
||||
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_OR_FACTS_LINKS_MAX) {
|
||||
n_skipped_sources++;
|
||||
break;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *p = path_join(sources_path, d->d_name);
|
||||
if (!p)
|
||||
return log_oom();
|
||||
|
||||
(void) call_collect(&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 = output_collected(&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;
|
||||
|
||||
@@ -720,11 +928,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"
|
||||
@@ -825,10 +1036,12 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
static int report_main(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "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 },
|
||||
{ "list-sources", VERB_ANY, 1, 0, verb_list_sources },
|
||||
{ "help", VERB_ANY, 1, 0, verb_help },
|
||||
{ "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_LIST_METRICS },
|
||||
{ "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_DESCRIBE_METRICS },
|
||||
{ "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 },
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "facts.h"
|
||||
#include "json-util.h"
|
||||
#include "log.h"
|
||||
#include "varlink-io.systemd.Facts.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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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',
|
||||
|
||||
@@ -72,8 +72,6 @@ int option_parse(
|
||||
const Option **ret_option,
|
||||
const char **ret_arg) {
|
||||
|
||||
assert(ret_arg);
|
||||
|
||||
/* Check and initialize */
|
||||
if (state->optind == 0) {
|
||||
if (state->argc < 1 || strv_isempty(state->argv))
|
||||
@@ -226,7 +224,13 @@ int option_parse(
|
||||
if (ret_option)
|
||||
/* Return the matched Option structure to allow the caller to "know" what was matched */
|
||||
*ret_option = option;
|
||||
*ret_arg = optval;
|
||||
|
||||
if (ret_arg)
|
||||
*ret_arg = optval;
|
||||
else
|
||||
/* It's fine to omit ret_arg, but only if no options return a value. */
|
||||
assert(!optval);
|
||||
|
||||
return option->id;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "memory-util.h"
|
||||
#include "shared-forward.h"
|
||||
|
||||
typedef enum OptionFlags {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/* 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.Basic.Hostname"),
|
||||
SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0),
|
||||
/* This is currently an unused placeholder. Add examples when we have them. */
|
||||
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, e.g. io.systemd.Basic.Hostname"),
|
||||
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);
|
||||
@@ -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;
|
||||
@@ -35,3 +35,26 @@ 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 {}
|
||||
|
||||
# Make sure the service for "system facts" is enabled
|
||||
systemctl start systemd-report-basic.socket
|
||||
|
||||
# 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.Basic io.systemd.Facts.List {}
|
||||
varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.Describe {}
|
||||
|
||||
@@ -624,6 +624,8 @@ units = [
|
||||
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
|
||||
'symlinks' : ['sysinit.target.wants/'],
|
||||
},
|
||||
{ 'file' : 'systemd-report-basic.socket' },
|
||||
{ 'file' : 'systemd-report-basic@.service.in' },
|
||||
{
|
||||
'file' : 'systemd-tpm2-clear.service.in',
|
||||
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
[Unit]
|
||||
Description=Report System Basic Facts Socket
|
||||
DefaultDependencies=no
|
||||
Before=sockets.target
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/report/io.systemd.Basic
|
||||
FileDescriptorName=varlink
|
||||
SocketMode=0666
|
||||
Accept=yes
|
||||
MaxConnectionsPerSource=16
|
||||
RemoveOnStop=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
||||
@@ -0,0 +1,41 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
[Unit]
|
||||
Description=Report System Basic Facts
|
||||
|
||||
DefaultDependencies=no
|
||||
Conflicts=shutdown.target
|
||||
Before=shutdown.target
|
||||
|
||||
[Service]
|
||||
CapabilityBoundingSet=
|
||||
DeviceAllow=
|
||||
DynamicUser=yes
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
PrivateDevices=yes
|
||||
PrivateIPC=yes
|
||||
PrivateNetwork=yes
|
||||
PrivateTmp=disconnected
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_UNIX
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service
|
||||
|
||||
ExecStart={{LIBEXECDIR}}/systemd-report-basic
|
||||
Reference in New Issue
Block a user