diff --git a/man/systemd-journald.service.xml b/man/systemd-journald.service.xml
index b099d74afd..7b33d3ff17 100644
--- a/man/systemd-journald.service.xml
+++ b/man/systemd-journald.service.xml
@@ -90,17 +90,19 @@
for more information about the collected metadata.
- Log data collected by the journal is primarily text-based
- but can also include binary data where necessary. All objects
- stored in the journal can be up to 2^64-1 bytes in size.
+ Log data collected by the journal is primarily text-based but can also include binary data where
+ necessary. Individual fields making up a log record stored in the journal may be up to 2^64-1 bytes in size.
- By default, the journal stores log data in
- /run/log/journal/. Since
- /run/ is volatile, log data is lost at
- reboot. To make the data persistent, it is sufficient to create
- /var/log/journal/ where
- systemd-journald will then store the
- data:
+ The journal service stores log data either persistently below /var/log/journal or in a
+ volatile way below /run/log/journal/ (in the latter case it is lost at reboot). By default, log
+ data is stored persistently if /var/log/journal/ exists during boot, with an implicit fallback
+ to volatile storage otherwise. Use Storage= in
+ journald.conf5 to configure
+ where log data is placed, independently of the existence of /var/log/journal/.
+
+ On systems where /var/log/journal/ does not exist yet but where persistent logging is
+ desired (and the default journald.conf is used), it is sufficient to create the directory, and
+ ensure it has the correct access modes and ownership:
mkdir -p /var/log/journal
systemd-tmpfiles --create --prefix /var/log/journal
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index 8b538edf77..d043555860 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -686,81 +686,96 @@
that the screen and scrollback buffer is cleared. Defaults to
no.
+
SyslogIdentifier=
- Sets the process name to prefix log lines sent
- to the logging system or the kernel log buffer with. If not
- set, defaults to the process name of the executed process.
- This option is only useful when
- StandardOutput= or
- StandardError= are set to
- , or
- (or to the same settings in combination
- with ).
+ Sets the process name ("syslog tag") to prefix log lines sent to the logging
+ system or the kernel log buffer with. If not set, defaults to the process name of the executed process. This
+ option is only useful when StandardOutput= or StandardError= are set to
+ , or (or to the same settings in
+ combination with ) and only applies to log messages written to stdout or
+ stderr.
+
SyslogFacility=
- Sets the syslog facility to use when logging
- to syslog. One of ,
- , ,
- , ,
- , ,
- , ,
- , ,
- , ,
- , ,
- , ,
- , or
- . See
+ Sets the syslog facility identifier to use when logging. One of
+ , , , ,
+ , , , ,
+ , , , ,
+ , , , ,
+ , , or . See
syslog3
- for details. This option is only useful when
- StandardOutput= or
- StandardError= are set to
- . Defaults to
- .
+ for details. This option is only useful when StandardOutput= or
+ StandardError= are set to , or
+ (or to the same settings in combination with ), and only applies
+ to log messages written to stdout or stderr. Defaults to .
+
SyslogLevel=
- The default syslog level to use when logging to
- syslog or the kernel log buffer. One of
- ,
- ,
- ,
- ,
- ,
- ,
- ,
- . See
- syslog3
- for details. This option is only useful when
- StandardOutput= or
- StandardError= are set to
- or . Note that
- individual lines output by the daemon might be prefixed with a
- different log level which can be used to override the default
- log level specified here. The interpretation of these prefixes
- may be disabled with SyslogLevelPrefix=,
- see below. For details, see
- sd-daemon3.
+ The default syslog log level to use when logging to the logging system or
+ the kernel log buffer. One of , , ,
+ , , , ,
+ . See syslog3 for
+ details. This option is only useful when StandardOutput= or
+ StandardError= are set to , or
+ (or to the same settings in combination with ), and only applies
+ to log messages written to stdout or stderr. Note that individual lines output by executed processes may be
+ prefixed with a different log level which can be used to override the default log level specified here. The
+ interpretation of these prefixes may be disabled with SyslogLevelPrefix=, see below. For
+ details, see sd-daemon3.
- Defaults to
- .
+ Defaults to .
SyslogLevelPrefix=
- Takes a boolean argument. If true and
- StandardOutput= or
- StandardError= are set to
- , or
- , log lines written by the executed
- process that are prefixed with a log level will be passed on
- to syslog with this log level set but the prefix removed. If
- set to false, the interpretation of these prefixes is disabled
- and the logged lines are passed on as-is. For details about
- this prefixing see
- sd-daemon3.
- Defaults to true.
+ Takes a boolean argument. If true and StandardOutput= or
+ StandardError= are set to , or
+ (or to the same settings in combination with ), log lines
+ written by the executed process that are prefixed with a log level will be processed with this log level set
+ but the prefix removed. If set to false, the interpretation of these prefixes is disabled and the logged lines
+ are passed on as-is. This only applies to log messages written to stdout or stderr. For details
+ about this prefixing see
+ sd-daemon3. Defaults to
+ true.
+
+
+
+ LogLevelMax=
+ Configures filtering by log level of log messages generated by this unit. Takes a
+ syslog log level, one of (lowest log level, only highest priority
+ messages), , , , ,
+ , , (highest log level, also lowest priority
+ messages). See syslog3 for
+ details. By default no filtering is applied (i.e. the default maximum log level is ). Use
+ this option to configure the logging system to drop log messages of a specific service above the specified
+ level. For example, set LogLevelMax= in order to turn off debug logging
+ of a particularly chatty unit. Note that the the configured level is applied to any log messages written by any
+ of the processes belonging to this unit, sent via any supported logging protocol. The filtering is applied
+ early in the logging pipeline, before any kind of further processing is done. Moreover, messages which pass
+ through this filter successfully might still be dropped by filters applied at a later stage in the logging
+ subsystem. For example, MaxLevelStore= configured in
+ journald.conf5 might
+ prohibit messages of higher log levels to be stored on disk, even though the per-unit
+ LogLevelMax= permitted it to be processed.
+
+
+
+ LogExtraFields=
+ Configures additional log metadata fields to include in all log records generated by processes
+ associated with this unit. This setting takes one or more journal field assignments in the format
+ FIELD=VALUE separated by whitespace. See
+ systemd.journal-fields7 for
+ details on the journal field concept. Even though the underlying journal implementation permits binary field
+ values, this setting accepts only valid UTF-8 values. To include space characters in a journal field value,
+ enclose the assignment in double quotes ("). The usual specifiers are expanded in all assignments (see
+ below). Note that this setting is not only useful for attaching additional metadata to log records of a unit,
+ but given that all fields and values are indexed may also be used to implement cross-unit log record
+ matching. Assign an empty string to reset the list.
diff --git a/src/basic/log.c b/src/basic/log.c
index 4f0fe54579..81e106803f 100644
--- a/src/basic/log.c
+++ b/src/basic/log.c
@@ -847,8 +847,8 @@ int log_oom_internal(LogRealm realm, const char *file, int line, const char *fun
int log_format_iovec(
struct iovec *iovec,
- unsigned iovec_len,
- unsigned *n,
+ size_t iovec_len,
+ size_t *n,
bool newline_separator,
int error,
const char *format,
@@ -928,7 +928,7 @@ int log_struct_internal(
if (journal_fd >= 0) {
char header[LINE_MAX];
struct iovec iovec[17] = {};
- unsigned n = 0, i;
+ size_t n = 0, i;
int r;
struct msghdr mh = {
.msg_iov = iovec,
diff --git a/src/basic/log.h b/src/basic/log.h
index 10a6032788..e2608b2a92 100644
--- a/src/basic/log.h
+++ b/src/basic/log.h
@@ -180,8 +180,8 @@ int log_oom_internal(
int log_format_iovec(
struct iovec *iovec,
- unsigned iovec_len,
- unsigned *n,
+ size_t iovec_len,
+ size_t *n,
bool newline_separator,
int error,
const char *format,
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 30861617bd..eeb4ac9a01 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -26,15 +26,17 @@
#include "af-list.h"
#include "alloc-util.h"
#include "bus-util.h"
-#include "capability-util.h"
#include "cap-list.h"
+#include "capability-util.h"
#include "dbus-execute.h"
#include "env-util.h"
#include "errno-list.h"
#include "execute.h"
#include "fd-util.h"
#include "fileio.h"
+#include "io-util.h"
#include "ioprio.h"
+#include "journal-util.h"
#include "missing.h"
#include "mount-util.h"
#include "namespace.h"
@@ -771,6 +773,37 @@ static int property_get_bind_paths(
return sd_bus_message_close_container(reply);
}
+static int property_get_log_extra_fields(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ExecContext *c = userdata;
+ size_t i;
+ int r;
+
+ assert(bus);
+ assert(c);
+ assert(property);
+ assert(reply);
+
+ r = sd_bus_message_open_container(reply, 'a', "ay");
+ if (r < 0)
+ return r;
+
+ for (i = 0; i < c->n_log_extra_fields; i++) {
+ r = sd_bus_message_append_array(reply, 'y', c->log_extra_fields[i].iov_base, c->log_extra_fields[i].iov_len);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -838,6 +871,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("SyslogLevelPrefix", "b", bus_property_get_bool, offsetof(ExecContext, syslog_level_prefix), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SyslogLevel", "i", property_get_syslog_level, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SyslogFacility", "i", property_get_syslog_facility, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LogLevelMax", "i", bus_property_get_int, offsetof(ExecContext, log_level_max), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LogExtraFields", "aay", property_get_log_extra_fields, 0, 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", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("AmbientCapabilities", "t", property_get_ambient_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1137,6 +1172,98 @@ int bus_exec_context_set_transient_property(
}
return 1;
+
+ } else if (streq(name, "LogLevelMax")) {
+ int32_t level;
+
+ r = sd_bus_message_read(message, "i", &level);
+ if (r < 0)
+ return r;
+
+ if (!log_level_is_valid(level))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Maximum log level value out of range");
+
+ if (mode != UNIT_CHECK) {
+ c->log_level_max = level;
+ unit_write_drop_in_private_format(u, mode, name, "LogLevelMax=%i", level);
+ }
+
+ return 1;
+
+ } else if (streq(name, "LogExtraFields")) {
+ size_t n = 0;
+
+ r = sd_bus_message_enter_container(message, 'a', "ay");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ _cleanup_free_ void *copy = NULL;
+ struct iovec *t;
+ const char *eq;
+ const void *p;
+ size_t sz;
+
+ /* Note that we expect a byte array for each field, instead of a string. That's because on the
+ * lower-level journal fields can actually contain binary data and are not restricted to text,
+ * and we should not "lose precision" in our types on the way. That said, I am pretty sure
+ * actually encoding binary data as unit metadata is not a good idea. Hence we actually refuse
+ * any actual binary data, and only accept UTF-8. This allows us to eventually lift this
+ * limitation, should a good, valid usecase arise. */
+
+ r = sd_bus_message_read_array(message, 'y', &p, &sz);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (memchr(p, 0, sz))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Journal field contains zero byte");
+
+ eq = memchr(p, '=', sz);
+ if (!eq)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Journal field contains no '=' character");
+ if (!journal_field_valid(p, eq - (const char*) p, false))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Journal field invalid");
+
+ if (mode != UNIT_CHECK) {
+ t = realloc_multiply(c->log_extra_fields, sizeof(struct iovec), c->n_log_extra_fields+1);
+ if (!t)
+ return -ENOMEM;
+ c->log_extra_fields = t;
+ }
+
+ copy = malloc(sz + 1);
+ if (!copy)
+ return -ENOMEM;
+
+ memcpy(copy, p, sz);
+ ((uint8_t*) copy)[sz] = 0;
+
+ if (!utf8_is_valid(copy))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Journal field is not valid UTF-8");
+
+ if (mode != UNIT_CHECK) {
+ c->log_extra_fields[c->n_log_extra_fields++] = IOVEC_MAKE(copy, sz);
+ unit_write_drop_in_private_format(u, mode, name, "LogExtraFields=%s", (char*) copy);
+
+ copy = NULL;
+ }
+
+ n++;
+ }
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ if (mode != UNIT_CHECK && n == 0) {
+ exec_context_free_log_extra_fields(c);
+ unit_write_drop_in_private(u, mode, name, "LogExtraFields=");
+ }
+
+ return 1;
+
} else if (streq(name, "SecureBits")) {
int n;
diff --git a/src/core/execute.c b/src/core/execute.c
index 9f7181549e..45df0318e0 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -3483,11 +3483,12 @@ void exec_context_init(ExecContext *c) {
c->directories[i].mode = 0755;
c->capability_bounding_set = CAP_ALL;
c->restrict_namespaces = NAMESPACE_FLAGS_ALL;
+ c->log_level_max = -1;
}
void exec_context_done(ExecContext *c) {
- unsigned l;
ExecDirectoryType i;
+ size_t l;
assert(c);
@@ -3534,6 +3535,10 @@ void exec_context_done(ExecContext *c) {
for (i = 0; i < _EXEC_DIRECTORY_TYPE_MAX; i++)
c->directories[i].paths = strv_free(c->directories[i].paths);
+
+ c->log_level_max = -1;
+
+ exec_context_free_log_extra_fields(c);
}
int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_prefix) {
@@ -3796,9 +3801,9 @@ static void strv_fprintf(FILE *f, char **l) {
}
void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
+ ExecDirectoryType dt;
char **e, **d;
unsigned i;
- ExecDirectoryType dt;
int r;
assert(c);
@@ -3966,6 +3971,26 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
fprintf(f, "%sSyslogLevel: %s\n", prefix, lvl_str);
}
+ if (c->log_level_max >= 0) {
+ _cleanup_free_ char *t = NULL;
+
+ (void) log_level_to_string_alloc(c->log_level_max, &t);
+
+ fprintf(f, "%sLogLevelMax: %s\n", prefix, strna(t));
+ }
+
+ if (c->n_log_extra_fields > 0) {
+ size_t j;
+
+ for (j = 0; j < c->n_log_extra_fields; j++) {
+ fprintf(f, "%sLogExtraFields: ", prefix);
+ fwrite(c->log_extra_fields[j].iov_base,
+ 1, c->log_extra_fields[j].iov_len,
+ f);
+ fputc('\n', f);
+ }
+ }
+
if (c->secure_bits) {
_cleanup_free_ char *str = NULL;
@@ -4177,6 +4202,17 @@ int exec_context_get_effective_ioprio(ExecContext *c) {
return p;
}
+void exec_context_free_log_extra_fields(ExecContext *c) {
+ size_t l;
+
+ assert(c);
+
+ for (l = 0; l < c->n_log_extra_fields; l++)
+ free(c->log_extra_fields[l].iov_base);
+ c->log_extra_fields = mfree(c->log_extra_fields);
+ c->n_log_extra_fields = 0;
+}
+
void exec_status_start(ExecStatus *s, pid_t pid) {
assert(s);
diff --git a/src/core/execute.h b/src/core/execute.h
index 23abdd4516..ab9d0dbe2d 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -212,6 +212,11 @@ struct ExecContext {
char *syslog_identifier;
bool syslog_level_prefix;
+ int log_level_max;
+
+ struct iovec* log_extra_fields;
+ size_t n_log_extra_fields;
+
bool cpu_sched_reset_on_fork;
bool non_blocking;
bool private_tmp;
@@ -353,6 +358,8 @@ bool exec_context_maintains_privileges(ExecContext *c);
int exec_context_get_effective_ioprio(ExecContext *c);
+void exec_context_free_log_extra_fields(ExecContext *c);
+
void exec_status_start(ExecStatus *s, pid_t pid);
void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status);
void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix);
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 42c2dbb9e4..5b73f9aa9b 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -49,6 +49,8 @@ $1.SyslogIdentifier, config_parse_unit_string_printf, 0,
$1.SyslogFacility, config_parse_log_facility, 0, offsetof($1, exec_context.syslog_priority)
$1.SyslogLevel, config_parse_log_level, 0, offsetof($1, exec_context.syslog_priority)
$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix)
+$1.LogLevelMax, config_parse_log_level, 0, offsetof($1, exec_context.log_level_max)
+$1.LogExtraFields, config_parse_log_extra_fields, 0, offsetof($1, exec_context)
$1.Capabilities, config_parse_warn_compat, DISABLED_LEGACY, offsetof($1, exec_context)
$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context)
$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 02d507f7b2..34fc04b65a 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -45,7 +45,9 @@
#include "escape.h"
#include "fd-util.h"
#include "fs-util.h"
+#include "io-util.h"
#include "ioprio.h"
+#include "journal-util.h"
#include "load-fragment.h"
#include "log.h"
#include "missing.h"
@@ -2331,6 +2333,78 @@ int config_parse_unset_environ(
return 0;
}
+int config_parse_log_extra_fields(
+ 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) {
+
+ ExecContext *c = data;
+ Unit *u = userdata;
+ const char *p;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(c);
+
+ if (isempty(rvalue)) {
+ exec_context_free_log_extra_fields(c);
+ return 0;
+ }
+
+ for (p = rvalue;; ) {
+ _cleanup_free_ char *word = NULL, *k = NULL;
+ struct iovec *t;
+ const char *eq;
+
+ r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_QUOTES);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ r = unit_full_printf(u, word, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring field: %m", word);
+ continue;
+ }
+
+ eq = strchr(k, '=');
+ if (!eq) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Log field lacks '=' character, ignoring field: %s", k);
+ continue;
+ }
+
+ if (!journal_field_valid(k, eq-k, false)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Log field name is invalid, ignoring field: %s", k);
+ continue;
+ }
+
+ t = realloc_multiply(c->log_extra_fields, sizeof(struct iovec), c->n_log_extra_fields+1);
+ if (!t)
+ return log_oom();
+
+ c->log_extra_fields = t;
+ c->log_extra_fields[c->n_log_extra_fields++] = IOVEC_MAKE_STRING(k);
+
+ k = NULL;
+ }
+
+ return 0;
+}
+
int config_parse_ip_tos(const char *unit,
const char *filename,
unsigned line,
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 0bd6ec15d6..b0a3ce2c67 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -121,6 +121,7 @@ int config_parse_bind_paths(const char *unit, const char *filename, unsigned lin
int config_parse_exec_keyring_mode(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);
int config_parse_job_timeout_sec(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);
int config_parse_job_running_timeout_sec(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);
+int config_parse_log_extra_fields(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);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/core/manager.c b/src/core/manager.c
index 9978be7a48..ecb87167fa 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -53,14 +53,15 @@
#include "dirent-util.h"
#include "env-util.h"
#include "escape.h"
-#include "execute.h"
#include "exec-util.h"
+#include "execute.h"
#include "exit-status.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hashmap.h"
#include "io-util.h"
+#include "label.h"
#include "locale-setup.h"
#include "log.h"
#include "macro.h"
@@ -714,6 +715,12 @@ int manager_new(UnitFileScope scope, unsigned test_run_flags, Manager **_m) {
goto fail;
}
+ if (MANAGER_IS_SYSTEM(m)) {
+ r = mkdir_label("/run/systemd/units", 0755);
+ if (r < 0 && r != -EEXIST)
+ goto fail;
+ }
+
/* Note that we do not set up the notify fd here. We do that after deserialization,
* since they might have gotten serialized across the reexec. */
diff --git a/src/core/mount.c b/src/core/mount.c
index e2c480a51d..6a89a2d539 100644
--- a/src/core/mount.c
+++ b/src/core/mount.c
@@ -769,6 +769,8 @@ static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) {
m->reset_accounting = false;
}
+ unit_export_state_files(UNIT(m));
+
r = unit_setup_exec_runtime(UNIT(m));
if (r < 0)
return r;
diff --git a/src/core/scope.c b/src/core/scope.c
index 073b216ea2..98be2191df 100644
--- a/src/core/scope.c
+++ b/src/core/scope.c
@@ -339,6 +339,8 @@ static int scope_start(Unit *u) {
(void) unit_reset_cpu_accounting(u);
(void) unit_reset_ip_accounting(u);
+ unit_export_state_files(UNIT(s));
+
r = unit_attach_pids_to_cgroup(u);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to add PIDs to scope's control group: %m");
diff --git a/src/core/service.c b/src/core/service.c
index f42f1effb7..ac3f14665d 100644
--- a/src/core/service.c
+++ b/src/core/service.c
@@ -1251,6 +1251,8 @@ static int service_spawn(
s->reset_accounting = false;
}
+ unit_export_state_files(UNIT(s));
+
r = unit_setup_exec_runtime(UNIT(s));
if (r < 0)
return r;
diff --git a/src/core/socket.c b/src/core/socket.c
index 0686de1d1b..40b2502a40 100644
--- a/src/core/socket.c
+++ b/src/core/socket.c
@@ -1884,6 +1884,8 @@ static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) {
s->reset_accounting = false;
}
+ unit_export_state_files(UNIT(s));
+
r = unit_setup_exec_runtime(UNIT(s));
if (r < 0)
return r;
diff --git a/src/core/swap.c b/src/core/swap.c
index 42d91ba3f7..fca8c8dd0a 100644
--- a/src/core/swap.c
+++ b/src/core/swap.c
@@ -619,6 +619,8 @@ static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) {
s->reset_accounting = false;
}
+ unit_export_state_files(UNIT(s));
+
r = unit_setup_exec_runtime(UNIT(s));
if (r < 0)
goto fail;
diff --git a/src/core/unit.c b/src/core/unit.c
index e2446804e7..785bd5dc44 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -38,6 +38,7 @@
#include "fd-util.h"
#include "fileio-label.h"
#include "format-util.h"
+#include "fs-util.h"
#include "id128-util.h"
#include "io-util.h"
#include "load-dropin.h"
@@ -51,6 +52,7 @@
#include "process-util.h"
#include "set.h"
#include "signal-util.h"
+#include "sparse-endian.h"
#include "special.h"
#include "stat-util.h"
#include "stdio-util.h"
@@ -597,6 +599,9 @@ void unit_free(Unit *u) {
unit_release_cgroup(u);
+ if (!MANAGER_IS_RELOADING(u->manager))
+ unit_unlink_state_files(u);
+
unit_unref_uid_gid(u, false);
(void) manager_update_failed_units(u->manager, u, false);
@@ -2296,9 +2301,11 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su
/* Keep track of failed units */
(void) manager_update_failed_units(u->manager, u, ns == UNIT_FAILED);
- /* Make sure the cgroup is always removed when we become inactive */
- if (UNIT_IS_INACTIVE_OR_FAILED(ns))
+ /* Make sure the cgroup and state files are always removed when we become inactive */
+ if (UNIT_IS_INACTIVE_OR_FAILED(ns)) {
unit_prune_cgroup(u);
+ unit_unlink_state_files(u);
+ }
/* Note that this doesn't apply to RemainAfterExit services exiting
* successfully, since there's no change of state in that case. Which is
@@ -3102,6 +3109,10 @@ int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) {
unit_serialize_item(u, f, "transient", yes_no(u->transient));
+ unit_serialize_item(u, f, "exported-invocation-id", yes_no(u->exported_invocation_id));
+ unit_serialize_item(u, f, "exported-log-level-max", yes_no(u->exported_log_level_max));
+ unit_serialize_item(u, f, "exported-log-extra-fields", yes_no(u->exported_log_extra_fields));
+
unit_serialize_item_format(u, f, "cpu-usage-base", "%" PRIu64, u->cpu_usage_base);
if (u->cpu_usage_last != NSEC_INFINITY)
unit_serialize_item_format(u, f, "cpu-usage-last", "%" PRIu64, u->cpu_usage_last);
@@ -3342,6 +3353,36 @@ int unit_deserialize(Unit *u, FILE *f, FDSet *fds) {
continue;
+ } else if (streq(l, "exported-invocation-id")) {
+
+ r = parse_boolean(v);
+ if (r < 0)
+ log_unit_debug(u, "Failed to parse exported invocation ID bool %s, ignoring.", v);
+ else
+ u->exported_invocation_id = r;
+
+ continue;
+
+ } else if (streq(l, "exported-log-level-max")) {
+
+ r = parse_boolean(v);
+ if (r < 0)
+ log_unit_debug(u, "Failed to parse exported log level max bool %s, ignoring.", v);
+ else
+ u->exported_log_level_max = r;
+
+ continue;
+
+ } else if (streq(l, "exported-log-extra-fields")) {
+
+ r = parse_boolean(v);
+ if (r < 0)
+ log_unit_debug(u, "Failed to parse exported log extra fields bool %s, ignoring.", v);
+ else
+ u->exported_log_extra_fields = r;
+
+ continue;
+
} else if (STR_IN_SET(l, "cpu-usage-base", "cpuacct-usage-base")) {
r = safe_atou64(v, &u->cpu_usage_base);
@@ -4913,3 +4954,174 @@ void unit_remove_dependencies(Unit *u, UnitDependencyMask mask) {
} while (!done);
}
}
+
+static int unit_export_invocation_id(Unit *u) {
+ const char *p;
+ int r;
+
+ assert(u);
+
+ if (u->exported_invocation_id)
+ return 0;
+
+ if (sd_id128_is_null(u->invocation_id))
+ return 0;
+
+ p = strjoina("/run/systemd/units/invocation:", u->id);
+ r = symlink_atomic(u->invocation_id_string, p);
+ if (r < 0)
+ return log_unit_debug_errno(u, r, "Failed to create invocation ID symlink %s: %m", p);
+
+ u->exported_invocation_id = true;
+ return 0;
+}
+
+static int unit_export_log_level_max(Unit *u, const ExecContext *c) {
+ const char *p;
+ char buf[2];
+ int r;
+
+ assert(u);
+ assert(c);
+
+ if (u->exported_log_level_max)
+ return 0;
+
+ if (c->log_level_max < 0)
+ return 0;
+
+ assert(c->log_level_max <= 7);
+
+ buf[0] = '0' + c->log_level_max;
+ buf[1] = 0;
+
+ p = strjoina("/run/systemd/units/log-level-max:", u->id);
+ r = symlink_atomic(buf, p);
+ if (r < 0)
+ return log_unit_debug_errno(u, r, "Failed to create maximum log level symlink %s: %m", p);
+
+ u->exported_log_level_max = true;
+ return 0;
+}
+
+static int unit_export_log_extra_fields(Unit *u, const ExecContext *c) {
+ _cleanup_close_ int fd = -1;
+ struct iovec *iovec;
+ const char *p;
+ char *pattern;
+ le64_t *sizes;
+ ssize_t n;
+ size_t i;
+ int r;
+
+ if (u->exported_log_extra_fields)
+ return 0;
+
+ if (c->n_log_extra_fields <= 0)
+ return 0;
+
+ sizes = newa(le64_t, c->n_log_extra_fields);
+ iovec = newa(struct iovec, c->n_log_extra_fields * 2);
+
+ for (i = 0; i < c->n_log_extra_fields; i++) {
+ sizes[i] = htole64(c->log_extra_fields[i].iov_len);
+
+ iovec[i*2] = IOVEC_MAKE(sizes + i, sizeof(le64_t));
+ iovec[i*2+1] = c->log_extra_fields[i];
+ }
+
+ p = strjoina("/run/systemd/units/log-extra-fields:", u->id);
+ pattern = strjoina(p, ".XXXXXX");
+
+ fd = mkostemp_safe(pattern);
+ if (fd < 0)
+ return log_unit_debug_errno(u, fd, "Failed to create extra fields file %s: %m", p);
+
+ n = writev(fd, iovec, c->n_log_extra_fields*2);
+ if (n < 0) {
+ r = log_unit_debug_errno(u, errno, "Failed to write extra fields: %m");
+ goto fail;
+ }
+
+ (void) fchmod(fd, 0644);
+
+ if (rename(pattern, p) < 0) {
+ r = log_unit_debug_errno(u, errno, "Failed to rename extra fields file: %m");
+ goto fail;
+ }
+
+ u->exported_log_extra_fields = true;
+ return 0;
+
+fail:
+ (void) unlink(pattern);
+ return r;
+}
+
+void unit_export_state_files(Unit *u) {
+ const ExecContext *c;
+
+ assert(u);
+
+ if (!u->id)
+ return;
+
+ if (!MANAGER_IS_SYSTEM(u->manager))
+ return;
+
+ /* Exports a couple of unit properties to /run/systemd/units/, so that journald can quickly query this data
+ * from there. Ideally, journald would use IPC to query this, like everybody else, but that's hard, as long as
+ * the IPC system itself and PID 1 also log to the journal.
+ *
+ * Note that these files really shouldn't be considered API for anyone else, as use a runtime file system as
+ * IPC replacement is not compatible with today's world of file system namespaces. However, this doesn't really
+ * apply to communication between the journal and systemd, as we assume that these two daemons live in the same
+ * namespace at least.
+ *
+ * Note that some of the "files" exported here are actually symlinks and not regular files. Symlinks work
+ * better for storing small bits of data, in particular as we can write them with two system calls, and read
+ * them with one. */
+
+ (void) unit_export_invocation_id(u);
+
+ c = unit_get_exec_context(u);
+ if (c) {
+ (void) unit_export_log_level_max(u, c);
+ (void) unit_export_log_extra_fields(u, c);
+ }
+}
+
+void unit_unlink_state_files(Unit *u) {
+ const char *p;
+
+ assert(u);
+
+ if (!u->id)
+ return;
+
+ if (!MANAGER_IS_SYSTEM(u->manager))
+ return;
+
+ /* Undoes the effect of unit_export_state() */
+
+ if (u->exported_invocation_id) {
+ p = strjoina("/run/systemd/units/invocation:", u->id);
+ (void) unlink(p);
+
+ u->exported_invocation_id = false;
+ }
+
+ if (u->exported_log_level_max) {
+ p = strjoina("/run/systemd/units/log-level-max:", u->id);
+ (void) unlink(p);
+
+ u->exported_log_level_max = false;
+ }
+
+ if (u->exported_log_extra_fields) {
+ p = strjoina("/run/systemd/units/extra-fields:", u->id);
+ (void) unlink(p);
+
+ u->exported_log_extra_fields = false;
+ }
+}
diff --git a/src/core/unit.h b/src/core/unit.h
index 5e5e791bc4..adab6eb80f 100644
--- a/src/core/unit.h
+++ b/src/core/unit.h
@@ -338,6 +338,11 @@ struct Unit {
/* For transient units: whether to add a bus track reference after creating the unit */
bool bus_track_add:1;
+
+ /* Remember which unit state files we created */
+ bool exported_invocation_id:1;
+ bool exported_log_level_max:1;
+ bool exported_log_extra_fields:1;
};
struct UnitStatusMessageFormats {
@@ -742,6 +747,9 @@ int unit_fork_helper_process(Unit *u, pid_t *ret);
void unit_remove_dependencies(Unit *u, UnitDependencyMask mask);
+void unit_export_state_files(Unit *u);
+void unit_unlink_state_files(Unit *u);
+
/* Macros which append UNIT= or USER_UNIT= to the message */
#define log_unit_full(unit, level, error, ...) \
diff --git a/src/journal/journald-audit.c b/src/journal/journald-audit.c
index 3334418f33..2db923fb49 100644
--- a/src/journal/journald-audit.c
+++ b/src/journal/journald-audit.c
@@ -29,10 +29,10 @@
typedef struct MapField {
const char *audit_field;
const char *journal_field;
- int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov);
+ int (*map)(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov);
} MapField;
-static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+static int map_simple_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
_cleanup_free_ char *c = NULL;
size_t l = 0, allocated = 0;
const char *e;
@@ -61,9 +61,7 @@ static int map_simple_field(const char *field, const char **p, struct iovec **io
if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
return -ENOMEM;
- (*iov)[*n_iov].iov_base = c;
- (*iov)[*n_iov].iov_len = l;
- (*n_iov)++;
+ (*iov)[(*n_iov)++] = IOVEC_MAKE(c, l);
*p = e;
c = NULL;
@@ -71,7 +69,7 @@ static int map_simple_field(const char *field, const char **p, struct iovec **io
return 1;
}
-static int map_string_field_internal(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov, bool filter_printable) {
+static int map_string_field_internal(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov, bool filter_printable) {
_cleanup_free_ char *c = NULL;
const char *s, *e;
size_t l;
@@ -140,9 +138,7 @@ static int map_string_field_internal(const char *field, const char **p, struct i
if (!GREEDY_REALLOC(*iov, *n_iov_allocated, *n_iov + 1))
return -ENOMEM;
- (*iov)[*n_iov].iov_base = c;
- (*iov)[*n_iov].iov_len = l;
- (*n_iov)++;
+ (*iov)[(*n_iov)++] = IOVEC_MAKE(c, l);
*p = e;
c = NULL;
@@ -150,15 +146,15 @@ static int map_string_field_internal(const char *field, const char **p, struct i
return 1;
}
-static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+static int map_string_field(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, false);
}
-static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+static int map_string_field_printable(const char *field, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
return map_string_field_internal(field, p, iov, n_iov_allocated, n_iov, true);
}
-static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, unsigned *n_iov) {
+static int map_generic_field(const char *prefix, const char **p, struct iovec **iov, size_t *n_iov_allocated, size_t *n_iov) {
const char *e, *f;
char *c, *t;
int r;
@@ -218,29 +214,29 @@ static const MapField map_fields_kernel[] = {
/* First, we map certain well-known audit fields into native
* well-known fields */
- { "pid=", "_PID=", map_simple_field },
- { "ppid=", "_PPID=", map_simple_field },
- { "uid=", "_UID=", map_simple_field },
- { "euid=", "_EUID=", map_simple_field },
- { "fsuid=", "_FSUID=", map_simple_field },
- { "gid=", "_GID=", map_simple_field },
- { "egid=", "_EGID=", map_simple_field },
- { "fsgid=", "_FSGID=", map_simple_field },
- { "tty=", "_TTY=", map_simple_field },
- { "ses=", "_AUDIT_SESSION=", map_simple_field },
- { "auid=", "_AUDIT_LOGINUID=", map_simple_field },
- { "subj=", "_SELINUX_CONTEXT=", map_simple_field },
- { "comm=", "_COMM=", map_string_field },
- { "exe=", "_EXE=", map_string_field },
- { "proctitle=", "_CMDLINE=", map_string_field_printable },
+ { "pid=", "_PID=", map_simple_field },
+ { "ppid=", "_PPID=", map_simple_field },
+ { "uid=", "_UID=", map_simple_field },
+ { "euid=", "_EUID=", map_simple_field },
+ { "fsuid=", "_FSUID=", map_simple_field },
+ { "gid=", "_GID=", map_simple_field },
+ { "egid=", "_EGID=", map_simple_field },
+ { "fsgid=", "_FSGID=", map_simple_field },
+ { "tty=", "_TTY=", map_simple_field },
+ { "ses=", "_AUDIT_SESSION=", map_simple_field },
+ { "auid=", "_AUDIT_LOGINUID=", map_simple_field },
+ { "subj=", "_SELINUX_CONTEXT=", map_simple_field },
+ { "comm=", "_COMM=", map_string_field },
+ { "exe=", "_EXE=", map_string_field },
+ { "proctitle=", "_CMDLINE=", map_string_field_printable },
/* Some fields don't map to native well-known fields. However,
* we know that they are string fields, hence let's undo
* string field escaping for them, though we stick to the
* generic field names. */
- { "path=", "_AUDIT_FIELD_PATH=", map_string_field },
- { "dev=", "_AUDIT_FIELD_DEV=", map_string_field },
- { "name=", "_AUDIT_FIELD_NAME=", map_string_field },
+ { "path=", "_AUDIT_FIELD_PATH=", map_string_field },
+ { "dev=", "_AUDIT_FIELD_DEV=", map_string_field },
+ { "name=", "_AUDIT_FIELD_NAME=", map_string_field },
{}
};
@@ -248,11 +244,11 @@ static const MapField map_fields_kernel[] = {
* msg='. All of these fields are untrusted, hence carry no "_"
* prefix. We map the fields we don't know to AUDIT_FIELD_XYZ= */
static const MapField map_fields_userspace[] = {
- { "cwd=", "AUDIT_FIELD_CWD=", map_string_field },
- { "cmd=", "AUDIT_FIELD_CMD=", map_string_field },
- { "acct=", "AUDIT_FIELD_ACCT=", map_string_field },
- { "exe=", "AUDIT_FIELD_EXE=", map_string_field },
- { "comm=", "AUDIT_FIELD_COMM=", map_string_field },
+ { "cwd=", "AUDIT_FIELD_CWD=", map_string_field },
+ { "cmd=", "AUDIT_FIELD_CMD=", map_string_field },
+ { "acct=", "AUDIT_FIELD_ACCT=", map_string_field },
+ { "exe=", "AUDIT_FIELD_EXE=", map_string_field },
+ { "comm=", "AUDIT_FIELD_COMM=", map_string_field },
{}
};
@@ -263,7 +259,7 @@ static int map_all_fields(
bool handle_msg,
struct iovec **iov,
size_t *n_iov_allocated,
- unsigned *n_iov) {
+ size_t *n_iov) {
int r;
@@ -335,16 +331,15 @@ static int map_all_fields(
}
static void process_audit_string(Server *s, int type, const char *data, size_t size) {
+ size_t n_iov_allocated = 0, n_iov = 0, z;
_cleanup_free_ struct iovec *iov = NULL;
- size_t n_iov_allocated = 0;
- unsigned n_iov = 0, k;
uint64_t seconds, msec, id;
const char *p, *type_name;
- unsigned z;
char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)],
type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)],
source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)];
char *m;
+ int k;
assert(s);
diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c
index eaa7f2544f..3a5a97b496 100644
--- a/src/journal/journald-context.c
+++ b/src/journal/journald-context.c
@@ -24,9 +24,16 @@
#include "alloc-util.h"
#include "audit-util.h"
#include "cgroup-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "io-util.h"
+#include "journal-util.h"
#include "journald-context.h"
#include "process-util.h"
#include "string-util.h"
+#include "syslog-util.h"
+#include "unaligned.h"
#include "user-util.h"
/* This implements a metadata cache for clients, which are identified by their PID. Requesting metadata through /proc
@@ -115,6 +122,8 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
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;
r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c);
if (r < 0) {
@@ -154,6 +163,13 @@ static void client_context_reset(ClientContext *c) {
c->label = mfree(c->label);
c->label_size = 0;
+
+ c->extra_fields_iovec = mfree(c->extra_fields_iovec);
+ c->extra_fields_n_iovec = 0;
+ c->extra_fields_data = mfree(c->extra_fields_data);
+ c->extra_fields_mtime = NSEC_INFINITY;
+
+ c->log_level_max = -1;
}
static ClientContext* client_context_free(Server *s, ClientContext *c) {
@@ -296,40 +312,141 @@ static int client_context_read_invocation_id(
Server *s,
ClientContext *c) {
- _cleanup_free_ char *escaped = NULL, *slice_path = NULL;
- char ids[SD_ID128_STRING_MAX];
+ _cleanup_free_ char *value = NULL;
const char *p;
int r;
assert(s);
assert(c);
- /* Read the invocation ID of a unit off a unit. It's stored in the "trusted.invocation_id" extended attribute
- * on the cgroup path. */
+ /* Read the invocation ID of a unit off a unit. PID 1 stores it in a per-unit symlink in /run/systemd/units/ */
- if (!c->unit || !c->slice)
+ if (!c->unit)
return 0;
- r = cg_slice_to_path(c->slice, &slice_path);
+ p = strjoina("/run/systemd/units/invocation:", c->unit);
+ r = readlink_malloc(p, &value);
if (r < 0)
return r;
- escaped = cg_escape(c->unit);
- if (!escaped)
- return -ENOMEM;
+ return sd_id128_from_string(value, &c->invocation_id);
+}
- p = strjoina(s->cgroup_root, "/", slice_path, "/", escaped);
- if (!p)
- return -ENOMEM;
+static int client_context_read_log_level_max(
+ Server *s,
+ ClientContext *c) {
- r = cg_get_xattr(SYSTEMD_CGROUP_CONTROLLER, p, "trusted.invocation_id", ids, 32);
+ _cleanup_free_ char *value = NULL;
+ const char *p;
+ int r, ll;
+
+ if (!c->unit)
+ return 0;
+
+ p = strjoina("/run/systemd/units/log-level-max:", c->unit);
+ r = readlink_malloc(p, &value);
if (r < 0)
return r;
- if (r != 32)
+
+ ll = log_level_from_string(value);
+ if (ll < 0)
return -EINVAL;
- ids[32] = 0;
- return sd_id128_from_string(ids, &c->invocation_id);
+ c->log_level_max = ll;
+ return 0;
+}
+
+static int client_context_read_extra_fields(
+ Server *s,
+ ClientContext *c) {
+
+ size_t size = 0, n_iovec = 0, n_allocated = 0, left;
+ _cleanup_free_ struct iovec *iovec = NULL;
+ _cleanup_free_ void *data = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ struct stat st;
+ const char *p;
+ uint8_t *q;
+ int r;
+
+ if (!c->unit)
+ return 0;
+
+ p = strjoina("/run/systemd/units/log-extra-fields:", c->unit);
+
+ if (c->extra_fields_mtime != NSEC_INFINITY) {
+ if (stat(p, &st) < 0) {
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ if (timespec_load_nsec(&st.st_mtim) == c->extra_fields_mtime)
+ return 0;
+ }
+
+ f = fopen(p, "re");
+ if (!f) {
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ if (fstat(fileno(f), &st) < 0) /* The file might have been replaced since the stat() above, let's get a new
+ * one, that matches the stuff we are reading */
+ return -errno;
+
+ r = read_full_stream(f, (char**) &data, &size);
+ if (r < 0)
+ return r;
+
+ q = data, left = size;
+ while (left > 0) {
+ uint8_t *field, *eq;
+ uint64_t v, n;
+
+ if (left < sizeof(uint64_t))
+ return -EBADMSG;
+
+ v = unaligned_read_le64(q);
+ if (v < 2)
+ return -EBADMSG;
+
+ n = sizeof(uint64_t) + v;
+ if (left < n)
+ return -EBADMSG;
+
+ field = q + sizeof(uint64_t);
+
+ eq = memchr(field, '=', v);
+ if (!eq)
+ return -EBADMSG;
+
+ if (!journal_field_valid((const char *) field, eq - field, false))
+ return -EBADMSG;
+
+ if (!GREEDY_REALLOC(iovec, n_allocated, n_iovec+1))
+ return -ENOMEM;
+
+ iovec[n_iovec++] = IOVEC_MAKE(field, v);
+
+ left -= n, q += n;
+ }
+
+ free(c->extra_fields_iovec);
+ free(c->extra_fields_data);
+
+ c->extra_fields_iovec = iovec;
+ c->extra_fields_n_iovec = n_iovec;
+ c->extra_fields_data = data;
+ c->extra_fields_mtime = timespec_load_nsec(&st.st_mtim);
+
+ iovec = NULL;
+ data = NULL;
+
+ return 0;
}
static void client_context_really_refresh(
@@ -356,6 +473,8 @@ static void client_context_really_refresh(
(void) client_context_read_cgroup(s, c, unit_id);
(void) client_context_read_invocation_id(s, c);
+ (void) client_context_read_log_level_max(s, c);
+ (void) client_context_read_extra_fields(s, c);
c->timestamp = timestamp;
diff --git a/src/journal/journald-context.h b/src/journal/journald-context.h
index eb1e21926a..d2a3772f08 100644
--- a/src/journal/journald-context.h
+++ b/src/journal/journald-context.h
@@ -60,6 +60,13 @@ struct ClientContext {
char *label;
size_t label_size;
+
+ int log_level_max;
+
+ struct iovec *extra_fields_iovec;
+ size_t extra_fields_n_iovec;
+ void *extra_fields_data;
+ nsec_t extra_fields_mtime;
};
int client_context_get(
@@ -90,3 +97,17 @@ void client_context_maybe_refresh(
void client_context_acquire_default(Server *s);
void client_context_flush_all(Server *s);
+
+static inline size_t client_context_extra_fields_n_iovec(const ClientContext *c) {
+ return c ? c->extra_fields_n_iovec : 0;
+}
+
+static inline bool client_context_test_priority(const ClientContext *c, int priority) {
+ if (!c)
+ return true;
+
+ if (c->log_level_max < 0)
+ return true;
+
+ return LOG_PRI(priority) <= c->log_level_max;
+}
diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c
index 36f16a1ec8..b4d3121a0d 100644
--- a/src/journal/journald-kmsg.c
+++ b/src/journal/journald-kmsg.c
@@ -109,15 +109,16 @@ static bool is_us(const char *pid) {
}
static void dev_kmsg_record(Server *s, const char *p, size_t l) {
- struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_KERNEL_FIELDS + 2 + N_IOVEC_UDEV_FIELDS];
+
_cleanup_free_ char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL, *identifier = NULL, *pid = NULL;
- int priority, r;
- unsigned n = 0, z = 0, j;
+ struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_KERNEL_FIELDS + 2 + N_IOVEC_UDEV_FIELDS];
+ char *kernel_device = NULL;
unsigned long long usec;
+ size_t n = 0, z = 0, j;
+ int priority, r;
char *e, *f, *k;
uint64_t serial;
size_t pl;
- char *kernel_device = NULL;
assert(s);
assert(p);
@@ -155,7 +156,7 @@ static void dev_kmsg_record(Server *s, const char *p, size_t l) {
/* Did we lose any? */
if (serial > *s->kernel_seqnum)
- server_driver_message(s,
+ server_driver_message(s, 0,
"MESSAGE_ID=" SD_MESSAGE_JOURNAL_MISSED_STR,
LOG_MESSAGE("Missed %"PRIu64" kernel messages",
serial - *s->kernel_seqnum),
diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c
index 1ff4e29849..5452b88ae2 100644
--- a/src/journal/journald-native.c
+++ b/src/journal/journald-native.c
@@ -28,6 +28,7 @@
#include "fs-util.h"
#include "io-util.h"
#include "journal-importer.h"
+#include "journal-util.h"
#include "journald-console.h"
#include "journald-kmsg.h"
#include "journald-native.h"
@@ -43,41 +44,6 @@
#include "string-util.h"
#include "unaligned.h"
-bool valid_user_field(const char *p, size_t l, bool allow_protected) {
- const char *a;
-
- /* We kinda enforce POSIX syntax recommendations for
- environment variables here, but make a couple of additional
- requirements.
-
- http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */
-
- /* No empty field names */
- if (l <= 0)
- return false;
-
- /* Don't allow names longer than 64 chars */
- if (l > 64)
- return false;
-
- /* Variables starting with an underscore are protected */
- if (!allow_protected && p[0] == '_')
- return false;
-
- /* Don't allow digits as first character */
- if (p[0] >= '0' && p[0] <= '9')
- return false;
-
- /* Only allow A-Z0-9 and '_' */
- for (a = p; a < p + l; a++)
- if ((*a < 'A' || *a > 'Z') &&
- (*a < '0' || *a > '9') &&
- *a != '_')
- return false;
-
- return true;
-}
-
static bool allow_object_pid(const struct ucred *ucred) {
return ucred && ucred->uid == 0;
}
@@ -148,19 +114,17 @@ static int server_process_entry(
const struct timeval *tv,
const char *label, size_t label_len) {
- /* Process a single entry from a native message.
- * Returns 0 if nothing special happened and the message processing should continue,
- * and a negative or positive value otherwise.
+ /* Process a single entry from a native message. Returns 0 if nothing special happened and the message
+ * processing should continue, and a negative or positive value otherwise.
*
* Note that *remaining is altered on both success and failure. */
- struct iovec *iovec = NULL;
- unsigned n = 0, j, tn = (unsigned) -1;
- const char *p;
- size_t m = 0, entry_size = 0;
- int priority = LOG_INFO;
+ size_t n = 0, j, tn = (size_t) -1, m = 0, entry_size = 0;
char *identifier = NULL, *message = NULL;
+ struct iovec *iovec = NULL;
+ int priority = LOG_INFO;
pid_t object_pid = 0;
+ const char *p;
int r = 0;
p = buffer;
@@ -194,26 +158,25 @@ static int server_process_entry(
/* A property follows */
/* n existing properties, 1 new, +1 for _TRANSPORT */
- if (!GREEDY_REALLOC(iovec, m, n + 2 + N_IOVEC_META_FIELDS + N_IOVEC_OBJECT_FIELDS)) {
+ if (!GREEDY_REALLOC(iovec, m,
+ n + 2 +
+ N_IOVEC_META_FIELDS + N_IOVEC_OBJECT_FIELDS +
+ client_context_extra_fields_n_iovec(context))) {
r = log_oom();
break;
}
q = memchr(p, '=', e - p);
if (q) {
- if (valid_user_field(p, q - p, false)) {
+ if (journal_field_valid(p, q - p, false)) {
size_t l;
l = e - p;
- /* If the field name starts with an
- * underscore, skip the variable,
- * since that indicates a trusted
- * field */
- iovec[n].iov_base = (char*) p;
- iovec[n].iov_len = l;
+ /* If the field name starts with an underscore, skip the variable, since that indicates
+ * a trusted field */
+ iovec[n++] = IOVEC_MAKE((char*) p, l);
entry_size += l;
- n++;
server_process_entry_meta(p, l, ucred,
&priority,
@@ -257,7 +220,7 @@ static int server_process_entry(
k[e - p] = '=';
memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l);
- if (valid_user_field(p, e - p, false)) {
+ if (journal_field_valid(p, e - p, false)) {
iovec[n].iov_base = k;
iovec[n].iov_len = (e - p) + 1 + l;
entry_size += iovec[n].iov_len;
@@ -281,13 +244,17 @@ static int server_process_entry(
goto finish;
}
+ if (!client_context_test_priority(context, priority)) {
+ r = 0;
+ goto finish;
+ }
+
tn = n++;
iovec[tn] = IOVEC_MAKE_STRING("_TRANSPORT=journal");
entry_size += strlen("_TRANSPORT=journal");
if (entry_size + n + 1 > ENTRY_SIZE_MAX) { /* data + separators + trailer */
- log_debug("Entry is too big with %u properties and %zu bytes, ignoring.",
- n, entry_size);
+ log_debug("Entry is too big with %zu properties and %zu bytes, ignoring.", n, entry_size);
goto finish;
}
diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index e719fae007..033f0b1a8b 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -219,7 +219,8 @@ void server_space_usage_message(Server *s, JournalStorage *storage) {
format_bytes(fb5, sizeof(fb5), storage->space.limit);
format_bytes(fb6, sizeof(fb6), storage->space.available);
- server_driver_message(s, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_USAGE_STR,
+ server_driver_message(s, 0,
+ "MESSAGE_ID=" SD_MESSAGE_JOURNAL_USAGE_STR,
LOG_MESSAGE("%s (%s) is %s, max %s, %s free.",
storage->name, storage->path, fb1, fb5, fb6),
"JOURNAL_NAME=%s", storage->name,
@@ -752,7 +753,7 @@ static void write_to_journal(Server *s, uid_t uid, struct iovec *iovec, unsigned
static void dispatch_message_real(
Server *s,
- struct iovec *iovec, unsigned n, unsigned m,
+ struct iovec *iovec, size_t n, size_t m,
const ClientContext *c,
const struct timeval *tv,
int priority,
@@ -765,7 +766,10 @@ static void dispatch_message_real(
assert(s);
assert(iovec);
assert(n > 0);
- assert(n + N_IOVEC_META_FIELDS + (pid_is_valid(object_pid) ? N_IOVEC_OBJECT_FIELDS : 0) <= m);
+ assert(n +
+ N_IOVEC_META_FIELDS +
+ (pid_is_valid(object_pid) ? N_IOVEC_OBJECT_FIELDS : 0) +
+ client_context_extra_fields_n_iovec(c) <= m);
if (c) {
IOVEC_ADD_NUMERIC_FIELD(iovec, n, c->pid, pid_t, pid_is_valid, PID_FMT, "_PID");
@@ -791,6 +795,11 @@ static void dispatch_message_real(
IOVEC_ADD_STRING_FIELD(iovec, n, c->user_slice, "_SYSTEMD_USER_SLICE");
IOVEC_ADD_ID128_FIELD(iovec, n, c->invocation_id, "_SYSTEMD_INVOCATION_ID");
+
+ if (c->extra_fields_n_iovec > 0) {
+ memcpy(iovec + n, c->extra_fields_iovec, c->extra_fields_n_iovec * sizeof(struct iovec));
+ n += c->extra_fields_n_iovec;
+ }
}
assert(n <= m);
@@ -859,16 +868,19 @@ static void dispatch_message_real(
write_to_journal(s, journal_uid, iovec, n, priority);
}
-void server_driver_message(Server *s, const char *message_id, const char *format, ...) {
+void server_driver_message(Server *s, pid_t object_pid, const char *message_id, const char *format, ...) {
- struct iovec iovec[N_IOVEC_META_FIELDS + 5 + N_IOVEC_PAYLOAD_FIELDS];
- unsigned n = 0, m;
+ struct iovec *iovec;
+ size_t n = 0, k, m;
va_list ap;
int r;
assert(s);
assert(format);
+ m = N_IOVEC_META_FIELDS + 5 + N_IOVEC_PAYLOAD_FIELDS + client_context_extra_fields_n_iovec(s->my_context);
+ iovec = newa(struct iovec, m);
+
assert_cc(3 == LOG_FAC(LOG_DAEMON));
iovec[n++] = IOVEC_MAKE_STRING("SYSLOG_FACILITY=3");
iovec[n++] = IOVEC_MAKE_STRING("SYSLOG_IDENTIFIER=systemd-journald");
@@ -879,18 +891,18 @@ void server_driver_message(Server *s, const char *message_id, const char *format
if (message_id)
iovec[n++] = IOVEC_MAKE_STRING(message_id);
- m = n;
+ k = n;
va_start(ap, format);
- r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, false, 0, format, ap);
+ r = log_format_iovec(iovec, m, &n, false, 0, format, ap);
/* Error handling below */
va_end(ap);
if (r >= 0)
- dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), s->my_context, NULL, LOG_INFO, 0);
+ dispatch_message_real(s, iovec, n, m, s->my_context, NULL, LOG_INFO, object_pid);
- while (m < n)
- free(iovec[m++].iov_base);
+ while (k < n)
+ free(iovec[k++].iov_base);
if (r < 0) {
/* We failed to format the message. Emit a warning instead. */
@@ -901,13 +913,13 @@ void server_driver_message(Server *s, const char *message_id, const char *format
n = 3;
iovec[n++] = IOVEC_MAKE_STRING("PRIORITY=4");
iovec[n++] = IOVEC_MAKE_STRING(buf);
- dispatch_message_real(s, iovec, n, ELEMENTSOF(iovec), s->my_context, NULL, LOG_INFO, 0);
+ dispatch_message_real(s, iovec, n, m, s->my_context, NULL, LOG_INFO, object_pid);
}
}
void server_dispatch_message(
Server *s,
- struct iovec *iovec, unsigned n, unsigned m,
+ struct iovec *iovec, size_t n, size_t m,
ClientContext *c,
const struct timeval *tv,
int priority,
@@ -939,8 +951,10 @@ void server_dispatch_message(
/* Write a suppression message if we suppressed something */
if (rl > 1)
- server_driver_message(s, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_DROPPED_STR,
- LOG_MESSAGE("Suppressed %u messages from %s", rl - 1, c->unit),
+ server_driver_message(s, c->pid,
+ "MESSAGE_ID=" SD_MESSAGE_JOURNAL_DROPPED_STR,
+ LOG_MESSAGE("Suppressed %i messages from %s", rl - 1, c->unit),
+ LOG_MESSAGE("N_DROPPED=%i", rl - 1),
NULL);
}
@@ -1038,7 +1052,7 @@ finish:
sd_journal_close(j);
- server_driver_message(s, NULL,
+ server_driver_message(s, 0, NULL,
LOG_MESSAGE("Time spent on flushing to /var is %s for %u entries.",
format_timespan(ts, sizeof(ts), now(CLOCK_MONOTONIC) - start, 0),
n),
diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h
index f4bdd588cb..f0076793e1 100644
--- a/src/journal/journald-server.h
+++ b/src/journal/journald-server.h
@@ -187,8 +187,8 @@ struct Server {
#define N_IOVEC_OBJECT_FIELDS 14
#define N_IOVEC_PAYLOAD_FIELDS 15
-void server_dispatch_message(Server *s, struct iovec *iovec, unsigned n, unsigned m, ClientContext *c, const struct timeval *tv, int priority, pid_t object_pid);
-void server_driver_message(Server *s, const char *message_id, const char *format, ...) _printf_(3,0) _sentinel_;
+void server_dispatch_message(Server *s, struct iovec *iovec, size_t n, size_t m, ClientContext *c, const struct timeval *tv, int priority, pid_t object_pid);
+void server_driver_message(Server *s, pid_t object_pid, const char *message_id, const char *format, ...) _sentinel_;
/* gperf lookup function */
const struct ConfigPerfItem* journald_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c
index 54dd096e45..95272db863 100644
--- a/src/journal/journald-stream.c
+++ b/src/journal/journald-stream.c
@@ -251,22 +251,33 @@ fail:
}
static int stdout_stream_log(StdoutStream *s, const char *p, LineBreak line_break) {
- struct iovec iovec[N_IOVEC_META_FIELDS + 7];
+ struct iovec *iovec;
int priority;
char syslog_priority[] = "PRIORITY=\0";
char syslog_facility[sizeof("SYSLOG_FACILITY=")-1 + DECIMAL_STR_MAX(int) + 1];
_cleanup_free_ char *message = NULL, *syslog_identifier = NULL;
- unsigned n = 0;
+ size_t n = 0, m;
int r;
assert(s);
assert(p);
+ if (s->context)
+ (void) client_context_maybe_refresh(s->server, s->context, NULL, NULL, 0, NULL, USEC_INFINITY);
+ else if (pid_is_valid(s->ucred.pid)) {
+ r = client_context_acquire(s->server, s->ucred.pid, &s->ucred, s->label, strlen_ptr(s->label), s->unit_id, &s->context);
+ if (r < 0)
+ log_warning_errno(r, "Failed to acquire client context, ignoring: %m");
+ }
+
priority = s->priority;
if (s->level_prefix)
syslog_parse_priority(&p, &priority, false);
+ if (!client_context_test_priority(s->context, priority))
+ return 0;
+
if (isempty(p))
return 0;
@@ -282,6 +293,9 @@ static int stdout_stream_log(StdoutStream *s, const char *p, LineBreak line_brea
if (s->server->forward_to_wall)
server_forward_wall(s->server, priority, s->identifier, p, &s->ucred);
+ m = N_IOVEC_META_FIELDS + 7 + client_context_extra_fields_n_iovec(s->context);
+ iovec = newa(struct iovec, m);
+
iovec[n++] = IOVEC_MAKE_STRING("_TRANSPORT=stdout");
iovec[n++] = IOVEC_MAKE_STRING(s->id_field);
@@ -315,15 +329,7 @@ static int stdout_stream_log(StdoutStream *s, const char *p, LineBreak line_brea
if (message)
iovec[n++] = IOVEC_MAKE_STRING(message);
- if (s->context)
- (void) client_context_maybe_refresh(s->server, s->context, NULL, NULL, 0, NULL, USEC_INFINITY);
- else if (pid_is_valid(s->ucred.pid)) {
- r = client_context_acquire(s->server, s->ucred.pid, &s->ucred, s->label, strlen_ptr(s->label), s->unit_id, &s->context);
- if (r < 0)
- log_warning_errno(r, "Failed to acquire client context, ignoring: %m");
- }
-
- server_dispatch_message(s->server, iovec, n, ELEMENTSOF(iovec), s->context, NULL, priority, 0);
+ server_dispatch_message(s->server, iovec, n, m, s->context, NULL, priority, 0);
return 0;
}
diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c
index 1fde0271a6..07f9dae67e 100644
--- a/src/journal/journald-syslog.c
+++ b/src/journal/journald-syslog.c
@@ -324,18 +324,27 @@ void server_process_syslog_message(
syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)];
const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL;
_cleanup_free_ char *identifier = NULL, *pid = NULL;
- struct iovec iovec[N_IOVEC_META_FIELDS + 6];
int priority = LOG_USER | LOG_INFO, r;
ClientContext *context = NULL;
+ struct iovec *iovec;
const char *orig;
- unsigned n = 0;
+ size_t n = 0, m;
assert(s);
assert(buf);
+ if (ucred && pid_is_valid(ucred->pid)) {
+ r = client_context_get(s, ucred->pid, ucred, label, label_len, NULL, &context);
+ if (r < 0)
+ log_warning_errno(r, "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m", ucred->pid);
+ }
+
orig = buf;
syslog_parse_priority(&buf, &priority, true);
+ if (!client_context_test_priority(context, priority))
+ return;
+
if (s->forward_to_syslog)
forward_syslog_raw(s, priority, orig, ucred, tv);
@@ -351,6 +360,9 @@ void server_process_syslog_message(
if (s->forward_to_wall)
server_forward_wall(s, priority, identifier, buf, ucred);
+ m = N_IOVEC_META_FIELDS + 6 + client_context_extra_fields_n_iovec(context);
+ iovec = newa(struct iovec, m);
+
iovec[n++] = IOVEC_MAKE_STRING("_TRANSPORT=syslog");
xsprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK);
@@ -375,13 +387,7 @@ void server_process_syslog_message(
if (message)
iovec[n++] = IOVEC_MAKE_STRING(message);
- if (ucred && pid_is_valid(ucred->pid)) {
- r = client_context_get(s, ucred->pid, ucred, label, label_len, NULL, &context);
- if (r < 0)
- log_warning_errno(r, "Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m", ucred->pid);
- }
-
- server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), context, tv, priority, 0);
+ server_dispatch_message(s, iovec, n, m, context, tv, priority, 0);
}
int server_open_syslog_socket(Server *s) {
@@ -449,7 +455,7 @@ void server_maybe_warn_forward_syslog_missed(Server *s) {
if (s->last_warn_forward_syslog_missed + WARN_FORWARD_SYSLOG_MISSED_USEC > n)
return;
- server_driver_message(s,
+ server_driver_message(s, 0,
"MESSAGE_ID=" SD_MESSAGE_FORWARD_SYSLOG_MISSED_STR,
LOG_MESSAGE("Forwarding to syslog missed %u messages.",
s->n_forward_syslog_missed),
diff --git a/src/journal/journald.c b/src/journal/journald.c
index 318e067563..1ee477cf1c 100644
--- a/src/journal/journald.c
+++ b/src/journal/journald.c
@@ -56,7 +56,7 @@ int main(int argc, char *argv[]) {
server_flush_dev_kmsg(&server);
log_debug("systemd-journald running as pid "PID_FMT, getpid_cached());
- server_driver_message(&server,
+ server_driver_message(&server, 0,
"MESSAGE_ID=" SD_MESSAGE_JOURNAL_START_STR,
LOG_MESSAGE("Journal started"),
NULL);
@@ -115,7 +115,7 @@ int main(int argc, char *argv[]) {
}
log_debug("systemd-journald stopped as pid "PID_FMT, getpid_cached());
- server_driver_message(&server,
+ server_driver_message(&server, 0,
"MESSAGE_ID=" SD_MESSAGE_JOURNAL_STOP_STR,
LOG_MESSAGE("Journal stopped"),
NULL);
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 529bc62886..e24c0d4e1c 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -156,6 +156,31 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "sv", n, "t", t);
goto finish;
+ } else if (streq(field, "LogExtraFields")) {
+
+ r = sd_bus_message_append(m, "s", "LogExtraFields");
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_message_open_container(m, 'v', "aay");
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_message_open_container(m, 'a', "ay");
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_message_append_array(m, 'y', eq, strlen(eq));
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ goto finish;
+
+ r = sd_bus_message_close_container(m);
+ goto finish;
+
} else if (STR_IN_SET(field, "MemoryLow", "MemoryHigh", "MemoryMax", "MemoryLimit")) {
uint64_t bytes;
@@ -363,7 +388,7 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "v", "(bs)", ignore, s);
- } else if (streq(field, "SyslogLevel")) {
+ } else if (STR_IN_SET(field, "SyslogLevel", "LogLevelMax")) {
int level;
level = log_level_from_string(eq);
diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c
index 10a26d45aa..9ca9750750 100644
--- a/src/shared/conf-parser.c
+++ b/src/shared/conf-parser.c
@@ -834,7 +834,6 @@ int config_parse_log_facility(
void *data,
void *userdata) {
-
int *o = data, x;
assert(filename);
@@ -865,7 +864,6 @@ int config_parse_log_level(
void *data,
void *userdata) {
-
int *o = data, x;
assert(filename);
@@ -879,7 +877,11 @@ int config_parse_log_level(
return 0;
}
- *o = (*o & LOG_FACMASK) | x;
+ if (*o < 0) /* if it wasn't initialized so far, assume zero facility */
+ *o = x;
+ else
+ *o = (*o & LOG_FACMASK) | x;
+
return 0;
}
diff --git a/src/shared/journal-util.c b/src/shared/journal-util.c
index fff3dfc9d1..82f193ffd8 100644
--- a/src/shared/journal-util.c
+++ b/src/shared/journal-util.c
@@ -149,3 +149,41 @@ int journal_access_check_and_warn(sd_journal *j, bool quiet) {
return r;
}
+
+bool journal_field_valid(const char *p, size_t l, bool allow_protected) {
+ const char *a;
+
+ /* We kinda enforce POSIX syntax recommendations for
+ environment variables here, but make a couple of additional
+ requirements.
+
+ http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */
+
+ if (l == (size_t) -1)
+ l = strlen(p);
+
+ /* No empty field names */
+ if (l <= 0)
+ return false;
+
+ /* Don't allow names longer than 64 chars */
+ if (l > 64)
+ return false;
+
+ /* Variables starting with an underscore are protected */
+ if (!allow_protected && p[0] == '_')
+ return false;
+
+ /* Don't allow digits as first character */
+ if (p[0] >= '0' && p[0] <= '9')
+ return false;
+
+ /* Only allow A-Z0-9 and '_' */
+ for (a = p; a < p + l; a++)
+ if ((*a < 'A' || *a > 'Z') &&
+ (*a < '0' || *a > '9') &&
+ *a != '_')
+ return false;
+
+ return true;
+}
diff --git a/src/shared/journal-util.h b/src/shared/journal-util.h
index 499e6c62ec..95613bba10 100644
--- a/src/shared/journal-util.h
+++ b/src/shared/journal-util.h
@@ -19,7 +19,10 @@
***/
#include
+#include
#include "sd-journal.h"
+bool journal_field_valid(const char *p, size_t l, bool allow_protected);
+
int journal_access_check_and_warn(sd_journal *j, bool quiet);
diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c
index c16a6342f7..da24c131f7 100644
--- a/src/test/test-unit-file.c
+++ b/src/test/test-unit-file.c
@@ -445,6 +445,73 @@ static void test_config_parse_exec(void) {
manager_free(m);
}
+static void test_config_parse_log_extra_fields(void) {
+ /* int config_parse_log_extra_fields(
+ 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) */
+
+ int r;
+
+ Manager *m = NULL;
+ Unit *u = NULL;
+ ExecContext c = {};
+
+ r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_MINIMAL, &m);
+ if (MANAGER_SKIP_TEST(r)) {
+ log_notice_errno(r, "Skipping test: manager_new: %m");
+ return;
+ }
+
+ assert_se(r >= 0);
+ assert_se(manager_startup(m, NULL, NULL) >= 0);
+
+ assert_se(u = unit_new(m, sizeof(Service)));
+
+ log_info("/* %s – basic test */", __func__);
+ r = config_parse_log_extra_fields(NULL, "fake", 1, "section", 1,
+ "LValue", 0, "FOO=BAR \"QOOF=quux ' ' \"",
+ &c, u);
+ assert_se(r >= 0);
+ assert_se(c.n_log_extra_fields == 2);
+ assert_se(strneq(c.log_extra_fields[0].iov_base, "FOO=BAR", c.log_extra_fields[0].iov_len));
+ assert_se(strneq(c.log_extra_fields[1].iov_base, "QOOF=quux ' ' ", c.log_extra_fields[1].iov_len));
+
+ log_info("/* %s – add some */", __func__);
+ r = config_parse_log_extra_fields(NULL, "fake", 1, "section", 1,
+ "LValue", 0, "FOO2=BAR2 QOOF2=quux ' '",
+ &c, u);
+ assert_se(r >= 0);
+ assert_se(c.n_log_extra_fields == 4);
+ assert_se(strneq(c.log_extra_fields[0].iov_base, "FOO=BAR", c.log_extra_fields[0].iov_len));
+ assert_se(strneq(c.log_extra_fields[1].iov_base, "QOOF=quux ' ' ", c.log_extra_fields[1].iov_len));
+ assert_se(strneq(c.log_extra_fields[2].iov_base, "FOO2=BAR2", c.log_extra_fields[2].iov_len));
+ assert_se(strneq(c.log_extra_fields[3].iov_base, "QOOF2=quux", c.log_extra_fields[3].iov_len));
+
+ exec_context_dump(&c, stdout, " --> ");
+
+ log_info("/* %s – reset */", __func__);
+ r = config_parse_log_extra_fields(NULL, "fake", 1, "section", 1,
+ "LValue", 0, "",
+ &c, u);
+ assert_se(r >= 0);
+ assert_se(c.n_log_extra_fields == 0);
+
+ exec_context_free_log_extra_fields(&c);
+
+ unit_free(u);
+ manager_free(m);
+
+ log_info("/* %s – bye */", __func__);
+}
+
#define env_file_1 \
"a=a\n" \
"b=b\\\n" \
@@ -868,6 +935,7 @@ int main(int argc, char *argv[]) {
r = test_unit_file_get_set();
test_config_parse_exec();
+ test_config_parse_log_extra_fields();
test_config_parse_capability_set();
test_config_parse_rlimit();
test_config_parse_pass_environ();