diff --git a/TODO b/TODO index 8695372bc6..a9902ddff2 100644 --- a/TODO +++ b/TODO @@ -894,11 +894,6 @@ Features: - journald: when we drop syslog messages because the syslog socket is full, make sure to write how many messages are lost as first thing to syslog when it works again. - - change systemd-journal-flush into a service that stays around during - boot, and causes the journal to be moved back to /run on shutdown, - so that we do not keep /var busy. This needs to happen synchronously, - hence doing this via signals is not going to work. - - optionally support running journald from the command line for testing purposes in external projects - journald: allow per-priority and per-service retention times when rotating/vacuuming - journald: make use of uid-range.h to managed uid ranges to split journals in. diff --git a/man/journalctl.xml b/man/journalctl.xml index f6703b06d6..46b22be699 100644 --- a/man/journalctl.xml +++ b/man/journalctl.xml @@ -749,6 +749,18 @@ + + + + Takes a journal namespace identifier string as argument. If not specified the data + collected by the default namespace is shown. If specified shows the log data of the specified + namespace instead. If the namespace is specified as * data from all namespaces is + shown, interleaved. If the namespace identifier is prefixed with + data from the + specified namespace and the default namespace is shown, interleaved, but no other. For details about + journal namespaces see + systemd-journald.service8. + + diff --git a/man/journald.conf.xml b/man/journald.conf.xml index 824db668c5..afbbe64bb8 100644 --- a/man/journald.conf.xml +++ b/man/journald.conf.xml @@ -18,6 +18,7 @@ journald.conf journald.conf.d + journald@.conf Journal service configuration files @@ -26,6 +27,7 @@ /etc/systemd/journald.conf.d/*.conf /run/systemd/journald.conf.d/*.conf /usr/lib/systemd/journald.conf.d/*.conf + /etc/systemd/journald@NAMESPACE.conf @@ -37,6 +39,12 @@ systemd.syntax5 for a general description of the syntax. + The systemd-journald instance managing the default namespace is configured by + /etc/systemd/journald.conf and associated drop-ins. Instances managing other + namespaces read /etc/systemd/journald@NAMESPACE.conf with + the namespace identifier filled in. This allows each namespace to carry a distinct configuration. See + systemd-journald.service8 + for details about journal namespaces. @@ -52,29 +60,19 @@ Storage= - Controls where to store journal data. One of - volatile, - persistent, - auto and - none. If - volatile, journal - log data will be stored only in memory, i.e. below the - /run/log/journal hierarchy (which is - created if needed). If persistent, data - will be stored preferably on disk, i.e. below the - /var/log/journal hierarchy (which is - created if needed), with a fallback to - /run/log/journal (which is created if - needed), during early boot and if the disk is not writable. - auto is similar to - persistent but the directory - /var/log/journal is not created if - needed, so that its existence controls where log data goes. - none turns off all storage, all log data - received will be dropped. Forwarding to other targets, such as - the console, the kernel log buffer, or a syslog socket will - still work however. Defaults to - auto. + Controls where to store journal data. One of volatile, + persistent, auto and none. If + volatile, journal log data will be stored only in memory, i.e. below the + /run/log/journal hierarchy (which is created if needed). If + persistent, data will be stored preferably on disk, i.e. below the + /var/log/journal hierarchy (which is created if needed), with a fallback to + /run/log/journal (which is created if needed), during early boot and if the disk + is not writable. auto is similar to persistent but the + directory /var/log/journal is not created if needed, so that its existence + controls where log data goes. none turns off all storage, all log data received + will be dropped. Forwarding to other targets, such as the console, the kernel log buffer, or a syslog + socket will still work however. Defaults to auto in the default journal namespace, + and persistent in all others. @@ -399,9 +397,9 @@ ReadKMsg= - Takes a boolean value. If enabled (the - default), journal reads /dev/kmsg - messages generated by the kernel. + Takes a boolean value. If enabled systemd-journal processes + /dev/kmsg messages generated by the kernel. In the default journal namespace + this option is enabled by default, it is disabled in all others. diff --git a/man/rules/meson.build b/man/rules/meson.build index 0e6c3d8270..9416c736c0 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -24,7 +24,7 @@ manpages = [ ['journal-remote.conf', '5', ['journal-remote.conf.d'], 'HAVE_MICROHTTPD'], ['journal-upload.conf', '5', ['journal-upload.conf.d'], 'HAVE_MICROHTTPD'], ['journalctl', '1', [], ''], - ['journald.conf', '5', ['journald.conf.d'], ''], + ['journald.conf', '5', ['journald.conf.d', 'journald@.conf'], ''], ['kernel-command-line', '7', [], ''], ['kernel-install', '8', [], ''], ['libudev', '3', [], ''], @@ -550,7 +550,9 @@ manpages = [ ''], ['sd_journal_open', '3', - ['SD_JOURNAL_CURRENT_USER', + ['SD_JOURNAL_ALL_NAMESPACES', + 'SD_JOURNAL_CURRENT_USER', + 'SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE', 'SD_JOURNAL_LOCAL_ONLY', 'SD_JOURNAL_OS_ROOT', 'SD_JOURNAL_RUNTIME_ONLY', @@ -737,7 +739,10 @@ manpages = [ ['systemd-journald', 'systemd-journald-audit.socket', 'systemd-journald-dev-log.socket', - 'systemd-journald.socket'], + 'systemd-journald-varlink@.socket', + 'systemd-journald.socket', + 'systemd-journald@.service', + 'systemd-journald@.socket'], ''], ['systemd-localed.service', '8', ['systemd-localed'], 'ENABLE_LOCALED'], ['systemd-logind.service', '8', ['systemd-logind'], 'ENABLE_LOGIND'], diff --git a/man/sd_journal_open.xml b/man/sd_journal_open.xml index 47e57107cd..9884beae1a 100644 --- a/man/sd_journal_open.xml +++ b/man/sd_journal_open.xml @@ -29,6 +29,8 @@ SD_JOURNAL_SYSTEM SD_JOURNAL_CURRENT_USER SD_JOURNAL_OS_ROOT + SD_JOURNAL_ALL_NAMESPACES + SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE Open the system journal for reading @@ -42,6 +44,13 @@ int flags + + int sd_journal_open_namespace + sd_journal **ret + const char *namespace + int flags + + int sd_journal_open_directory sd_journal **ret @@ -101,6 +110,19 @@ SD_JOURNAL_CURRENT_USER are specified, all journal file types will be opened. + sd_journal_open_namespace() is similar to + sd_journal_open() but takes an additional namespace parameter + that specifies which journal namespace to operate on. If specified as NULL the call + is identical to sd_journal_open(). If non-NULL only data from + the namespace identified by the specified parameter is accessed. This call understands two additional + flags: if SD_JOURNAL_ALL_NAMESPACES is specified the + namespace parameter is ignored and all defined namespaces are accessed + simultaneously; if SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE the specified namespace and + the default namespace are accessed but no others (this flag has no effect when + namespace is passed as NULL). For details about journal + namespaces see + systemd-journald.service8. + sd_journal_open_directory() is similar to sd_journal_open() but takes an absolute directory path as argument. All journal files in this directory will be opened and interleaved automatically. This call also takes a flags argument. The flags parameters accepted by this call are diff --git a/man/systemd-journald.service.xml b/man/systemd-journald.service.xml index ec9f974f2d..f4ce4e4fe2 100644 --- a/man/systemd-journald.service.xml +++ b/man/systemd-journald.service.xml @@ -20,6 +20,9 @@ systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket + systemd-journald@.service + systemd-journald@.socket + systemd-journald-varlink@.socket systemd-journald Journal service @@ -29,6 +32,9 @@ systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket + systemd-journald@.service + systemd-journald@.socket + systemd-journald-varlink@.socket /usr/lib/systemd/systemd-journald @@ -129,6 +135,40 @@ systemd-tmpfiles --create --prefix /var/log/journal EPIPE right from the beginning. + + Journal Namespaces + + Journal 'namespaces' are both a mechanism for logically isolating the log stream of projects + consisting of one or more services from the rest of the system and a mechanism for improving + performance. Multiple journal namespaces may exist simultaneously, each defining its own, independent log + stream managed by its own instance of systemd-journald. Namespaces are independent of + each other, both in the data store and in the IPC interface. By default only a single 'default' namespace + exists, managed by systemd-journald.service (and its associated socket + units). Additional namespaces are created by starting an instance of the + systemd-journald@.service service template. The instance name is the namespace + identifier, which is a short string used for referencing the journal namespace. Service units may be + assigned to a specific journal namespace through the LogNamespace= unit file setting, + see systemd.exec5 for + details. The switch of + journalctl1 may be + used to view the log stream of a specific namespace. If the switch is not used the log stream of the + default namespace is shown, i.e. log data from other namespaces is not visible. + + Services associated with a specific log namespace may log via syslog, the native logging protocol + of the journal and via stdout/stderr; the logging from all three transports is associated with the + namespace. + + By default only the default namespace will collect kernel and audit log messages. + + The systemd-journald instance of the default namespace is configured through + /etc/systemd/journald.conf (see below), while the other instances are configured + through /etc/systemd/journald@NAMESPACE.conf. The journal + log data for the default namespace is placed in + /var/log/journal/MACHINE_ID (see below) while the data + for the other namespaces is located in + /var/log/journal/MACHINE_ID.NAMESPACE. + + Signals @@ -190,6 +230,9 @@ systemd-tmpfiles --create --prefix /var/log/journal + + Note that these kernel command line options are only honoured by the default namespace, see + above. @@ -279,12 +322,14 @@ systemd-tmpfiles --create --prefix /var/log/journal /run/systemd/journal/socket /run/systemd/journal/stdout - Sockets and other paths that - systemd-journald will listen on that are - visible in the file system. In addition to these, journald can - listen for audit events using netlink. + Sockets and other file node paths that systemd-journald will + listen on and are visible in the file system. In addition to these, + systemd-journald can listen for audit events using netlink7. + + If journal namespacing is used these paths are slightly altered to include a namespace identifier, see above. @@ -296,7 +341,7 @@ systemd-tmpfiles --create --prefix /var/log/journal systemd.journal-fields7, sd-journal3, systemd-coredump8, - setfacl1, + setfacl1, sd_journal_print3, pydoc systemd.journal diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index b7f44c9473..8f1695ad29 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -70,6 +70,10 @@ or (or their combinations with console output, see below) automatically acquire dependencies of type After= on systemd-journald.socket. + + Units using LogNamespace= will automatically gain ordering and + requirement dependencies on the two socket units associated with + systemd-journald@.service instances. @@ -2254,6 +2258,36 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy + + LogNamespace= + + Run the unit's processes in the specified journal namespace. Expects a short + user-defined string identifying the namespace. If not used the processes of the service are run in + the default journal namespace, i.e. their log stream is collected and processed by + systemd-journald.service. If this option is used any log data generated by + processes of this unit (regardless if via the syslog(), journal native logging + or stdout/stderr logging) is collected and processed by an instance of the + systemd-journald@.service template unit, which manages the specified + namespace. The log data is stored in a data store independent from the default log namespace's data + store. See + systemd-journald.service8 + for details about journal namespaces. + + Internally, journal namespaces are implemented through Linux mount namespacing and + over-mounting the directory that contains the relevant AF_UNIX sockets used for + logging in the unit's mount namespace. Since mount namespaces are used this setting disconnects + propagation of mounts from the unit's processes to the host, similar to how + ReadOnlyPaths= and similar settings (see above) work. Journal namespaces may hence + not be used for services that need to establish mount points on the host. + + When this option is used the unit will automatically gain ordering and requirement dependencies + on the two socket units associated with the systemd-journald@.service instance + so that they are automatically established prior to the unit starting up. Note that when this option + is used log output of this service does not appear in the regular + journalctl1 + output, unless the option is used. + + SyslogIdentifier= diff --git a/man/systemd.journal-fields.xml b/man/systemd.journal-fields.xml index 9248726b9e..a0771f3c13 100644 --- a/man/systemd.journal-fields.xml +++ b/man/systemd.journal-fields.xml @@ -358,6 +358,15 @@ marking the log line end. + + _NAMESPACE= + + If this file was written by a systemd-journald instance managing a + journal namespace that is not the default, this field contains the namespace identifier. See + systemd-journald.service8 + for details about journal namespaces. + + diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 61809ab065..8f812d7cbe 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -1054,6 +1054,8 @@ bool string_is_safe(const char *p) { if (!p) return false; + /* Checks if the specified string contains no quotes or control characters */ + for (t = p; *t; t++) { if (*t > 0 && *t < ' ') /* no control characters */ return false; diff --git a/src/basic/syslog-util.c b/src/basic/syslog-util.c index 29c9ec9ac4..caeba46db4 100644 --- a/src/basic/syslog-util.c +++ b/src/basic/syslog-util.c @@ -2,10 +2,15 @@ #include +#include "sd-id128.h" + +#include "glob-util.h" #include "hexdecoct.h" #include "macro.h" +#include "path-util.h" #include "string-table.h" #include "syslog-util.h" +#include "unit-name.h" int syslog_parse_priority(const char **p, int *priority, bool with_facility) { int a = 0, b = 0, c = 0; @@ -96,3 +101,31 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG); bool log_level_is_valid(int level) { return level >= 0 && level <= LOG_DEBUG; } + +/* The maximum size for a log namespace length. This is the file name size limit 255 minus the size of a + * formatted machine ID minus a separator char */ +#define LOG_NAMESPACE_MAX (NAME_MAX - (SD_ID128_STRING_MAX - 1) - 1) + +bool log_namespace_name_valid(const char *s) { + /* Let's make sure the namespace fits in a filename that is prefixed with the machine ID and a dot + * (so that /var/log/journal/. can be created based on it). Also make sure it + * is suitable as unit instance name, and does not contain fishy characters. */ + + if (!filename_is_valid(s)) + return false; + + if (strlen(s) > LOG_NAMESPACE_MAX) + return false; + + if (!unit_instance_is_valid(s)) + return false; + + if (!string_is_safe(s)) + return false; + + /* Let's avoid globbing for now */ + if (string_is_glob(s)) + return false; + + return true; +} diff --git a/src/basic/syslog-util.h b/src/basic/syslog-util.h index 8f419e8151..998641fa01 100644 --- a/src/basic/syslog-util.h +++ b/src/basic/syslog-util.h @@ -12,3 +12,5 @@ int log_level_from_string(const char *s); bool log_level_is_valid(int level); int syslog_parse_priority(const char **p, int *priority, bool with_facility); + +bool log_namespace_name_valid(const char *s); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 4f412649c8..9ff3f157f5 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -766,6 +766,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("LogRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(ExecContext, log_ratelimit_interval_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogRateLimitBurst", "u", bus_property_get_unsigned, offsetof(ExecContext, log_ratelimit_burst), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LogExtraFields", "aay", property_get_log_extra_fields, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LogNamespace", "s", NULL, offsetof(ExecContext, log_namespace), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CapabilityBoundingSet", "t", NULL, offsetof(ExecContext, capability_bounding_set), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("AmbientCapabilities", "t", NULL, offsetof(ExecContext, capability_ambient_set), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1436,6 +1437,32 @@ int bus_exec_context_set_transient_property( return 1; + } else if (streq(name, "LogNamespace")) { + const char *n; + + r = sd_bus_message_read(message, "s", &n); + if (r < 0) + return r; + + if (!isempty(n) && !log_namespace_name_valid(n)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log namespace name not valid"); + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + + if (isempty(n)) { + c->log_namespace = mfree(c->log_namespace); + unit_write_settingf(u, flags, name, "%s=", name); + } else { + r = free_and_strdup(&c->log_namespace, n); + if (r < 0) + return r; + + unit_write_settingf(u, flags, name, "%s=%s", name, n); + } + } + + return 1; + } else if (streq(name, "LogExtraFields")) { size_t n = 0; diff --git a/src/core/execute.c b/src/core/execute.c index b6a4f5479b..27777c0d85 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -265,15 +265,27 @@ static int open_null_as(int flags, int nfd) { return move_fd(fd, nfd, false); } -static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { - static const union sockaddr_union sa = { +static int connect_journal_socket( + int fd, + const char *log_namespace, + uid_t uid, + gid_t gid) { + + union sockaddr_union sa = { .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/stdout", }; uid_t olduid = UID_INVALID; gid_t oldgid = GID_INVALID; + const char *j; int r; + j = log_namespace ? + strjoina("/run/systemd/journal.", log_namespace, "/stdout") : + "/run/systemd/journal/stdout"; + r = sockaddr_un_set_path(&sa.un, j); + if (r < 0) + return r; + if (gid_is_valid(gid)) { oldgid = getgid(); @@ -328,7 +340,7 @@ static int connect_logger_as( if (fd < 0) return -errno; - r = connect_journal_socket(fd, uid, gid); + r = connect_journal_socket(fd, context->log_namespace, uid, gid); if (r < 0) return r; @@ -1686,7 +1698,7 @@ static int build_environment( assert(p); assert(ret); - our_env = new0(char*, 14 + _EXEC_DIRECTORY_TYPE_MAX); + our_env = new0(char*, 15 + _EXEC_DIRECTORY_TYPE_MAX); if (!our_env) return -ENOMEM; @@ -1795,6 +1807,14 @@ static int build_environment( our_env[n_env++] = x; } + if (c->log_namespace) { + x = strjoin("LOG_NAMESPACE=", c->log_namespace); + if (!x) + return -ENOMEM; + + our_env[n_env++] = x; + } + for (t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) { _cleanup_free_ char *pre = NULL, *joined = NULL; const char *n; @@ -1919,6 +1939,9 @@ static bool exec_needs_mount_namespace( !strv_isempty(context->directories[EXEC_DIRECTORY_LOGS].paths))) return true; + if (context->log_namespace) + return true; + return false; } @@ -2517,6 +2540,9 @@ static bool insist_on_sandboxing( if (!path_equal(bind_mounts[i].source, bind_mounts[i].destination)) return true; + if (context->log_namespace) + return true; + return false; } @@ -2600,6 +2626,7 @@ static int apply_mount_namespace( context->n_temporary_filesystems, tmp, var, + context->log_namespace, needs_sandboxing ? context->protect_home : PROTECT_HOME_NO, needs_sandboxing ? context->protect_system : PROTECT_SYSTEM_NO, context->mount_flags, @@ -4140,6 +4167,8 @@ void exec_context_done(ExecContext *c) { c->stdin_data_size = 0; c->network_namespace_path = mfree(c->network_namespace_path); + + c->log_namespace = mfree(c->log_namespace); } int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) { @@ -4675,6 +4704,9 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { } } + if (c->log_namespace) + fprintf(f, "%sLogNamespace: %s\n", prefix, c->log_namespace); + if (c->secure_bits) { _cleanup_free_ char *str = NULL; diff --git a/src/core/execute.h b/src/core/execute.h index 5aacac4a40..09c1510aaf 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -250,6 +250,8 @@ struct ExecContext { int log_level_max; + char *log_namespace; + bool private_tmp; bool private_network; bool private_devices; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 9e622597be..69abdb65ba 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -119,6 +119,7 @@ $1.ProtectKernelLogs, config_parse_bool, 0, $1.ProtectClock, config_parse_bool, 0, offsetof($1, exec_context.protect_clock) $1.ProtectControlGroups, config_parse_bool, 0, offsetof($1, exec_context.protect_control_groups) $1.NetworkNamespacePath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.network_namespace_path) +$1.LogNamespace, config_parse_log_namespace, 0, offsetof($1, exec_context) $1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network) $1.PrivateUsers, config_parse_bool, 0, offsetof($1, exec_context.private_users) $1.PrivateMounts, config_parse_bool, 0, offsetof($1, exec_context.private_mounts) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 379be9c96b..71a9873da4 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -52,10 +52,11 @@ #include "stat-util.h" #include "string-util.h" #include "strv.h" +#include "syslog-util.h" +#include "time-util.h" #include "unit-name.h" #include "unit-printf.h" #include "user-util.h" -#include "time-util.h" #include "web-util.h" static int parse_socket_protocol(const char *s) { @@ -2519,6 +2520,48 @@ int config_parse_log_extra_fields( } } +int config_parse_log_namespace( + 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) { + + _cleanup_free_ char *k = NULL; + ExecContext *c = data; + const Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(c); + + if (isempty(rvalue)) { + c->log_namespace = mfree(c->log_namespace); + return 0; + } + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue); + return 0; + } + + if (!log_namespace_name_valid(k)) { + log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Specified log namespace name is not valid: %s", k); + return 0; + } + + free_and_replace(c->log_namespace, k); + return 0; +} + int config_parse_unit_condition_path( const char *unit, const char *filename, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 28613ef5b3..b6b46b2449 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -107,6 +107,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_keyring_mode); CONFIG_PARSER_PROTOTYPE(config_parse_job_timeout_sec); CONFIG_PARSER_PROTOTYPE(config_parse_job_running_timeout_sec); CONFIG_PARSER_PROTOTYPE(config_parse_log_extra_fields); +CONFIG_PARSER_PROTOTYPE(config_parse_log_namespace); CONFIG_PARSER_PROTOTYPE(config_parse_collect_mode); CONFIG_PARSER_PROTOTYPE(config_parse_pid_file); CONFIG_PARSER_PROTOTYPE(config_parse_exit_status); diff --git a/src/core/namespace.c b/src/core/namespace.c index f121cb7ca4..7785405830 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1132,6 +1132,7 @@ static size_t namespace_calculate_mounts( size_t n_temporary_filesystems, const char* tmp_dir, const char* var_tmp_dir, + const char* log_namespace, ProtectHome protect_home, ProtectSystem protect_system) { @@ -1166,7 +1167,8 @@ static size_t namespace_calculate_mounts( (ns_info->protect_control_groups ? 1 : 0) + protect_home_cnt + protect_system_cnt + (ns_info->protect_hostname ? 2 : 0) + - (namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0); + (namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0) + + !!log_namespace; } static void normalize_mounts(const char *root_directory, MountEntry *mounts, size_t *n_mounts) { @@ -1247,6 +1249,7 @@ int setup_namespace( size_t n_temporary_filesystems, const char* tmp_dir, const char* var_tmp_dir, + const char *log_namespace, ProtectHome protect_home, ProtectSystem protect_system, unsigned long mount_flags, @@ -1323,6 +1326,7 @@ int setup_namespace( n_bind_mounts, n_temporary_filesystems, tmp_dir, var_tmp_dir, + log_namespace, protect_home, protect_system); if (n_mounts > 0) { @@ -1428,6 +1432,23 @@ int setup_namespace( }; } + if (log_namespace) { + _cleanup_free_ char *q; + + q = strjoin("/run/systemd/journal.", log_namespace); + if (!q) { + r = -ENOMEM; + goto finish; + } + + *(m++) = (MountEntry) { + .path_const = "/run/systemd/journal", + .mode = BIND_MOUNT_RECURSIVE, + .read_only = true, + .source_malloc = TAKE_PTR(q), + }; + } + assert(mounts + n_mounts == m); /* Prepend the root directory where that's necessary */ diff --git a/src/core/namespace.h b/src/core/namespace.h index 60a6abcd45..ef6c9bdc9b 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -84,6 +84,7 @@ int setup_namespace( size_t n_temporary_filesystems, const char *tmp_dir, const char *var_tmp_dir, + const char *log_namespace, ProtectHome protect_home, ProtectSystem protect_system, unsigned long mount_flags, diff --git a/src/core/unit.c b/src/core/unit.c index 8570eaefb4..6b97c35ffa 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -1059,13 +1059,33 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { !IN_SET(c->std_error, EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE, EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE, - EXEC_OUTPUT_SYSLOG, EXEC_OUTPUT_SYSLOG_AND_CONSOLE)) + EXEC_OUTPUT_SYSLOG, EXEC_OUTPUT_SYSLOG_AND_CONSOLE) && + !c->log_namespace) return 0; - /* If syslog or kernel logging is requested, make sure our own - * logging daemon is run first. */ + /* If syslog or kernel logging is requested (or log namespacing is), make sure our own logging daemon + * is run first. */ - r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, true, UNIT_DEPENDENCY_FILE); + if (c->log_namespace) { + _cleanup_free_ char *socket_unit = NULL, *varlink_socket_unit = NULL; + + r = unit_name_build_from_type("systemd-journald", c->log_namespace, UNIT_SOCKET, &socket_unit); + if (r < 0) + return r; + + r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, socket_unit, true, UNIT_DEPENDENCY_FILE); + if (r < 0) + return r; + + r = unit_name_build_from_type("systemd-journald-varlink", c->log_namespace, UNIT_SOCKET, &varlink_socket_unit); + if (r < 0) + return r; + + r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, varlink_socket_unit, true, UNIT_DEPENDENCY_FILE); + if (r < 0) + return r; + } else + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, true, UNIT_DEPENDENCY_FILE); if (r < 0) return r; diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index cc96825288..031e82587d 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -754,9 +754,13 @@ static int open_journal(sd_journal **j) { r = sd_journal_open_directory(j, arg_directory, arg_journal_type); else if (arg_file) r = sd_journal_open_files(j, (const char**) arg_file, 0); - else if (arg_machine) + else if (arg_machine) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + /* FIXME: replace with D-Bus call OpenMachineRootDirectory() so that things also work with raw disk images */ r = sd_journal_open_container(j, arg_machine, 0); - else +#pragma GCC diagnostic pop + } else r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); if (r < 0) log_error_errno(r, "Failed to open %s: %m", diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h index 80a69da4d8..1454df602b 100644 --- a/src/journal/journal-internal.h +++ b/src/journal/journal-internal.h @@ -69,6 +69,7 @@ struct sd_journal { char *path; char *prefix; + char *namespace; OrderedHashmap *files; IteratedCache *files_cache; diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 17565abe21..4e2a8f6d11 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -70,36 +70,8 @@ #include "varlink.h" #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) - #define PROCESS_INOTIFY_INTERVAL 1024 /* Every 1,024 messages processed */ -#if HAVE_PCRE2 -DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_match_data*, pcre2_match_data_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_code*, pcre2_code_free); - -static int pattern_compile(const char *pattern, unsigned flags, pcre2_code **out) { - int errorcode, r; - PCRE2_SIZE erroroffset; - pcre2_code *p; - - p = pcre2_compile((PCRE2_SPTR8) pattern, - PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL); - if (!p) { - unsigned char buf[LINE_MAX]; - - r = pcre2_get_error_message(errorcode, buf, sizeof buf); - - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Bad pattern \"%s\": %s", pattern, - r < 0 ? "unknown error" : (char *)buf); - } - - *out = p; - return 0; -} - -#endif - enum { /* Special values for arg_lines */ ARG_LINES_DEFAULT = -2, @@ -143,13 +115,14 @@ static const char *arg_field = NULL; static bool arg_catalog = false; static bool arg_reverse = false; static int arg_journal_type = 0; +static int arg_namespace_flags = 0; static char *arg_root = NULL; static const char *arg_machine = NULL; +static const char *arg_namespace = NULL; static uint64_t arg_vacuum_size = 0; static uint64_t arg_vacuum_n_files = 0; static usec_t arg_vacuum_time = 0; static char **arg_output_fields = NULL; - #if HAVE_PCRE2 static const char *arg_pattern = NULL; static pcre2_code *arg_compiled_pattern = NULL; @@ -184,6 +157,33 @@ typedef struct BootId { LIST_FIELDS(struct BootId, boot_list); } BootId; +#if HAVE_PCRE2 +DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_match_data*, pcre2_match_data_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_code*, pcre2_code_free); + +static int pattern_compile(const char *pattern, unsigned flags, pcre2_code **out) { + int errorcode, r; + PCRE2_SIZE erroroffset; + pcre2_code *p; + + p = pcre2_compile((PCRE2_SPTR8) pattern, + PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL); + if (!p) { + unsigned char buf[LINE_MAX]; + + r = pcre2_get_error_message(errorcode, buf, sizeof buf); + + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Bad pattern \"%s\": %s", pattern, + r < 0 ? "unknown error" : (char *)buf); + } + + *out = p; + return 0; +} + +#endif + static int add_matches_for_device(sd_journal *j, const char *devpath) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; sd_device *d = NULL; @@ -313,9 +313,9 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [MATCHES...]\n\n" - "%sQuery the journal.%s\n\n" - "Options:\n" + printf("%1$s [OPTIONS...] [MATCHES...]\n\n" + "%5$sQuery the journal.%6$s\n\n" + "%3$sOptions:%4$s\n" " --system Show the system journal\n" " --user Show the user journal for the current user\n" " -M --machine=CONTAINER Operate on local container\n" @@ -356,10 +356,11 @@ static int help(void) { " -D --directory=PATH Show journal files from directory\n" " --file=PATH Show journal file\n" " --root=ROOT Operate on files below a root directory\n" + " --namespace=NAMESPACE Show journal data from specified namespace\n" " --interval=TIME Time interval for changing the FSS sealing key\n" " --verify-key=KEY Specify FSS verification key\n" " --force Override of the FSS key pair with --setup-keys\n" - "\nCommands:\n" + "\n%3$sCommands:%4$s\n" " -h --help Show this help text\n" " --version Show package version\n" " -N --fields List all field names currently used\n" @@ -379,10 +380,11 @@ static int help(void) { " --dump-catalog Show entries in the message catalog\n" " --update-catalog Update the message catalog database\n" " --setup-keys Generate a new FSS key pair\n" - "\nSee the %s for details.\n" + "\nSee the %2$s for details.\n" , program_invocation_short_name - , ansi_highlight(), ansi_normal() , link + , ansi_underline(), ansi_normal() + , ansi_highlight(), ansi_normal() ); return 0; @@ -428,6 +430,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VACUUM_TIME, ARG_NO_HOSTNAME, ARG_OUTPUT_FIELDS, + ARG_NAMESPACE, }; static const struct option options[] = { @@ -492,6 +495,7 @@ static int parse_argv(int argc, char *argv[]) { { "vacuum-time", required_argument, NULL, ARG_VACUUM_TIME }, { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME }, { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, + { "namespace", required_argument, NULL, ARG_NAMESPACE }, {} }; @@ -533,10 +537,8 @@ static int parse_argv(int argc, char *argv[]) { } arg_output = output_mode_from_string(optarg); - if (arg_output < 0) { - log_error("Unknown output format '%s'.", optarg); - return -EINVAL; - } + if (arg_output < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown output format '%s'.", optarg); if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT)) arg_quiet = true; @@ -561,10 +563,8 @@ static int parse_argv(int argc, char *argv[]) { arg_lines = ARG_LINES_ALL; else { r = safe_atoi(optarg, &arg_lines); - if (r < 0 || arg_lines < 0) { - log_error("Failed to parse lines '%s'", optarg); - return -EINVAL; - } + if (r < 0 || arg_lines < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse lines '%s'", optarg); } } else { arg_lines = 10; @@ -656,6 +656,23 @@ static int parse_argv(int argc, char *argv[]) { arg_machine = optarg; break; + case ARG_NAMESPACE: + if (streq(optarg, "*")) { + arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES; + arg_namespace = NULL; + } else if (startswith(optarg, "+")) { + arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE; + arg_namespace = optarg + 1; + } else if (isempty(optarg)) { + arg_namespace_flags = 0; + arg_namespace = NULL; + } else { + arg_namespace_flags = 0; + arg_namespace = optarg; + } + + break; + case 'D': arg_directory = optarg; break; @@ -710,30 +727,24 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VACUUM_SIZE: r = parse_size(optarg, 1024, &arg_vacuum_size); - if (r < 0) { - log_error("Failed to parse vacuum size: %s", optarg); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to parse vacuum size: %s", optarg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; case ARG_VACUUM_FILES: r = safe_atou64(optarg, &arg_vacuum_n_files); - if (r < 0) { - log_error("Failed to parse vacuum files: %s", optarg); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to parse vacuum files: %s", optarg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; case ARG_VACUUM_TIME: r = parse_sec(optarg, &arg_vacuum_time); - if (r < 0) { - log_error("Failed to parse vacuum time: %s", optarg); - return r; - } + if (r < 0) + return log_error_errno(r, "Failed to parse vacuum time: %s", optarg); arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM; break; @@ -748,7 +759,6 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_VERIFY_KEY: - arg_action = ACTION_VERIFY; r = free_and_strdup(&arg_verify_key, optarg); if (r < 0) return r; @@ -756,23 +766,23 @@ static int parse_argv(int argc, char *argv[]) { * in ps or htop output. */ memset(optarg, 'x', strlen(optarg)); + arg_action = ACTION_VERIFY; arg_merge = false; break; case ARG_INTERVAL: r = parse_sec(optarg, &arg_interval); - if (r < 0 || arg_interval <= 0) { - log_error("Failed to parse sealing key change interval: %s", optarg); - return -EINVAL; - } + if (r < 0 || arg_interval <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse sealing key change interval: %s", optarg); break; #else case ARG_SETUP_KEYS: case ARG_VERIFY_KEY: case ARG_INTERVAL: case ARG_FORCE: - log_error("Compiled without forward-secure sealing support."); - return -EOPNOTSUPP; + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Compiled without forward-secure sealing support."); #endif case 'p': { @@ -780,7 +790,7 @@ static int parse_argv(int argc, char *argv[]) { dots = strstr(optarg, ".."); if (dots) { - char *a; + _cleanup_free_ char *a = NULL; int from, to, i; /* a range */ @@ -790,12 +800,10 @@ static int parse_argv(int argc, char *argv[]) { from = log_level_from_string(a); to = log_level_from_string(dots + 2); - free(a); - if (from < 0 || to < 0) { - log_error("Failed to parse log level range %s", optarg); - return -EINVAL; - } + if (from < 0 || to < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse log level range %s", optarg); arg_priorities = 0; @@ -811,10 +819,9 @@ static int parse_argv(int argc, char *argv[]) { int p, i; p = log_level_from_string(optarg); - if (p < 0) { - log_error("Unknown log level %s", optarg); - return -EINVAL; - } + if (p < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown log level %s", optarg); arg_priorities = 0; @@ -848,19 +855,17 @@ static int parse_argv(int argc, char *argv[]) { case 'S': r = parse_timestamp(optarg, &arg_since); - if (r < 0) { - log_error("Failed to parse timestamp: %s", optarg); - return -EINVAL; - } + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse timestamp: %s", optarg); arg_since_set = true; break; case 'U': r = parse_timestamp(optarg, &arg_until); - if (r < 0) { - log_error("Failed to parse timestamp: %s", optarg); - return -EINVAL; - } + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to parse timestamp: %s", optarg); arg_until_set = true; break; @@ -1417,7 +1422,6 @@ static int add_boot(sd_journal *j) { * so take the slow path if log location is specified. */ if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) && !arg_directory && !arg_file && !arg_root) - return add_match_this_boot(j, arg_machine); boot_id = arg_boot_id; @@ -1944,15 +1948,19 @@ static int verify(sd_journal *j) { static int simple_varlink_call(const char *option, const char *method) { _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL; - const char *error; + const char *error, *fn; int r; if (arg_machine) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "%s is not supported in conjunction with --machine=.", option); - r = varlink_connect_address(&link, "/run/systemd/journal/io.systemd.journal"); + fn = arg_namespace ? + strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") : + "/run/systemd/journal/io.systemd.journal"; + + r = varlink_connect_address(&link, fn); if (r < 0) - return log_error_errno(r, "Failed to connect to /run/systemd/journal/io.systemd.journal: %m"); + return log_error_errno(r, "Failed to connect to %s: %m", fn); (void) varlink_set_description(link, "journal"); (void) varlink_set_relative_timeout(link, USEC_INFINITY); @@ -2122,10 +2130,9 @@ int main(int argc, char *argv[]) { r = sd_journal_open_directory(&j, arg_directory, arg_journal_type); else if (arg_root) r = sd_journal_open_directory(&j, arg_root, arg_journal_type | SD_JOURNAL_OS_ROOT); - else if (arg_file_stdin) { - int ifd = STDIN_FILENO; - r = sd_journal_open_files_fd(&j, &ifd, 1, 0); - } else if (arg_file) + else if (arg_file_stdin) + r = sd_journal_open_files_fd(&j, (int[]) { STDIN_FILENO }, 1, 0); + else if (arg_file) r = sd_journal_open_files(&j, (const char**) arg_file, 0); else if (arg_machine) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -2136,8 +2143,7 @@ int main(int argc, char *argv[]) { if (geteuid() != 0) { /* The file descriptor returned by OpenMachineRootDirectory() will be owned by users/groups of * the container, thus we need root privileges to override them. */ - log_error("Using the --machine= switch requires root privileges."); - r = -EPERM; + r = log_error_errno(SYNTHETIC_ERRNO(EPERM), "Using the --machine= switch requires root privileges."); goto finish; } @@ -2177,7 +2183,11 @@ int main(int argc, char *argv[]) { if (r < 0) safe_close(fd); } else - r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type); + r = sd_journal_open_namespace( + &j, + arg_namespace, + (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | + arg_namespace_flags | arg_journal_type); if (r < 0) { log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal"); goto finish; diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index cd4cfbee48..bac67036b0 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -117,23 +117,24 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) { if (r < 0) return r; - c = new0(ClientContext, 1); + c = new(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; - c->extra_fields_mtime = NSEC_INFINITY; - c->log_level_max = -1; - c->log_ratelimit_interval = s->ratelimit_interval; - c->log_ratelimit_burst = s->ratelimit_burst; + *c = (ClientContext) { + .pid = pid, + .uid = UID_INVALID, + .gid = GID_INVALID, + .auditid = AUDIT_SESSION_INVALID, + .loginuid = UID_INVALID, + .owner_uid = UID_INVALID, + .lru_index = PRIOQ_IDX_NULL, + .timestamp = USEC_INFINITY, + .extra_fields_mtime = NSEC_INFINITY, + .log_level_max = -1, + .log_ratelimit_interval = s->ratelimit_interval, + .log_ratelimit_burst = s->ratelimit_burst, + }; r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c); if (r < 0) { @@ -779,7 +780,9 @@ void client_context_acquire_default(Server *s) { log_warning_errno(r, "Failed to acquire our own context, ignoring: %m"); } - if (!s->pid1_context) { + if (!s->namespace && !s->pid1_context) { + /* Acquire PID1's context, but only if we are in non-namespaced mode, since PID 1 is only + * going to log to the non-namespaced journal instance. */ r = client_context_acquire(s, 1, NULL, NULL, 0, NULL, &s->pid1_context); if (r < 0) diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c index 366298758c..ec404145ee 100644 --- a/src/journal/journald-kmsg.c +++ b/src/journal/journald-kmsg.c @@ -24,11 +24,11 @@ #include "string-util.h" void server_forward_kmsg( - Server *s, - int priority, - const char *identifier, - const char *message, - const struct ucred *ucred) { + Server *s, + int priority, + const char *identifier, + const char *message, + const struct ucred *ucred) { _cleanup_free_ char *ident_buf = NULL; struct iovec iovec[5]; @@ -416,19 +416,23 @@ fail: } int server_open_kernel_seqnum(Server *s) { - _cleanup_close_ int fd; + _cleanup_close_ int fd = -1; + const char *fn; uint64_t *p; int r; 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 - * persistent and automatically flushed at reboot. */ + /* We store the seqnum we last read in an mmaped file. That way we can just use it like a variable, + * but it is persistent and automatically flushed at reboot. */ - fd = open("/run/systemd/journal/kernel-seqnum", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); + if (!s->read_kmsg) + return 0; + + fn = strjoina(s->runtime_directory, "/kernel-seqnum"); + fd = open(fn, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); if (fd < 0) { - log_error_errno(errno, "Failed to open /run/systemd/journal/kernel-seqnum, ignoring: %m"); + log_error_errno(errno, "Failed to open %s, ignoring: %m", fn); return 0; } diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index 73a96da9f7..30d988544f 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -450,17 +450,21 @@ void server_process_native_file( } } -int server_open_native_socket(Server *s) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/socket", - }; +int server_open_native_socket(Server *s, const char *native_socket) { int r; assert(s); + assert(native_socket); if (s->native_fd < 0) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + }; + + r = sockaddr_un_set_path(&sa.un, native_socket); + if (r < 0) + return log_error_errno(r, "Unable to use namespace path %s for AF_UNIX socket: %m", native_socket); + s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (s->native_fd < 0) return log_error_errno(errno, "socket() failed: %m"); diff --git a/src/journal/journald-native.h b/src/journal/journald-native.h index 2a33ef74c5..8d06636262 100644 --- a/src/journal/journald-native.h +++ b/src/journal/journald-native.h @@ -20,4 +20,4 @@ void server_process_native_file( const char *label, size_t label_len); -int server_open_native_socket(Server *s); +int server_open_native_socket(Server *s, const char *native_socket); diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c index af7196c6de..fa21e7da29 100644 --- a/src/journal/journald-rate-limit.c +++ b/src/journal/journald-rate-limit.c @@ -99,7 +99,7 @@ void journal_ratelimit_free(JournalRateLimit *r) { free(r); } -_pure_ static bool journal_ratelimit_group_expired(JournalRateLimitGroup *g, usec_t ts) { +static bool journal_ratelimit_group_expired(JournalRateLimitGroup *g, usec_t ts) { unsigned i; assert(g); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index f6caf0e212..eb124aa73f 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -44,6 +44,7 @@ #include "missing_audit.h" #include "mkdir.h" #include "parse-util.h" +#include "path-util.h" #include "proc-cmdline.h" #include "process-util.h" #include "rm-rf.h" @@ -76,11 +77,20 @@ #define DEFERRED_CLOSES_MAX (4096) -static int determine_path_usage(Server *s, const char *path, uint64_t *ret_used, uint64_t *ret_free) { +#define IDLE_TIMEOUT_USEC (30*USEC_PER_SEC) + +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; struct statvfs ss; + assert(s); + assert(path); assert(ret_used); assert(ret_free); @@ -163,13 +173,19 @@ static void patch_min_use(JournalStorage *storage) { storage->metrics.min_use = MAX(storage->metrics.min_use, storage->space.vfs_used); } +static JournalStorage* server_current_storage(Server *s) { + assert(s); + + return s->system_journal ? &s->system_storage : &s->runtime_storage; +} + static int determine_space(Server *s, uint64_t *available, uint64_t *limit) { JournalStorage *js; int r; assert(s); - js = s->system_journal ? &s->system_storage : &s->runtime_storage; + js = server_current_storage(s); r = cache_space_refresh(s, js); if (r >= 0) { @@ -189,7 +205,7 @@ void server_space_usage_message(Server *s, JournalStorage *storage) { assert(s); if (!storage) - storage = s->system_journal ? &s->system_storage : &s->runtime_storage; + storage = server_current_storage(s); if (cache_space_refresh(s, storage) < 0) return; @@ -280,8 +296,18 @@ static int open_journal( return r; } -static bool flushed_flag_is_set(void) { - return access("/run/systemd/journal/flushed", F_OK) >= 0; +static bool flushed_flag_is_set(Server *s) { + const char *fn; + + assert(s); + + /* We don't support the "flushing" concept for namespace instances, we assume them to always have + * access to /var */ + if (s->namespace) + return true; + + fn = strjoina(s->runtime_directory, "/flushed"); + return access(fn, F_OK) >= 0; } static int system_journal_open(Server *s, bool flush_requested, bool relinquish_requested) { @@ -290,17 +316,15 @@ static int system_journal_open(Server *s, bool flush_requested, bool relinquish_ if (!s->system_journal && IN_SET(s->storage, STORAGE_PERSISTENT, STORAGE_AUTO) && - (flush_requested || flushed_flag_is_set()) && + (flush_requested || flushed_flag_is_set(s)) && !relinquish_requested) { - /* If in auto mode: first try to create the machine - * path, but not the prefix. + /* 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 in persistent mode: create /var/log/journal and the machine path */ if (s->storage == STORAGE_PERSISTENT) - (void) mkdir_p("/var/log/journal/", 0755); + (void) mkdir_parents(s->system_storage.path, 0755); (void) mkdir(s->system_storage.path, 0755); @@ -317,12 +341,11 @@ static int system_journal_open(Server *s, bool flush_requested, bool relinquish_ r = 0; } - /* If the runtime journal is open, and we're post-flush, we're - * recovering from a failed system journal rotate (ENOSPC) - * for which the runtime journal was reopened. + /* If the runtime journal is open, and we're post-flush, we're recovering from a failed + * system journal rotate (ENOSPC) for which the runtime journal was reopened. * - * Perform an implicit flush to var, leaving the runtime - * journal closed, now that the system journal is back. + * Perform an implicit flush to var, leaving the runtime journal closed, now that the system + * journal is back. */ if (!flush_requested) (void) server_flush_to_var(s, true); @@ -349,12 +372,10 @@ static int system_journal_open(Server *s, bool flush_requested, bool relinquish_ } else { - /* OK, we really need the runtime journal, so create - * it if necessary. */ + /* OK, we really need the runtime journal, so create it if necessary. */ - (void) mkdir("/run/log", 0755); - (void) mkdir("/run/log/journal", 0755); - (void) mkdir_parents(fn, 0750); + (void) mkdir_parents(s->runtime_storage.path, 0755); + (void) mkdir(s->runtime_storage.path, 0750); r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_storage.metrics, &s->runtime_journal); if (r < 0) @@ -373,27 +394,23 @@ static int system_journal_open(Server *s, bool flush_requested, bool relinquish_ static JournalFile* find_journal(Server *s, uid_t uid) { _cleanup_free_ char *p = NULL; - int r; JournalFile *f; - sd_id128_t machine; + int r; assert(s); - /* A rotate that fails to create the new journal (ENOSPC) leaves the - * rotated journal as NULL. Unless we revisit opening, even after - * space is made available we'll continue to return NULL indefinitely. + /* A rotate that fails to create the new journal (ENOSPC) leaves the rotated journal as NULL. Unless + * we revisit opening, even after space is made available we'll continue to return NULL indefinitely. * - * system_journal_open() is a noop if the journals are already open, so - * we can just call it here to recover from failed rotates (or anything - * else that's left the journals as NULL). + * system_journal_open() is a noop if the journals are already open, so we can just call it here to + * recover from failed rotates (or anything else that's left the journals as NULL). * * Fixes https://github.com/systemd/systemd/issues/3968 */ (void) system_journal_open(s, false, false); - /* We split up user logs only on /var, not on /run. If the - * runtime file is open, we write to it exclusively, in order - * to guarantee proper order as soon as we flush /run to - * /var and close the runtime file. */ + /* We split up user logs only on /var, not on /run. If the runtime file is open, we write to it + * exclusively, in order to guarantee proper order as soon as we flush /run to /var and close the + * runtime file. */ if (s->runtime_journal) return s->runtime_journal; @@ -405,22 +422,14 @@ static JournalFile* find_journal(Server *s, uid_t 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) { + if (asprintf(&p, "%s/user-" UID_FMT ".journal", s->system_storage.path, uid) < 0) { log_oom(); return s->system_journal; } + /* Too many open? Then let's close one (or more) */ while (ordered_hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) { - /* Too many open? Then let's close one */ - f = ordered_hashmap_steal_first(s->user_journals); - assert(f); + assert_se(f = ordered_hashmap_steal_first(s->user_journals)); (void) journal_file_close(f); } @@ -428,14 +437,13 @@ static JournalFile* find_journal(Server *s, uid_t uid) { if (r < 0) return s->system_journal; - server_add_acls(f, uid); - r = ordered_hashmap_put(s->user_journals, UID_TO_PTR(uid), f); if (r < 0) { (void) journal_file_close(f); return s->system_journal; } + server_add_acls(f, uid); return f; } @@ -461,7 +469,6 @@ static int do_rotate( } server_add_acls(*f, uid); - return r; } @@ -498,36 +505,110 @@ static void server_vacuum_deferred_closes(Server *s) { } } -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; +static int vacuum_offline_user_journals(Server *s) { + _cleanup_closedir_ DIR *d = NULL; 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"); + d = opendir(s->system_storage.path); + if (!d) { + if (errno == ENOENT) + return 0; - if (asprintf(&path, "/var/log/journal/" SD_ID128_FORMAT_STR "/", SD_ID128_FORMAT_VAL(machine)) < 0) - return log_oom(); + return log_error_errno(errno, "Failed to open %s: %m", s->system_storage.path); + } - dir = opendir(path); - if (!dir) - return log_error_errno(errno, "Failed to open user journal directory '%s': %m", path); + for (;;) { + _cleanup_free_ char *u = NULL, *full = NULL; + _cleanup_close_ int fd = -1; + const char *a, *b; + struct dirent *de; + JournalFile *f; + uid_t uid; - if (ret_dir) - *ret_dir = TAKE_PTR(dir); - if (ret_path) - *ret_path = TAKE_PTR(path); + errno = 0; + de = readdir_no_dot(d); + if (!de) { + if (errno != 0) + log_warning_errno(errno, "Failed to enumerate %s, ignoring: %m", s->system_storage.path); + + break; + } + + 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) + return log_oom(); + + 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 = path_join(s->system_storage.path, de->d_name); + if (!full) + return log_oom(); + + 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); + } return 0; } void server_rotate(Server *s) { - _cleanup_free_ char *path = NULL; - _cleanup_closedir_ DIR *d = NULL; JournalFile *f; Iterator i; void *k; @@ -549,92 +630,10 @@ void server_rotate(Server *s) { ordered_hashmap_remove(s->user_journals, k); } - /* 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); - } - } + /* 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) + (void) vacuum_offline_user_journals(s); server_process_deferred_closes(s); } @@ -741,8 +740,7 @@ static void server_cache_hostname(Server *s) { if (!x) return; - free(s->hostname_field); - s->hostname_field = x; + free_and_replace(s->hostname_field, x); } static bool shall_try_append_again(JournalFile *f, int r) { @@ -1003,6 +1001,9 @@ static void dispatch_message_real( if (!isempty(s->hostname_field)) iovec[n++] = IOVEC_MAKE_STRING(s->hostname_field); + if (!isempty(s->namespace_field)) + iovec[n++] = IOVEC_MAKE_STRING(s->namespace_field); + assert(n <= m); if (s->split_mode == SPLIT_UID && c && uid_is_valid(c->uid)) @@ -1115,10 +1116,11 @@ void server_dispatch_message( } int server_flush_to_var(Server *s, bool require_flag_file) { - sd_journal *j = NULL; char ts[FORMAT_TIMESPAN_MAX]; - usec_t start; + sd_journal *j = NULL; + const char *fn; unsigned n = 0; + usec_t start; int r, k; assert(s); @@ -1126,10 +1128,13 @@ int server_flush_to_var(Server *s, bool require_flag_file) { if (!IN_SET(s->storage, STORAGE_AUTO, STORAGE_PERSISTENT)) return 0; - if (!s->runtime_journal) + if (s->namespace) /* Flushing concept does not exist for namespace instances */ return 0; - if (require_flag_file && !flushed_flag_is_set()) + if (!s->runtime_journal) /* Nothing to flush? */ + return 0; + + if (require_flag_file && !flushed_flag_is_set(s)) return 0; (void) system_journal_open(s, true, false); @@ -1137,7 +1142,7 @@ int server_flush_to_var(Server *s, bool require_flag_file) { if (!s->system_journal) return 0; - log_debug("Flushing to /var..."); + log_debug("Flushing to %s...", s->system_storage.path); start = now(CLOCK_MONOTONIC); @@ -1197,33 +1202,40 @@ finish: s->runtime_journal = journal_file_close(s->runtime_journal); if (r >= 0) - (void) rm_rf("/run/log/journal", REMOVE_ROOT); + (void) rm_rf(s->runtime_storage.path, REMOVE_ROOT); sd_journal_close(j); server_driver_message(s, 0, NULL, - LOG_MESSAGE("Time spent on flushing to /var is %s for %u entries.", + LOG_MESSAGE("Time spent on flushing to %s is %s for %u entries.", + s->system_storage.path, format_timespan(ts, sizeof(ts), now(CLOCK_MONOTONIC) - start, 0), n), NULL); - k = touch("/run/systemd/journal/flushed"); + fn = strjoina(s->runtime_directory, "/flushed"); + k = touch(fn); if (k < 0) - log_warning_errno(k, "Failed to touch /run/systemd/journal/flushed, ignoring: %m"); + log_warning_errno(k, "Failed to touch %s, ignoring: %m", fn); + server_refresh_idle_timer(s); return r; } static int server_relinquish_var(Server *s) { + const char *fn; assert(s); if (s->storage == STORAGE_NONE) return 0; + if (s->namespace) /* Concept does not exist for namespaced instances */ + return -EOPNOTSUPP; + if (s->runtime_journal && !s->system_journal) return 0; - log_debug("Relinquishing /var..."); + log_debug("Relinquishing %s...", s->system_storage.path); (void) system_journal_open(s, false, true); @@ -1231,13 +1243,20 @@ static int server_relinquish_var(Server *s) { ordered_hashmap_clear_with_destructor(s->user_journals, journal_file_close); set_clear_with_destructor(s->deferred_closes, journal_file_close); - if (unlink("/run/systemd/journal/flushed") < 0 && errno != ENOENT) - log_warning_errno(errno, "Failed to unlink /run/systemd/journal/flushed, ignoring: %m"); + fn = strjoina(s->runtime_directory, "/flushed"); + if (unlink(fn) < 0 && errno != ENOENT) + log_warning_errno(errno, "Failed to unlink %s, ignoring: %m", fn); + server_refresh_idle_timer(s); return 0; } -int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata) { +int server_process_datagram( + sd_event_source *es, + int fd, + uint32_t revents, + void *userdata) { + Server *s = userdata; struct ucred *ucred = NULL; struct timeval *tv = NULL; @@ -1352,6 +1371,8 @@ int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void } close_many(fds, n_fds); + + server_refresh_idle_timer(s); return 0; } @@ -1363,6 +1384,8 @@ static void server_full_flush(Server *s) { server_vacuum(s, false); server_space_usage_message(s, NULL); + + server_refresh_idle_timer(s); } static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { @@ -1370,6 +1393,11 @@ static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo * assert(s); + if (s->namespace) { + log_error("Received SIGUSR1 signal from PID " PID_FMT ", but flushing runtime journals not supported for namespaced instances.", si->ssi_pid); + return 0; + } + log_info("Received SIGUSR1 signal from PID " PID_FMT ", as request to flush runtime journal.", si->ssi_pid); server_full_flush(s); @@ -1377,6 +1405,7 @@ static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo * } static void server_full_rotate(Server *s) { + const char *fn; int r; assert(s); @@ -1390,9 +1419,10 @@ static void server_full_rotate(Server *s) { patch_min_use(&s->runtime_storage); /* Let clients know when the most recent rotation happened. */ - r = write_timestamp_file_atomic("/run/systemd/journal/rotated", now(CLOCK_MONOTONIC)); + fn = strjoina(s->runtime_directory, "/rotated"); + r = write_timestamp_file_atomic(fn, now(CLOCK_MONOTONIC)); if (r < 0) - log_warning_errno(r, "Failed to write /run/systemd/journal/rotated, ignoring: %m"); + log_warning_errno(r, "Failed to write %s, ignoring: %m", fn); } static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { @@ -1418,6 +1448,7 @@ static int dispatch_sigterm(sd_event_source *es, const struct signalfd_siginfo * } static void server_full_sync(Server *s) { + const char *fn; int r; assert(s); @@ -1425,9 +1456,10 @@ static void server_full_sync(Server *s) { server_sync(s); /* Let clients know when the most recent sync happened. */ - r = write_timestamp_file_atomic("/run/systemd/journal/synced", now(CLOCK_MONOTONIC)); + fn = strjoina(s->runtime_directory, "/synced"); + r = write_timestamp_file_atomic(fn, now(CLOCK_MONOTONIC)); if (r < 0) - log_warning_errno(r, "Failed to write /run/systemd/journal/synced, ignoring: %m"); + log_warning_errno(r, "Failed to write %s, ignoring: %m", fn); return; } @@ -1592,8 +1624,28 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat } static int server_parse_config_file(Server *s) { + int r; + assert(s); + if (s->namespace) { + const char *namespaced; + + /* If we are running in namespace mode, load the namespace specific configuration file, and nothing else */ + namespaced = strjoina(PKGSYSCONFDIR "/journald@", s->namespace, ".conf"); + + r = config_parse( + NULL, + namespaced, NULL, + "Journal\0", + config_item_perf_lookup, journald_gperf_lookup, + CONFIG_PARSE_WARN, s); + if (r < 0) + return r; + + return 0; + } + return config_parse_many_nulstr(PKGSYSCONFDIR "/journald.conf", CONF_PATHS_NULSTR("systemd/journald.conf.d"), "Journal\0", @@ -1936,6 +1988,8 @@ static int vl_method_flush_to_var(Varlink *link, JsonVariant *parameters, Varlin if (json_variant_elements(parameters) > 0) return varlink_error_invalid_parameter(link, parameters); + if (s->namespace) + return varlink_error(link, "io.systemd.Journal.NotSupportedByNamespaces", NULL); log_info("Received client request to flush runtime journal."); server_full_flush(s); @@ -1951,14 +2005,38 @@ static int vl_method_relinquish_var(Varlink *link, JsonVariant *parameters, Varl if (json_variant_elements(parameters) > 0) return varlink_error_invalid_parameter(link, parameters); + if (s->namespace) + return varlink_error(link, "io.systemd.Journal.NotSupportedByNamespaces", NULL); - log_info("Received client request to relinquish /var access."); + log_info("Received client request to relinquish %s access.", s->system_storage.path); server_relinquish_var(s); return varlink_reply(link, NULL); } -static int server_open_varlink(Server *s) { +static int vl_connect(VarlinkServer *server, Varlink *link, void *userdata) { + Server *s = userdata; + + assert(server); + assert(link); + assert(s); + + (void) server_start_or_stop_idle_timer(s); /* maybe we are no longer idle */ + + return 0; +} + +static void vl_disconnect(VarlinkServer *server, Varlink *link, void *userdata) { + Server *s = userdata; + + assert(server); + assert(link); + assert(s); + + (void) server_start_or_stop_idle_timer(s); /* maybe we are idle now */ +} + +static int server_open_varlink(Server *s, const char *socket, int fd) { int r; assert(s); @@ -1978,7 +2056,18 @@ static int server_open_varlink(Server *s) { if (r < 0) return r; - r = varlink_server_listen_address(s->varlink_server, "/run/systemd/journal/io.systemd.journal", 0600); + r = varlink_server_bind_connect(s->varlink_server, vl_connect); + if (r < 0) + return r; + + r = varlink_server_bind_disconnect(s->varlink_server, vl_disconnect); + if (r < 0) + return r; + + if (fd < 0) + r = varlink_server_listen_address(s->varlink_server, socket, 0600); + else + r = varlink_server_listen_fd(s->varlink_server, fd); if (r < 0) return r; @@ -1989,9 +2078,117 @@ static int server_open_varlink(Server *s) { return 0; } -int server_init(Server *s) { +static bool server_is_idle(Server *s) { + assert(s); + + /* The server for the main namespace is never idle */ + if (!s->namespace) + return false; + + /* If a retention maximum is set larger than the idle time we need to be running to enforce it, hence + * turn off the idle logic. */ + if (s->max_retention_usec > IDLE_TIMEOUT_USEC) + return false; + + /* We aren't idle if we have a varlink client */ + if (varlink_server_current_connections(s->varlink_server) > 0) + return false; + + /* If we have stdout streams we aren't idle */ + if (s->n_stdout_streams > 0) + return false; + + return true; +} + +static int server_idle_handler(sd_event_source *source, uint64_t usec, void *userdata) { + Server *s = userdata; + + assert(source); + assert(s); + + log_debug("Server is idle, exiting."); + sd_event_exit(s->event, 0); + return 0; +} + +int server_start_or_stop_idle_timer(Server *s) { + _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL; + usec_t when; + int r; + + assert(s); + + if (!server_is_idle(s)) { + s->idle_event_source = sd_event_source_disable_unref(s->idle_event_source); + return 0; + } + + if (s->idle_event_source) + return 1; + + r = sd_event_now(s->event, CLOCK_MONOTONIC, &when); + if (r < 0) + return log_error_errno(r, "Failed to determine current time: %m"); + + r = sd_event_add_time(s->event, &source, CLOCK_MONOTONIC, usec_add(when, IDLE_TIMEOUT_USEC), 0, server_idle_handler, s); + if (r < 0) + return log_error_errno(r, "Failed to allocate idle timer: %m"); + + r = sd_event_source_set_priority(source, SD_EVENT_PRIORITY_IDLE); + if (r < 0) + return log_error_errno(r, "Failed to set idle timer priority: %m"); + + (void) sd_event_source_set_description(source, "idle-timer"); + + s->idle_event_source = TAKE_PTR(source); + return 1; +} + +int server_refresh_idle_timer(Server *s) { + usec_t when; + int r; + + assert(s); + + if (!s->idle_event_source) + return 0; + + r = sd_event_now(s->event, CLOCK_MONOTONIC, &when); + if (r < 0) + return log_error_errno(r, "Failed to determine current time: %m"); + + r = sd_event_source_set_time(s->idle_event_source, usec_add(when, IDLE_TIMEOUT_USEC)); + if (r < 0) + return log_error_errno(r, "Failed to refresh idle timer: %m"); + + return 1; +} + +static int set_namespace(Server *s, const char *namespace) { + assert(s); + + if (!namespace) + return 0; + + if (!log_namespace_name_valid(namespace)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified namespace name not valid, refusing: %s", namespace); + + s->namespace = strdup(namespace); + if (!s->namespace) + return log_oom(); + + s->namespace_field = strjoin("_NAMESPACE=", namespace); + if (!s->namespace_field) + return log_oom(); + + return 1; +} + +int server_init(Server *s, const char *namespace) { + const char *native_socket, *syslog_socket, *stdout_socket, *varlink_socket, *e; _cleanup_fdset_free_ FDSet *fds = NULL; - int n, r, fd; + int n, r, fd, varlink_fd = -1; bool no_sockets; assert(s); @@ -2008,7 +2205,6 @@ int server_init(Server *s) { .compress.enabled = true, .compress.threshold_bytes = (uint64_t) -1, .seal = true, - .read_kmsg = true, .watchdog_usec = USEC_INFINITY, @@ -2034,22 +2230,43 @@ int server_init(Server *s) { .system_storage.name = "System Journal", }; + r = set_namespace(s, namespace); + if (r < 0) + return r; + + /* By default, only read from /dev/kmsg if are the main namespace */ + s->read_kmsg = !s->namespace; + s->storage = s->namespace ? STORAGE_PERSISTENT : STORAGE_AUTO; + journal_reset_metrics(&s->system_storage.metrics); journal_reset_metrics(&s->runtime_storage.metrics); server_parse_config_file(s); - r = proc_cmdline_parse(parse_proc_cmdline_item, s, PROC_CMDLINE_STRIP_RD_PREFIX); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + if (!s->namespace) { + /* Parse kernel command line, but only if we are not a namespace instance */ + r = proc_cmdline_parse(parse_proc_cmdline_item, s, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + } - if (!!s->ratelimit_interval ^ !!s->ratelimit_burst) { + if (!!s->ratelimit_interval != !!s->ratelimit_burst) { /* One set to 0 and the other not? */ log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0", s->ratelimit_interval, s->ratelimit_burst); s->ratelimit_interval = s->ratelimit_burst = 0; } - (void) mkdir_p("/run/systemd/journal", 0755); + e = getenv("RUNTIME_DIRECTORY"); + if (e) + s->runtime_directory = strdup(e); + else if (s->namespace) + s->runtime_directory = strjoin("/run/systemd/journal.", s->namespace); + else + s->runtime_directory = strdup("/run/systemd/journal"); + if (!s->runtime_directory) + return log_oom(); + + (void) mkdir_p(s->runtime_directory, 0755); s->user_journals = ordered_hashmap_new(NULL); if (!s->user_journals) @@ -2071,9 +2288,14 @@ int server_init(Server *s) { if (n < 0) return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); + native_socket = strjoina(s->runtime_directory, "/socket"); + stdout_socket = strjoina(s->runtime_directory, "/stdout"); + syslog_socket = strjoina(s->runtime_directory, "/dev-log"); + varlink_socket = strjoina(s->runtime_directory, "/io.systemd.journal"); + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { - if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) { + if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, native_socket, 0) > 0) { if (s->native_fd >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -2081,7 +2303,7 @@ int server_init(Server *s) { s->native_fd = fd; - } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) { + } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, stdout_socket, 0) > 0) { if (s->stdout_fd >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -2089,8 +2311,7 @@ int server_init(Server *s) { 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) { + } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, syslog_socket, 0) > 0) { if (s->syslog_fd >= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -2098,6 +2319,13 @@ int server_init(Server *s) { s->syslog_fd = fd; + } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, varlink_socket, 0) > 0) { + + if (varlink_fd >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Too many varlink sockets passed."); + + varlink_fd = fd; } else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) { if (s->audit_fd >= 0) @@ -2128,22 +2356,22 @@ int server_init(Server *s) { fds = fdset_free(fds); } - no_sockets = s->native_fd < 0 && s->stdout_fd < 0 && s->syslog_fd < 0 && s->audit_fd < 0; + no_sockets = s->native_fd < 0 && s->stdout_fd < 0 && s->syslog_fd < 0 && s->audit_fd < 0 && varlink_fd < 0; /* always open stdout, syslog, native, and kmsg sockets */ /* systemd-journald.socket: /run/systemd/journal/stdout */ - r = server_open_stdout_socket(s); + r = server_open_stdout_socket(s, stdout_socket); if (r < 0) return r; /* systemd-journald-dev-log.socket: /run/systemd/journal/dev-log */ - r = server_open_syslog_socket(s); + r = server_open_syslog_socket(s, syslog_socket); if (r < 0) return r; /* systemd-journald.socket: /run/systemd/journal/socket */ - r = server_open_native_socket(s); + r = server_open_native_socket(s, native_socket); if (r < 0) return r; @@ -2159,7 +2387,7 @@ int server_init(Server *s) { return r; } - r = server_open_varlink(s); + r = server_open_varlink(s, varlink_socket, varlink_fd); if (r < 0) return r; @@ -2177,26 +2405,43 @@ int server_init(Server *s) { s->ratelimit = journal_ratelimit_new(); if (!s->ratelimit) - return -ENOMEM; + return log_oom(); r = cg_get_root_path(&s->cgroup_root); if (r < 0) - return r; + return log_error_errno(r, "Failed to acquire cgroup root path: %m"); server_cache_hostname(s); server_cache_boot_id(s); server_cache_machine_id(s); - s->runtime_storage.path = path_join("/run/log/journal", SERVER_MACHINE_ID(s)); - s->system_storage.path = path_join("/var/log/journal", SERVER_MACHINE_ID(s)); - if (!s->runtime_storage.path || !s->system_storage.path) - return -ENOMEM; + if (s->namespace) + s->runtime_storage.path = strjoin("/run/log/journal/", SERVER_MACHINE_ID(s), ".", s->namespace); + else + s->runtime_storage.path = strjoin("/run/log/journal/", SERVER_MACHINE_ID(s)); + if (!s->runtime_storage.path) + return log_oom(); + + e = getenv("LOGS_DIRECTORY"); + if (e) + s->system_storage.path = strdup(e); + else if (s->namespace) + s->system_storage.path = strjoin("/var/log/journal/", SERVER_MACHINE_ID(s), ".", s->namespace); + else + s->system_storage.path = strjoin("/var/log/journal/", SERVER_MACHINE_ID(s)); + if (!s->system_storage.path) + return log_oom(); (void) server_connect_notify(s); (void) client_context_acquire_default(s); - return system_journal_open(s, false, false); + r = system_journal_open(s, false, false); + if (r < 0) + return r; + + server_start_or_stop_idle_timer(s); + return 0; } void server_maybe_append_tags(Server *s) { @@ -2218,6 +2463,9 @@ void server_maybe_append_tags(Server *s) { void server_done(Server *s) { assert(s); + free(s->namespace); + free(s->namespace_field); + set_free_with_destructor(s->deferred_closes, journal_file_close); while (s->stdout_streams) @@ -2246,6 +2494,7 @@ void server_done(Server *s) { sd_event_source_unref(s->hostname_event_source); sd_event_source_unref(s->notify_event_source); sd_event_source_unref(s->watchdog_event_source); + sd_event_source_unref(s->idle_event_source); sd_event_unref(s->event); safe_close(s->syslog_fd); @@ -2268,6 +2517,7 @@ void server_done(Server *s) { free(s->hostname_field); free(s->runtime_storage.path); free(s->system_storage.path); + free(s->runtime_directory); mmap_cache_unref(s->mmap); } diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index a609e9db06..f3405e967c 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -60,6 +60,8 @@ typedef struct JournalStorage { } JournalStorage; struct Server { + char *namespace; + int syslog_fd; int native_fd; int stdout_fd; @@ -84,6 +86,7 @@ struct Server { sd_event_source *hostname_event_source; sd_event_source *notify_event_source; sd_event_source *watchdog_event_source; + sd_event_source *idle_event_source; JournalFile *runtime_journal; JournalFile *system_journal; @@ -147,6 +150,8 @@ struct Server { char machine_id_field[sizeof("_MACHINE_ID=") + 32]; char boot_id_field[sizeof("_BOOT_ID=") + 32]; char *hostname_field; + char *namespace_field; + char *runtime_directory; /* Cached cgroup root, so that we don't have to query that all the time */ char *cgroup_root; @@ -172,7 +177,7 @@ struct Server { #define SERVER_MACHINE_ID(s) ((s)->machine_id_field + STRLEN("_MACHINE_ID=")) /* Extra fields for any log messages */ -#define N_IOVEC_META_FIELDS 22 +#define N_IOVEC_META_FIELDS 23 /* Extra fields for log messages that contain OBJECT_PID= (i.e. log about another process) */ #define N_IOVEC_OBJECT_FIELDS 18 @@ -204,7 +209,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_split_mode); const char *split_mode_to_string(SplitMode s) _const_; SplitMode split_mode_from_string(const char *s) _pure_; -int server_init(Server *s); +int server_init(Server *s, const char *namespace); void server_done(Server *s); void server_sync(Server *s); int server_vacuum(Server *s, bool verbose); @@ -214,3 +219,6 @@ int server_flush_to_var(Server *s, bool require_flag_file); void server_maybe_append_tags(Server *s); int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata); void server_space_usage_message(Server *s, JournalStorage *storage); + +int server_start_or_stop_idle_timer(Server *s); +int server_refresh_idle_timer(Server *s); diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 22a70ce5a6..d5147cb61a 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -17,6 +17,7 @@ #include "escape.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "io-util.h" #include "journald-console.h" #include "journald-context.h" @@ -109,6 +110,8 @@ void stdout_stream_free(StdoutStream *s) { if (s->in_notify_queue) LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s); + + (void) server_start_or_stop_idle_timer(s->server); /* Maybe we are idle now? */ } if (s->event_source) { @@ -139,7 +142,7 @@ void stdout_stream_destroy(StdoutStream *s) { } static int stdout_stream_save(StdoutStream *s) { - _cleanup_free_ char *temp_path = NULL; + _cleanup_(unlink_and_freep) char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -156,11 +159,12 @@ static int stdout_stream_save(StdoutStream *s) { return log_warning_errno(errno, "Failed to stat connected stream: %m"); /* We use device and inode numbers as identifier for the stream */ - if (asprintf(&s->state_file, "/run/systemd/journal/streams/%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0) + r = asprintf(&s->state_file, "%s/streams/%lu:%lu", s->server->runtime_directory, (unsigned long) st.st_dev, (unsigned long) st.st_ino); + if (r < 0) return log_oom(); } - (void) mkdir_p("/run/systemd/journal/streams", 0755); + (void) mkdir_parents(s->state_file, 0755); r = fopen_temporary(s->state_file, &f, &temp_path); if (r < 0) @@ -214,6 +218,8 @@ static int stdout_stream_save(StdoutStream *s) { goto fail; } + temp_path = mfree(temp_path); + if (!s->fdstore && !s->in_notify_queue) { LIST_PREPEND(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s); s->in_notify_queue = true; @@ -229,10 +235,6 @@ static int stdout_stream_save(StdoutStream *s) { fail: (void) unlink(s->state_file); - - if (temp_path) - (void) unlink(temp_path); - return log_error_errno(r, "Failed to save stream data %s: %m", s->state_file); } @@ -590,12 +592,14 @@ int stdout_stream_install(Server *s, int fd, StdoutStream **ret) { if (r < 0) return log_error_errno(r, "Failed to generate stream ID: %m"); - stream = new0(StdoutStream, 1); + stream = new(StdoutStream, 1); if (!stream) return log_oom(); - stream->fd = -1; - stream->priority = LOG_INFO; + *stream = (StdoutStream) { + .fd = -1, + .priority = LOG_INFO, + }; xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)); @@ -629,11 +633,12 @@ int stdout_stream_install(Server *s, int fd, StdoutStream **ret) { LIST_PREPEND(stdout_stream, s->stdout_streams, stream); s->n_stdout_streams++; + (void) server_start_or_stop_idle_timer(s); /* Maybe no longer idle? */ + if (ret) *ret = stream; - stream = NULL; - + TAKE_PTR(stream); return 0; } @@ -676,7 +681,7 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent if (r < 0) return r; - fd = -1; + TAKE_FD(fd); return 0; } @@ -694,7 +699,7 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) { assert(fname); if (!stream->state_file) { - stream->state_file = path_join("/run/systemd/journal/streams", fname); + stream->state_file = path_join(stream->server->runtime_directory, "streams", fname); if (!stream->state_file) return log_oom(); } @@ -783,14 +788,16 @@ static int stdout_stream_restore(Server *s, const char *fname, int fd) { int server_restore_streams(Server *s, FDSet *fds) { _cleanup_closedir_ DIR *d = NULL; struct dirent *de; + const char *path; int r; - d = opendir("/run/systemd/journal/streams"); + path = strjoina(s->runtime_directory, "/streams"); + d = opendir(path); if (!d) { if (errno == ENOENT) return 0; - return log_warning_errno(errno, "Failed to enumerate /run/systemd/journal/streams: %m"); + return log_warning_errno(errno, "Failed to enumerate %s: %m", path); } FOREACH_DIRENT(de, d, goto fail) { @@ -818,8 +825,7 @@ int server_restore_streams(Server *s, FDSet *fds) { /* No file descriptor? Then let's delete the state file */ log_debug("Cannot restore stream file %s", de->d_name); if (unlinkat(dirfd(d), de->d_name, 0) < 0) - log_warning_errno(errno, "Failed to remove /run/systemd/journal/streams/%s: %m", - de->d_name); + log_warning_errno(errno, "Failed to remove %s%s: %m", path, de->d_name); continue; } @@ -836,16 +842,21 @@ fail: return log_error_errno(errno, "Failed to read streams directory: %m"); } -int server_open_stdout_socket(Server *s) { - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/stdout", - }; +int server_open_stdout_socket(Server *s, const char *stdout_socket) { int r; assert(s); + assert(stdout_socket); if (s->stdout_fd < 0) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + }; + + r = sockaddr_un_set_path(&sa.un, stdout_socket); + if (r < 0) + return log_error_errno(r, "Unable to use namespace path %s for AF_UNIX socket: %m", stdout_socket); + s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (s->stdout_fd < 0) return log_error_errno(errno, "socket() failed: %m"); diff --git a/src/journal/journald-stream.h b/src/journal/journald-stream.h index 487376e763..7ab0016618 100644 --- a/src/journal/journald-stream.h +++ b/src/journal/journald-stream.h @@ -6,7 +6,7 @@ typedef struct StdoutStream StdoutStream; #include "fdset.h" #include "journald-server.h" -int server_open_stdout_socket(Server *s); +int server_open_stdout_socket(Server *s, const char *stdout_socket); int server_restore_streams(Server *s, FDSet *fds); void stdout_stream_free(StdoutStream *s); diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index a60a259bc4..59ce983bb9 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -25,11 +25,15 @@ /* Warn once every 30s if we missed syslog message */ #define WARN_FORWARD_SYSLOG_MISSED_USEC (30 * USEC_PER_SEC) -static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, const struct ucred *ucred, const struct timeval *tv) { +static void forward_syslog_iovec( + Server *s, + const struct iovec *iovec, + unsigned n_iovec, + const struct ucred *ucred, + const struct timeval *tv) { - static const union sockaddr_union sa = { + union sockaddr_union sa = { .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/syslog", }; struct msghdr msghdr = { .msg_iov = (struct iovec *) iovec, @@ -42,11 +46,20 @@ static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned struct cmsghdr cmsghdr; uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; } control; + const char *j; + int r; assert(s); assert(iovec); assert(n_iovec > 0); + j = strjoina(s->runtime_directory, "/syslog"); + r = sockaddr_un_set_path(&sa.un, j); + if (r < 0) { + log_debug_errno(r, "Forwarding socket path %s too long for AF_UNIX, not forwarding: %m", j); + return; + } + if (ucred) { zero(control); msghdr.msg_control = &control; @@ -441,17 +454,21 @@ void server_process_syslog_message( server_dispatch_message(s, iovec, n, m, context, tv, priority, 0); } -int server_open_syslog_socket(Server *s) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/dev-log", - }; +int server_open_syslog_socket(Server *s, const char *syslog_socket) { int r; assert(s); + assert(syslog_socket); if (s->syslog_fd < 0) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + }; + + r = sockaddr_un_set_path(&sa.un, syslog_socket); + if (r < 0) + return log_error_errno(r, "Unable to use namespace path %s for AF_UNIX socket: %m", syslog_socket); + s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); if (s->syslog_fd < 0) return log_error_errno(errno, "socket() failed: %m"); diff --git a/src/journal/journald-syslog.h b/src/journal/journald-syslog.h index 7ca5897264..5ad601001c 100644 --- a/src/journal/journald-syslog.h +++ b/src/journal/journald-syslog.h @@ -10,6 +10,6 @@ size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid); void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv); void server_process_syslog_message(Server *s, const char *buf, size_t buf_len, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len); -int server_open_syslog_socket(Server *s); +int server_open_syslog_socket(Server *s, const char *syslog_socket); void server_maybe_warn_forward_syslog_missed(Server *s); diff --git a/src/journal/journald.c b/src/journal/journald.c index 5e7b1dcb4a..d78ab3aa20 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.c @@ -14,14 +14,17 @@ #include "sigbus.h" int main(int argc, char *argv[]) { + const char *namespace; Server server; int r; - if (argc > 1) { - log_error("This program does not take arguments."); + if (argc > 2) { + log_error("This program takes one or no arguments."); return EXIT_FAILURE; } + namespace = argc > 1 ? empty_to_null(argv[1]) : NULL; + log_set_prohibit_ipc(true); log_set_target(LOG_TARGET_AUTO); log_set_facility(LOG_SYSLOG); @@ -32,7 +35,7 @@ int main(int argc, char *argv[]) { sigbus_install(); - r = server_init(&server); + r = server_init(&server, namespace); if (r < 0) goto finish; @@ -40,7 +43,11 @@ int main(int argc, char *argv[]) { server_flush_to_var(&server, true); server_flush_dev_kmsg(&server); - log_debug("systemd-journald running as pid "PID_FMT, getpid_cached()); + if (server.namespace) + log_debug("systemd-journald running as PID "PID_FMT" for namespace '%s'.", getpid_cached(), server.namespace ?: ""); + else + log_debug("systemd-journald running as PID "PID_FMT" for the system.", getpid_cached()); + server_driver_message(&server, 0, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_START_STR, LOG_MESSAGE("Journal started"), @@ -55,8 +62,10 @@ int main(int argc, char *argv[]) { usec_t t = USEC_INFINITY, n; r = sd_event_get_state(server.event); - if (r < 0) + if (r < 0) { + log_error_errno(r, "Failed to get event loop state: %m"); goto finish; + } if (r == SD_EVENT_FINISHED) break; @@ -99,7 +108,11 @@ int main(int argc, char *argv[]) { server_maybe_warn_forward_syslog_missed(&server); } - log_debug("systemd-journald stopped as pid "PID_FMT, getpid_cached()); + if (server.namespace) + log_debug("systemd-journald stopped as PID "PID_FMT" for namespace '%s'.", getpid_cached(), server.namespace ?: ""); + else + log_debug("systemd-journald stopped as PID "PID_FMT" for the system.", getpid_cached()); + server_driver_message(&server, 0, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_STOP_STR, LOG_MESSAGE("Journal stopped"), diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 837aecdf60..bced8af3e3 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -39,6 +39,7 @@ #include "stdio-util.h" #include "string-util.h" #include "strv.h" +#include "syslog-util.h" #define JOURNAL_FILES_MAX 7168 @@ -204,16 +205,17 @@ static bool same_field(const void *_a, size_t s, const void *_b, size_t t) { static Match *match_new(Match *p, MatchType t) { Match *m; - m = new0(Match, 1); + m = new(Match, 1); if (!m) return NULL; - m->type = t; + *m = (Match) { + .type = t, + .parent = p, + }; - if (p) { - m->parent = p; + if (p) LIST_PREPEND(matches, p->matches, m); - } return m; } @@ -1434,22 +1436,63 @@ static void remove_file_real(sd_journal *j, JournalFile *f) { static int dirname_is_machine_id(const char *fn) { sd_id128_t id, machine; + const char *e; int r; + /* Returns true if the specified directory name matches the local machine ID */ + r = sd_id128_get_machine(&machine); if (r < 0) return r; - r = sd_id128_from_string(fn, &id); + e = strchr(fn, '.'); + if (e) { + const char *k; + + /* Looks like it has a namespace suffix. Verify that. */ + if (!log_namespace_name_valid(e + 1)) + return false; + + k = strndupa(fn, e - fn); + r = sd_id128_from_string(k, &id); + } else + r = sd_id128_from_string(fn, &id); if (r < 0) return r; return sd_id128_equal(id, machine); } +static int dirname_has_namespace(const char *fn, const char *namespace) { + const char *e; + + /* Returns true if the specified directory name matches the specified namespace */ + + e = strchr(fn, '.'); + if (e) { + const char *k; + + if (!namespace) + return false; + + if (!streq(e + 1, namespace)) + return false; + + k = strndupa(fn, e - fn); + return id128_is_valid(k); + } + + if (namespace) + return false; + + return id128_is_valid(fn); +} + static bool dirent_is_journal_file(const struct dirent *de) { assert(de); + /* Returns true if the specified directory entry looks like a journal file we might be interested in */ + if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN)) return false; @@ -1457,13 +1500,26 @@ static bool dirent_is_journal_file(const struct dirent *de) { endswith(de->d_name, ".journal~"); } -static bool dirent_is_id128_subdir(const struct dirent *de) { +static bool dirent_is_journal_subdir(const struct dirent *de) { + const char *e, *n; assert(de); + /* returns true if the specified directory entry looks like a directory that might contain journal + * files we might be interested in, i.e. is either a 128bit ID or a 128bit ID suffixed by a + * namespace. */ + if (!IN_SET(de->d_type, DT_DIR, DT_LNK, DT_UNKNOWN)) return false; - return id128_is_valid(de->d_name); + e = strchr(de->d_name, '.'); + if (!e) + return id128_is_valid(de->d_name); /* No namespace */ + + n = strndupa(de->d_name, e - de->d_name); + if (!id128_is_valid(n)) + return false; + + return log_namespace_name_valid(e + 1); } static int directory_open(sd_journal *j, const char *path, DIR **ret) { @@ -1500,7 +1556,7 @@ static void directory_enumerate(sd_journal *j, Directory *m, DIR *d) { if (dirent_is_journal_file(de)) (void) add_file_by_name(j, m->path, de->d_name); - if (m->is_root && dirent_is_id128_subdir(de)) + if (m->is_root && dirent_is_journal_subdir(de)) (void) add_directory(j, m->path, de->d_name); } @@ -1540,7 +1596,11 @@ static void directory_watch(sd_journal *j, Directory *m, int fd, uint32_t mask) } } -static int add_directory(sd_journal *j, const char *prefix, const char *dirname) { +static int add_directory( + sd_journal *j, + const char *prefix, + const char *dirname) { + _cleanup_free_ char *path = NULL; _cleanup_closedir_ DIR *d = NULL; Directory *m; @@ -1565,6 +1625,11 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname) !((dirname && dirname_is_machine_id(dirname) > 0) || path_has_prefix(j, path, "/run"))) return 0; + if (!(FLAGS_SET(j->flags, SD_JOURNAL_ALL_NAMESPACES) || + dirname_has_namespace(dirname, j->namespace) > 0 || + (FLAGS_SET(j->flags, SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE) && dirname_has_namespace(dirname, NULL) > 0))) + return 0; + r = directory_open(j, path, &d); if (r < 0) { log_debug_errno(r, "Failed to open directory '%s': %m", path); @@ -1573,14 +1638,16 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname) m = hashmap_get(j->directories_by_path, path); if (!m) { - m = new0(Directory, 1); + m = new(Directory, 1); if (!m) { r = -ENOMEM; goto fail; } - m->is_root = false; - m->path = path; + *m = (Directory) { + .is_root = false, + .path = path, + }; if (hashmap_put(j->directories_by_path, m->path, m) < 0) { free(m); @@ -1803,7 +1870,7 @@ static int allocate_inotify(sd_journal *j) { return hashmap_ensure_allocated(&j->directories_by_wd, NULL); } -static sd_journal *journal_new(int flags, const char *path) { +static sd_journal *journal_new(int flags, const char *path, const char *namespace) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; j = new0(sd_journal, 1); @@ -1829,6 +1896,12 @@ static sd_journal *journal_new(int flags, const char *path) { j->path = t; } + if (namespace) { + j->namespace = strdup(namespace); + if (!j->namespace) + return NULL; + } + j->files = ordered_hashmap_new(&path_hash_ops); if (!j->files) return NULL; @@ -1845,16 +1918,19 @@ static sd_journal *journal_new(int flags, const char *path) { #define OPEN_ALLOWED_FLAGS \ (SD_JOURNAL_LOCAL_ONLY | \ SD_JOURNAL_RUNTIME_ONLY | \ - SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER) + SD_JOURNAL_SYSTEM | \ + SD_JOURNAL_CURRENT_USER | \ + SD_JOURNAL_ALL_NAMESPACES | \ + SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE) -_public_ int sd_journal_open(sd_journal **ret, int flags) { +_public_ int sd_journal_open_namespace(sd_journal **ret, const char *namespace, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; int r; assert_return(ret, -EINVAL); assert_return((flags & ~OPEN_ALLOWED_FLAGS) == 0, -EINVAL); - j = journal_new(flags, NULL); + j = journal_new(flags, NULL, namespace); if (!j) return -ENOMEM; @@ -1866,6 +1942,10 @@ _public_ int sd_journal_open(sd_journal **ret, int flags) { return 0; } +_public_ int sd_journal_open(sd_journal **ret, int flags) { + return sd_journal_open_namespace(ret, NULL, flags); +} + #define OPEN_CONTAINER_ALLOWED_FLAGS \ (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM) @@ -1875,7 +1955,7 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in char *p; int r; - /* This is pretty much deprecated, people should use machined's OpenMachineRootDirectory() call instead in + /* This is deprecated, people should use machined's OpenMachineRootDirectory() call instead in * combination with sd_journal_open_directory_fd(). */ assert_return(machine, -EINVAL); @@ -1897,7 +1977,7 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in if (!streq_ptr(class, "container")) return -EIO; - j = journal_new(flags, root); + j = journal_new(flags, root, NULL); if (!j) return -ENOMEM; @@ -1921,7 +2001,7 @@ _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int f assert_return(path, -EINVAL); assert_return((flags & ~OPEN_DIRECTORY_ALLOWED_FLAGS) == 0, -EINVAL); - j = journal_new(flags, path); + j = journal_new(flags, path, NULL); if (!j) return -ENOMEM; @@ -1944,7 +2024,7 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla assert_return(ret, -EINVAL); assert_return(flags == 0, -EINVAL); - j = journal_new(flags, NULL); + j = journal_new(flags, NULL, NULL); if (!j) return -ENOMEM; @@ -1979,7 +2059,7 @@ _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { if (!S_ISDIR(st.st_mode)) return -EBADFD; - j = journal_new(flags, NULL); + j = journal_new(flags, NULL, NULL); if (!j) return -ENOMEM; @@ -2007,7 +2087,7 @@ _public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fd assert_return(n_fds > 0, -EBADF); assert_return(flags == 0, -EINVAL); - j = journal_new(flags, NULL); + j = journal_new(flags, NULL, NULL); if (!j) return -ENOMEM; @@ -2079,6 +2159,7 @@ _public_ void sd_journal_close(sd_journal *j) { free(j->path); free(j->prefix); + free(j->namespace); free(j->unique_field); free(j->fields_buffer); free(j); @@ -2480,7 +2561,7 @@ static void process_q_overflow(sd_journal *j) { log_debug("Reiteration complete."); } -static void process_inotify_event(sd_journal *j, struct inotify_event *e) { +static void process_inotify_event(sd_journal *j, const struct inotify_event *e) { Directory *d; assert(j); diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index a3dd9f68b7..17a39e1d0f 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -693,4 +693,5 @@ global: sd_event_source_get_child_process_own; sd_event_source_set_child_process_own; sd_event_source_send_child_signal; + sd_journal_open_namespace; } LIBSYSTEMD_243; diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 6fea2c9e33..2e39f55763 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -576,6 +576,7 @@ static int print_session_status_info(sd_bus *bus, const char *path, bool *new_li show_journal_by_unit( stdout, i.scope, + NULL, arg_output, 0, i.timestamp.monotonic, @@ -660,6 +661,7 @@ static int print_user_status_info(sd_bus *bus, const char *path, bool *new_line) show_journal_by_unit( stdout, i.slice, + NULL, arg_output, 0, i.timestamp.monotonic, diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 2377416b44..61234582a3 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -626,6 +626,7 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { show_journal_by_unit( stdout, i->unit, + NULL, arg_output, 0, i->timestamp.monotonic, diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 254007ef11..28d85944a8 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -833,7 +833,8 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con "RuntimeDirectoryPreserve", "Personality", "KeyringMode", - "NetworkNamespacePath")) + "NetworkNamespacePath", + "LogNamespace")) return bus_append_string(m, field, eq); if (STR_IN_SET(field, "IgnoreSIGPIPE", diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index ce0a4acf9c..605412aec5 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -1453,6 +1453,7 @@ int add_match_this_boot(sd_journal *j, const char *machine) { int show_journal_by_unit( FILE *f, const char *unit, + const char *log_namespace, OutputMode mode, unsigned n_columns, usec_t not_before, @@ -1473,7 +1474,7 @@ int show_journal_by_unit( if (how_many <= 0) return 0; - r = sd_journal_open(&j, journal_open_flags); + r = sd_journal_open_namespace(&j, log_namespace, journal_open_flags | SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE); if (r < 0) return log_error_errno(r, "Failed to open journal: %m"); diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index 1e0c4ea146..345efa4b2b 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -46,6 +46,7 @@ int add_matches_for_user_unit( int show_journal_by_unit( FILE *f, const char *unit, + const char *namespace, OutputMode mode, unsigned n_columns, usec_t not_before, diff --git a/src/shared/varlink.c b/src/shared/varlink.c index 77cea00cb9..012ce5308c 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -168,6 +168,7 @@ struct VarlinkServer { Hashmap *methods; VarlinkConnect connect_callback; + VarlinkDisconnect disconnect_callback; sd_event *event; int64_t event_priority; @@ -1146,6 +1147,7 @@ int varlink_flush(Varlink *v) { } static void varlink_detach_server(Varlink *v) { + VarlinkServer *saved_server; assert(v); if (!v->server) @@ -1169,8 +1171,15 @@ static void varlink_detach_server(Varlink *v) { v->server->n_connections--; /* If this is a connection associated to a server, then let's disconnect the server and the - * connection from each other. This drops the dangling reference that connect_callback() set up. */ - v->server = varlink_server_unref(v->server); + * connection from each other. This drops the dangling reference that connect_callback() set up. But + * before we release the references, let's call the disconnection callback if it is defined. */ + + saved_server = TAKE_PTR(v->server); + + if (saved_server->disconnect_callback) + saved_server->disconnect_callback(saved_server, v, saved_server->userdata); + + varlink_server_unref(saved_server); varlink_unref(v); } @@ -2413,6 +2422,16 @@ int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect callback) { return 0; } +int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect callback) { + assert_return(s, -EINVAL); + + if (callback && s->disconnect_callback && callback != s->disconnect_callback) + return -EBUSY; + + s->disconnect_callback = callback; + return 0; +} + unsigned varlink_server_connections_max(VarlinkServer *s) { int dts; @@ -2460,6 +2479,12 @@ int varlink_server_set_connections_max(VarlinkServer *s, unsigned m) { return 0; } +unsigned varlink_server_current_connections(VarlinkServer *s) { + assert_return(s, UINT_MAX); + + return s->n_connections; +} + int varlink_server_set_description(VarlinkServer *s, const char *description) { assert_return(s, -EINVAL); diff --git a/src/shared/varlink.h b/src/shared/varlink.h index 0d9617d403..7440f2ca44 100644 --- a/src/shared/varlink.h +++ b/src/shared/varlink.h @@ -51,6 +51,7 @@ typedef enum VarlinkServerFlags { typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); typedef int (*VarlinkReply)(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata); typedef int (*VarlinkConnect)(VarlinkServer *server, Varlink *link, void *userdata); +typedef void (*VarlinkDisconnect)(VarlinkServer *server, Varlink *link, void *userdata); int varlink_connect_address(Varlink **ret, const char *address); int varlink_connect_fd(Varlink **ret, int fd); @@ -134,6 +135,7 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth int varlink_server_bind_method_many_internal(VarlinkServer *s, ...); #define varlink_server_bind_method_many(s, ...) varlink_server_bind_method_many_internal(s, __VA_ARGS__, NULL) int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect connect); +int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect disconnect); void* varlink_server_set_userdata(VarlinkServer *s, void *userdata); void* varlink_server_get_userdata(VarlinkServer *s); @@ -150,6 +152,8 @@ unsigned varlink_server_connections_per_uid_max(VarlinkServer *s); int varlink_server_set_connections_per_uid_max(VarlinkServer *s, unsigned m); int varlink_server_set_connections_max(VarlinkServer *s, unsigned m); +unsigned varlink_server_current_connections(VarlinkServer *s); + int varlink_server_set_description(VarlinkServer *s, const char *description); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 60872bc537..5547058296 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4007,6 +4007,8 @@ typedef struct UnitStatusInfo { int exit_code, exit_status; + const char *log_namespace; + usec_t condition_timestamp; bool condition_result; LIST_HEAD(UnitCondition, conditions); @@ -4545,6 +4547,7 @@ static void print_status_info( show_journal_by_unit( stdout, i->id, + i->log_namespace, arg_output, 0, i->inactive_exit_timestamp_monotonic, @@ -5491,6 +5494,7 @@ static int show_one( { "ExecMainExitTimestamp", "t", NULL, offsetof(UnitStatusInfo, exit_timestamp) }, { "ExecMainCode", "i", NULL, offsetof(UnitStatusInfo, exit_code) }, { "ExecMainStatus", "i", NULL, offsetof(UnitStatusInfo, exit_status) }, + { "LogNamespace", "s", NULL, offsetof(UnitStatusInfo, log_namespace) }, { "ConditionTimestamp", "t", NULL, offsetof(UnitStatusInfo, condition_timestamp) }, { "ConditionResult", "b", NULL, offsetof(UnitStatusInfo, condition_result) }, { "Conditions", "a(sbbsi)", map_conditions, 0 }, diff --git a/src/systemd/_sd-common.h b/src/systemd/_sd-common.h index b3ee7bbc24..8158ee733e 100644 --- a/src/systemd/_sd-common.h +++ b/src/systemd/_sd-common.h @@ -45,6 +45,18 @@ typedef void (*_sd_destroy_t)(void *userdata); # define _sd_pure_ __attribute__((__pure__)) #endif +/* Note that strictly speaking __deprecated__ has been available before GCC 6. However, starting with GCC 6 + * it also works on enum values, which we are interested in. Since this is a developer-facing feature anyway + * (as opposed to build engineer-facing), let's hence conditionalize this to gcc 6, given that the developers + * are probably going to use something newer anyway. */ +#ifndef _sd_deprecated_ +# if __GNUC__ >= 6 +# define _sd_deprecated_ __attribute__((__deprecated__)) +# else +# define _sd_deprecated_ +# endif +#endif + #ifndef _SD_STRINGIFY # define _SD_XSTRINGIFY(x) #x # define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h index b4bf3b9176..29ff40ab26 100644 --- a/src/systemd/sd-journal.h +++ b/src/systemd/sd-journal.h @@ -64,13 +64,15 @@ typedef struct sd_journal sd_journal; /* Open flags */ enum { - SD_JOURNAL_LOCAL_ONLY = 1 << 0, - SD_JOURNAL_RUNTIME_ONLY = 1 << 1, - SD_JOURNAL_SYSTEM = 1 << 2, - SD_JOURNAL_CURRENT_USER = 1 << 3, - SD_JOURNAL_OS_ROOT = 1 << 4, + SD_JOURNAL_LOCAL_ONLY = 1 << 0, + SD_JOURNAL_RUNTIME_ONLY = 1 << 1, + SD_JOURNAL_SYSTEM = 1 << 2, + SD_JOURNAL_CURRENT_USER = 1 << 3, + SD_JOURNAL_OS_ROOT = 1 << 4, + SD_JOURNAL_ALL_NAMESPACES = 1 << 5, /* Show all namespaces, not just the default or specified one */ + SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE = 1 << 6, /* Show default namespace in addition to specified one */ - SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM /* deprecated name */ + SD_JOURNAL_SYSTEM_ONLY _sd_deprecated_ = SD_JOURNAL_SYSTEM /* deprecated name */ }; /* Wakeup event types */ @@ -81,11 +83,12 @@ enum { }; int sd_journal_open(sd_journal **ret, int flags); +int sd_journal_open_namespace(sd_journal **ret, const char *name_space, int flags); int sd_journal_open_directory(sd_journal **ret, const char *path, int flags); int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags); int sd_journal_open_files(sd_journal **ret, const char **paths, int flags); int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags); -int sd_journal_open_container(sd_journal **ret, const char *machine, int flags); /* deprecated */ +int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) _sd_deprecated_; /* deprecated */ void sd_journal_close(sd_journal *j); int sd_journal_previous(sd_journal *j); diff --git a/src/test/generate-sym-test.py b/src/test/generate-sym-test.py index 4d358b8e34..fdb9e3ecb7 100755 --- a/src/test/generate-sym-test.py +++ b/src/test/generate-sym-test.py @@ -6,6 +6,9 @@ for header in sys.argv[2:]: print('#include "{}"'.format(header.split('/')[-1])) print(''' +/* We want to check deprecated symbols too, without complaining */ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + const void* symbols[] = {''') for line in open(sys.argv[1]): diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 41ea733b7e..f2bfc6c62b 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -148,6 +148,7 @@ static void test_protect_kernel_logs(void) { NULL, 0, NULL, NULL, + NULL, PROTECT_HOME_NO, PROTECT_SYSTEM_NO, 0, diff --git a/src/test/test-ns.c b/src/test/test-ns.c index e9233a1643..48a5ff9f2d 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -72,6 +72,7 @@ int main(int argc, char *argv[]) { &(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1, tmp_dir, var_tmp_dir, + NULL, PROTECT_HOME_NO, PROTECT_SYSTEM_NO, 0, diff --git a/test/TEST-44-LOG-NAMESPACE/Makefile b/test/TEST-44-LOG-NAMESPACE/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-44-LOG-NAMESPACE/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-44-LOG-NAMESPACE/test.sh b/test/TEST-44-LOG-NAMESPACE/test.sh new file mode 100755 index 0000000000..4dfb4418db --- /dev/null +++ b/test/TEST-44-LOG-NAMESPACE/test.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -e +TEST_DESCRIPTION="test log namespaces" + +. $TEST_BASE_DIR/test-functions + +test_setup() { + create_empty_image_rootdir + + ( + LOG_LEVEL=5 + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + setup_basic_environment + + mask_supporting_services + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service < /tmp/hello-world +journalctl > /tmp/no-hello-world + +grep "hello world" /tmp/hello-world +! grep "hello world" /tmp/no-hello-world + +systemd-analyze log-level info + +echo OK > /testok + +exit 0 diff --git a/tmpfiles.d/systemd.conf.m4 b/tmpfiles.d/systemd.conf.m4 index 4914e8ec8c..11d87d275b 100644 --- a/tmpfiles.d/systemd.conf.m4 +++ b/tmpfiles.d/systemd.conf.m4 @@ -32,17 +32,17 @@ Z /run/log/journal/%m ~2750 root systemd-journal - - m4_ifdef(`HAVE_ACL',`m4_dnl m4_ifdef(`ENABLE_ADM_GROUP',`m4_dnl m4_ifdef(`ENABLE_WHEEL_GROUP',`` -a+ /run/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x -a+ /run/log/journal/%m - - - - group:adm:r-x,group:wheel:r-x +a+ /run/log/journal - - - - d:group::r-x,d:group:adm:r-x,d:group:wheel:r-x,group::r-x,group:adm:r-x,group:wheel:r-x +a+ /run/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x,group:adm:r-x,group:wheel:r-x a+ /run/log/journal/%m/*.journal* - - - - group:adm:r--,group:wheel:r-- '',`` -a+ /run/log/journal/%m - - - - d:group:adm:r-x -a+ /run/log/journal/%m - - - - group:adm:r-x +a+ /run/log/journal - - - - d:group::r-x,d:group:adm:r-x,group::r-x,group:adm:r-x +a+ /run/log/journal/%m - - - - d:group:adm:r-x,group:adm:r-x a+ /run/log/journal/%m/*.journal* - - - - group:adm:r-- '')',`m4_dnl m4_ifdef(`ENABLE_WHEEL_GROUP',`` -a+ /run/log/journal/%m - - - - d:group:wheel:r-x -a+ /run/log/journal/%m - - - - group:wheel:r-x +a+ /run/log/journal - - - - d:group::r-x,d:group:wheel:r-x,group::r-x,group:wheel:r-x +a+ /run/log/journal/%m - - - - d:group:wheel:r-x,group:wheel:r-x a+ /run/log/journal/%m/*.journal* - - - - group:wheel:r-- '')')')m4_dnl @@ -52,23 +52,17 @@ z /var/log/journal/%m/system.journal 0640 root systemd-journal - - m4_ifdef(`HAVE_ACL',`m4_dnl m4_ifdef(`ENABLE_ADM_GROUP',`m4_dnl m4_ifdef(`ENABLE_WHEEL_GROUP',`` -a+ /var/log/journal - - - - d:group::r-x,d:group:adm:r-x,d:group:wheel:r-x -a+ /var/log/journal - - - - group::r-x,group:adm:r-x,group:wheel:r-x -a+ /var/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x -a+ /var/log/journal/%m - - - - group:adm:r-x,group:wheel:r-x +a+ /var/log/journal - - - - d:group::r-x,d:group:adm:r-x,d:group:wheel:r-x,group::r-x,group:adm:r-x,group:wheel:r-x +a+ /var/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x,group:adm:r-x,group:wheel:r-x a+ /var/log/journal/%m/system.journal - - - - group:adm:r--,group:wheel:r-- '', `` -a+ /var/log/journal - - - - d:group::r-x,d:group:adm:r-x -a+ /var/log/journal - - - - group::r-x,group:adm:r-x -a+ /var/log/journal/%m - - - - d:group:adm:r-x -a+ /var/log/journal/%m - - - - group:adm:r-x +a+ /var/log/journal - - - - d:group::r-x,d:group:adm:r-x,group::r-x,group:adm:r-x +a+ /var/log/journal/%m - - - - d:group:adm:r-x,group:adm:r-x a+ /var/log/journal/%m/system.journal - - - - group:adm:r-- '')',`m4_dnl m4_ifdef(`ENABLE_WHEEL_GROUP',`` -a+ /var/log/journal - - - - d:group::r-x,d:group:wheel:r-x -a+ /var/log/journal - - - - group::r-x,group:wheel:r-x -a+ /var/log/journal/%m - - - - d:group:wheel:r-x -a+ /var/log/journal/%m - - - - group:wheel:r-x +a+ /var/log/journal - - - - d:group::r-x,d:group:wheel:r-x,group::r-x,group:wheel:r-x +a+ /var/log/journal/%m - - - - d:group:wheel:r-x,group:wheel:r-x a+ /var/log/journal/%m/system.journal - - - - group:wheel:r-- '')')')m4_dnl diff --git a/units/meson.build b/units/meson.build index d99cafb39f..0dc85ac64a 100644 --- a/units/meson.build +++ b/units/meson.build @@ -114,6 +114,8 @@ units = [ ['systemd-kexec.service', ''], ['systemd-machine-id-commit.service', '', 'sysinit.target.wants/'], + ['systemd-journald@.socket', ''], + ['systemd-journald-varlink@.socket', ''], ['systemd-networkd.socket', 'ENABLE_NETWORKD'], ['systemd-poweroff.service', ''], ['systemd-reboot.service', ''], @@ -180,6 +182,7 @@ in_units = [ ['systemd-journal-upload.service', 'ENABLE_REMOTE HAVE_LIBCURL'], ['systemd-journald.service', '', 'sysinit.target.wants/'], + ['systemd-journald@.service', ''], ['systemd-localed.service', 'ENABLE_LOCALED', 'dbus-org.freedesktop.locale1.service'], ['systemd-logind.service', 'ENABLE_LOGIND', diff --git a/units/systemd-journald-varlink@.socket b/units/systemd-journald-varlink@.socket new file mode 100644 index 0000000000..b6730c2fa5 --- /dev/null +++ b/units/systemd-journald-varlink@.socket @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# This file is part of systemd. +# +# 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. + +[Unit] +Description=Journal Varlink Socket for Namespace %i +Documentation=man:systemd-journald.service(8) man:journald.conf(5) +StopWhenUnneeded=yes + +[Socket] +Service=systemd-journald@%i.service +ListenStream=/run/systemd/journal.%i/io.systemd.journal +SocketMode=0600 diff --git a/units/systemd-journald.service.in b/units/systemd-journald.service.in index 303d5a4826..5144868bcb 100644 --- a/units/systemd-journald.service.in +++ b/units/systemd-journald.service.in @@ -16,7 +16,6 @@ After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-a Before=sysinit.target [Service] -OOMScoreAdjust=-250 CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE DeviceAllow=char-* rw ExecStart=@rootlibexecdir@/systemd-journald @@ -25,12 +24,15 @@ IPAddressDeny=any LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes +OOMScoreAdjust=-250 Restart=always RestartSec=0 RestrictAddressFamilies=AF_UNIX AF_NETLINK RestrictNamespaces=yes RestrictRealtime=yes RestrictSUIDSGID=yes +RuntimeDirectory=systemd/journal +RuntimeDirectoryPreserve=yes Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket StandardOutput=null SystemCallArchitectures=native diff --git a/units/systemd-journald@.service.in b/units/systemd-journald@.service.in new file mode 100644 index 0000000000..e7ea919328 --- /dev/null +++ b/units/systemd-journald@.service.in @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# This file is part of systemd. +# +# 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. + +[Unit] +Description=Journal Service for Namespace %i +Documentation=man:systemd-journald.service(8) man:journald.conf(5) +Requires=systemd-journald@%i.socket systemd-journald-varlink@%i.socket +After=systemd-journald@%i.socket systemd-journald-varlink@%i.socket + +[Service] +CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE +DevicePolicy=closed +ExecStart=@rootlibexecdir@/systemd-journald %i +FileDescriptorStoreMax=4224 +Group=systemd-journal +IPAddressDeny=any +LockPersonality=yes +LogsDirectory=journal/%m.%i +LogsDirectoryMode=02755 +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +RestrictAddressFamilies=AF_UNIX AF_NETLINK +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +RuntimeDirectory=systemd/journal.%i +RuntimeDirectoryPreserve=yes +Sockets=systemd-journald@%i.socket +StandardOutput=null +SystemCallArchitectures=native +SystemCallErrorNumber=EPERM +SystemCallFilter=@system-service +Type=notify +@SERVICE_WATCHDOG@ + +# If there are many split up journal files we need a lot of fds to access them +# all in parallel. +LimitNOFILE=@HIGH_RLIMIT_NOFILE@ diff --git a/units/systemd-journald@.socket b/units/systemd-journald@.socket new file mode 100644 index 0000000000..3badd7842a --- /dev/null +++ b/units/systemd-journald@.socket @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: LGPL-2.1+ +# +# This file is part of systemd. +# +# 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. + +[Unit] +Description=Journal Socket for Namespace %i +Documentation=man:systemd-journald.service(8) man:journald.conf(5) +StopWhenUnneeded=yes + +[Socket] +Service=systemd-journald@%i.service +ListenStream=/run/systemd/journal.%i/stdout +ListenDatagram=/run/systemd/journal.%i/socket +ListenDatagram=/run/systemd/journal.%i/dev-log +SocketMode=0666 +PassCredentials=yes +PassSecurity=yes +ReceiveBuffer=8M +SendBuffer=8M