]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/journal/journald-stream.c
util-lib: split out all temporary file related calls into tmpfiles-util.c
[thirdparty/systemd.git] / src / journal / journald-stream.c
index f074f0476fc7434fc5d3288fc3c29bec0819e491..7283001158d542c69f741b4075daf3a08e40bf5d 100644 (file)
@@ -1,26 +1,9 @@
-/***
-  This file is part of systemd.
-
-  Copyright 2011 Lennart Poettering
-
-  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.
-
-  systemd is distributed in the hope that it will be useful, but
-  WITHOUT ANY WARRANTY; without even the implied warranty of
-  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-  Lesser General Public License for more details.
-
-  You should have received a copy of the GNU Lesser General Public License
-  along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
+/* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include <stddef.h>
 #include <unistd.h>
 
-#ifdef HAVE_SELINUX
+#if HAVE_SELINUX
 #include <selinux/selinux.h>
 #endif
 
@@ -48,6 +31,7 @@
 #include "stdio-util.h"
 #include "string-util.h"
 #include "syslog-util.h"
+#include "tmpfile-util.h"
 #include "unit-name.h"
 
 #define STDOUT_STREAMS_MAX 4096
@@ -63,6 +47,16 @@ typedef enum StdoutStreamState {
         STDOUT_STREAM_RUNNING
 } StdoutStreamState;
 
+/* The different types of log record terminators: a real \n was read, a NUL character was read, the maximum line length
+ * was reached, or the end of the stream was reached */
+
+typedef enum LineBreak {
+        LINE_BREAK_NEWLINE,
+        LINE_BREAK_NUL,
+        LINE_BREAK_LINE_MAX,
+        LINE_BREAK_EOF,
+} LineBreak;
+
 struct StdoutStream {
         Server *server;
         StdoutStreamState state;
@@ -82,8 +76,9 @@ struct StdoutStream {
         bool fdstore:1;
         bool in_notify_queue:1;
 
-        char buffer[LINE_MAX+1];
+        char *buffer;
         size_t length;
+        size_t allocated;
 
         sd_event_source *event_source;
 
@@ -93,6 +88,8 @@ struct StdoutStream {
 
         LIST_FIELDS(StdoutStream, stdout_stream);
         LIST_FIELDS(StdoutStream, stdout_stream_notify_queue);
+
+        char id_field[STRLEN("_STREAM_ID=") + SD_ID128_STRING_MAX];
 };
 
 void stdout_stream_free(StdoutStream *s) {
@@ -122,13 +119,14 @@ void stdout_stream_free(StdoutStream *s) {
         free(s->identifier);
         free(s->unit_id);
         free(s->state_file);
+        free(s->buffer);
 
         free(s);
 }
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(StdoutStream*, stdout_stream_free);
 
-static void stdout_stream_destroy(StdoutStream *s) {
+void stdout_stream_destroy(StdoutStream *s) {
         if (!s)
                 return;
 
@@ -172,12 +170,14 @@ static int stdout_stream_save(StdoutStream *s) {
                 "LEVEL_PREFIX=%i\n"
                 "FORWARD_TO_SYSLOG=%i\n"
                 "FORWARD_TO_KMSG=%i\n"
-                "FORWARD_TO_CONSOLE=%i\n",
+                "FORWARD_TO_CONSOLE=%i\n"
+                "STREAM_ID=%s\n",
                 s->priority,
                 s->level_prefix,
                 s->forward_to_syslog,
                 s->forward_to_kmsg,
-                s->forward_to_console);
+                s->forward_to_console,
+                s->id_field + STRLEN("_STREAM_ID="));
 
         if (!isempty(s->identifier)) {
                 _cleanup_free_ char *escaped;
@@ -234,23 +234,34 @@ fail:
         return log_error_errno(r, "Failed to save stream data %s: %m", s->state_file);
 }
 
-static int stdout_stream_log(StdoutStream *s, const char *p) {
-        struct iovec iovec[N_IOVEC_META_FIELDS + 5];
+static int stdout_stream_log(StdoutStream *s, const char *p, LineBreak line_break) {
+        struct iovec *iovec;
         int priority;
         char syslog_priority[] = "PRIORITY=\0";
-        char syslog_facility[sizeof("SYSLOG_FACILITY=")-1 + DECIMAL_STR_MAX(int) + 1];
+        char syslog_facility[STRLEN("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int) + 1];
         _cleanup_free_ char *message = NULL, *syslog_identifier = NULL;
-        unsigned n = 0;
+        size_t n = 0, m;
         int r;
 
         assert(s);
         assert(p);
 
+        if (s->context)
+                (void) client_context_maybe_refresh(s->server, s->context, NULL, NULL, 0, NULL, USEC_INFINITY);
+        else if (pid_is_valid(s->ucred.pid)) {
+                r = client_context_acquire(s->server, s->ucred.pid, &s->ucred, s->label, strlen_ptr(s->label), s->unit_id, &s->context);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to acquire client context, ignoring: %m");
+        }
+
         priority = s->priority;
 
         if (s->level_prefix)
                 syslog_parse_priority(&p, &priority, false);
 
+        if (!client_context_test_priority(s->context, priority))
+                return 0;
+
         if (isempty(p))
                 return 0;
 
@@ -266,39 +277,47 @@ static int stdout_stream_log(StdoutStream *s, const char *p) {
         if (s->server->forward_to_wall)
                 server_forward_wall(s->server, priority, s->identifier, p, &s->ucred);
 
-        IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout");
+        m = N_IOVEC_META_FIELDS + 7 + client_context_extra_fields_n_iovec(s->context);
+        iovec = newa(struct iovec, m);
+
+        iovec[n++] = IOVEC_MAKE_STRING("_TRANSPORT=stdout");
+        iovec[n++] = IOVEC_MAKE_STRING(s->id_field);
 
-        syslog_priority[strlen("PRIORITY=")] = '0' + LOG_PRI(priority);
-        IOVEC_SET_STRING(iovec[n++], syslog_priority);
+        syslog_priority[STRLEN("PRIORITY=")] = '0' + LOG_PRI(priority);
+        iovec[n++] = IOVEC_MAKE_STRING(syslog_priority);
 
         if (priority & LOG_FACMASK) {
                 xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority));
-                IOVEC_SET_STRING(iovec[n++], syslog_facility);
+                iovec[n++] = IOVEC_MAKE_STRING(syslog_facility);
         }
 
         if (s->identifier) {
                 syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier);
                 if (syslog_identifier)
-                        IOVEC_SET_STRING(iovec[n++], syslog_identifier);
+                        iovec[n++] = IOVEC_MAKE_STRING(syslog_identifier);
         }
 
-        message = strappend("MESSAGE=", p);
-        if (message)
-                IOVEC_SET_STRING(iovec[n++], message);
+        if (line_break != LINE_BREAK_NEWLINE) {
+                const char *c;
 
-        if (s->context)
-                (void) client_context_maybe_refresh(s->server, s->context, NULL, NULL, 0, NULL, USEC_INFINITY);
-        else if (pid_is_valid(s->ucred.pid)) {
-                r = client_context_acquire(s->server, s->ucred.pid, &s->ucred, s->label, strlen_ptr(s->label), s->unit_id, &s->context);
-                if (r < 0)
-                        log_warning_errno(r, "Failed to acquire client context, ignoring: %m");
+                /* If this log message was generated due to an uncommon line break then mention this in the log
+                 * entry */
+
+                c =     line_break == LINE_BREAK_NUL ?      "_LINE_BREAK=nul" :
+                        line_break == LINE_BREAK_LINE_MAX ? "_LINE_BREAK=line-max" :
+                                                            "_LINE_BREAK=eof";
+                iovec[n++] = IOVEC_MAKE_STRING(c);
         }
 
-        server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), s->context, NULL, priority, 0);
+        message = strappend("MESSAGE=", p);
+        if (message)
+                iovec[n++] = IOVEC_MAKE_STRING(message);
+
+        server_dispatch_message(s->server, iovec, n, m, s->context, NULL, priority, 0);
         return 0;
 }
 
-static int stdout_stream_line(StdoutStream *s, char *p) {
+static int stdout_stream_line(StdoutStream *s, char *p, LineBreak line_break) {
         int r;
         char *orig;
 
@@ -308,6 +327,12 @@ static int stdout_stream_line(StdoutStream *s, char *p) {
         orig = p;
         p = strstrip(p);
 
+        /* line breaks by NUL, line max length or EOF are not permissible during the negotiation part of the protocol */
+        if (line_break != LINE_BREAK_NEWLINE && s->state != STDOUT_STREAM_RUNNING) {
+                log_warning("Control protocol line not properly terminated.");
+                return -EINVAL;
+        }
+
         switch (s->state) {
 
         case STDOUT_STREAM_IDENTIFIER:
@@ -349,7 +374,7 @@ static int stdout_stream_line(StdoutStream *s, char *p) {
                         return -EINVAL;
                 }
 
-                s->level_prefix = !!r;
+                s->level_prefix = r;
                 s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG;
                 return 0;
 
@@ -360,7 +385,7 @@ static int stdout_stream_line(StdoutStream *s, char *p) {
                         return -EINVAL;
                 }
 
-                s->forward_to_syslog = !!r;
+                s->forward_to_syslog = r;
                 s->state = STDOUT_STREAM_FORWARD_TO_KMSG;
                 return 0;
 
@@ -371,7 +396,7 @@ static int stdout_stream_line(StdoutStream *s, char *p) {
                         return -EINVAL;
                 }
 
-                s->forward_to_kmsg = !!r;
+                s->forward_to_kmsg = r;
                 s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE;
                 return 0;
 
@@ -382,7 +407,7 @@ static int stdout_stream_line(StdoutStream *s, char *p) {
                         return -EINVAL;
                 }
 
-                s->forward_to_console = !!r;
+                s->forward_to_console = r;
                 s->state = STDOUT_STREAM_RUNNING;
 
                 /* Try to save the stream, so that journald can be restarted and we can recover */
@@ -390,7 +415,7 @@ static int stdout_stream_line(StdoutStream *s, char *p) {
                 return 0;
 
         case STDOUT_STREAM_RUNNING:
-                return stdout_stream_log(s, orig);
+                return stdout_stream_log(s, orig, line_break);
         }
 
         assert_not_reached("Unknown stream state");
@@ -409,21 +434,31 @@ static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
         /* XXX: This function does nothing if (s->length == 0) */
 
         for (;;) {
-                char *end;
+                LineBreak line_break;
                 size_t skip;
-
-                end = memchr(p, '\n', remaining);
-                if (end)
-                        skip = end - p + 1;
-                else if (remaining >= sizeof(s->buffer) - 1) {
-                        end = p + sizeof(s->buffer) - 1;
+                char *end1, *end2;
+
+                end1 = memchr(p, '\n', remaining);
+                end2 = memchr(p, 0, end1 ? (size_t) (end1 - p) : remaining);
+
+                if (end2) {
+                        /* We found a NUL terminator */
+                        skip = end2 - p + 1;
+                        line_break = LINE_BREAK_NUL;
+                } else if (end1) {
+                        /* We found a \n terminator */
+                        *end1 = 0;
+                        skip = end1 - p + 1;
+                        line_break = LINE_BREAK_NEWLINE;
+                } else if (remaining >= s->server->line_max) {
+                        /* Force a line break after the maximum line length */
+                        *(p + s->server->line_max) = 0;
                         skip = remaining;
+                        line_break = LINE_BREAK_LINE_MAX;
                 } else
                         break;
 
-                *end = 0;
-
-                r = stdout_stream_line(s, p);
+                r = stdout_stream_line(s, p, line_break);
                 if (r < 0)
                         return r;
 
@@ -433,7 +468,7 @@ static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
 
         if (force_flush && remaining > 0) {
                 p[remaining] = 0;
-                r = stdout_stream_line(s, p);
+                r = stdout_stream_line(s, p, LINE_BREAK_EOF);
                 if (r < 0)
                         return r;
 
@@ -451,6 +486,7 @@ static int stdout_stream_scan(StdoutStream *s, bool force_flush) {
 
 static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
         StdoutStream *s = userdata;
+        size_t limit;
         ssize_t l;
         int r;
 
@@ -461,9 +497,20 @@ static int stdout_stream_process(sd_event_source *es, int fd, uint32_t revents,
                 goto terminate;
         }
 
-        l = read(s->fd, s->buffer+s->length, sizeof(s->buffer)-1-s->length);
-        if (l < 0) {
+        /* If the buffer is full already (discounting the extra NUL we need), add room for another 1K */
+        if (s->length + 1 >= s->allocated) {
+                if (!GREEDY_REALLOC(s->buffer, s->allocated, s->length + 1 + 1024)) {
+                        log_oom();
+                        goto terminate;
+                }
+        }
+
+        /* Try to make use of the allocated buffer in full, but never read more than the configured line size. Also,
+         * always leave room for a terminating NUL we might need to add. */
+        limit = MIN(s->allocated - 1, s->server->line_max);
 
+        l = read(s->fd, s->buffer + s->length, limit - s->length);
+        if (l < 0) {
                 if (errno == EAGAIN)
                         return 0;
 
@@ -488,13 +535,18 @@ terminate:
         return 0;
 }
 
-static int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
+int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
         _cleanup_(stdout_stream_freep) StdoutStream *stream = NULL;
+        sd_id128_t id;
         int r;
 
         assert(s);
         assert(fd >= 0);
 
+        r = sd_id128_randomize(&id);
+        if (r < 0)
+                return log_error_errno(r, "Failed to generate stream ID: %m");
+
         stream = new0(StdoutStream, 1);
         if (!stream)
                 return log_oom();
@@ -502,6 +554,8 @@ static int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
         stream->fd = -1;
         stream->priority = LOG_INFO;
 
+        xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
+
         r = getpeercred(fd, &stream->ucred);
         if (r < 0)
                 return log_error_errno(r, "Failed to determine peer credentials: %m");
@@ -543,10 +597,10 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent
 
         assert(s);
 
-        if (revents != EPOLLIN) {
-                log_error("Got invalid event from epoll for stdout server fd: %"PRIx32, revents);
-                return -EIO;
-        }
+        if (revents != EPOLLIN)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO),
+                                       "Got invalid event from epoll for stdout server fd: %" PRIx32,
+                                       revents);
 
         fd = accept4(s->stdout_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
         if (fd < 0) {
@@ -557,7 +611,17 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent
         }
 
         if (s->n_stdout_streams >= STDOUT_STREAMS_MAX) {
-                log_warning("Too many stdout streams, refusing connection.");
+                struct ucred u;
+
+                r = getpeercred(fd, &u);
+
+                /* By closing fd here we make sure that the client won't wait too long for journald to
+                 * gather all the data it adds to the error message to find out that the connection has
+                 * just been refused.
+                 */
+                fd = safe_close(fd);
+
+                server_driver_message(s, r < 0 ? 0 : u.pid, NULL, LOG_MESSAGE("Too many stdout streams, refusing connection."), NULL);
                 return 0;
         }
 
@@ -575,7 +639,8 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
                 *level_prefix = NULL,
                 *forward_to_syslog = NULL,
                 *forward_to_kmsg = NULL,
-                *forward_to_console = NULL;
+                *forward_to_console = NULL,
+                *stream_id = NULL;
         int r;
 
         assert(stream);
@@ -587,7 +652,7 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
                         return log_oom();
         }
 
-        r = parse_env_file(stream->state_file, NEWLINE,
+        r = parse_env_file(NULL, stream->state_file,
                            "PRIORITY", &priority,
                            "LEVEL_PREFIX", &level_prefix,
                            "FORWARD_TO_SYSLOG", &forward_to_syslog,
@@ -595,7 +660,7 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
                            "FORWARD_TO_CONSOLE", &forward_to_console,
                            "IDENTIFIER", &stream->identifier,
                            "UNIT", &stream->unit_id,
-                           NULL);
+                           "STREAM_ID", &stream_id);
         if (r < 0)
                 return log_error_errno(r, "Failed to read: %s", stream->state_file);
 
@@ -631,6 +696,14 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
                         stream->forward_to_console = r;
         }
 
+        if (stream_id) {
+                sd_id128_t id;
+
+                r = sd_id128_from_string(stream_id, &id);
+                if (r >= 0)
+                        xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
+        }
+
         return 0;
 }
 
@@ -697,7 +770,9 @@ int server_restore_streams(Server *s, FDSet *fds) {
                 if (!found) {
                         /* No file descriptor? Then let's delete the state file */
                         log_debug("Cannot restore stream file %s", de->d_name);
-                        unlinkat(dirfd(d), de->d_name, 0);
+                        if (unlinkat(dirfd(d), de->d_name, 0) < 0)
+                                log_warning_errno(errno, "Failed to remove /run/systemd/journal/streams/%s: %m",
+                                                  de->d_name);
                         continue;
                 }
 
@@ -728,7 +803,7 @@ int server_open_stdout_socket(Server *s) {
                 if (s->stdout_fd < 0)
                         return log_error_errno(errno, "socket() failed: %m");
 
-                (void) unlink(sa.un.sun_path);
+                (void) sockaddr_un_unlink(&sa.un);
 
                 r = bind(s->stdout_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
                 if (r < 0)
@@ -739,7 +814,7 @@ int server_open_stdout_socket(Server *s) {
                 if (listen(s->stdout_fd, SOMAXCONN) < 0)
                         return log_error_errno(errno, "listen(%s) failed: %m", sa.un.sun_path);
         } else
-                fd_nonblock(s->stdout_fd, 1);
+                (void) fd_nonblock(s->stdout_fd, true);
 
         r = sd_event_add_io(s->event, &s->stdout_event_source, s->stdout_fd, EPOLLIN, stdout_stream_new, s);
         if (r < 0)
@@ -755,7 +830,7 @@ int server_open_stdout_socket(Server *s) {
 void stdout_stream_send_notify(StdoutStream *s) {
         struct iovec iovec = {
                 .iov_base = (char*) "FDSTORE=1",
-                .iov_len = strlen("FDSTORE=1"),
+                .iov_len = STRLEN("FDSTORE=1"),
         };
         struct msghdr msghdr = {
                 .msg_iov = &iovec,