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