]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #31754 from YHNdnzj/journal-fd-namespace
authorYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 14 Mar 2024 10:59:19 +0000 (19:59 +0900)
committerGitHub <noreply@github.com>
Thu, 14 Mar 2024 10:59:19 +0000 (19:59 +0900)
journal/cat: allow connecting output to specific journal namespace

14 files changed:
man/rules/meson.build
man/sd_journal_stream_fd.xml
man/systemd-cat.xml
src/basic/path-util.h
src/boot/bless-boot.c
src/core/exec-invoke.c
src/journal/cat.c
src/libsystemd/libsystemd.sym
src/libsystemd/sd-journal/journal-send.c
src/libsystemd/sd-journal/journal-send.h
src/libsystemd/sd-journal/sd-journal.c
src/systemd/sd-journal.h
test/units/testsuite-04.cat.sh [new file with mode: 0755]
units/systemd-journald@.socket

index 1f07e606c9689f75f6ac1bf16f4ac9771132f099..465ea4e4b3ece15cf278a337f50607efd8e620b1 100644 (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'],
index 13939ff19e4e5dadfda32e38c61f047d196d77ec..ff0d2eedd939c6c014f4474098bb2761bcc6c286 100644 (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>
 
         <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>,
     <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>It is recommended that applications log UTF-8 messages only with this API, but this is not enforced.</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>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>
   <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>
index 040da7c5c237c3629ab6865b2e9311ca89f0d68b..280492dea592bd7d349c66e6c22224b81a51311d 100644 (file)
         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>
   <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>
index 052bbd7031b0471295abe083a74a734fab8b27d3..47699e6414210e7efb2f9464068019664f5beda3 100644 (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;
index 0c0b4f23c7709a170f0e315655180e8f39f55df4..07e4e306a3ea83fc269a66ee1e295ffa4b374ef2 100644 (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");
 
index 0db13bfa8807d3c7c960ff393762aa3e4dcfccd2..a1401f3e4903a9586778cffadfacb0f1472c5e87 100644 (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();
index 0325add12f4d7f01d3af712b14e1fcb37c2edfad..1634f30b44e1958940ed364f31384ba354624397 100644 (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");
         }
index 22cf48c5f8db7b05c3479edf595d243a360b9a46..d23da4c1515c84cd4e54fea36a117c3478d1f9ad 100644 (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;
index be23b2fe75d631f03f032f984ba2f5eb0e020049..650581addf41bee70a18c2c0394180b60393ba95 100644 (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;
index 24315e249b32d9d0f7a88189d487dd98ac0d0305..6fe6325090ec768c08c58af37babfbe1aa33a152 100644 (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;                                                                   \
+        })
index fba436fbe3f0e1afadb3b86f45c2d3cb7c371989..ef57f156ac5910378b877302106b8b9356416de7 100644 (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;
 
index e4a67f048b8524111d3e1274d96a81844b8d8903..7434051ce1fb92a1d8b7aea760d50a63d3c0d5b3 100644 (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 */
 
diff --git a/test/units/testsuite-04.cat.sh b/test/units/testsuite-04.cat.sh
new file mode 100755 (executable)
index 0000000..bef3e18
--- /dev/null
@@ -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
index 60c025fcc3b90d27f8ea5ec0e466ee80bd54e838..96b7ae05f91b574d4ddfb0ddb5862b4069e40829 100644 (file)
@@ -22,3 +22,6 @@ PassCredentials=yes
 PassSecurity=yes
 ReceiveBuffer=8M
 SendBuffer=8M
+
+[Install]
+WantedBy=sockets.target