From: Lennart Poettering Date: Thu, 28 Jan 2010 01:06:20 +0000 (+0100) Subject: implement proper logging for services X-Git-Tag: v1~770 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=071830ff32351c19343ff1f0343c13d5c2b69250;hp=ce578209aa4e061aa38d3a4727612bb388307bf9 implement proper logging for services --- diff --git a/execute.c b/execute.c index 1ca91fddd..ccf951a25 100644 --- a/execute.c +++ b/execute.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #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; diff --git a/execute.h b/execute.h index b7dbe6849..3283e1f81 100644 --- 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 800801c4e..4e82c92ed 100644 --- 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 diff --git a/load-fragment.c b/load-fragment.c index 2df5c04f7..2757506ab 100644 --- a/load-fragment.c +++ b/load-fragment.c @@ -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; } diff --git a/test1/exec-demo.service b/test1/exec-demo.service index ea40b1fec..b772518e3 100644 --- a/test1/exec-demo.service +++ b/test1/exec-demo.service @@ -2,4 +2,6 @@ Description=Simple Execution Demo [Service] -ExecStart=/bin/ls +ExecStart=/bin/cat /etc/hosts +Type=simple +Output=syslog diff --git a/test1/systemd-logger.socket b/test1/systemd-logger.socket index a013067ab..ae0c81a91 100644 --- a/test1/systemd-logger.socket +++ b/test1/systemd-logger.socket @@ -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