Merge pull request #31754 from YHNdnzj/journal-fd-namespace

journal/cat: allow connecting output to specific journal namespace
This commit is contained in:
Yu Watanabe 2024-03-14 19:59:19 +09:00 committed by GitHub
commit cdafb51ab4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 133 additions and 59 deletions

View file

@ -795,7 +795,7 @@ manpages = [
'sd_journal_seek_realtime_usec',
'sd_journal_seek_tail'],
''],
['sd_journal_stream_fd', '3', [], ''],
['sd_journal_stream_fd', '3', ['sd_journal_stream_fd_with_namespace'], ''],
['sd_listen_fds',
'3',
['SD_LISTEN_FDS_START', 'sd_listen_fds_with_names'],

View file

@ -17,6 +17,7 @@
<refnamediv>
<refname>sd_journal_stream_fd</refname>
<refname>sd_journal_stream_fd_with_namespace</refname>
<refpurpose>Create log stream file descriptor to the journal</refpurpose>
</refnamediv>
@ -31,26 +32,31 @@
<paramdef>int <parameter>level_prefix</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>int <function>sd_journal_stream_fd_with_namespace</function></funcdef>
<paramdef>const char *<parameter>name_space</parameter></paramdef>
<paramdef>const char *<parameter>identifier</parameter></paramdef>
<paramdef>int <parameter>priority</parameter></paramdef>
<paramdef>int <parameter>level_prefix</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><function>sd_journal_stream_fd()</function> may be used to
create a log stream file descriptor. Log messages written to this
file descriptor as simple newline-separated text strings are
written to the journal. This file descriptor can be used
internally by applications or be made standard output or standard
error of other processes executed.</para>
<para><function>sd_journal_stream_fd()</function> may be used to create a log stream file descriptor.
Log messages written to this file descriptor as simple newline-separated text strings are written
to the journal. This file descriptor can be used internally by applications or be made standard output
or standard error of other processes executed.</para>
<para><function>sd_journal_stream_fd()</function> takes a short
program identifier string as first argument, which will be written
to the journal as SYSLOG_IDENTIFIER= field for each log entry
<para><function>sd_journal_stream_fd()</function> takes a short program identifier string as
first argument, which will be written to the journal as SYSLOG_IDENTIFIER= field for each log entry
(see
<citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for more information). The second argument shall be the default
priority level for all messages. The priority level is one of
for more information). The second argument shall be the default priority level for all messages.
The priority level is one of
<constant>LOG_EMERG</constant>, <constant>LOG_ALERT</constant>,
<constant>LOG_CRIT</constant>, <constant>LOG_ERR</constant>,
<constant>LOG_WARNING</constant>, <constant>LOG_NOTICE</constant>,
@ -63,25 +69,30 @@
<citerefentry><refentrytitle>sd-daemon</refentrytitle><manvolnum>3</manvolnum></citerefentry>
for more information.</para>
<para>It is recommended that applications log UTF-8 messages only
with this API, but this is not enforced.</para>
<para><function>sd_journal_stream_fd_with_namespace()</function> is similar to
<function>sd_journal_stream_fd()</function>, but takes an additional <parameter>name_space</parameter> parameter
that specifies which journal namespace to operate on. If specified as <constant>NULL</constant> the call
is identical to <function>sd_journal_stream_fd()</function>. For details about journal namespaces, see
<citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<para>Each invocation of <function>sd_journal_stream_fd()</function> allocates a new log stream file descriptor,
that is not shared with prior or later invocations. The file descriptor is write-only (its reading direction is
shut down), and <constant>O_NONBLOCK</constant> is turned off initially.</para>
<para>It is recommended that applications log UTF-8 messages only with this API, but this is not enforced.</para>
<para>Each invocation of these functions allocates a new log stream file descriptor,
that is not shared with prior or later invocations. The file descriptor is write-only (its reading direction
is shut down), and <constant>O_NONBLOCK</constant> is turned off initially.</para>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>The call returns a valid write-only file descriptor on
success or a negative errno-style error code.</para>
<para>The call returns a valid write-only file descriptor on success or a negative errno-style error code.</para>
</refsect1>
<refsect1>
<title>Signal safety</title>
<para><function>sd_journal_stream_fd()</function> is "async signal safe" in the meaning of <citerefentry
<para><function>sd_journal_stream_fd()</function> and <function>sd_journal_stream_fd_with_namespace()</function>
are "async signal safe" in the meaning of <citerefentry
project='man-pages'><refentrytitle>signal-safety</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
</refsect1>
@ -106,6 +117,7 @@
<refsect1>
<title>History</title>
<para><function>sd_journal_stream_fd()</function> was added in version 187.</para>
<para><function>sd_journal_stream_fd_with_namespace()</function> was added in version 256.</para>
</refsect1>
<refsect1>

View file

@ -123,6 +123,17 @@
boolean argument.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--namespace=</option></term>
<listitem><para>Specifies the journal namespace to which the standard IO should be connected.
For details about journal namespaces, see
<citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
</variablelist>
</refsect1>
@ -130,8 +141,7 @@
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code
otherwise.</para>
<para>On success, 0 is returned, a non-zero failure code otherwise.</para>
</refsect1>
<refsect1>

