diff --git a/TODO b/TODO
index 1be6f2e4b3..97c84f7a23 100644
--- a/TODO
+++ b/TODO
@@ -132,6 +132,8 @@ Deprecations and removals:
Features:
+* varlink: extend varlink IDL macros to include documentation strings
+
* Get rid of the symlinks in /run/systemd/units/* and exclusively use cgroupfs
xattrs to convey info about invocation ids, logging settings and so on.
support for cgroupfs xattrs in the "trusted." namespace was added in linux
@@ -335,7 +337,6 @@ Features:
- systemd-dissect
- systemd-sysupdate
- systemd-analyze
- - systemd-pcrlock (to allow fwupd to relax policy)
- kernel-install
- systemd-mount (with PK so that desktop environments could use it to mount disks)
diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml
index eff49af349..646fea0d24 100644
--- a/man/varlinkctl.xml
+++ b/man/varlinkctl.xml
@@ -184,6 +184,15 @@
+
+
+
+ This is similar to but collects all responses in a JSON
+ array, and prints it, rather than in JSON_SEQ mode.
+
+
+
+
diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c
index 9a9da049b2..1dd3d86a73 100644
--- a/src/pcrlock/pcrlock.c
+++ b/src/pcrlock/pcrlock.c
@@ -48,6 +48,8 @@
#include "unaligned.h"
#include "unit-name.h"
#include "utf8.h"
+#include "varlink.h"
+#include "varlink-io.systemd.PCRLock.h"
#include "verbs.h"
static PagerFlags arg_pager_flags = 0;
@@ -65,6 +67,7 @@ static char *arg_policy_path = NULL;
static bool arg_force = false;
static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
static char *arg_entry_token = NULL;
+static bool arg_varlink = false;
STATIC_DESTRUCTOR_REGISTER(arg_components, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_pcrlock_path, freep);
@@ -2412,6 +2415,75 @@ static int verb_show_log(int argc, char *argv[], void *userdata) {
return 0;
}
+static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, JsonVariant **ret) {
+ _cleanup_(json_variant_unrefp) JsonVariant *ja = NULL, *fj = NULL;
+ JsonVariant *cd = NULL;
+ const char *ct = NULL;
+ int r;
+
+ assert(record);
+ assert(recnum);
+ assert(ret);
+
+ LIST_FOREACH(banks, bank, record->banks) {
+ r = json_variant_append_arrayb(
+ &ja, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)),
+ JSON_BUILD_PAIR_HEX("digest", bank->hash.buffer, bank->hash.size)));
+ if (r < 0)
+ return log_error_errno(r, "Failed to append CEL digest entry: %m");
+ }
+
+ if (!ja) {
+ r = json_variant_new_array(&ja, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate JSON array: %m");
+ }
+
+ if (EVENT_LOG_RECORD_IS_FIRMWARE(record)) {
+ _cleanup_free_ char *et = NULL;
+ const char *z;
+
+ z = tpm2_log_event_type_to_string(record->firmware_event_type);
+ if (z) {
+ _cleanup_free_ char *b = NULL;
+
+ b = strreplace(z, "-", "_");
+ if (!b)
+ return log_oom();
+
+ et = strjoin("EV_", ascii_strupper(b));
+ if (!et)
+ return log_oom();
+ } else if (asprintf(&et, "%" PRIu32, record->firmware_event_type) < 0)
+ return log_oom();
+
+ r = json_build(&fj, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_STRING("event_type", et),
+ JSON_BUILD_PAIR_HEX("event_data", record->firmware_payload, record->firmware_payload_size)));
+ if (r < 0)
+ return log_error_errno(r, "Failed to build firmware event data: %m");
+
+ cd = fj;
+ ct = "pcclient_std";
+ } else if (EVENT_LOG_RECORD_IS_USERSPACE(record)) {
+ cd = record->userspace_content;
+ ct = "systemd";
+ }
+
+ r = json_build(ret,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr),
+ JSON_BUILD_PAIR_UNSIGNED("recnum", ++(*recnum)),
+ JSON_BUILD_PAIR_VARIANT("digests", ja),
+ JSON_BUILD_PAIR_CONDITION(ct, "content_type", JSON_BUILD_STRING(ct)),
+ JSON_BUILD_PAIR_CONDITION(cd, "content", JSON_BUILD_VARIANT(cd))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to make CEL record: %m");
+
+ return 0;
+}
+
static int verb_show_cel(int argc, char *argv[], void *userdata) {
_cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
_cleanup_(event_log_freep) EventLog *el = NULL;
@@ -2429,64 +2501,13 @@ static int verb_show_cel(int argc, char *argv[], void *userdata) {
/* Output the event log in TCG CEL-JSON. */
FOREACH_ARRAY(rr, el->records, el->n_records) {
- _cleanup_(json_variant_unrefp) JsonVariant *ja = NULL, *fj = NULL;
- EventLogRecord *record = *rr;
- JsonVariant *cd = NULL;
- const char *ct = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *cel = NULL;
- LIST_FOREACH(banks, bank, record->banks) {
- r = json_variant_append_arrayb(
- &ja, JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)),
- JSON_BUILD_PAIR_HEX("digest", bank->hash.buffer, bank->hash.size)));
- if (r < 0)
- return log_error_errno(r, "Failed to append CEL digest entry: %m");
- }
+ r = event_log_record_to_cel(*rr, &recnum, &cel);
+ if (r < 0)
+ return r;
- if (!ja) {
- r = json_variant_new_array(&ja, NULL, 0);
- if (r < 0)
- return log_error_errno(r, "Failed to allocate JSON array: %m");
- }
-
- if (EVENT_LOG_RECORD_IS_FIRMWARE(record)) {
- _cleanup_free_ char *et = NULL;
- const char *z;
-
- z = tpm2_log_event_type_to_string(record->firmware_event_type);
- if (z) {
- _cleanup_free_ char *b = NULL;
-
- b = strreplace(z, "-", "_");
- if (!b)
- return log_oom();
-
- et = strjoin("EV_", ascii_strupper(b));
- if (!et)
- return log_oom();
- } else if (asprintf(&et, "%" PRIu32, record->firmware_event_type) < 0)
- return log_oom();
-
- r = json_build(&fj, JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR_STRING("event_type", et),
- JSON_BUILD_PAIR_HEX("event_data", record->firmware_payload, record->firmware_payload_size)));
- if (r < 0)
- return log_error_errno(r, "Failed to build firmware event data: %m");
-
- cd = fj;
- ct = "pcclient_std";
- } else if (EVENT_LOG_RECORD_IS_USERSPACE(record)) {
- cd = record->userspace_content;
- ct = "systemd";
- }
-
- r = json_variant_append_arrayb(&array,
- JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr),
- JSON_BUILD_PAIR_UNSIGNED("recnum", ++recnum),
- JSON_BUILD_PAIR_VARIANT("digests", ja),
- JSON_BUILD_PAIR_CONDITION(ct, "content_type", JSON_BUILD_STRING(ct)),
- JSON_BUILD_PAIR_CONDITION(cd, "content", JSON_BUILD_VARIANT(cd))));
+ r = json_variant_append_array(&array, cel);
if (r < 0)
return log_error_errno(r, "Failed to append CEL record: %m");
}
@@ -4292,7 +4313,7 @@ static int write_boot_policy_file(const char *json_text) {
return 1;
}
-static int verb_make_policy(int argc, char *argv[], void *userdata) {
+static int make_policy(bool force, bool recovery_pin) {
int r;
/* Here's how this all works: after predicting all possible PCR values for next boot (with
@@ -4367,11 +4388,11 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) {
if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index);
- if (!arg_force &&
+ if (!force &&
old_policy.algorithm == el->primary_algorithm &&
tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) {
log_info("Prediction is identical to current policy, skipping update.");
- return EXIT_SUCCESS;
+ return 0; /* NOP */
}
}
@@ -4416,7 +4437,7 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) {
/* Acquire a recovery PIN, either from the user, or create a randomized one */
_cleanup_(erase_and_freep) char *pin = NULL;
- if (arg_recovery_pin) {
+ if (recovery_pin) {
r = getenv_steal_erase("PIN", &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire PIN from environment: %m");
@@ -4694,7 +4715,11 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) {
log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1));
- return 0;
+ return 1; /* installed new policy */
+}
+
+static int verb_make_policy(int argc, char *argv[], void *userdata) {
+ return make_policy(arg_force, arg_recovery_pin);
}
static int undefine_policy_nv_index(
@@ -4750,7 +4775,7 @@ static int undefine_policy_nv_index(
return 0;
}
-static int verb_remove_policy(int argc, char *argv[], void *userdata) {
+static int remove_policy(void) {
int ret = 0, r;
_cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
@@ -4789,6 +4814,10 @@ static int verb_remove_policy(int argc, char *argv[], void *userdata) {
return ret;
}
+static int verb_remove_policy(int argc, char *argv[], void *userdata) {
+ return remove_policy();
+}
+
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
int r;
@@ -5064,6 +5093,14 @@ static int parse_argv(int argc, char *argv[]) {
return log_oom();
}
+ 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;
+ arg_pager_flags |= PAGER_DISABLE;
+ }
+
return 1;
}
@@ -5106,17 +5143,125 @@ static int pcrlock_main(int argc, char *argv[]) {
return dispatch_verb(argc, argv, verbs, NULL);
}
+static int vl_method_read_event_log(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+ _cleanup_(event_log_freep) EventLog *el = NULL;
+ uint64_t recnum = 0;
+ int r;
+
+ assert(link);
+
+ if (json_variant_elements(parameters) > 0)
+ return varlink_error_invalid_parameter(link, parameters);
+
+ el = event_log_new();
+ if (!el)
+ return log_oom();
+
+ r = event_log_load(el);
+ if (r < 0)
+ return r;
+
+ _cleanup_(json_variant_unrefp) JsonVariant *rec_cel = NULL;
+
+ FOREACH_ARRAY(rr, el->records, el->n_records) {
+
+ if (rec_cel) {
+ r = varlink_notifyb(link,
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR_VARIANT("record", rec_cel)));
+ if (r < 0)
+ return r;
+
+ rec_cel = json_variant_unref(rec_cel);
+ }
+
+ r = event_log_record_to_cel(*rr, &recnum, &rec_cel);
+ if (r < 0)
+ return r;
+ }
+
+ return varlink_replyb(link,
+ JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(rec_cel, "record", JSON_BUILD_VARIANT(rec_cel))));
+}
+
+typedef struct MethodMakePolicyParameters {
+ bool force;
+} MethodMakePolicyParameters;
+
+static int vl_method_make_policy(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+ static const JsonDispatch dispatch_table[] = {
+ { "force", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMakePolicyParameters, force), 0 },
+ {}
+ };
+ MethodMakePolicyParameters p = {};
+ int r;
+
+ assert(link);
+
+ r = varlink_dispatch(link, parameters, dispatch_table, &p);
+ if (r != 0)
+ return r;
+
+ r = make_policy(p.force, /* recovery_key= */ false);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return varlink_error(link, "io.systemd.PCRLock.NoChange", NULL);
+
+ return varlink_reply(link, NULL);
+}
+
+static int vl_method_remove_policy(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) {
+ int r;
+
+ assert(link);
+
+ if (json_variant_elements(parameters) > 0)
+ return varlink_error_invalid_parameter(link, parameters);
+
+ r = remove_policy();
+ if (r < 0)
+ return r;
+
+ return varlink_reply(link, NULL);
+}
+
static int run(int argc, char *argv[]) {
int r;
- log_show_color(true);
- log_parse_environment();
- log_open();
+ log_setup();
r = parse_argv(argc, 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_PCRLock);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+ r = varlink_server_bind_method_many(
+ varlink_server,
+ "io.systemd.PCRLock.ReadEventLog", vl_method_read_event_log,
+ "io.systemd.PCRLock.MakePolicy", vl_method_make_policy,
+ "io.systemd.PCRLock.RemovePolicy", vl_method_remove_policy);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind Varlink methods: %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;
+ }
+
return pcrlock_main(argc, argv);
}
diff --git a/src/shared/meson.build b/src/shared/meson.build
index dc9adeddc1..81de6708f0 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -180,6 +180,7 @@ shared_sources = files(
'varlink-io.systemd.ManagedOOM.c',
'varlink-io.systemd.Network.c',
'varlink-io.systemd.PCRExtend.c',
+ 'varlink-io.systemd.PCRLock.c',
'varlink-io.systemd.Resolve.c',
'varlink-io.systemd.Resolve.Monitor.c',
'varlink-io.systemd.UserDatabase.c',
diff --git a/src/shared/varlink-io.systemd.PCRLock.c b/src/shared/varlink-io.systemd.PCRLock.c
new file mode 100644
index 0000000000..3b2c408bc9
--- /dev/null
+++ b/src/shared/varlink-io.systemd.PCRLock.c
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.PCRLock.h"
+
+static VARLINK_DEFINE_METHOD(
+ ReadEventLog);
+
+static VARLINK_DEFINE_METHOD(
+ MakePolicy,
+ VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE));
+
+static VARLINK_DEFINE_METHOD(
+ RemovePolicy);
+
+VARLINK_DEFINE_ERROR(
+ NoChange);
+
+VARLINK_DEFINE_INTERFACE(
+ io_systemd_PCRLock,
+ "io.systemd.PCRLock",
+ &vl_method_ReadEventLog,
+ &vl_method_MakePolicy,
+ &vl_method_RemovePolicy,
+ &vl_error_NoChange);
diff --git a/src/shared/varlink-io.systemd.PCRLock.h b/src/shared/varlink-io.systemd.PCRLock.h
new file mode 100644
index 0000000000..687f09ef84
--- /dev/null
+++ b/src/shared/varlink-io.systemd.PCRLock.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_PCRLock;
diff --git a/src/shared/varlink.c b/src/shared/varlink.c
index 1e1e4d48f9..8a5a9907a8 100644
--- a/src/shared/varlink.c
+++ b/src/shared/varlink.c
@@ -37,6 +37,7 @@
#define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC)
#define VARLINK_BUFFER_MAX (16U*1024U*1024U)
#define VARLINK_READ_SIZE (64U*1024U)
+#define VARLINK_COLLECT_MAX 1024U
typedef enum VarlinkState {
/* Client side states */
@@ -45,6 +46,8 @@ typedef enum VarlinkState {
VARLINK_AWAITING_REPLY_MORE,
VARLINK_CALLING,
VARLINK_CALLED,
+ VARLINK_COLLECTING,
+ VARLINK_COLLECTING_REPLY,
VARLINK_PROCESSING_REPLY,
/* Server side states */
@@ -79,6 +82,8 @@ typedef enum VarlinkState {
VARLINK_AWAITING_REPLY_MORE, \
VARLINK_CALLING, \
VARLINK_CALLED, \
+ VARLINK_COLLECTING, \
+ VARLINK_COLLECTING_REPLY, \
VARLINK_PROCESSING_REPLY, \
VARLINK_IDLE_SERVER, \
VARLINK_PROCESSING_METHOD, \
@@ -169,6 +174,8 @@ struct Varlink {
VarlinkReply reply_callback;
JsonVariant *current;
+ JsonVariant *current_collected;
+ VarlinkReplyFlags current_reply_flags;
VarlinkSymbol *current_method;
int peer_pidfd;
@@ -243,18 +250,14 @@ struct VarlinkServer {
bool exit_on_idle;
};
-typedef struct VarlinkCollectContext {
- JsonVariant *parameters;
- const char *error_id;
- VarlinkReplyFlags flags;
-} VarlinkCollectContext ;
-
static const char* const varlink_state_table[_VARLINK_STATE_MAX] = {
[VARLINK_IDLE_CLIENT] = "idle-client",
[VARLINK_AWAITING_REPLY] = "awaiting-reply",
[VARLINK_AWAITING_REPLY_MORE] = "awaiting-reply-more",
[VARLINK_CALLING] = "calling",
[VARLINK_CALLED] = "called",
+ [VARLINK_COLLECTING] = "collecting",
+ [VARLINK_COLLECTING_REPLY] = "collecting-reply",
[VARLINK_PROCESSING_REPLY] = "processing-reply",
[VARLINK_IDLE_SERVER] = "idle-server",
[VARLINK_PROCESSING_METHOD] = "processing-method",
@@ -688,7 +691,9 @@ static void varlink_clear_current(Varlink *v) {
/* Clears the currently processed incoming message */
v->current = json_variant_unref(v->current);
+ v->current_collected = json_variant_unref(v->current_collected);
v->current_method = NULL;
+ v->current_reply_flags = 0;
close_many(v->input_fds, v->n_input_fds);
v->input_fds = mfree(v->input_fds);
@@ -771,7 +776,7 @@ static int varlink_test_disconnect(Varlink *v) {
goto disconnect;
/* If we are waiting for incoming data but the read side is shut down, disconnect. */
- if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) && v->read_disconnected)
+ if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && v->read_disconnected)
goto disconnect;
/* Similar, if are a client that hasn't written anything yet but the write side is dead, also
@@ -894,7 +899,7 @@ static int varlink_read(Varlink *v) {
assert(v);
- if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER))
+ if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER))
return 0;
if (v->connecting) /* read() on a socket while we are in connect() will fail with EINVAL, hence exit early here */
return 0;
@@ -1090,7 +1095,7 @@ static int varlink_parse_message(Varlink *v) {
static int varlink_test_timeout(Varlink *v) {
assert(v);
- if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING))
+ if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING))
return 0;
if (v->timeout == USEC_INFINITY)
return 0;
@@ -1180,7 +1185,7 @@ static int varlink_dispatch_reply(Varlink *v) {
assert(v);
- if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING))
+ if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING))
return 0;
if (!v->current)
return 0;
@@ -1223,7 +1228,7 @@ static int varlink_dispatch_reply(Varlink *v) {
}
/* Replies with 'continue' set are only OK if we set 'more' when the method call was initiated */
- if (v->state != VARLINK_AWAITING_REPLY_MORE && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
+ if (!IN_SET(v->state, VARLINK_AWAITING_REPLY_MORE, VARLINK_COLLECTING) && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
goto invalid;
/* An error is final */
@@ -1234,6 +1239,8 @@ static int varlink_dispatch_reply(Varlink *v) {
if (r < 0)
goto invalid;
+ v->current_reply_flags = flags;
+
if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE)) {
varlink_set_state(v, VARLINK_PROCESSING_REPLY);
@@ -1246,7 +1253,6 @@ static int varlink_dispatch_reply(Varlink *v) {
varlink_clear_current(v);
if (v->state == VARLINK_PROCESSING_REPLY) {
-
assert(v->n_pending > 0);
if (!FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
@@ -1256,7 +1262,9 @@ static int varlink_dispatch_reply(Varlink *v) {
FLAGS_SET(flags, VARLINK_REPLY_CONTINUES) ? VARLINK_AWAITING_REPLY_MORE :
v->n_pending == 0 ? VARLINK_IDLE_CLIENT : VARLINK_AWAITING_REPLY);
}
- } else {
+ } else if (v->state == VARLINK_COLLECTING)
+ varlink_set_state(v, VARLINK_COLLECTING_REPLY);
+ else {
assert(v->state == VARLINK_CALLING);
varlink_set_state(v, VARLINK_CALLED);
}
@@ -1734,7 +1742,7 @@ int varlink_get_events(Varlink *v) {
return EPOLLOUT;
if (!v->read_disconnected &&
- IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) &&
+ IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) &&
!v->current &&
v->input_buffer_unscanned <= 0)
ret |= EPOLLIN;
@@ -1752,7 +1760,7 @@ int varlink_get_timeout(Varlink *v, usec_t *ret) {
if (v->state == VARLINK_DISCONNECTED)
return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected.");
- if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING) &&
+ if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING) &&
v->timeout != USEC_INFINITY) {
if (ret)
*ret = usec_add(v->timestamp, v->timeout);
@@ -2199,7 +2207,6 @@ int varlink_call_full(
v->timestamp = now(CLOCK_MONOTONIC);
while (v->state == VARLINK_CALLING) {
-
r = varlink_process(v);
if (r < 0)
return r;
@@ -2232,7 +2239,7 @@ int varlink_call_full(
if (ret_error_id)
*ret_error_id = e ? json_variant_string(e) : NULL;
if (ret_flags)
- *ret_flags = 0;
+ *ret_flags = v->current_reply_flags;
return 1;
}
@@ -2318,44 +2325,7 @@ int varlink_callb_and_log(
return varlink_call_and_log(v, method, parameters, ret_parameters);
}
-static void varlink_collect_context_free(VarlinkCollectContext *cc) {
- assert(cc);
-
- json_variant_unref(cc->parameters);
- free((char *)cc->error_id);
-}
-
-static int collect_callback(
- Varlink *v,
- JsonVariant *parameters,
- const char *error_id,
- VarlinkReplyFlags flags,
- void *userdata) {
-
- VarlinkCollectContext *context = ASSERT_PTR(userdata);
- int r;
-
- assert(v);
-
- context->flags = flags;
- /* If we hit an error, we will drop all collected replies and just return the error_id and flags in varlink_collect() */
- if (error_id) {
- context->error_id = error_id;
-
- json_variant_unref(context->parameters);
- context->parameters = json_variant_ref(parameters);
-
- return 0;
- }
-
- r = json_variant_append_array(&context->parameters, parameters);
- if (r < 0)
- return varlink_log_errno(v, r, "Failed to append JSON object to array: %m");
-
- return 1;
-}
-
-int varlink_collect(
+int varlink_collect_full(
Varlink *v,
const char *method,
JsonVariant *parameters,
@@ -2363,7 +2333,7 @@ int varlink_collect(
const char **ret_error_id,
VarlinkReplyFlags *ret_flags) {
- _cleanup_(varlink_collect_context_free) VarlinkCollectContext context = {};
+ _cleanup_(json_variant_unrefp) JsonVariant *m = NULL, *collected = NULL;
int r;
assert_return(v, -EINVAL);
@@ -2380,71 +2350,102 @@ int varlink_collect(
* that we can assign a new reply shortly. */
varlink_clear_current(v);
- r = varlink_bind_reply(v, collect_callback);
+ r = varlink_sanitize_parameters(¶meters);
if (r < 0)
- return varlink_log_errno(v, r, "Failed to bind collect callback");
+ return varlink_log_errno(v, r, "Failed to sanitize parameters: %m");
- varlink_set_userdata(v, &context);
- r = varlink_observe(v, method, parameters);
+ r = json_build(&m, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)),
+ JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters)),
+ JSON_BUILD_PAIR("more", JSON_BUILD_BOOLEAN(true))));
if (r < 0)
- return varlink_log_errno(v, r, "Failed to collect varlink method: %m");
+ return varlink_log_errno(v, r, "Failed to build json message: %m");
- while (v->state == VARLINK_AWAITING_REPLY_MORE) {
+ r = varlink_enqueue_json(v, m);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to enqueue json message: %m");
- r = varlink_process(v);
- if (r < 0)
- return r;
+ varlink_set_state(v, VARLINK_COLLECTING);
+ v->n_pending++;
+ v->timestamp = now(CLOCK_MONOTONIC);
- /* If we get an error from any of the replies, return immediately with just the error_id and flags*/
- if (context.error_id) {
+ for (;;) {
+ while (v->state == VARLINK_COLLECTING) {
+ r = varlink_process(v);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ continue;
- /* If caller doesn't ask for the error string, then let's return an error code in case of failure */
- if (!ret_error_id)
- return varlink_error_to_errno(context.error_id, context.parameters);
-
- if (ret_parameters)
- *ret_parameters = TAKE_PTR(context.parameters);
- if (ret_error_id)
- *ret_error_id = TAKE_PTR(context.error_id);
- if (ret_flags)
- *ret_flags = context.flags;
- return 0;
+ r = varlink_wait(v, USEC_INFINITY);
+ if (r < 0)
+ return r;
}
- if (r > 0)
- continue;
+ switch (v->state) {
- r = varlink_wait(v, USEC_INFINITY);
- if (r < 0)
- return r;
+ case VARLINK_COLLECTING_REPLY: {
+ assert(v->current);
+
+ JsonVariant *e = json_variant_by_key(v->current, "error"),
+ *p = json_variant_by_key(v->current, "parameters");
+
+ if (e) {
+ if (!ret_error_id)
+ return varlink_error_to_errno(json_variant_string(e), p);
+
+ if (ret_parameters)
+ *ret_parameters = p;
+ if (ret_error_id)
+ *ret_error_id = e ? json_variant_string(e) : NULL;
+ if (ret_flags)
+ *ret_flags = v->current_reply_flags;
+
+ return 1;
+ }
+
+ if (json_variant_elements(collected) >= VARLINK_COLLECT_MAX)
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(E2BIG), "Number of reply messages grew too large (%zu) while collecting.", json_variant_elements(collected));
+
+ r = json_variant_append_array(&collected, p);
+ if (r < 0)
+ return varlink_log_errno(v, r, "Failed to append JSON object to array: %m");
+
+ if (FLAGS_SET(v->current_reply_flags, VARLINK_REPLY_CONTINUES)) {
+ /* There's more to collect, continue */
+ varlink_clear_current(v);
+ varlink_set_state(v, VARLINK_COLLECTING);
+ continue;
+ }
+
+ varlink_set_state(v, VARLINK_IDLE_CLIENT);
+ assert(v->n_pending == 1);
+ v->n_pending--;
+
+ if (ret_parameters)
+ /* Install the collection array in the connection object, so that we can hand
+ * out a pointer to it without passing over ownership, to make it work more
+ * alike regular method call replies */
+ *ret_parameters = v->current_collected = TAKE_PTR(collected);
+ if (ret_error_id)
+ *ret_error_id = NULL;
+ if (ret_flags)
+ *ret_flags = v->current_reply_flags;
+
+ return 1;
+ }
+
+ case VARLINK_PENDING_DISCONNECT:
+ case VARLINK_DISCONNECTED:
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed.");
+
+ case VARLINK_PENDING_TIMEOUT:
+ return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out.");
+
+ default:
+ assert_not_reached();
+ }
}
-
- switch (v->state) {
-
- case VARLINK_IDLE_CLIENT:
- break;
-
- case VARLINK_PENDING_DISCONNECT:
- case VARLINK_DISCONNECTED:
- return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed.");
-
- case VARLINK_PENDING_TIMEOUT:
- return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out.");
-
- default:
- assert_not_reached();
- }
-
- if (!ret_error_id && context.error_id)
- return varlink_error_to_errno(context.error_id, context.parameters);
-
- if (ret_parameters)
- *ret_parameters = TAKE_PTR(context.parameters);
- if (ret_error_id)
- *ret_error_id = TAKE_PTR(context.error_id);
- if (ret_flags)
- *ret_flags = context.flags;
- return 1;
}
int varlink_collectb(
@@ -2452,7 +2453,7 @@ int varlink_collectb(
const char *method,
JsonVariant **ret_parameters,
const char **ret_error_id,
- VarlinkReplyFlags *ret_flags, ...) {
+ ...) {
_cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL;
va_list ap;
@@ -2460,14 +2461,14 @@ int varlink_collectb(
assert_return(v, -EINVAL);
- va_start(ap, ret_flags);
+ va_start(ap, ret_error_id);
r = json_buildv(¶meters, ap);
va_end(ap);
if (r < 0)
return varlink_log_errno(v, r, "Failed to build json message: %m");
- return varlink_collect(v, method, parameters, ret_parameters, ret_error_id, ret_flags);
+ return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
}
int varlink_reply(Varlink *v, JsonVariant *parameters) {
diff --git a/src/shared/varlink.h b/src/shared/varlink.h
index 622ab797c5..db7227b215 100644
--- a/src/shared/varlink.h
+++ b/src/shared/varlink.h
@@ -116,8 +116,11 @@ static inline int varlink_callb(Varlink *v, const char *method, JsonVariant **re
int varlink_callb_and_log(Varlink *v, const char *method, JsonVariant **ret_parameters, ...);
/* Send method call and begin collecting all 'more' replies into an array, finishing when a final reply is sent */
-int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
-int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...);
+int varlink_collect_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags);
+static inline int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) {
+ return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL);
+}
+int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...);
/* Enqueue method call, expect a reply, which is eventually delivered to the reply callback */
int varlink_invoke(Varlink *v, const char *method, JsonVariant *parameters);
diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c
index d322c02c55..e5708b73b5 100644
--- a/src/test/test-varlink-idl.c
+++ b/src/test/test-varlink-idl.c
@@ -13,6 +13,7 @@
#include "varlink-io.systemd.ManagedOOM.h"
#include "varlink-io.systemd.Network.h"
#include "varlink-io.systemd.PCRExtend.h"
+#include "varlink-io.systemd.PCRLock.h"
#include "varlink-io.systemd.Resolve.Monitor.h"
#include "varlink-io.systemd.Resolve.h"
#include "varlink-io.systemd.UserDatabase.h"
@@ -143,6 +144,8 @@ TEST(parse_format) {
print_separator();
test_parse_format_one(&vl_interface_io_systemd_PCRExtend);
print_separator();
+ test_parse_format_one(&vl_interface_io_systemd_PCRLock);
+ print_separator();
test_parse_format_one(&vl_interface_io_systemd_service);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_sysext);
diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c
index b0b244e917..67ad213002 100644
--- a/src/test/test-varlink.c
+++ b/src/test/test-varlink.c
@@ -238,10 +238,9 @@ static void flood_test(const char *address) {
static void *thread(void *arg) {
_cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL;
- _cleanup_(json_variant_unrefp) JsonVariant *i = NULL, *j = NULL;
- JsonVariant *o = NULL, *k = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *i = NULL;
+ JsonVariant *o = NULL, *k = NULL, *j = NULL;
const char *error_id;
- VarlinkReplyFlags flags = 0;
const char *e;
int x = 0;
@@ -253,10 +252,9 @@ static void *thread(void *arg) {
assert_se(varlink_set_allow_fd_passing_input(c, true) >= 0);
assert_se(varlink_set_allow_fd_passing_output(c, true) >= 0);
- assert_se(varlink_collect(c, "io.test.DoSomethingMore", i, &j, &error_id, &flags) >= 0);
+ assert_se(varlink_collect(c, "io.test.DoSomethingMore", i, &j, &error_id) >= 0);
assert_se(!error_id);
- assert_se(!flags);
assert_se(json_variant_is_array(j) && !json_variant_is_blank_array(j));
JSON_VARIANT_ARRAY_FOREACH(k, j) {
diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c
index 8f3a88aba5..26b764aca6 100644
--- a/src/varlinkctl/varlinkctl.c
+++ b/src/varlinkctl/varlinkctl.c
@@ -19,6 +19,7 @@
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static VarlinkMethodFlags arg_method_flags = 0;
+static bool arg_collect = false;
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -47,6 +48,7 @@ static int help(void) {
" --version Show package version\n"
" --no-pager Do not pipe output into a pager\n"
" --more Request multiple responses\n"
+ " --collect Collect multiple responses in a JSON array\n"
" --oneway Do not request response\n"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
@@ -73,6 +75,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_MORE,
ARG_ONEWAY,
ARG_JSON,
+ ARG_COLLECT,
};
static const struct option options[] = {
@@ -82,6 +85,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "more", no_argument, NULL, ARG_MORE },
{ "oneway", no_argument, NULL, ARG_ONEWAY },
{ "json", required_argument, NULL, ARG_JSON },
+ { "collect", no_argument, NULL, ARG_COLLECT },
{},
};
@@ -112,6 +116,10 @@ static int parse_argv(int argc, char *argv[]) {
arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_MORE) | VARLINK_METHOD_ONEWAY;
break;
+ case ARG_COLLECT:
+ arg_collect = true;
+ break;
+
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
if (r <= 0)
@@ -371,7 +379,13 @@ static int verb_call(int argc, char *argv[], void *userdata) {
method = argv[2];
parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
- arg_json_format_flags &= ~JSON_FORMAT_OFF;
+ /* No JSON mode explicitly configured? Then default to the same as -j */
+ if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+ arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
+
+ /* For pipeable text tools it's kinda customary to finish output off in a newline character, and not
+ * leave incomplete lines hanging around. */
+ arg_json_format_flags |= JSON_FORMAT_NEWLINE;
if (parameter) {
/* is correct, as dispatch_verb() shifts arguments by one for the verb. */
@@ -388,7 +402,26 @@ static int verb_call(int argc, char *argv[], void *userdata) {
if (r < 0)
return r;
- if (arg_method_flags & VARLINK_METHOD_ONEWAY) {
+ if (arg_collect) {
+ JsonVariant *reply = NULL;
+ const char *error = NULL;
+
+ r = varlink_collect(vl, method, jp, &reply, &error);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue %s() call: %m", method);
+ 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;
+
+ } else 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);
diff --git a/test/units/testsuite-54.sh b/test/units/testsuite-54.sh
index 8f20ac4328..7618e92c9f 100755
--- a/test/units/testsuite-54.sh
+++ b/test/units/testsuite-54.sh
@@ -348,10 +348,10 @@ fi
# Decrypt/encrypt via varlink
-echo -n '{"data":"Zm9vYmFyCg=="}' > /tmp/vlcredsdata
+echo '{"data":"Zm9vYmFyCg=="}' > /tmp/vlcredsdata
varlinkctl call /run/systemd/io.systemd.Credentials io.systemd.Credentials.Encrypt "$(cat /tmp/vlcredsdata)" | \
- varlinkctl call /run/systemd/io.systemd.Credentials io.systemd.Credentials.Decrypt > /tmp/vlcredsdata2
+ varlinkctl call --json=short /run/systemd/io.systemd.Credentials io.systemd.Credentials.Decrypt > /tmp/vlcredsdata2
cmp /tmp/vlcredsdata /tmp/vlcredsdata2
rm /tmp/vlcredsdata /tmp/vlcredsdata2
diff --git a/test/units/testsuite-70.pcrlock.sh b/test/units/testsuite-70.pcrlock.sh
index cd031b4ed6..a9c5e9cd5e 100755
--- a/test/units/testsuite-70.pcrlock.sh
+++ b/test/units/testsuite-70.pcrlock.sh
@@ -156,4 +156,20 @@ SYSTEMD_XBOOTLDR_PATH=/tmp/fakexbootldr SYSTEMD_RELAX_XBOOTLDR_CHECKS=1 "$SD_PCR
(! "$SD_PCRLOCK" lock-uki /bin/true)
(! "$SD_PCRLOCK" lock-file-system "")
+# Excercise Varlink API a bit (but first turn off condition)
+
+mkdir -p /run/systemd/system/systemd-pcrlock.socket.d
+cat > /run/systemd/system/systemd-pcrlock.socket.d/50-no-condition.conf <