chiark / gitweb /
logger: when passing on PID info, fall back to our own if originating process is...
[elogind.git] / src / logger.c
index 48eee6cd12958bcc5617b2cf69abc0480a7fe6ea..482ec41244502298a1b12b877367a00b37a77ece 100644 (file)
@@ -1,4 +1,4 @@
-/*-*- Mode: C; c-basic-offset: 8 -*-*/
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
 
 /***
   This file is part of systemd.
@@ -36,8 +36,8 @@
 #include "log.h"
 #include "list.h"
 #include "sd-daemon.h"
+#include "tcpwrap.h"
 
-#define STREAM_BUFFER 2048
 #define STREAMS_MAX 256
 #define SERVER_FD_MAX 16
 #define TIMEOUT ((int) (10*MSEC_PER_SEC))
@@ -51,6 +51,8 @@ typedef struct Server {
 
         unsigned n_server_fd;
 
+        bool syslog_is_stream;
+
         LIST_HEAD(Stream, streams);
         unsigned n_streams;
 } Server;
@@ -80,10 +82,11 @@ struct Stream {
         char *process;
         pid_t pid;
         uid_t uid;
+        gid_t gid;
 
         bool prefix;
 
-        char buffer[STREAM_BUFFER];
+        char buffer[LINE_MAX];
         size_t length;
 
         LIST_FIELDS(Stream, stream);
@@ -142,7 +145,7 @@ static int stream_log(Stream *s, char *p, usec_t ts) {
                         return -EINVAL;
         }
 
-        snprintf(header_pid, sizeof(header_pid), "[%llu]: ", (unsigned long long) s->pid);
+        snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) s->pid);
         char_array_0(header_pid);
 
         zero(iovec);
@@ -150,18 +153,69 @@ static int stream_log(Stream *s, char *p, usec_t ts) {
 
         if (s->target == STREAM_SYSLOG) {
                 struct msghdr msghdr;
+                union {
+                        struct cmsghdr cmsghdr;
+                        uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+                } control;
+                struct ucred *ucred;
+
+                zero(control);
+                control.cmsghdr.cmsg_level = SOL_SOCKET;
+                control.cmsghdr.cmsg_type = SCM_CREDENTIALS;
+                control.cmsghdr.cmsg_len = CMSG_LEN(sizeof(struct ucred));
+
+                ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
+                ucred->pid = s->pid;
+                ucred->uid = s->uid;
+                ucred->gid = s->gid;
 
                 IOVEC_SET_STRING(iovec[1], header_time);
                 IOVEC_SET_STRING(iovec[2], s->process);
                 IOVEC_SET_STRING(iovec[3], header_pid);
                 IOVEC_SET_STRING(iovec[4], p);
 
+                /* When using syslog via SOCK_STREAM separate the messages by NUL chars */
+                if (s->server->syslog_is_stream)
+                        iovec[4].iov_len++;
+
                 zero(msghdr);
                 msghdr.msg_iov = iovec;
                 msghdr.msg_iovlen = ELEMENTSOF(iovec);
+                msghdr.msg_control = &control;
+                msghdr.msg_controllen = control.cmsghdr.cmsg_len;
 
