X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fjournal%2Fjournald.c;h=d431953f0701ef148b0977aef104e3f54ba1a7eb;hp=7ea68aab3cac7dd4c4de4ea4c4f45a28218516c8;hb=0284adc6a60ce0af1107cb0b50041a65d731f39e;hpb=66a78c2b95ba6cc0be15dab68c5af816fb5b7a33 diff --git a/src/journal/journald.c b/src/journal/journald.c index 7ea68aab3..d431953f0 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,7 @@ #include "list.h" #include "journal-rate-limit.h" #include "journal-internal.h" +#include "journal-vacuum.h" #include "conf-parser.h" #include "journald.h" #include "virt.h" @@ -69,9 +71,8 @@ #define RECHECK_AVAILABLE_SPACE_USEC (30*USEC_PER_SEC) -#define RECHECK_VAR_AVAILABLE_USEC (30*USEC_PER_SEC) - #define N_IOVEC_META_FIELDS 17 +#define N_IOVEC_KERNEL_FIELDS 64 #define ENTRY_SIZE_MAX (1024*1024*32) @@ -111,7 +112,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; @@ -273,7 +282,6 @@ static JournalFile* find_journal(Server *s, uid_t uid) { char *p; int r; JournalFile *f; - char ids[33]; sd_id128_t machine; assert(s); @@ -297,7 +305,8 @@ static JournalFile* find_journal(Server *s, uid_t uid) { if (f) return f; - if (asprintf(&p, "/var/log/journal/%s/user-%lu.journal", sd_id128_to_string(machine, ids), (unsigned long) uid) < 0) + if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/user-%lu.journal", + SD_ID128_FORMAT_VAL(machine), (unsigned long) uid) < 0) return s->system_journal; while (hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) { @@ -307,7 +316,7 @@ static JournalFile* find_journal(Server *s, uid_t uid) { journal_file_close(f); } - r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->system_journal, &f); + r = journal_file_open_reliably(p, O_RDWR|O_CREAT, 0640, s->compress, false, &s->system_metrics, s->mmap, s->system_journal, &f); free(p); if (r < 0) @@ -333,7 +342,7 @@ static void server_rotate(Server *s) { log_info("Rotating..."); if (s->runtime_journal) { - r = journal_file_rotate(&s->runtime_journal); + r = journal_file_rotate(&s->runtime_journal, s->compress, false); if (r < 0) if (s->runtime_journal) log_error("Failed to rotate %s: %s", s->runtime_journal->path, strerror(-r)); @@ -344,7 +353,7 @@ static void server_rotate(Server *s) { } if (s->system_journal) { - r = journal_file_rotate(&s->system_journal); + r = journal_file_rotate(&s->system_journal, s->compress, true); if (r < 0) if (s->system_journal) log_error("Failed to rotate %s: %s", s->system_journal->path, strerror(-r)); @@ -356,7 +365,7 @@ static void server_rotate(Server *s) { } HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) { - r = journal_file_rotate(&f); + r = journal_file_rotate(&f, s->compress, false); if (r < 0) if (f->path) log_error("Failed to rotate %s: %s", f->path, strerror(-r)); @@ -387,7 +396,7 @@ static void server_vacuum(Server *s) { if (s->system_journal) { if (asprintf(&p, "/var/log/journal/%s", ids) < 0) { - log_error("Out of memory."); + log_oom(); return; } @@ -397,10 +406,9 @@ 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."); + log_oom(); return; } @@ -455,6 +463,71 @@ static char *shortened_cgroup_path(pid_t pid) { return path; } +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, @@ -475,8 +548,6 @@ static void dispatch_message_real( int r; char *t; uid_t loginuid = 0, realuid = 0; - JournalFile *f; - bool vacuumed = false; assert(s); assert(iovec); @@ -621,41 +692,7 @@ static void dispatch_message_real( 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 || /* 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 */ - !vacuumed) { - - if (r == -E2BIG) - log_info("Allocation limit reached, rotating."); - else - log_warning("Journal file corrupted, rotating."); - - 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); @@ -687,7 +724,7 @@ static void driver_message(Server *s, sd_id128_t message_id, const char *format, assert(s); assert(format); - IOVEC_SET_STRING(iovec[n++], "PRIORITY=5"); + IOVEC_SET_STRING(iovec[n++], "PRIORITY=6"); IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=driver"); memcpy(buffer, "MESSAGE=", 8); @@ -834,7 +871,8 @@ static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned 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, int priority, const char *buffer, struct ucred *ucred, struct timeval *tv) { @@ -920,14 +958,16 @@ static void forward_kmsg(Server *s, int priority, const char *identifier, const char header_priority[6], header_pid[16]; int n = 0; char *ident_buf = NULL; - int fd; assert(s); assert(priority >= 0); assert(priority <= 999); assert(message); - if (LOG_PRI(priority) > s->max_level_kmsg) + if (_unlikely_(LOG_PRI(priority) > s->max_level_kmsg)) + return; + + if (_unlikely_(s->dev_kmsg_fd < 0)) return; /* Never allow messages with kernel facility to be written to @@ -962,18 +1002,9 @@ static void forward_kmsg(Server *s, int priority, const char *identifier, const IOVEC_SET_STRING(iovec[n++], message); IOVEC_SET_STRING(iovec[n++], "\n"); - fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - log_debug("Failed to open /dev/kmsg for logging: %s", strerror(errno)); - goto finish; - } - - if (writev(fd, iovec, n) < 0) + if (writev(s->dev_kmsg_fd, iovec, n) < 0) log_debug("Failed to write to /dev/kmsg for logging: %s", strerror(errno)); - close_nointr_nofail(fd); - -finish: free(ident_buf); } @@ -1139,6 +1170,7 @@ static void process_syslog_message(Server *s, const char *buf, struct ucred *ucr free(syslog_priority); free(syslog_facility); free(syslog_identifier); + free(syslog_pid); } static bool valid_user_field(const char *p, size_t l) { @@ -1235,7 +1267,7 @@ static void process_native_message( u = MAX((n+N_IOVEC_META_FIELDS+1) * 2U, 4U); c = realloc(iovec, u * sizeof(struct iovec)); if (!c) { - log_error("Out of memory"); + log_oom(); break; } @@ -1322,7 +1354,7 @@ static void process_native_message( k = malloc((e - p) + 1 + l); if (!k) { - log_error("Out of memory"); + log_oom(); break; } @@ -1415,7 +1447,7 @@ static void process_native_file( p = malloc(st.st_size); if (!p) { - log_error("Out of memory"); + log_oom(); return; } @@ -1507,10 +1539,8 @@ static int stdout_stream_line(StdoutStream *s, char *p) { s->identifier = NULL; else { s->identifier = strdup(p); - if (!s->identifier) { - log_error("Out of memory"); - return -ENOMEM; - } + if (!s->identifier) + return log_oom(); } s->state = STDOUT_STREAM_UNIT_ID; @@ -1522,10 +1552,8 @@ static int stdout_stream_line(StdoutStream *s, char *p) { s->unit_id = NULL; else { s->unit_id = strdup(p); - if (!s->unit_id) { - log_error("Out of memory"); - return -ENOMEM; - } + if (!s->unit_id) + return log_oom(); } } @@ -1726,9 +1754,8 @@ static int stdout_stream_new(Server *s) { stream = new0(StdoutStream, 1); if (!stream) { - log_error("Out of memory."); close_nointr_nofail(fd); - return -ENOMEM; + return log_oom(); } stream->fd = fd; @@ -1771,110 +1798,143 @@ fail: return r; } -static int parse_kernel_timestamp(char **_p, usec_t *t) { - usec_t r; - int k, i; - char *p; +static bool is_us(const char *pid) { + pid_t t; - assert(_p); - assert(*_p); - assert(t); + assert(pid); - p = *_p; + if (parse_pid(pid, &t) < 0) + return false; - if (strlen(p) < 14 || p[0] != '[' || p[13] != ']' || p[6] != '.') - return 0; + return t == getpid(); +} - r = 0; +static void dev_kmsg_record(Server *s, char *p, size_t l) { + struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_KERNEL_FIELDS]; + char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL; + int priority, r; + unsigned n = 0, z = 0, j; + usec_t usec; + char *identifier = NULL, *pid = NULL, *e, *f, *k; + uint64_t serial; + size_t pl; - for (i = 1; i <= 5; i++) { - r *= 10; + assert(s); + assert(p); - if (p[i] == ' ') - continue; + if (l <= 0) + return; - k = undecchar(p[i]); - if (k < 0) - return 0; + e = memchr(p, ',', l); + if (!e) + return; + *e = 0; - r += k; - } + r = safe_atoi(p, &priority); + if (r < 0 || priority < 0 || priority > 999) + return; + + if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN) + return; - for (i = 7; i <= 12; i++) { - r *= 10; + l -= (e - p) + 1; + p = e + 1; + e = memchr(p, ',', l); + if (!e) + return; + *e = 0; - k = undecchar(p[i]); - if (k < 0) - return 0; + r = safe_atou64(p, &serial); + if (r < 0) + return; + + if (s->kernel_seqnum) { + /* We already read this one? */ + if (serial < *s->kernel_seqnum) + return; - r += k; + /* Did we lose any? */ + if (serial > *s->kernel_seqnum) + driver_message(s, SD_MESSAGE_JOURNAL_MISSED, "Missed %llu kernel messages", (unsigned long long) serial - *s->kernel_seqnum - 1); + + /* Make sure we never read this one again. Note that + * we always store the next message serial we expect + * here, simply because this makes handling the first + * message with serial 0 easy. */ + *s->kernel_seqnum = serial + 1; } - *t = r; - *_p += 14; - *_p += strspn(*_p, WHITESPACE); + l -= (e - p) + 1; + p = e + 1; + f = memchr(p, ';', l); + if (!f) + return; + /* Kernel 3.6 has the flags field, kernel 3.5 lacks that */ + e = memchr(p, ',', l); + if (!e || f < e) + e = f; + *e = 0; - return 1; -} + r = parse_usec(p, &usec); + if (r < 0) + return; -static bool is_us(const char *pid) { - pid_t t; + l -= (f - p) + 1; + p = f + 1; + e = memchr(p, '\n', l); + if (!e) + return; + *e = 0; - assert(pid); + pl = e - p; + l -= (e - p) + 1; + k = e + 1; - if (parse_pid(pid, &t) < 0) - return false; + for (j = 0; l > 0 && j < N_IOVEC_KERNEL_FIELDS; j++) { + char *m; + /* Meta data fields attached */ - return t == getpid(); -} + if (*k != ' ') + break; -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; + k ++, l --; - assert(s); - assert(p); + e = memchr(k, '\n', l); + if (!e) + return; - if (isempty(p)) - return; + *e = 0; - parse_syslog_priority((char **) &p, &priority); + m = cunescape_length_with_prefix(k, e - k, "_KERNEL_"); + if (!m) + break; - if (s->forward_to_kmsg && (priority & LOG_FACMASK) != LOG_KERN) - return; + IOVEC_SET_STRING(iovec[n++], m); + z++; - 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); + l -= (e - k) + 1; + k = e + 1; } + 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); - + if ((priority & LOG_FACMASK) == LOG_KERN) IOVEC_SET_STRING(iovec[n++], "SYSLOG_IDENTIFIER=kernel"); - } else { - read_identifier(&p, &identifier, &pid); + else { + read_identifier((const char**) &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) @@ -1891,13 +1951,16 @@ static void proc_kmsg_line(Server *s, const char *p) { IOVEC_SET_STRING(iovec[n++], syslog_facility); } - message = strappend("MESSAGE=", p); + message = cunescape_length_with_prefix(p, pl, "MESSAGE="); if (message) IOVEC_SET_STRING(iovec[n++], message); dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority); finish: + for (j = 0; j < z; j++) + free(iovec[j].iov_base); + free(message); free(syslog_priority); free(syslog_identifier); @@ -1908,41 +1971,6 @@ finish: 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; @@ -1955,31 +1983,36 @@ 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_reliably(fn, O_RDWR|O_CREAT, 0640, NULL, &s->system_journal); + r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, true, &s->system_metrics, s->mmap, 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; - + if (r >= 0) server_fix_perms(s, s->system_journal, 0); - } else if (r < 0) { + else if (r < 0) { if (r != -ENOENT && r != -EROFS) log_warning("Failed to open system journal: %s", strerror(-r)); @@ -1988,9 +2021,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; @@ -2000,7 +2034,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->compress, false, &s->runtime_metrics, s->mmap, NULL, &s->runtime_journal); free(fn); if (r < 0) { @@ -2015,8 +2049,8 @@ static int system_journal_open(Server *s) { /* OK, we really need the runtime journal, so create * it if necessary. */ - (void) mkdir_parents_label(fn, 0755); - r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, NULL, &s->runtime_journal); + (void) mkdir_parents(fn, 0755); + r = journal_file_open_reliably(fn, O_RDWR|O_CREAT, 0640, s->compress, false, &s->runtime_metrics, s->mmap, NULL, &s->runtime_journal); free(fn); if (r < 0) { @@ -2025,38 +2059,28 @@ 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; - + if (s->runtime_journal) server_fix_perms(s, s->runtime_journal, 0); - } } return r; } 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) @@ -2111,21 +2135,30 @@ 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) { +static int server_read_dev_kmsg(Server *s) { + char buffer[8192+1]; /* the kernel-side limit per record is 8K currently */ ssize_t l; + assert(s); - assert(s->proc_kmsg_fd >= 0); + assert(s->dev_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); + l = read(s->dev_kmsg_fd, buffer, sizeof(buffer) - 1); + if (l == 0) + return 0; if (l < 0) { + /* Old kernels who don't allow reading from /dev/kmsg + * return EINVAL when we try. So handle this cleanly, + * but don' try to ever read from it again. */ + if (errno == EINVAL) { + epoll_ctl(s->epoll_fd, EPOLL_CTL_DEL, s->dev_kmsg_fd, NULL); + return 0; + } if (errno == EAGAIN || errno == EINTR) return 0; @@ -2134,24 +2167,25 @@ static int server_read_proc_kmsg(Server *s) { return -errno; } - s->proc_kmsg_length += l; - - proc_kmsg_scan(s); + dev_kmsg_record(s, buffer, l); return 1; } -static int server_flush_proc_kmsg(Server *s) { +static int server_flush_dev_kmsg(Server *s) { int r; assert(s); - if (s->proc_kmsg_fd < 0) + if (s->dev_kmsg_fd < 0) + return 0; + + if (!s->dev_kmsg_readable) return 0; - log_info("Flushing /proc/kmsg..."); + log_info("Flushing /dev/kmsg..."); for (;;) { - r = server_read_proc_kmsg(s); + r = server_read_dev_kmsg(s); if (r < 0) return r; @@ -2187,7 +2221,10 @@ static int process_event(Server *s, struct epoll_event *ev) { return -errno; } + log_info("Received SIG%s", signal_to_string(sfsi.ssi_signo)); + if (sfsi.ssi_signo == SIGUSR1) { + touch("/run/systemd/journal/flushed"); server_flush_to_var(s); return 1; } @@ -2198,10 +2235,9 @@ static int process_event(Server *s, struct epoll_event *ev) { return 1; } - log_debug("Received SIG%s", signal_to_string(sfsi.ssi_signo)); return 0; - } else if (ev->data.fd == s->proc_kmsg_fd) { + } else if (ev->data.fd == s->dev_kmsg_fd) { int r; if (ev->events != EPOLLIN) { @@ -2209,7 +2245,7 @@ static int process_event(Server *s, struct epoll_event *ev) { return -EIO; } - r = server_read_proc_kmsg(s); + r = server_read_dev_kmsg(s); if (r < 0) return r; @@ -2554,28 +2590,68 @@ static int open_stdout_socket(Server *s) { return 0; } -static int open_proc_kmsg(Server *s) { +static int open_dev_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"); + s->dev_kmsg_fd = open("/dev/kmsg", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (s->dev_kmsg_fd < 0) { + log_warning("Failed to open /dev/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"); + ev.data.fd = s->dev_kmsg_fd; + if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->dev_kmsg_fd, &ev) < 0) { + + /* This will fail with EPERM on older kernels where + * /dev/kmsg is not readable. */ + if (errno == EPERM) + return 0; + + log_error("Failed to add /dev/kmsg fd to epoll object: %m"); return -errno; } + s->dev_kmsg_readable = true; + + return 0; +} + +static int open_kernel_seqnum(Server *s) { + int fd; + uint64_t *p; + + assert(s); + + /* We store the seqnum we last read in an mmaped file. That + * way we can just use it like a variable, but it is + * persistant and automatically flushed at reboot. */ + + fd = open("/run/systemd/journal/kernel-seqnum", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); + if (fd < 0) { + log_error("Failed to open /run/systemd/journal/kernel-seqnum, ignoring: %m"); + return 0; + } + + if (posix_fallocate(fd, 0, sizeof(uint64_t)) < 0) { + log_error("Failed to allocate sequential number file, ignoring: %m"); + close_nointr_nofail(fd); + return 0; + } + + p = mmap(NULL, sizeof(uint64_t), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) { + log_error("Failed to map sequential number file, ignoring: %m"); + close_nointr_nofail(fd); + return 0; + } + + close_nointr_nofail(fd); + s->kernel_seqnum = p; + return 0; } @@ -2693,14 +2769,13 @@ 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 = s->proc_kmsg_fd = -1; + s->syslog_fd = s->native_fd = s->stdout_fd = s->signal_fd = s->epoll_fd = s->dev_kmsg_fd = -1; s->compress = true; s->rate_limit_interval = DEFAULT_RATE_LIMIT_INTERVAL; s->rate_limit_burst = DEFAULT_RATE_LIMIT_BURST; s->forward_to_syslog = true; - s->import_proc_kmsg = true; s->max_level_store = LOG_DEBUG; s->max_level_syslog = LOG_DEBUG; @@ -2713,11 +2788,15 @@ static int server_init(Server *s) { server_parse_config_file(s); server_parse_proc_cmdline(s); + mkdir_p("/run/systemd/journal", 0755); + s->user_journals = hashmap_new(trivial_hash_func, trivial_compare_func); - if (!s->user_journals) { - log_error("Out of memory."); - return -ENOMEM; - } + if (!s->user_journals) + return log_oom(); + + s->mmap = mmap_cache_new(_OBJECT_TYPE_MAX, USER_JOURNALS_MAX + 2); + if (!s->mmap) + return log_oom(); s->epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (s->epoll_fd < 0) { @@ -2778,7 +2857,11 @@ static int server_init(Server *s) { if (r < 0) return r; - r = open_proc_kmsg(s); + r = open_dev_kmsg(s); + if (r < 0) + return r; + + r = open_kernel_seqnum(s); if (r < 0) return r; @@ -2830,14 +2913,20 @@ 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->dev_kmsg_fd >= 0) + close_nointr_nofail(s->dev_kmsg_fd); if (s->rate_limit) journal_rate_limit_free(s->rate_limit); + if (s->kernel_seqnum) + munmap(s->kernel_seqnum, sizeof(uint64_t)); + free(s->buffer); free(s->tty_path); + + if (s->mmap) + mmap_cache_unref(s->mmap); } int main(int argc, char *argv[]) { @@ -2856,6 +2945,7 @@ int main(int argc, char *argv[]) { log_set_target(LOG_TARGET_SAFE); log_set_facility(LOG_SYSLOG); + log_set_max_level(LOG_DEBUG); log_parse_environment(); log_open(); @@ -2867,7 +2957,7 @@ int main(int argc, char *argv[]) { server_vacuum(&server); server_flush_to_var(&server); - server_flush_proc_kmsg(&server); + server_flush_dev_kmsg(&server); log_debug("systemd-journald running as pid %lu", (unsigned long) getpid()); driver_message(&server, SD_MESSAGE_JOURNAL_START, "Journal started");