chiark / gitweb /
implement proper logging for services
authorLennart Poettering <lennart@poettering.net>
Thu, 28 Jan 2010 01:06:20 +0000 (02:06 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 28 Jan 2010 01:06:20 +0000 (02:06 +0100)
execute.c
execute.h
fixme
load-fragment.c
test1/exec-demo.service
test1/systemd-logger.socket

index 1ca91fd..ccf951a 100644 (file)
--- a/execute.c
+++ b/execute.c
@@ -7,6 +7,8 @@
 #include <unistd.h>
 #include <string.h>
 #include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
 
 #include "execute.h"
 #include "strv.h"
@@ -139,6 +141,112 @@ static int flags_fds(int fds[], unsigned n_fds) {
         return 0;
 }
 
+static int replace_null_fd(int fd, int flags) {
+        int nfd;
+        assert(fd >= 0);
+
+        close_nointr(fd);
+
+        if ((nfd = open("/dev/null", flags|O_NOCTTY)) < 0)
+                return -errno;
+
+        if (nfd != fd) {
+                close_nointr_nofail(nfd);
+                return -EIO;
+        }
+
+        return 0;
+}
+
+static int setup_output(const ExecContext *context, const char *ident) {
+        int r;
+
+        assert(context);
+
+        switch (context->output) {
+
+        case EXEC_CONSOLE:
+                return 0;
+
+        case EXEC_NULL:
+
+                if ((r = replace_null_fd(STDIN_FILENO, O_RDONLY)) < 0 ||
+                    (r = replace_null_fd(STDOUT_FILENO, O_WRONLY)) < 0 ||
+                    (r = replace_null_fd(STDERR_FILENO, O_WRONLY)) < 0)
+                        return r;
+
+                return 0;
+
+        case EXEC_KERNEL:
+        case EXEC_SYSLOG: {
+
+                int fd;
+                union {
+                        struct sockaddr sa;
+                        struct sockaddr_un un;
+                } sa;
+
+                if ((r = replace_null_fd(STDIN_FILENO, O_RDONLY)) < 0)
+                        return r;
+
+                close_nointr(STDOUT_FILENO);
+                close_nointr(STDERR_FILENO);
+
+                if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+                        return -errno;
+
+                if (fd != STDOUT_FILENO) {
+                        close_nointr_nofail(fd);
+                        return -EIO;
+                }
+
+                zero(sa);
+                sa.sa.sa_family = AF_UNIX;
+                strncpy(sa.un.sun_path+1, LOGGER_SOCKET, sizeof(sa.un.sun_path)-1);
+
+                if (connect(fd, &sa.sa, sizeof(sa)) < 0) {
+                        close_nointr_nofail(fd);
+                        return -errno;
+                }
+
+                if (shutdown(fd, SHUT_RD) < 0) {
+                        close_nointr_nofail(fd);
+                        return -errno;
+                }
+
+                if ((fd = dup(fd)) < 0) {
+                        close_nointr_nofail(fd);
+                        return -errno;
+                }
+
+                if (fd != STDERR_FILENO) {
+                        close_nointr_nofail(fd);
+                        return -EIO;
+                }
+
+                /* We speak a very simple protocol between log server
+                 * and client: one line for the log destination (kmsg
+                 * or syslog), followed by the priority field,
+                 * followed by the process name. Since we replaced
+                 * stdin/stderr we simple use stdio to write to
+                 * it. Note that we use stderr, to minimize buffer
+                 * flushing issues. */
+
+                fprintf(stderr,
+                        "%s\n"
+                        "%i\n"
+                        "%s\n",
+                        context->output == EXEC_KERNEL ? "kmsg" : "syslog",
+                        context->syslog_priority,
+                        context->syslog_identifier ? context->syslog_identifier : ident);
+
+                return 0;
+        }
+        }
+
+        assert_not_reached("Unknown logging type");
+}
+
 int exec_spawn(const ExecCommand *command, const ExecContext *context, int *fds, unsigned n_fds, pid_t *ret) {
         pid_t pid;
 
@@ -173,6 +281,11 @@ int exec_spawn(const ExecCommand *command, const ExecContext *context, int *fds,
                         goto fail;
                 }
 
+                if (setup_output(context, file_name_from_path(command->path)) < 0) {
+                        r = EXIT_OUTPUT;
+                        goto fail;
+                }
+
                 snprintf(t, sizeof(t), "%i", context->oom_adjust);
                 char_array_0(t);
 
@@ -251,6 +364,9 @@ void exec_context_init(ExecContext *c) {
         cap_clear(c->capabilities);
         c->oom_adjust = 0;
         c->nice = 0;
+
+        c->output = 0;
+        c->syslog_priority = LOG_DAEMON|LOG_INFO;
 }
 
 void exec_context_done(ExecContext *c) {
@@ -269,6 +385,9 @@ void exec_context_done(ExecContext *c) {
         free(c->directory);
         c->directory = NULL;
 
+        free(c->syslog_identifier);
+        c->syslog_identifier = NULL;
+
         free(c->user);
         c->user = NULL;
 
index b7dbe68..3283e1f 100644 (file)
--- a/execute.h
+++ b/execute.h
@@ -16,6 +16,16 @@ typedef struct ExecContext ExecContext;
 #include "list.h"
 #include "util.h"
 
+/* Abstract namespace! */
+#define LOGGER_SOCKET "/systemd/logger"
+
+typedef enum ExecOutput {
+        EXEC_CONSOLE,
+        EXEC_NULL,
+        EXEC_SYSLOG,
+        EXEC_KERNEL
+} ExecOutput;
+
 struct ExecStatus {
         pid_t pid;
         usec_t timestamp;
@@ -33,11 +43,16 @@ struct ExecCommand {
 struct ExecContext {
         char **environment;
         mode_t umask;
-        struct rlimit *rlimit[RLIMIT_NLIMITS];
+        struct rlimit *rlimit[RLIMIT_NLIMITS];  /* FIXME: load-fragment parser missing */
         int oom_adjust;
         int nice;
         char *directory;
 
+        ExecOutput output;
+        int syslog_priority;
+        char *syslog_identifier;
+
+         /* FIXME: all privs related settings need parser and enforcer */
         cap_t capabilities;
         bool capabilities_set:1;
 
@@ -72,7 +87,8 @@ typedef enum ExitStatus {
         EXIT_MEMORY,
         EXIT_LIMITS,
         EXIT_OOM_ADJUST,
-        EXIT_SIGNAL_MASK
+        EXIT_SIGNAL_MASK,
+        EXIT_OUTPUT
 } ExitStatus;
 
 int exec_spawn(const ExecCommand *command, const ExecContext *context, int *fds, unsigned n_fds, pid_t *ret);
diff --git a/fixme b/fixme
index 800801c..4e82c92 100644 (file)
--- a/fixme
+++ b/fixme
@@ -42,3 +42,5 @@
 - rate limit startups
 
 - automatically delete stale unix sockets
+
+- .socket needs to be notified not only by .service state changes, but also unsuccessful start jobs
index 2df5c04..2757506 100644 (file)
@@ -479,8 +479,151 @@ int config_parse_bindtodevice(
         return 0;
 }
 
-#define FOLLOW_MAX 8
+int config_parse_output(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        ExecOutput *o = data;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (streq(rvalue, "syslog"))
+                *o = EXEC_SYSLOG;
+        else if (streq(rvalue, "null"))
+                *o = EXEC_NULL;
+        else if (streq(rvalue, "syslog"))
+                *o = EXEC_SYSLOG;
+        else if (streq(rvalue, "kernel"))
+                *o = EXEC_KERNEL;
+        else {
+                log_error("[%s:%u] Failed to parse log output: %s", filename, line, rvalue);
+                return -EBADMSG;
+        }
+
+        return 0;
+}
 
+int config_parse_facility(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        static const char * const table[LOG_NFACILITIES] = {
+                [LOG_FAC(LOG_KERN)] = "kern",
+                [LOG_FAC(LOG_USER)] = "user",
+                [LOG_FAC(LOG_MAIL)] = "mail",
+                [LOG_FAC(LOG_DAEMON)] = "daemon",
+                [LOG_FAC(LOG_AUTH)] = "auth",
+                [LOG_FAC(LOG_SYSLOG)] = "syslog",
+                [LOG_FAC(LOG_LPR)] = "lpr",
+                [LOG_FAC(LOG_NEWS)] = "news",
+                [LOG_FAC(LOG_UUCP)] = "uucp",
+                [LOG_FAC(LOG_CRON)] = "cron",
+                [LOG_FAC(LOG_AUTHPRIV)] = "authpriv",
+                [LOG_FAC(LOG_FTP)] = "ftp",
+                [LOG_FAC(LOG_LOCAL0)] = "local0",
+                [LOG_FAC(LOG_LOCAL1)] = "local1",
+                [LOG_FAC(LOG_LOCAL2)] = "local2",
+                [LOG_FAC(LOG_LOCAL3)] = "local3",
+                [LOG_FAC(LOG_LOCAL4)] = "local4",
+                [LOG_FAC(LOG_LOCAL5)] = "local5",
+                [LOG_FAC(LOG_LOCAL6)] = "local6",
+                [LOG_FAC(LOG_LOCAL7)] = "local7"
+        };
+
+        ExecOutput *o = data;
+        int i;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        for (i = 0; i < (int) ELEMENTSOF(table); i++)
+                if (streq(rvalue, table[i])) {
+                        *o = LOG_MAKEPRI(i, LOG_PRI(*o));
+                        break;
+                }
+
+        if (i >= (int) ELEMENTSOF(table)) {
+
+                /* Second try, let's see if this is a number. */
+                if (safe_atoi(rvalue, &i) >= 0 &&
+                    i >= 0 &&
+                    i < (int) ELEMENTSOF(table))
+                        *o = LOG_MAKEPRI(i, LOG_PRI(*o));
+                else {
+                        log_error("[%s:%u] Failed to parse log output: %s", filename, line, rvalue);
+                        return -EBADMSG;
+                }
+        }
+
+        return 0;
+}
+
+int config_parse_level(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        static const char * const table[LOG_DEBUG+1] = {
+                [LOG_EMERG] = "emerg",
+                [LOG_ALERT] = "alert",
+                [LOG_CRIT] = "crit",
+                [LOG_ERR] = "err",
+                [LOG_WARNING] = "warning",
+                [LOG_NOTICE] = "notice",
+                [LOG_INFO] = "info",
+                [LOG_DEBUG] = "debug"
+        };
+
+        ExecOutput *o = data;
+        int i;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        for (i = 0; i < (int) ELEMENTSOF(table); i++)
+                if (streq(rvalue, table[i])) {
+                        *o = LOG_MAKEPRI(LOG_FAC(*o), i);
+                        break;
+                }
+
+        if (i >= LOG_NFACILITIES) {
+
+                /* Second try, let's see if this is a number. */
+                if (safe_atoi(rvalue, &i) >= 0 &&
+                    i >= 0 &&
+                    i < (int) ELEMENTSOF(table))
+                        *o = LOG_MAKEPRI(LOG_FAC(*o), i);
+                else {
+                        log_error("[%s:%u] Failed to parse log output: %s", filename, line, rvalue);
+                        return -EBADMSG;
+                }
+        }
+
+        return 0;
+}
+
+#define FOLLOW_MAX 8
 
 static int open_follow(char **filename, FILE **_f, Set *names, char **_id) {
         unsigned c = 0;
@@ -569,7 +712,11 @@ static int load_from_path(Unit *u, const char *path) {
                 { "Nice",                   config_parse_nice,            &(context).nice,                                 section   }, \
                 { "OOMAdjust",              config_parse_oom_adjust,      &(context).oom_adjust,                           section   }, \
                 { "UMask",                  config_parse_umask,           &(context).umask,                                section   }, \
-                { "Environment",            config_parse_strv,            &(context).environment,                          section   }
+                { "Environment",            config_parse_strv,            &(context).environment,                          section   }, \
+                { "Output",                 config_parse_output,          &(context).output,                               section   }, \
+                { "SyslogIdentifier",       config_parse_string,          &(context).syslog_identifier,                    section   }, \
+                { "SyslogFacility",         config_parse_facility,        &(context).syslog_priority,                      section   }, \
+                { "SyslogLevel",            config_parse_level,           &(context).syslog_priority,                      section   }
 
         const ConfigItem items[] = {
                 { "Names",                  config_parse_names,           u,                                               "Meta"    },
@@ -678,6 +825,7 @@ finish:
 
 int unit_load_fragment(Unit *u) {
         int r = -ENOENT;
+        ExecContext *c;
 
         assert(u);
         assert(u->meta.load_state == UNIT_STUB);
@@ -694,5 +842,24 @@ int unit_load_fragment(Unit *u) {
                                 return r;
         }
 
+        if (u->meta.type == UNIT_SOCKET)
+                c = &u->socket.exec_context;
+        else if (u->meta.type == UNIT_SERVICE)
+                c = &u->service.exec_context;
+        else
+                c = NULL;
+
+        if (r >= 0 && c &&
+            (c->output == EXEC_KERNEL || c->output == EXEC_SYSLOG)) {
+                /* If syslog or kernel logging is requested, make sure
+                 * our own logging daemon is run first. */
+
+                if ((r = unit_add_dependency(u, UNIT_AFTER, u->meta.manager->special_units[SPECIAL_LOGGER_SOCKET])) < 0)
+                        return r;
+
+                if ((r = unit_add_dependency(u, UNIT_REQUIRES, u->meta.manager->special_units[SPECIAL_LOGGER_SOCKET])) < 0)
+                        return r;
+        }
+
         return r;
 }
index ea40b1f..b772518 100644 (file)
@@ -2,4 +2,6 @@
 Description=Simple Execution Demo
 
 [Service]
-ExecStart=/bin/ls
+ExecStart=/bin/cat /etc/hosts
+Type=simple
+Output=syslog
index a013067..ae0c81a 100644 (file)
@@ -2,6 +2,6 @@
 Description=systemd Logging Socket
 
 [Socket]
-ExecStartPre=/bin/rm /tmp/systemd-logger
+ExecStartPre=/bin/rm -f /tmp/systemd-logger
 ListenStream==/systemd/logger
 ListenStream=/tmp/systemd-logger