/* SPDX-License-Identifier: LGPL-2.1+ */
-/***
- This file is part of systemd.
-
- Copyright 2011 Lennart Poettering
-
- systemd is free software; you can redistribute it and/or modify it
- 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
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
#if HAVE_SELINUX
#include <selinux/selinux.h>
#include <sys/statvfs.h>
#include <linux/sockios.h>
-#include "libudev.h"
#include "sd-daemon.h"
#include "sd-journal.h"
#include "sd-messages.h"
#define DEFAULT_SYNC_INTERVAL_USEC (5*USEC_PER_MINUTE)
#define DEFAULT_RATE_LIMIT_INTERVAL (30*USEC_PER_SEC)
-#define DEFAULT_RATE_LIMIT_BURST 1000
+#define DEFAULT_RATE_LIMIT_BURST 10000
#define DEFAULT_MAX_FILE_USEC USEC_PER_MONTH
#define RECHECK_SPACE_USEC (30*USEC_PER_SEC)
* for a bit of additional metadata. */
#define DEFAULT_LINE_MAX (48*1024)
+#define DEFERRED_CLOSES_MAX (4096)
+
static int determine_path_usage(Server *s, const char *path, uint64_t *ret_used, uint64_t *ret_free) {
_cleanup_closedir_ DIR *d = NULL;
struct dirent *de;
storage->metrics.min_use = MAX(storage->metrics.min_use, storage->space.vfs_used);
}
-
static int determine_space(Server *s, uint64_t *available, uint64_t *limit) {
JournalStorage *js;
int r;
bool seal,
JournalMetrics *metrics,
JournalFile **ret) {
- int r;
+
JournalFile *f;
+ int r;
assert(s);
assert(fname);
assert(ret);
if (reliably)
- r = journal_file_open_reliably(fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f);
+ r = journal_file_open_reliably(fname, flags, 0640, s->compress.enabled, s->compress.threshold_bytes,
+ seal, metrics, s->mmap, s->deferred_closes, NULL, &f);
else
- r = journal_file_open(-1, fname, flags, 0640, s->compress, seal, metrics, s->mmap, s->deferred_closes, NULL, &f);
+ r = journal_file_open(-1, fname, flags, 0640, s->compress.enabled, s->compress.threshold_bytes, seal,
+ metrics, s->mmap, s->deferred_closes, NULL, &f);
+
if (r < 0)
return r;
server_add_acls(s->system_journal, 0);
(void) cache_space_refresh(s, &s->system_storage);
patch_min_use(&s->system_storage);
- } else if (r < 0) {
+ } else {
if (!IN_SET(r, -ENOENT, -EROFS))
log_warning_errno(r, "Failed to open system journal: %m");
if (uid_for_system_journal(uid))
return s->system_journal;
- r = sd_id128_get_machine(&machine);
- if (r < 0)
- return s->system_journal;
-
f = ordered_hashmap_get(s->user_journals, UID_TO_PTR(uid));
if (f)
return f;
+ r = sd_id128_get_machine(&machine);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to determine machine ID, using system log: %m");
+ return s->system_journal;
+ }
+
if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/user-"UID_FMT".journal",
- SD_ID128_FORMAT_VAL(machine), uid) < 0)
+ SD_ID128_FORMAT_VAL(machine), uid) < 0) {
+ log_oom();
return s->system_journal;
+ }
while (ordered_hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) {
/* Too many open? Then let's close one */
if (!*f)
return -EINVAL;
- r = journal_file_rotate(f, s->compress, seal, s->deferred_closes);
+ r = journal_file_rotate(f, s->compress.enabled, s->compress.threshold_bytes, seal, s->deferred_closes);
if (r < 0) {
if (*f)
return log_error_errno(r, "Failed to rotate %s: %m", (*f)->path);
return r;
}
+static void server_process_deferred_closes(Server *s) {
+ JournalFile *f;
+ Iterator i;
+
+ /* Perform any deferred closes which aren't still offlining. */
+ SET_FOREACH(f, s->deferred_closes, i) {
+ if (journal_file_is_offlining(f))
+ continue;
+
+ (void) set_remove(s->deferred_closes, f);
+ (void) journal_file_close(f);
+ }
+}
+
+static void server_vacuum_deferred_closes(Server *s) {
+ assert(s);
+
+ /* Make some room in the deferred closes list, so that it doesn't grow without bounds */
+ if (set_size(s->deferred_closes) < DEFERRED_CLOSES_MAX)
+ return;
+
+ /* Let's first remove all journal files that might already have completed closing */
+ server_process_deferred_closes(s);
+
+ /* And now, let's close some more until we reach the limit again. */
+ while (set_size(s->deferred_closes) >= DEFERRED_CLOSES_MAX) {
+ JournalFile *f;
+
+ assert_se(f = set_steal_first(s->deferred_closes));
+ journal_file_close(f);
+ }
+}
+
+static int open_user_journal_directory(Server *s, DIR **ret_dir, char **ret_path) {
+ _cleanup_closedir_ DIR *dir = NULL;
+ _cleanup_free_ char *path = NULL;
+ sd_id128_t machine;
+ int r;
+
+ assert(s);
+
+ r = sd_id128_get_machine(&machine);
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine machine ID, ignoring: %m");
+
+ if (asprintf(&path, "/var/log/journal/" SD_ID128_FORMAT_STR "/", SD_ID128_FORMAT_VAL(machine)) < 0)
+ return log_oom();
+
+ dir = opendir(path);
+ if (!dir)
+ return log_error_errno(errno, "Failed to open user journal directory '%s': %m", path);
+
+ if (ret_dir)
+ *ret_dir = TAKE_PTR(dir);
+ if (ret_path)
+ *ret_path = TAKE_PTR(path);
+
+ return 0;
+}
+
void server_rotate(Server *s) {
+ _cleanup_free_ char *path = NULL;
+ _cleanup_closedir_ DIR *d = NULL;
JournalFile *f;
- void *k;
Iterator i;
+ void *k;
int r;
log_debug("Rotating...");
+ /* First, rotate the system journal (either in its runtime flavour or in its runtime flavour) */
(void) do_rotate(s, &s->runtime_journal, "runtime", false, 0);
(void) do_rotate(s, &s->system_journal, "system", s->seal, 0);
+ /* Then, rotate all user journals we have open (keeping them open) */
ORDERED_HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) {
r = do_rotate(s, &f, "user", s->seal, PTR_TO_UID(k));
if (r >= 0)
ordered_hashmap_remove(s->user_journals, k);
}
- /* Perform any deferred closes which aren't still offlining. */
- SET_FOREACH(f, s->deferred_closes, i)
- if (!journal_file_is_offlining(f)) {
- (void) set_remove(s->deferred_closes, f);
- (void) journal_file_close(f);
+ /* Finally, also rotate all user journals we currently do not have open. (But do so only if we actually have
+ * access to /var, i.e. are not in the log-to-runtime-journal mode). */
+ if (!s->runtime_journal &&
+ open_user_journal_directory(s, &d, &path) >= 0) {
+
+ struct dirent *de;
+
+ FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to enumerate %s, ignoring: %m", path)) {
+ _cleanup_free_ char *u = NULL, *full = NULL;
+ _cleanup_close_ int fd = -1;
+ const char *a, *b;
+ uid_t uid;
+
+ a = startswith(de->d_name, "user-");
+ if (!a)
+ continue;
+ b = endswith(de->d_name, ".journal");
+ if (!b)
+ continue;
+
+ u = strndup(a, b-a);
+ if (!u) {
+ log_oom();
+ break;
+ }
+
+ r = parse_uid(u, &uid);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse UID from file name '%s', ignoring: %m", de->d_name);
+ continue;
+ }
+
+ /* Already rotated in the above loop? i.e. is it an open user journal? */
+ if (ordered_hashmap_contains(s->user_journals, UID_TO_PTR(uid)))
+ continue;
+
+ full = strjoin(path, de->d_name);
+ if (!full) {
+ log_oom();
+ break;
+ }
+
+ fd = openat(dirfd(d), de->d_name, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
+ if (fd < 0) {
+ log_full_errno(IN_SET(errno, ELOOP, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno,
+ "Failed to open journal file '%s' for rotation: %m", full);
+ continue;
+ }
+
+ /* Make some room in the set of deferred close()s */
+ server_vacuum_deferred_closes(s);
+
+ /* Open the file briefly, so that we can archive it */
+ r = journal_file_open(fd,
+ full,
+ O_RDWR,
+ 0640,
+ s->compress.enabled,
+ s->compress.threshold_bytes,
+ s->seal,
+ &s->system_storage.metrics,
+ s->mmap,
+ s->deferred_closes,
+ NULL,
+ &f);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to read journal file %s for rotation, trying to move it out of the way: %m", full);
+
+ r = journal_file_dispose(dirfd(d), de->d_name);
+ if (r < 0)
+ log_warning_errno(r, "Failed to move %s out of the way, ignoring: %m", full);
+ else
+ log_debug("Successfully moved %s out of the way.", full);
+
+ continue;
+ }
+
+ TAKE_FD(fd); /* Donated to journal_file_open() */
+
+ r = journal_file_archive(f);
+ if (r < 0)
+ log_debug_errno(r, "Failed to archive journal file '%s', ignoring: %m", full);
+
+ f = journal_initiate_close(f, s->deferred_closes);
}
+ }
+
+ server_process_deferred_closes(s);
}
void server_sync(Server *s) {
}
}
-static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned n, int priority) {
+static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, size_t n, int priority) {
bool vacuumed = false, rotate = false;
struct dual_timestamp ts;
JournalFile *f;
s->last_realtime_clock = ts.realtime;
- r = journal_file_append_entry(f, &ts, iovec, n, &s->seqnum, NULL, NULL);
+ r = journal_file_append_entry(f, &ts, NULL, iovec, n, &s->seqnum, NULL, NULL);
if (r >= 0) {
server_schedule_sync(s, priority);
return;
}
if (vacuumed || !shall_try_append_again(f, r)) {
- log_error_errno(r, "Failed to write entry (%d items, %zu bytes), ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n));
+ log_error_errno(r, "Failed to write entry (%zu items, %zu bytes), ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n));
return;
}
return;
log_debug("Retrying write.");
- r = journal_file_append_entry(f, &ts, iovec, n, &s->seqnum, NULL, NULL);
+ r = journal_file_append_entry(f, &ts, NULL, iovec, n, &s->seqnum, NULL, NULL);
if (r < 0)
- log_error_errno(r, "Failed to write entry (%d items, %zu bytes) despite vacuuming, ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n));
+ log_error_errno(r, "Failed to write entry (%zu items, %zu bytes) despite vacuuming, ignoring: %m", n, IOVEC_TOTAL_SIZE(iovec, n));
else
server_schedule_sync(s, priority);
}
if (c && c->unit) {
(void) determine_space(s, &available, NULL);
- rl = journal_rate_limit_test(s->rate_limit, c->unit, priority & LOG_PRIMASK, available);
+ rl = journal_rate_limit_test(s->rate_limit, c->unit, c->log_rate_limit_interval, c->log_rate_limit_burst, priority & LOG_PRIMASK, available);
if (rl == 0)
return;
goto finish;
}
- r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL);
+ r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset);
if (r >= 0)
continue;
}
log_debug("Retrying write.");
- r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset, NULL, NULL, NULL);
+ r = journal_file_copy_entry(f, s->system_journal, o, f->current_offset);
if (r < 0) {
log_error_errno(r, "Can't write entry: %m");
goto finish;
r = 0;
finish:
- journal_file_post_change(s->system_journal);
+ if (s->system_journal)
+ journal_file_post_change(s->system_journal);
s->runtime_journal = journal_file_close(s->runtime_journal);
struct iovec iovec;
ssize_t n;
int *fds = NULL, v = 0;
- unsigned n_fds = 0;
+ size_t n_fds = 0;
union {
struct cmsghdr cmsghdr;
assert(s);
assert(fd == s->native_fd || fd == s->syslog_fd || fd == s->audit_fd);
- if (revents != EPOLLIN) {
- log_error("Got invalid event from epoll for datagram fd: %"PRIx32, revents);
- return -EIO;
- }
+ if (revents != EPOLLIN)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Got invalid event from epoll for datagram fd: %" PRIx32,
+ revents);
/* Try to get the right size, if we can. (Not all sockets support SIOCINQ, hence we just try, but don't rely on
* it.) */
if (!GREEDY_REALLOC(s->buffer, s->buffer_size, m))
return log_oom();
- iovec.iov_base = s->buffer;
- iovec.iov_len = s->buffer_size - 1; /* Leave room for trailing NUL we add later */
+ iovec = IOVEC_MAKE(s->buffer, s->buffer_size - 1); /* Leave room for trailing NUL we add later */
n = recvmsg(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
if (n < 0) {
return log_error_errno(errno, "recvmsg() failed: %m");
}
- CMSG_FOREACH(cmsg, &msghdr) {
-
+ CMSG_FOREACH(cmsg, &msghdr)
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_CREDENTIALS &&
cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred)))
fds = (int*) CMSG_DATA(cmsg);
n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
}
- }
/* And a trailing NUL, just in case */
s->buffer[n] = 0;
if (fd == s->syslog_fd) {
if (n > 0 && n_fds == 0)
- server_process_syslog_message(s, strstrip(s->buffer), ucred, tv, label, label_len);
+ server_process_syslog_message(s, s->buffer, n, ucred, tv, label, label_len);
else if (n_fds > 0)
log_warning("Got file descriptors via syslog socket. Ignoring.");
assert(s);
- s->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY);
+ s->hostname_fd = open("/proc/sys/kernel/hostname",
+ O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
if (s->hostname_fd < 0)
return log_error_errno(errno, "Failed to open /proc/sys/kernel/hostname: %m");
}
static int server_connect_notify(Server *s) {
- union sockaddr_union sa = {
- .un.sun_family = AF_UNIX,
- };
+ union sockaddr_union sa = {};
const char *e;
- int r;
+ int r, salen;
assert(s);
assert(s->notify_fd < 0);
if (!e)
return 0;
- if (!IN_SET(e[0], '@', '/') || e[1] == 0) {
- log_error("NOTIFY_SOCKET set to an invalid value: %s", e);
- return -EINVAL;
- }
-
- if (strlen(e) > sizeof(sa.un.sun_path)) {
- log_error("NOTIFY_SOCKET path too long: %s", e);
- return -EINVAL;
- }
+ salen = sockaddr_un_set_path(&sa.un, e);
+ if (salen < 0)
+ return log_error_errno(salen, "NOTIFY_SOCKET set to invalid value '%s': %m", e);
s->notify_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (s->notify_fd < 0)
(void) fd_inc_sndbuf(s->notify_fd, NOTIFY_SNDBUF_SIZE);
- strncpy(sa.un.sun_path, e, sizeof(sa.un.sun_path));
- if (sa.un.sun_path[0] == '@')
- sa.un.sun_path[0] = 0;
-
- r = connect(s->notify_fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
+ r = connect(s->notify_fd, &sa.sa, salen);
if (r < 0)
return log_error_errno(errno, "Failed to connect to notify socket: %m");
zero(*s);
s->syslog_fd = s->native_fd = s->stdout_fd = s->dev_kmsg_fd = s->audit_fd = s->hostname_fd = s->notify_fd = -1;
- s->compress = true;
+ s->compress.enabled = true;
+ s->compress.threshold_bytes = (uint64_t) -1;
s->seal = true;
s->read_kmsg = true;
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.");
- return -EINVAL;
- }
+ if (s->native_fd >= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Too many native sockets passed.");
s->native_fd = fd;
} 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.");
- return -EINVAL;
- }
+ if (s->stdout_fd >= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Too many stdout sockets passed.");
s->stdout_fd = fd;
} else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/dev/log", 0) > 0 ||
sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/dev-log", 0) > 0) {
- if (s->syslog_fd >= 0) {
- log_error("Too many /dev/log sockets passed.");
- return -EINVAL;
- }
+ if (s->syslog_fd >= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Too many /dev/log sockets passed.");
s->syslog_fd = fd;
} else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) {
- if (s->audit_fd >= 0) {
- log_error("Too many audit sockets passed.");
- return -EINVAL;
- }
+ if (s->audit_fd >= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Too many audit sockets passed.");
s->audit_fd = fd;
if (r < 0)
return r;
- s->udev = udev_new();
- if (!s->udev)
- return -ENOMEM;
-
- s->rate_limit = journal_rate_limit_new(s->rate_limit_interval, s->rate_limit_burst);
+ s->rate_limit = journal_rate_limit_new();
if (!s->rate_limit)
return -ENOMEM;
if (s->mmap)
mmap_cache_unref(s->mmap);
-
- udev_unref(s->udev);
}
static const char* const storage_table[_STORAGE_MAX] = {
return 0;
}
+
+int config_parse_compress(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ JournalCompressOptions* compress = data;
+ int r;
+
+ if (streq(rvalue, "1")) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Compress= ambiguously specified as 1, enabling compression with default threshold");
+ compress->enabled = true;
+ } else if (streq(rvalue, "0")) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Compress= ambiguously specified as 0, disabling compression");
+ compress->enabled = false;
+ } else if ((r = parse_boolean(rvalue)) >= 0)
+ compress->enabled = r;
+ else if (parse_size(rvalue, 1024, &compress->threshold_bytes) == 0)
+ compress->enabled = true;
+ else if (isempty(rvalue)) {
+ compress->enabled = true;
+ compress->threshold_bytes = (uint64_t) -1;
+ } else
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Compress= value, ignoring: %s", rvalue);
+
+ return 0;
+}