diff --git a/TODO b/TODO
index 8111703d16..fde2013c83 100644
--- a/TODO
+++ b/TODO
@@ -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
diff --git a/man/busctl.xml b/man/busctl.xml
index ce23dd1b33..cc404529ae 100644
--- a/man/busctl.xml
+++ b/man/busctl.xml
@@ -582,6 +582,7 @@ o "/org/freedesktop/systemd1/job/42684"
dbus-daemon1,
D-Bus,
sd-bus3,
+ varlinkctl1,
systemd1,
machinectl1,
wireshark1
diff --git a/man/rules/meson.build b/man/rules/meson.build
index 3454668cab..3265db9c2d 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -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']
]
diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml
new file mode 100644
index 0000000000..5d0ec6ace7
--- /dev/null
+++ b/man/varlinkctl.xml
@@ -0,0 +1,315 @@
+
+
+
+
+
+
+
+ varlinkctl
+ systemd
+
+
+
+ varlinkctl
+ 1
+
+
+
+ varlinkctl
+ Introspect with and invoke Varlink services
+
+
+
+
+ varlinkctl
+ OPTIONS
+ info
+ ADDRESS
+
+
+
+ varlinkctl
+ OPTIONS
+ list-interfaces
+ ADDRESS
+
+
+
+ varlinkctl
+ OPTIONS
+ introspect
+ ADDRESS
+ INTERFACE
+
+
+
+ varlinkctl
+ OPTIONS
+ call
+ ADDRESS
+ METHOD
+ PARAMETERS
+
+
+
+ varlinkctl
+ OPTIONS
+ validate-idl
+ FILE
+
+
+
+
+ Description
+
+ varlinkctl may be used to introspect and invoke Varlink services.
+
+ Services are referenced by one of the following:
+
+
+ A Varlink service reference starting with the unix: string, followed
+ by an absolute AF_UNIX path, or by @ and an arbitrary string
+ (the latter for referencing sockets in the abstract namespace).
+
+ A Varlink service reference starting with the exec: string, followed
+ by an absolute path of a binary to execute.
+
+
+ For convenience these two simpler (redundant) service address syntaxes are also supported:
+
+
+ A file system path to an AF_UNIX socket, either absolute
+ (i.e. begins with /) or relative (in which case it must begin with
+ ./).
+
+ A file system path to an executable, either absolute or relative (as above, must begin
+ with /, resp. ./).
+
+
+
+
+ Commands
+
+ The following commands are understood:
+
+
+
+ info ADDRESS
+
+ Show brief information about the specified service, including vendor name and list of
+ implemented interfaces. Expects a service address in the formats described above.
+
+
+
+
+
+ list-interfaces ADDRESS
+
+ Show list of interfaces implemented by the specified service. Expects a service
+ address in the formats described above.
+
+
+
+
+
+ introspect ADDRESS INTERFACE
+
+ 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.
+
+
+
+
+
+ call ADDRESS METHOD [ARGUMENTS]
+
+ 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 {}.
+
+ The reply parameters are written as JSON object to STDOUT.
+
+
+
+
+
+ validate-idl [FILE]
+
+ 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.
+
+
+
+
+
+ help
+
+ Show command syntax help.
+
+
+
+
+
+
+
+ Options
+
+ The following options are understood:
+
+
+
+
+
+ When used with call: expect multiple method replies. If this flag is
+ set the method call is sent with the more 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.
+
+ If this mode is enabled output is automatically switched to JSON-SEQ mode, so that individual
+ reply objects can be easily discerned.
+
+
+
+
+
+
+
+ When used with call: do not expect a method reply. If this flag
+ is set the method call is sent with the oneway flag set (the command exits
+ immediately after), which tells the service not to generate a reply.
+
+
+
+
+
+ MODE
+
+
+ Selects the JSON output formatting, one of pretty (for nicely indented,
+ colorized output) or short (for terse output with minimal whitespace and no
+ newlines), defaults to short.
+
+
+
+
+
+
+
+
+
+ Equivalent to when invoked interactively from a terminal. Otherwise
+ equivalent to , in particular when the output is piped to some other
+ program.
+
+
+
+
+
+
+
+
+
+
+
+
+ Examples
+
+
+ Investigating a Service
+
+ The following three commands inspect the io.systemd.Resolve service
+ implemented by
+ systemd-resolved.service8,
+ listing general service information and implemented interfaces, and then displaying the interface
+ definition of its primary interface:
+
+ $ 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,
+ …
+
+ (Interface definition has been truncated in the example above, in the interest of brevity.)
+
+
+
+ Invoking a Method
+
+ The following command resolves a hostname via systemd-resolved.service8's ResolveHostname method call.
+
+ $ 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
+}
+
+
+
+ Investigating a Service Executable
+
+ The following comand inspects the /usr/lib/systemd/systemd-pcrextend
+ executable and the IPC APIs it provides. It then invokes a method on it:
+
+ # 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"}'
+{}
+
+
+
+
+
+ See Also
+
+
+ busctl1,
+ Varlink
+
+
+
diff --git a/meson.build b/meson.build
index 88bc1367e0..5e9ca285ac 100644
--- a/meson.build
+++ b/meson.build
@@ -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')
diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h
index 156c973e69..3ef834acee 100644
--- a/src/basic/alloc-util.h
+++ b/src/basic/alloc-util.h
@@ -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"
diff --git a/src/core/core-varlink.c b/src/core/core-varlink.c
index 1ff4a93cce..71714e9473 100644
--- a/src/core/core-varlink.c
+++ b/src/core/core-varlink.c
@@ -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,
diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c
index 903207b50f..c4525310fc 100644
--- a/src/home/homed-manager.c
+++ b/src/home/homed-manager.c
@@ -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,
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index 615b3c6c6b..371338211a 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -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,
diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c
index ec625ad7b4..4b72ee9b0a 100644
--- a/src/machine/machined-varlink.c
+++ b/src/machine/machined-varlink.c
@@ -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,
diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c
index 49896ea7f1..75918a5fe7 100644
--- a/src/oom/oomd-manager.c
+++ b/src/oom/oomd-manager.c
@@ -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");
diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c
index 378d1b0e8f..c44e78c4ca 100644
--- a/src/pcrextend/pcrextend.c
+++ b/src/pcrextend/pcrextend.c
@@ -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;
}
diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c
index 83b8e52807..c9bb616acb 100644
--- a/src/resolve/resolved-varlink.c
+++ b/src/resolve/resolved-varlink.c
@@ -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,
diff --git a/src/shared/meson.build b/src/shared/meson.build
index 3df178bfa1..569b865636 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -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',
diff --git a/src/shared/varlink-idl.c b/src/shared/varlink-idl.c
new file mode 100644
index 0000000000..157474facf
--- /dev/null
+++ b/src/shared/varlink-idl.c
@@ -0,0 +1,1582 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "memstream-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "varlink-idl.h"
+#include "set.h"
+
+enum {
+ COLOR_SYMBOL_TYPE, /* interface, method, type, error */
+ COLOR_FIELD_TYPE, /* string, bool, … */
+ COLOR_IDENTIFIER,
+ COLOR_MARKS, /* [], ->, ?, … */
+ COLOR_RESET,
+ _COLOR_MAX,
+};
+
+static int varlink_idl_format_all_fields(FILE *f, const VarlinkSymbol *symbol, VarlinkFieldDirection direction, const char *indent, const char *const colors[static _COLOR_MAX]);
+
+static int varlink_idl_format_enum_values(
+ FILE *f,
+ const VarlinkSymbol *symbol,
+ const char *indent,
+ const char *const colors[static _COLOR_MAX]) {
+
+ bool first = true;
+
+ assert(f);
+ assert(symbol);
+ assert(symbol->symbol_type == VARLINK_ENUM_TYPE);
+
+ for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+
+ if (first) {
+ first = false;
+ fputs("(\n", f);
+ } else
+ fputs(",\n", f);
+
+ fputs(strempty(indent), f);
+ fputs("\t", f);
+ fputs(colors[COLOR_IDENTIFIER], f);
+ fputs(field->name, f);
+ fputs(colors[COLOR_RESET], f);
+ }
+
+ if (first)
+ fputs("()", f);
+ else {
+ fputs("\n", f);
+ fputs(strempty(indent), f);
+ fputs(")", f);
+ }
+
+ return 0;
+}
+
+static int varlink_idl_format_field(
+ FILE *f,
+ const VarlinkField *field,
+ const char *indent,
+ const char *const colors[static _COLOR_MAX]) {
+
+ assert(f);
+ assert(field);
+
+ fputs(strempty(indent), f);
+ fputs(colors[COLOR_IDENTIFIER], f);
+ fputs(field->name, f);
+ fputs(colors[COLOR_RESET], f);
+ fputs(": ", f);
+
+ if (FLAGS_SET(field->field_flags, VARLINK_NULLABLE)) {
+ fputs(colors[COLOR_MARKS], f);
+ fputs("?", f);
+ fputs(colors[COLOR_RESET], f);
+ }
+
+ switch (field->field_flags & (VARLINK_MAP|VARLINK_ARRAY)) {
+
+ case VARLINK_MAP:
+ fputs(colors[COLOR_MARKS], f);
+ fputs("[", f);
+ fputs(colors[COLOR_FIELD_TYPE], f);
+ fputs("string", f);
+ fputs(colors[COLOR_MARKS], f);
+ fputs("]", f);
+ fputs(colors[COLOR_RESET], f);
+ break;
+
+ case VARLINK_ARRAY:
+ fputs(colors[COLOR_MARKS], f);
+ fputs("[]", f);
+ fputs(colors[COLOR_RESET], f);
+ break;
+
+ case 0:
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ switch (field->field_type) {
+
+ case VARLINK_BOOL:
+ fputs(colors[COLOR_FIELD_TYPE], f);
+ fputs("bool", f);
+ fputs(colors[COLOR_RESET], f);
+ break;
+
+ case VARLINK_INT:
+ fputs(colors[COLOR_FIELD_TYPE], f);
+ fputs("int", f);
+ fputs(colors[COLOR_RESET], f);
+ break;
+
+ case VARLINK_FLOAT:
+ fputs(colors[COLOR_FIELD_TYPE], f);
+ fputs("float", f);
+ fputs(colors[COLOR_RESET], f);
+ break;
+
+ case VARLINK_STRING:
+ fputs(colors[COLOR_FIELD_TYPE], f);
+ fputs("string", f);
+ fputs(colors[COLOR_RESET], f);
+ break;
+
+ case VARLINK_OBJECT:
+ fputs(colors[COLOR_FIELD_TYPE], f);
+ fputs("object", f);
+ fputs(colors[COLOR_RESET], f);
+ break;
+
+ case VARLINK_NAMED_TYPE:
+ fputs(colors[COLOR_IDENTIFIER], f);
+ fputs(ASSERT_PTR(field->named_type), f);
+ fputs(colors[COLOR_RESET], f);
+ break;
+
+ case VARLINK_STRUCT:
+ return varlink_idl_format_all_fields(f, ASSERT_PTR(field->symbol), VARLINK_REGULAR, indent, colors);
+
+ case VARLINK_ENUM:
+ return varlink_idl_format_enum_values(f, ASSERT_PTR(field->symbol), indent, colors);
+
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+}
+
+static int varlink_idl_format_all_fields(
+ FILE *f,
+ const VarlinkSymbol *symbol,
+ VarlinkFieldDirection filter_direction,
+ const char *indent,
+ const char *const colors[static _COLOR_MAX]) {
+
+ _cleanup_free_ char *indent2 = NULL;
+ bool first = true;
+ int r;
+
+ assert(f);
+ assert(symbol);
+ assert(IN_SET(symbol->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_METHOD, VARLINK_ERROR));
+
+ indent2 = strjoin(strempty(indent), "\t");
+ if (!indent2)
+ return -ENOMEM;
+
+ for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+
+ if (field->field_direction != filter_direction)
+ continue;
+
+ if (first) {
+ first = false;
+ fputs("(\n", f);
+ } else
+ fputs(",\n", f);
+
+ r = varlink_idl_format_field(f, field, indent2, colors);
+ if (r < 0)
+ return r;
+ }
+
+ if (first)
+ fputs("()", f);
+ else {
+ fputs("\n", f);
+ fputs(strempty(indent), f);
+ fputs(")", f);
+ }
+
+ return 0;
+}
+
+static int varlink_idl_format_symbol(
+ FILE *f,
+ const VarlinkSymbol *symbol,
+ const char *const colors[static _COLOR_MAX]) {
+ int r;
+
+ assert(f);
+ assert(symbol);
+
+ switch (symbol->symbol_type) {
+
+ case VARLINK_ENUM_TYPE:
+ fputs(colors[COLOR_SYMBOL_TYPE], f);
+ fputs("type ", f);
+ fputs(colors[COLOR_IDENTIFIER], f);
+ fputs(symbol->name, f);
+ fputs(colors[COLOR_RESET], f);
+
+ r = varlink_idl_format_enum_values(f, symbol, /* indent= */ NULL, colors);
+ break;
+
+ case VARLINK_STRUCT_TYPE:
+ fputs(colors[COLOR_SYMBOL_TYPE], f);
+ fputs("type ", f);
+ fputs(colors[COLOR_IDENTIFIER], f);
+ fputs(symbol->name, f);
+ fputs(colors[COLOR_RESET], f);
+
+ r = varlink_idl_format_all_fields(f, symbol, VARLINK_REGULAR, /* indent= */ NULL, colors);
+ break;
+
+ case VARLINK_METHOD:
+ fputs(colors[COLOR_SYMBOL_TYPE], f);
+ fputs("method ", f);
+ fputs(colors[COLOR_IDENTIFIER], f);
+ fputs(symbol->name, f);
+ fputs(colors[COLOR_RESET], f);
+
+ r = varlink_idl_format_all_fields(f, symbol, VARLINK_INPUT, /* indent= */ NULL, colors);
+ if (r < 0)
+ return r;
+
+ fputs(colors[COLOR_MARKS], f);
+ fputs(" -> ", f);
+ fputs(colors[COLOR_RESET], f);
+
+ r = varlink_idl_format_all_fields(f, symbol, VARLINK_OUTPUT, /* indent= */ NULL, colors);
+ break;
+
+ case VARLINK_ERROR:
+ fputs(colors[COLOR_SYMBOL_TYPE], f);
+ fputs("error ", f);
+ fputs(colors[COLOR_IDENTIFIER], f);
+ fputs(symbol->name, f);
+ fputs(colors[COLOR_RESET], f);
+
+ r = varlink_idl_format_all_fields(f, symbol, VARLINK_REGULAR, /* indent= */ NULL, colors);
+ break;
+
+ default:
+ assert_not_reached();
+ }
+ if (r < 0)
+ return r;
+
+ fputs("\n", f);
+ return 0;
+}
+
+static int varlink_idl_format_all_symbols(
+ FILE *f,
+ const VarlinkInterface *interface,
+ VarlinkSymbolType filter_type,
+ const char *const colors[static _COLOR_MAX]) {
+
+ int r;
+
+ assert(f);
+ assert(interface);
+
+ for (const VarlinkSymbol *const*symbol = interface->symbols; *symbol; symbol++) {
+
+ if ((*symbol)->symbol_type != filter_type)
+ continue;
+
+ fputs("\n", f);
+
+ r = varlink_idl_format_symbol(f, *symbol, colors);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int varlink_idl_dump(FILE *f, int use_colors, const VarlinkInterface *interface) {
+ static const char* const color_table[_COLOR_MAX] = {
+ [COLOR_SYMBOL_TYPE] = ANSI_HIGHLIGHT_GREEN,
+ [COLOR_FIELD_TYPE] = ANSI_HIGHLIGHT_BLUE,
+ [COLOR_IDENTIFIER] = ANSI_NORMAL,
+ [COLOR_MARKS] = ANSI_HIGHLIGHT_MAGENTA,
+ [COLOR_RESET] = ANSI_NORMAL,
+ };
+
+ static const char* const color_off[_COLOR_MAX] = {
+ "", "", "", "", "",
+ };
+
+ int r;
+
+ assert(interface);
+
+ if (!f)
+ f = stdout;
+
+ if (use_colors < 0)
+ use_colors = colors_enabled();
+
+ const char *const *colors = use_colors ? color_table : color_off;
+
+ fputs(colors[COLOR_SYMBOL_TYPE], f);
+ fputs("interface ", f);
+ fputs(colors[COLOR_IDENTIFIER], f);
+ fputs(ASSERT_PTR(interface->name), f);
+ fputs(colors[COLOR_RESET], f);
+ fputs("\n", f);
+
+ for (VarlinkSymbolType t = 0; t < _VARLINK_SYMBOL_TYPE_MAX; t++) {
+ r = varlink_idl_format_all_symbols(f, interface, t, colors);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int varlink_idl_format(const VarlinkInterface *interface, char **ret) {
+ _cleanup_(memstream_done) MemStream memstream = {};
+ int r;
+
+ if (!memstream_init(&memstream))
+ return -errno;
+
+ r = varlink_idl_dump(memstream.f, /* use_colors= */ false, interface);
+ if (r < 0)
+ return r;
+
+ return memstream_finalize(&memstream, ret, NULL);
+}
+
+static VarlinkSymbol *varlink_symbol_free(VarlinkSymbol *symbol) {
+ if (!symbol)
+ return NULL;
+
+ /* See comment in varlink_interface_free() regarding the casting away of `const` */
+
+ free((char*) symbol->name);
+
+ for (size_t i = 0; symbol->fields[i].field_type != _VARLINK_FIELD_TYPE_END_MARKER; i++) {
+ VarlinkField *field = symbol->fields + i;
+
+ free((void*) field->name);
+ free((void*) field->named_type);
+
+ /* The symbol pointer might either point to a named symbol, in which case that symbol is
+ * owned by the interface, or by an anomyous symbol, in which case it is owned by us, and we
+ * need to free it */
+ if (field->symbol && field->field_type != VARLINK_NAMED_TYPE)
+ varlink_symbol_free((VarlinkSymbol*) field->symbol);
+ }
+
+ return mfree(symbol);
+}
+
+VarlinkInterface* varlink_interface_free(VarlinkInterface *interface) {
+ if (!interface)
+ return NULL;
+
+ /* So here's the thing: in most cases we want that users of this define their interface descriptions
+ * in C code, and hence the definitions are constant and immutable during the life-cycle of the
+ * system. Because of that we define all structs with const* pointers. It makes it very nice and
+ * straight-forward to populate these structs with literal C strings. However, in some not so common
+ * cases we also want to allocate these structures dynamically on the heap, when parsing interface
+ * descriptions. But given this should be the exceptional and not the common case, we decided to
+ * simple cast away the 'const' where needed, even if it is ugly. */
+
+ free((char*) interface->name);
+
+ for (size_t i = 0; interface->symbols[i]; i++)
+ varlink_symbol_free((VarlinkSymbol*) interface->symbols[i]);
+
+ return mfree(interface);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkSymbol*, varlink_symbol_free);
+
+static int varlink_interface_realloc(VarlinkInterface **interface, size_t n_symbols) {
+ VarlinkInterface *n;
+
+ assert(interface);
+
+ n_symbols ++; /* Space for trailing NULL end marker symbol */
+
+ /* Overflow check */
+ if (n_symbols > (SIZE_MAX - offsetof(VarlinkInterface, symbols)) / sizeof(VarlinkSymbol*))
+ return -ENOMEM;
+
+ n = realloc0(*interface, offsetof(VarlinkInterface, symbols) + sizeof(VarlinkSymbol*) * n_symbols);
+ if (!n)
+ return -ENOMEM;
+
+ *interface = n;
+ return 0;
+}
+
+static int varlink_symbol_realloc(VarlinkSymbol **symbol, size_t n_fields) {
+ VarlinkSymbol *n;
+
+ assert(symbol);
+
+ n_fields ++; /* Space for trailing end marker field */
+
+ /* Overflow check */
+ if (n_fields > (SIZE_MAX - offsetof(VarlinkSymbol, fields)) / sizeof(VarlinkField))
+ return -ENOMEM;
+
+ n = realloc0(*symbol, offsetof(VarlinkSymbol, fields) + sizeof(VarlinkField) * n_fields);
+ if (!n)
+ return -ENOMEM;
+
+ *symbol = n;
+ return 0;
+}
+
+#define VALID_CHARS_IDENTIFIER ALPHANUMERICAL "_"
+#define VALID_CHARS_RESERVED LOWERCASE_LETTERS
+#define VALID_CHARS_INTERFACE_NAME ALPHANUMERICAL ".-"
+
+static void advance_line_column(const char *p, size_t n, unsigned *line, unsigned *column) {
+
+ assert(p);
+ assert(line);
+ assert(column);
+
+ for (; n > 0; p++, n--) {
+
+ if (*p == '\n') {
+ (*line)++;
+ *column = 1;
+ } else
+ (*column)++;
+ }
+}
+
+static size_t token_match(
+ const char *p,
+ const char *allowed_delimiters,
+ const char *allowed_chars) {
+
+ /* Checks if the string p begins either with one of the token characters in allowed_delimiters or
+ * with a string consisting of allowed_chars. */
+
+ assert(p);
+
+ if (allowed_delimiters && strchr(allowed_delimiters, *p))
+ return 1;
+
+ if (!allowed_chars)
+ return 0;
+
+ return strspn(p, allowed_chars);
+}
+
+static int varlink_idl_subparse_token(
+ const char **p,
+ unsigned *line,
+ unsigned *column,
+ const char *allowed_delimiters,
+ const char *allowed_chars,
+ char **ret_token) {
+
+ _cleanup_free_ char *t = NULL;
+ size_t l;
+
+ assert(p);
+ assert(*p);
+ assert(line);
+ assert(column);
+ assert(ret_token);
+
+ if (**p == '\0') { /* eof */
+ *ret_token = NULL;
+ return 0;
+ }
+
+ l = token_match(*p, allowed_delimiters, allowed_chars);
+
+ /* No token of the permitted character set found? Then let's try to skip over whitespace and try again */
+ if (l == 0) {
+ size_t ll;
+
+ ll = strspn(*p, WHITESPACE);
+ advance_line_column(*p, ll, line, column);
+ *p += ll;
+
+ if (**p == '\0') { /* eof */
+ *ret_token = NULL;
+ return 0;
+ }
+
+ l = token_match(*p, allowed_delimiters, allowed_chars);
+ if (l == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters));
+ }
+
+ t = strndup(*p, l);
+ if (!t)
+ return -ENOMEM;
+
+ advance_line_column(*p, l, line, column);
+ *p += l;
+
+ *ret_token = TAKE_PTR(t);
+ return 1;
+}
+
+static int varlink_idl_subparse_comment(
+ const char **p,
+ unsigned *line,
+ unsigned *column) {
+
+ size_t l;
+
+ assert(p);
+ assert(*p);
+ assert(line);
+ assert(column);
+
+ l = strcspn(*p, NEWLINE);
+ advance_line_column(*p, l + 1, line, column);
+ *p += l;
+
+ return 1;
+}
+
+static int varlink_idl_subparse_whitespace(
+ const char **p,
+ unsigned *line,
+ unsigned *column) {
+
+ size_t l;
+
+ assert(p);
+ assert(*p);
+ assert(line);
+ assert(column);
+
+ l = strspn(*p, WHITESPACE);
+ advance_line_column(*p, l, line, column);
+ *p += l;
+
+ return 1;
+}
+
+static int varlink_idl_subparse_struct_or_enum(const char **p, unsigned *line, unsigned *column, VarlinkSymbol **symbol, size_t *n_fields, VarlinkFieldDirection direction);
+
+static int varlink_idl_subparse_field_type(
+ const char **p,
+ unsigned *line,
+ unsigned *column,
+ VarlinkField *field) {
+
+ size_t l;
+ int r;
+
+ assert(p);
+ assert(*p);
+ assert(line);
+ assert(field);
+
+ r = varlink_idl_subparse_whitespace(p, line, column);
+ if (r < 0)
+ return r;
+
+ if (startswith(*p, "?")) {
+ field->field_flags |= VARLINK_NULLABLE;
+ l = 1;
+ } else {
+ field->field_flags &= ~VARLINK_NULLABLE;
+ l = 0;
+ }
+
+ advance_line_column(*p, l, line, column);
+ *p += l;
+
+ if (startswith(*p, "[]")) {
+ l = 2;
+ field->field_flags = (field->field_flags & ~VARLINK_MAP) | VARLINK_ARRAY;
+ } else if (startswith(*p, "[string]")) {
+ l = 8;
+ field->field_flags = (field->field_flags & ~VARLINK_ARRAY) | VARLINK_MAP;
+ } else {
+ l = 0;
+ field->field_flags = field->field_flags & ~(VARLINK_MAP | VARLINK_ARRAY);
+ }
+
+ advance_line_column(*p, l, line, column);
+ *p += l;
+
+ if (startswith(*p, "bool")) {
+ l = 4;
+ field->field_type = VARLINK_BOOL;
+ } else if (startswith(*p, "int")) {
+ l = 3;
+ field->field_type = VARLINK_INT;
+ } else if (startswith(*p, "float")) {
+ l = 5;
+ field->field_type = VARLINK_FLOAT;
+ } else if (startswith(*p, "string")) {
+ l = 6;
+ field->field_type = VARLINK_STRING;
+ } else if (startswith(*p, "object")) {
+ l = 6;
+ field->field_type = VARLINK_OBJECT;
+ } else if (**p == '(') {
+ _cleanup_(varlink_symbol_freep) VarlinkSymbol *symbol = NULL;
+ size_t n_fields = 0;
+
+ r = varlink_symbol_realloc(&symbol, n_fields);
+ if (r < 0)
+ return r;
+
+ symbol->symbol_type = _VARLINK_SYMBOL_TYPE_INVALID;
+
+ r = varlink_idl_subparse_struct_or_enum(
+ p,
+ line,
+ column,
+ &symbol,
+ &n_fields,
+ VARLINK_REGULAR);
+ if (r < 0)
+ return r;
+
+ if (symbol->symbol_type == VARLINK_STRUCT_TYPE)
+ field->field_type = VARLINK_STRUCT;
+ else {
+ assert(symbol->symbol_type == VARLINK_ENUM_TYPE);
+ field->field_type = VARLINK_ENUM;
+ }
+
+ field->symbol = TAKE_PTR(symbol);
+ l = 0;
+ } else {
+ _cleanup_free_ char *token = NULL;
+
+ r = varlink_idl_subparse_token(p, line, column, /* valid_tokens= */ NULL, VALID_CHARS_IDENTIFIER, &token);
+ if (r < 0)
+ return r;
+ if (!token)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF: %m", *line, *column);
+
+ field->named_type = TAKE_PTR(token);
+ field->field_type = VARLINK_NAMED_TYPE;
+ l = 0;
+ }
+
+ advance_line_column(*p, l, line, column);
+ *p += l;
+
+ return 0;
+}
+
+static int varlink_idl_subparse_struct_or_enum(
+ const char **p,
+ unsigned *line,
+ unsigned *column,
+ VarlinkSymbol **symbol,
+ size_t *n_fields,
+ VarlinkFieldDirection direction) {
+
+ enum {
+ STATE_OPEN,
+ STATE_NAME,
+ STATE_COLON,
+ STATE_COMMA,
+ STATE_DONE,
+ } state = STATE_OPEN;
+ _cleanup_free_ char *field_name = NULL;
+ const char *allowed_delimiters = "(", *allowed_chars = NULL;
+ int r;
+
+ assert(p);
+ assert(*p);
+ assert(line);
+ assert(column);
+ assert(symbol);
+ assert(*symbol);
+ assert(n_fields);
+
+ while (state != STATE_DONE) {
+ _cleanup_free_ char *token = NULL;
+
+ r = varlink_idl_subparse_token(
+ p,
+ line,
+ column,
+ allowed_delimiters,
+ allowed_chars,
+ &token);
+ if (r < 0)
+ return r;
+
+ switch (state) {
+
+ case STATE_OPEN:
+ if (!token)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF: %m", *line, *column);
+ if (!streq(token, "("))
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s': %m", *line, *column, token);
+
+ state = STATE_NAME;
+ allowed_delimiters = ")";
+ allowed_chars = VALID_CHARS_IDENTIFIER;
+ break;
+
+ case STATE_NAME:
+ assert(!field_name);
+
+ if (!token)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF: %m", *line, *column);
+ if (streq(token, "#")) {
+ r = varlink_idl_subparse_comment(p, line, column);
+ if (r < 0)
+ return r;
+ } else if (streq(token, ")"))
+ state = STATE_DONE;
+ else {
+ field_name = TAKE_PTR(token);
+ state = STATE_COLON;
+ allowed_delimiters = ":,)";
+ allowed_chars = NULL;
+ }
+
+ break;
+
+ case STATE_COLON:
+ assert(field_name);
+
+ if (!token)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF: %m", *line, *column);
+
+ if (streq(token, ":")) {
+ VarlinkField *field;
+
+ if ((*symbol)->symbol_type < 0)
+ (*symbol)->symbol_type = VARLINK_STRUCT_TYPE;
+ if ((*symbol)->symbol_type == VARLINK_ENUM_TYPE)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing: %m", *line, *column);
+
+ r = varlink_symbol_realloc(symbol, *n_fields + 1);
+ if (r < 0)
+ return r;
+
+ field = (*symbol)->fields + (*n_fields)++;
+ *field = (VarlinkField) {
+ .name = TAKE_PTR(field_name),
+ .field_type = _VARLINK_FIELD_TYPE_INVALID,
+ .field_direction = direction,
+ };
+
+ r = varlink_idl_subparse_field_type(p, line, column, field);
+ if (r < 0)
+ return r;
+
+ state = STATE_COMMA;
+ allowed_delimiters = ",)";
+ allowed_chars = NULL;
+
+ } else if (STR_IN_SET(token, ",", ")")) {
+ VarlinkField *field;
+
+ if ((*symbol)->symbol_type < 0)
+ (*symbol)->symbol_type = VARLINK_ENUM_TYPE;
+ if ((*symbol)->symbol_type != VARLINK_ENUM_TYPE)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing: %m", *line, *column);
+
+ r = varlink_symbol_realloc(symbol, *n_fields + 1);
+ if (r < 0)
+ return r;
+
+ field = (*symbol)->fields + (*n_fields)++;
+ *field = (VarlinkField) {
+ .name = TAKE_PTR(field_name),
+ .field_type = VARLINK_ENUM_VALUE,
+ };
+
+ if (streq(token, ",")) {
+ state = STATE_NAME;
+ allowed_delimiters = NULL;
+ allowed_chars = VALID_CHARS_IDENTIFIER;
+ } else {
+ assert(streq(token, ")"));
+ state = STATE_DONE;
+ }
+ } else
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s': %m", *line, *column, token);
+
+ break;
+
+ case STATE_COMMA:
+ assert(!field_name);
+
+ if (!token)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF: %m", *line, *column);
+ if (streq(token, ",")) {
+ state = STATE_NAME;
+ allowed_delimiters = NULL;
+ allowed_chars = VALID_CHARS_IDENTIFIER;
+ } else if (streq(token, ")"))
+ state = STATE_DONE;
+ else
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s': %m", *line, *column, token);
+ break;
+
+ default:
+ assert_not_reached();
+ }
+ }
+
+ return 0;
+}
+
+static int varlink_idl_resolve_symbol_types(VarlinkInterface *interface, VarlinkSymbol *symbol) {
+ assert(interface);
+ assert(symbol);
+
+ for (VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+ const VarlinkSymbol *found;
+
+ if (field->field_type != VARLINK_NAMED_TYPE)
+ continue;
+
+ if (field->symbol) /* Already resolved */
+ continue;
+
+ if (!field->named_type)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name.");
+
+ found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->named_type);
+ if (!found)
+ return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type);
+
+ if (!IN_SET(found->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type);
+
+ field->symbol = found;
+ }
+
+ return 0;
+}
+
+static int varlink_idl_resolve_types(VarlinkInterface *interface) {
+ int r;
+
+ assert(interface);
+
+ for (VarlinkSymbol **symbol = (VarlinkSymbol**) interface->symbols; *symbol; symbol++) {
+ r = varlink_idl_resolve_symbol_types(interface, *symbol);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int varlink_idl_parse(
+ const char *text,
+ unsigned *line,
+ unsigned *column,
+ VarlinkInterface **ret) {
+
+ _cleanup_(varlink_interface_freep) VarlinkInterface *interface = NULL;
+ _cleanup_(varlink_symbol_freep) VarlinkSymbol *symbol = NULL;
+ enum {
+ STATE_PRE_INTERFACE,
+ STATE_INTERFACE,
+ STATE_PRE_SYMBOL,
+ STATE_METHOD,
+ STATE_METHOD_NAME,
+ STATE_METHOD_ARROW,
+ STATE_TYPE,
+ STATE_TYPE_NAME,
+ STATE_ERROR,
+ STATE_ERROR_NAME,
+ STATE_DONE,
+ } state = STATE_PRE_INTERFACE;
+ const char *allowed_delimiters = "#", *allowed_chars = VALID_CHARS_RESERVED;
+ size_t n_symbols = 0, n_fields = 1;
+ unsigned _line = 0, _column = 1;
+ const char **p = &text;
+ int r;
+
+ if (!line)
+ line = &_line;
+ if (!column)
+ column = &_column;
+
+ while (state != STATE_DONE) {
+ _cleanup_free_ char *token = NULL;
+
+ r = varlink_idl_subparse_token(
+ p,
+ line,
+ column,
+ allowed_delimiters,
+ allowed_chars,
+ &token);
+ if (r < 0)
+ return r;
+
+ switch (state) {
+
+ case STATE_PRE_INTERFACE:
+ if (!token)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF: %m", *line, *column);
+ if (streq(token, "#")) {
+ r = varlink_idl_subparse_comment(&text, line, column);
+ if (r < 0)
+ return r;
+ } else if (streq(token, "interface")) {
+ state = STATE_INTERFACE;
+ allowed_delimiters = NULL;
+ allowed_chars = VALID_CHARS_INTERFACE_NAME;
+ } else
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s': %m", *line, *column, token);
+ break;
+
+ case STATE_INTERFACE:
+ assert(!interface);
+ assert(n_symbols == 0);
+
+ if (!token)
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF: %m", *line, *column);
+
+ r = varlink_interface_realloc(&interface, n_symbols);
+ if (r < 0)
+ return r;
+
+ interface->name = TAKE_PTR(token);
+ state = STATE_PRE_SYMBOL;
+ allowed_delimiters = "#";
+ allowed_chars = VALID_CHARS_RESERVED;
+ break;
+
+ case STATE_PRE_SYMBOL:
+ if (!token) {
+ state = STATE_DONE;
+ break;
+ }
+
+ if (streq(token, "#")) {
+ r = varlink_idl_subparse_comment(&text, line, column);
+ if (r < 0)
+ return r;
+ } else if (streq(token, "method")) {
+ state = STATE_METHOD;
+ allowed_chars = VALID_CHARS_IDENTIFIER;
+ } else if (streq(token, "type")) {
+ state = STATE_TYPE;
+ allowed_chars = VALID_CHARS_IDENTIFIER;
+ } else if (streq(token, "error")) {
+ state = STATE_ERROR;
+ allowed_chars = VALID_CHARS_IDENTIFIER;
+ } else
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s': %m", *line, *column, token);
+
+ break;
+
+ case STATE_METHOD:
+ assert(!symbol);
+ n_fields = 0;
+
+ r = varlink_symbol_realloc(&symbol, n_fields);
+ if (r < 0)
+ return r;
+
+ symbol->symbol_type = VARLINK_METHOD;
+ symbol->name = TAKE_PTR(token);
+
+ r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_INPUT);
+ if (r < 0)
+ return r;
+
+ state = STATE_METHOD_ARROW;
+ allowed_chars = "->";
+ break;
+
+ case STATE_METHOD_ARROW:
+ assert(symbol);
+
+ if (!streq(token, "->"))
+ return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s': %m", *line, *column, token);
+
+ r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_OUTPUT);
+ if (r < 0)
+ return r;
+
+ r = varlink_interface_realloc(&interface, n_symbols + 1);
+ if (r < 0)
+ return r;
+
+ interface->symbols[n_symbols++] = TAKE_PTR(symbol);
+
+ state = STATE_PRE_SYMBOL;
+ allowed_chars = VALID_CHARS_RESERVED "#";
+ break;
+
+ case STATE_TYPE:
+ assert(!symbol);
+ n_fields = 0;
+
+ r = varlink_symbol_realloc(&symbol, n_fields);
+ if (r < 0)
+ return r;
+
+ symbol->symbol_type = _VARLINK_SYMBOL_TYPE_INVALID; /* don't know yet if enum or struct, will be fileld in by varlink_idl_subparse_struct_or_enum() */
+ symbol->name = TAKE_PTR(token);
+
+ r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_REGULAR);
+ if (r < 0)
+ return r;
+
+ r = varlink_interface_realloc(&interface, n_symbols + 1);
+ if (r < 0)
+ return r;
+
+ interface->symbols[n_symbols++] = TAKE_PTR(symbol);
+
+ state = STATE_PRE_SYMBOL;
+ allowed_chars = VALID_CHARS_RESERVED "#";
+ break;
+
+ case STATE_ERROR:
+ assert(!symbol);
+ n_fields = 0;
+
+ r = varlink_symbol_realloc(&symbol, n_fields);
+ if (r < 0)
+ return r;
+
+ symbol->symbol_type = VARLINK_ERROR;
+ symbol->name = TAKE_PTR(token);
+
+ r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_REGULAR);
+ if (r < 0)
+ return r;
+
+ r = varlink_interface_realloc(&interface, n_symbols + 1);
+ if (r < 0)
+ return r;
+
+ interface->symbols[n_symbols++] = TAKE_PTR(symbol);
+
+ state = STATE_PRE_SYMBOL;
+ allowed_chars = VALID_CHARS_RESERVED "#";
+ break;
+
+ default:
+ assert_not_reached();
+ }
+ }
+
+ r = varlink_idl_resolve_types(interface);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(interface);
+ return 0;
+}
+
+bool varlink_idl_field_name_is_valid(const char *name) {
+ if (isempty(name))
+ return false;
+
+ /* Field names may start with lower or uppercase char, but no numerals or underscore */
+ if (!strchr(LETTERS, name[0]))
+ return false;
+
+ /* Otherwise fields may be alphanumerical or underscore, but no two underscore may immediately follow
+ * each other or be trailing */
+ bool underscore = false;
+ for (const char *c = name + 1; *c; c++) {
+ if (*c == '_') {
+ if (underscore)
+ return false;
+
+ underscore = true;
+ continue;
+ }
+
+ if (!strchr(ALPHANUMERICAL, *c))
+ return false;
+
+ underscore = false;
+ }
+
+ if (underscore)
+ return false;
+
+ return true;
+}
+
+bool varlink_idl_symbol_name_is_valid(const char *name) {
+ if (isempty(name))
+ return false;
+
+ /* We might want to reference VARLINK_STRUCT_TYPE and VARLINK_ENUM_TYPE symbols where we also
+ * reference native types, hence make sure the native type names are refused as symbol names. */
+ if (STR_IN_SET(name, "bool", "int", "float", "string", "object"))
+ return false;
+
+ /* Symbols must be named with an uppercase letter as first character */
+ if (!strchr(UPPERCASE_LETTERS, name[0]))
+ return false;
+
+ for (const char *c = name + 1; *c; c++)
+ if (!strchr(ALPHANUMERICAL, *c))
+ return false;
+
+ return true;
+}
+
+bool varlink_idl_interface_name_is_valid(const char *name) {
+ if (isempty(name))
+ return false;
+
+ /* Interface names must start with a letter, uppercase or lower case, but nothing else */
+ if (!strchr(LETTERS, name[0]))
+ return false;
+
+ /* Otherwise it may be a series of non-empty dot separated labels, which are alphanumerical and may
+ * contain single dashes in the middle */
+ bool dot = false, dash = false;
+ for (const char *c = name + 1; *c; c++) {
+ switch (*c) {
+
+ case '.':
+ if (dot || dash)
+ return false;
+
+ dot = true;
+ break;
+
+ case '-':
+ if (dot || dash)
+ return false;
+
+ dash = true;
+ break;
+
+ default:
+ if (!strchr(ALPHANUMERICAL, *c))
+ return false;
+
+ dot = dash = false;
+ }
+ }
+
+ if (dot || dash)
+ return false;
+
+ return true;
+}
+
+static int varlink_idl_symbol_consistent(const VarlinkInterface *interface, const VarlinkSymbol *symbol, bool level);
+
+static int varlink_idl_field_consistent(
+ const VarlinkInterface *interface,
+ const VarlinkSymbol *symbol,
+ const VarlinkField *field,
+ bool level) {
+
+ const char *symbol_name;
+ int r;
+
+ assert(interface);
+ assert(symbol);
+ assert(field);
+ assert(field->name);
+
+ symbol_name = symbol->name ?: "";
+
+ if (field->field_type <= 0 || field->field_type >= _VARLINK_FIELD_TYPE_MAX)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name);
+
+ if (field->field_type == VARLINK_ENUM_VALUE) {
+
+ if (symbol->symbol_type != VARLINK_ENUM_TYPE)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name);
+
+ if (field->field_flags != 0)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name);
+ } else {
+ if (symbol->symbol_type == VARLINK_ENUM_TYPE)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name);
+
+ if (!IN_SET(field->field_flags & ~VARLINK_NULLABLE, 0, VARLINK_ARRAY, VARLINK_MAP))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name);
+ }
+
+ if (symbol->symbol_type != VARLINK_METHOD) {
+ if (field->field_direction != VARLINK_REGULAR)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name);
+ } else {
+ if (!IN_SET(field->field_direction, VARLINK_INPUT, VARLINK_OUTPUT))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name);
+ }
+
+ if (field->symbol) {
+ if (!IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
+
+ if (field->field_type == VARLINK_NAMED_TYPE) {
+ const VarlinkSymbol *found;
+
+ if (!field->symbol->name || !field->named_type || !streq(field->symbol->name, field->named_type))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do do not match, refusing.", field->name, symbol_name);
+
+ /* If this is a named type, then check if it's properly part of the interface */
+ found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->symbol->name);
+ if (!found)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name);
+
+ if (!IN_SET(found->symbol_type, VARLINK_ENUM_TYPE, VARLINK_STRUCT_TYPE))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name);
+ } else {
+ /* If this is an anonymous type, then we recursively check if it's consistent, since
+ * it's not part of the interface, and hence we won't validate it from there. */
+
+ r = varlink_idl_symbol_consistent(interface, field->symbol, level);
+ if (r < 0)
+ return r;
+ }
+
+ } else {
+ if (IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name);
+
+ if (field->named_type)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name);
+ }
+
+ if (field->named_type) {
+ if (field->field_type != VARLINK_NAMED_TYPE)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name);
+ } else {
+ if (field->field_type == VARLINK_NAMED_TYPE)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name);
+ }
+
+ return 0;
+}
+
+static bool varlink_symbol_is_empty(const VarlinkSymbol *symbol) {
+ assert(symbol);
+
+ return symbol->fields[0].field_type == _VARLINK_FIELD_TYPE_END_MARKER;
+}
+
+static int varlink_idl_symbol_consistent(
+ const VarlinkInterface *interface,
+ const VarlinkSymbol *symbol,
+ bool level) {
+
+ _cleanup_(set_freep) Set *input_set = NULL, *output_set = NULL;
+ const char *symbol_name;
+ int r;
+
+ assert(interface);
+ assert(symbol);
+
+ symbol_name = symbol->name ?: "";
+
+ if (symbol->symbol_type < 0 || symbol->symbol_type >= _VARLINK_SYMBOL_TYPE_MAX)
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name);
+
+ if (IN_SET(symbol->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE) && varlink_symbol_is_empty(symbol))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name);
+
+ for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+ Set **name_set = field->field_direction == VARLINK_OUTPUT ? &output_set : &input_set; /* for the method case we need two separate sets, otherwise we use the same */
+
+ if (!varlink_idl_field_name_is_valid(field->name))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name);
+
+ if (set_contains(*name_set, field->name))
+ return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name);
+
+ if (set_ensure_put(name_set, &string_hash_ops, field->name) < 0)
+ return log_oom();
+
+ r = varlink_idl_field_consistent(interface, symbol, field, level);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int varlink_idl_consistent(const VarlinkInterface *interface, bool level) {
+ _cleanup_(set_freep) Set *name_set = NULL;
+ int r;
+
+ assert(interface);
+
+ if (!varlink_idl_interface_name_is_valid(interface->name))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name);
+
+ for (const VarlinkSymbol *const *symbol = interface->symbols; *symbol; symbol++) {
+
+ if (!varlink_idl_symbol_name_is_valid((*symbol)->name))
+ return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name));
+
+ if (set_contains(name_set, (*symbol)->name))
+ return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name);
+
+ if (set_ensure_put(&name_set, &string_hash_ops, (*symbol)->name) < 0)
+ return log_oom();
+
+ r = varlink_idl_symbol_consistent(interface, *symbol, level);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant *v, VarlinkFieldDirection direction, const char **bad_field);
+
+static int varlink_idl_validate_field_element_type(const VarlinkField *field, JsonVariant *v) {
+ assert(field);
+
+ switch (field->field_type) {
+
+ case VARLINK_STRUCT:
+ case VARLINK_ENUM:
+ case VARLINK_NAMED_TYPE:
+ return varlink_idl_validate_symbol(field->symbol, v, VARLINK_REGULAR, NULL);
+
+ case VARLINK_BOOL:
+ if (!json_variant_is_boolean(v))
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name));
+
+ break;
+
+ case VARLINK_INT:
+ if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v))
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name));
+
+ break;
+
+ case VARLINK_FLOAT:
+ if (!json_variant_is_number(v))
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name));
+
+ break;
+
+ case VARLINK_STRING:
+ if (!json_variant_is_string(v))
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name));
+
+ break;
+
+ case VARLINK_OBJECT:
+ if (!json_variant_is_object(v))
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
+
+ break;
+
+ default:
+ assert_not_reached();
+ }
+
+ return 0;
+}
+
+static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v) {
+ int r;
+
+ assert(field);
+
+ if (!v || json_variant_is_null(v)) {
+
+ if (!FLAGS_SET(field->field_flags, VARLINK_NULLABLE))
+ return log_debug_errno(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name));
+
+ } else if (FLAGS_SET(field->field_flags, VARLINK_ARRAY)) {
+ JsonVariant *i;
+
+ if (!json_variant_is_array(v))
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name));
+
+ JSON_VARIANT_ARRAY_FOREACH(i, v) {
+ r = varlink_idl_validate_field_element_type(field, i);
+ if (r < 0)
+ return r;
+ }
+
+ } else if (FLAGS_SET(field->field_flags, VARLINK_MAP)) {
+ _unused_ const char *k;
+ JsonVariant *e;
+
+ if (!json_variant_is_object(v))
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name));
+
+ JSON_VARIANT_OBJECT_FOREACH(k, e, v) {
+ r = varlink_idl_validate_field_element_type(field, e);
+ if (r < 0)
+ return r;
+ }
+ } else {
+
+ r = varlink_idl_validate_field_element_type(field, v);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant *v, VarlinkFieldDirection direction, const char **bad_field) {
+ int r;
+
+ assert(symbol);
+
+ if (!v) {
+ if (bad_field)
+ *bad_field = NULL;
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing.");
+ }
+
+ switch (symbol->symbol_type) {
+
+ case VARLINK_ENUM_TYPE: {
+ bool found = false;
+ const char *s;
+
+ if (!json_variant_is_string(v)) {
+ if (bad_field)
+ *bad_field = symbol->name;
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name));
+ }
+
+ assert_se(s = json_variant_string(v));
+
+ for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+
+ assert(field->field_type == VARLINK_ENUM_VALUE);
+
+ if (streq_ptr(field->name, s)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (bad_field)
+ *bad_field = s;
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name));
+ }
+
+ break;
+ }
+
+ case VARLINK_STRUCT_TYPE:
+ case VARLINK_METHOD:
+ case VARLINK_ERROR: {
+ if (!json_variant_is_object(v)) {
+ if (bad_field)
+ *bad_field = symbol->name;
+ return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name));
+ }
+
+ for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+
+ if (field->field_direction != direction)
+ continue;
+
+ r = varlink_idl_validate_field(field, json_variant_by_key(v, field->name));
+ if (r < 0) {
+ if (bad_field)
+ *bad_field = field->name;
+ return r;
+ }
+ }
+
+ _unused_ JsonVariant *e;
+ const char *name;
+ JSON_VARIANT_OBJECT_FOREACH(name, e, v) {
+ if (!varlink_idl_find_field(symbol, name)) {
+ if (bad_field)
+ *bad_field = name;
+ return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name);
+ }
+ }
+
+ break;
+ }
+
+ default:
+ assert_not_reached();
+ }
+
+ return 1; /* validated */
+}
+
+static int varlink_idl_validate_method(const VarlinkSymbol *method, JsonVariant *v, VarlinkFieldDirection direction, const char **bad_field) {
+ assert(IN_SET(direction, VARLINK_INPUT, VARLINK_OUTPUT));
+
+ if (!method)
+ return 0; /* Can't validate */
+ if (method->symbol_type != VARLINK_METHOD)
+ return -EBADMSG;
+
+ return varlink_idl_validate_symbol(method, v, direction, bad_field);
+}
+
+int varlink_idl_validate_method_call(const VarlinkSymbol *method, JsonVariant *v, const char **bad_field) {
+ return varlink_idl_validate_method(method, v, VARLINK_INPUT, bad_field);
+}
+
+int varlink_idl_validate_method_reply(const VarlinkSymbol *method, JsonVariant *v, const char **bad_field) {
+ return varlink_idl_validate_method(method, v, VARLINK_OUTPUT, bad_field);
+}
+
+int varlink_idl_validate_error(const VarlinkSymbol *error, JsonVariant *v, const char **bad_field) {
+ if (!error)
+ return 0; /* Can't validate */
+ if (error->symbol_type != VARLINK_ERROR)
+ return -EBADMSG;
+
+ return varlink_idl_validate_symbol(error, v, VARLINK_REGULAR, bad_field);
+}
+
+const VarlinkSymbol* varlink_idl_find_symbol(
+ const VarlinkInterface *interface,
+ VarlinkSymbolType type,
+ const char *name) {
+
+ assert(interface);
+ assert(type < _VARLINK_SYMBOL_TYPE_MAX);
+
+ if (isempty(name))
+ return NULL;
+
+ for (const VarlinkSymbol *const*symbol = interface->symbols; *symbol; symbol++) {
+ if (type >= 0 && (*symbol)->symbol_type != type)
+ continue;
+
+ if (streq_ptr((*symbol)->name, name))
+ return *symbol;
+ }
+
+ return NULL;
+}
+
+const VarlinkField* varlink_idl_find_field(
+ const VarlinkSymbol *symbol,
+ const char *name) {
+
+ assert(symbol);
+
+ if (isempty(name))
+ return NULL;
+
+ for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++)
+ if (streq_ptr(field->name, name))
+ return field;
+
+ return NULL;
+}
diff --git a/src/shared/varlink-idl.h b/src/shared/varlink-idl.h
new file mode 100644
index 0000000000..3a7075e76f
--- /dev/null
+++ b/src/shared/varlink-idl.h
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+#include
+#include
+
+#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);
diff --git a/src/shared/varlink-io.systemd.Journal.c b/src/shared/varlink-io.systemd.Journal.c
new file mode 100644
index 0000000000..b93fb72aca
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Journal.c
@@ -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);
diff --git a/src/shared/varlink-io.systemd.Journal.h b/src/shared/varlink-io.systemd.Journal.h
new file mode 100644
index 0000000000..0bc94a775c
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Journal.h
@@ -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;
diff --git a/src/shared/varlink-io.systemd.ManagedOOM.c b/src/shared/varlink-io.systemd.ManagedOOM.c
new file mode 100644
index 0000000000..d6414b3718
--- /dev/null
+++ b/src/shared/varlink-io.systemd.ManagedOOM.c
@@ -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);
diff --git a/src/shared/varlink-io.systemd.ManagedOOM.h b/src/shared/varlink-io.systemd.ManagedOOM.h
new file mode 100644
index 0000000000..2c8bf54c4e
--- /dev/null
+++ b/src/shared/varlink-io.systemd.ManagedOOM.h
@@ -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;
diff --git a/src/shared/varlink-io.systemd.PCRExtend.c b/src/shared/varlink-io.systemd.PCRExtend.c
new file mode 100644
index 0000000000..37d403f0ee
--- /dev/null
+++ b/src/shared/varlink-io.systemd.PCRExtend.c
@@ -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);
diff --git a/src/shared/varlink-io.systemd.PCRExtend.h b/src/shared/varlink-io.systemd.PCRExtend.h
new file mode 100644
index 0000000000..ffc075af2c
--- /dev/null
+++ b/src/shared/varlink-io.systemd.PCRExtend.h
@@ -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;
diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c
new file mode 100644
index 0000000000..179b9bcf1e
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c
@@ -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);
diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.h b/src/shared/varlink-io.systemd.Resolve.Monitor.h
new file mode 100644
index 0000000000..a133ec30a5
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Resolve.Monitor.h
@@ -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;
diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c
new file mode 100644
index 0000000000..0d8ad281fa
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Resolve.c
@@ -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);
diff --git a/src/shared/varlink-io.systemd.Resolve.h b/src/shared/varlink-io.systemd.Resolve.h
new file mode 100644
index 0000000000..5c7ed39d0d
--- /dev/null
+++ b/src/shared/varlink-io.systemd.Resolve.h
@@ -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;
diff --git a/src/shared/varlink-io.systemd.UserDatabase.c b/src/shared/varlink-io.systemd.UserDatabase.c
new file mode 100644
index 0000000000..a640d2e29a
--- /dev/null
+++ b/src/shared/varlink-io.systemd.UserDatabase.c
@@ -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);
diff --git a/src/shared/varlink-io.systemd.UserDatabase.h b/src/shared/varlink-io.systemd.UserDatabase.h
new file mode 100644
index 0000000000..346ca848ac
--- /dev/null
+++ b/src/shared/varlink-io.systemd.UserDatabase.h
@@ -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;
diff --git a/src/shared/varlink-io.systemd.c b/src/shared/varlink-io.systemd.c
new file mode 100644
index 0000000000..cdfe9ac263
--- /dev/null
+++ b/src/shared/varlink-io.systemd.c
@@ -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);
diff --git a/src/shared/varlink-io.systemd.h b/src/shared/varlink-io.systemd.h
new file mode 100644
index 0000000000..6c17c6c4c7
--- /dev/null
+++ b/src/shared/varlink-io.systemd.h
@@ -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;
diff --git a/src/shared/varlink-io.systemd.oom.c b/src/shared/varlink-io.systemd.oom.c
new file mode 100644
index 0000000000..e1da3fa527
--- /dev/null
+++ b/src/shared/varlink-io.systemd.oom.c
@@ -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);
diff --git a/src/shared/varlink-io.systemd.oom.h b/src/shared/varlink-io.systemd.oom.h
new file mode 100644
index 0000000000..911dbc29e6
--- /dev/null
+++ b/src/shared/varlink-io.systemd.oom.h
@@ -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;
diff --git a/src/shared/varlink-org.varlink.service.c b/src/shared/varlink-org.varlink.service.c
new file mode 100644
index 0000000000..52ba99dd2b
--- /dev/null
+++ b/src/shared/varlink-org.varlink.service.c
@@ -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);
diff --git a/src/shared/varlink-org.varlink.service.h b/src/shared/varlink-org.varlink.service.h
new file mode 100644
index 0000000000..75c55e6e82
--- /dev/null
+++ b/src/shared/varlink-org.varlink.service.h
@@ -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;
diff --git a/src/shared/varlink.c b/src/shared/varlink.c
index d2f649485b..f061024785 100644
--- a/src/shared/varlink.c
+++ b/src/shared/varlink.c
@@ -3,6 +3,8 @@
#include
#include
+#include
+
#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;
+}
diff --git a/src/shared/varlink.h b/src/shared/varlink.h
index 0984222c96..5fe529c853 100644
--- a/src/shared/varlink.h
+++ b/src/shared/varlink.h
@@ -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"
diff --git a/src/test/meson.build b/src/test/meson.build
index 7e25fcdbaa..3f1b7afbf7 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -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',
diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c
new file mode 100644
index 0000000000..a93d717f3f
--- /dev/null
+++ b/src/test/test-varlink-idl.c
@@ -0,0 +1,344 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#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);
diff --git a/src/userdb/userwork.c b/src/userdb/userwork.c
index 9fccadc950..7413378bcb 100644
--- a/src/userdb/userwork.c
+++ b/src/userdb/userwork.c
@@ -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,
diff --git a/src/varlinkctl/meson.build b/src/varlinkctl/meson.build
new file mode 100644
index 0000000000..c1074dc50a
--- /dev/null
+++ b/src/varlinkctl/meson.build
@@ -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,
+ },
+]
diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c
new file mode 100644
index 0000000000..a656bfcaf0
--- /dev/null
+++ b/src/varlinkctl/varlinkctl.c
@@ -0,0 +1,520 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#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, "", 0, &jp, &line, &column);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse parameters at :%u:%u: %m", line, column);
+ } else {
+ r = json_parse_file_at(stdin, AT_FDCWD, "", 0, &jp, &line, &column);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse parameters at :%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 = "";
+ }
+
+ 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);
diff --git a/test/units/testsuite-04.journal.sh b/test/units/testsuite-04.journal.sh
index 1e105a0566..3efac616e0 100755
--- a/test/units/testsuite-04.journal.sh
+++ b/test/units/testsuite-04.journal.sh
@@ -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 /run/systemd/system/systemd-pcrextend.socket.d/50-no-condition.conf <