diff --git a/meson.build b/meson.build index b50466dcfd..1143181131 100644 --- a/meson.build +++ b/meson.build @@ -895,6 +895,7 @@ foreach option : ['adm-gid', 'video-gid', 'wheel-gid', 'systemd-journal-gid', + 'systemd-journal-uid', 'systemd-network-uid', 'systemd-resolve-uid', 'systemd-timesync-uid'] diff --git a/meson_options.txt b/meson_options.txt index c1af7ce237..16d7f69611 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -328,6 +328,8 @@ option('wheel-gid', type : 'integer', value : 0, description : 'soft-static allocation for the "wheel" group') option('systemd-journal-gid', type : 'integer', value : 0, description : 'soft-static allocation for the systemd-journal group') +option('systemd-journal-uid', type : 'integer', value : 0, + description : 'soft-static allocation for the systemd-journal user') option('systemd-network-uid', type : 'integer', value : 0, description : 'soft-static allocation for the systemd-network user') option('systemd-resolve-uid', type : 'integer', value : 0, diff --git a/src/journal/journalctl-filter.c b/src/journal/journalctl-filter.c index 54a54d105c..308f735477 100644 --- a/src/journal/journalctl-filter.c +++ b/src/journal/journalctl-filter.c @@ -9,7 +9,6 @@ #include "chase.h" #include "devnum-util.h" #include "fileio.h" -#include "glob-util.h" #include "journal-internal.h" #include "journalctl.h" #include "journalctl-filter.h" @@ -73,112 +72,17 @@ static int add_dmesg(sd_journal *j) { } static int add_units(sd_journal *j) { - _cleanup_strv_free_ char **patterns = NULL; - bool added = false; MatchUnitFlag flags = MATCH_UNIT_ALL; - int r; assert(j); - if (strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) - return 0; - /* When --directory/-D, --root, --file/-i, or --machine/-M is specified, the opened journal file may * be external, and the uid of the systemd-coredump user that generates the coredump entries may be * different from the one in the current host. Let's relax the filter condition in such cases. */ if (arg_directory || arg_root || arg_file_stdin || arg_file || arg_machine) flags &= ~MATCH_UNIT_COREDUMP_UID; - STRV_FOREACH(i, arg_system_units) { - _cleanup_free_ char *u = NULL; - - r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u); - if (r < 0) - return r; - - if (string_is_glob(u)) { - r = strv_consume(&patterns, TAKE_PTR(u)); - if (r < 0) - return r; - } else { - r = add_matches_for_unit_full(j, flags, u); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - added = true; - } - } - - if (!strv_isempty(patterns)) { - _cleanup_set_free_ Set *units = NULL; - char *u; - - r = get_possible_units(j, SYSTEM_UNITS_FULL, patterns, &units); - if (r < 0) - return r; - - SET_FOREACH(u, units) { - r = add_matches_for_unit_full(j, flags, u); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - added = true; - } - } - - patterns = strv_free(patterns); - - STRV_FOREACH(i, arg_user_units) { - _cleanup_free_ char *u = NULL; - - r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u); - if (r < 0) - return r; - - if (string_is_glob(u)) { - r = strv_consume(&patterns, TAKE_PTR(u)); - if (r < 0) - return r; - } else { - r = add_matches_for_user_unit_full(j, flags, u); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - added = true; - } - } - - if (!strv_isempty(patterns)) { - _cleanup_set_free_ Set *units = NULL; - char *u; - - r = get_possible_units(j, USER_UNITS_FULL, patterns, &units); - if (r < 0) - return r; - - SET_FOREACH(u, units) { - r = add_matches_for_user_unit_full(j, flags, u); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - added = true; - } - } - - /* Complain if the user request matches but nothing whatsoever was found, since otherwise everything - * would be matched. */ - if (!added) - return -ENODATA; - - return sd_journal_add_conjunction(j); + return journal_add_unit_matches(j, flags, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, arg_system_units, arg_user_units); } static int add_syslog_identifier(sd_journal *j) { diff --git a/src/journal/journalctl-util.c b/src/journal/journalctl-util.c index aedb2da20d..68761c83c9 100644 --- a/src/journal/journalctl-util.c +++ b/src/journal/journalctl-util.c @@ -177,6 +177,108 @@ int get_possible_units( return 0; } +int journal_add_unit_matches(sd_journal *j, MatchUnitFlag flags, UnitNameMangle mangle_flags, char **system_units, char **user_units) { + _cleanup_strv_free_ char **patterns = NULL; + bool added = false; + int r; + + assert(j); + + if (strv_isempty(system_units) && strv_isempty(user_units)) + return 0; + + STRV_FOREACH(i, system_units) { + _cleanup_free_ char *u = NULL; + + r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | mangle_flags, &u); + if (r < 0) + return r; + + if (string_is_glob(u)) { + r = strv_consume(&patterns, TAKE_PTR(u)); + if (r < 0) + return r; + } else { + r = add_matches_for_unit_full(j, flags, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + added = true; + } + } + + if (!strv_isempty(patterns)) { + _cleanup_set_free_ Set *units = NULL; + + r = get_possible_units(j, SYSTEM_UNITS_FULL, patterns, &units); + if (r < 0) + return r; + + char *u; + SET_FOREACH(u, units) { + r = add_matches_for_unit_full(j, flags, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + added = true; + } + } + + patterns = strv_free(patterns); + + STRV_FOREACH(i, user_units) { + _cleanup_free_ char *u = NULL; + + r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | mangle_flags, &u); + if (r < 0) + return r; + + if (string_is_glob(u)) { + r = strv_consume(&patterns, TAKE_PTR(u)); + if (r < 0) + return r; + } else { + r = add_matches_for_user_unit_full(j, flags, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + added = true; + } + } + + if (!strv_isempty(patterns)) { + _cleanup_set_free_ Set *units = NULL; + + r = get_possible_units(j, USER_UNITS_FULL, patterns, &units); + if (r < 0) + return r; + + char *u; + SET_FOREACH(u, units) { + r = add_matches_for_user_unit_full(j, flags, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + added = true; + } + } + + /* Complain if the user request matches but nothing whatsoever was found, since otherwise everything + * would be matched. */ + if (!added) + return -ENODATA; + + return sd_journal_add_conjunction(j); +} + int acquire_unit(sd_journal *j, const char *option_name, const char **ret_unit, LogIdType *ret_type) { size_t n; int r; diff --git a/src/journal/journalctl-util.h b/src/journal/journalctl-util.h index f237aab2b0..0b4993053a 100644 --- a/src/journal/journalctl-util.h +++ b/src/journal/journalctl-util.h @@ -3,6 +3,7 @@ #include "shared-forward.h" #include "logs-show.h" +#include "unit-name.h" /* The lists below are supposed to return the superset of unit names possibly matched by rules added with * add_matches_for_unit() and add_matches_for_user_unit(). */ @@ -31,5 +32,6 @@ int acquire_journal(sd_journal **ret); bool journal_boot_has_effect(sd_journal *j); int journal_acquire_boot(sd_journal *j); int get_possible_units(sd_journal *j, const char *fields, char * const *patterns, Set **ret); +int journal_add_unit_matches(sd_journal *j, MatchUnitFlag flags, UnitNameMangle mangle_flags, char **system_units, char **user_units); int acquire_unit(sd_journal *j, const char *option_name, const char **ret_unit, LogIdType *ret_type); int journal_acquire_invocation(sd_journal *j); diff --git a/src/journal/journalctl-varlink-server.c b/src/journal/journalctl-varlink-server.c new file mode 100644 index 0000000000..e78aa16be2 --- /dev/null +++ b/src/journal/journalctl-varlink-server.c @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-journal.h" +#include "sd-varlink.h" + +#include "journal-internal.h" +#include "journalctl-util.h" +#include "journalctl-varlink-server.h" +#include "json-util.h" +#include "log.h" +#include "logs-show.h" +#include "output-mode.h" +#include "strv.h" +#include "varlink-util.h" + +typedef struct GetEntriesParameters { + char **units; + char **user_units; + const char *namespace; + int priority; + uint64_t limit; +} GetEntriesParameters; + +static void get_entries_parameters_done(GetEntriesParameters *p) { + assert(p); + p->units = strv_free(p->units); + p->user_units = strv_free(p->user_units); +} + +int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "units", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(GetEntriesParameters, units), 0 }, + { "userUnits", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(GetEntriesParameters, user_units), 0 }, + { "namespace", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(GetEntriesParameters, namespace), 0 }, + { "priority", _SD_JSON_VARIANT_TYPE_INVALID, json_dispatch_log_level, offsetof(GetEntriesParameters, priority), 0 }, + { "limit", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(GetEntriesParameters, limit), 0 }, + {} + }; + + _cleanup_(get_entries_parameters_done) GetEntriesParameters p = { + .priority = -1, + }; + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(link); + assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + /* systemd ships with sensible defaults for the system/user services and the socket permissions so we + * do not need to do extra sd_varlink_get_peer_uid() or policykit checks here */ + r = sd_journal_open_namespace(&j, p.namespace, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); + if (r < 0) + return log_error_errno(r, "Failed to open journal: %m"); + + r = journal_add_unit_matches(j, MATCH_UNIT_ALL, /* mangle_flags= */ 0, p.units, p.user_units); + if (r == -ENODATA) + return sd_varlink_error(link, SD_VARLINK_ERROR_INVALID_PARAMETER, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add unit matches: %m"); + + if (p.priority >= 0) { + for (int i = 0; i <= p.priority; i++) { + r = journal_add_matchf(j, "PRIORITY=%d", i); + if (r < 0) + return log_error_errno(r, "Failed to add priority match: %m"); + } + + r = sd_journal_add_conjunction(j); + if (r < 0) + return r; + } + + /* this simulates "journalctl -n $p.limit" */ + r = sd_journal_seek_tail(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to tail: %m"); + + /* FIXME: this restriction should be removed eventually */ + if (p.limit > 10000) + return sd_varlink_error_invalid_parameter_name(link, "limit"); + + uint64_t n = p.limit == 0 ? 100 : p.limit; + + r = varlink_set_sentinel(link, "io.systemd.JournalAccess.NoEntries"); + if (r < 0) + return r; + + for (uint64_t i = 0; i < n; i++) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *entry = NULL; + + r = sd_journal_previous(j); + if (r < 0) + return log_error_errno(r, "Failed to iterate journal: %m"); + if (r == 0) + break; + + r = journal_entry_to_json(j, OUTPUT_SHOW_ALL, /* output_fields= */ NULL, &entry); + if (r < 0) + return r; + if (r == 0) + continue; /* skip corrupted entry */ + + r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR("entry", SD_JSON_BUILD_VARIANT(entry))); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/journal/journalctl-varlink-server.h b/src/journal/journalctl-varlink-server.h new file mode 100644 index 0000000000..45cf0e6918 --- /dev/null +++ b/src/journal/journalctl-varlink-server.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink.h" + +int vl_method_get_entries(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 95b6cf20da..82bf6b8894 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -4,6 +4,7 @@ #include #include "sd-journal.h" +#include "sd-varlink.h" #include "build.h" #include "dissect-image.h" @@ -17,6 +18,7 @@ #include "journalctl-misc.h" #include "journalctl-show.h" #include "journalctl-varlink.h" +#include "journalctl-varlink-server.h" #include "log.h" #include "loop-util.h" #include "main-func.h" @@ -35,6 +37,8 @@ #include "strv.h" #include "syslog-util.h" #include "time-util.h" +#include "varlink-io.systemd.JournalAccess.h" +#include "varlink-util.h" #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) @@ -107,6 +111,7 @@ pcre2_code *arg_compiled_pattern = NULL; PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO; ImagePolicy *arg_image_policy = NULL; bool arg_synchronize_on_exit = false; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_cursor, freep); STATIC_DESTRUCTOR_REGISTER(arg_cursor_file, freep); @@ -324,6 +329,29 @@ static int help(void) { return 0; } +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + int r; + + r = varlink_server_new(&varlink_server, /* flags= */ 0, /* userdata= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_JournalAccess); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method(varlink_server, "io.systemd.JournalAccess.GetEntries", vl_method_get_entries); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method: %m"); + + r = sd_varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -453,6 +481,15 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); + r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) { + arg_varlink = true; + arg_pager_flags |= PAGER_DISABLE; + return 1; + } + while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:INF:xrM:i:W", options, NULL)) >= 0) switch (c) { @@ -1094,6 +1131,9 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) + return vl_server(); + r = strv_copy_unless_empty(strv_skip(argv, optind), &args); if (r < 0) return log_oom(); diff --git a/src/journal/journald-varlink.c b/src/journal/journald-varlink.c index 6ea293791d..674f2524eb 100644 --- a/src/journal/journald-varlink.c +++ b/src/journal/journald-varlink.c @@ -1,7 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "sd-event.h" - #include "journald-manager.h" #include "journald-sync.h" #include "journald-varlink.h" diff --git a/src/journal/meson.build b/src/journal/meson.build index 1ad5c40dfa..5f64304219 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -41,6 +41,7 @@ journalctl_sources = files( 'journalctl-show.c', 'journalctl-util.c', 'journalctl-varlink.c', + 'journalctl-varlink-server.c', ) if get_option('link-journalctl-shared') diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 119c48936a..7fa8935af3 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -1193,16 +1193,7 @@ static int update_json_data_split( return update_json_data(h, flags, name, eq + 1, size - fieldlen - 1); } -static int output_json( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags, - const Set *output_fields, - const size_t highlight[2], - dual_timestamp *previous_display_ts, /* unused */ - sd_id128_t *previous_boot_id) { /* unused */ +int journal_entry_to_json(sd_journal *j, OutputFlags flags, const Set *output_fields, sd_json_variant **ret) { char usecbuf[CONST_MAX(DECIMAL_STR_MAX(usec_t), DECIMAL_STR_MAX(uint64_t))]; _cleanup_(sd_json_variant_unrefp) sd_json_variant *object = NULL; @@ -1217,6 +1208,7 @@ static int output_json( int r; assert(j); + assert(ret); (void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); @@ -1323,6 +1315,28 @@ static int output_json( if (r < 0) return log_error_errno(r, "Failed to allocate JSON object: %m"); + *ret = TAKE_PTR(object); + return 1; +} + +static int output_json( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + const Set *output_fields, + const size_t highlight[2], + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *object = NULL; + int r; + + r = journal_entry_to_json(j, flags, output_fields, &object); + if (r <= 0) + return r; + return sd_json_variant_dump(object, output_mode_to_json_format_flags(mode) | (FLAGS_SET(flags, OUTPUT_COLOR) ? SD_JSON_FORMAT_COLOR : 0), diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index e9f537681c..e2886d69cd 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -81,6 +81,8 @@ void json_escape( size_t l, OutputFlags flags); +int journal_entry_to_json(sd_journal *j, OutputFlags flags, const Set *output_fields, sd_json_variant **ret); + int discover_next_id( sd_journal *j, LogIdType type, diff --git a/src/shared/meson.build b/src/shared/meson.build index 5becbf3b4f..1a07b908ca 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -205,6 +205,7 @@ shared_sources = files( 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Import.c', 'varlink-io.systemd.Journal.c', + 'varlink-io.systemd.JournalAccess.c', 'varlink-io.systemd.Login.c', 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.MachineImage.c', diff --git a/src/shared/varlink-io.systemd.JournalAccess.c b/src/shared/varlink-io.systemd.JournalAccess.c new file mode 100644 index 0000000000..c9e65c1a4f --- /dev/null +++ b/src/shared/varlink-io.systemd.JournalAccess.c @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.JournalAccess.h" + +static SD_VARLINK_DEFINE_METHOD_FULL( + GetEntries, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Show messages for the specified systemd units (e.g. ['foo.service'])."), + SD_VARLINK_DEFINE_INPUT(units, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("Show messages for the specified user units (e.g. ['foo.service'])."), + SD_VARLINK_DEFINE_INPUT(userUnits, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("If specified, shows the log data of the specified namespace, otherwise the default namespace."), + SD_VARLINK_DEFINE_INPUT(namespace, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Filter output by message priorities or priority ranges (i.e. between 0/'emerg' and 7/'debug')"), + SD_VARLINK_DEFINE_INPUT(priority, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Maximum number of entries to return. Defaults to 100, capped at 10000."), + SD_VARLINK_DEFINE_INPUT(limit, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The journal entry in flat JSON format, matching journalctl --output=json."), + SD_VARLINK_DEFINE_OUTPUT(entry, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_ERROR(NoEntries); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_JournalAccess, + "io.systemd.JournalAccess", + SD_VARLINK_INTERFACE_COMMENT("Journal log read APIs"), + SD_VARLINK_SYMBOL_COMMENT("Retrieve journal log entries, optionally filtered by unit, priority, etc."), + &vl_method_GetEntries, + SD_VARLINK_SYMBOL_COMMENT("No journal entries matched the specified filters."), + &vl_error_NoEntries); diff --git a/src/shared/varlink-io.systemd.JournalAccess.h b/src/shared/varlink-io.systemd.JournalAccess.h new file mode 100644 index 0000000000..d2d27dc600 --- /dev/null +++ b/src/shared/varlink-io.systemd.JournalAccess.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_JournalAccess; diff --git a/sysusers.d/systemd-journal.conf.in b/sysusers.d/systemd-journal.conf.in index 61768b234e..bb1277f2bb 100644 --- a/sysusers.d/systemd-journal.conf.in +++ b/sysusers.d/systemd-journal.conf.in @@ -5,4 +5,5 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. -g systemd-journal {{SYSTEMD_JOURNAL_GID}} - +g systemd-journal {{SYSTEMD_JOURNAL_GID}} - +u! systemd-journal {{SYSTEMD_JOURNAL_UID}} "systemd Journal" diff --git a/test/units/TEST-04-JOURNAL.journalctl-varlink.sh b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh new file mode 100755 index 0000000000..2d7b19990c --- /dev/null +++ b/test/units/TEST-04-JOURNAL.journalctl-varlink.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +VARLINK_SOCKET="/run/systemd/io.systemd.JournalAccess" + +# ensure the varlink basics work +varlinkctl list-interfaces "$VARLINK_SOCKET" | grep io.systemd.JournalAccess +varlinkctl introspect "$VARLINK_SOCKET" | grep "method GetEntries(" + +# lets start with a basic log entry +TAG="$(systemd-id128 new)" +echo "varlink-test-message" | systemd-cat -t "$TAG" +systemd-cat -t "$TAG" -p warning echo "varlink-test-warning" +journalctl --sync + +# most basic call works +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | jq --seq . +# validate the JSON has some basic properties (similar to journalctls json output) +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | jq --seq '.entry | {MESSAGE, PRIORITY, _UID}' + +# check that default limit works (100), we don't know how many entries we have so we just check +# bounds +ENTRIES=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{}' | wc -l) +test "$ENTRIES" -gt 0 +test "$ENTRIES" -le 100 + +# check explicit limit +ENTRIES=$(varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"limit": 3}' | wc -l) +test "$ENTRIES" -le 3 + +# check unit filter: use transient units to get deterministic results +UNIT_NAME_1="test-journalctl-varlink-1-$RANDOM.service" +systemd-run --unit="$UNIT_NAME_1" --wait bash -c 'echo hello-from-varlink-test-1' +UNIT_NAME_2="test-journalctl-varlink-2-$RANDOM.service" +systemd-run --unit="$UNIT_NAME_2" --wait bash -c 'echo hello-from-varlink-test-2' +journalctl --sync + +# single unit filter +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep -q "hello-from-varlink-test-1" +(! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\"]}" | grep "hello-from-varlink-test-2") +# multi unit filter +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep -q "hello-from-varlink-test-1" +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries "{\"units\": [\"$UNIT_NAME_1\", \"$UNIT_NAME_2\"]}" | grep -q "hello-from-varlink-test-2" + +# check priority filter: priority 4 (warning) should include our warning message +varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 4, "limit": 1000}' | grep -q "varlink-test-warning" +# check priority filter: priority 3 (error) should NOT include our warning (priority 4) +(! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 3, "limit": 1000}' | grep "varlink-test-warning") + +# invalid parameter: limit over 10000 should fail +(! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"limit": 99999}') + +# invalid parameter: priority over 7 should fail +(! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"priority": 99}') + +# NoEntries error is raised if there is no result +(! varlinkctl call --more "$VARLINK_SOCKET" io.systemd.JournalAccess.GetEntries '{"units": ["nonexistent-unit-that-should-have-no-entries.service"]}' 2>&1 | grep io.systemd.JournalAccess.NoEntries ) diff --git a/units/meson.build b/units/meson.build index ebefaf87f0..b2cf9bd8f3 100644 --- a/units/meson.build +++ b/units/meson.build @@ -439,6 +439,11 @@ units = [ 'file' : 'systemd-journal-upload.service.in', 'conditions' : ['ENABLE_REMOTE', 'HAVE_LIBCURL'], }, + { 'file' : 'systemd-journalctl@.service' }, + { + 'file' : 'systemd-journalctl.socket', + 'symlinks' : ['sockets.target.wants/'], + }, { 'file' : 'systemd-journald-audit.socket' }, { 'file' : 'systemd-journald-dev-log.socket', diff --git a/units/systemd-journalctl.socket b/units/systemd-journalctl.socket new file mode 100644 index 0000000000..267b89adbb --- /dev/null +++ b/units/systemd-journalctl.socket @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Journal Log Access Socket +Documentation=man:journalctl(1) +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.JournalAccess +Symlinks=/run/varlink/registry/io.systemd.JournalAccess +FileDescriptorName=varlink +SocketGroup=systemd-journal +SocketMode=0660 +Accept=yes +MaxConnectionsPerSource=16 diff --git a/units/systemd-journalctl@.service b/units/systemd-journalctl@.service new file mode 100644 index 0000000000..84ac18cbbc --- /dev/null +++ b/units/systemd-journalctl@.service @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Journal Log Access Service +Documentation=man:journalctl(1) +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +ExecStart=journalctl +User=systemd-journal diff --git a/units/user/meson.build b/units/user/meson.build index baa30c500f..a9c6d44281 100644 --- a/units/user/meson.build +++ b/units/user/meson.build @@ -56,6 +56,11 @@ units = [ }, { 'file' : 'systemd-ask-password@.service' }, { 'file' : 'systemd-exit.service' }, + { 'file' : 'systemd-journalctl@.service' }, + { + 'file' : 'systemd-journalctl.socket', + 'symlinks' : ['sockets.target.wants/'], + }, { 'file' : 'systemd-tmpfiles-clean.service' }, { 'file' : 'systemd-tmpfiles-clean.timer' }, { 'file' : 'systemd-tmpfiles-setup.service' }, diff --git a/units/user/systemd-journalctl.socket b/units/user/systemd-journalctl.socket new file mode 100644 index 0000000000..0ea3a25c90 --- /dev/null +++ b/units/user/systemd-journalctl.socket @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Journal Log Access Socket +Documentation=man:journalctl(1) + +[Socket] +ListenStream=%t/systemd/io.systemd.JournalAccess +Symlinks=%t/varlink/registry/io.systemd.JournalAccess +FileDescriptorName=varlink +SocketMode=0600 +Accept=yes +MaxConnectionsPerSource=16 diff --git a/units/user/systemd-journalctl@.service b/units/user/systemd-journalctl@.service new file mode 100644 index 0000000000..04749fdb58 --- /dev/null +++ b/units/user/systemd-journalctl@.service @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Journal Log Access Service +Documentation=man:journalctl(1) + +[Service] +ExecStart=journalctl