-/***
- 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
#include "fileio.h"
#include "io-util.h"
#include "journald-console.h"
+#include "journald-context.h"
#include "journald-kmsg.h"
#include "journald-server.h"
#include "journald-stream.h"
#include "journald-wall.h"
#include "mkdir.h"
#include "parse-util.h"
+#include "process-util.h"
#include "selinux-util.h"
#include "socket-util.h"
#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
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;
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;
char *state_file;
+ ClientContext *context;
+
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) {
return;
if (s->server) {
+
+ if (s->context)
+ client_context_release(s->server, s->context);
+
assert(s->server->n_stdout_streams > 0);
s->server->n_stdout_streams--;
LIST_REMOVE(stdout_stream, s->server->stdout_streams, 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;
"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;
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 label_len;
+ 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;
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);
+ }
+
+ if (line_break != LINE_BREAK_NEWLINE) {
+ const char *c;
+
+ /* 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);
}
message = strappend("MESSAGE=", p);
if (message)
- IOVEC_SET_STRING(iovec[n++], message);
+ iovec[n++] = IOVEC_MAKE_STRING(message);
- label_len = s->label ? strlen(s->label) : 0;
- server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, s->label, label_len, s->unit_id, priority, 0);
+ 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;
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:
- if (isempty(p))
- s->identifier = NULL;
- else {
+ if (!isempty(p)) {
s->identifier = strdup(p);
if (!s->identifier)
return log_oom();
return 0;
case STDOUT_STREAM_UNIT_ID:
- if (s->ucred.uid == 0) {
- if (isempty(p))
- s->unit_id = NULL;
- else {
- s->unit_id = strdup(p);
- if (!s->unit_id)
- return log_oom();
- }
+ if (s->ucred.uid == 0 &&
+ unit_name_is_valid(p, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) {
+
+ s->unit_id = strdup(p);
+ if (!s->unit_id)
+ return log_oom();
}
s->state = STDOUT_STREAM_PRIORITY;
return -EINVAL;
}
- s->level_prefix = !!r;
+ s->level_prefix = r;
s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG;
return 0;
return -EINVAL;
}
- s->forward_to_syslog = !!r;
+ s->forward_to_syslog = r;
s->state = STDOUT_STREAM_FORWARD_TO_KMSG;
return 0;
return -EINVAL;
}
- s->forward_to_kmsg = !!r;
+ s->forward_to_kmsg = r;
s->state = STDOUT_STREAM_FORWARD_TO_CONSOLE;
return 0;
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 */
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");
/* 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;
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;
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;
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;
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();
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");
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) {
}
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;
}
*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);
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,
"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);
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;
}
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;
}
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)
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)
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,