-                if (sendmsg(s->server->syslog_fd, &msghdr, MSG_NOSIGNAL) < 0)
-                        return -errno;
+                for (;;) {
+                        ssize_t n;
+
+                        if ((n = sendmsg(s->server->syslog_fd, &msghdr, MSG_NOSIGNAL)) < 0) {
+
+                                if (errno == ESRCH) {
+                                        pid_t our_pid;
+
+                                        /* Hmm, maybe the process this
+                                         * line originates from is
+                                         * dead? Then let's patch in
+                                         * our own pid and retry,
+                                         * since we have nothing
+                                         * better */
+
+                                        our_pid = getpid();
+
+                                        if (ucred->pid != our_pid) {
+                                                ucred->pid = our_pid;
+                                                continue;
+                                        }
+                                }
+
+                                return -errno;
+                        }
+
+                        if (!s->server->syslog_is_stream ||
+                            (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec)))
+                                break;
+
+                        IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n);
+                }
 
         } else if (s->target == STREAM_KMSG) {
                 IOVEC_SET_STRING(iovec[1], s->process);
@@ -207,12 +261,12 @@ static int stream_line(Stream *s, char *p, usec_t ts) {
 
         case STREAM_PRIORITY:
                 if ((r = safe_atoi(p, &s->priority)) < 0) {
-                        log_warning("Failed to parse log priority line: %s", strerror(errno));
+                        log_warning("Failed to parse log priority line: %m");
                         return r;
                 }
 
                 if (s->priority < 0) {
-                        log_warning("Log priority negative: %s", strerror(errno));
+                        log_warning("Log priority negative: %m");
                         return -ERANGE;
                 }
 
@@ -278,13 +332,13 @@ static int stream_process(Stream *s, usec_t ts) {
         int r;
         assert(s);
 
-        if ((l = read(s->fd, s->buffer+s->length, STREAM_BUFFER-s->length)) < 0) {
+        if ((l = read(s->fd, s->buffer+s->length, LINE_MAX-s->length)) < 0) {
 
                 if (errno == EAGAIN)
                         return 0;
 
-                log_warning("Failed to read from stream: %s", strerror(errno));
-                return -1;
+                log_warning("Failed to read from stream: %m");
+                return -errno;
         }
 
 
@@ -340,6 +394,11 @@ static int stream_new(Server *s, int server_fd) {
                 return 0;
         }
 
+        if (!socket_tcpwrap(fd, "systemd-logger")) {
+                close_nointr_nofail(fd);
+                return 0;
+        }
+
         if (!(stream = new0(Stream, 1))) {
                 close_nointr_nofail(fd);
                 return -ENOMEM;
@@ -367,6 +426,7 @@ static int stream_new(Server *s, int server_fd) {
 
         stream->pid = ucred.pid;
         stream->uid = ucred.uid;
+        stream->gid = ucred.gid;
 
         stream->server = s;
         LIST_PREPEND(Stream, stream, s->streams, stream);
@@ -418,7 +478,7 @@ static int server_init(Server *s, unsigned n_sockets) {
 
         if ((s->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) {
                 r = -errno;
-                log_error("Failed to create epoll object: %s", strerror(errno));
+                log_error("Failed to create epoll object: %m");
                 goto fail;
         }
 
@@ -439,35 +499,54 @@ static int server_init(Server *s, unsigned n_sockets) {
                         goto fail;
                 }
 
+                /* We use ev.data.ptr instead of ev.data.fd here,
+                 * since on 64bit archs fd is 32bit while a pointer is
+                 * 64bit. To make sure we can easily distuingish fd
+                 * values and pointer values we want to make sure to
+                 * write the full field unconditionally. */
+
                 zero(ev);
                 ev.events = EPOLLIN;
-                ev.data.ptr = UINT_TO_PTR(fd);
+                ev.data.ptr = INT_TO_PTR(fd);
                 if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
                         r = -errno;
-                        log_error("Failed to add server fd to epoll object: %s", strerror(errno));
+                        log_error("Failed to add server fd to epoll object: %m");
                         goto fail;
                 }
         }
 
-        if ((s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
-                r = -errno;
-                log_error("Failed to create log fd: %s", strerror(errno));
-                goto fail;
-        }
-
         zero(sa);
         sa.un.sun_family = AF_UNIX;
         strncpy(sa.un.sun_path, "/dev/log", sizeof(sa.un.sun_path));
 
-        if (connect(s->syslog_fd, &sa.sa, sizeof(sa)) < 0) {
+        if ((s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
                 r = -errno;
-                log_error("Failed to connect log socket to /dev/log: %s", strerror(errno));
+                log_error("Failed to create log fd: %m");
                 goto fail;
         }
 
+        if (connect(s->syslog_fd, &sa.sa, sizeof(sa)) < 0) {
+                close_nointr_nofail(s->syslog_fd);
+
+                if ((s->syslog_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0)) < 0) {
+                        r = -errno;
+                        log_error("Failed to create log fd: %m");
+                        goto fail;
+                }
+
+                if (connect(s->syslog_fd, &sa.sa, sizeof(sa)) < 0) {
+                        r = -errno;
+                        log_error("Failed to connect log socket to /dev/log: %m");
+                        goto fail;
+                }
+
+                s->syslog_is_stream = true;
+        } else
+                s->syslog_is_stream = false;
+
         /* /dev/kmsg logging is strictly optional */
         if ((s->kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC)) < 0)
-                log_debug("Failed to open /dev/kmsg for logging, disabling kernel log buffer support: %s", strerror(errno));
+                log_warning("Failed to open /dev/kmsg for logging, disabling kernel log buffer support: %m");
 
         return 0;
 
@@ -484,18 +563,18 @@ static int process_event(Server *s, struct epoll_event *ev) {
         /* Yes, this is a bit ugly, we assume that that valid pointers
          * are > SD_LISTEN_FDS_START+SERVER_FD_MAX. Which is certainly
          * true on Linux (and probably most other OSes, too, since the
-         * first 4k usually are part of a seperate null pointer
+         * first 4k usually are part of a separate null pointer
          * dereference page. */
 
-        if (PTR_TO_UINT(ev->data.ptr) >= SD_LISTEN_FDS_START &&
-            PTR_TO_UINT(ev->data.ptr) < SD_LISTEN_FDS_START+s->n_server_fd) {
+        if (PTR_TO_INT(ev->data.ptr) >= SD_LISTEN_FDS_START &&
+            PTR_TO_INT(ev->data.ptr) < SD_LISTEN_FDS_START+(int)s->n_server_fd) {
 
                 if (ev->events != EPOLLIN) {
                         log_info("Got invalid event from epoll. (1)");
                         return -EIO;
                 }
 
-                if ((r = stream_new(s, PTR_TO_UINT(ev->data.ptr))) < 0) {
+                if ((r = stream_new(s, PTR_TO_INT(ev->data.ptr))) < 0) {
                         log_info("Failed to accept new connection: %s", strerror(-r));
                         return r;
                 }
@@ -527,25 +606,36 @@ static int process_event(Server *s, struct epoll_event *ev) {
 
 int main(int argc, char *argv[]) {
         Server server;
-        int r = 3, n;
+        int r = EXIT_FAILURE, n;
+
+        if (getppid() != 1) {
+                log_error("This program should be invoked by init only.");
+                return EXIT_FAILURE;
+        }
+
+        if (argc > 1) {
+                log_error("This program does not take arguments.");
+                return EXIT_FAILURE;
+        }
 
         log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
         log_parse_environment();
-
-        log_info("systemd-logger running as pid %llu", (unsigned long long) getpid());
+        log_open();
 
         if ((n = sd_listen_fds(true)) < 0) {
                 log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
-                return 1;
+                return EXIT_FAILURE;
         }
 
         if (n <= 0 || n > SERVER_FD_MAX) {
                 log_error("No or too many file descriptors passed.");
-                return 2;
+                return EXIT_FAILURE;
         }
 
         if (server_init(&server, (unsigned) n) < 0)
-                return 3;
+                return EXIT_FAILURE;
+
+        log_debug("systemd-logger running as pid %lu", (unsigned long) getpid());
 
         sd_notify(false,
                   "READY=1\n"
@@ -562,17 +652,20 @@ int main(int argc, char *argv[]) {
                         if (errno == EINTR)
                                 continue;
 
-                        log_error("epoll_wait() failed: %s", strerror(errno));
+                        log_error("epoll_wait() failed: %m");
                         goto fail;
                 }
 
                 if (k <= 0)
                         break;
 
-                if ((k = process_event(&server, &event)) < 0)
+                if (process_event(&server, &event) < 0)
                         goto fail;
         }
-        r = 0;
+
+        r = EXIT_SUCCESS;
+
+        log_debug("systemd-logger stopped as pid %lu", (unsigned long) getpid());
 
 fail:
         sd_notify(false,
@@ -580,7 +673,5 @@ fail:
 
         server_done(&server);
 
-        log_info("systemd-logger stopped as pid %llu", (unsigned long long) getpid());
-
         return r;
 }