diff --git a/man/rules/meson.build b/man/rules/meson.build
index 1f07e606c9..465ea4e4b3 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -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'],
diff --git a/man/sd_journal_stream_fd.xml b/man/sd_journal_stream_fd.xml
index 13939ff19e..ff0d2eedd9 100644
--- a/man/sd_journal_stream_fd.xml
+++ b/man/sd_journal_stream_fd.xml
@@ -17,6 +17,7 @@
sd_journal_stream_fd
+ sd_journal_stream_fd_with_namespace
Create log stream file descriptor to the journal
@@ -31,26 +32,31 @@
int level_prefix
+
+ int sd_journal_stream_fd_with_namespace
+ const char *name_space
+ const char *identifier
+ int priority
+ int level_prefix
+
+
Description
- sd_journal_stream_fd() 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.
+ sd_journal_stream_fd() 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.
- sd_journal_stream_fd() takes a short
- program identifier string as first argument, which will be written
- to the journal as SYSLOG_IDENTIFIER= field for each log entry
+ sd_journal_stream_fd() 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
systemd.journal-fields7
- 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
LOG_EMERG, LOG_ALERT,
LOG_CRIT, LOG_ERR,
LOG_WARNING, LOG_NOTICE,
@@ -63,25 +69,30 @@
sd-daemon3
for more information.
- It is recommended that applications log UTF-8 messages only
- with this API, but this is not enforced.
+ sd_journal_stream_fd_with_namespace() is similar to
+ sd_journal_stream_fd(), but takes an additional name_space parameter
+ that specifies which journal namespace to operate on. If specified as NULL the call
+ is identical to sd_journal_stream_fd(). For details about journal namespaces, see
+ systemd-journald.service8.
- Each invocation of sd_journal_stream_fd() 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 O_NONBLOCK is turned off initially.
+ It is recommended that applications log UTF-8 messages only with this API, but this is not enforced.
+
+ 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 O_NONBLOCK is turned off initially.
Return Value
- The call returns a valid write-only file descriptor on
- success or a negative errno-style error code.
+ The call returns a valid write-only file descriptor on success or a negative errno-style error code.
Signal safety
- sd_journal_stream_fd() is "async signal safe" in the meaning of sd_journal_stream_fd() and sd_journal_stream_fd_with_namespace()
+ are "async signal safe" in the meaning of signal-safety7.
@@ -106,6 +117,7 @@
History
sd_journal_stream_fd() was added in version 187.
+ sd_journal_stream_fd_with_namespace() was added in version 256.
diff --git a/man/systemd-cat.xml b/man/systemd-cat.xml
index 040da7c5c2..280492dea5 100644
--- a/man/systemd-cat.xml
+++ b/man/systemd-cat.xml
@@ -123,6 +123,17 @@
boolean argument.
+
+
+
+ Specifies the journal namespace to which the standard IO should be connected.
+ For details about journal namespaces, see
+ systemd-journald.service8.
+
+
+
+
+
@@ -130,8 +141,7 @@
Exit status
- On success, 0 is returned, a non-zero failure code
- otherwise.
+ On success, 0 is returned, a non-zero failure code otherwise.
diff --git a/src/basic/path-util.h b/src/basic/path-util.h
index 052bbd7031..47699e6414 100644
--- a/src/basic/path-util.h
+++ b/src/basic/path-util.h
@@ -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;
diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c
index 0c0b4f23c7..07e4e306a3 100644
--- a/src/boot/bless-boot.c
+++ b/src/boot/bless-boot.c
@@ -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");
diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c
index 0db13bfa88..a1401f3e49 100644
--- a/src/core/exec-invoke.c
+++ b/src/core/exec-invoke.c
@@ -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();
diff --git a/src/journal/cat.c b/src/journal/cat.c
index 0325add12f..1634f30b44 100644
--- a/src/journal/cat.c
+++ b/src/journal/cat.c
@@ -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");
}
diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym
index 22cf48c5f8..d23da4c151 100644
--- a/src/libsystemd/libsystemd.sym
+++ b/src/libsystemd/libsystemd.sym
@@ -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;
diff --git a/src/libsystemd/sd-journal/journal-send.c b/src/libsystemd/sd-journal/journal-send.c
index be23b2fe75..650581addf 100644
--- a/src/libsystemd/sd-journal/journal-send.c
+++ b/src/libsystemd/sd-journal/journal-send.c
@@ -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;
diff --git a/src/libsystemd/sd-journal/journal-send.h b/src/libsystemd/sd-journal/journal-send.h
index 24315e249b..6fe6325090 100644
--- a/src/libsystemd/sd-journal/journal-send.h
+++ b/src/libsystemd/sd-journal/journal-send.h
@@ -2,6 +2,23 @@
#pragma once
#include
+#include
+
+#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; \
+ })
diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c
index fba436fbe3..ef57f156ac 100644
--- a/src/libsystemd/sd-journal/sd-journal.c
+++ b/src/libsystemd/sd-journal/sd-journal.c
@@ -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;
diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h
index e4a67f048b..7434051ce1 100644
--- a/src/systemd/sd-journal.h
+++ b/src/systemd/sd-journal.h
@@ -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 */
diff --git a/test/units/testsuite-04.cat.sh b/test/units/testsuite-04.cat.sh
new file mode 100755
index 0000000000..bef3e189a6
--- /dev/null
+++ b/test/units/testsuite-04.cat.sh
@@ -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
diff --git a/units/systemd-journald@.socket b/units/systemd-journald@.socket
index 60c025fcc3..96b7ae05f9 100644
--- a/units/systemd-journald@.socket
+++ b/units/systemd-journald@.socket
@@ -22,3 +22,6 @@ PassCredentials=yes
PassSecurity=yes
ReceiveBuffer=8M
SendBuffer=8M
+
+[Install]
+WantedBy=sockets.target