X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fjournal%2Fjournald.c;h=3e7581815b562d849ea2698a605f501d0befa990;hp=d7f0ed52b4ac742ac10c52560e105c2feba905f1;hb=7827b1a10f4dfe2c6771b515f28f7ae22e0ae039;hpb=81527be142678057215665be66e4b3c8306a7ab3 diff --git a/src/journal/journald.c b/src/journal/journald.c index d7f0ed52b..3e7581815 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.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 . ***/ @@ -25,28 +25,41 @@ #include #include #include -#include -#include #include #include #include #include #include -#include #include #include +#ifdef HAVE_LOGIND +#include +#endif + +#include "mkdir.h" #include "hashmap.h" #include "journal-file.h" #include "socket-util.h" -#include "acl-util.h" #include "cgroup-util.h" #include "list.h" #include "journal-rate-limit.h" #include "journal-internal.h" #include "conf-parser.h" #include "journald.h" +#include "virt.h" +#include "missing.h" + +#ifdef HAVE_ACL +#include +#include +#include "acl-util.h" +#endif + +#ifdef HAVE_SELINUX +#include +#endif #define USER_JOURNALS_MAX 1024 #define STDOUT_STREAMS_MAX 4096 @@ -56,16 +69,15 @@ #define RECHECK_AVAILABLE_SPACE_USEC (30*USEC_PER_SEC) -#define RECHECK_VAR_AVAILABLE_USEC (30*USEC_PER_SEC) - -#define SYSLOG_TIMEOUT_USEC (5*USEC_PER_SEC) +#define N_IOVEC_META_FIELDS 17 -#define N_IOVEC_META_FIELDS 16 +#define ENTRY_SIZE_MAX (1024*1024*32) typedef enum StdoutStreamState { - STDOUT_STREAM_TAG, + STDOUT_STREAM_IDENTIFIER, + STDOUT_STREAM_UNIT_ID, STDOUT_STREAM_PRIORITY, - STDOUT_STREAM_PRIORITY_PREFIX, + STDOUT_STREAM_LEVEL_PREFIX, STDOUT_STREAM_FORWARD_TO_SYSLOG, STDOUT_STREAM_FORWARD_TO_KMSG, STDOUT_STREAM_FORWARD_TO_CONSOLE, @@ -79,10 +91,14 @@ struct StdoutStream { int fd; struct ucred ucred; +#ifdef HAVE_SELINUX + security_context_t security_context; +#endif - char *tag; + char *identifier; + char *unit_id; int priority; - bool priority_prefix:1; + bool level_prefix:1; bool forward_to_syslog:1; bool forward_to_kmsg:1; bool forward_to_console:1; @@ -93,7 +109,15 @@ struct StdoutStream { LIST_FIELDS(StdoutStream, stdout_stream); }; -static int server_flush_to_var(Server *s); +static const char* const storage_table[] = { + [STORAGE_AUTO] = "auto", + [STORAGE_VOLATILE] = "volatile", + [STORAGE_PERSISTENT] = "persistent", + [STORAGE_NONE] = "none" +}; + +DEFINE_STRING_TABLE_LOOKUP(storage, Storage); +DEFINE_CONFIG_PARSE_ENUM(config_parse_storage, storage, Storage, "Failed to parse storage setting"); static uint64_t available_space(Server *s) { char ids[33], *p; @@ -141,24 +165,25 @@ static uint64_t available_space(Server *s) { for (;;) { struct stat st; struct dirent buf, *de; - int k; - k = readdir_r(d, &buf, &de); - if (k != 0) { - r = -k; - goto finish; - } + r = readdir_r(d, &buf, &de); + if (r != 0) + break; if (!de) break; - if (!dirent_is_file_with_suffix(de, ".journal")) + if (!endswith(de->d_name, ".journal") && + !endswith(de->d_name, ".journal~")) continue; if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) continue; - sum += (uint64_t) st.st_blocks * (uint64_t) st.st_blksize; + if (!S_ISREG(st.st_mode)) + continue; + + sum += (uint64_t) st.st_blocks * 512UL; } avail = sum >= m->max_use ? 0 : m->max_use - sum; @@ -179,18 +204,42 @@ finish: return avail; } -static void fix_perms(JournalFile *f, uid_t uid) { +static void server_read_file_gid(Server *s) { + const char *adm = "adm"; + int r; + + assert(s); + + if (s->file_gid_valid) + return; + + r = get_group_creds(&adm, &s->file_gid); + if (r < 0) + log_warning("Failed to resolve 'adm' group: %s", strerror(-r)); + + /* if we couldn't read the gid, then it will be 0, but that's + * fine and we shouldn't try to resolve the group again, so + * let's just pretend it worked right-away. */ + s->file_gid_valid = true; +} + +static void server_fix_perms(Server *s, JournalFile *f, uid_t uid) { + int r; +#ifdef HAVE_ACL acl_t acl; acl_entry_t entry; acl_permset_t permset; - int r; +#endif assert(f); - r = fchmod_and_fchown(f->fd, 0640, 0, 0); + server_read_file_gid(s); + + r = fchmod_and_fchown(f->fd, 0640, 0, s->file_gid); if (r < 0) log_warning("Failed to fix access mode/rights on %s, ignoring: %s", f->path, strerror(-r)); +#ifdef HAVE_ACL if (uid <= 0) return; @@ -223,6 +272,7 @@ static void fix_perms(JournalFile *f, uid_t uid) { finish: acl_free(acl); +#endif } static JournalFile* find_journal(Server *s, uid_t uid) { @@ -263,15 +313,13 @@ static JournalFile* find_journal(Server *s, uid_t uid) { journal_file_close(f); } - r = journal_file_open(p, O_RDWR|O_CREAT, 0640, s->system_journal, &f); + r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, &s->system_metrics, s->system_journal, &f); free(p); if (r < 0) return s->system_journal; - fix_perms(f, uid); - f->metrics = s->system_metrics; - f->compress = s->compress; + server_fix_perms(s, f, uid); r = hashmap_put(s->user_journals, UINT32_TO_PTR(uid), f); if (r < 0) { @@ -293,21 +341,37 @@ static void server_rotate(Server *s) { if (s->runtime_journal) { r = journal_file_rotate(&s->runtime_journal); if (r < 0) - log_error("Failed to rotate %s: %s", s->runtime_journal->path, strerror(-r)); + if (s->runtime_journal) + log_error("Failed to rotate %s: %s", s->runtime_journal->path, strerror(-r)); + else + log_error("Failed to create new runtime journal: %s", strerror(-r)); + else + server_fix_perms(s, s->runtime_journal, 0); } if (s->system_journal) { r = journal_file_rotate(&s->system_journal); if (r < 0) - log_error("Failed to rotate %s: %s", s->system_journal->path, strerror(-r)); + if (s->system_journal) + log_error("Failed to rotate %s: %s", s->system_journal->path, strerror(-r)); + else + log_error("Failed to create new system journal: %s", strerror(-r)); + + else + server_fix_perms(s, s->system_journal, 0); } HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) { r = journal_file_rotate(&f); if (r < 0) - log_error("Failed to rotate %s: %s", f->path, strerror(-r)); - else + if (f->path) + log_error("Failed to rotate %s: %s", f->path, strerror(-r)); + else + log_error("Failed to create user journal: %s", strerror(-r)); + else { hashmap_replace(s->user_journals, k, f); + server_fix_perms(s, s->system_journal, PTR_TO_UINT32(k)); + } } } @@ -339,7 +403,6 @@ static void server_vacuum(Server *s) { free(p); } - if (s->runtime_journal) { if (asprintf(&p, "/run/log/journal/%s", ids) < 0) { log_error("Out of memory."); @@ -397,25 +460,91 @@ static char *shortened_cgroup_path(pid_t pid) { return path; } -static void dispatch_message_real(Server *s, - struct iovec *iovec, unsigned n, unsigned m, - struct ucred *ucred, - struct timeval *tv) { +static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n) { + JournalFile *f; + bool vacuumed = false; + int r; + + assert(s); + assert(iovec); + assert(n > 0); + + f = find_journal(s, uid); + if (!f) + return; + + if (journal_file_rotate_suggested(f)) { + log_info("Journal header limits reached or header out-of-date, rotating."); + server_rotate(s); + server_vacuum(s); + vacuumed = true; + + f = find_journal(s, uid); + if (!f) + return; + } + + for (;;) { + r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); + if (r >= 0) + return; + + if (vacuumed || + (r != -E2BIG && /* hit limit */ + r != -EFBIG && /* hit fs limit */ + r != -EDQUOT && /* quota hit */ + r != -ENOSPC && /* disk full */ + r != -EBADMSG && /* corrupted */ + r != -ENODATA && /* truncated */ + r != -EHOSTDOWN && /* other machine */ + r != -EPROTONOSUPPORT && /* unsupported feature */ + r != -EBUSY && /* unclean shutdown */ + r != -ESHUTDOWN /* already archived */)) { + log_error("Failed to write entry, ignoring: %s", strerror(-r)); + return; + } + + if (r == -E2BIG || r == -EFBIG || r == EDQUOT || r == ENOSPC) + log_info("Allocation limit reached, rotating."); + else if (r == -EHOSTDOWN) + log_info("Journal file from other machine, rotating."); + else if (r == -EBUSY) + log_info("Unlcean shutdown, rotating."); + else + log_warning("Journal file corrupted, rotating."); + + server_rotate(s); + server_vacuum(s); + vacuumed = true; + + f = find_journal(s, uid); + if (!f) + return; + + log_info("Retrying write."); + } +} + +static void dispatch_message_real( + Server *s, + struct iovec *iovec, unsigned n, unsigned m, + struct ucred *ucred, + struct timeval *tv, + const char *label, size_t label_len, + const char *unit_id) { char *pid = NULL, *uid = NULL, *gid = NULL, *source_time = NULL, *boot_id = NULL, *machine_id = NULL, *comm = NULL, *cmdline = NULL, *hostname = NULL, *audit_session = NULL, *audit_loginuid = NULL, *exe = NULL, *cgroup = NULL, *session = NULL, - *owner_uid = NULL, *unit = NULL; + *owner_uid = NULL, *unit = NULL, *selinux_context = NULL; char idbuf[33]; sd_id128_t id; int r; char *t; uid_t loginuid = 0, realuid = 0; - JournalFile *f; - bool vacuumed = false; assert(s); assert(iovec); @@ -424,7 +553,9 @@ static void dispatch_message_real(Server *s, if (ucred) { uint32_t audit; +#ifdef HAVE_LOGIND uid_t owner; +#endif realuid = ucred->uid; @@ -451,7 +582,7 @@ static void dispatch_message_real(Server *s, exe = strappend("_EXE=", t); free(t); - if (comm) + if (exe) IOVEC_SET_STRING(iovec[n++], exe); } @@ -483,6 +614,7 @@ static void dispatch_message_real(Server *s, IOVEC_SET_STRING(iovec[n++], cgroup); } +#ifdef HAVE_LOGIND if (sd_pid_get_session(ucred->pid, &t) >= 0) { session = strappend("_SYSTEMD_SESSION=", t); free(t); @@ -491,17 +623,41 @@ static void dispatch_message_real(Server *s, IOVEC_SET_STRING(iovec[n++], session); } - if (sd_pid_get_unit(ucred->pid, &t) >= 0) { + if (sd_pid_get_owner_uid(ucred->uid, &owner) >= 0) + if (asprintf(&owner_uid, "_SYSTEMD_OWNER_UID=%lu", (unsigned long) owner) >= 0) + IOVEC_SET_STRING(iovec[n++], owner_uid); +#endif + + if (cg_pid_get_unit(ucred->pid, &t) >= 0) { unit = strappend("_SYSTEMD_UNIT=", t); free(t); + } else if (unit_id) + unit = strappend("_SYSTEMD_UNIT=", unit_id); + + if (unit) + IOVEC_SET_STRING(iovec[n++], unit); + +#ifdef HAVE_SELINUX + if (label) { + selinux_context = malloc(sizeof("_SELINUX_CONTEXT=") + label_len); + if (selinux_context) { + memcpy(selinux_context, "_SELINUX_CONTEXT=", sizeof("_SELINUX_CONTEXT=")-1); + memcpy(selinux_context+sizeof("_SELINUX_CONTEXT=")-1, label, label_len); + selinux_context[sizeof("_SELINUX_CONTEXT=")-1+label_len] = 0; + IOVEC_SET_STRING(iovec[n++], selinux_context); + } + } else { + security_context_t con; - if (unit) - IOVEC_SET_STRING(iovec[n++], unit); - } + if (getpidcon(ucred->pid, &con) >= 0) { + selinux_context = strappend("_SELINUX_CONTEXT=", con); + if (selinux_context) + IOVEC_SET_STRING(iovec[n++], selinux_context); - if (sd_pid_get_owner_uid(ucred->uid, &owner) >= 0) - if (asprintf(&owner_uid, "_SYSTEMD_OWNER_UID=%lu", (unsigned long) owner) >= 0) - IOVEC_SET_STRING(iovec[n++], owner_uid); + freecon(con); + } + } +#endif } if (tv) { @@ -533,29 +689,7 @@ static void dispatch_message_real(Server *s, assert(n <= m); - server_flush_to_var(s); - -retry: - f = find_journal(s, realuid == 0 ? 0 : loginuid); - if (!f) - log_warning("Dropping message, as we can't find a place to store the data."); - else { - r = journal_file_append_entry(f, NULL, iovec, n, &s->seqnum, NULL, NULL); - - if (r == -E2BIG && !vacuumed) { - log_info("Allocation limit reached."); - - server_rotate(s); - server_vacuum(s); - vacuumed = true; - - log_info("Retrying write."); - goto retry; - } - - if (r < 0) - log_error("Failed to write entry, ignoring: %s", strerror(-r)); - } + write_to_journal(s, realuid == 0 ? 0 : loginuid, iovec, n); free(pid); free(uid); @@ -573,12 +707,13 @@ retry: free(session); free(owner_uid); free(unit); + free(selinux_context); } static void driver_message(Server *s, sd_id128_t message_id, const char *format, ...) { char mid[11 + 32 + 1]; char buffer[16 + LINE_MAX + 1]; - struct iovec iovec[N_IOVEC_META_FIELDS + 3]; + struct iovec iovec[N_IOVEC_META_FIELDS + 4]; int n = 0; va_list ap; struct ucred ucred; @@ -587,6 +722,7 @@ static void driver_message(Server *s, sd_id128_t message_id, const char *format, assert(format); IOVEC_SET_STRING(iovec[n++], "PRIORITY=5"); + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver"); memcpy(buffer, "MESSAGE=", 8); va_start(ap, format); @@ -604,13 +740,15 @@ static void driver_message(Server *s, sd_id128_t message_id, const char *format, ucred.uid = getuid(); ucred.gid = getgid(); - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL); } static void dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, struct ucred *ucred, struct timeval *tv, + const char *label, size_t label_len, + const char *unit_id, int priority) { int rl; char *path = NULL, *c; @@ -621,6 +759,9 @@ static void dispatch_message(Server *s, if (n == 0) return; + if (LOG_PRI(priority) > s->max_level_store) + return; + if (!ucred) goto finish; @@ -658,7 +799,7 @@ static void dispatch_message(Server *s, free(path); finish: - dispatch_message_real(s, iovec, n, m, ucred, tv); + dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id); } static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, struct ucred *ucred, struct timeval *tv) { @@ -680,7 +821,7 @@ static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned zero(sa); sa.un.sun_family = AF_UNIX; - strncpy(sa.un.sun_path, "/run/systemd/syslog", sizeof(sa.un.sun_path)); + strncpy(sa.un.sun_path, "/run/systemd/journal/syslog", sizeof(sa.un.sun_path)); msghdr.msg_name = &sa; msghdr.msg_namelen = offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path); @@ -704,6 +845,11 @@ static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0) return; + /* The socket is full? I guess the syslog implementation is + * too slow, and we shouldn't wait for that... */ + if (errno == EAGAIN) + return; + if (ucred && errno == ESRCH) { struct ucred u; @@ -717,34 +863,44 @@ static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0) return; + + if (errno == EAGAIN) + return; } - log_debug("Failed to forward syslog message: %m"); + if (errno != ENOENT) + log_debug("Failed to forward syslog message: %m"); } -static void forward_syslog_raw(Server *s, const char *buffer, struct ucred *ucred, struct timeval *tv) { +static void forward_syslog_raw(Server *s, int priority, const char *buffer, struct ucred *ucred, struct timeval *tv) { struct iovec iovec; assert(s); assert(buffer); + if (LOG_PRI(priority) > s->max_level_syslog) + return; + IOVEC_SET_STRING(iovec, buffer); forward_syslog_iovec(s, &iovec, 1, ucred, tv); } -static void forward_syslog(Server *s, int priority, const char *tag, const char *message, struct ucred *ucred, struct timeval *tv) { +static void forward_syslog(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred, struct timeval *tv) { struct iovec iovec[5]; char header_priority[6], header_time[64], header_pid[16]; int n = 0; time_t t; struct tm *tm; - char *tag_buf = NULL; + char *ident_buf = NULL; assert(s); assert(priority >= 0); assert(priority <= 999); assert(message); + if (LOG_PRI(priority) > s->max_level_syslog) + return; + /* First: priority field */ snprintf(header_priority, sizeof(header_priority), "<%i>", priority); char_array_0(header_priority); @@ -759,22 +915,22 @@ static void forward_syslog(Server *s, int priority, const char *tag, const char return; IOVEC_SET_STRING(iovec[n++], header_time); - /* Third: tag and PID */ + /* Third: identifier and PID */ if (ucred) { - if (!tag) { - get_process_comm(ucred->pid, &tag_buf); - tag = tag_buf; + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; } snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid); char_array_0(header_pid); - if (tag) - IOVEC_SET_STRING(iovec[n++], tag); + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); IOVEC_SET_STRING(iovec[n++], header_pid); - } else if (tag) { - IOVEC_SET_STRING(iovec[n++], tag); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); IOVEC_SET_STRING(iovec[n++], ": "); } @@ -783,7 +939,7 @@ static void forward_syslog(Server *s, int priority, const char *tag, const char forward_syslog_iovec(s, iovec, n, ucred, tv); - free(tag_buf); + free(ident_buf); } static int fixup_priority(int priority) { @@ -794,11 +950,11 @@ static int fixup_priority(int priority) { return priority; } -static void forward_kmsg(Server *s, int priority, const char *tag, const char *message, struct ucred *ucred) { +static void forward_kmsg(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred) { struct iovec iovec[5]; char header_priority[6], header_pid[16]; int n = 0; - char *tag_buf = NULL; + char *ident_buf = NULL; int fd; assert(s); @@ -806,6 +962,9 @@ static void forward_kmsg(Server *s, int priority, const char *tag, const char *m assert(priority <= 999); assert(message); + if (LOG_PRI(priority) > s->max_level_kmsg) + return; + /* Never allow messages with kernel facility to be written to * kmsg, regardless where the data comes from. */ priority = fixup_priority(priority); @@ -815,22 +974,22 @@ static void forward_kmsg(Server *s, int priority, const char *tag, const char *m char_array_0(header_priority); IOVEC_SET_STRING(iovec[n++], header_priority); - /* Second: tag and PID */ + /* Second: identifier and PID */ if (ucred) { - if (!tag) { - get_process_comm(ucred->pid, &tag_buf); - tag = tag_buf; + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; } snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid); char_array_0(header_pid); - if (tag) - IOVEC_SET_STRING(iovec[n++], tag); + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); IOVEC_SET_STRING(iovec[n++], header_pid); - } else if (tag) { - IOVEC_SET_STRING(iovec[n++], tag); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); IOVEC_SET_STRING(iovec[n++], ": "); } @@ -850,34 +1009,38 @@ static void forward_kmsg(Server *s, int priority, const char *tag, const char *m close_nointr_nofail(fd); finish: - free(tag_buf); + free(ident_buf); } -static void forward_console(Server *s, const char *tag, const char *message, struct ucred *ucred) { +static void forward_console(Server *s, int priority, const char *identifier, const char *message, struct ucred *ucred) { struct iovec iovec[4]; char header_pid[16]; int n = 0, fd; - char *tag_buf = NULL; + char *ident_buf = NULL; + const char *tty; assert(s); assert(message); - /* First: tag and PID */ + if (LOG_PRI(priority) > s->max_level_console) + return; + + /* First: identifier and PID */ if (ucred) { - if (!tag) { - get_process_comm(ucred->pid, &tag_buf); - tag = tag_buf; + if (!identifier) { + get_process_comm(ucred->pid, &ident_buf); + identifier = ident_buf; } snprintf(header_pid, sizeof(header_pid), "[%lu]: ", (unsigned long) ucred->pid); char_array_0(header_pid); - if (tag) - IOVEC_SET_STRING(iovec[n++], tag); + if (identifier) + IOVEC_SET_STRING(iovec[n++], identifier); IOVEC_SET_STRING(iovec[n++], header_pid); - } else if (tag) { - IOVEC_SET_STRING(iovec[n++], tag); + } else if (identifier) { + IOVEC_SET_STRING(iovec[n++], identifier); IOVEC_SET_STRING(iovec[n++], ": "); } @@ -885,28 +1048,31 @@ static void forward_console(Server *s, const char *tag, const char *message, str IOVEC_SET_STRING(iovec[n++], message); IOVEC_SET_STRING(iovec[n++], "\n"); - fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + tty = s->tty_path ? s->tty_path : "/dev/console"; + + fd = open_terminal(tty, O_WRONLY|O_NOCTTY|O_CLOEXEC); if (fd < 0) { - log_debug("Failed to open /dev/console for logging: %s", strerror(errno)); + log_debug("Failed to open %s for logging: %s", tty, strerror(errno)); goto finish; } if (writev(fd, iovec, n) < 0) - log_debug("Failed to write to /dev/console for logging: %s", strerror(errno)); + log_debug("Failed to write to %s for logging: %s", tty, strerror(errno)); close_nointr_nofail(fd); finish: - free(tag_buf); + free(ident_buf); } -static void read_tag(const char **buf, char **tag) { +static void read_identifier(const char **buf, char **identifier, char **pid) { const char *p; char *t; size_t l, e; assert(buf); - assert(tag); + assert(identifier); + assert(pid); p = *buf; @@ -926,6 +1092,10 @@ static void read_tag(const char **buf, char **tag) { for (;;) { if (p[k] == '[') { + t = strndup(p+k+1, l-k-2); + if (t) + *pid = t; + l = k; break; } @@ -939,34 +1109,39 @@ static void read_tag(const char **buf, char **tag) { t = strndup(p, l); if (t) - *tag = t; + *identifier = t; *buf = p + e; *buf += strspn(*buf, WHITESPACE); } -static void process_syslog_message(Server *s, const char *buf, struct ucred *ucred, struct timeval *tv) { - char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_tag = NULL; - struct iovec iovec[N_IOVEC_META_FIELDS + 4]; +static void process_syslog_message(Server *s, const char *buf, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len) { + char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *syslog_pid = NULL; + struct iovec iovec[N_IOVEC_META_FIELDS + 6]; unsigned n = 0; int priority = LOG_USER | LOG_INFO; - char *tag = NULL; + char *identifier = NULL, *pid = NULL; + const char *orig; assert(s); assert(buf); + orig = buf; + parse_syslog_priority((char**) &buf, &priority); + if (s->forward_to_syslog) - forward_syslog_raw(s, buf, ucred, tv); + forward_syslog_raw(s, priority, orig, ucred, tv); - parse_syslog_priority((char**) &buf, &priority); skip_syslog_date((char**) &buf); - read_tag(&buf, &tag); + read_identifier(&buf, &identifier, &pid); if (s->forward_to_kmsg) - forward_kmsg(s, priority, tag, buf, ucred); + forward_kmsg(s, priority, identifier, buf, ucred); if (s->forward_to_console) - forward_console(s, tag, buf, ucred); + forward_console(s, priority, identifier, buf, ucred); + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog"); if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) IOVEC_SET_STRING(iovec[n++], syslog_priority); @@ -975,23 +1150,30 @@ static void process_syslog_message(Server *s, const char *buf, struct ucred *ucr if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) IOVEC_SET_STRING(iovec[n++], syslog_facility); - if (tag) { - syslog_tag = strappend("SYSLOG_TAG=", tag); - if (syslog_tag) - IOVEC_SET_STRING(iovec[n++], syslog_tag); + if (identifier) { + syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier); + if (syslog_identifier) + IOVEC_SET_STRING(iovec[n++], syslog_identifier); + } + + if (pid) { + syslog_pid = strappend("SYSLOG_PID=", pid); + if (syslog_pid) + IOVEC_SET_STRING(iovec[n++], syslog_pid); } message = strappend("MESSAGE=", buf); if (message) IOVEC_SET_STRING(iovec[n++], message); - dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, priority); + dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority); free(message); - free(tag); + free(identifier); + free(pid); free(syslog_priority); free(syslog_facility); - free(syslog_tag); + free(syslog_identifier); } static bool valid_user_field(const char *p, size_t l) { @@ -1029,16 +1211,22 @@ static bool valid_user_field(const char *p, size_t l) { return true; } -static void process_native_message(Server *s, const void *buffer, size_t buffer_size, struct ucred *ucred, struct timeval *tv) { +static void process_native_message( + Server *s, + const void *buffer, size_t buffer_size, + struct ucred *ucred, + struct timeval *tv, + const char *label, size_t label_len) { + struct iovec *iovec = NULL; - unsigned n = 0, m = 0, j; + unsigned n = 0, m = 0, j, tn = (unsigned) -1; const char *p; size_t remaining; int priority = LOG_INFO; - char *tag = NULL, *message = NULL; + char *identifier = NULL, *message = NULL; assert(s); - assert(buffer || n == 0); + assert(buffer || buffer_size == 0); p = buffer; remaining = buffer_size; @@ -1056,7 +1244,7 @@ static void process_native_message(Server *s, const void *buffer, size_t buffer_ if (e == p) { /* Entry separator */ - dispatch_message(s, iovec, n, m, ucred, tv, priority); + dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority); n = 0; priority = LOG_INFO; @@ -1079,7 +1267,7 @@ static void process_native_message(Server *s, const void *buffer, size_t buffer_ struct iovec *c; unsigned u; - u = MAX((n+N_IOVEC_META_FIELDS) * 2U, 4U); + u = MAX((n+N_IOVEC_META_FIELDS+1) * 2U, 4U); c = realloc(iovec, u * sizeof(struct iovec)); if (!c) { log_error("Out of memory"); @@ -1124,14 +1312,14 @@ static void process_native_message(Server *s, const void *buffer, size_t buffer_ p[17] >= '0' && p[17] <= '9') priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3); - else if (l >= 12 && - memcmp(p, "SYSLOG_TAG=", 11) == 0) { + else if (l >= 19 && + memcmp(p, "SYSLOG_IDENTIFIER=", 18) == 0) { char *t; - t = strndup(p + 11, l - 11); + t = strndup(p + 18, l - 18); if (t) { - free(tag); - tag = t; + free(identifier); + identifier = t; } } else if (l >= 8 && memcmp(p, "MESSAGE=", 8) == 0) { @@ -1149,6 +1337,7 @@ static void process_native_message(Server *s, const void *buffer, size_t buffer_ p = e + 1; continue; } else { + le64_t l_le; uint64_t l; char *k; @@ -1157,8 +1346,8 @@ static void process_native_message(Server *s, const void *buffer, size_t buffer_ break; } - memcpy(&l, e + 1, sizeof(uint64_t)); - l = le64toh(l); + memcpy(&l_le, e + 1, sizeof(uint64_t)); + l = le64toh(l_le); if (remaining < e - p + 1 + sizeof(uint64_t) + l + 1 || e[1+sizeof(uint64_t)+l] != '\n') { @@ -1188,50 +1377,121 @@ static void process_native_message(Server *s, const void *buffer, size_t buffer_ } } + if (n <= 0) + goto finish; + + tn = n++; + IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal"); + if (message) { if (s->forward_to_syslog) - forward_syslog(s, priority, tag, message, ucred, tv); + forward_syslog(s, priority, identifier, message, ucred, tv); if (s->forward_to_kmsg) - forward_kmsg(s, priority, tag, message, ucred); + forward_kmsg(s, priority, identifier, message, ucred); if (s->forward_to_console) - forward_console(s, tag, message, ucred); + forward_console(s, priority, identifier, message, ucred); } - dispatch_message(s, iovec, n, m, ucred, tv, priority); + dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority); + +finish: + for (j = 0; j < n; j++) { + if (j == tn) + continue; - for (j = 0; j < n; j++) if (iovec[j].iov_base < buffer || (const uint8_t*) iovec[j].iov_base >= (const uint8_t*) buffer + buffer_size) free(iovec[j].iov_base); + } - free(tag); + free(iovec); + free(identifier); free(message); } +static void process_native_file( + Server *s, + int fd, + struct ucred *ucred, + struct timeval *tv, + const char *label, size_t label_len) { + + struct stat st; + void *p; + ssize_t n; + + assert(s); + assert(fd >= 0); + + /* Data is in the passed file, since it didn't fit in a + * datagram. We can't map the file here, since clients might + * then truncate it and trigger a SIGBUS for us. So let's + * stupidly read it */ + + if (fstat(fd, &st) < 0) { + log_error("Failed to stat passed file, ignoring: %m"); + return; + } + + if (!S_ISREG(st.st_mode)) { + log_error("File passed is not regular. Ignoring."); + return; + } + + if (st.st_size <= 0) + return; + + if (st.st_size > ENTRY_SIZE_MAX) { + log_error("File passed too large. Ignoring."); + return; + } + + p = malloc(st.st_size); + if (!p) { + log_error("Out of memory"); + return; + } + + n = pread(fd, p, st.st_size, 0); + if (n < 0) + log_error("Failed to read file, ignoring: %s", strerror(-n)); + else if (n > 0) + process_native_message(s, p, n, ucred, tv, label, label_len); + + free(p); +} + static int stdout_stream_log(StdoutStream *s, const char *p) { - struct iovec iovec[N_IOVEC_META_FIELDS + 4]; - char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_tag = NULL; + struct iovec iovec[N_IOVEC_META_FIELDS + 5]; + char *message = NULL, *syslog_priority = NULL, *syslog_facility = NULL, *syslog_identifier = NULL; unsigned n = 0; int priority; + char *label = NULL; + size_t label_len = 0; assert(s); assert(p); + if (isempty(p)) + return 0; + priority = s->priority; - if (s->priority_prefix) + if (s->level_prefix) parse_syslog_priority((char**) &p, &priority); if (s->forward_to_syslog || s->server->forward_to_syslog) - forward_syslog(s->server, fixup_priority(priority), s->tag, p, &s->ucred, NULL); + forward_syslog(s->server, fixup_priority(priority), s->identifier, p, &s->ucred, NULL); if (s->forward_to_kmsg || s->server->forward_to_kmsg) - forward_kmsg(s->server, priority, s->tag, p, &s->ucred); + forward_kmsg(s->server, priority, s->identifier, p, &s->ucred); if (s->forward_to_console || s->server->forward_to_console) - forward_console(s->server, s->tag, p, &s->ucred); + forward_console(s->server, priority, s->identifier, p, &s->ucred); + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=stdout"); if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) IOVEC_SET_STRING(iovec[n++], syslog_priority); @@ -1240,22 +1500,29 @@ static int stdout_stream_log(StdoutStream *s, const char *p) { if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) IOVEC_SET_STRING(iovec[n++], syslog_facility); - if (s->tag) { - syslog_tag = strappend("SYSLOG_TAG=", s->tag); - if (syslog_tag) - IOVEC_SET_STRING(iovec[n++], syslog_tag); + if (s->identifier) { + syslog_identifier = strappend("SYSLOG_IDENTIFIER=", s->identifier); + if (syslog_identifier) + IOVEC_SET_STRING(iovec[n++], syslog_identifier); } message = strappend("MESSAGE=", p); if (message) IOVEC_SET_STRING(iovec[n++], message); - dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, priority); +#ifdef HAVE_SELINUX + if (s->security_context) { + label = (char*) s->security_context; + label_len = strlen((char*) s->security_context); + } +#endif + + dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, label, label_len, s->unit_id, priority); free(message); free(syslog_priority); free(syslog_facility); - free(syslog_tag); + free(syslog_identifier); return 0; } @@ -1270,11 +1537,31 @@ static int stdout_stream_line(StdoutStream *s, char *p) { switch (s->state) { - case STDOUT_STREAM_TAG: - s->tag = strdup(p); - if (!s->tag) { - log_error("Out of memory"); - return -ENOMEM; + case STDOUT_STREAM_IDENTIFIER: + if (isempty(p)) + s->identifier = NULL; + else { + s->identifier = strdup(p); + if (!s->identifier) { + log_error("Out of memory"); + return -ENOMEM; + } + } + + s->state = STDOUT_STREAM_UNIT_ID; + return 0; + + case STDOUT_STREAM_UNIT_ID: + if (s->ucred.uid == 0) { + if (isempty(p)) + s->unit_id = NULL; + else { + s->unit_id = strdup(p); + if (!s->unit_id) { + log_error("Out of memory"); + return -ENOMEM; + } + } } s->state = STDOUT_STREAM_PRIORITY; @@ -1287,17 +1574,17 @@ static int stdout_stream_line(StdoutStream *s, char *p) { return -EINVAL; } - s->state = STDOUT_STREAM_PRIORITY_PREFIX; + s->state = STDOUT_STREAM_LEVEL_PREFIX; return 0; - case STDOUT_STREAM_PRIORITY_PREFIX: + case STDOUT_STREAM_LEVEL_PREFIX: r = parse_boolean(p); if (r < 0) { - log_warning("Failed to parse priority prefix line."); + log_warning("Failed to parse level prefix line."); return -EINVAL; } - s->priority_prefix = !!r; + s->level_prefix = !!r; s->state = STDOUT_STREAM_FORWARD_TO_SYSLOG; return 0; @@ -1359,7 +1646,7 @@ static int stdout_stream_scan(StdoutStream *s, bool force_flush) { skip = end - p + 1; else if (remaining >= sizeof(s->buffer) - 1) { end = p + sizeof(s->buffer) - 1; - skip = sizeof(s->buffer) - 1; + skip = remaining; } else break; @@ -1440,7 +1727,12 @@ static void stdout_stream_free(StdoutStream *s) { close_nointr_nofail(s->fd); } - free(s->tag); +#ifdef HAVE_SELINUX + if (s->security_context) + freecon(s->security_context); +#endif + + free(s->identifier); free(s); } @@ -1483,6 +1775,11 @@ static int stdout_stream_new(Server *s) { goto fail; } +#ifdef HAVE_SELINUX + if (getpeercon(fd, &stream->security_context) < 0 && errno != ENOPROTOOPT) + log_error("Failed to determine peer security context: %m"); +#endif + if (shutdown(fd, SHUT_WR) < 0) { log_error("Failed to shutdown writing side of socket: %m"); r = -errno; @@ -1509,6 +1806,178 @@ fail: return r; } +static int parse_kernel_timestamp(char **_p, usec_t *t) { + usec_t r; + int k, i; + char *p; + + assert(_p); + assert(*_p); + assert(t); + + p = *_p; + + if (strlen(p) < 14 || p[0] != '[' || p[13] != ']' || p[6] != '.') + return 0; + + r = 0; + + for (i = 1; i <= 5; i++) { + r *= 10; + + if (p[i] == ' ') + continue; + + k = undecchar(p[i]); + if (k < 0) + return 0; + + r += k; + } + + for (i = 7; i <= 12; i++) { + r *= 10; + + k = undecchar(p[i]); + if (k < 0) + return 0; + + r += k; + } + + *t = r; + *_p += 14; + *_p += strspn(*_p, WHITESPACE); + + return 1; +} + +static bool is_us(const char *pid) { + pid_t t; + + assert(pid); + + if (parse_pid(pid, &t) < 0) + return false; + + return t == getpid(); +} + +static void proc_kmsg_line(Server *s, const char *p) { + struct iovec iovec[N_IOVEC_META_FIELDS + 7]; + char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL; + int priority = LOG_KERN | LOG_INFO; + unsigned n = 0; + usec_t usec; + char *identifier = NULL, *pid = NULL; + + assert(s); + assert(p); + + if (isempty(p)) + return; + + parse_syslog_priority((char **) &p, &priority); + + if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN) + return; + + if (parse_kernel_timestamp((char **) &p, &usec) > 0) { + if (asprintf(&source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", + (unsigned long long) usec) >= 0) + IOVEC_SET_STRING(iovec[n++], source_time); + } + + IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=kernel"); + + if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_priority); + + if ((priority & LOG_FACMASK) == LOG_KERN) { + + if (s->forward_to_syslog) + forward_syslog(s, priority, "kernel", p, NULL, NULL); + + IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=kernel"); + } else { + read_identifier(&p, &identifier, &pid); + + /* Avoid any messages we generated ourselves via + * log_info() and friends. */ + if (pid && is_us(pid)) + goto finish; + + if (s->forward_to_syslog) + forward_syslog(s, priority, identifier, p, NULL, NULL); + + if (identifier) { + syslog_identifier = strappend("SYSLOG_IDENTIFIER=", identifier); + if (syslog_identifier) + IOVEC_SET_STRING(iovec[n++], syslog_identifier); + } + + if (pid) { + syslog_pid = strappend("SYSLOG_PID=", pid); + if (syslog_pid) + IOVEC_SET_STRING(iovec[n++], syslog_pid); + } + + if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) + IOVEC_SET_STRING(iovec[n++], syslog_facility); + } + + message = strappend("MESSAGE=", p); + if (message) + IOVEC_SET_STRING(iovec[n++], message); + + dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority); + +finish: + free(message); + free(syslog_priority); + free(syslog_identifier); + free(syslog_pid); + free(syslog_facility); + free(source_time); + free(identifier); + free(pid); +} + +static void proc_kmsg_scan(Server *s) { + char *p; + size_t remaining; + + assert(s); + + p = s->proc_kmsg_buffer; + remaining = s->proc_kmsg_length; + for (;;) { + char *end; + size_t skip; + + end = memchr(p, '\n', remaining); + if (end) + skip = end - p + 1; + else if (remaining >= sizeof(s->proc_kmsg_buffer) - 1) { + end = p + sizeof(s->proc_kmsg_buffer) - 1; + skip = remaining; + } else + break; + + *end = 0; + + proc_kmsg_line(s, p); + + remaining -= skip; + p += skip; + } + + if (p > s->proc_kmsg_buffer) { + memmove(s->proc_kmsg_buffer, p, remaining); + s->proc_kmsg_length = remaining; + } +} + static int system_journal_open(Server *s) { int r; char *fn; @@ -1521,30 +1990,37 @@ static int system_journal_open(Server *s) { sd_id128_to_string(machine, ids); - if (!s->system_journal) { + if (!s->system_journal && + (s->storage == STORAGE_PERSISTENT || s->storage == STORAGE_AUTO) && + access("/run/systemd/journal/flushed", F_OK) >= 0) { + + /* If in auto mode: first try to create the machine + * path, but not the prefix. + * + * If in persistent mode: create /var/log/journal and + * the machine path */ + + if (s->storage == STORAGE_PERSISTENT) + (void) mkdir("/var/log/journal/", 0755); - /* First try to create the machine path, but not the prefix */ fn = strappend("/var/log/journal/", ids); if (!fn) return -ENOMEM; + (void) mkdir(fn, 0755); free(fn); - /* The create the system journal file */ - fn = join("/var/log/journal/", ids, "/system.journal", NULL); + fn = strjoin("/var/log/journal/", ids, "/system.journal", NULL); if (!fn) return -ENOMEM; - r = journal_file_open(fn, O_RDWR|O_CREAT, 0640, NULL, &s->system_journal); + r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, &s->system_metrics, NULL, &s->system_journal); free(fn); if (r >= 0) { - journal_default_metrics(&s->system_metrics, s->system_journal->fd); - - s->system_journal->metrics = s->system_metrics; s->system_journal->compress = s->compress; - fix_perms(s->system_journal, 0); + server_fix_perms(s, s->system_journal, 0); } else if (r < 0) { if (r != -ENOENT && r != -EROFS) @@ -1554,9 +2030,10 @@ static int system_journal_open(Server *s) { } } - if (!s->runtime_journal) { + if (!s->runtime_journal && + (s->storage != STORAGE_NONE)) { - fn = join("/run/log/journal/", ids, "/system.journal", NULL); + fn = strjoin("/run/log/journal/", ids, "/system.journal", NULL); if (!fn) return -ENOMEM; @@ -1566,7 +2043,7 @@ static int system_journal_open(Server *s) { * if it already exists, so that we can flush * it into the system journal */ - r = journal_file_open(fn, O_RDWR, 0640, NULL, &s->runtime_journal); + r = journal_file_open(fn, O_RDWR, 0640, &s->runtime_metrics, NULL, &s->runtime_journal); free(fn); if (r < 0) { @@ -1582,7 +2059,7 @@ static int system_journal_open(Server *s) { * it if necessary. */ (void) mkdir_parents(fn, 0755); - r = journal_file_open(fn, O_RDWR|O_CREAT, 0640, NULL, &s->runtime_journal); + r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, &s->runtime_metrics, NULL, &s->runtime_journal); free(fn); if (r < 0) { @@ -1592,12 +2069,9 @@ static int system_journal_open(Server *s) { } if (s->runtime_journal) { - journal_default_metrics(&s->runtime_metrics, s->runtime_journal->fd); - - s->runtime_journal->metrics = s->runtime_metrics; s->runtime_journal->compress = s->compress; - fix_perms(s->runtime_journal, 0); + server_fix_perms(s, s->runtime_journal, 0); } } @@ -1605,29 +2079,27 @@ static int system_journal_open(Server *s) { } static int server_flush_to_var(Server *s) { - char path[] = "/run/log/journal/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; Object *o = NULL; int r; sd_id128_t machine; sd_journal *j; - usec_t ts; assert(s); - if (!s->runtime_journal) + if (s->storage != STORAGE_AUTO && + s->storage != STORAGE_PERSISTENT) return 0; - ts = now(CLOCK_MONOTONIC); - if (s->var_available_timestamp + RECHECK_VAR_AVAILABLE_USEC > ts) + if (!s->runtime_journal) return 0; - s->var_available_timestamp = ts; - system_journal_open(s); if (!s->system_journal) return 0; + log_info("Flushing to /var..."); + r = sd_id128_get_machine(&machine); if (r < 0) { log_error("Failed to get machine id: %s", strerror(-r)); @@ -1675,16 +2147,62 @@ finish: journal_file_close(s->runtime_journal); s->runtime_journal = NULL; - if (r >= 0) { - sd_id128_to_string(machine, path + 17); - rm_rf(path, false, true, false); - } + if (r >= 0) + rm_rf("/run/log/journal", false, true, false); return r; } +static int server_read_proc_kmsg(Server *s) { + ssize_t l; + assert(s); + assert(s->proc_kmsg_fd >= 0); + + l = read(s->proc_kmsg_fd, s->proc_kmsg_buffer + s->proc_kmsg_length, sizeof(s->proc_kmsg_buffer) - 1 - s->proc_kmsg_length); + if (l == 0) /* the kernel is stupid and in some race + * conditions returns 0 in the middle of the + * stream. */ + return 0; + if (l < 0) { + + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_error("Failed to read from kernel: %m"); + return -errno; + } + + s->proc_kmsg_length += l; + + proc_kmsg_scan(s); + return 1; +} + +static int server_flush_proc_kmsg(Server *s) { + int r; + + assert(s); + + if (s->proc_kmsg_fd < 0) + return 0; + + log_info("Flushing /proc/kmsg..."); + + for (;;) { + r = server_read_proc_kmsg(s); + if (r < 0) + return r; + + if (r == 0) + break; + } + + return 0; +} + static int process_event(Server *s, struct epoll_event *ev) { assert(s); + assert(ev); if (ev->data.fd == s->signal_fd) { struct signalfd_siginfo sfsi; @@ -1702,19 +2220,40 @@ static int process_event(Server *s, struct epoll_event *ev) { return -EIO; if (errno == EINTR || errno == EAGAIN) - return 0; + return 1; return -errno; } if (sfsi.ssi_signo == SIGUSR1) { + touch("/run/systemd/journal/flushed"); server_flush_to_var(s); - return 0; + return 1; + } + + if (sfsi.ssi_signo == SIGUSR2) { + server_rotate(s); + server_vacuum(s); + return 1; } log_debug("Received SIG%s", signal_to_string(sfsi.ssi_signo)); return 0; + } else if (ev->data.fd == s->proc_kmsg_fd) { + int r; + + if (ev->events != EPOLLIN) { + log_info("Got invalid event from epoll."); + return -EIO; + } + + r = server_read_proc_kmsg(s); + if (r < 0) + return r; + + return 1; + } else if (ev->data.fd == s->native_fd || ev->data.fd == s->syslog_fd) { @@ -1729,22 +2268,35 @@ static int process_event(Server *s, struct epoll_event *ev) { struct ucred *ucred = NULL; struct timeval *tv = NULL; struct cmsghdr *cmsg; + char *label = NULL; + size_t label_len = 0; union { struct cmsghdr cmsghdr; + + /* We use NAME_MAX space for the + * SELinux label here. The kernel + * currently enforces no limit, but + * according to suggestions from the + * SELinux people this will change and + * it will probably be identical to + * NAME_MAX. For now we use that, but + * this should be updated one day when + * the final limit is known.*/ uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) + - CMSG_SPACE(sizeof(struct timeval))]; + CMSG_SPACE(sizeof(struct timeval)) + + CMSG_SPACE(sizeof(int)) + /* fd */ + CMSG_SPACE(NAME_MAX)]; /* selinux label */ } control; ssize_t n; int v; + int *fds = NULL; + unsigned n_fds = 0; if (ioctl(ev->data.fd, SIOCINQ, &v) < 0) { log_error("SIOCINQ failed: %m"); return -errno; } - if (v <= 0) - return 1; - if (s->buffer_size < (size_t) v) { void *b; size_t l; @@ -1772,7 +2324,7 @@ static int process_event(Server *s, struct epoll_event *ev) { msghdr.msg_control = &control; msghdr.msg_controllen = sizeof(control); - n = recvmsg(ev->data.fd, &msghdr, MSG_DONTWAIT); + n = recvmsg(ev->data.fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); if (n < 0) { if (errno == EINTR || errno == EAGAIN) @@ -1789,23 +2341,44 @@ static int process_event(Server *s, struct epoll_event *ev) { cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) ucred = (struct ucred*) CMSG_DATA(cmsg); else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_SECURITY) { + label = (char*) CMSG_DATA(cmsg); + label_len = cmsg->cmsg_len - CMSG_LEN(0); + } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP && cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) tv = (struct timeval*) CMSG_DATA(cmsg); + else if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + fds = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + } } if (ev->data.fd == s->syslog_fd) { char *e; - e = memchr(s->buffer, '\n', n); - if (e) - *e = 0; - else - s->buffer[n] = 0; + if (n > 0 && n_fds == 0) { + e = memchr(s->buffer, '\n', n); + if (e) + *e = 0; + else + s->buffer[n] = 0; + + process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len); + } else if (n_fds > 0) + log_warning("Got file descriptors via syslog socket. Ignoring."); + + } else { + if (n > 0 && n_fds == 0) + process_native_message(s, s->buffer, n, ucred, tv, label, label_len); + else if (n == 0 && n_fds == 1) + process_native_file(s, fds[0], ucred, tv, label, label_len); + else if (n_fds > 0) + log_warning("Got too many file descriptors via native socket. Ignoring."); + } - process_syslog_message(s, strstrip(s->buffer), ucred, tv); - } else - process_native_message(s, s->buffer, n, ucred, tv); + close_many(fds, n_fds); } return 1; @@ -1851,13 +2424,12 @@ static int open_syslog_socket(Server *s) { union sockaddr_union sa; int one, r; struct epoll_event ev; - struct timeval tv; assert(s); if (s->syslog_fd < 0) { - s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (s->syslog_fd < 0) { log_error("socket() failed: %m"); return -errno; @@ -1876,7 +2448,8 @@ static int open_syslog_socket(Server *s) { } chmod(sa.un.sun_path, 0666); - } + } else + fd_nonblock(s->syslog_fd, 1); one = 1; r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); @@ -1885,6 +2458,13 @@ static int open_syslog_socket(Server *s) { return -errno; } +#ifdef HAVE_SELINUX + one = 1; + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); + if (r < 0) + log_warning("SO_PASSSEC failed: %m"); +#endif + one = 1; r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); if (r < 0) { @@ -1892,15 +2472,6 @@ static int open_syslog_socket(Server *s) { return -errno; } - /* Since we use the same socket for forwarding this to some - * other syslog implementation, make sure we don't hang - * forever */ - timeval_store(&tv, SYSLOG_TIMEOUT_USEC); - if (setsockopt(s->syslog_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) { - log_error("SO_SNDTIMEO failed: %m"); - return -errno; - } - zero(ev); ev.events = EPOLLIN; ev.data.fd = s->syslog_fd; @@ -1921,7 +2492,7 @@ static int open_native_socket(Server*s) { if (s->native_fd < 0) { - s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (s->native_fd < 0) { log_error("socket() failed: %m"); return -errno; @@ -1929,7 +2500,7 @@ static int open_native_socket(Server*s) { zero(sa); sa.un.sun_family = AF_UNIX; - strncpy(sa.un.sun_path, "/run/systemd/journal", sizeof(sa.un.sun_path)); + strncpy(sa.un.sun_path, "/run/systemd/journal/socket", sizeof(sa.un.sun_path)); unlink(sa.un.sun_path); @@ -1940,7 +2511,8 @@ static int open_native_socket(Server*s) { } chmod(sa.un.sun_path, 0666); - } + } else + fd_nonblock(s->native_fd, 1); one = 1; r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)); @@ -1949,6 +2521,13 @@ static int open_native_socket(Server*s) { return -errno; } +#ifdef HAVE_SELINUX + one = 1; + r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)); + if (r < 0) + log_warning("SO_PASSSEC failed: %m"); +#endif + one = 1; r = setsockopt(s->native_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); if (r < 0) { @@ -1976,7 +2555,7 @@ static int open_stdout_socket(Server *s) { if (s->stdout_fd < 0) { - s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (s->stdout_fd < 0) { log_error("socket() failed: %m"); return -errno; @@ -1984,7 +2563,7 @@ static int open_stdout_socket(Server *s) { zero(sa); sa.un.sun_family = AF_UNIX; - strncpy(sa.un.sun_path, "/run/systemd/stdout", sizeof(sa.un.sun_path)); + strncpy(sa.un.sun_path, "/run/systemd/journal/stdout", sizeof(sa.un.sun_path)); unlink(sa.un.sun_path); @@ -2000,7 +2579,8 @@ static int open_stdout_socket(Server *s) { log_error("liste() failed: %m"); return -errno; } - } + } else + fd_nonblock(s->stdout_fd, 1); zero(ev); ev.events = EPOLLIN; @@ -2013,6 +2593,31 @@ static int open_stdout_socket(Server *s) { return 0; } +static int open_proc_kmsg(Server *s) { + struct epoll_event ev; + + assert(s); + + if (!s->import_proc_kmsg) + return 0; + + s->proc_kmsg_fd = open("/proc/kmsg", O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (s->proc_kmsg_fd < 0) { + log_warning("Failed to open /proc/kmsg, ignoring: %m"); + return 0; + } + + zero(ev); + ev.events = EPOLLIN; + ev.data.fd = s->proc_kmsg_fd; + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->proc_kmsg_fd, &ev) < 0) { + log_error("Failed to add /proc/kmsg fd to epoll object: %m"); + return -errno; + } + + return 0; +} + static int open_signalfd(Server *s) { sigset_t mask; struct epoll_event ev; @@ -2020,7 +2625,7 @@ static int open_signalfd(Server *s) { assert(s); assert_se(sigemptyset(&mask) == 0); - sigset_add_many(&mask, SIGINT, SIGTERM, SIGUSR1, -1); + sigset_add_many(&mask, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, -1); assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); s->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); @@ -2041,6 +2646,60 @@ static int open_signalfd(Server *s) { return 0; } +static int server_parse_proc_cmdline(Server *s) { + char *line, *w, *state; + int r; + size_t l; + + if (detect_container(NULL) > 0) + return 0; + + r = read_one_line_file("/proc/cmdline", &line); + if (r < 0) { + log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r)); + return 0; + } + + FOREACH_WORD_QUOTED(w, l, line, state) { + char *word; + + word = strndup(w, l); + if (!word) { + r = -ENOMEM; + goto finish; + } + + if (startswith(word, "systemd.journald.forward_to_syslog=")) { + r = parse_boolean(word + 35); + if (r < 0) + log_warning("Failed to parse forward to syslog switch %s. Ignoring.", word + 35); + else + s->forward_to_syslog = r; + } else if (startswith(word, "systemd.journald.forward_to_kmsg=")) { + r = parse_boolean(word + 33); + if (r < 0) + log_warning("Failed to parse forward to kmsg switch %s. Ignoring.", word + 33); + else + s->forward_to_kmsg = r; + } else if (startswith(word, "systemd.journald.forward_to_console=")) { + r = parse_boolean(word + 36); + if (r < 0) + log_warning("Failed to parse forward to console switch %s. Ignoring.", word + 36); + else + s->forward_to_console = r; + } else if (startswith(word, "systemd.journald")) + log_warning("Invalid systemd.journald parameter. Ignoring."); + + free(word); + } + + r = 0; + +finish: + free(line); + return r; +} + static int server_parse_config_file(Server *s) { FILE *f; const char *fn; @@ -2048,7 +2707,7 @@ static int server_parse_config_file(Server *s) { assert(s); - fn = "/etc/systemd/systemd-journald.conf"; + fn = "/etc/systemd/journald.conf"; f = fopen(fn, "re"); if (!f) { if (errno == ENOENT) @@ -2073,7 +2732,7 @@ static int server_init(Server *s) { assert(s); zero(*s); - s->syslog_fd = s->native_fd = s->stdout_fd = s->signal_fd = s->epoll_fd = -1; + s->syslog_fd = s->native_fd = s->stdout_fd = s->signal_fd = s->epoll_fd = s->proc_kmsg_fd = -1; s->compress = true; s->rate_limit_interval = DEFAULT_RATE_LIMIT_INTERVAL; @@ -2081,10 +2740,16 @@ static int server_init(Server *s) { s->forward_to_syslog = true; + s->max_level_store = LOG_DEBUG; + s->max_level_syslog = LOG_DEBUG; + s->max_level_kmsg = LOG_NOTICE; + s->max_level_console = LOG_INFO; + memset(&s->system_metrics, 0xFF, sizeof(s->system_metrics)); memset(&s->runtime_metrics, 0xFF, sizeof(s->runtime_metrics)); server_parse_config_file(s); + server_parse_proc_cmdline(s); s->user_journals = hashmap_new(trivial_hash_func, trivial_compare_func); if (!s->user_journals) { @@ -2106,7 +2771,7 @@ static int server_init(Server *s) { for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { - if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/native", 0) > 0) { + if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) { if (s->native_fd >= 0) { log_error("Too many native sockets passed."); @@ -2115,7 +2780,7 @@ static int server_init(Server *s) { s->native_fd = fd; - } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/stdout", 0) > 0) { + } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) { if (s->stdout_fd >= 0) { log_error("Too many stdout sockets passed."); @@ -2151,7 +2816,7 @@ static int server_init(Server *s) { if (r < 0) return r; - r = system_journal_open(s); + r = open_proc_kmsg(s); if (r < 0) return r; @@ -2163,6 +2828,10 @@ static int server_init(Server *s) { if (!s->rate_limit) return -ENOMEM; + r = system_journal_open(s); + if (r < 0) + return r; + return 0; } @@ -2199,10 +2868,14 @@ static void server_done(Server *s) { if (s->stdout_fd >= 0) close_nointr_nofail(s->stdout_fd); + if (s->proc_kmsg_fd >= 0) + close_nointr_nofail(s->proc_kmsg_fd); + if (s->rate_limit) journal_rate_limit_free(s->rate_limit); free(s->buffer); + free(s->tty_path); } int main(int argc, char *argv[]) { @@ -2219,7 +2892,8 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - log_set_target(LOG_TARGET_CONSOLE); + log_set_target(LOG_TARGET_SAFE); + log_set_facility(LOG_SYSLOG); log_parse_environment(); log_open(); @@ -2231,6 +2905,7 @@ int main(int argc, char *argv[]) { server_vacuum(&server); server_flush_to_var(&server); + server_flush_proc_kmsg(&server); log_debug("systemd-journald running as pid %lu", (unsigned long) getpid()); driver_message(&server, SD_MESSAGE_JOURNAL_START, "Journal started");