diff options
author | Lennart Poettering <lennart@poettering.net> | 2017-07-31 20:01:05 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-31 20:01:05 +0200 |
commit | 6b43d079a251f11ec70ac91579ecc24f3448b963 (patch) | |
tree | 9de58289d618e16a8e1ebab6769476bb9440f7fc | |
parent | 3a0bf6d6aa08a267aefd5d5620fb6bee2556affb (diff) | |
parent | 22e3a02b9d618bbebcf987bc1411acda367271ec (diff) | |
download | systemd-6b43d079a251f11ec70ac91579ecc24f3448b963.tar.gz |
Merge pull request #6392 from poettering/journal-cache
add limited metadata caching to journald and other journal improvements
33 files changed, 988 insertions, 434 deletions
diff --git a/man/systemd-escape.xml b/man/systemd-escape.xml index bb4c7e48e5..fb20d2d94f 100644 --- a/man/systemd-escape.xml +++ b/man/systemd-escape.xml @@ -45,7 +45,7 @@ <refnamediv> <refname>systemd-escape</refname> - <refpurpose>Escape strings for usage in system unit names</refpurpose> + <refpurpose>Escape strings for usage in systemd unit names</refpurpose> </refnamediv> <refsynopsisdiv> diff --git a/src/basic/alloc-util.c b/src/basic/alloc-util.c index b540dcddf5..948389f276 100644 --- a/src/basic/alloc-util.c +++ b/src/basic/alloc-util.c @@ -25,16 +25,31 @@ #include "util.h" void* memdup(const void *p, size_t l) { - void *r; + void *ret; - assert(p); + assert(l == 0 || p); + + ret = malloc(l); + if (!ret) + return NULL; + + memcpy(ret, p, l); + return ret; +} + +void* memdup_suffix0(const void*p, size_t l) { + void *ret; + + assert(l == 0 || p); + + /* The same as memdup() but place a safety NUL byte after the allocated memory */ - r = malloc(l); - if (!r) + ret = malloc(l + 1); + if (!ret) return NULL; - memcpy(r, p, l); - return r; + *((uint8_t*) mempcpy(ret, p, l)) = 0; + return ret; } void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h index a44dd473c1..0a89691bae 100644 --- a/src/basic/alloc-util.h +++ b/src/basic/alloc-util.h @@ -36,6 +36,8 @@ #define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) +#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, sizeof(t), (n))) + #define malloc0(n) (calloc(1, (n))) static inline void *mfree(void *memory) { @@ -52,6 +54,7 @@ static inline void *mfree(void *memory) { }) void* memdup(const void *p, size_t l) _alloc_(2); +void* memdup_suffix0(const void*p, size_t l) _alloc_(2); static inline void freep(void *p) { free(*(void**) p); @@ -84,6 +87,13 @@ _alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t size, si return memdup(p, size * need); } +_alloc_(2, 3) static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t need) { + if (size_multiply_overflow(size, need)) + return NULL; + + return memdup_suffix0(p, size * need); +} + void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size); void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size); diff --git a/src/basic/audit-util.c b/src/basic/audit-util.c index d1c9695973..24a6c8a936 100644 --- a/src/basic/audit-util.c +++ b/src/basic/audit-util.c @@ -54,7 +54,7 @@ int audit_session_from_pid(pid_t pid, uint32_t *id) { if (r < 0) return r; - if (u == AUDIT_SESSION_INVALID || u <= 0) + if (!audit_session_is_valid(u)) return -ENODATA; *id = u; @@ -81,7 +81,7 @@ int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { if (r < 0) return r; - *uid = (uid_t) u; + *uid = u; return 0; } diff --git a/src/basic/audit-util.h b/src/basic/audit-util.h index e048503991..3088951326 100644 --- a/src/basic/audit-util.h +++ b/src/basic/audit-util.h @@ -29,3 +29,7 @@ int audit_session_from_pid(pid_t pid, uint32_t *id); int audit_loginuid_from_pid(pid_t pid, uid_t *uid); bool use_audit(void); + +static inline bool audit_session_is_valid(uint32_t id) { + return id > 0 && id != AUDIT_SESSION_INVALID; +} diff --git a/src/basic/bus-label.h b/src/basic/bus-label.h index 62fb2c450c..600268b767 100644 --- a/src/basic/bus-label.h +++ b/src/basic/bus-label.h @@ -23,9 +23,11 @@ #include <stdlib.h> #include <string.h> +#include "string-util.h" + char *bus_label_escape(const char *s); char *bus_label_unescape_n(const char *f, size_t l); static inline char *bus_label_unescape(const char *f) { - return bus_label_unescape_n(f, f ? strlen(f) : 0); + return bus_label_unescape_n(f, strlen_ptr(f)); } diff --git a/src/basic/escape.c b/src/basic/escape.c index 85e4b5282e..22b8b04156 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -314,7 +314,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi /* Undoes C style string escaping, and optionally prefixes it. */ - pl = prefix ? strlen(prefix) : 0; + pl = strlen_ptr(prefix); r = new(char, pl+length+1); if (!r) diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index 2d6e377f0a..766770389c 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "hexdecoct.h" #include "macro.h" +#include "string-util.h" #include "util.h" char octchar(int x) { @@ -569,7 +570,7 @@ static int base64_append_width(char **prefix, int plen, lines = (len + width - 1) / width; - slen = sep ? strlen(sep) : 0; + slen = strlen_ptr(sep); t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines); if (!t) return -ENOMEM; diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 4532f222c8..89bb667c5f 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -59,7 +59,7 @@ int parse_pid(const char *s, pid_t* ret_pid) { if ((unsigned long) pid != ul) return -ERANGE; - if (pid <= 0) + if (!pid_is_valid(pid)) return -ERANGE; *ret_pid = pid; diff --git a/src/basic/process-util.c b/src/basic/process-util.c index ce6a59c8a4..f5bd6c9487 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -388,7 +388,7 @@ int is_kernel_thread(pid_t pid) { bool eof; FILE *f; - if (pid == 0 || pid == 1) /* pid 1, and we ourselves certainly aren't a kernel thread */ + if (pid == 0 || pid == 1 || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */ return 0; assert(pid > 1); @@ -471,6 +471,9 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) { assert(field); assert(uid); + if (pid < 0) + return -EINVAL; + p = procfs_file_alloca(pid, "status"); f = fopen(p, "re"); if (!f) { @@ -498,10 +501,22 @@ static int get_process_id(pid_t pid, const char *field, uid_t *uid) { } int get_process_uid(pid_t pid, uid_t *uid) { + + if (pid == 0 || pid == getpid_cached()) { + *uid = getuid(); + return 0; + } + return get_process_id(pid, "Uid:", uid); } int get_process_gid(pid_t pid, gid_t *gid) { + + if (pid == 0 || pid == getpid_cached()) { + *gid = getgid(); + return 0; + } + assert_cc(sizeof(uid_t) == sizeof(gid_t)); return get_process_id(pid, "Gid:", gid); } @@ -577,7 +592,7 @@ int get_process_ppid(pid_t pid, pid_t *_ppid) { assert(pid >= 0); assert(_ppid); - if (pid == 0) { + if (pid == 0 || pid == getpid_cached()) { *_ppid = getppid(); return 0; } @@ -775,6 +790,9 @@ bool pid_is_unwaited(pid_t pid) { if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ return true; + if (pid == getpid_cached()) + return true; + if (kill(pid, 0) >= 0) return true; @@ -792,6 +810,9 @@ bool pid_is_alive(pid_t pid) { if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */ return true; + if (pid == getpid_cached()) + return true; + r = get_process_state(pid); if (r == -ESRCH || r == 'Z') return false; @@ -803,7 +824,10 @@ int pid_from_same_root_fs(pid_t pid) { const char *root; if (pid < 0) - return 0; + return false; + + if (pid == 0 || pid == getpid_cached()) + return true; root = procfs_file_alloca(pid, "root"); diff --git a/src/basic/process-util.h b/src/basic/process-util.h index 17746b4ebf..b45d60dbd1 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -118,6 +118,10 @@ static inline bool ioprio_priority_is_valid(int i) { return i >= 0 && i < IOPRIO_BE_NR; } +static inline bool pid_is_valid(pid_t p) { + return p > 0; +} + int ioprio_parse_priority(const char *s, int *ret); pid_t getpid_cached(void); diff --git a/src/basic/string-util.c b/src/basic/string-util.c index cd58ef97ef..5808b1280e 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -215,7 +215,7 @@ char *strnappend(const char *s, const char *suffix, size_t b) { } char *strappend(const char *s, const char *suffix) { - return strnappend(s, suffix, suffix ? strlen(suffix) : 0); + return strnappend(s, suffix, strlen_ptr(suffix)); } char *strjoin_real(const char *x, ...) { @@ -558,7 +558,7 @@ bool nulstr_contains(const char *nulstr, const char *needle) { char* strshorten(char *s, size_t l) { assert(s); - if (l < strlen(s)) + if (strnlen(s, l+1) > l) s[l] = 0; return s; @@ -707,7 +707,7 @@ char *strextend(char **x, ...) { assert(x); - l = f = *x ? strlen(*x) : 0; + l = f = strlen_ptr(*x); va_start(ap, x); for (;;) { diff --git a/src/basic/string-util.h b/src/basic/string-util.h index e8a0836538..34eb952ce9 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -200,3 +200,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase); #define _cleanup_string_free_erase_ _cleanup_(string_free_erasep) bool string_is_safe(const char *p) _pure_; + +static inline size_t strlen_ptr(const char *s) { + if (!s) + return 0; + + return strlen(s); +} diff --git a/src/core/execute.c b/src/core/execute.c index 48b84815ca..7481588b08 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -278,7 +278,7 @@ static int open_null_as(int flags, int nfd) { } static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { - union sockaddr_union sa = { + static const union sockaddr_union sa = { .un.sun_family = AF_UNIX, .un.sun_path = "/run/systemd/journal/stdout", }; @@ -286,36 +286,32 @@ static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { gid_t oldgid = GID_INVALID; int r; - if (gid != GID_INVALID) { + if (gid_is_valid(gid)) { oldgid = getgid(); - r = setegid(gid); - if (r < 0) + if (setegid(gid) < 0) return -errno; } - if (uid != UID_INVALID) { + if (uid_is_valid(uid)) { olduid = getuid(); - r = seteuid(uid); - if (r < 0) { + if (seteuid(uid) < 0) { r = -errno; goto restore_gid; } } - r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) - r = -errno; + r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0 ? -errno : 0; /* If we fail to restore the uid or gid, things will likely fail later on. This should only happen if an LSM interferes. */ - if (uid != UID_INVALID) + if (uid_is_valid(uid)) (void) seteuid(olduid); restore_gid: - if (gid != GID_INVALID) + if (gid_is_valid(gid)) (void) setegid(oldgid); return r; @@ -360,8 +356,8 @@ static int connect_logger_as( "%i\n" "%i\n" "%i\n", - context->syslog_identifier ? context->syslog_identifier : ident, - unit->id, + context->syslog_identifier ?: ident, + MANAGER_IS_SYSTEM(unit->manager) ? unit->id : "", context->syslog_priority, !!context->syslog_level_prefix, output == EXEC_OUTPUT_SYSLOG || output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE, diff --git a/src/escape/escape.c b/src/escape/escape.c index 89e885d47c..5518c2a6fa 100644 --- a/src/escape/escape.c +++ b/src/escape/escape.c @@ -38,7 +38,7 @@ static bool arg_path = false; static void help(void) { printf("%s [OPTIONS...] [NAME...]\n\n" - "Escape strings for usage in system unit names.\n\n" + "Escape strings for usage in systemd unit names.\n\n" " -h --help Show this help\n" " --version Show package version\n" " --suffix=SUFFIX Unit suffix to append to escaped strings\n" diff --git a/src/journal/journald-audit.c b/src/journal/journald-audit.c index a433c91c54..38ac3befdd 100644 --- a/src/journal/journald-audit.c +++ b/src/journal/journald-audit.c @@ -413,7 +413,7 @@ static void process_audit_string(Server *s, int type, const char *data, size_t s goto finish; } - server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, NULL, 0, NULL, LOG_NOTICE, 0); + server_dispatch_message(s, iov, n_iov, n_iov_allocated, NULL, NULL, LOG_NOTICE, 0); finish: /* free() all entries that map_all_fields() added. All others diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c new file mode 100644 index 0000000000..10e9615f23 --- /dev/null +++ b/src/journal/journald-context.c @@ -0,0 +1,588 @@ +/*** + This file is part of systemd. + + Copyright 2017 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/>. +***/ + +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif + +#include "alloc-util.h" +#include "audit-util.h" +#include "cgroup-util.h" +#include "journald-context.h" +#include "process-util.h" +#include "string-util.h" +#include "user-util.h" + +/* This implements a metadata cache for clients, which are identified by their PID. Requesting metadata through /proc + * is expensive, hence let's cache the data if we can. Note that this means the metadata might be out-of-date when we + * store it, but it might already be anyway, as we request the data asynchronously from /proc at a different time the + * log entry was originally created. We hence just increase the "window of inaccuracy" a bit. + * + * The cache is indexed by the PID. Entries may be "pinned" in the cache, in which case the entries are not removed + * until they are unpinned. Unpinned entries are kept around until cache pressure is seen. Cache entries older than 5s + * are never used (a sad attempt to deal with the UNIX weakness of PIDs reuse), cache entries older than 1s are + * refreshed in an incremental way (meaning: data is reread from /proc, but any old data we can't refresh is not + * flushed out). Data newer than 1s is used immediately without refresh. + * + * Log stream clients (i.e. all clients using the AF_UNIX/SOCK_STREAM stdout/stderr transport) will pin a cache entry + * as long as their socket is connected. Note that cache entries are shared between different transports. That means a + * cache entry pinned for the stream connection logic may be reused for the syslog or native protocols. + * + * Caching metadata like this has two major benefits: + * + * 1. Reading metadata is expensive, and we can thus substantially speed up log processing under flood. + * + * 2. Because metadata caching is shared between stream and datagram transports and stream connections pin a cache + * entry there's a good chance we can properly map a substantial set of datagram log messages to their originating + * service, as all services (unless explicitly configured otherwise) will have their stdout/stderr connected to a + * stream connection. This should improve cases where a service process logs immediately before exiting and we + * previously had trouble associating the log message with the service. + * + * NB: With and without the metadata cache: the implicitly added entry metadata in the journal (with the exception of + * UID/PID/GID and SELinux label) must be understood as possibly slightly out of sync (i.e. sometimes slighly older + * and sometimes slightly newer than what was current at the log event). + */ + +/* We refresh every 1s */ +#define REFRESH_USEC (1*USEC_PER_SEC) + +/* Data older than 5s we flush out */ +#define MAX_USEC (5*USEC_PER_SEC) + +/* Keep at most 16K entries in the cache. (Note though that this limit may be violated if enough streams pin entries in + * the cache, in which case we *do* permit this limit to be breached. That's safe however, as the number of stream + * clients itself is limited.) */ +#define CACHE_MAX (16*1024) + +static int client_context_compare(const void *a, const void *b) { + const ClientContext *x = a, *y = b; + + if (x->timestamp < y->timestamp) + return -1; + if (x->timestamp > y->timestamp) + return 1; + + if (x->pid < y->pid) + return -1; + if (x->pid > y->pid) + return 1; + + return 0; +} + +static int client_context_new(Server *s, pid_t pid, ClientContext **ret) { + ClientContext *c; + int r; + + assert(s); + assert(pid_is_valid(pid)); + assert(ret); + + r = hashmap_ensure_allocated(&s->client_contexts, NULL); + if (r < 0) + return r; + + r = prioq_ensure_allocated(&s->client_contexts_lru, client_context_compare); + if (r < 0) + return r; + + c = new0(ClientContext, 1); + if (!c) + return -ENOMEM; + + c->pid = pid; + + c->uid = UID_INVALID; + c->gid = GID_INVALID; + c->auditid = AUDIT_SESSION_INVALID; + c->loginuid = UID_INVALID; + c->owner_uid = UID_INVALID; + c->lru_index = PRIOQ_IDX_NULL; + c->timestamp = USEC_INFINITY; + + r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c); + if (r < 0) { + free(c); + return r; + } + + *ret = c; + return 0; +} + +static void client_context_reset(ClientContext *c) { + assert(c); + + c->timestamp = USEC_INFINITY; + + c->uid = UID_INVALID; + c->gid = GID_INVALID; + + c->comm = mfree(c->comm); + c->exe = mfree(c->exe); + c->cmdline = mfree(c->cmdline); + c->capeff = mfree(c->capeff); + + c->auditid = AUDIT_SESSION_INVALID; + c->loginuid = UID_INVALID; + + c->cgroup = mfree(c->cgroup); + c->session = mfree(c->session); + c->owner_uid = UID_INVALID; + c->unit = mfree(c->unit); + c->user_unit = mfree(c->user_unit); + c->slice = mfree(c->slice); + c->user_slice = mfree(c->user_slice); + + c->invocation_id = SD_ID128_NULL; + + c->label = mfree(c->label); + c->label_size = 0; +} + +static ClientContext* client_context_free(Server *s, ClientContext *c) { + assert(s); + + if (!c) + return NULL; + + assert_se(hashmap_remove(s->client_contexts, PID_TO_PTR(c->pid)) == c); + + if (c->in_lru) + assert_se(prioq_remove(s->client_contexts_lru, c, &c->lru_index) >= 0); + + client_context_reset(c); + + return mfree(c); +} + +static void client_context_read_uid_gid(ClientContext *c, const struct ucred *ucred) { + assert(c); + assert(pid_is_valid(c->pid)); + + /* The ucred data passed in is always the most current and accurate, if we have any. Use it. */ + if (ucred && uid_is_valid(ucred->uid)) + c->uid = ucred->uid; + else + (void) get_process_uid(c->pid, &c->uid); + + if (ucred && gid_is_valid(ucred->gid)) + c->gid = ucred->gid; + else + (void) get_process_gid(c->pid, &c->gid); +} + +static void client_context_read_basic(ClientContext *c) { + char *t; + + assert(c); + assert(pid_is_valid(c->pid)); + + if (get_process_comm(c->pid, &t) >= 0) + free_and_replace(c->comm, t); + + if (get_process_exe(c->pid, &t) >= 0) + free_and_replace(c->exe, t); + + if (get_process_cmdline(c->pid, 0, false, &t) >= 0) + free_and_replace(c->cmdline, t); + + if (get_process_capeff(c->pid, &t) >= 0) + free_and_replace(c->capeff, t); +} + +static int client_context_read_label( + ClientContext *c, + const char *label, size_t label_size) { + + assert(c); + assert(pid_is_valid(c->pid)); + assert(label_size == 0 || label); + + if (label_size > 0) { + char *l; + + /* If we got an SELinux label passed in it counts. */ + + l = newdup_suffix0(char, label, label_size); + if (!l) + return -ENOMEM; + + free_and_replace(c->label, l); + c->label_size = label_size; + } +#ifdef HAVE_SELINUX + else { + char *con; + + /* If we got no SELinux label passed in, let's try to acquire one */ + + if (getpidcon(c->pid, &con) >= 0) { + free_and_replace(c->label, con); + c->label_size = strlen(c->label); + } + } +#endif + + return 0; +} + +static int client_context_read_cgroup(Server *s, ClientContext *c, const char *unit_id) { + char *t = NULL; + int r; + + assert(c); + + /* Try to acquire the current cgroup path */ + r = cg_pid_get_path_shifted(c->pid, s->cgroup_root, &t); + if (r < 0) { + + /* If that didn't work, we use the unit ID passed in as fallback, if we have nothing cached yet */ + if (unit_id && !c->unit) { + c->unit = strdup(unit_id); + if (c->unit) + return 0; + } + + return r; + } + + /* Let's shortcut this if the cgroup path didn't change */ + if (streq_ptr(c->cgroup, t)) { + free(t); + return 0; + } + + free_and_replace(c->cgroup, t); + + (void) cg_path_get_session(c->cgroup, &t); + free_and_replace(c->session, t); + + if (cg_path_get_owner_uid(c->cgroup, &c->owner_uid) < 0) + c->owner_uid = UID_INVALID; + + (void) cg_path_get_unit(c->cgroup, &t); + free_and_replace(c->unit, t); + + (void) cg_path_get_user_unit(c->cgroup, &t); + free_and_replace(c->user_unit, t); + + (void) cg_path_get_slice(c->cgroup, &t); + free_and_replace(c->slice, t); + + (void) cg_path_get_user_slice(c->cgroup, &t); + free_and_replace(c->user_slice, t); + + return 0; +} + +static int client_context_read_invocation_id( + Server *s, + ClientContext *c) { + + _cleanup_free_ char *escaped = NULL, *slice_path = NULL; + char ids[SD_ID128_STRING_MAX]; + const char *p; + int r; + + assert(s); + assert(c); + + /* Read the invocation ID of a unit off a unit. It's stored in the "trusted.invocation_id" extended attribute + * on the cgroup path. */ + + if (!c->unit || !c->slice) + return 0; + + r = cg_slice_to_path(c->slice, &slice_path); + if (r < 0) + return r; + + escaped = cg_escape(c->unit); + if (!escaped) + return -ENOMEM; + + p = strjoina(s->cgroup_root, "/", slice_path, "/", escaped); + if (!p) + return -ENOMEM; + + r = cg_get_xattr(SYSTEMD_CGROUP_CONTROLLER, p, "trusted.invocation_id", ids, 32); + if (r < 0) + return r; + if (r != 32) + return -EINVAL; + ids[32] = 0; + + return sd_id128_from_string(ids, &c->invocation_id); +} + +static void client_context_really_refresh( + Server *s, + ClientContext *c, + const struct ucred *ucred, + const char *label, size_t label_size, + const char *unit_id, + usec_t timestamp) { + + assert(s); + assert(c); + assert(pid_is_valid(c->pid)); + + if (timestamp == USEC_INFINITY) + timestamp = now(CLOCK_MONOTONIC); + + client_context_read_uid_gid(c, ucred); + client_context_read_basic(c); + (void) client_context_read_label(c, label, label_size); + + (void) audit_session_from_pid(c->pid, &c->auditid); + (void) audit_loginuid_from_pid(c->pid, &c->loginuid); + + (void) client_context_read_cgroup(s, c, unit_id); + (void) client_context_read_invocation_id(s, c); + + c->timestamp = timestamp; + + if (c->in_lru) { + assert(c->n_ref == 0); + assert_se(prioq_reshuffle(s->client_contexts_lru, c, &c->lru_index) >= 0); + } +} + +void client_context_maybe_refresh( + Server *s, + ClientContext *c, + const struct ucred *ucred, + const char *label, size_t label_size, + const char *unit_id, + usec_t timestamp) { + + assert(s); + assert(c); + + if (timestamp == USEC_INFINITY) + timestamp = now(CLOCK_MONOTONIC); + + /* No cached data so far? Let's fill it up */ + if (c->timestamp == USEC_INFINITY) + goto refresh; + + /* If the data isn't pinned and if the cashed data is older than the upper limit, we flush it out + * entirely. This follows the logic that as long as an entry is pinned the PID reuse is unlikely. */ + if (c->n_ref == 0 && c->timestamp + MAX_USEC < timestamp) { + client_context_reset(c); + goto refresh; + } + + /* If the data is older than the lower limit, we refresh, but keep the old data for all we can't update */ + if (c->timestamp + REFRESH_USEC < timestamp) + goto refresh; + + /* If the data passed along doesn't match the cached data we also do a refresh */ + if (ucred && uid_is_valid(ucred->uid) && c->uid != ucred->uid) + goto refresh; + + if (ucred && gid_is_valid(ucred->gid) && c->gid != ucred->gid) + goto refresh; + + if (label_size > 0 && (label_size != c->label_size || memcmp(label, c->label, label_size) != 0)) + goto refresh; + + return; + +refresh: + client_context_really_refresh(s, c, ucred, label, label_size, unit_id, timestamp); +} + +static void client_context_try_shrink_to(Server *s, size_t limit) { + assert(s); + + /* Bring the number of cache entries below the indicated limit, so that we can create a new entry without + * breaching the limit. Note that we only flush out entries that aren't pinned here. This means the number of + * cache entries may very well grow beyond the limit, if all entries stored remain pinned. */ + + while (hashmap_size(s->client_contexts) > limit) { + ClientContext *c; + + c = prioq_pop(s->client_contexts_lru); + if (!c) + break; /* All remaining entries are pinned, give up */ + + assert(c->in_lru); + assert(c->n_ref == 0); + + c->in_lru = false; + + client_context_free(s, c); + } +} + +void client_context_flush_all(Server *s) { + assert(s); + + /* Flush out all remaining entries. This assumes all references are already dropped. */ + + s->my_context = client_context_release(s, s->my_context); + s->pid1_context = client_context_release(s, s->pid1_context); + + client_context_try_shrink_to(s, 0); + + assert(prioq_size(s->client_contexts_lru) == 0); + assert(hashmap_size(s->client_contexts) == 0); + + s->client_contexts_lru = prioq_free(s->client_contexts_lru); + s->client_contexts = hashmap_free(s->client_contexts); +} + +static int client_context_get_internal( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + bool add_ref, + ClientContext **ret) { + + ClientContext *c; + int r; + + assert(s); + assert(ret); + + if (!pid_is_valid(pid)) + return -EINVAL; + + c = hashmap_get(s->client_contexts, PID_TO_PTR(pid)); + if (c) { + + if (add_ref) { + if (c->in_lru) { + /* The entry wasn't pinned so far, let's remove it from the LRU list then */ + assert(c->n_ref == 0); + assert_se(prioq_remove(s->client_contexts_lru, c, &c->lru_index) >= 0); + c->in_lru = false; + } + + c->n_ref++; + } + + client_context_maybe_refresh(s, c, ucred, label, label_len, unit_id, USEC_INFINITY); + + *ret = c; + return 0; + } + + client_context_try_shrink_to(s, CACHE_MAX-1); + + r = client_context_new(s, pid, &c); + if (r < 0) + return r; + + if (add_ref) + c->n_ref++; + else { + r = prioq_put(s->client_contexts_lru, c, &c->lru_index); + if (r < 0) { + client_context_free(s, c); + return r; + } + + c->in_lru = true; + } + + client_context_really_refresh(s, c, ucred, label, label_len, unit_id, USEC_INFINITY); + + *ret = c; + return 0; +} + +int client_context_get( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + ClientContext **ret) { + + return client_context_get_internal(s, pid, ucred, label, label_len, unit_id, false, ret); +} + +int client_context_acquire( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + ClientContext **ret) { + + return client_context_get_internal(s, pid, ucred, label, label_len, unit_id, true, ret); +}; + +ClientContext *client_context_release(Server *s, ClientContext *c) { + assert(s); + + if (!c) + return NULL; + + assert(c->n_ref > 0); + assert(!c->in_lru); + + c->n_ref--; + if (c->n_ref > 0) + return NULL; + + /* The entry is not pinned anymore, let's add it to the LRU prioq if we can. If we can't we'll drop it + * right-away */ + + if (prioq_put(s->client_contexts_lru, c, &c->lru_index) < 0) + client_context_free(s, c); + else + c->in_lru = true; + + return NULL; +} + +void client_context_acquire_default(Server *s) { + int r; + + assert(s); + + /* Ensure that our own and PID1's contexts are always pinned. Our own context is particularly useful to + * generate driver messages. */ + + if (!s->my_context) { + struct ucred ucred = { + .pid = getpid_cached(), + .uid = getuid(), + .gid = getgid(), + }; + + r = client_context_acquire(s, ucred.pid, &ucred, NULL, 0, NULL, &s->my_context); + if (r < 0) + log_warning_errno(r, "Failed to acquire our own context, ignoring: %m"); + } + + if (!s->pid1_context) { + + r = client_context_acquire(s, 1, NULL, NULL, 0, NULL, &s->pid1_context); + if (r < 0) + log_warning_errno(r, "Failed to acquire PID1's context, ignoring: %m"); + + } +} diff --git a/src/journal/journald-context.h b/src/journal/journald-context.h new file mode 100644 index 0000000000..eb1e21926a --- /dev/null +++ b/src/journal/journald-context.h @@ -0,0 +1,92 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2017 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/>. +***/ + +#include <inttypes.h> +#include <sys/types.h> + +#include "sd-id128.h" + +typedef struct ClientContext ClientContext; + +#include "journald-server.h" + +struct ClientContext { + unsigned n_ref; + unsigned lru_index; + usec_t timestamp; + bool in_lru; + + pid_t pid; + uid_t uid; + gid_t gid; + + char *comm; + char *exe; + char *cmdline; + char *capeff; + + uint32_t auditid; + uid_t loginuid; + + char *cgroup; + char *session; + uid_t owner_uid; + + char *unit; + char *user_unit; + + char *slice; + char *user_slice; + + sd_id128_t invocation_id; + + char *label; + size_t label_size; +}; + +int client_context_get( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + ClientContext **ret); + +int client_context_acquire( + Server *s, + pid_t pid, + const struct ucred *ucred, + const char *label, size_t label_len, + const char *unit_id, + ClientContext **ret); + +ClientContext* client_context_release(Server *s, ClientContext *c); + +void client_context_maybe_refresh( + Server *s, + ClientContext *c, + const struct ucred *ucred, + const char *label, size_t label_size, + const char *unit_id, + usec_t tstamp); + +void client_context_acquire_default(Server *s); +void client_context_flush_all(Server *s); diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c index 7fea85a5d8..2be82be5f6 100644 --- a/src/journal/journald-kmsg.c +++ b/src/journal/journald-kmsg.c @@ -310,7 +310,7 @@ static void dev_kmsg_record(Server *s, const char *p, size_t l) { if (cunescape_length_with_prefix(p, pl, "MESSAGE=", UNESCAPE_RELAX, &message) >= 0) IOVEC_SET_STRING(iovec[n++], message); - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, NULL, 0, NULL, priority, 0); + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, priority, 0); finish: for (j = 0; j < z; j++) diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index abd06b1adc..23afe59bd5 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -37,6 +37,7 @@ #include "memfd-util.h" #include "parse-util.h" #include "path-util.h" +#include "process-util.h" #include "selinux-util.h" #include "socket-util.h" #include "string-util.h" @@ -142,6 +143,7 @@ static void server_process_entry_meta( static int server_process_entry( Server *s, const void *buffer, size_t *remaining, + ClientContext *context, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len) { @@ -303,7 +305,7 @@ static int server_process_entry( server_forward_wall(s, priority, identifier, message, ucred); } - server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority, object_pid); + server_dispatch_message(s, iovec, n, m, context, tv, priority, object_pid); finish: for (j = 0; j < n; j++) { @@ -329,16 +331,23 @@ void server_process_native_message( const struct timeval *tv, const char *label, size_t label_len) { - int r; size_t remaining = buffer_size; + ClientContext *context; + int r; assert(s); assert(buffer || buffer_size == 0); + if (ucred && pid_is_valid(ucred->pid)) { + r = client_context_get(s, ucred->pid, ucred, label, label_len, NULL, &context); + if (r < 0) + log_warning_errno(r, "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m", ucred->pid); + } + do { r = server_process_entry(s, (const uint8_t*) buffer + (buffer_size - remaining), &remaining, - ucred, tv, label, label_len); + context, ucred, tv, label, label_len); } while (r == 0); } diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c index f48639cf58..a3404222e0 100644 --- a/src/journal/journald-rate-limit.c +++ b/src/journal/journald-rate-limit.c @@ -216,6 +216,13 @@ int journal_rate_limit_test(JournalRateLimit *r, const char *id, int priority, u assert(id); + /* Returns: + * + * 0 → the log message shall be suppressed, + * 1 + n → the log message shall be permitted, and n messages were dropped from the peer before + * < 0 → error + */ + if (!r) return 1; diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index f391845ba9..feef10c2db 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -51,6 +51,7 @@ #include "journal-internal.h" #include "journal-vacuum.h" #include "journald-audit.h" +#include "journald-context.h" #include "journald-kmsg.h" #include "journald-native.h" #include "journald-rate-limit.h" @@ -70,8 +71,8 @@ #include "stdio-util.h" #include "string-table.h" #include "string-util.h" -#include "user-util.h" #include "syslog-util.h" +#include "user-util.h" #define USER_JOURNALS_MAX 1024 @@ -714,323 +715,109 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned server_schedule_sync(s, priority); } -static int get_invocation_id(const char *cgroup_root, const char *slice, const char *unit, char **ret) { - _cleanup_free_ char *escaped = NULL, *slice_path = NULL, *p = NULL; - char *copy, ids[SD_ID128_STRING_MAX]; - int r; - - /* Read the invocation ID of a unit off a unit. It's stored in the "trusted.invocation_id" extended attribute - * on the cgroup path. */ - - r = cg_slice_to_path(slice, &slice_path); - if (r < 0) - return r; - - escaped = cg_escape(unit); - if (!escaped) - return -ENOMEM; - - p = strjoin(cgroup_root, "/", slice_path, "/", escaped); - if (!p) - return -ENOMEM; - - r = cg_get_xattr(SYSTEMD_CGROUP_CONTROLLER, p, "trusted.invocation_id", ids, 32); - if (r < 0) - return r; - if (r != 32) - return -EINVAL; - ids[32] = 0; +#define IOVEC_ADD_NUMERIC_FIELD(iovec, n, value, type, isset, format, field) \ + if (isset(value)) { \ + char *k; \ + k = newa(char, strlen(field "=") + DECIMAL_STR_MAX(type) + 1); \ + sprintf(k, field "=" format, value); \ + IOVEC_SET_STRING(iovec[n++], k); \ + } - if (!id128_is_valid(ids)) - return -EINVAL; +#define IOVEC_ADD_STRING_FIELD(iovec, n, value, field) \ + if (!isempty(value)) { \ + char *k; \ + k = strjoina(field "=", value); \ + IOVEC_SET_STRING(iovec[n++], k); \ + } - copy = strdup(ids); - if (!copy) - return -ENOMEM; +#define IOVEC_ADD_ID128_FIELD(iovec, n, value, field) \ + if (!sd_id128_is_null(value)) { \ + char *k; \ + k = newa(char, strlen(field "=") + SD_ID128_STRING_MAX); \ + sd_id128_to_string(value, stpcpy(k, field "=")); \ + IOVEC_SET_STRING(iovec[n++], k); \ + } - *ret = copy; - return 0; -} +#define IOVEC_ADD_SIZED_FIELD(iovec, n, value, value_size, field) \ + if (value_size > 0) { \ + char *k; \ + k = newa(char, strlen(field "=") + value_size + 1); \ + *((char*) mempcpy(stpcpy(k, field "="), value, value_size)) = 0; \ + IOVEC_SET_STRING(iovec[n++], k); \ + } \ static void dispatch_message_real( Server *s, struct iovec *iovec, unsigned n, unsigned m, - const struct ucred *ucred, + const ClientContext *c, const struct timeval *tv, - const char *label, size_t label_len, - const char *unit_id, int priority, - pid_t object_pid, - char *cgroup) { - - char pid[sizeof("_PID=") + DECIMAL_STR_MAX(pid_t)], - uid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)], - gid[sizeof("_GID=") + DECIMAL_STR_MAX(gid_t)], - owner_uid[sizeof("_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)], - source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)], - o_uid[sizeof("OBJECT_UID=") + DECIMAL_STR_MAX(uid_t)], - o_gid[sizeof("OBJECT_GID=") + DECIMAL_STR_MAX(gid_t)], - o_owner_uid[sizeof("OBJECT_SYSTEMD_OWNER_UID=") + DECIMAL_STR_MAX(uid_t)]; - uid_t object_uid; - gid_t object_gid; - char *x; - int r; - char *t, *c; - uid_t realuid = 0, owner = 0, journal_uid; - bool owner_valid = false; -#ifdef HAVE_AUDIT - char audit_session[sizeof("_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], - audit_loginuid[sizeof("_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)], - o_audit_session[sizeof("OBJECT_AUDIT_SESSION=") + DECIMAL_STR_MAX(uint32_t)], - o_audit_loginuid[sizeof("OBJECT_AUDIT_LOGINUID=") + DECIMAL_STR_MAX(uid_t)]; - - uint32_t audit; - uid_t loginuid; -#endif + pid_t object_pid) { + + char source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)]; + uid_t journal_uid; + ClientContext *o; assert(s); assert(iovec); assert(n > 0); - assert(n + N_IOVEC_META_FIELDS + (object_pid > 0 ? N_IOVEC_OBJECT_FIELDS : 0) <= m); - - if (ucred) { - realuid = ucred->uid; - - sprintf(pid, "_PID="PID_FMT, ucred->pid); - IOVEC_SET_STRING(iovec[n++], pid); - - sprintf(uid, "_UID="UID_FMT, ucred->uid); - IOVEC_SET_STRING(iovec[n++], uid); - - sprintf(gid, "_GID="GID_FMT, ucred->gid); - IOVEC_SET_STRING(iovec[n++], gid); - - r = get_process_comm(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_COMM=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_exe(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_EXE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_cmdline(ucred->pid, 0, false, &t); - if (r >= 0) { - x = strjoina("_CMDLINE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_capeff(ucred->pid, &t); - if (r >= 0) { - x = strjoina("_CAP_EFFECTIVE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - -#ifdef HAVE_AUDIT - r = audit_session_from_pid(ucred->pid, &audit); - if (r >= 0) { - sprintf(audit_session, "_AUDIT_SESSION=%"PRIu32, audit); - IOVEC_SET_STRING(iovec[n++], audit_session); - } - - r = audit_loginuid_from_pid(ucred->pid, &loginuid); - if (r >= 0) { - sprintf(audit_loginuid, "_AUDIT_LOGINUID="UID_FMT, loginuid); - IOVEC_SET_STRING(iovec[n++], audit_loginuid); - } -#endif - - r = 0; - if (cgroup) - c = cgroup; - else - r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &c); - - if (r >= 0) { - _cleanup_free_ char *raw_unit = NULL, *raw_slice = NULL; - char *session = NULL; - - x = strjoina("_SYSTEMD_CGROUP=", c); - IOVEC_SET_STRING(iovec[n++], x); - - r = cg_path_get_session(c, &t); - if (r >= 0) { - session = strjoina("_SYSTEMD_SESSION=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], session); - } - - if (cg_path_get_owner_uid(c, &owner) >= 0) { - owner_valid = true; - - sprintf(owner_uid, "_SYSTEMD_OWNER_UID="UID_FMT, owner); - IOVEC_SET_STRING(iovec[n++], owner_uid); - } - - if (cg_path_get_unit(c, &raw_unit) >= 0) { - x = strjoina("_SYSTEMD_UNIT=", raw_unit); - IOVEC_SET_STRING(iovec[n++], x); - } else if (unit_id && !session) { - x = strjoina("_SYSTEMD_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } + assert(n + N_IOVEC_META_FIELDS + (pid_is_valid(object_pid) ? N_IOVEC_OBJECT_FIELDS : 0) <= m); - if (cg_path_get_user_unit(c, &t) >= 0) { - x = strjoina("_SYSTEMD_USER_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } else if (unit_id && session) { - x = strjoina("_SYSTEMD_USER_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_slice(c, &raw_slice) >= 0) { - x = strjoina("_SYSTEMD_SLICE=", raw_slice); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_user_slice(c, &t) >= 0) { - x = strjoina("_SYSTEMD_USER_SLICE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (raw_slice && raw_unit) { - if (get_invocation_id(s->cgroup_root, raw_slice, raw_unit, &t) >= 0) { - x = strjoina("_SYSTEMD_INVOCATION_ID=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - } + if (c) { + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->pid, pid_t, pid_is_valid, PID_FMT, "_PID"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->uid, uid_t, uid_is_valid, UID_FMT, "_UID"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->gid, gid_t, gid_is_valid, GID_FMT, "_GID"); - if (!cgroup) - free(c); - } else if (unit_id) { - x = strjoina("_SYSTEMD_UNIT=", unit_id); - IOVEC_SET_STRING(iovec[n++], x); - } + IOVEC_ADD_STRING_FIELD(iovec, n, c->comm, "_COMM"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->exe, "_EXE"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->cmdline, "_CMDLINE"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->capeff, "_CAP_EFFECTIVE"); -#ifdef HAVE_SELINUX - if (mac_selinux_use()) { - if (label) { - x = alloca(strlen("_SELINUX_CONTEXT=") + label_len + 1); + IOVEC_ADD_SIZED_FIELD(iovec, n, c->label, c->label_size, "_SELINUX_CONTEXT"); - *((char*) mempcpy(stpcpy(x, "_SELINUX_CONTEXT="), label, label_len)) = 0; - IOVEC_SET_STRING(iovec[n++], x); - } else { - char *con; + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->auditid, uint32_t, audit_session_is_valid, "%" PRIu32, "_AUDIT_SESSION"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->loginuid, uid_t, uid_is_valid, UID_FMT, "_AUDIT_LOGINUID"); - if (getpidcon(ucred->pid, &con) >= 0) { - x = strjoina("_SELINUX_CONTEXT=", con); + IOVEC_ADD_STRING_FIELD(iovec, n, c->cgroup, "_SYSTEMD_CGROUP"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->session, "_SYSTEMD_SESSION"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->owner_uid, uid_t, uid_is_valid, UID_FMT, "_SYSTEMD_OWNER_UID"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->unit, "_SYSTEMD_UNIT"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->user_unit, "_SYSTEMD_USER_UNIT"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->slice, "_SYSTEMD_SLICE"); + IOVEC_ADD_STRING_FIELD(iovec, n, c->user_slice, "_SYSTEMD_USER_SLICE"); - freecon(con); - IOVEC_SET_STRING(iovec[n++], x); - } - } - } -#endif + IOVEC_ADD_ID128_FIELD(iovec, n, c->invocation_id, "_SYSTEMD_INVOCATION_ID"); } - assert(n <= m); - if (object_pid) { - r = get_process_uid(object_pid, &object_uid); - if (r >= 0) { - sprintf(o_uid, "OBJECT_UID="UID_FMT, object_uid); - IOVEC_SET_STRING(iovec[n++], o_uid); - } - - r = get_process_gid(object_pid, &object_gid); - if (r >= 0) { - sprintf(o_gid, "OBJECT_GID="GID_FMT, object_gid); - IOVEC_SET_STRING(iovec[n++], o_gid); - } - - r = get_process_comm(object_pid, &t); - if (r >= 0) { - x = strjoina("OBJECT_COMM=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_exe(object_pid, &t); - if (r >= 0) { - x = strjoina("OBJECT_EXE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - r = get_process_cmdline(object_pid, 0, false, &t); - if (r >= 0) { - x = strjoina("OBJECT_CMDLINE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - -#ifdef HAVE_AUDIT - r = audit_session_from_pid(object_pid, &audit); - if (r >= 0) { - sprintf(o_audit_session, "OBJECT_AUDIT_SESSION=%"PRIu32, audit); - IOVEC_SET_STRING(iovec[n++], o_audit_session); - } + assert(n <= m); - r = audit_loginuid_from_pid(object_pid, &loginuid); - if (r >= 0) { - sprintf(o_audit_loginuid, "OBJECT_AUDIT_LOGINUID="UID_FMT, loginuid); - IOVEC_SET_STRING(iovec[n++], o_audit_loginuid); - } -#endif + if (pid_is_valid(object_pid) && client_context_get(s, object_pid, NULL, NULL, 0, NULL, &o) >= 0) { - r = cg_pid_get_path_shifted(object_pid, s->cgroup_root, &c); - if (r >= 0) { - x = strjoina("OBJECT_SYSTEMD_CGROUP=", c); - IOVEC_SET_STRING(iovec[n++], x); - - r = cg_path_get_session(c, &t); - if (r >= 0) { - x = strjoina("OBJECT_SYSTEMD_SESSION=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->pid, pid_t, pid_is_valid, PID_FMT, "OBJECT_PID"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->uid, uid_t, uid_is_valid, UID_FMT, "OBJECT_UID"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->gid, gid_t, gid_is_valid, GID_FMT, "OBJECT_GID"); - if (cg_path_get_owner_uid(c, &owner) >= 0) { - sprintf(o_owner_uid, "OBJECT_SYSTEMD_OWNER_UID="UID_FMT, owner); - IOVEC_SET_STRING(iovec[n++], o_owner_uid); - } + IOVEC_ADD_STRING_FIELD(iovec, n, o->comm, "OBJECT_COMM"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->exe, "OBJECT_EXE"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->cmdline, "OBJECT_CMDLINE"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->capeff, "OBJECT_CAP_EFFECTIVE"); - if (cg_path_get_unit(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } + IOVEC_ADD_SIZED_FIELD(iovec, n, o->label, o->label_size, "OBJECT_SELINUX_CONTEXT"); - if (cg_path_get_user_unit(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_USER_UNIT=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } - - if (cg_path_get_slice(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_SLICE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->auditid, uint32_t, audit_session_is_valid, "%" PRIu32, "OBJECT_AUDIT_SESSION"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->loginuid, uid_t, uid_is_valid, UID_FMT, "OBJECT_AUDIT_LOGINUID"); - if (cg_path_get_user_slice(c, &t) >= 0) { - x = strjoina("OBJECT_SYSTEMD_USER_SLICE=", t); - free(t); - IOVEC_SET_STRING(iovec[n++], x); - } + IOVEC_ADD_STRING_FIELD(iovec, n, o->cgroup, "OBJECT_SYSTEMD_CGROUP"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->session, "OBJECT_SYSTEMD_SESSION"); + IOVEC_ADD_NUMERIC_FIELD(iovec, n, o->owner_uid, uid_t, uid_is_valid, UID_FMT, "OBJECT_SYSTEMD_OWNER_UID"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->unit, "OBJECT_SYSTEMD_UNIT"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->user_unit, "OBJECT_SYSTEMD_USER_UNIT"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->slice, "OBJECT_SYSTEMD_SLICE"); + IOVEC_ADD_STRING_FIELD(iovec, n, o->user_slice, "OBJECT_SYSTEMD_USER_SLICE"); - free(c); - } + IOVEC_ADD_ID128_FIELD(iovec, n, o->invocation_id, "OBJECT_SYSTEMD_INVOCATION_ID="); } + assert(n <= m); if (tv) { @@ -1052,16 +839,16 @@ static void dispatch_message_real( assert(n <= m); - if (s->split_mode == SPLIT_UID && realuid > 0) - /* Split up strictly by any UID */ - journal_uid = realuid; - else if (s->split_mode == SPLIT_LOGIN && realuid > 0 && owner_valid && owner > 0) + if (s->split_mode == SPLIT_UID && c && uid_is_valid(c->uid)) + /* Split up strictly by (non-root) UID */ + journal_uid = c->uid; + else if (s->split_mode == SPLIT_LOGIN && c && c->uid > 0 && uid_is_valid(c->owner_uid)) /* Split up by login UIDs. We do this only if the * realuid is not root, in order not to accidentally * leak privileged information to the user that is * logged by a privileged process that is part of an * unprivileged session. */ - journal_uid = owner; + journal_uid = c->owner_uid; else journal_uid = 0; @@ -1069,11 +856,11 @@ static void dispatch_message_real( } void server_driver_message(Server *s, const char *message_id, const char *format, ...) { + struct iovec iovec[N_IOVEC_META_FIELDS + 5 + N_IOVEC_PAYLOAD_FIELDS]; unsigned n = 0, m; - int r; va_list ap; - struct ucred ucred = {}; + int r; assert(s); assert(format); @@ -1095,12 +882,8 @@ void server_driver_message(Server *s, const char *message_id, const char *format /* Error handling below */ va_end(ap); - ucred.pid = getpid_cached(); - ucred.uid = getuid(); - ucred.gid = getgid(); - if (r >= 0) - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0, NULL); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), s->my_context, NULL, LOG_INFO, 0); while (m < n) free(iovec[m++].iov_base); @@ -1114,24 +897,20 @@ void server_driver_message(Server *s, const char *message_id, const char *format n = 3; IOVEC_SET_STRING(iovec[n++], "PRIORITY=4"); IOVEC_SET_STRING(iovec[n++], buf); - dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), &ucred, NULL, NULL, 0, NULL, LOG_INFO, 0, NULL); + dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), s->my_context, NULL, LOG_INFO, 0); } } void server_dispatch_message( Server *s, struct iovec *iovec, unsigned n, unsigned m, - const struct ucred *ucred, + ClientContext *c, const struct timeval *tv, - const char *label, size_t label_len, - const char *unit_id, int priority, pid_t object_pid) { - int rl, r; - _cleanup_free_ char *path = NULL; uint64_t available = 0; - char *c = NULL; + int rl; assert(s); assert(iovec || n == 0); @@ -1147,45 +926,21 @@ void server_dispatch_message( if (s->storage == STORAGE_NONE) return; - if (!ucred) - goto finish; - - r = cg_pid_get_path_shifted(ucred->pid, s->cgroup_root, &path); - if (r < 0) - goto finish; + if (c && c->unit) { + (void) determine_space(s, &available, NULL); - /* example: /user/lennart/3/foobar - * /system/dbus.service/foobar - * - * So let's cut of everything past the third /, since that is - * where user directories start */ + rl = journal_rate_limit_test(s->rate_limit, c->unit, priority & LOG_PRIMASK, available); + if (rl == 0) + return; - c = strchr(path, '/'); - if (c) { - c = strchr(c+1, '/'); - if (c) { - c = strchr(c+1, '/'); - if (c) - *c = 0; - } + /* Write a suppression message if we suppressed something */ + if (rl > 1) + server_driver_message(s, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_DROPPED_STR, + LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, c->unit), + NULL); } - (void) determine_space(s, &available, NULL); - rl = journal_rate_limit_test(s->rate_limit, path, priority & LOG_PRIMASK, available); - if (rl == 0) - return; - - /* Write a suppression message if we suppressed something */ - if (rl > 1) - server_driver_message(s, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_DROPPED_STR, - LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, path), - NULL); - -finish: - /* restore cgroup path for logging */ - if (c) - *c = '/'; - dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid, path); + dispatch_message_real(s, iovec, n, m, c, tv, priority, object_pid); } int server_flush_to_var(Server *s, bool require_flag_file) { @@ -1335,9 +1090,8 @@ int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void return -EIO; } - /* Try to get the right size, if we can. (Not all - * sockets support SIOCINQ, hence we just try, but - * don't rely on it. */ + /* Try to get the right size, if we can. (Not all sockets support SIOCINQ, hence we just try, but don't rely on + * it.) */ (void) ioctl(fd, SIOCINQ, &v); /* Fix it up, if it is too small. We use the same fixed value as auditd here. Awful! */ @@ -2102,6 +1856,8 @@ int server_init(Server *s) { (void) server_connect_notify(s); + (void) client_context_acquire_default(s); + return system_journal_open(s, false); } @@ -2133,6 +1889,8 @@ void server_done(Server *s) { while (s->stdout_streams) stdout_stream_free(s->stdout_streams); + client_context_flush_all(s); + if (s->system_journal) (void) journal_file_close(s->system_journal); diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index 882bcead8d..6a4549b1ba 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -28,9 +28,11 @@ typedef struct Server Server; #include "hashmap.h" #include "journal-file.h" +#include "journald-context.h" #include "journald-rate-limit.h" #include "journald-stream.h" #include "list.h" +#include "prioq.h" typedef enum Storage { STORAGE_AUTO, @@ -166,6 +168,13 @@ struct Server { usec_t watchdog_usec; usec_t last_realtime_clock; + + /* Caching of client metadata */ + Hashmap *client_contexts; + Prioq *client_contexts_lru; + + ClientContext *my_context; /* the context of journald itself */ + ClientContext *pid1_context; /* the context of PID 1 */ }; #define SERVER_MACHINE_ID(s) ((s)->machine_id_field + strlen("_MACHINE_ID=")) @@ -176,7 +185,7 @@ struct Server { #define N_IOVEC_OBJECT_FIELDS 14 #define N_IOVEC_PAYLOAD_FIELDS 15 -void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len, const char *unit_id, int priority, pid_t object_pid); +void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, ClientContext *c, const struct timeval *tv, int priority, pid_t object_pid); void server_driver_message(Server *s, const char *message_id, const char *format, ...) _printf_(3,0) _sentinel_; /* gperf lookup function */ diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 77551dc14b..f074f0476f 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -34,6 +34,7 @@ #include "fileio.h" #include "io-util.h" #include "journald-console.h" +#include "journald-context.h" #include "journald-kmsg.h" #include "journald-server.h" #include "journald-stream.h" @@ -41,11 +42,13 @@ #include "journald-wall.h" #include "mkdir.h" #include "parse-util.h" +#include "process-util.h" #include "selinux-util.h" #include "socket-util.h" #include "stdio-util.h" #include "string-util.h" #include "syslog-util.h" +#include "unit-name.h" #define STDOUT_STREAMS_MAX 4096 @@ -86,6 +89,8 @@ struct StdoutStream { char *state_file; + ClientContext *context; + LIST_FIELDS(StdoutStream, stdout_stream); LIST_FIELDS(StdoutStream, stdout_stream_notify_queue); }; @@ -95,6 +100,10 @@ void stdout_stream_free(StdoutStream *s) { return; if (s->server) { + + if (s->context) + client_context_release(s->server, s->context); + assert(s->server->n_stdout_streams > 0); s->server->n_stdout_streams--; LIST_REMOVE(stdout_stream, s->server->stdout_streams, s); @@ -232,7 +241,7 @@ static int stdout_stream_log(StdoutStream *s, const char *p) { char syslog_facility[sizeof("SYSLOG_FACILITY=")-1 + DECIMAL_STR_MAX(int) + 1]; _cleanup_free_ char *message = NULL, *syslog_identifier = NULL; unsigned n = 0; - size_t label_len; + int r; assert(s); assert(p); @@ -277,8 +286,15 @@ static int stdout_stream_log(StdoutStream *s, const char *p) { if (message) IOVEC_SET_STRING(iovec[n++], message); - label_len = s->label ? strlen(s->label) : 0; - server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), &s->ucred, NULL, s->label, label_len, s->unit_id, priority, 0); + if (s->context) + (void) client_context_maybe_refresh(s->server, s->context, NULL, NULL, 0, NULL, USEC_INFINITY); + else if (pid_is_valid(s->ucred.pid)) { + r = client_context_acquire(s->server, s->ucred.pid, &s->ucred, s->label, strlen_ptr(s->label), s->unit_id, &s->context); + if (r < 0) + log_warning_errno(r, "Failed to acquire client context, ignoring: %m"); + } + + server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), s->context, NULL, priority, 0); return 0; } @@ -295,9 +311,7 @@ static int stdout_stream_line(StdoutStream *s, char *p) { switch (s->state) { case STDOUT_STREAM_IDENTIFIER: - if (isempty(p)) - s->identifier = NULL; - else { + if (!isempty(p)) { s->identifier = strdup(p); if (!s->identifier) return log_oom(); @@ -307,14 +321,12 @@ static int stdout_stream_line(StdoutStream *s, char *p) { 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) - return log_oom(); - } + if (s->ucred.uid == 0 && + unit_name_is_valid(p, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) { + + s->unit_id = strdup(p); + if (!s->unit_id) + return log_oom(); } s->state = STDOUT_STREAM_PRIORITY; diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index 17f855e967..a03c36df34 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -325,11 +325,12 @@ void server_process_syslog_message( char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)], syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL; - struct iovec iovec[N_IOVEC_META_FIELDS + 6]; - unsigned n = 0; - int priority = LOG_USER | LOG_INFO; _cleanup_free_ char *identifier = NULL, *pid = NULL; + struct iovec iovec[N_IOVEC_META_FIELDS + 6]; + int priority = LOG_USER | LOG_INFO, r; + ClientContext *context = NULL; const char *orig; + unsigned n = 0; assert(s); assert(buf); @@ -376,7 +377,13 @@ void server_process_syslog_message( if (message) IOVEC_SET_STRING(iovec[n++], message); - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority, 0); + if (ucred && pid_is_valid(ucred->pid)) { + r = client_context_get(s, ucred->pid, ucred, label, label_len, NULL, &context); + if (r < 0) + log_warning_errno(r, "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m", ucred->pid); + } + + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), context, tv, priority, 0); } int server_open_syslog_socket(Server *s) { diff --git a/src/journal/meson.build b/src/journal/meson.build index 582f83afb9..b95efc2041 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -59,24 +59,26 @@ journal_internal_sources += [audit_type_to_name] ############################################################ libjournal_core_sources = files(''' - journald-kmsg.c - journald-kmsg.h - journald-syslog.c - journald-syslog.h - journald-stream.c - journald-stream.h - journald-server.c - journald-server.h + journald-audit.c + journald-audit.h journald-console.c journald-console.h - journald-wall.c - journald-wall.h + journald-context.c + journald-context.h + journald-kmsg.c + journald-kmsg.h journald-native.c journald-native.h - journald-audit.c - journald-audit.h journald-rate-limit.c journald-rate-limit.h + journald-server.c + journald-server.h + journald-stream.c + journald-stream.h + journald-syslog.c + journald-syslog.h + journald-wall.c + journald-wall.h journal-internal.h '''.split()) diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c index 649fcdba44..a05b4215fb 100644 --- a/src/libsystemd/sd-bus/bus-creds.c +++ b/src/libsystemd/sd-bus/bus-creds.c @@ -570,7 +570,7 @@ _public_ int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessio if (!(c->mask & SD_BUS_CREDS_AUDIT_SESSION_ID)) return -ENODATA; - if (c->audit_session_id == AUDIT_SESSION_INVALID) + if (!audit_session_is_valid(c->audit_session_id)) return -ENXIO; *sessionid = c->audit_session_id; @@ -584,7 +584,7 @@ _public_ int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *uid) { if (!(c->mask & SD_BUS_CREDS_AUDIT_LOGIN_UID)) return -ENODATA; - if (c->audit_login_uid == UID_INVALID) + if (!uid_is_valid(c->audit_login_uid)) return -ENXIO; *uid = c->audit_login_uid; diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index c9b7d99818..cd22ff4fca 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -88,7 +88,7 @@ int manager_get_user_from_creds(Manager *m, sd_bus_message *message, uid_t uid, assert(message); assert(ret); - if (uid == UID_INVALID) { + if (!uid_is_valid(uid)) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; /* Note that we get the owner UID of the session, not the actual client UID here! */ @@ -767,8 +767,8 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus if (hashmap_size(m->sessions) >= m->sessions_max) return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Maximum number of sessions (%" PRIu64 ") reached, refusing further sessions.", m->sessions_max); - audit_session_from_pid(leader, &audit_id); - if (audit_id > 0) { + (void) audit_session_from_pid(leader, &audit_id); + if (audit_session_is_valid(audit_id)) { /* Keep our session IDs and the audit session IDs in sync */ if (asprintf(&id, "%"PRIu32, audit_id) < 0) @@ -780,7 +780,7 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus * ID */ if (hashmap_get(m->sessions, id)) { log_warning("Existing logind session ID %s used by new audit session, ignoring", id); - audit_id = 0; + audit_id = AUDIT_SESSION_INVALID; id = mfree(id); } @@ -1132,7 +1132,7 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu if (r < 0) return r; - if (uid == UID_INVALID) { + if (!uid_is_valid(uid)) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; /* Note that we get the owner UID of the session, not the actual client UID here! */ diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 42dfecaffb..3778bb7d70 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -45,6 +45,7 @@ #include "terminal-util.h" #include "user-util.h" #include "util.h" +#include "process-util.h" #define RELEASE_USEC (20*USEC_PER_SEC) @@ -82,6 +83,7 @@ Session* session_new(Manager *m, const char *id) { s->manager = m; s->fifo_fd = -1; s->vtfd = -1; + s->audit_id = AUDIT_SESSION_INVALID; return s; } @@ -280,10 +282,10 @@ int session_save(Session *s) { if (!s->vtnr) fprintf(f, "POSITION=%u\n", s->position); - if (s->leader > 0) + if (pid_is_valid(s->leader)) fprintf(f, "LEADER="PID_FMT"\n", s->leader); - if (s->audit_id > 0) + if (audit_session_is_valid(s->audit_id)) fprintf(f, "AUDIT=%"PRIu32"\n", s->audit_id); if (dual_timestamp_is_set(&s->timestamp)) @@ -459,9 +461,8 @@ int session_load(Session *s) { } if (leader) { - k = parse_pid(leader, &s->leader); - if (k >= 0) - audit_session_from_pid(s->leader, &s->audit_id); + if (parse_pid(leader, &s->leader) >= 0) + (void) audit_session_from_pid(s->leader, &s->audit_id); } if (type) { diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index 0570fde592..869d233d49 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -211,7 +211,7 @@ static enum nss_status fill_in_hostent( c++; l_canonical = strlen(canonical); - l_additional = additional ? strlen(additional) : 0; + l_additional = strlen_ptr(additional); ms = ALIGN(l_canonical+1)+ (additional ? ALIGN(l_additional+1) : 0) + sizeof(char*) + diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 02ae4265c6..54516cfb96 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -95,13 +95,12 @@ static int parse_field(const void *data, size_t length, const char *field, char return 0; nl = length - fl; - buf = new(char, nl+1); + + + buf = newdup_suffix0(char, (const char*) data + fl, nl); if (!buf) return log_oom(); - memcpy(buf, (const char*) data + fl, nl); - buf[nl] = 0; - free(*target); *target = buf; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 55fce62480..36675dc499 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -2010,7 +2010,7 @@ static void output_machines_list(struct machine_info *machine_infos, unsigned n) for (m = machine_infos; m < machine_infos + n; m++) { namelen = MAX(namelen, strlen(m->name) + (m->is_host ? sizeof(" (host)") - 1 : 0)); - statelen = MAX(statelen, m->state ? strlen(m->state) : 0); + statelen = MAX(statelen, strlen_ptr(m->state)); failedlen = MAX(failedlen, DECIMAL_STR_WIDTH(m->n_failed_units)); jobslen = MAX(jobslen, DECIMAL_STR_WIDTH(m->n_jobs)); diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index 4b3e924cfb..604701ff7a 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -336,6 +336,12 @@ static void test_first_word(void) { assert_se(!first_word("Hellooo", "Hello")); } +static void test_strlen_ptr(void) { + assert_se(strlen_ptr("foo") == 3); + assert_se(strlen_ptr("") == 0); + assert_se(strlen_ptr(NULL) == 0); +} + int main(int argc, char *argv[]) { test_string_erase(); test_ascii_strcasecmp_n(); @@ -358,6 +364,7 @@ int main(int argc, char *argv[]) { test_in_charset(); test_split_pair(); test_first_word(); + test_strlen_ptr(); return 0; } |