X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fjournal%2Fjournal-send.c;h=0ee83cdb778fc75421049697b6864c6f6d64be12;hp=db5dbc0711c7c9a06d0b8f158529064178292ea2;hb=73843b52585d42cc1a970a1c664818ece6942e9e;hpb=a5344d2c3b0f14e954ce1c0ef905c5b44bc5bf0a diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c index db5dbc071..0ee83cdb7 100644 --- a/src/journal/journal-send.c +++ b/src/journal/journal-send.c @@ -6,16 +6,16 @@ Copyright 2011 Lennart Poettering systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or + 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 - General Public License for more details. + Lesser General Public License for more details. - You should have received a copy of the GNU General Public License + You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ @@ -23,10 +23,29 @@ #include #include #include +#include +#include +#include + +#define SD_JOURNAL_SUPPRESS_LOCATION #include "sd-journal.h" #include "util.h" #include "socket-util.h" +#include "memfd.h" + +#define SNDBUF_SIZE (8*1024*1024) + +#define ALLOCA_CODE_FUNC(f, func) \ + do { \ + size_t _fl; \ + const char *_func = (func); \ + char **_f = &(f); \ + _fl = strlen(_func) + 1; \ + *_f = alloca(_fl + 10); \ + memcpy(*_f, "CODE_FUNC=", 10); \ + memcpy(*_f + 10, _func, _fl); \ + } while(false) /* We open a single fd, and we'll share it with the current process, * all its threads, and all its subprocesses. This means we need to @@ -45,8 +64,10 @@ retry: if (fd < 0) return -errno; + fd_inc_sndbuf(fd, SNDBUF_SIZE); + if (!__sync_bool_compare_and_swap(&fd_plus_one, 0, fd+1)) { - close_nointr_nofail(fd); + safe_close(fd); goto retry; } @@ -65,14 +86,15 @@ _public_ int sd_journal_print(int priority, const char *format, ...) { } _public_ int sd_journal_printv(int priority, const char *format, va_list ap) { - char buffer[8 + LINE_MAX], p[11]; - struct iovec iov[2]; - if (priority < 0 || priority > 7) - return -EINVAL; + /* FIXME: Instead of limiting things to LINE_MAX we could do a + C99 variable-length array on the stack here in a loop. */ - if (!format) - return -EINVAL; + char buffer[8 + LINE_MAX], p[11]; struct iovec iov[2]; + + assert_return(priority >= 0, -EINVAL); + assert_return(priority <= 7, -EINVAL); + assert_return(format, -EINVAL); snprintf(p, sizeof(p), "PRIORITY=%i", priority & LOG_PRIMASK); char_array_0(p); @@ -88,15 +110,28 @@ _public_ int sd_journal_printv(int priority, const char *format, va_list ap) { return sd_journal_sendv(iov, 2); } -_public_ int sd_journal_send(const char *format, ...) { +_printf_(1, 0) static int fill_iovec_sprintf(const char *format, va_list ap, int extra, struct iovec **_iov) { + PROTECT_ERRNO; int r, n = 0, i = 0, j; - va_list ap; struct iovec *iov = NULL; - va_start(ap, format); + assert(_iov); + + if (extra > 0) { + n = MAX(extra * 2, extra + 4); + iov = malloc0(n * sizeof(struct iovec)); + if (!iov) { + r = -ENOMEM; + goto fail; + } + + i = extra; + } + while (format) { struct iovec *c; char *buffer; + va_list aq; if (i >= n) { n = MAX(i*2, 4); @@ -109,20 +144,51 @@ _public_ int sd_journal_send(const char *format, ...) { iov = c; } - if (vasprintf(&buffer, format, ap) < 0) { + va_copy(aq, ap); + if (vasprintf(&buffer, format, aq) < 0) { + va_end(aq); r = -ENOMEM; goto fail; } + va_end(aq); + + VA_FORMAT_ADVANCE(format, ap); IOVEC_SET_STRING(iov[i++], buffer); format = va_arg(ap, char *); } + + *_iov = iov; + + return i; + +fail: + for (j = 0; j < i; j++) + free(iov[j].iov_base); + + free(iov); + + return r; +} + +_public_ int sd_journal_send(const char *format, ...) { + int r, i, j; + va_list ap; + struct iovec *iov = NULL; + + va_start(ap, format); + i = fill_iovec_sprintf(format, ap, 0, &iov); va_end(ap); + if (_unlikely_(i < 0)) { + r = i; + goto finish; + } + r = sd_journal_sendv(iov, i); -fail: +finish: for (j = 0; j < i; j++) free(iov[j].iov_base); @@ -132,33 +198,52 @@ fail: } _public_ int sd_journal_sendv(const struct iovec *iov, int n) { - int fd; + PROTECT_ERRNO; + int fd, r; + _cleanup_close_ int buffer_fd = -1; struct iovec *w; uint64_t *l; int i, j = 0; - struct msghdr mh; - struct sockaddr_un sa; - - if (!iov || n <= 0) - return -EINVAL; - - w = alloca(sizeof(struct iovec) * n * 5); + struct sockaddr_un sa = { + .sun_family = AF_UNIX, + .sun_path = "/run/systemd/journal/socket", + }; + struct msghdr mh = { + .msg_name = &sa, + .msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path), + }; + ssize_t k; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control; + struct cmsghdr *cmsg; + bool have_syslog_identifier = false; + bool seal = true; + + assert_return(iov, -EINVAL); + assert_return(n > 0, -EINVAL); + + w = alloca(sizeof(struct iovec) * n * 5 + 3); l = alloca(sizeof(uint64_t) * n); for (i = 0; i < n; i++) { char *c, *nl; - if (!iov[i].iov_base || - iov[i].iov_len <= 1) + if (_unlikely_(!iov[i].iov_base || iov[i].iov_len <= 1)) return -EINVAL; c = memchr(iov[i].iov_base, '=', iov[i].iov_len); - if (!c || c == iov[i].iov_base) + if (_unlikely_(!c || c == iov[i].iov_base)) return -EINVAL; + have_syslog_identifier = have_syslog_identifier || + (c == (char *) iov[i].iov_base + 17 && + startswith(iov[i].iov_base, "SYSLOG_IDENTIFIER")); + nl = memchr(iov[i].iov_base, '\n', iov[i].iov_len); if (nl) { - if (nl < c) + if (_unlikely_(nl < c)) return -EINVAL; /* Already includes a newline? Bummer, then @@ -189,75 +274,307 @@ _public_ int sd_journal_sendv(const struct iovec *iov, int n) { IOVEC_SET_STRING(w[j++], "\n"); } + if (!have_syslog_identifier && + string_is_safe(program_invocation_short_name)) { + + /* Implicitly add program_invocation_short_name, if it + * is not set explicitly. We only do this for + * program_invocation_short_name, and nothing else + * since everything else is much nicer to retrieve + * from the outside. */ + + IOVEC_SET_STRING(w[j++], "SYSLOG_IDENTIFIER="); + IOVEC_SET_STRING(w[j++], program_invocation_short_name); + IOVEC_SET_STRING(w[j++], "\n"); + } + fd = journal_fd(); - if (fd < 0) + if (_unlikely_(fd < 0)) return fd; - zero(sa); - sa.sun_family = AF_UNIX; - strncpy(sa.sun_path,"/run/systemd/journal", sizeof(sa.sun_path)); - - zero(mh); - mh.msg_name = &sa; - mh.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(sa.sun_path); mh.msg_iov = w; mh.msg_iovlen = j; - if (sendmsg(fd, &mh, MSG_NOSIGNAL) < 0) + k = sendmsg(fd, &mh, MSG_NOSIGNAL); + if (k >= 0) + return 0; + + /* Fail silently if the journal is not available */ + if (errno == ENOENT) + return 0; + + if (errno != EMSGSIZE && errno != ENOBUFS) + return -errno; + + /* Message doesn't fit... Let's dump the data in a memfd or + * temporary file and just pass a file descriptor of it to the + * other side. + * + * For the temporary files we use /dev/shm instead of /tmp + * here, since we want this to be a tmpfs, and one that is + * available from early boot on and where unprivileged users + * can create files. */ + buffer_fd = memfd_new(NULL); + if (buffer_fd < 0) { + if (buffer_fd == -ENOSYS) { + buffer_fd = open_tmpfile("/dev/shm", O_RDWR | O_CLOEXEC); + if (buffer_fd < 0) + return buffer_fd; + + seal = false; + } else + return buffer_fd; + } + + n = writev(buffer_fd, w, j); + if (n < 0) + return -errno; + + if (seal) { + r = memfd_set_sealed(buffer_fd); + if (r < 0) + return r; + } + + mh.msg_iov = NULL; + mh.msg_iovlen = 0; + + zero(control); + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &buffer_fd, sizeof(int)); + + mh.msg_controllen = cmsg->cmsg_len; + + k = sendmsg(fd, &mh, MSG_NOSIGNAL); + if (k < 0) return -errno; return 0; } -_public_ int sd_journal_stream_fd(const char *tag, int priority, int priority_prefix) { - union sockaddr_union sa; - int fd; +static int fill_iovec_perror_and_send(const char *message, int skip, struct iovec iov[]) { + PROTECT_ERRNO; + size_t n, k; + + k = isempty(message) ? 0 : strlen(message) + 2; + n = 8 + k + 256 + 1; + + for (;;) { + char buffer[n]; + char* j; + + errno = 0; + j = strerror_r(_saved_errno_, buffer + 8 + k, n - 8 - k); + if (errno == 0) { + char error[6 + 10 + 1]; /* for a 32bit value */ + + if (j != buffer + 8 + k) + memmove(buffer + 8 + k, j, strlen(j)+1); + + memcpy(buffer, "MESSAGE=", 8); + + if (k > 0) { + memcpy(buffer + 8, message, k - 2); + memcpy(buffer + 8 + k - 2, ": ", 2); + } + + snprintf(error, sizeof(error), "ERRNO=%u", _saved_errno_); + char_array_0(error); + + IOVEC_SET_STRING(iov[skip+0], "PRIORITY=3"); + IOVEC_SET_STRING(iov[skip+1], buffer); + IOVEC_SET_STRING(iov[skip+2], error); + + return sd_journal_sendv(iov, skip + 3); + } + + if (errno != ERANGE) + return -errno; + + n *= 2; + } +} + +_public_ int sd_journal_perror(const char *message) { + struct iovec iovec[3]; + + return fill_iovec_perror_and_send(message, 0, iovec); +} + +_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/stdout", + }; + _cleanup_close_ int fd = -1; char *header; size_t l; - ssize_t r; + int r; - if (priority < 0 || priority > 7) - return -EINVAL; + assert_return(priority >= 0, -EINVAL); + assert_return(priority <= 7, -EINVAL); fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); if (fd < 0) return -errno; - zero(sa); - sa.un.sun_family = AF_UNIX; - strncpy(sa.un.sun_path, "/run/systemd/stdout", sizeof(sa.un.sun_path)); - r = connect(fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)); - if (r < 0) { - close_nointr_nofail(fd); + if (r < 0) return -errno; - } - if (!tag) - tag = ""; + if (shutdown(fd, SHUT_RD) < 0) + return -errno; + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + if (!identifier) + identifier = ""; - l = strlen(tag); - header = alloca(l + 1 + 2 + 2 + 2); + l = strlen(identifier); + header = alloca(l + 1 + 1 + 2 + 2 + 2 + 2 + 2); - memcpy(header, tag, l); + memcpy(header, identifier, l); header[l++] = '\n'; + header[l++] = '\n'; /* unit id */ header[l++] = '0' + priority; header[l++] = '\n'; - header[l++] = '0' + !!priority_prefix; + header[l++] = '0' + !!level_prefix; + header[l++] = '\n'; + header[l++] = '0'; + header[l++] = '\n'; + header[l++] = '0'; header[l++] = '\n'; header[l++] = '0'; header[l++] = '\n'; - r = loop_write(fd, header, l, false); - if (r < 0) { - close_nointr_nofail(fd); - return (int) r; - } + r = (int) loop_write(fd, header, l, false); + if (r < 0) + return r; - if ((size_t) r != l) { - close_nointr_nofail(fd); + if ((size_t) r != l) return -errno; + + r = fd; + fd = -1; + return r; +} + +_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; + + va_start(ap, format); + r = sd_journal_printv_with_location(priority, file, line, func, format, ap); + va_end(ap); + + return r; +} + +_public_ int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) { + char buffer[8 + LINE_MAX], p[11]; + struct iovec iov[5]; + char *f; + + assert_return(priority >= 0, -EINVAL); + assert_return(priority <= 7, -EINVAL); + assert_return(format, -EINVAL); + + snprintf(p, sizeof(p), "PRIORITY=%i", priority & LOG_PRIMASK); + char_array_0(p); + + memcpy(buffer, "MESSAGE=", 8); + vsnprintf(buffer+8, sizeof(buffer) - 8, format, ap); + char_array_0(buffer); + + /* func is initialized from __func__ which is not a macro, but + * a static const char[], hence cannot easily be prefixed with + * CODE_FUNC=, hence let's do it manually here. */ + ALLOCA_CODE_FUNC(f, func); + + zero(iov); + IOVEC_SET_STRING(iov[0], buffer); + IOVEC_SET_STRING(iov[1], p); + IOVEC_SET_STRING(iov[2], file); + IOVEC_SET_STRING(iov[3], line); + IOVEC_SET_STRING(iov[4], f); + + return sd_journal_sendv(iov, ELEMENTSOF(iov)); +} + +_public_ int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) { + int r, i, j; + va_list ap; + struct iovec *iov = NULL; + char *f; + + va_start(ap, format); + i = fill_iovec_sprintf(format, ap, 3, &iov); + va_end(ap); + + if (_unlikely_(i < 0)) { + r = i; + goto finish; } - return fd; + ALLOCA_CODE_FUNC(f, func); + + IOVEC_SET_STRING(iov[0], file); + IOVEC_SET_STRING(iov[1], line); + IOVEC_SET_STRING(iov[2], f); + + r = sd_journal_sendv(iov, i); + +finish: + for (j = 3; j < i; j++) + free(iov[j].iov_base); + + free(iov); + + return r; +} + +_public_ int sd_journal_sendv_with_location( + const char *file, const char *line, + const char *func, + const struct iovec *iov, int n) { + + struct iovec *niov; + char *f; + + assert_return(iov, -EINVAL); + assert_return(n > 0, -EINVAL); + + niov = alloca(sizeof(struct iovec) * (n + 3)); + memcpy(niov, iov, sizeof(struct iovec) * n); + + ALLOCA_CODE_FUNC(f, func); + + IOVEC_SET_STRING(niov[n++], file); + IOVEC_SET_STRING(niov[n++], line); + IOVEC_SET_STRING(niov[n++], f); + + return sd_journal_sendv(niov, n); +} + +_public_ int sd_journal_perror_with_location( + const char *file, const char *line, + const char *func, + const char *message) { + + struct iovec iov[6]; + char *f; + + ALLOCA_CODE_FUNC(f, func); + + IOVEC_SET_STRING(iov[0], file); + IOVEC_SET_STRING(iov[1], line); + IOVEC_SET_STRING(iov[2], f); + + return fill_iovec_perror_and_send(message, 3, iov); }