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 <