report: add simple metrics provider that just reads files

This commit is contained in:
Lennart Poettering
2026-06-19 05:23:18 +02:00
parent 2640a3b0e2
commit 9c3e443021
10 changed files with 436 additions and 0 deletions
+5
View File
@@ -1185,6 +1185,11 @@ manpages = [
['systemd-remount-fs.service', '8', ['systemd-remount-fs'], ''],
['systemd-repart', '8', ['systemd-repart.service'], 'ENABLE_REPART'],
['systemd-report', '1', [], ''],
['systemd-report-files@.service',
'8',
['systemd-report-files',
'systemd-report-files.socket'],
''],
['systemd-report-sign-plain@.service',
'8',
['systemd-report-sign-plain',
+72
View File
@@ -0,0 +1,72 @@
<?xml version="1.0"?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-report-files_.service" xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-report-files@.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-report-files@.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-report-files@.service</refname>
<refname>systemd-report-files.socket</refname>
<refname>systemd-report-files</refname>
<refpurpose>Report the contents of files as system report metrics</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-report-files@.service</filename></para>
<para><filename>systemd-report-files.socket</filename></para>
<para><filename>/usr/lib/systemd/systemd-report-files</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>systemd-report-files@.service</command> is a metrics source for
<citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry>. It
reports the contents of a configurable set of files as metrics, by implementing the
<constant>io.systemd.Metrics</constant> Varlink interface on a socket linked into the
<filename>/run/systemd/report/</filename> directory.</para>
<para>The files to report are picked up from <filename>report.files/</filename> subdirectories of the
usual configuration directories, i.e. from
<filename>/etc/systemd/report.files/</filename>,
<filename>/run/systemd/report.files/</filename>,
<filename>/var/lib/systemd/report.files/</filename>,
<filename>/usr/local/lib/systemd/report.files/</filename> and
<filename>/usr/lib/systemd/report.files/</filename>. Typically these directories contain symlinks pointing
to the actual files in the file system whose contents shall be reported. Entries with the same name in an
earlier directory override those in a later one.</para>
<para>Each entry is reported as a metric named
<literal>io.systemd.Files.<replaceable>NAME</replaceable></literal>, where
<replaceable>NAME</replaceable> is the name of the entry (which must be a valid metric field name). The
metric value is the verbatim contents of the file. Files that cannot be read (for example because a
symlink is dangling) are skipped, as are files whose contents are not valid UTF-8 text.</para>
<para>Note that access to this service is unprivileged, but it runs privileged. This means take care when
symlinking files into the various <filename>report.files/</filename> directories: this service might
provide read access even if the client does not have the required direct access rights itself.</para>
</refsect1>
<!-- Note: we do not document the command line switches here. The systemd-report-files binary itself should
not be invoked by users, but only as a service. Use systemd-report(1) to acquire metrics. -->
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+1
View File
@@ -33,6 +33,7 @@ enable systemd-nsresourced.socket
enable systemd-pstore.service
enable systemd-report-basic.socket
enable systemd-report-cgroup.socket
enable systemd-report-files.socket
enable systemd-report-sign-plain.socket
enable systemd-resolved.service
enable systemd-sysext.service
+7
View File
@@ -27,6 +27,13 @@ executables += [
'report-cgroup-server.c',
),
},
libexec_template + {
'name' : 'systemd-report-files',
'sources' : files(
'report-files.c',
'report-files-server.c',
),
},
libexec_template + {
'name' : 'systemd-report-sign-plain',
'conditions' : [
+103
View File
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-varlink.h"
#include "build.h"
#include "format-table.h"
#include "help-util.h"
#include "log.h"
#include "main-func.h"
#include "options.h"
#include "report-files.h"
#include "varlink-io.systemd.Metrics.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 = sd_varlink_server_add_interface(vs, &vl_interface_io_systemd_Metrics);
if (r < 0)
return log_error_errno(r, "Failed to add Varlink interface: %m");
r = sd_varlink_server_bind_method_many(
vs,
"io.systemd.Metrics.List", vl_method_list_metrics,
"io.systemd.Metrics.Describe", vl_method_describe_metrics);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %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;
help_cmdline("[OPTIONS...]");
help_abstract("Report the contents of files as system report metrics.");
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("systemd-report-files@.service", "8");
return 0;
}
static int parse_argv(int argc, char *argv[]) {
int r;
assert(argc >= 0);
assert(argv);
OptionParser opts = { argc, argv };
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
OPTION_COMMON_HELP:
return help();
OPTION_COMMON_VERSION:
return version();
}
if (option_parser_get_n_args(&opts) > 0)
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);
+193
View File
@@ -0,0 +1,193 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "sd-varlink.h"
#include "alloc-util.h"
#include "chase.h"
#include "conf-files.h"
#include "fd-util.h"
#include "fileio.h"
#include "log.h"
#include "metrics.h"
#include "path-util.h"
#include "report-files.h"
#include "string-util.h"
#include "strv.h"
#include "utf8.h"
#include "varlink-idl-util.h"
/* Upper bounds, to protect against pathologically large directories or files. */
#define REPORT_FILES_MAX 1024U
#define REPORT_FILE_SIZE_MAX (4U * 1024U * 1024U)
/* The directories we look for files to report in. This is the usual CONF_PATHS() set (/etc/, /run/,
* /usr/local/lib/, /usr/lib/), plus an extra directory below /var/lib/ for persistent local additions. Files
* (typically symlinks to the actual files to report) dropped into any of these are reported as metrics, keyed
* by their name. Entries in earlier directories override identically named ones in later directories. */
static const char* const report_files_dirs[] = {
"/etc/systemd/report.files",
"/run/systemd/report.files",
"/var/lib/systemd/report.files",
"/usr/local/lib/systemd/report.files",
"/usr/lib/systemd/report.files",
NULL,
};
static MetricFamily* metric_family_array_free(MetricFamily *families) {
if (!families)
return NULL;
/* The array is NULL-name terminated. We own the name/description strings. */
for (MetricFamily *mf = families; mf->name; mf++) {
free((char*) mf->name);
free((char*) mf->description);
}
return mfree(families);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(MetricFamily*, metric_family_array_free);
static int file_metric_generate(const MetricFamily *mf, sd_varlink *link, void *userdata) {
int r;
assert(mf && mf->name);
assert(link);
/* Recover the file name from the metric family name: it is the part following the interface prefix.
* We look it up across our directories again (rather than caching the path found while building the
* metric list), and read the first instance we find, matching the precedence used at enumeration. */
const char *field = startswith(mf->name, METRIC_IO_SYSTEMD_FILES_PREFIX);
assert(field);
_cleanup_free_ char *buf = NULL, *discovered_path = NULL;
size_t size = 0;
STRV_FOREACH(d, report_files_dirs) {
_cleanup_free_ char *path = path_join(*d, field);
if (!path)
return log_oom();
_cleanup_free_ char *resolved = NULL;
_cleanup_close_ int fd = chase_and_open(path, /* root= */ NULL, CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, &resolved);
if (fd == -ENOENT) /* Not in this directory (or dangling symlink): try the next one. */
continue;
if (fd < 0) {
log_warning_errno(fd, "Failed to open '%s', skipping: %m", path);
return 0;
}
r = read_full_file_full(
fd,
/* filename= */ NULL,
/* offset= */ UINT64_MAX,
REPORT_FILE_SIZE_MAX,
READ_FULL_FILE_FAIL_WHEN_LARGER,
/* bind_name= */ NULL,
&buf,
&size);
if (r < 0) {
log_warning_errno(r, "Failed to read '%s', skipping: %m", path);
return 0;
}
discovered_path = TAKE_PTR(resolved);
break;
}
if (!discovered_path) {
log_debug("File for metric '%s' unavailable, skipping.", mf->name);
return 0;
}
/* Metric values are JSON strings, so we can only report text files. Skip anything that isn't valid,
* NUL-free UTF-8. */
if (memchr(buf, 0, size) || !utf8_is_valid(buf)) {
log_debug("File for metric '%s' is not valid UTF-8 text, skipping.", mf->name);
return 0;
}
return metric_build_send_string(mf, link, discovered_path, buf, /* fields= */ NULL);
}
static int build_file_metrics(MetricFamily **ret) {
_cleanup_(metric_family_array_freep) MetricFamily *families = NULL;
_cleanup_strv_free_ char **files = NULL;
size_t n = 0;
int r;
assert(ret);
/* Enumerate the files to report across all our directories, deduplicated by name. The entry name is
* used as the metric field name. */
r = conf_files_list_strv(&files, /* suffix= */ NULL, /* root= */ NULL, CONF_FILES_REGULAR, report_files_dirs);
if (r < 0)
return log_error_errno(r, "Failed to enumerate report files: %m");
STRV_FOREACH(f, files) {
_cleanup_free_ char *base = NULL;
r = path_extract_filename(*f, &base);
if (r < 0)
return log_error_errno(r, "Failed to extract file name from '%s': %m", *f);
/* The name becomes the metric field name, so it must be a valid one. */
if (!varlink_idl_field_name_is_valid(base)) {
log_debug("Report file '%s' does not have a valid metric field name, skipping.", *f);
continue;
}
if (n >= REPORT_FILES_MAX) {
log_warning("More than %u report files found, not reporting the rest.", REPORT_FILES_MAX);
break;
}
_cleanup_free_ char *name = strjoin(METRIC_IO_SYSTEMD_FILES_PREFIX, base);
_cleanup_free_ char *description = strjoin("Contents of the '", base, "' report file");
if (!name || !description)
return log_oom();
/* Room for the new entry plus the NULL-name terminator. */
if (!GREEDY_REALLOC(families, n + 2))
return log_oom();
families[n++] = (MetricFamily) {
.name = TAKE_PTR(name),
.description = TAKE_PTR(description),
.type = METRIC_FAMILY_TYPE_STRING,
.generate = file_metric_generate,
};
families[n] = (MetricFamily) {}; /* terminator */
}
/* The metrics helpers expect a valid, terminated array even when empty. */
if (!families) {
families = new0(MetricFamily, 1);
if (!families)
return log_oom();
}
*ret = TAKE_PTR(families);
return 0;
}
int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
int r;
_cleanup_(metric_family_array_freep) MetricFamily *families = NULL;
r = build_file_metrics(&families);
if (r < 0)
return r;
return metrics_method_list(families, link, parameters, flags, /* userdata= */ NULL);
}
int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
int r;
_cleanup_(metric_family_array_freep) MetricFamily *families = NULL;
r = build_file_metrics(&families);
if (r < 0)
return r;
return metrics_method_describe(families, link, parameters, flags, /* userdata= */ NULL);
}
+9
View File
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "shared-forward.h"
#define METRIC_IO_SYSTEMD_FILES_PREFIX "io.systemd.Files."
int vl_method_list_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
int vl_method_describe_metrics(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata);
+2
View File
@@ -731,6 +731,8 @@ units = [
},
{ 'file' : 'systemd-report-cgroup.socket' },
{ 'file' : 'systemd-report-cgroup@.service.in' },
{ 'file' : 'systemd-report-files.socket' },
{ 'file' : 'systemd-report-files@.service.in' },
{
'file' : 'systemd-report-sign-plain.socket',
'conditions' : ['HAVE_OPENSSL'],
+26
View File
@@ -0,0 +1,26 @@
# 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 Files Metrics Socket
Documentation=man:systemd-report-files@.service(8)
DefaultDependencies=no
Before=sockets.target shutdown.target
Conflicts=shutdown.target
[Socket]
ListenStream=/run/systemd/report/io.systemd.Files
FileDescriptorName=varlink
SocketMode=0666
Accept=yes
MaxConnectionsPerSource=16
RemoveOnStop=yes
[Install]
WantedBy=sockets.target
+18
View File
@@ -0,0 +1,18 @@
# 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 Files Metrics
Documentation=man:systemd-report-files@.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
Before=shutdown.target
[Service]
ExecStart={{LIBEXECDIR}}/systemd-report-files