1
0
mirror of https://github.com/systemd/systemd synced 2026-02-25 16:54:44 +01:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Lennart Poettering
3a7486c9fc
journalctl: add new varlink GetEntries endpoint (#40650)
journalctl: add new varlink read service to get entries

We already have some varlink support for the journal to perform
some actions like `Rotate`. It would be nice to be able to query
the journal via varlink too so this commit adds a new varlinkctl
based journal service that exposes a single GetEntries() call
to retrieve journal entries. Basic filtering is supported and
we can expand the API as needed.

This is a separate `io.systemd.JournalControl` [1] service from the
existing `io.systemd.Journald` to decouple read and write (thanks
to Lennart for suggesting this).

This also extracts some shared helper so that we do not duplicate
code when generating the json or when adding the filters.

[1] The name mirrors the bootctl->io.systemd.BootControl naming.
2026-02-22 15:14:09 +01:00
Luca Boccassi
05f5156ad1 core: validate ref_uid before checking in AttachProcesses method
ref_uid is initialized to invalid, and is only set in some
circumstances. The AttachProcesses will attempt to check it,
and assert that it is valid. Check beforehand.

Reported as YWH-PGM9780-89

Follow-up for 59857b672ca6a3a9253ef9c888172c5e68243160
2026-02-22 15:13:02 +01:00
Michael Vogt
3660f0e7da TODO: dropped todo about journalctl varlink support 2026-02-21 13:02:39 +01:00
Michael Vogt
a109189fab journalctl: add new varlink read service to get entries
We already have some varlink support for the journal to perform
some actions like `Rotate`. It would be nice to be able to query
the journal via varlink too so this commit adds a new varlinkctl
based journal service that exposes a single GetEntries() call
to retrieve journal entries. Basic filtering is supported and
we can expand the API as needed.

This is a separate `io.systemd.JournalControl` [1] service from the
existing `io.systemd.Journald` to decouple read and write (thanks
to Lennart for suggesting this).

This also extracts some shared helper so that we do not duplicate
code when generating the json or when adding the filters.

[1] The name mirrors the bootctl->io.systemd.BootControl naming.
2026-02-21 13:02:38 +01:00
26 changed files with 518 additions and 111 deletions

1
TODO
View File

@ -968,7 +968,6 @@ Features:
* Varlinkification of the following command line tools, to open them up to
other programs via IPC:
- bootctl
- journalctl (allowing journal read access via IPC)
- coredumpcl
- systemd-bless-boot
- systemd-measure

View File

@ -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']

View File

@ -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,

View File

@ -34,6 +34,7 @@
#include "strv.h"
#include "transaction.h" /* IWYU pragma: keep */
#include "unit-name.h"
#include "user-util.h"
#include "web-util.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_collect_mode, collect_mode, CollectMode);
@ -1649,6 +1650,9 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd
if (r == 0)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_ACCESS_DENIED, "Process " PID_FMT " not owned by client's UID. Refusing.", pidref->pid);
if (!uid_is_valid(u->ref_uid)) /* process_is_owned_by_uid() requires a valid uid */
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_ACCESS_DENIED, "Unit does not have a valid ref_uid, refusing to attach process " PID_FMT ".", pidref->pid);
r = process_is_owned_by_uid(pidref, u->ref_uid);
if (r < 0)
return sd_bus_error_set_errnof(reterr_error, r, "Failed to check if process " PID_FMT " is owned by target unit's UID: %m", pidref->pid);

View File

@ -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) {

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -4,6 +4,7 @@
#include <locale.h>
#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();

View File

@ -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"

View File

@ -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')

View File

@ -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),

View File

@ -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,

View File

@ -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',

View File

@ -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);

View File

@ -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;

View File

@ -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"

View File

@ -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 )

View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
# Assert when calling AttachProcesses on a unit without ref_uid set
at_exit() {
set +e
systemctl stop attach_and_barf.service
rm -f /run/systemd/system/attach_and_barf.service
systemctl daemon-reload
}
trap at_exit EXIT
mkdir -p /run/systemd/system
cat >/run/systemd/system/attach_and_barf.service <<EOF
[Service]
Type=simple
Delegate=yes
ExecStart=sleep infinity
EOF
systemctl daemon-reload
systemctl start attach_and_barf.service
run0 -u testuser \
busctl --system call \
org.freedesktop.systemd1 \
/org/freedesktop/systemd1 \
org.freedesktop.systemd1.Manager \
AttachProcessesToUnit \
"ssau" "attach_and_barf.service" "" 1 0 |& grep -F "Access denied" &>/dev/null

View File

@ -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',

View File

@ -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

View File

@ -0,0 +1,19 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Journal Log Access Service
Documentation=man:journalctl(1)
DefaultDependencies=no
Conflicts=shutdown.target
Before=shutdown.target
[Service]
ExecStart=journalctl
User=systemd-journal

View File

@ -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' },

View File

@ -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

View File

@ -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