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,
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,
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,
#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"
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),
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),
}
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;
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);
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) {
}
void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) {
+ ExecDirectoryType dt;
char **e, **d;
unsigned i;
- ExecDirectoryType dt;
int r;
assert(c);
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;
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);
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;
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);
$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)
#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"
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,
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);
#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"
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. */
m->reset_accounting = false;
}
+ unit_export_state_files(UNIT(m));
+
r = unit_setup_exec_runtime(UNIT(m));
if (r < 0)
return r;
(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");
s->reset_accounting = false;
}
+ unit_export_state_files(UNIT(s));
+
r = unit_setup_exec_runtime(UNIT(s));
if (r < 0)
return r;
s->reset_accounting = false;
}
+ unit_export_state_files(UNIT(s));
+
r = unit_setup_exec_runtime(UNIT(s));
if (r < 0)
return r;
s->reset_accounting = false;
}
+ unit_export_state_files(UNIT(s));
+
r = unit_setup_exec_runtime(UNIT(s));
if (r < 0)
goto fail;
#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"
#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"
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);
/* 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
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);
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);
} 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;
+ }
+}
/* 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 {
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, ...) \
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;
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;
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;
bool handle_msg,
struct iovec **iov,
size_t *n_iov_allocated,
- unsigned *n_iov) {
+ size_t *n_iov) {
int r;
}
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);
#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
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) {
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) {
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(
(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;
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(
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;
+}
}
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);
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. */
+ size_t n = 0, j, tn = (size_t) -1, m = 0, entry_size = 0;
+ char *identifier = NULL, *message = NULL;
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;
- char *identifier = NULL, *message = NULL;
pid_t object_pid = 0;
+ const char *p;
int r = 0;
p = buffer;
/* 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;
}
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;
}
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,
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");
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);
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");
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, object_pid);
+ 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. */
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, object_pid);
+ 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,
#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_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 */
}
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;
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);
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;
}
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);
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);
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) {
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;
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);
void *data,
void *userdata) {
-
int *o = data, x;
assert(filename);
void *data,
void *userdata) {
-
int *o = data, x;
assert(filename);
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;
}