Merge pull request #29325 from poettering/varlink-introspect

varlink: add introspection support + varlinkctl + varlinkify one first command line tool (systemd-pcrextend)
This commit is contained in:
Yu Watanabe
2023-10-06 21:58:17 +09:00
committed by GitHub
47 changed files with 4259 additions and 87 deletions
+21 -22
View File
@@ -148,6 +148,24 @@ Features:
hostname pattern and has a ProxyCommand set that allows connecting to any
local VM/container that way without any networking configured.
* Varlinkification of the following command line tools, to open them up to
other programs via IPC:
- bootctl
- journalctl (allowing journal read access via IPC)
- coredumpcl
- systemd-bless-boot
- systemd-measure
- systemd-creds (allowing clients to encrypt credentials locally)
- systemd-cryptenroll (to allow UIs to enroll FIDO2 keys and such)
- systemd-dissect
- systemd-sysext
- systemd-sysupdate
- systemd-analyze
- kernel-install
* Varlink: add glue code to allow varlink clients to be authenticated via
Polkit by passing client pidfd over.
* in the service manager, pick up ERRNO= + BUSERROR= + VARLINKERROR= error
identifiers, and store them along with the exit status of a server and report
via "systemctl status".
@@ -159,32 +177,15 @@ Features:
* automatically mount one virtiofs during early boot phase to /run/host/,
similar to how we do that for nspawn, based on some clear tag.
* make systemd-pcrextend accessible via varlink so that clients can measure
things reasonably freely, and get a proper entry in the TPM event log we
maintain, correctly synchronized
* add some service that makes an atomic snapshot of PCR state and event log up
to that point available, possibly even with quote by the TPM.
* encode type1 entries in some UKI section to add additional entries to the
menu.
* extend the various CLI tools we have that output JSON already to also read
their command to execute from JSON in varlink format, so that you can fork
them off and talk to them fully via varlink.
* add a "varlinkctl" tool that allows interacting with varlink services from
the shell. In particular add a "--exec=" switch, which allows specifying a
binary to invoke to which to talk via stdin/stdout
* make tools that speak varlink over stdin/stdout trivially sockect
activatable. i.e. once bootctl, kernel-install, systemd-measure and similar
speak varlink make them available via a .socket unit with Accept=yes, so that
they can be talked to via IPC out-of-process
* beef up .service units that are socket activated with Accept=yes with options
AllowPeerUser= + AllowPeerGroup= to allow trivially simple access control
when invoked via socket as IPC services
* Add ACL-based access management to .socket units. i.e. add AllowPeerUser= +
AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX
sockets.
* systemd-tpm2-setup should probably have a factory reset logic, i.e. when some
kernel command line option is set we reset the TPM (equivalent of tpm2_clear
@@ -929,8 +930,6 @@ Features:
2. systemd-gpt-auto-generator should auto-mount /var if it finds it on disk
3. mount.x-initrd mount option in fstab should be implied for /var
* implement varlink introspection
* make persistent restarts easier by adding a new setting OpenPersistentFile=
or so, which allows opening one or more files that is "persistent" across
service restarts, hot reboot, cold reboots (depending on configuration): the
+1
View File
@@ -582,6 +582,7 @@ o "/org/freedesktop/systemd1/job/42684"</programlisting>
<citerefentry project='dbus'><refentrytitle>dbus-daemon</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<ulink url="https://www.freedesktop.org/wiki/Software/dbus">D-Bus</ulink>,
<citerefentry><refentrytitle>sd-bus</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
<citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>machinectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry project='die-net'><refentrytitle>wireshark</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+1
View File
@@ -1249,6 +1249,7 @@ manpages = [
['systemd-user-runtime-dir', 'user-runtime-dir@.service'],
''],
['userdbctl', '1', [], 'ENABLE_USERDB'],
['varlinkctl', '1', [], ''],
['vconsole.conf', '5', [], 'ENABLE_VCONSOLE'],
['veritytab', '5', [], 'HAVE_LIBCRYPTSETUP']
]
+315
View File
@@ -0,0 +1,315 @@
<?xml version='1.0'?>
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="varlinkctl"
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>varlinkctl</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>varlinkctl</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>varlinkctl</refname>
<refpurpose>Introspect with and invoke Varlink services</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">info</arg>
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">list-interfaces</arg>
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">introspect</arg>
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
<arg choice="plain"><replaceable>INTERFACE</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">call</arg>
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
<arg choice="plain"><replaceable>METHOD</replaceable></arg>
<arg choice="opt"><replaceable>PARAMETERS</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">validate-idl</arg>
<arg choice="opt"><replaceable>FILE</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><command>varlinkctl</command> may be used to introspect and invoke <ulink
url="https://varlink.org/">Varlink</ulink> services.</para>
<para>Services are referenced by one of the following:</para>
<itemizedlist>
<listitem><para>A Varlink service reference starting with the <literal>unix:</literal> string, followed
by an absolute <constant>AF_UNIX</constant> path, or by <literal>@</literal> and an arbitrary string
(the latter for referencing sockets in the abstract namespace).</para></listitem>
<listitem><para>A Varlink service reference starting with the <literal>exec:</literal> string, followed
by an absolute path of a binary to execute.</para></listitem>
</itemizedlist>
<para>For convenience these two simpler (redundant) service address syntaxes are also supported:</para>
<itemizedlist>
<listitem><para>A file system path to an <constant>AF_UNIX</constant> socket, either absolute
(i.e. begins with <literal>/</literal>) or relative (in which case it must begin with
<literal>./</literal>).</para></listitem>
<listitem><para>A file system path to an executable, either absolute or relative (as above, must begin
with <literal>/</literal>, resp. <literal>./</literal>).</para></listitem>
</itemizedlist>
</refsect1>
<refsect1>
<title>Commands</title>
<para>The following commands are understood:</para>
<variablelist>
<varlistentry>
<term><command>info</command> <replaceable>ADDRESS</replaceable></term>
<listitem><para>Show brief information about the specified service, including vendor name and list of
implemented interfaces. Expects a service address in the formats described above.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><command>list-interfaces</command> <replaceable>ADDRESS</replaceable></term>
<listitem><para>Show list of interfaces implemented by the specified service. Expects a service
address in the formats described above.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><command>introspect</command> <replaceable>ADDRESS</replaceable> <replaceable>INTERFACE</replaceable></term>
<listitem><para>Show interface definition of the specified interface provided by the specified
service. Expects a service address in the formats described above and a Varlink interface
name.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><command>call</command> <replaceable>ADDRESS</replaceable> <replaceable>METHOD</replaceable> [<replaceable>ARGUMENTS</replaceable>]</term>
<listitem><para>Call the specified method of the specified service. Expects a service address in the
format described above, a fully qualified Varlink method name, and a JSON arguments object. If the
arguments object is not specified, it is read from STDIN instead. To pass an empty list of
parameters, specify the empty object <literal>{}</literal>.</para>
<para>The reply parameters are written as JSON object to STDOUT.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><command>validate-idl</command> [<replaceable>FILE</replaceable>]</term>
<listitem><para>Reads a Varlink interface definition file, parses and validates it, then outputs it
with syntax highlighting. This checks for syntax and internal consistency of the interface. Expects a
file name to read the interface definition from. If omitted reads the interface definition from
STDIN.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><command>help</command></term>
<listitem><para>Show command syntax help.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Options</title>
<para>The following options are understood:</para>
<variablelist>
<varlistentry>
<term><option>--more</option></term>
<listitem><para>When used with <command>call</command>: expect multiple method replies. If this flag is
set the method call is sent with the <constant>more</constant> flag set, which tells the service to
generate multiple replies, if needed. The command remains running until the service sends a reply
message that indicates it is the last in the series. This flag should be set only for method calls
that support this mechanism.</para>
<para>If this mode is enabled output is automatically switched to JSON-SEQ mode, so that individual
reply objects can be easily discerned.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--oneway</option></term>
<listitem><para>When used with <command>call</command>: do not expect a method reply. If this flag
is set the method call is sent with the <constant>oneway</constant> flag set (the command exits
immediately after), which tells the service not to generate a reply.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--json=</option><replaceable>MODE</replaceable></term>
<listitem>
<para>Selects the JSON output formatting, one of <literal>pretty</literal> (for nicely indented,
colorized output) or <literal>short</literal> (for terse output with minimal whitespace and no
newlines), defaults to <literal>short</literal>.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-j</option></term>
<listitem>
<para>Equivalent to <option>--json=pretty</option> when invoked interactively from a terminal. Otherwise
equivalent to <option>--json=short</option>, in particular when the output is piped to some other
program.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Examples</title>
<example>
<title>Investigating a Service</title>
<para>The following three commands inspect the <literal>io.systemd.Resolve</literal> service
implemented by
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
listing general service information and implemented interfaces, and then displaying the interface
definition of its primary interface:</para>
<programlisting>$ varlinkctl info /run/systemd/resolve/io.systemd.Resolve
Vendor: The systemd Project
Product: systemd (systemd-resolved)
Version: 254 (254-1522-g4790521^)
URL: https://systemd.io/
Interfaces: io.systemd
io.systemd.Resolve
org.varlink.service
$ varlinkctl list-interfaces /run/systemd/resolve/io.systemd.Resolve
io.systemd
io.systemd.Resolve
org.varlink.service
$ varlinkctl introspect /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve
interface io.systemd.Resolve
type ResolvedAddress(
ifindex: ?int,
…</programlisting>
<para>(Interface definition has been truncated in the example above, in the interest of brevity.)</para>
</example>
<example>
<title>Invoking a Method</title>
<para>The following command resolves a hostname via <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s <function>ResolveHostname</function> method call.</para>
<programlisting>$ varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name":"systemd.io","family":2}' -j
{
"addresses" : [
{
"ifindex" : 2,
"family" : 2,
"address" : [
185,
199,
111,
153
]
}
],
"name" : "systemd.io",
"flags" : 1048577
}</programlisting>
</example>
<example>
<title>Investigating a Service Executable</title>
<para>The following comand inspects the <filename>/usr/lib/systemd/systemd-pcrextend</filename>
executable and the IPC APIs it provides. It then invokes a method on it:</para>
<programlisting># varlinkctl info /usr/lib/systemd/systemd-pcrextend
Vendor: The systemd Project
Product: systemd (systemd-pcrextend)
Version: 254 (254-1536-g97734fb)
URL: https://systemd.io/
Interfaces: io.systemd
io.systemd.PCRExtend
org.varlink.service
# varlinkctl introspect /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend
interface io.systemd.PCRExtend
method Extend(
pcr: int,
text: ?string,
data: ?string
) -> ()
# varlinkctl call /usr/lib/systemd/systemd-pcrextend io.systemd.PCRExtend.Extend '{"pcr":15,"text":"foobar"}'
{}</programlisting>
</example>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>busctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<ulink url="https://varlink.org/">Varlink</ulink>
</para>
</refsect1>
</refentry>
+1
View File
@@ -2193,6 +2193,7 @@ subdir('src/update-done')
subdir('src/update-utmp')
subdir('src/user-sessions')
subdir('src/userdb')
subdir('src/varlinkctl')
subdir('src/vconsole')
subdir('src/veritysetup')
subdir('src/volatile-root')
+20
View File
@@ -254,4 +254,24 @@ static inline void free_many_charp(char **c, size_t n) {
free_many((void**) c, n);
}
_alloc_(2) static inline void *realloc0(void *p, size_t new_size) {
size_t old_size;
void *q;
/* Like realloc(), but initializes anything appended to zero */
old_size = MALLOC_SIZEOF_SAFE(p);
q = realloc(p, new_size);
if (!q)
return NULL;
new_size = MALLOC_SIZEOF_SAFE(q); /* Update with actually allocated space */
if (new_size > old_size)
memset((uint8_t*) q + old_size, 0, new_size - old_size);
return q;
}
#include "memory-util.h"
+10 -1
View File
@@ -5,6 +5,8 @@
#include "strv.h"
#include "user-util.h"
#include "varlink.h"
#include "varlink-io.systemd.UserDatabase.h"
#include "varlink-io.systemd.ManagedOOM.h"
typedef struct LookupParameters {
const char *user_name;
@@ -232,7 +234,7 @@ static int vl_method_subscribe_managed_oom_cgroups(
/* We only take one subscriber for this method so return an error if there's already an existing one.
* This shouldn't happen since systemd-oomd is the only client of this method. */
if (FLAGS_SET(flags, VARLINK_METHOD_MORE) && m->managed_oom_varlink)
return varlink_error(link, VARLINK_ERROR_SUBSCRIPTION_TAKEN, NULL);
return varlink_error(link, "io.systemd.ManagedOOM.SubscriptionTaken", NULL);
r = build_managed_oom_cgroups_json(m, &v);
if (r < 0)
@@ -600,6 +602,13 @@ int manager_setup_varlink_server(Manager *m, VarlinkServer **ret) {
varlink_server_set_userdata(s, m);
r = varlink_server_add_interface_many(
s,
&vl_interface_io_systemd_UserDatabase,
&vl_interface_io_systemd_ManagedOOM);
if (r < 0)
return log_error_errno(r, "Failed to add interfaces to varlink server: %m");
r = varlink_server_bind_method_many(
s,
"io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
+5
View File
@@ -53,6 +53,7 @@
#include "user-record-util.h"
#include "user-record.h"
#include "user-util.h"
#include "varlink-io.systemd.UserDatabase.h"
/* Where to look for private/public keys that are used to sign the user records. We are not using
* CONF_PATHS_NULSTR() here since we want to insert /var/lib/systemd/home/ in the middle. And we insert that
@@ -1003,6 +1004,10 @@ static int manager_bind_varlink(Manager *m) {
varlink_server_set_userdata(m->varlink_server, m);
r = varlink_server_add_interface(m->varlink_server, &vl_interface_io_systemd_UserDatabase);
if (r < 0)
return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m");
r = varlink_server_bind_method_many(
m->varlink_server,
"io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
+5
View File
@@ -58,6 +58,7 @@
#include "syslog-util.h"
#include "uid-alloc-range.h"
#include "user-util.h"
#include "varlink-io.systemd.Journal.h"
#define USER_JOURNALS_MAX 1024
@@ -2224,6 +2225,10 @@ static int server_open_varlink(Server *s, const char *socket, int fd) {
varlink_server_set_userdata(s->varlink_server, s);
r = varlink_server_add_interface(s->varlink_server, &vl_interface_io_systemd_Journal);
if (r < 0)
return log_error_errno(r, "Failed to add Journal interface to varlink server: %m");
r = varlink_server_bind_method_many(
s->varlink_server,
"io.systemd.Journal.Synchronize", vl_method_synchronize,
+5
View File
@@ -5,6 +5,7 @@
#include "mkdir.h"
#include "user-util.h"
#include "varlink.h"
#include "varlink-io.systemd.UserDatabase.h"
typedef struct LookupParameters {
const char *user_name;
@@ -392,6 +393,10 @@ int manager_varlink_init(Manager *m) {
varlink_server_set_userdata(s, m);
r = varlink_server_add_interface(s, &vl_interface_io_systemd_UserDatabase);
if (r < 0)
return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m");
r = varlink_server_bind_method_many(
s,
"io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
+5
View File
@@ -15,6 +15,7 @@
#include "oomd-manager.h"
#include "path-util.h"
#include "percent-util.h"
#include "varlink-io.systemd.oom.h"
typedef struct ManagedOOMMessage {
ManagedOOMMode mode;
@@ -733,6 +734,10 @@ static int manager_varlink_init(Manager *m, int fd) {
varlink_server_set_userdata(s, m);
r = varlink_server_add_interface(s, &vl_interface_io_systemd_oom);
if (r < 0)
return log_error_errno(r, "Failed to add oom interface to varlink server: %m");
r = varlink_server_bind_method(s, "io.systemd.oom.ReportManagedOOMCGroups", process_managed_oom_request);
if (r < 0)
return log_error_errno(r, "Failed to register varlink method: %m");
+147 -30
View File
@@ -21,6 +21,8 @@
#include "pretty-print.h"
#include "tpm2-pcr.h"
#include "tpm2-util.h"
#include "varlink.h"
#include "varlink-io.systemd.PCRExtend.h"
static bool arg_graceful = false;
static char *arg_tpm2_device = NULL;
@@ -28,11 +30,14 @@ static char **arg_banks = NULL;
static char *arg_file_system = NULL;
static bool arg_machine_id = false;
static unsigned arg_pcr_index = UINT_MAX;
static bool arg_varlink = false;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
#define EXTENSION_STRING_SAFE_LIMIT 1024
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
int r;
@@ -165,7 +170,12 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_file_system && arg_machine_id)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined.");
if (arg_pcr_index == UINT_MAX)
r = varlink_invocation(VARLINK_ALLOW_ACCEPT);
if (r < 0)
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
if (r > 0)
arg_varlink = true;
else if (arg_pcr_index == UINT_MAX)
arg_pcr_index = (arg_file_system || arg_machine_id) ?
TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */
TPM2_PCR_KERNEL_BOOT; /* → PCR 11 */
@@ -257,10 +267,119 @@ static int get_file_system_word(
return 0;
}
static int extend_now(unsigned pcr, const void *data, size_t size, Tpm2UserspaceEventType event) {
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
int r;
r = tpm2_context_new(arg_tpm2_device, &c);
if (r < 0)
return r;
r = determine_banks(c, pcr);
if (r < 0)
return r;
if (strv_isempty(arg_banks)) /* Still none? */
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate.");
_cleanup_free_ char *joined_banks = NULL;
joined_banks = strv_join(arg_banks, ", ");
if (!joined_banks)
return log_oom();
_cleanup_free_ char *safe = NULL;
if (size > EXTENSION_STRING_SAFE_LIMIT) {
safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT);
if (!safe)
return log_oom();
if (!strextend(&safe, "..."))
return log_oom();
} else {
safe = cescape_length(data, size);
if (!safe)
return log_oom();
}
log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks);
r = tpm2_extend_bytes(c, arg_banks, pcr, data, size, /* secret= */ NULL, /* secret_size= */ 0, event, safe);
if (r < 0)
return log_error_errno(r, "Could not extend PCR: %m");
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", pcr, safe, joined_banks),
"MEASURING=%s", safe,
"PCR=%u", pcr,
"BANKS=%s", joined_banks);
return 0;
}
typedef struct MethodExtendParameters {
unsigned pcr;
const char *text;
void *data;
size_t data_size;
} MethodExtendParameters;
static int json_dispatch_binary_data(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
MethodExtendParameters *p = ASSERT_PTR(userdata);
_cleanup_free_ void *d = NULL;
size_t l;
int r;
r = json_variant_unbase64(variant, &d, &l);
if (r < 0)
return json_log(variant, flags, r, "JSON variant is not a base64 string.");
free_and_replace(p->data, d);
p->data_size = l;
return 0;
}
static int vl_method_extend(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
static const JsonDispatch dispatch_table[] = {
{ "pcr", JSON_VARIANT_UNSIGNED, json_dispatch_uint, offsetof(MethodExtendParameters, pcr), JSON_MANDATORY },
{ "text", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 },
{ "data", JSON_VARIANT_STRING, json_dispatch_binary_data, 0, 0 },
{}
};
MethodExtendParameters p = {
.pcr = UINT_MAX,
};
int r;
assert(link);
r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
if (r < 0)
return r;
if (!TPM2_PCR_INDEX_VALID(p.pcr))
return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "pcr")));
if (p.text) {
/* Specifying both the text string and the binary data is not allowed */
if (p.data)
return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "data")));
r = extend_now(p.pcr, p.text, strlen(p.text), _TPM2_USERSPACE_EVENT_TYPE_INVALID);
} else if (p.data)
r = extend_now(p.pcr, p.data, p.data_size, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
else
return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "text")));
if (r < 0)
return r;
return varlink_reply(link, NULL);
}
static int run(int argc, char *argv[]) {
_cleanup_free_ char *joined = NULL, *word = NULL;
_cleanup_free_ char *word = NULL;
Tpm2UserspaceEventType event;
size_t length;
int r;
log_setup();
@@ -269,6 +388,30 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
if (arg_varlink) {
_cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL;
/* Invocation as Varlink service */
r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_PCRExtend);
if (r < 0)
return log_error_errno(r, "Failed to add Varlink interface: %m");
r = varlink_server_bind_method(varlink_server, "io.systemd.PCRExtend.Extend", vl_method_extend);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink method: %m");
r = varlink_server_loop_auto(varlink_server);
if (r < 0)
return log_error_errno(r, "Failed to run Varlink event loop: %m");
return EXIT_SUCCESS;
}
if (arg_file_system) {
_cleanup_free_ char *normalized = NULL, *normalized_escaped = NULL;
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
@@ -348,8 +491,6 @@ static int run(int argc, char *argv[]) {
return EXIT_SUCCESS;
}
length = strlen(word);
/* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
r = efi_measured_uki(LOG_ERR);
if (r < 0)
@@ -359,34 +500,10 @@ static int run(int argc, char *argv[]) {
return EXIT_SUCCESS;
}
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
r = tpm2_context_new(arg_tpm2_device, &c);
r = extend_now(arg_pcr_index, word, strlen(word), event);
if (r < 0)
return log_error_errno(r, "Failed to create TPM2 context: %m");
r = determine_banks(c, arg_pcr_index);
if (r < 0)
return r;
if (strv_isempty(arg_banks)) /* Still none? */
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate.");
joined = strv_join(arg_banks, ", ");
if (!joined)
return log_oom();
log_debug("Measuring '%s' into PCR index %u, banks %s.", word, arg_pcr_index, joined);
r = tpm2_extend_bytes(c, arg_banks, arg_pcr_index, word, length, NULL, 0, event, word);
if (r < 0)
return log_error_errno(r, "Could not extend PCR: %m");
log_struct(LOG_INFO,
"MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", arg_pcr_index, word, joined),
"MEASURING=%s", word,
"PCR=%u", arg_pcr_index,
"BANKS=%s", joined);
return EXIT_SUCCESS;
}
+10
View File
@@ -5,6 +5,8 @@
#include "resolved-dns-synthesize.h"
#include "resolved-varlink.h"
#include "socket-netlink.h"
#include "varlink-io.systemd.Resolve.h"
#include "varlink-io.systemd.Resolve.Monitor.h"
typedef struct LookupParameters {
int ifindex;
@@ -699,6 +701,10 @@ static int varlink_monitor_server_init(Manager *m) {
varlink_server_set_userdata(server, m);
r = varlink_server_add_interface(server, &vl_interface_io_systemd_Resolve_Monitor);
if (r < 0)
return log_error_errno(r, "Failed to add Resolve.Monitor interface to varlink server: %m");
r = varlink_server_bind_method_many(
server,
"io.systemd.Resolve.Monitor.SubscribeQueryResults", vl_method_subscribe_query_results,
@@ -741,6 +747,10 @@ static int varlink_main_server_init(Manager *m) {
varlink_server_set_userdata(s, m);
r = varlink_server_add_interface(s, &vl_interface_io_systemd_Resolve);
if (r < 0)
return log_error_errno(r, "Failed to add Resolve interface to varlink server: %m");
r = varlink_server_bind_method_many(
s,
"io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname,
+10
View File
@@ -166,6 +166,16 @@ shared_sources = files(
'userdb-dropin.c',
'userdb.c',
'varlink.c',
'varlink-idl.c',
'varlink-io.systemd.c',
'varlink-io.systemd.Journal.c',
'varlink-io.systemd.ManagedOOM.c',
'varlink-io.systemd.PCRExtend.c',
'varlink-io.systemd.Resolve.Monitor.c',
'varlink-io.systemd.Resolve.c',
'varlink-io.systemd.UserDatabase.c',
'varlink-io.systemd.oom.c',
'varlink-org.varlink.service.c',
'verb-log-control.c',
'verbs.c',
'vlan-util.c',
File diff suppressed because it is too large Load Diff
+158
View File
@@ -0,0 +1,158 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include "json.h"
#include "macro.h"
/* This implements the Varlink Interface Definition Language ("Varlink IDL"),
* i.e. https://varlink.org/Interface-Definition
*
* Primarily allows encoding static interface definitions in C code, that can be converted to the textual IDL
* format on-the-fly. Can also parse the textual format back to C structures. Validates the interface
* definitions for internal consistency and validates JSON objects against the interface definitions. */
typedef enum VarlinkSymbolType {
VARLINK_ENUM_TYPE,
VARLINK_STRUCT_TYPE,
VARLINK_METHOD,
VARLINK_ERROR,
_VARLINK_SYMBOL_TYPE_MAX,
_VARLINK_SYMBOL_TYPE_INVALID = -EINVAL,
} VarlinkSymbolType;
typedef enum VarlinkFieldType {
_VARLINK_FIELD_TYPE_END_MARKER = 0, /* zero type means: this is the last entry in the fields[] array of VarlinkSymbol */
VARLINK_STRUCT,
VARLINK_ENUM,
VARLINK_NAMED_TYPE,
VARLINK_BOOL,
VARLINK_INT,
VARLINK_FLOAT,
VARLINK_STRING,
VARLINK_OBJECT,
VARLINK_ENUM_VALUE,
_VARLINK_FIELD_TYPE_MAX,
_VARLINK_FIELD_TYPE_INVALID = -EINVAL,
} VarlinkFieldType;
typedef enum VarlinkFieldDirection {
VARLINK_REGULAR,
VARLINK_INPUT,
VARLINK_OUTPUT,
_VARLINK_FIELD_DIRECTION_MAX,
_VARLINK_FIELD_DIRECTION_INVALID = -EINVAL,
} VarlinkFieldDirection;
typedef enum VarlinkFieldFlags {
VARLINK_ARRAY = 1 << 0,
VARLINK_MAP = 1 << 1,
VARLINK_NULLABLE = 1 << 2,
_VARLINK_FIELD_FLAGS_MAX = (1 << 3) - 1,
_VARLINK_FIELD_FLAGS_INVALID = -EINVAL,
} VarlinkFieldFlags;
typedef struct VarlinkField VarlinkField;
typedef struct VarlinkSymbol VarlinkSymbol;
typedef struct VarlinkInterface VarlinkInterface;
/* Fields are the components making up symbols */
struct VarlinkField {
const char *name;
VarlinkFieldType field_type;
VarlinkFieldFlags field_flags;
VarlinkFieldDirection field_direction; /* in case of method call fields: whether input or output argument */
const VarlinkSymbol *symbol; /* VARLINK_STRUCT, VARLINK_ENUM: anonymous symbol that carries the definitions, VARLINK_NAMED_TYPE: resolved symbol */
const char *named_type; /* VARLINK_NAMED_TYPE */
};
/* Symbols are primary named concepts in an interface, and are methods, errors or named types (either enum or struct). */
struct VarlinkSymbol {
const char *name; /* most symbols have a name, but sometimes they are created on-the-fly for fields, in which case they are anonymous */
VarlinkSymbolType symbol_type;
VarlinkField fields[];
};
/* An interface definition has a name and consist of symbols */
struct VarlinkInterface {
const char *name;
const VarlinkSymbol *symbols[];
};
#define VARLINK_DEFINE_FIELD(_name, _field_type, _field_flags) \
{ .name = #_name, .field_type = (_field_type), .field_flags = (_field_flags) }
#define VARLINK_DEFINE_FIELD_BY_TYPE(_name, _named_type, _field_flags) \
{ .name = #_name, .field_type = VARLINK_NAMED_TYPE, .named_type = #_named_type, .symbol = &vl_type_ ## _named_type, .field_flags = (_field_flags) }
#define VARLINK_DEFINE_INPUT(_name, _field_type, _field_flags) \
{ .name = #_name, .field_type = (_field_type), .field_flags = (_field_flags), .field_direction = VARLINK_INPUT }
#define VARLINK_DEFINE_INPUT_BY_TYPE(_name, _named_type, _field_flags) \
{ .name = #_name, .field_type = VARLINK_NAMED_TYPE, .named_type = #_named_type, .symbol = &vl_type_ ## _named_type, .field_flags = (_field_flags), .field_direction = VARLINK_INPUT }
#define VARLINK_DEFINE_OUTPUT(_name, _field_type, _field_flags) \
{ .name = #_name, .field_type = (_field_type), .field_flags = (_field_flags), .field_direction = VARLINK_OUTPUT }
#define VARLINK_DEFINE_OUTPUT_BY_TYPE(_name, _named_type, _field_flags) \
{ .name = #_name, .field_type = VARLINK_NAMED_TYPE, .named_type = #_named_type, .symbol = &vl_type_ ## _named_type, .field_flags = (_field_flags), .field_direction = VARLINK_OUTPUT }
#define VARLINK_DEFINE_ENUM_VALUE(_name) \
{ .name = #_name, .field_type = VARLINK_ENUM_VALUE }
#define VARLINK_DEFINE_METHOD(_name, ...) \
const VarlinkSymbol vl_method_ ## _name = { \
.name = #_name, \
.symbol_type = VARLINK_METHOD, \
.fields = { __VA_ARGS__ __VA_OPT__(,) {}}, \
}
#define VARLINK_DEFINE_ERROR(_name, ...) \
const VarlinkSymbol vl_error_ ## _name = { \
.name = #_name, \
.symbol_type = VARLINK_ERROR, \
.fields = { __VA_ARGS__ __VA_OPT__(,) {}}, \
}
#define VARLINK_DEFINE_STRUCT_TYPE(_name, ...) \
const VarlinkSymbol vl_type_ ## _name = { \
.name = #_name, \
.symbol_type = VARLINK_STRUCT_TYPE, \
.fields = { __VA_ARGS__ __VA_OPT__(,) {}}, \
}
#define VARLINK_DEFINE_ENUM_TYPE(_name, ...) \
const VarlinkSymbol vl_type_ ## _name = { \
.name = #_name, \
.symbol_type = VARLINK_ENUM_TYPE, \
.fields = { __VA_ARGS__ __VA_OPT__(,) {}}, \
}
#define VARLINK_DEFINE_INTERFACE(_name, _full_name, ...) \
const VarlinkInterface vl_interface_ ## _name = { \
.name = (_full_name), \
.symbols = { __VA_ARGS__ __VA_OPT__(,) NULL}, \
}
int varlink_idl_dump(FILE *f, int use_colors, const VarlinkInterface *interface);
int varlink_idl_format(const VarlinkInterface *interface, char **ret);
int varlink_idl_parse(const char *text, unsigned *ret_line, unsigned *ret_column, VarlinkInterface **ret);
VarlinkInterface* varlink_interface_free(VarlinkInterface *interface);
DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkInterface*, varlink_interface_free);
bool varlink_idl_field_name_is_valid(const char *name);
bool varlink_idl_symbol_name_is_valid(const char *name);
bool varlink_idl_interface_name_is_valid(const char *name);
int varlink_idl_consistent(const VarlinkInterface *interface, bool level);
const VarlinkSymbol* varlink_idl_find_symbol(const VarlinkInterface *interface, VarlinkSymbolType type, const char *name);
const VarlinkField* varlink_idl_find_field(const VarlinkSymbol *symbol, const char *name);
int varlink_idl_validate_method_call(const VarlinkSymbol *method, JsonVariant *v, const char **bad_field);
int varlink_idl_validate_method_reply(const VarlinkSymbol *method, JsonVariant *v, const char **bad_field);
int varlink_idl_validate_error(const VarlinkSymbol *error, JsonVariant *v, const char **bad_field);
+19
View File
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.Journal.h"
static VARLINK_DEFINE_METHOD(Synchronize);
static VARLINK_DEFINE_METHOD(Rotate);
static VARLINK_DEFINE_METHOD(FlushToVar);
static VARLINK_DEFINE_METHOD(RelinquishVar);
static VARLINK_DEFINE_ERROR(NotSupportedByNamespaces);
VARLINK_DEFINE_INTERFACE(
io_systemd_Journal,
"io.systemd.Journal",
&vl_method_Synchronize,
&vl_method_Rotate,
&vl_method_FlushToVar,
&vl_method_RelinquishVar,
&vl_error_NotSupportedByNamespaces);
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd_Journal;
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.ManagedOOM.h"
/* Pull in vl_type_ControlGroup, since both interfaces need it */
#include "varlink-io.systemd.oom.h"
/* This is PID1's Varlink service, where PID 1 is the server and oomd is the client.
*
* Compare with io.systemd.oom where the client/server roles of oomd and the service manager are swapped! */
static VARLINK_DEFINE_METHOD(
SubscribeManagedOOMCGroups,
VARLINK_DEFINE_OUTPUT_BY_TYPE(cgroups, ControlGroup, VARLINK_ARRAY));
static VARLINK_DEFINE_ERROR(SubscriptionTaken);
VARLINK_DEFINE_INTERFACE(
io_systemd_ManagedOOM,
"io.systemd.ManagedOOM",
&vl_method_SubscribeManagedOOMCGroups,
&vl_type_ControlGroup,
&vl_error_SubscriptionTaken);
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd_ManagedOOM;
+14
View File
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.PCRExtend.h"
static VARLINK_DEFINE_METHOD(
Extend,
VARLINK_DEFINE_INPUT(pcr, VARLINK_INT, 0),
VARLINK_DEFINE_INPUT(text, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(data, VARLINK_STRING, VARLINK_NULLABLE));
VARLINK_DEFINE_INTERFACE(
io_systemd_PCRExtend,
"io.systemd.PCRExtend",
&vl_method_Extend);
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd_PCRExtend;
@@ -0,0 +1,136 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.Resolve.Monitor.h"
VARLINK_DEFINE_STRUCT_TYPE(
ResourceKey,
VARLINK_DEFINE_FIELD(class, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(type, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0));
VARLINK_DEFINE_STRUCT_TYPE(
ResourceRecord,
VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0),
VARLINK_DEFINE_FIELD(priority, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(weight, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(port, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(cpu, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(os, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(items, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(address, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(mname, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(rname, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(serial, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(refresh, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(expire, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(minimum, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(exchange, VARLINK_STRING, VARLINK_NULLABLE));
VARLINK_DEFINE_STRUCT_TYPE(
Answer,
VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE));
VARLINK_DEFINE_METHOD(
SubscribeQueryResults,
/* First reply */
VARLINK_DEFINE_OUTPUT(ready, VARLINK_BOOL, VARLINK_NULLABLE),
/* Subsequent replies */
VARLINK_DEFINE_OUTPUT(state, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(rcode, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(errno, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT_BY_TYPE(question, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(collectedQuestions, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT_BY_TYPE(answer, Answer, VARLINK_NULLABLE|VARLINK_ARRAY));
VARLINK_DEFINE_STRUCT_TYPE(
CacheEntry,
VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0),
VARLINK_DEFINE_FIELD_BY_TYPE(rrs, ResourceRecord, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(type, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(until, VARLINK_INT, 0));
VARLINK_DEFINE_STRUCT_TYPE(
ScopeCache,
VARLINK_DEFINE_FIELD(protocol, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(family, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(ifname, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD_BY_TYPE(cache, CacheEntry, VARLINK_ARRAY));
VARLINK_DEFINE_METHOD(
DumpCache,
VARLINK_DEFINE_OUTPUT_BY_TYPE(dump, ScopeCache, VARLINK_ARRAY));
VARLINK_DEFINE_STRUCT_TYPE(
ServerState,
VARLINK_DEFINE_FIELD(Server, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(Type, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(Interface, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(InterfaceIndex, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(VerifiedFeatureLevel, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(PossibleFeatureLevel, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(DNSSECMode, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(DNSSECSupported, VARLINK_BOOL, 0),
VARLINK_DEFINE_FIELD(ReceivedUDPFragmentMax, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(FailedUDPAttempts, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(FailedTCPAttempts, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(PacketTruncated, VARLINK_BOOL, 0),
VARLINK_DEFINE_FIELD(PacketBadOpt, VARLINK_BOOL, 0),
VARLINK_DEFINE_FIELD(PacketRRSIGMissing, VARLINK_BOOL, 0),
VARLINK_DEFINE_FIELD(PacketInvalid, VARLINK_BOOL, 0),
VARLINK_DEFINE_FIELD(PacketDoOff, VARLINK_BOOL, 0));
VARLINK_DEFINE_METHOD(
DumpServerState,
VARLINK_DEFINE_OUTPUT_BY_TYPE(dump, ServerState, VARLINK_ARRAY));
VARLINK_DEFINE_STRUCT_TYPE(
TransactionStatistics,
VARLINK_DEFINE_FIELD(currentTransactions, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(totalTransactions, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(totalTimeouts, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(totalTimeoutsServedStale, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(totalFailedResponses, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(totalFailedResponsesServedStale, VARLINK_INT, 0));
VARLINK_DEFINE_STRUCT_TYPE(
CacheStatistics,
VARLINK_DEFINE_FIELD(size, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(hits, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(misses, VARLINK_INT, 0));
VARLINK_DEFINE_STRUCT_TYPE(
DnssecStatistics,
VARLINK_DEFINE_FIELD(secure, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(insecure, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(bogus, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(indeterminate, VARLINK_INT, 0));
VARLINK_DEFINE_METHOD(
DumpStatistics,
VARLINK_DEFINE_OUTPUT_BY_TYPE(transactions, TransactionStatistics, 0),
VARLINK_DEFINE_OUTPUT_BY_TYPE(cache, CacheStatistics, 0),
VARLINK_DEFINE_OUTPUT_BY_TYPE(dnssec, DnssecStatistics, 0));
VARLINK_DEFINE_METHOD(ResetStatistics);
VARLINK_DEFINE_INTERFACE(
io_systemd_Resolve_Monitor,
"io.systemd.Resolve.Monitor",
&vl_method_SubscribeQueryResults,
&vl_method_DumpCache,
&vl_method_DumpServerState,
&vl_method_DumpStatistics,
&vl_method_ResetStatistics,
&vl_type_ResourceKey,
&vl_type_ResourceRecord,
&vl_type_Answer,
&vl_type_CacheEntry,
&vl_type_ScopeCache,
&vl_type_TransactionStatistics,
&vl_type_CacheStatistics,
&vl_type_DnssecStatistics,
&vl_type_ServerState);
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd_Resolve_Monitor;
+76
View File
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.Resolve.h"
static VARLINK_DEFINE_STRUCT_TYPE(
ResolvedAddress,
VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(family, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(address, VARLINK_INT, VARLINK_ARRAY));
static VARLINK_DEFINE_METHOD(
ResolveHostname,
VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0),
VARLINK_DEFINE_INPUT(family, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT_BY_TYPE(addresses, ResolvedAddress, VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT(name, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0));
static VARLINK_DEFINE_STRUCT_TYPE(
ResolvedName,
VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0));
static VARLINK_DEFINE_METHOD(
ResolveAddress,
VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(family, VARLINK_INT, 0),
VARLINK_DEFINE_INPUT(address, VARLINK_INT, VARLINK_ARRAY),
VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT_BY_TYPE(names, ResolvedName, VARLINK_ARRAY),
VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0));
static VARLINK_DEFINE_ERROR(NoNameServers);
static VARLINK_DEFINE_ERROR(NoSuchResourceRecord);
static VARLINK_DEFINE_ERROR(QueryTimedOut);
static VARLINK_DEFINE_ERROR(MaxAttemptsReached);
static VARLINK_DEFINE_ERROR(InvalidReply);
static VARLINK_DEFINE_ERROR(QueryAborted);
static VARLINK_DEFINE_ERROR(
DNSSECValidationFailed,
VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0));
static VARLINK_DEFINE_ERROR(NoTrustAnchor);
static VARLINK_DEFINE_ERROR(ResourceRecordTypeUnsupported);
static VARLINK_DEFINE_ERROR(NetworkDown);
static VARLINK_DEFINE_ERROR(NoSource);
static VARLINK_DEFINE_ERROR(StubLoop);
static VARLINK_DEFINE_ERROR(
DNSError,
VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0));
static VARLINK_DEFINE_ERROR(CNAMELoop);
static VARLINK_DEFINE_ERROR(BadAddressSize);
VARLINK_DEFINE_INTERFACE(
io_systemd_Resolve,
"io.systemd.Resolve",
&vl_method_ResolveHostname,
&vl_method_ResolveAddress,
&vl_type_ResolvedAddress,
&vl_type_ResolvedName,
&vl_error_NoNameServers,
&vl_error_NoSuchResourceRecord,
&vl_error_QueryTimedOut,
&vl_error_MaxAttemptsReached,
&vl_error_InvalidReply,
&vl_error_QueryAborted,
&vl_error_DNSSECValidationFailed,
&vl_error_NoTrustAnchor,
&vl_error_ResourceRecordTypeUnsupported,
&vl_error_NetworkDown,
&vl_error_NoSource,
&vl_error_StubLoop,
&vl_error_DNSError,
&vl_error_CNAMELoop,
&vl_error_BadAddressSize);
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd_Resolve;
@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.UserDatabase.h"
static VARLINK_DEFINE_METHOD(
GetUserRecord,
VARLINK_DEFINE_INPUT(uid, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(userName, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(service, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(record, VARLINK_OBJECT, 0),
VARLINK_DEFINE_OUTPUT(incomplete, VARLINK_BOOL, 0));
static VARLINK_DEFINE_METHOD(
GetGroupRecord,
VARLINK_DEFINE_INPUT(gid, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(groupName, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(service, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(record, VARLINK_OBJECT, 0),
VARLINK_DEFINE_OUTPUT(incomplete, VARLINK_BOOL, 0));
static VARLINK_DEFINE_METHOD(
GetMemberships,
VARLINK_DEFINE_INPUT(userName, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(groupName, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_INPUT(service, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(userName, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(groupName, VARLINK_STRING, 0));
static VARLINK_DEFINE_ERROR(NoRecordFound);
static VARLINK_DEFINE_ERROR(BadService);
static VARLINK_DEFINE_ERROR(ServiceNotAvailable);
static VARLINK_DEFINE_ERROR(ConflictingRecordNotFound);
static VARLINK_DEFINE_ERROR(EnumerationNotSupported);
/* As per https://systemd.io/USER_GROUP_API/ */
VARLINK_DEFINE_INTERFACE(
io_systemd_UserDatabase,
"io.systemd.UserDatabase",
&vl_method_GetUserRecord,
&vl_method_GetGroupRecord,
&vl_method_GetMemberships,
&vl_error_NoRecordFound,
&vl_error_BadService,
&vl_error_ServiceNotAvailable,
&vl_error_ConflictingRecordNotFound,
&vl_error_EnumerationNotSupported);
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd_UserDatabase;
+21
View File
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.h"
/* These are local errors that never cross the wire, and are our own invention */
static VARLINK_DEFINE_ERROR(Disconnected);
static VARLINK_DEFINE_ERROR(TimedOut);
static VARLINK_DEFINE_ERROR(Protocol);
/* This one we invented, and use for generically propagating system errors (errno) to clients */
static VARLINK_DEFINE_ERROR(
System,
VARLINK_DEFINE_FIELD(errno, VARLINK_INT, 0));
VARLINK_DEFINE_INTERFACE(
io_systemd,
"io.systemd",
&vl_error_Disconnected,
&vl_error_TimedOut,
&vl_error_Protocol,
&vl_error_System);
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_io_systemd;
+25
View File
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-io.systemd.oom.h"
/* This is oomd's Varlink service, where oomd is server and systemd --user is the client.
*
* Compare with io.systemd.ManagedOOM where the client/server roles of the service manager and oomd are
* swapped! */
VARLINK_DEFINE_STRUCT_TYPE(
ControlGroup,
VARLINK_DEFINE_FIELD(mode, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(path, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(property, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(limit, VARLINK_INT, VARLINK_NULLABLE));
static VARLINK_DEFINE_METHOD(
ReportManagedOOMCGroups,
VARLINK_DEFINE_INPUT_BY_TYPE(cgroups, ControlGroup, VARLINK_ARRAY));
VARLINK_DEFINE_INTERFACE(
io_systemd_oom,
"io.systemd.oom",
&vl_method_ReportManagedOOMCGroups,
&vl_type_ControlGroup);
+7
View File
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkSymbol vl_type_ControlGroup;
extern const VarlinkInterface vl_interface_io_systemd_oom;
+45
View File
@@ -0,0 +1,45 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "varlink-org.varlink.service.h"
static VARLINK_DEFINE_METHOD(
GetInfo,
VARLINK_DEFINE_OUTPUT(vendor, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(product, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(version, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(url, VARLINK_STRING, 0),
VARLINK_DEFINE_OUTPUT(interfaces, VARLINK_STRING, VARLINK_ARRAY));
static VARLINK_DEFINE_METHOD(
GetInterfaceDescription,
VARLINK_DEFINE_OUTPUT(interface, VARLINK_STRING, 0));
static VARLINK_DEFINE_ERROR(
InterfaceNotFound,
VARLINK_DEFINE_FIELD(interface, VARLINK_STRING, 0));
static VARLINK_DEFINE_ERROR(
MethodNotFound,
VARLINK_DEFINE_FIELD(method, VARLINK_STRING, 0));
static VARLINK_DEFINE_ERROR(
MethodNotImplemented,
VARLINK_DEFINE_FIELD(method, VARLINK_STRING, 0));
static VARLINK_DEFINE_ERROR(
InvalidParameter,
VARLINK_DEFINE_FIELD(parameter, VARLINK_STRING, 0));
static VARLINK_DEFINE_ERROR(PermissionDenied);
/* As per https://varlink.org/Service */
VARLINK_DEFINE_INTERFACE(
org_varlink_service,
"org.varlink.service",
&vl_method_GetInfo,
&vl_method_GetInterfaceDescription,
&vl_error_InterfaceNotFound,
&vl_error_MethodNotFound,
&vl_error_MethodNotImplemented,
&vl_error_InvalidParameter,
&vl_error_PermissionDenied);
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "varlink-idl.h"
extern const VarlinkInterface vl_interface_org_varlink_service;
+515 -30
View File
@@ -3,6 +3,8 @@
#include <malloc.h>
#include <poll.h>
#include <sd-daemon.h>
#include "alloc-util.h"
#include "errno-util.h"
#include "fd-util.h"
@@ -10,6 +12,7 @@
#include "hashmap.h"
#include "io-util.h"
#include "list.h"
#include "path-util.h"
#include "process-util.h"
#include "selinux-util.h"
#include "serialize.h"
@@ -23,6 +26,9 @@
#include "user-util.h"
#include "varlink.h"
#include "varlink-internal.h"
#include "varlink-org.varlink.service.h"
#include "varlink-io.systemd.h"
#include "version.h"
#define VARLINK_DEFAULT_CONNECTIONS_MAX 4096U
#define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 1024U
@@ -162,6 +168,7 @@ struct Varlink {
VarlinkReply reply_callback;
JsonVariant *current;
VarlinkSymbol *current_method;
struct ucred ucred;
bool ucred_acquired:1;
@@ -189,6 +196,8 @@ struct Varlink {
sd_event_source *time_event_source;
sd_event_source *quit_event_source;
sd_event_source *defer_event_source;
pid_t exec_pid;
};
typedef struct VarlinkServerSocket VarlinkServerSocket;
@@ -210,7 +219,9 @@ struct VarlinkServer {
LIST_HEAD(VarlinkServerSocket, sockets);
Hashmap *methods;
Hashmap *methods; /* Fully qualified symbol name of a method → VarlinkMethod */
Hashmap *interfaces; /* Fully qualified interface name → VarlinkInterface* */
Hashmap *symbols; /* Fully qualified symbol name of methord/error → VarlinkSymbol* */
VarlinkConnect connect_callback;
VarlinkDisconnect disconnect_callback;
@@ -218,13 +229,15 @@ struct VarlinkServer {
int64_t event_priority;
unsigned n_connections;
Hashmap *by_uid;
Hashmap *by_uid; /* UID_TO_PTR(uid) → UINT_TO_PTR(n_connections) */
void *userdata;
char *description;
unsigned connections_max;
unsigned connections_per_uid_max;
bool exit_on_idle;
};
static const char* const varlink_state_table[_VARLINK_STATE_MAX] = {
@@ -264,6 +277,7 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(varlink_state, VarlinkState);
log_debug("%s: " fmt, varlink_server_description(s), ##__VA_ARGS__)
static int varlink_format_queue(Varlink *v);
static void varlink_server_test_exit_on_idle(VarlinkServer *s);
static const char *varlink_description(Varlink *v) {
return (v ? v->description : NULL) ?: "varlink";
@@ -398,6 +412,143 @@ int varlink_connect_address(Varlink **ret, const char *address) {
return 0;
}
int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) {
_cleanup_close_pair_ int pair[2] = PIPE_EBADF;
_cleanup_(sigkill_waitp) pid_t pid = 0;
_cleanup_free_ char *command = NULL;
_cleanup_strv_free_ char **argv = NULL;
int r;
assert_return(ret, -EINVAL);
assert_return(_command, -EINVAL);
/* Copy the strings, in case they point into our own argv[], which we'll invalidate shortly because
* we rename the child process */
command = strdup(_command);
if (!command)
return -ENOMEM;
if (strv_isempty(_argv))
argv = strv_new(command);
else
argv = strv_copy(_argv);
if (!argv)
return -ENOMEM;
log_debug("Forking off Varlink child process '%s'.", command);
if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
r = safe_fork_full(
"(sd-vlexec)",
/* stdio_fds= */ NULL,
/* except_fds= */ (int[]) { pair[1] },
/* n_except_fds= */ 1,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE,
&pid);
if (r < 0)
return log_debug_errno(r, "Failed to spawn process: %m");
if (r == 0) {
char spid[DECIMAL_STR_MAX(pid_t)+1];
const char *setenv_list[] = {
"LISTEN_FDS", "1",
"LISTEN_PID", spid,
"LISTEN_FDNAMES", "varlink",
NULL, NULL,
};
/* Child */
pair[0] = -EBADF;
r = move_fd(pair[1], 3, /* cloexec= */ false);
if (r < 0) {
log_debug_errno(r, "Failed to move file descriptor to 3: %m");
_exit(EXIT_FAILURE);
}
xsprintf(spid, PID_FMT, pid);
STRV_FOREACH_PAIR(a, b, setenv_list) {
if (setenv(*a, *b, /* override= */ true) < 0) {
log_debug_errno(errno, "Failed to set environment variable '%s': %m", *a);
_exit(EXIT_FAILURE);
}
}
execvp(command, argv);
log_debug_errno(r, "Failed to invoke process '%s': %m", command);
_exit(EXIT_FAILURE);
}
pair[1] = safe_close(pair[1]);
Varlink *v;
r = varlink_new(&v);
if (r < 0)
return log_debug_errno(r, "Failed to create varlink object: %m");
v->fd = TAKE_FD(pair[0]);
v->af = AF_UNIX;
v->exec_pid = TAKE_PID(pid);
varlink_set_state(v, VARLINK_IDLE_CLIENT);
*ret = v;
return 0;
}
int varlink_connect_url(Varlink **ret, const char *url) {
_cleanup_free_ char *c = NULL;
const char *p;
bool exec;
assert_return(ret, -EINVAL);
assert_return(url, -EINVAL);
// FIXME: Add support for vsock:, ssh-exec:, ssh-unix: URL schemes here. (The latter with OpenSSH
// 9.4's -W switch for referencing remote AF_UNIX sockets.)
/* The Varlink URL scheme is a bit underdefined. We support only the unix: transport for now, plus an
* exec: transport we made up ourselves. Strictly speaking this shouldn't even be called URL, since
* it has nothing to do with Internet URLs by RFC. */
p = startswith(url, "unix:");
if (p)
exec = false;
else {
p = startswith(url, "exec:");
if (!p)
return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported.");
exec = true;
}
/* The varlink.org reference C library supports more than just file system paths. We might want to
* support that one day too. For now simply refuse that. */
if (p[strcspn(p, ";?#")] != '\0')
return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported.");
if (exec || p[0] != '@') { /* no validity checks for abstract namespace */
if (!path_is_absolute(p))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path not absolute, refusing.");
c = strdup(p);
if (!c)
return log_oom_debug();
path_simplify(c);
if (!path_is_normalized(c))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing.");
}
if (exec)
return varlink_connect_exec(ret, c ?: p, NULL);
return varlink_connect_address(ret, c ?: p);
}
int varlink_connect_fd(Varlink **ret, int fd) {
Varlink *v;
int r;
@@ -442,6 +593,7 @@ static void varlink_clear_current(Varlink *v) {
/* Clears the currently processed incoming message */
v->current = json_variant_unref(v->current);
v->current_method = NULL;
close_many(v->input_fds, v->n_input_fds);
v->input_fds = mfree(v->input_fds);
@@ -475,6 +627,11 @@ static void varlink_clear(Varlink *v) {
v->output_queue_tail = NULL;
v->event = sd_event_unref(v->event);
if (v->exec_pid > 0) {
sigterm_wait(v->exec_pid);
v->exec_pid = 0;
}
}
static Varlink* varlink_destroy(Varlink *v) {
@@ -987,10 +1144,84 @@ invalid:
return 1;
}
static int generic_method_get_info(
Varlink *link,
JsonVariant *parameters,
VarlinkMethodFlags flags,
void *userdata) {
_cleanup_strv_free_ char **interfaces = NULL;
_cleanup_free_ char *product = NULL;
int r;
assert(link);
if (json_variant_elements(parameters) != 0)
return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_VARIANT("parameter", json_variant_by_index(parameters, 0))));
product = strjoin("systemd (", program_invocation_short_name, ")");
if (!product)
return -ENOMEM;
VarlinkInterface *interface;
HASHMAP_FOREACH(interface, ASSERT_PTR(link->server)->interfaces) {
r = strv_extend(&interfaces, interface->name);
if (r < 0)
return r;
}
strv_sort(interfaces);
return varlink_replyb(link, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("vendor", "The systemd Project"),
JSON_BUILD_PAIR_STRING("product", product),
JSON_BUILD_PAIR_STRING("version", STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"),
JSON_BUILD_PAIR_STRING("url", "https://systemd.io/"),
JSON_BUILD_PAIR_STRV("interfaces", interfaces)));
}
static int generic_method_get_interface_description(
Varlink *link,
JsonVariant *parameters,
VarlinkMethodFlags flags,
void *userdata) {
static const struct JsonDispatch dispatch_table[] = {
{ "interface", JSON_VARIANT_STRING, json_dispatch_const_string, 0, JSON_MANDATORY },
{}
};
_cleanup_free_ char *text = NULL;
const VarlinkInterface *interface;
const char *name = NULL;
int r;
assert(link);
r = json_dispatch(parameters, dispatch_table, NULL, 0, &name);
if (r < 0)
return r;
interface = hashmap_get(ASSERT_PTR(link->server)->interfaces, name);
if (!interface)
return varlink_errorb(link, VARLINK_ERROR_INTERFACE_NOT_FOUND,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("interface", name)));
r = varlink_idl_format(interface, &text);
if (r < 0)
return r;
return varlink_replyb(link,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("description", text)));
}
static int varlink_dispatch_method(Varlink *v) {
_cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
VarlinkMethodFlags flags = 0;
const char *method = NULL, *error;
const char *method = NULL;
JsonVariant *e;
VarlinkMethod callback;
const char *k;
@@ -1063,37 +1294,51 @@ static int varlink_dispatch_method(Varlink *v) {
assert(v->server);
if (STR_IN_SET(method, "org.varlink.service.GetInfo", "org.varlink.service.GetInterface")) {
/* For now, we don't implement a single of varlink's own methods */
callback = NULL;
error = VARLINK_ERROR_METHOD_NOT_IMPLEMENTED;
} else if (startswith(method, "org.varlink.service.")) {
callback = NULL;
error = VARLINK_ERROR_METHOD_NOT_FOUND;
} else {
callback = hashmap_get(v->server->methods, method);
error = VARLINK_ERROR_METHOD_NOT_FOUND;
/* First consult user supplied method implementations */
callback = hashmap_get(v->server->methods, method);
if (!callback) {
if (streq(method, "org.varlink.service.GetInfo"))
callback = generic_method_get_info;
else if (streq(method, "org.varlink.service.GetInterfaceDescription"))
callback = generic_method_get_interface_description;
}
if (callback) {
r = callback(v, parameters, flags, v->userdata);
if (r < 0) {
log_debug_errno(r, "Callback for %s returned error: %m", method);
bool invalid = false;
/* We got an error back from the callback. Propagate it to the client if the method call remains unanswered. */
if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) {
r = varlink_error_errno(v, r);
if (r < 0)
return r;
v->current_method = hashmap_get(v->server->symbols, method);
if (!v->current_method)
log_debug("No interface description defined for method '%s', not validating.", method);
else {
const char *bad_field;
r = varlink_idl_validate_method_call(v->current_method, parameters, &bad_field);
if (r < 0) {
log_debug_errno(r, "Parameters for method %s() didn't pass validation on field '%s': %m", method, strna(bad_field));
r = varlink_errorb(v, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", bad_field)));
invalid = true;
}
}
if (!invalid) {
r = callback(v, parameters, flags, v->userdata);
if (r < 0) {
log_debug_errno(r, "Callback for %s returned error: %m", method);
/* We got an error back from the callback. Propagate it to the client if the method call remains unanswered. */
if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) {
r = varlink_error_errno(v, r);
if (r < 0)
return r;
}
}
}
} else if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) {
assert(error);
r = varlink_errorb(v, error, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method))));
r = varlink_errorb(v, VARLINK_ERROR_METHOD_NOT_FOUND, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method))));
if (r < 0)
return r;
}
} else
r = 0;
switch (v->state) {
@@ -1113,7 +1358,6 @@ static int varlink_dispatch_method(Varlink *v) {
default:
assert_not_reached();
}
return r;
@@ -1282,6 +1526,15 @@ int varlink_wait(Varlink *v, usec_t timeout) {
return 1;
}
int varlink_is_idle(Varlink *v) {
assert_return(v, -EINVAL);
/* Returns true if there's nothing pending on the connection anymore, i.e. we processed all incoming
* or outgoing messages fully, or finished disconnection */
return IN_SET(v->state, VARLINK_DISCONNECTED, VARLINK_IDLE_CLIENT, VARLINK_IDLE_SERVER);
}
int varlink_get_fd(Varlink *v) {
assert_return(v, -EINVAL);
@@ -1406,6 +1659,7 @@ static void varlink_detach_server(Varlink *v) {
if (saved_server->disconnect_callback)
saved_server->disconnect_callback(saved_server, v, saved_server->userdata);
varlink_server_test_exit_on_idle(saved_server);
varlink_server_unref(saved_server);
varlink_unref(v);
}
@@ -1849,6 +2103,14 @@ int varlink_reply(Varlink *v, JsonVariant *parameters) {
if (r < 0)
return varlink_log_errno(v, r, "Failed to build json message: %m");
if (v->current_method) {
const char *bad_field = NULL;
r = varlink_idl_validate_method_reply(v->current_method, parameters, &bad_field);
if (r < 0)
log_debug_errno(r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", v->current_method->name, strna(bad_field));
}
r = varlink_enqueue_json(v, m);
if (r < 0)
return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
@@ -1915,6 +2177,17 @@ int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters) {
if (r < 0)
return varlink_log_errno(v, r, "Failed to build json message: %m");
VarlinkSymbol *symbol = hashmap_get(v->server->symbols, error_id);
if (!symbol)
log_debug("No interface description defined for error '%s', not validating.", error_id);
else {
const char *bad_field = NULL;
r = varlink_idl_validate_method_reply(symbol, parameters, &bad_field);
if (r < 0)
log_debug_errno(r, "Parameters for error %s didn't pass validation on field '%s', ignoring: %m", error_id, strna(bad_field));
}
r = varlink_enqueue_json(v, m);
if (r < 0)
return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
@@ -2016,6 +2289,14 @@ int varlink_notify(Varlink *v, JsonVariant *parameters) {
if (r < 0)
return varlink_log_errno(v, r, "Failed to build json message: %m");
if (v->current_method) {
const char *bad_field = NULL;
r = varlink_idl_validate_method_reply(v->current_method, parameters, &bad_field);
if (r < 0)
log_debug_errno(r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", v->current_method->name, strna(bad_field));
}
r = varlink_enqueue_json(v, m);
if (r < 0)
return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
@@ -2445,7 +2726,8 @@ int varlink_set_allow_fd_passing_output(Varlink *v, bool b) {
}
int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
VarlinkServer *s;
_cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL;
int r;
assert_return(ret, -EINVAL);
assert_return((flags & ~_VARLINK_SERVER_FLAGS_ALL) == 0, -EINVAL);
@@ -2461,7 +2743,14 @@ int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) {
.connections_per_uid_max = varlink_server_connections_per_uid_max(NULL),
};
*ret = s;
r = varlink_server_add_interface_many(
s,
&vl_interface_io_systemd,
&vl_interface_org_varlink_service);
if (r < 0)
return r;
*ret = TAKE_PTR(s);
return 0;
}
@@ -2477,6 +2766,8 @@ static VarlinkServer* varlink_server_destroy(VarlinkServer *s) {
free(m);
hashmap_free(s->methods);
hashmap_free(s->interfaces);
hashmap_free(s->symbols);
hashmap_free(s->by_uid);
sd_event_unref(s->event);
@@ -2772,6 +3063,49 @@ int varlink_server_listen_address(VarlinkServer *s, const char *address, mode_t
return 0;
}
int varlink_server_listen_auto(VarlinkServer *s) {
_cleanup_strv_free_ char **names = NULL;
int r, n = 0;
assert_return(s, -EINVAL);
/* Adds all passed fds marked as "varlink" to our varlink server. These fds can either refer to a
* listening socket or to a connection socket.
*
* See https://varlink.org/#activation for the environment variables this is backed by and the
* recommended "varlink" identifier in $LISTEN_FDNAMES. */
r = sd_listen_fds_with_names(/* unset_environment= */ false, &names);
if (r < 0)
return r;
for (int i = 0; i < r; i++) {
int b, fd;
socklen_t l = sizeof(b);
if (!streq(names[i], "varlink"))
continue;
fd = SD_LISTEN_FDS_START + i;
if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &b, &l) < 0)
return -errno;
assert(l == sizeof(b));
if (b) /* Listening socket? */
r = varlink_server_listen_fd(s, fd);
else /* Otherwise assume connection socket */
r = varlink_server_add_connection(s, fd, NULL);
if (r < 0)
return r;
n++;
}
return n;
}
void* varlink_server_set_userdata(VarlinkServer *s, void *userdata) {
void *ret;
@@ -2789,6 +3123,34 @@ void* varlink_server_get_userdata(VarlinkServer *s) {
return s->userdata;
}
int varlink_server_loop_auto(VarlinkServer *server) {
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
int r;
assert_return(server, -EINVAL);
assert_return(!server->event, -EBUSY);
/* Runs a Varlink service event loop populated with a passed fd. Exits on the last connection. */
r = sd_event_new(&event);
if (r < 0)
return r;
r = varlink_server_set_exit_on_idle(server, true);
if (r < 0)
return r;
r = varlink_server_attach_event(server, event, 0);
if (r < 0)
return r;
r = varlink_server_listen_auto(server);
if (r < 0)
return r;
return sd_event_loop(event);
}
static VarlinkServerSocket* varlink_server_socket_destroy(VarlinkServerSocket *ss) {
if (!ss)
return NULL;
@@ -2813,9 +3175,23 @@ int varlink_server_shutdown(VarlinkServer *s) {
return 0;
}
static void varlink_server_test_exit_on_idle(VarlinkServer *s) {
assert(s);
if (s->exit_on_idle && s->event && s->n_connections == 0)
(void) sd_event_exit(s->event, 0);
}
int varlink_server_set_exit_on_idle(VarlinkServer *s, bool b) {
assert_return(s, -EINVAL);
s->exit_on_idle = b;
varlink_server_test_exit_on_idle(s);
return 0;
}
static int varlink_server_add_socket_event_source(VarlinkServer *s, VarlinkServerSocket *ss, int64_t priority) {
_cleanup_(sd_event_source_unrefp) sd_event_source *es = NULL;
int r;
assert(s);
@@ -2880,6 +3256,22 @@ sd_event *varlink_server_get_event(VarlinkServer *s) {
return s->event;
}
static bool varlink_symbol_in_interface(const char *method, const char *interface) {
const char *p;
assert(method);
assert(interface);
p = startswith(method, interface);
if (!p)
return false;
if (*p != '.')
return false;
return !strchr(p+1, '.');
}
int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMethod callback) {
_cleanup_free_ char *m = NULL;
int r;
@@ -2888,7 +3280,8 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth
assert_return(method, -EINVAL);
assert_return(callback, -EINVAL);
if (startswith(method, "org.varlink.service."))
if (varlink_symbol_in_interface(method, "org.varlink.service") ||
varlink_symbol_in_interface(method, "io.systemd"))
return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method);
m = strdup(method);
@@ -2952,6 +3345,63 @@ int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect callback)
return 0;
}
int varlink_server_add_interface(VarlinkServer *s, const VarlinkInterface *interface) {
int r;
assert_return(s, -EINVAL);
assert_return(interface, -EINVAL);
assert_return(interface->name, -EINVAL);
if (hashmap_contains(s->interfaces, interface->name))
return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate registration of interface '%s'.", interface->name);
r = hashmap_ensure_put(&s->interfaces, &string_hash_ops, interface->name, (void*) interface);
if (r < 0)
return r;
for (const VarlinkSymbol *const*symbol = interface->symbols; *symbol; symbol++) {
_cleanup_free_ char *j = NULL;
/* We only ever want to validate method calls/replies and errors against the interface
* definitions, hence don't bother with the type symbols */
if (!IN_SET((*symbol)->symbol_type, VARLINK_METHOD, VARLINK_ERROR))
continue;
j = strjoin(interface->name, ".", (*symbol)->name);
if (!j)
return -ENOMEM;
r = hashmap_ensure_put(&s->symbols, &string_hash_ops_free, j, (void*) *symbol);
if (r < 0)
return r;
TAKE_PTR(j);
}
return 0;
}
int varlink_server_add_interface_many_internal(VarlinkServer *s, ...) {
va_list ap;
int r = 0;
assert_return(s, -EINVAL);
va_start(ap, s);
for (;;) {
const VarlinkInterface *interface = va_arg(ap, const VarlinkInterface*);
if (!interface)
break;
r = varlink_server_add_interface(s, interface);
if (r < 0)
break;
}
va_end(ap);
return r;
}
unsigned varlink_server_connections_max(VarlinkServer *s) {
int dts;
@@ -3090,3 +3540,38 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f
LIST_PREPEND(sockets, s->sockets, TAKE_PTR(ss));
return 0;
}
int varlink_invocation(VarlinkInvocationFlags flags) {
_cleanup_strv_free_ char **names = NULL;
int r, b;
socklen_t l = sizeof(b);
/* Returns true if this is a "pure" varlink server invocation, i.e. with one fd passed. */
r = sd_listen_fds_with_names(/* unset_environment= */ false, &names);
if (r < 0)
return r;
if (r == 0)
return false;
if (r > 1)
return -ETOOMANYREFS;
if (!strv_equal(names, STRV_MAKE("varlink")))
return false;
if (FLAGS_SET(flags, VARLINK_ALLOW_LISTEN|VARLINK_ALLOW_ACCEPT)) /* Both flags set? Then allow everything */
return true;
if ((flags & (VARLINK_ALLOW_LISTEN|VARLINK_ALLOW_ACCEPT)) == 0) /* Neither is set, then fail */
return -EISCONN;
if (getsockopt(SD_LISTEN_FDS_START, SOL_SOCKET, SO_ACCEPTCONN, &b, &l) < 0)
return -errno;
assert(l == sizeof(b));
if (!FLAGS_SET(flags, b ? VARLINK_ALLOW_LISTEN : VARLINK_ALLOW_ACCEPT))
return -EISCONN;
return true;
}
+26 -2
View File
@@ -5,6 +5,7 @@
#include "json.h"
#include "time-util.h"
#include "varlink-idl.h"
/* A minimal Varlink implementation. We only implement the minimal, obvious bits here though. No validation,
* no introspection, no name service, just the stuff actually needed.
@@ -45,7 +46,6 @@ typedef enum VarlinkServerFlags {
VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */
VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */
VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from VarlinkServer userdata */
_VARLINK_SERVER_FLAGS_ALL = (1 << 4) - 1,
} VarlinkServerFlags;
@@ -55,6 +55,8 @@ typedef int (*VarlinkConnect)(VarlinkServer *server, Varlink *link, void *userda
typedef void (*VarlinkDisconnect)(VarlinkServer *server, Varlink *link, void *userdata);
int varlink_connect_address(Varlink **ret, const char *address);
int varlink_connect_exec(Varlink **ret, const char *command, char **argv);
int varlink_connect_url(Varlink **ret, const char *url);
int varlink_connect_fd(Varlink **ret, int fd);
Varlink* varlink_ref(Varlink *link);
@@ -71,6 +73,8 @@ sd_event *varlink_get_event(Varlink *v);
int varlink_process(Varlink *v);
int varlink_wait(Varlink *v, usec_t timeout);
int varlink_is_idle(Varlink *v);
int varlink_flush(Varlink *v);
int varlink_close(Varlink *v);
@@ -142,6 +146,7 @@ VarlinkServer *varlink_server_unref(VarlinkServer *s);
/* Add addresses or fds to listen on */
int varlink_server_listen_address(VarlinkServer *s, const char *address, mode_t mode);
int varlink_server_listen_fd(VarlinkServer *s, int fd);
int varlink_server_listen_auto(VarlinkServer *s);
int varlink_server_add_connection(VarlinkServer *s, int fd, Varlink **ret);
/* Bind callbacks */
@@ -151,6 +156,11 @@ int varlink_server_bind_method_many_internal(VarlinkServer *s, ...);
int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect connect);
int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect disconnect);
/* Add interface definition */
int varlink_server_add_interface(VarlinkServer *s, const VarlinkInterface *interface);
int varlink_server_add_interface_many_internal(VarlinkServer *s, ...);
#define varlink_server_add_interface_many(s, ...) varlink_server_add_interface_many_internal(s, __VA_ARGS__, NULL)
void* varlink_server_set_userdata(VarlinkServer *s, void *userdata);
void* varlink_server_get_userdata(VarlinkServer *s);
@@ -158,8 +168,12 @@ int varlink_server_attach_event(VarlinkServer *v, sd_event *e, int64_t priority)
int varlink_server_detach_event(VarlinkServer *v);
sd_event *varlink_server_get_event(VarlinkServer *v);
int varlink_server_loop_auto(VarlinkServer *server);
int varlink_server_shutdown(VarlinkServer *server);
int varlink_server_set_exit_on_idle(VarlinkServer *s, bool b);
unsigned varlink_server_connections_max(VarlinkServer *s);
unsigned varlink_server_connections_per_uid_max(VarlinkServer *s);
@@ -170,6 +184,15 @@ unsigned varlink_server_current_connections(VarlinkServer *s);
int varlink_server_set_description(VarlinkServer *s, const char *description);
typedef enum VarlinkInvocationFlags {
VARLINK_ALLOW_LISTEN = 1 << 0,
VARLINK_ALLOW_ACCEPT = 1 << 1,
_VARLINK_SERVER_INVOCATION_FLAGS_MAX = (1 << 2) - 1,
_VARLINK_SERVER_INVOCATION_FLAGS_INVALID = -EINVAL,
} VarlinkInvocationFlags;
int varlink_invocation(VarlinkInvocationFlags flags);
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref);
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref);
DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref);
@@ -179,6 +202,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref);
#define VARLINK_ERROR_DISCONNECTED "io.systemd.Disconnected"
#define VARLINK_ERROR_TIMEOUT "io.systemd.TimedOut"
#define VARLINK_ERROR_PROTOCOL "io.systemd.Protocol"
/* This one we invented, and use for generically propagating system errors (errno) to clients */
#define VARLINK_ERROR_SYSTEM "io.systemd.System"
/* These are errors defined in the Varlink spec */
@@ -188,5 +213,4 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref);
#define VARLINK_ERROR_INVALID_PARAMETER "org.varlink.service.InvalidParameter"
/* These are errors we came up with and squatted the namespace with */
#define VARLINK_ERROR_SUBSCRIPTION_TAKEN "org.varlink.service.SubscriptionTaken"
#define VARLINK_ERROR_PERMISSION_DENIED "org.varlink.service.PermissionDenied"
+4
View File
@@ -417,6 +417,10 @@ executables += [
'sources' : files('test-varlink.c'),
'dependencies' : threads,
},
test_template + {
'sources' : files('test-varlink-idl.c'),
'dependencies' : threads,
},
test_template + {
'sources' : files('test-watchdog.c'),
'type' : 'unsafe',
+344
View File
@@ -0,0 +1,344 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <pthread.h>
#include "fd-util.h"
#include "pretty-print.h"
#include "tests.h"
#include "varlink.h"
#include "varlink-idl.h"
#include "varlink-io.systemd.h"
#include "varlink-io.systemd.Journal.h"
#include "varlink-io.systemd.ManagedOOM.h"
#include "varlink-io.systemd.PCRExtend.h"
#include "varlink-io.systemd.Resolve.Monitor.h"
#include "varlink-io.systemd.Resolve.h"
#include "varlink-io.systemd.UserDatabase.h"
#include "varlink-io.systemd.oom.h"
#include "varlink-org.varlink.service.h"
static VARLINK_DEFINE_ENUM_TYPE(
EnumTest,
VARLINK_DEFINE_ENUM_VALUE(foo),
VARLINK_DEFINE_ENUM_VALUE(bar),
VARLINK_DEFINE_ENUM_VALUE(baz));
static VARLINK_DEFINE_STRUCT_TYPE(
NestedStructTest,
VARLINK_DEFINE_FIELD(x, VARLINK_INT, 0));
static VARLINK_DEFINE_STRUCT_TYPE(
StructTest,
VARLINK_DEFINE_FIELD(bbb, VARLINK_BOOL, 0),
VARLINK_DEFINE_FIELD(bbbn, VARLINK_BOOL, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(bbba, VARLINK_BOOL, VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(bbbna, VARLINK_BOOL, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(bbbm, VARLINK_BOOL, VARLINK_MAP),
VARLINK_DEFINE_FIELD(bbbnm, VARLINK_BOOL, VARLINK_NULLABLE|VARLINK_MAP),
VARLINK_DEFINE_FIELD(iii, VARLINK_INT, 0),
VARLINK_DEFINE_FIELD(iiin, VARLINK_INT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(iiia, VARLINK_INT, VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(iiina, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(iiim, VARLINK_INT, VARLINK_MAP),
VARLINK_DEFINE_FIELD(iiinm, VARLINK_INT, VARLINK_NULLABLE|VARLINK_MAP),
VARLINK_DEFINE_FIELD(fff, VARLINK_FLOAT, 0),
VARLINK_DEFINE_FIELD(fffn, VARLINK_FLOAT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(fffa, VARLINK_FLOAT, VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(fffna, VARLINK_FLOAT, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(fffm, VARLINK_FLOAT, VARLINK_MAP),
VARLINK_DEFINE_FIELD(fffnm, VARLINK_FLOAT, VARLINK_NULLABLE|VARLINK_MAP),
VARLINK_DEFINE_FIELD(sss, VARLINK_STRING, 0),
VARLINK_DEFINE_FIELD(sssn, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(sssa, VARLINK_STRING, VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(sssna, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(sssm, VARLINK_STRING, VARLINK_MAP),
VARLINK_DEFINE_FIELD(sssnm, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_MAP),
VARLINK_DEFINE_FIELD(ooo, VARLINK_OBJECT, 0),
VARLINK_DEFINE_FIELD(ooon, VARLINK_OBJECT, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD(oooa, VARLINK_OBJECT, VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(ooona, VARLINK_OBJECT, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD(ooom, VARLINK_OBJECT, VARLINK_MAP),
VARLINK_DEFINE_FIELD(ooonm, VARLINK_OBJECT, VARLINK_NULLABLE|VARLINK_MAP),
VARLINK_DEFINE_FIELD_BY_TYPE(eee, EnumTest, 0),
VARLINK_DEFINE_FIELD_BY_TYPE(eeen, EnumTest, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD_BY_TYPE(eeea, EnumTest, VARLINK_ARRAY),
VARLINK_DEFINE_FIELD_BY_TYPE(eeena, EnumTest, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD_BY_TYPE(eeem, EnumTest, VARLINK_MAP),
VARLINK_DEFINE_FIELD_BY_TYPE(eeenm, EnumTest, VARLINK_NULLABLE|VARLINK_MAP),
VARLINK_DEFINE_FIELD_BY_TYPE(nnn, NestedStructTest, 0),
VARLINK_DEFINE_FIELD_BY_TYPE(nnnn, NestedStructTest, VARLINK_NULLABLE),
VARLINK_DEFINE_FIELD_BY_TYPE(nnna, NestedStructTest, VARLINK_ARRAY),
VARLINK_DEFINE_FIELD_BY_TYPE(nnnna, NestedStructTest, VARLINK_NULLABLE|VARLINK_ARRAY),
VARLINK_DEFINE_FIELD_BY_TYPE(nnnm, NestedStructTest, VARLINK_MAP),
VARLINK_DEFINE_FIELD_BY_TYPE(nnnnm, NestedStructTest, VARLINK_NULLABLE|VARLINK_MAP));
static VARLINK_DEFINE_METHOD(
MethodTest,
VARLINK_DEFINE_INPUT(x, VARLINK_BOOL, 0),
VARLINK_DEFINE_INPUT_BY_TYPE(y, EnumTest, 0),
VARLINK_DEFINE_INPUT_BY_TYPE(z, StructTest, 0),
VARLINK_DEFINE_OUTPUT(x, VARLINK_BOOL, 0),
VARLINK_DEFINE_OUTPUT_BY_TYPE(y, EnumTest, 0),
VARLINK_DEFINE_OUTPUT_BY_TYPE(z, StructTest, 0));
static VARLINK_DEFINE_ERROR(
ErrorTest,
VARLINK_DEFINE_FIELD(x, VARLINK_BOOL, 0),
VARLINK_DEFINE_FIELD_BY_TYPE(y, EnumTest, 0),
VARLINK_DEFINE_FIELD_BY_TYPE(z, StructTest, 0));
static VARLINK_DEFINE_INTERFACE(
xyz_test,
"xyz.test",
&vl_type_EnumTest,
&vl_type_NestedStructTest,
&vl_type_StructTest,
&vl_method_MethodTest,
&vl_error_ErrorTest);
static void test_parse_format_one(const VarlinkInterface *iface) {
_cleanup_(varlink_interface_freep) VarlinkInterface *parsed = NULL;
_cleanup_free_ char *text = NULL, *text2 = NULL;
assert_se(iface);
assert_se(varlink_idl_dump(stdout, /* use_colors=*/ true, iface) >= 0);
assert_se(varlink_idl_consistent(iface, LOG_ERR) >= 0);
assert_se(varlink_idl_format(iface, &text) >= 0);
assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0);
assert_se(varlink_idl_consistent(parsed, LOG_ERR) >= 0);
assert_se(varlink_idl_format(parsed, &text2) >= 0);
assert_se(streq(text, text2));
}
TEST(parse_format) {
test_parse_format_one(&vl_interface_org_varlink_service);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_UserDatabase);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Journal);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Resolve);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Resolve_Monitor);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_ManagedOOM);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_oom);
print_separator();
test_parse_format_one(&vl_interface_io_systemd);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_PCRExtend);
print_separator();
test_parse_format_one(&vl_interface_xyz_test);
}
TEST(parse) {
_cleanup_(varlink_interface_freep) VarlinkInterface *parsed = NULL;
/* This one has (nested) enonymous enums and structs */
static const char text[] =
"interface quu.waa\n"
"type Fooenum ( a, b, c )\n"
"type Barstruct ( a : (x, y, z), b : (x : int), c: (f, ff, fff), d: object, e : (sub : (subsub: (subsubsub: string, subsubsub2: (iii, ooo)))))"
;
assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0);
test_parse_format_one(parsed);
}
TEST(interface_name_is_valid) {
assert_se(!varlink_idl_interface_name_is_valid(NULL));
assert_se(!varlink_idl_interface_name_is_valid(""));
assert_se(!varlink_idl_interface_name_is_valid(","));
assert_se(!varlink_idl_interface_name_is_valid("."));
assert_se(!varlink_idl_interface_name_is_valid("-"));
assert_se(varlink_idl_interface_name_is_valid("a"));
assert_se(varlink_idl_interface_name_is_valid("a.a"));
assert_se(!varlink_idl_interface_name_is_valid("-.a"));
assert_se(!varlink_idl_interface_name_is_valid("-a.a"));
assert_se(!varlink_idl_interface_name_is_valid("a-.a"));
assert_se(varlink_idl_interface_name_is_valid("a-a.a"));
assert_se(!varlink_idl_interface_name_is_valid("a-a.a-"));
assert_se(!varlink_idl_interface_name_is_valid("a-a.-a"));
assert_se(!varlink_idl_interface_name_is_valid("a-a.-"));
assert_se(varlink_idl_interface_name_is_valid("a-a.a-a"));
assert_se(varlink_idl_interface_name_is_valid("io.systemd.Foobar"));
}
TEST(symbol_name_is_valid) {
assert_se(!varlink_idl_symbol_name_is_valid(NULL));
assert_se(!varlink_idl_symbol_name_is_valid(""));
assert_se(!varlink_idl_symbol_name_is_valid("_"));
assert_se(!varlink_idl_symbol_name_is_valid("_foo"));
assert_se(varlink_idl_symbol_name_is_valid("Foofoo"));
assert_se(varlink_idl_symbol_name_is_valid("Foo"));
assert_se(varlink_idl_symbol_name_is_valid("Foo0"));
assert_se(!varlink_idl_symbol_name_is_valid("0Foo"));
assert_se(!varlink_idl_symbol_name_is_valid("foo"));
assert_se(varlink_idl_symbol_name_is_valid("Foo0foo"));
assert_se(!varlink_idl_symbol_name_is_valid("bool"));
assert_se(!varlink_idl_symbol_name_is_valid("int"));
assert_se(!varlink_idl_symbol_name_is_valid("float"));
assert_se(!varlink_idl_symbol_name_is_valid("string"));
assert_se(!varlink_idl_symbol_name_is_valid("object"));
}
TEST(field_name_is_valid) {
assert_se(!varlink_idl_field_name_is_valid(NULL));
assert_se(!varlink_idl_field_name_is_valid(""));
assert_se(!varlink_idl_field_name_is_valid("_"));
assert_se(!varlink_idl_field_name_is_valid("_foo"));
assert_se(!varlink_idl_field_name_is_valid("_foo_"));
assert_se(!varlink_idl_field_name_is_valid("foo_"));
assert_se(varlink_idl_field_name_is_valid("foo_foo"));
assert_se(varlink_idl_field_name_is_valid("f_o_o_f_o_o"));
assert_se(!varlink_idl_field_name_is_valid("foo__foo"));
assert_se(varlink_idl_field_name_is_valid("Foofoo"));
assert_se(varlink_idl_field_name_is_valid("Foo"));
assert_se(varlink_idl_field_name_is_valid("Foo0"));
assert_se(!varlink_idl_field_name_is_valid("0Foo"));
assert_se(varlink_idl_field_name_is_valid("foo"));
assert_se(varlink_idl_field_name_is_valid("Foo0foo"));
assert_se(varlink_idl_field_name_is_valid("foo0foo"));
}
TEST(validate_json) {
_cleanup_(varlink_interface_freep) VarlinkInterface *parsed = NULL;
/* This one has (nested) enonymous enums and structs */
static const char text[] =
"interface validate.test\n"
"method Mymethod ( a:string, b:int, c:?bool, d:[]int, e:?[string]bool, f:?(piff, paff), g:(f:float) ) -> ()\n";
assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0);
test_parse_format_one(parsed);
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
assert_se(json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("a", JSON_BUILD_STRING("x")),
JSON_BUILD_PAIR("b", JSON_BUILD_UNSIGNED(44)),
JSON_BUILD_PAIR("d", JSON_BUILD_ARRAY(JSON_BUILD_UNSIGNED(5), JSON_BUILD_UNSIGNED(7), JSON_BUILD_UNSIGNED(107))),
JSON_BUILD_PAIR("g", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("f", JSON_BUILD_REAL(0.5f)))))) >= 0);
json_variant_dump(v, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, stdout, NULL);
const VarlinkSymbol* symbol = ASSERT_PTR(varlink_idl_find_symbol(parsed, VARLINK_METHOD, "Mymethod"));
assert_se(varlink_idl_validate_method_call(symbol, v, NULL) >= 0);
}
static int test_method(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
JsonVariant *foo = json_variant_by_key(parameters, "foo"), *bar = json_variant_by_key(parameters, "bar");
return varlink_replyb(link,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("waldo", json_variant_unsigned(foo) * json_variant_unsigned(bar)),
JSON_BUILD_PAIR_UNSIGNED("quux", json_variant_unsigned(foo) + json_variant_unsigned(bar))));
}
static int done_method(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
assert_se(sd_event_exit(varlink_get_event(link), 0) >= 0);
return 0;
}
static VARLINK_DEFINE_METHOD(
TestMethod,
VARLINK_DEFINE_INPUT(foo, VARLINK_INT, 0),
VARLINK_DEFINE_INPUT(bar, VARLINK_INT, 0),
VARLINK_DEFINE_INPUT(optional, VARLINK_STRING, VARLINK_NULLABLE),
VARLINK_DEFINE_OUTPUT(waldo, VARLINK_INT, 0),
VARLINK_DEFINE_OUTPUT(quux, VARLINK_INT, 0));
static VARLINK_DEFINE_METHOD(Done);
static VARLINK_DEFINE_INTERFACE(
xyz,
"xyz",
&vl_method_TestMethod,
&vl_method_Done);
static void* server_thread(void *userdata) {
_cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
assert_se(varlink_server_new(&server, 0) >= 0);
assert_se(varlink_server_add_interface(server, &vl_interface_xyz) >= 0);
assert_se(varlink_server_bind_method(server, "xyz.TestMethod", test_method) >= 0);
assert_se(varlink_server_bind_method(server, "xyz.Done", done_method) >= 0);
assert_se(sd_event_new(&event) >= 0);
assert_se(varlink_server_attach_event(server, event, 0) >= 0);
assert_se(varlink_server_add_connection(server, PTR_TO_FD(userdata), NULL) >= 0);
assert_se(sd_event_loop(event) >= 0);
return NULL;
}
TEST(validate_method_call) {
_cleanup_close_pair_ int fd[2] = PIPE_EBADF;
_cleanup_(varlink_unrefp) Varlink *v = NULL;
pthread_t t;
assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, fd) >= 0);
assert_se(pthread_create(&t, NULL, server_thread, FD_TO_PTR(TAKE_FD(fd[1]))) == 0);
assert_se(varlink_connect_fd(&v, TAKE_FD(fd[0])) >= 0);
JsonVariant *reply = NULL;
const char *error_id = NULL;
assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("foo", 8),
JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0);
_cleanup_(json_variant_unrefp) JsonVariant *expected_reply = NULL;
assert_se(json_build(&expected_reply,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("waldo", 8*9),
JSON_BUILD_PAIR_UNSIGNED("quux", 8+9))) >= 0);
assert_se(!error_id);
json_variant_dump(reply, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
json_variant_dump(expected_reply, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
assert_se(json_variant_equal(reply, expected_reply));
assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("foo", 9),
JSON_BUILD_PAIR_UNSIGNED("bar", 8),
JSON_BUILD_PAIR_STRING("optional", "pfft"))) >= 0);
assert_se(!error_id);
assert_se(json_variant_equal(reply, expected_reply));
assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_UNSIGNED("foo", 8),
JSON_BUILD_PAIR_UNSIGNED("bar", 9),
JSON_BUILD_PAIR_STRING("zzz", "pfft"))) >= 0);
assert_se(streq_ptr(error_id, VARLINK_ERROR_INVALID_PARAMETER));
assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_STRING("foo", "wuff"),
JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0);
assert_se(streq_ptr(error_id, VARLINK_ERROR_INVALID_PARAMETER));
assert_se(varlink_send(v, "xyz.Done", NULL) >= 0);
assert_se(varlink_flush(v) >= 0);
assert_se(pthread_join(t, NULL) == 0);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
+5
View File
@@ -18,6 +18,7 @@
#include "user-util.h"
#include "userdb.h"
#include "varlink.h"
#include "varlink-io.systemd.UserDatabase.h"
#define ITERATIONS_MAX 64U
#define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE)
@@ -483,6 +484,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to allocate server: %m");
r = varlink_server_add_interface(server, &vl_interface_io_systemd_UserDatabase);
if (r < 0)
return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m");
r = varlink_server_bind_method_many(
server,
"io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
+13
View File
@@ -0,0 +1,13 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
varlinkctl_sources = files(
'varlinkctl.c',
)
executables += [
executable_template + {
'name' : 'varlinkctl',
'public' : true,
'sources' : varlinkctl_sources,
},
]
+520
View File
@@ -0,0 +1,520 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include "build.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "main-func.h"
#include "pager.h"
#include "parse-argument.h"
#include "path-util.h"
#include "pretty-print.h"
#include "terminal-util.h"
#include "varlink.h"
#include "verbs.h"
#include "version.h"
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static VarlinkMethodFlags arg_method_flags = 0;
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("varlinkctl", "1", &link);
if (r < 0)
return log_oom();
pager_open(arg_pager_flags);
printf("%1$s [OPTIONS...] COMMAND ...\n\n"
"%5$sIntrospect Varlink Services.%6$s\n"
"\n%3$sCommands:%4$s\n"
" info ADDRESS Show service information\n"
" list-interfaces ADDRESS\n"
" List interfaces implemented by service\n"
" introspect ADDRESS INTERFACE\n"
" Show interface definition\n"
" call ADDRESS METHOD [PARAMS]\n"
" Invoke method\n"
" validate-idl [FILE] Validate interface description\n"
" help Show this help\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
" --more Request multiple responses\n"
" --oneway Do not request response\n"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ansi_underline(),
ansi_normal(),
ansi_highlight(),
ansi_normal());
return 0;
}
static int verb_help(int argc, char **argv, void *userdata) {
return help();
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_MORE,
ARG_ONEWAY,
ARG_JSON,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "more", no_argument, NULL, ARG_MORE },
{ "oneway", no_argument, NULL, ARG_ONEWAY },
{ "json", required_argument, NULL, ARG_JSON },
{},
};
int c, r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hj", options, NULL)) >= 0)
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
case ARG_NO_PAGER:
arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_MORE:
arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_ONEWAY) | VARLINK_METHOD_MORE;
break;
case ARG_ONEWAY:
arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_MORE) | VARLINK_METHOD_ONEWAY;
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
case 'j':
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
/* If more than one reply is expected, imply JSON-SEQ output */
if (FLAGS_SET(arg_method_flags, VARLINK_METHOD_MORE))
arg_json_format_flags |= JSON_FORMAT_SEQ;
return 1;
}
static int varlink_connect_auto(Varlink **ret, const char *where) {
int r;
assert(ret);
assert(where);
if (STARTSWITH_SET(where, "/", "./")) { /* If the string starts with a slash or dot slash we use it as a file system path */
_cleanup_close_ int fd = -EBADF;
struct stat st;
fd = open(where, O_PATH|O_CLOEXEC);
if (fd < 0)
return log_error_errno(errno, "Failed to open '%s': %m", where);
if (fstat(fd, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", where);
/* Is this a socket in the fs? Then connect() to it. */
if (S_ISSOCK(st.st_mode)) {
r = varlink_connect_address(ret, FORMAT_PROC_FD_PATH(fd));
if (r < 0)
return log_error_errno(r, "Failed to connect to '%s': %m", where);
return 0;
}
/* Is this an executable binary? Then fork it off. */
if (S_ISREG(st.st_mode) && (st.st_mode & 0111)) {
r = varlink_connect_exec(ret, where, STRV_MAKE(where)); /* Ideally we'd use FORMAT_PROC_FD_PATH(fd) here too, but that breaks the #! logic */
if (r < 0)
return log_error_errno(r, "Failed to spawn '%s' process: %m", where);
return 0;
}
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unrecognized path '%s' is neither an AF_UNIX socket, nor an executable binary.", where);
}
/* Otherwise assume this is an URL */
r = varlink_connect_url(ret, where);
if (r < 0)
return log_error_errno(r, "Failed to connect to URL '%s': %m", where);
return 0;
}
typedef struct GetInfoData {
const char *vendor;
const char *product;
const char *version;
const char *url;
char **interfaces;
} GetInfoData;
static void get_info_data_done(GetInfoData *d) {
assert(d);
d->interfaces = strv_free(d->interfaces);
}
static int verb_info(int argc, char *argv[], void *userdata) {
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
const char *url;
int r;
assert(argc == 2);
url = argv[1];
r = varlink_connect_auto(&vl, url);
if (r < 0)
return r;
JsonVariant *reply = NULL;
const char *error = NULL;
r = varlink_call(vl, "org.varlink.service.GetInfo", NULL, &reply, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to issue GetInfo() call: %m");
if (error)
return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInfo() failed: %s", error);
pager_open(arg_pager_flags);
if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
static const struct JsonDispatch dispatch_table[] = {
{ "vendor", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(GetInfoData, vendor), JSON_MANDATORY },
{ "product", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(GetInfoData, product), JSON_MANDATORY },
{ "version", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(GetInfoData, version), JSON_MANDATORY },
{ "url", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(GetInfoData, url), JSON_MANDATORY },
{ "interfaces", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(GetInfoData, interfaces), JSON_MANDATORY },
{}
};
_cleanup_(get_info_data_done) GetInfoData data = {};
r = json_dispatch(reply, dispatch_table, NULL, JSON_LOG, &data);
if (r < 0)
return r;
strv_sort(data.interfaces);
if (streq_ptr(argv[0], "list-interfaces")) {
STRV_FOREACH(i, data.interfaces)
puts(*i);
} else {
_cleanup_(table_unrefp) Table *t = NULL;
t = table_new_vertical();
if (!t)
return log_oom();
r = table_add_many(
t,
TABLE_FIELD, "Vendor",
TABLE_STRING, data.vendor,
TABLE_FIELD, "Product",
TABLE_STRING, data.product,
TABLE_FIELD, "Version",
TABLE_STRING, data.version,
TABLE_FIELD, "URL",
TABLE_STRING, data.url,
TABLE_SET_URL, data.url,
TABLE_FIELD, "Interfaces",
TABLE_STRV, data.interfaces);
if (r < 0)
return table_log_add_error(r);
r = table_print(t, NULL);
if (r < 0)
return table_log_print_error(r);
}
} else {
JsonVariant *v;
v = streq_ptr(argv[0], "list-interfaces") ?
json_variant_by_key(reply, "interfaces") : reply;
json_variant_dump(v, arg_json_format_flags, stdout, NULL);
}
return 0;
}
typedef struct GetInterfaceDescriptionData {
const char *description;
} GetInterfaceDescriptionData;
static int verb_introspect(int argc, char *argv[], void *userdata) {
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
const char *url, *interface;
int r;
assert(argc == 3);
url = argv[1];
interface = argv[2];
r = varlink_connect_auto(&vl, url);
if (r < 0)
return r;
JsonVariant *reply = NULL;
const char *error = NULL;
r = varlink_callb(vl, "org.varlink.service.GetInterfaceDescription", &reply, &error, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface)));
if (r < 0)
return log_error_errno(r, "Failed to issue GetInterfaceDescription() call: %m");
if (error)
return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInterfaceDescription() failed: %s", error);
pager_open(arg_pager_flags);
if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
static const struct JsonDispatch dispatch_table[] = {
{ "description", JSON_VARIANT_STRING, json_dispatch_const_string, 0, JSON_MANDATORY },
{}
};
_cleanup_(varlink_interface_freep) VarlinkInterface *vi = NULL;
const char *description = NULL;
unsigned line = 0, column = 0;
r = json_dispatch(reply, dispatch_table, NULL, JSON_LOG, &description);
if (r < 0)
return r;
/* Try to parse the returned description, so that we can add syntax highlighting */
r = varlink_idl_parse(ASSERT_PTR(description), &line, &column, &vi);
if (r < 0) {
log_warning_errno(r, "Failed to parse returned interface description at %u:%u, showing raw interface description: %m", line, column);
fputs(description, stdout);
if (!endswith(description, "\n"))
fputs("\n", stdout);
} else {
r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi);
if (r < 0)
return log_error_errno(r, "Failed to format parsed interface description: %m");
}
} else
json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
return 0;
}
static int reply_callback(
Varlink *link,
JsonVariant *parameters,
const char *error,
VarlinkReplyFlags flags,
void *userdata) {
int r;
assert(link);
if (error) {
/* Propagate the error we received via sd_notify() */
(void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);
r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error);
} else
r = 0;
json_variant_dump(parameters, arg_json_format_flags, stdout, NULL);
return r;
}
static int verb_call(int argc, char *argv[], void *userdata) {
_cleanup_(json_variant_unrefp) JsonVariant *jp = NULL;
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
const char *url, *method, *parameter;
unsigned line = 0, column = 0;
int r;
assert(argc >= 3);
assert(argc <= 4);
url = argv[1];
method = argv[2];
parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
arg_json_format_flags &= ~JSON_FORMAT_OFF;
if (parameter) {
r = json_parse_with_source(parameter, "<argv[4]>", 0, &jp, &line, &column);
if (r < 0)
return log_error_errno(r, "Failed to parse parameters at <argv[4]>:%u:%u: %m", line, column);
} else {
r = json_parse_file_at(stdin, AT_FDCWD, "<stdin>", 0, &jp, &line, &column);
if (r < 0)
return log_error_errno(r, "Failed to parse parameters at <stdin>:%u:%u: %m", line, column);
}
r = varlink_connect_auto(&vl, url);
if (r < 0)
return r;
if (arg_method_flags & VARLINK_METHOD_ONEWAY) {
r = varlink_send(vl, method, jp);
if (r < 0)
return log_error_errno(r, "Failed to issue %s() call: %m", method);
r = varlink_flush(vl);
if (r < 0)
return log_error_errno(r, "Failed to flush Varlink connection: %m");
} else if (arg_method_flags & VARLINK_METHOD_MORE) {
varlink_set_userdata(vl, (void*) method);
r = varlink_bind_reply(vl, reply_callback);
if (r < 0)
return log_error_errno(r, "Failed to bind reply callback: %m");
r = varlink_observe(vl, method, jp);
if (r < 0)
return log_error_errno(r, "Failed to issue %s() call: %m", method);
for (;;) {
r = varlink_is_idle(vl);
if (r < 0)
return log_error_errno(r, "Failed to check if varlink connection is idle: %m");
if (r > 0)
break;
r = varlink_process(vl);
if (r < 0)
return log_error_errno(r, "Failed to process varlink connection: %m");
if (r != 0)
continue;
r = varlink_wait(vl, USEC_INFINITY);
if (r < 0)
return log_error_errno(r, "Failed to wait for varlink connection events: %m");
}
} else {
JsonVariant *reply = NULL;
const char *error = NULL;
r = varlink_call(vl, method, jp, &reply, &error, NULL);
if (r < 0)
return log_error_errno(r, "Failed to issue %s() call: %m", method);
/* If the server returned an error to us, then fail, but first output the associated parameters */
if (error) {
/* Propagate the error we received via sd_notify() */
(void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error);
r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error);
} else
r = 0;
pager_open(arg_pager_flags);
json_variant_dump(reply, arg_json_format_flags, stdout, NULL);
return r;
}
return 0;
}
static int verb_validate_idl(int argc, char *argv[], void *userdata) {
_cleanup_(varlink_interface_freep) VarlinkInterface *vi = NULL;
_cleanup_free_ char *text = NULL;
const char *fname;
unsigned line = 1, column = 1;
int r;
fname = argc > 1 ? argv[1] : NULL;
if (fname) {
r = read_full_file(fname, &text, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read interface description file '%s': %m", fname);
} else {
r = read_full_stream(stdin, &text, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read interface description from stdin: %m");
fname = "<stdin>";
}
r = varlink_idl_parse(text, &line, &column, &vi);
if (r < 0)
return log_error_errno(r, "%s:%u:%u: Failed to parse interface description: %m", fname, line, column);
r = varlink_idl_consistent(vi, LOG_ERR);
if (r < 0)
return r;
pager_open(arg_pager_flags);
r = varlink_idl_dump(stdout, /* use_colors= */ -1, vi);
if (r < 0)
return log_error_errno(r, "Failed to format parsed interface description: %m");
return 0;
}
static int varlinkctl_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "info", 2, 2, 0, verb_info },
{ "list-interfaces", 2, 2, 0, verb_info },
{ "introspect", 3, 3, 0, verb_introspect },
{ "call", 3, 4, 0, verb_call },
{ "validate-idl", 1, 2, 0, verb_validate_idl },
{ "help", VERB_ANY, VERB_ANY, 0, verb_help },
{}
};
return dispatch_verb(argc, argv, verbs, NULL);
}
static int run(int argc, char *argv[]) {
int r;
log_setup();
r = parse_argv(argc, argv);
if (r <= 0)
return r;
return varlinkctl_main(argc, argv);
}
DEFINE_MAIN_FUNCTION(run);
+6 -1
View File
@@ -10,8 +10,12 @@ for ((i = 0; i < ITERATIONS; i++)); do
dd if=/dev/urandom bs=1M count=1 | base64 | systemd-cat
done
journalctl --rotate
# Let's test varlinkctl a bit, i.e. implement the equivalent of 'journalctl --rotate' via varlinkctl
varlinkctl call /run/systemd/journal/io.systemd.journal io.systemd.Journal.Rotate '{}'
journalctl --flush
varlinkctl call /run/systemd/journal/io.systemd.journal io.systemd.Journal.FlushToVar '{}'
journalctl --sync
varlinkctl call /run/systemd/journal/io.systemd.journal io.systemd.Journal.Synchronize '{}'
journalctl --rotate --vacuum-size=8M
# Reset the ratelimit buckets for the subsequent tests below.
@@ -42,7 +46,8 @@ write_and_match "<5> \t Leading spaces\n" " \t Leading spaces\n" --level-prefix
# --output-fields restricts output
ID="$(systemd-id128 new)"
echo -ne "foo" | systemd-cat -t "$ID" --level-prefix false
journalctl --sync
# Let's test varlinkctl a bit, i.e. implement the equivalent of 'journalctl --sync' via varlinkctl
varlinkctl call /run/systemd/journal/io.systemd.journal io.systemd.Journal.Synchronize '{}'
journalctl -b -o export --output-fields=MESSAGE,FOO --output-fields=PRIORITY,MESSAGE -t "$ID" >/tmp/output
[[ $(wc -l </tmp/output) -eq 9 ]]
grep -q '^__CURSOR=' /tmp/output
+8
View File
@@ -285,6 +285,14 @@ userdbctl groups-of-user 🐱
userdbctl services
userdbctl services -j | jq
varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"testuser","service":"io.systemd.Multiplexer"}'
varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"root","service":"io.systemd.Multiplexer"}'
varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"dropinuser","service":"io.systemd.Multiplexer"}'
varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"uid":2000000,"service":"io.systemd.Multiplexer"}'
(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"","service":"io.systemd.Multiplexer"}')
(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"🐱","service":"io.systemd.Multiplexer"}')
(! varlinkctl call /run/systemd/userdb/io.systemd.Multiplexer io.systemd.UserDatabase.GetUserRecord '{"userName":"i-do-not-exist","service":"io.systemd.Multiplexer"}')
userdbctl ssh-authorized-keys dropinuser | tee /tmp/authorized-keys
grep "ssh-ed25519" /tmp/authorized-keys
grep "ecdsa-sha2-nistp256" /tmp/authorized-keys
+10 -1
View File
@@ -270,7 +270,16 @@ if [[ -x "$SD_PCREXTEND" ]] && tpm_has_pcr sha256 11 && tpm_has_pcr sha256 15; t
# And similar for the boot phase measurement into PCR 11
tpm2_pcrread sha256:11 -Q -o /tmp/oldpcr11
SYSTEMD_FORCE_MEASURE=1 "$SD_PCREXTEND" foobar
# Do the equivalent of 'SYSTEMD_FORCE_MEASURE=1 "$SD_PCREXTEND" foobar' via Varlink, just to test the Varlink logic (but first we need to patch out the conditionalization...)
mkdir -p /run/systemd/system/systemd-pcrextend.socket.d
cat > /run/systemd/system/systemd-pcrextend.socket.d/50-no-condition.conf <<EOF
[Unit]
# Turn off all conditions */
ConditionSecurity=
EOF
systemctl daemon-reload
systemctl restart systemd-pcrextend.socket
varlinkctl call /run/systemd/io.systemd.PCRExtend io.systemd.PCRExtend.Extend '{"pcr":11,"text":"foobar"}'
tpm2_pcrread sha256:11 -Q -o /tmp/newpcr11
diff /tmp/newpcr11 \
+9
View File
@@ -430,6 +430,15 @@ units = [
'file' : 'systemd-oomd.socket',
'conditions' : ['ENABLE_OOMD'],
},
{
'file' : 'systemd-pcrextend@.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
},
{
'file' : 'systemd-pcrextend.socket',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sockets.target.wants/'],
},
{
'file' : 'systemd-pcrfs-root.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
+24
View File
@@ -0,0 +1,24 @@
# 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=TPM2 PCR Extension (Varlink)
Documentation=man:systemd-pcrextend(8)
DefaultDependencies=no
Before=sockets.target
ConditionSecurity=measured-uki
[Socket]
ListenStream=/run/systemd/io.systemd.PCRExtend
FileDescriptorName=varlink
SocketMode=0600
Accept=yes
[Install]
WantedBy=sockets.target
+19
View File
@@ -0,0 +1,19 @@
# 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=TPM2 PCR Extension (Varlink)
Documentation=man:systemd-pcrphase.service(8)
DefaultDependencies=no
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target
[Service]
Environment=LISTEN_FDNAMES=varlink
ExecStart=-{{LIBEXECDIR}}/systemd-pcrextend