implement proper logging for services

This commit is contained in:
Lennart Poettering 2010-01-28 02:06:20 +01:00
parent ce578209aa
commit 071830ff32
6 changed files with 312 additions and 6 deletions

119
execute.c
View File

@ -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;

View File

@ -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);

2
fixme
View File

@ -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

View 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;
}

View File

@ -2,4 +2,6 @@
Description=Simple Execution Demo
[Service]
ExecStart=/bin/ls
ExecStart=/bin/cat /etc/hosts
Type=simple
Output=syslog

View 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