View file

@ -76,6 +76,10 @@ char* path_extend_internal(char **x, ...);
#define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
#define path_join(...) path_extend_internal(NULL, __VA_ARGS__, POINTER_MAX)
static inline char* skip_leading_slash(const char *p) {
return skip_leading_chars(p, "/");
}
typedef enum PathSimplifyFlags {
PATH_SIMPLIFY_KEEP_TRAILING_SLASH = 1 << 0,
} PathSimplifyFlags;

View file

@ -316,13 +316,6 @@ static int make_bad(const char *prefix, uint64_t done, const char *suffix, char
return 0;
}
static const char *skip_slash(const char *path) {
assert(path);
assert(path[0] == '/');
return path + 1;
}
static int verb_status(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL;
uint64_t left, done;
@ -370,14 +363,14 @@ static int verb_status(int argc, char *argv[], void *userdata) {
return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
}
if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) {
if (faccessat(fd, skip_leading_slash(path), F_OK, 0) >= 0) {
puts("indeterminate");
return 0;
}
if (errno != ENOENT)
return log_error_errno(errno, "Failed to check if '%s' exists: %m", path);
if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) {
if (faccessat(fd, skip_leading_slash(good), F_OK, 0) >= 0) {
puts("good");
return 0;
}
@ -385,7 +378,7 @@ static int verb_status(int argc, char *argv[], void *userdata) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to check if '%s' exists: %m", good);
if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) {
if (faccessat(fd, skip_leading_slash(bad), F_OK, 0) >= 0) {
puts("bad");
return 0;
}
@ -445,17 +438,17 @@ static int verb_set(int argc, char *argv[], void *userdata) {
if (fd < 0)
return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p);
r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target));
r = rename_noreplace(fd, skip_leading_slash(source1), fd, skip_leading_slash(target));
if (r == -EEXIST)
goto exists;
if (r == -ENOENT) {
r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target));
r = rename_noreplace(fd, skip_leading_slash(source2), fd, skip_leading_slash(target));
if (r == -EEXIST)
goto exists;
if (r == -ENOENT) {
if (faccessat(fd, skip_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */
if (faccessat(fd, skip_leading_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */
goto exists;
if (errno != ENOENT)
@ -474,7 +467,7 @@ static int verb_set(int argc, char *argv[], void *userdata) {
log_debug("Successfully renamed '%s' to '%s'.", source1, target);
/* First, fsync() the directory these files are located in */
r = fsync_parent_at(fd, skip_slash(target));
r = fsync_parent_at(fd, skip_leading_slash(target));
if (r < 0)
log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m");

View file

@ -41,6 +41,7 @@
#include "hexdecoct.h"
#include "io-util.h"
#include "iovec-util.h"
#include "journal-send.h"
#include "missing_ioprio.h"
#include "missing_prctl.h"
#include "missing_securebits.h"
@ -159,9 +160,11 @@ static int connect_journal_socket(
const char *j;
int r;
j = log_namespace ?
strjoina("/run/systemd/journal.", log_namespace, "/stdout") :
"/run/systemd/journal/stdout";
assert(fd >= 0);
j = journal_stream_path(log_namespace);
if (!j)
return -EINVAL;
if (gid_is_valid(gid)) {
oldgid = getgid();

View file

@ -24,6 +24,7 @@
#include "terminal-util.h"
static const char *arg_identifier = NULL;
static const char *arg_namespace = NULL;
static int arg_priority = LOG_INFO;
static int arg_stderr_priority = -1;
static bool arg_level_prefix = true;
@ -44,6 +45,7 @@ static int help(void) {
" -p --priority=PRIORITY Set priority value (0..7)\n"
" --stderr-priority=PRIORITY Set priority value (0..7) used for stderr\n"
" --level-prefix=BOOL Control whether level prefix shall be parsed\n"
" --namespace=NAMESPACE Connect to specified journal namespace\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@ -58,7 +60,8 @@ static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_STDERR_PRIORITY,
ARG_LEVEL_PREFIX
ARG_LEVEL_PREFIX,
ARG_NAMESPACE,
};
static const struct option options[] = {
@ -68,6 +71,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "priority", required_argument, NULL, 'p' },
{ "stderr-priority", required_argument, NULL, ARG_STDERR_PRIORITY },
{ "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX },
{ "namespace", required_argument, NULL, ARG_NAMESPACE },
{}
};
@ -117,6 +121,13 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
case ARG_NAMESPACE:
if (isempty(optarg))
arg_namespace = NULL;
else
arg_namespace = optarg;
break;
case '?':
return -EINVAL;
@ -137,12 +148,12 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
outfd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix);
outfd = sd_journal_stream_fd_with_namespace(arg_namespace, arg_identifier, arg_priority, arg_level_prefix);
if (outfd < 0)
return log_error_errno(outfd, "Failed to create stream fd: %m");
if (arg_stderr_priority >= 0 && arg_stderr_priority != arg_priority) {
errfd = sd_journal_stream_fd(arg_identifier, arg_stderr_priority, arg_level_prefix);
errfd = sd_journal_stream_fd_with_namespace(arg_namespace, arg_identifier, arg_stderr_priority, arg_level_prefix);
if (errfd < 0)
return log_error_errno(errfd, "Failed to create stream fd: %m");
}

View file

@ -839,4 +839,5 @@ LIBSYSTEMD_256 {
global:
sd_bus_creds_get_pidfd_dup;
sd_bus_creds_new_from_pidfd;
sd_journal_stream_fd_with_namespace;
} LIBSYSTEMD_255;

View file

@ -398,20 +398,28 @@ _public_ int sd_journal_perror(const char *message) {
return fill_iovec_perror_and_send(message, 0, iovec);
}
_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) {
_public_ int sd_journal_stream_fd_with_namespace(
const char *name_space,
const char *identifier,
int priority,
int level_prefix) {
_cleanup_close_ int fd = -EBADF;
char *header;
size_t l;
const char *path;
int r;
assert_return(priority >= 0, -EINVAL);
assert_return(priority <= 7, -EINVAL);
path = journal_stream_path(name_space);
if (!path)
return -EINVAL;
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
if (fd < 0)
return -errno;
r = connect_unix_path(fd, AT_FDCWD, "/run/systemd/journal/stdout");
r = connect_unix_path(fd, AT_FDCWD, path);
if (r < 0)
return r;
@ -422,6 +430,9 @@ _public_ int sd_journal_stream_fd(const char *identifier, int priority, int leve
identifier = strempty(identifier);
char *header;
size_t l;
l = strlen(identifier);
header = newa(char, l + 1 + 1 + 2 + 2 + 2 + 2 + 2);
@ -446,6 +457,10 @@ _public_ int sd_journal_stream_fd(const char *identifier, int priority, int leve
return TAKE_FD(fd);
}
_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) {
return sd_journal_stream_fd_with_namespace(NULL, identifier, priority, level_prefix);
}
_public_ int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) {
int r;
va_list ap;

View file

@ -2,6 +2,23 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include "syslog-util.h"
int journal_fd_nonblock(bool nonblock);
void close_journal_fd(void);
/* We declare sd_journal_stream_fd() as async-signal-safe. So instead of strjoin(), which calls malloc()
* internally, use a macro + alloca(). */
#define journal_stream_path(log_namespace) \
({ \
const char *_ns = (log_namespace), *_ret; \
if (!_ns) \
_ret = "/run/systemd/journal/stdout"; \
else if (log_namespace_name_valid(_ns)) \
_ret = strjoina("/run/systemd/journal.", _ns, "/stdout"); \
else \
_ret = NULL; \
_ret; \
})

View file

@ -1473,17 +1473,6 @@ static void track_file_disposition(sd_journal *j, JournalFile *f) {
j->has_persistent_files = true;
}
static const char *skip_slash(const char *p) {
if (!p)
return NULL;
while (*p == '/')
p++;
return p;
}
static int add_any_file(
sd_journal *j,
int fd,
@ -1503,7 +1492,7 @@ static int add_any_file(
/* If there's a top-level fd defined make the path relative, explicitly, since otherwise
* openat() ignores the first argument. */
fd = our_fd = openat(j->toplevel_fd, skip_slash(path), O_RDONLY|O_CLOEXEC|O_NONBLOCK);
fd = our_fd = openat(j->toplevel_fd, skip_leading_slash(path), O_RDONLY|O_CLOEXEC|O_NONBLOCK);
else
fd = our_fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
if (fd < 0) {
@ -1811,7 +1800,7 @@ static int directory_open(sd_journal *j, const char *path, DIR **ret) {
else
/* Open the specified directory relative to the toplevel fd. Enforce that the path specified is
* relative, by dropping the initial slash */
d = xopendirat(j->toplevel_fd, skip_slash(path), 0);
d = xopendirat(j->toplevel_fd, skip_leading_slash(path), 0);
if (!d)
return -errno;

View file

@ -57,6 +57,7 @@ int sd_journal_perror_with_location(const char *file, const char *line, const ch
#endif
int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix);
int sd_journal_stream_fd_with_namespace(const char *name_space, const char *identifier, int priority, int level_prefix);
/* Browse journal stream */

15
test/units/testsuite-04.cat.sh Executable file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
systemctl enable --now systemd-journald@cat-test.socket
systemd-cat --namespace cat-test env CAT_TEST_RESULT=1
timeout 30 bash -c "until systemctl -q is-active systemd-journald@cat-test.service; do sleep .5; done"
journalctl --namespace cat-test --grep "JOURNAL_STREAM="
journalctl --namespace cat-test --grep "CAT_TEST_RESULT=1"
systemctl disable --now systemd-journald@cat-test.socket

View file

@ -22,3 +22,6 @@ PassCredentials=yes
PassSecurity=yes
ReceiveBuffer=8M
SendBuffer=8M
[Install]
WantedBy=sockets.target