From 6550c24c7f21ee398a1ca0ab43603b23a3414108 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 3 May 2018 18:45:39 +0200 Subject: [PATCH 01/21] rlimit-util: rework rlimit_{from|to}_string() to work without "Limit" prefix let's make the call more generic, so that we can also easily use it for parsing "RLIMIT_xyz" style constants. --- src/basic/rlimit-util.c | 48 ++++++++++----- src/basic/rlimit-util.h | 1 + src/core/dbus-execute.c | 114 +++++++++++++++++++----------------- src/core/execute.c | 6 +- src/shared/bus-unit-util.c | 37 +++++++----- src/shared/bus-util.c | 18 +++--- src/test/test-rlimit-util.c | 36 +++++++++++- 7 files changed, 161 insertions(+), 99 deletions(-) diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index d371f0a550..154b34b8c1 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -289,22 +289,38 @@ int rlimit_format(const struct rlimit *rl, char **ret) { } static const char* const rlimit_table[_RLIMIT_MAX] = { - [RLIMIT_CPU] = "LimitCPU", - [RLIMIT_FSIZE] = "LimitFSIZE", - [RLIMIT_DATA] = "LimitDATA", - [RLIMIT_STACK] = "LimitSTACK", - [RLIMIT_CORE] = "LimitCORE", - [RLIMIT_RSS] = "LimitRSS", - [RLIMIT_NOFILE] = "LimitNOFILE", - [RLIMIT_AS] = "LimitAS", - [RLIMIT_NPROC] = "LimitNPROC", - [RLIMIT_MEMLOCK] = "LimitMEMLOCK", - [RLIMIT_LOCKS] = "LimitLOCKS", - [RLIMIT_SIGPENDING] = "LimitSIGPENDING", - [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", - [RLIMIT_NICE] = "LimitNICE", - [RLIMIT_RTPRIO] = "LimitRTPRIO", - [RLIMIT_RTTIME] = "LimitRTTIME" + [RLIMIT_AS] = "AS", + [RLIMIT_CORE] = "CORE", + [RLIMIT_CPU] = "CPU", + [RLIMIT_DATA] = "DATA", + [RLIMIT_FSIZE] = "FSIZE", + [RLIMIT_LOCKS] = "LOCKS", + [RLIMIT_MEMLOCK] = "MEMLOCK", + [RLIMIT_MSGQUEUE] = "MSGQUEUE", + [RLIMIT_NICE] = "NICE", + [RLIMIT_NOFILE] = "NOFILE", + [RLIMIT_NPROC] = "NPROC", + [RLIMIT_RSS] = "RSS", + [RLIMIT_RTPRIO] = "RTPRIO", + [RLIMIT_RTTIME] = "RTTIME", + [RLIMIT_SIGPENDING] = "SIGPENDING", + [RLIMIT_STACK] = "STACK", }; DEFINE_STRING_TABLE_LOOKUP(rlimit, int); + +int rlimit_from_string_harder(const char *s) { + const char *suffix; + + /* The official prefix */ + suffix = startswith(s, "RLIMIT_"); + if (suffix) + return rlimit_from_string(suffix); + + /* Our own unit file setting prefix */ + suffix = startswith(s, "Limit"); + if (suffix) + return rlimit_from_string(suffix); + + return rlimit_from_string(s); +} diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h index 3766f78d58..42f2e0e547 100644 --- a/src/basic/rlimit-util.h +++ b/src/basic/rlimit-util.h @@ -13,6 +13,7 @@ const char *rlimit_to_string(int i) _const_; int rlimit_from_string(const char *s) _pure_; +int rlimit_from_string_harder(const char *s) _pure_; int setrlimit_closest(int resource, const struct rlimit *rlim); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 3b1c77e967..143fca71b0 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -1091,8 +1091,8 @@ int bus_exec_context_set_transient_property( UnitWriteFlags flags, sd_bus_error *error) { - const char *soft = NULL; - int r, ri; + const char *suffix; + int r; assert(u); assert(c); @@ -2313,73 +2313,77 @@ int bus_exec_context_set_transient_property( } return 1; - } - ri = rlimit_from_string(name); - if (ri < 0) { - soft = endswith(name, "Soft"); - if (soft) { - const char *n; + } else if ((suffix = startswith(name, "Limit"))) { + const char *soft = NULL; + int ri; - n = strndupa(name, soft - name); - ri = rlimit_from_string(n); - if (ri >= 0) - name = n; + ri = rlimit_from_string(suffix); + if (ri < 0) { + soft = endswith(suffix, "Soft"); + if (soft) { + const char *n; - } - } - - if (ri >= 0) { - uint64_t rl; - rlim_t x; - - r = sd_bus_message_read(message, "t", &rl); - if (r < 0) - return r; - - if (rl == (uint64_t) -1) - x = RLIM_INFINITY; - else { - x = (rlim_t) rl; - - if ((uint64_t) x != rl) - return -ERANGE; + n = strndupa(suffix, soft - suffix); + ri = rlimit_from_string(n); + if (ri >= 0) + name = strjoina("Limit", n); + } } - if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - _cleanup_free_ char *f = NULL; - struct rlimit nl; + if (ri >= 0) { + uint64_t rl; + rlim_t x; - if (c->rlimit[ri]) { - nl = *c->rlimit[ri]; - - if (soft) - nl.rlim_cur = x; - else - nl.rlim_max = x; - } else - /* When the resource limit is not initialized yet, then assign the value to both fields */ - nl = (struct rlimit) { - .rlim_cur = x, - .rlim_max = x, - }; - - r = rlimit_format(&nl, &f); + r = sd_bus_message_read(message, "t", &rl); if (r < 0) return r; - if (c->rlimit[ri]) - *c->rlimit[ri] = nl; + if (rl == (uint64_t) -1) + x = RLIM_INFINITY; else { - c->rlimit[ri] = newdup(struct rlimit, &nl, 1); - if (!c->rlimit[ri]) - return -ENOMEM; + x = (rlim_t) rl; + + if ((uint64_t) x != rl) + return -ERANGE; } - unit_write_settingf(u, flags, name, "%s=%s", name, f); + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + _cleanup_free_ char *f = NULL; + struct rlimit nl; + + if (c->rlimit[ri]) { + nl = *c->rlimit[ri]; + + if (soft) + nl.rlim_cur = x; + else + nl.rlim_max = x; + } else + /* When the resource limit is not initialized yet, then assign the value to both fields */ + nl = (struct rlimit) { + .rlim_cur = x, + .rlim_max = x, + }; + + r = rlimit_format(&nl, &f); + if (r < 0) + return r; + + if (c->rlimit[ri]) + *c->rlimit[ri] = nl; + else { + c->rlimit[ri] = newdup(struct rlimit, &nl, 1); + if (!c->rlimit[ri]) + return -ENOMEM; + } + + unit_write_settingf(u, flags, name, "%s=%s", name, f); + } + + return 1; } - return 1; } return 0; diff --git a/src/core/execute.c b/src/core/execute.c index fca20c5567..d60e8f6a1a 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -3176,7 +3176,7 @@ static int exec_child( r = setrlimit_closest(i, context->rlimit[i]); if (r < 0) { *exit_status = EXIT_LIMITS; - return log_unit_error_errno(unit, r, "Failed to adjust resource limit %s: %m", rlimit_to_string(i)); + return log_unit_error_errno(unit, r, "Failed to adjust resource limit RLIMIT_%s: %m", rlimit_to_string(i)); } } @@ -3975,9 +3975,9 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { for (i = 0; i < RLIM_NLIMITS; i++) if (c->rlimit[i]) { - fprintf(f, "%s%s: " RLIM_FMT "\n", + fprintf(f, "Limit%s%s: " RLIM_FMT "\n", prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max); - fprintf(f, "%s%sSoft: " RLIM_FMT "\n", + fprintf(f, "Limit%s%sSoft: " RLIM_FMT "\n", prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur); } diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 78d5141053..fc035e796e 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -684,7 +684,8 @@ static int bus_append_automount_property(sd_bus_message *m, const char *field, c } static int bus_append_execute_property(sd_bus_message *m, const char *field, const char *eq) { - int r, rl; + const char *suffix; + int r; if (STR_IN_SET(field, "User", "Group", @@ -863,25 +864,29 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_append_byte_array(m, field, decoded, sz); } - rl = rlimit_from_string(field); - if (rl >= 0) { - const char *sn; - struct rlimit l; + if ((suffix = startswith(field, "Limit"))) { + int rl; - r = rlimit_parse(rl, eq, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse resource limit: %s", eq); + rl = rlimit_from_string(suffix); + if (rl >= 0) { + const char *sn; + struct rlimit l; - r = sd_bus_message_append(m, "(sv)", field, "t", l.rlim_max); - if (r < 0) - return bus_log_create_error(r); + r = rlimit_parse(rl, eq, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse resource limit: %s", eq); - sn = strjoina(field, "Soft"); - r = sd_bus_message_append(m, "(sv)", sn, "t", l.rlim_cur); - if (r < 0) - return bus_log_create_error(r); + r = sd_bus_message_append(m, "(sv)", field, "t", l.rlim_max); + if (r < 0) + return bus_log_create_error(r); - return 1; + sn = strjoina(field, "Soft"); + r = sd_bus_message_append(m, "(sv)", sn, "t", l.rlim_cur); + if (r < 0) + return bus_log_create_error(r); + + return 1; + } } if (STR_IN_SET(field, "AppArmorProfile", "SmackProcessLabel")) { diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c index 66724d0e78..c80fc4fcbb 100644 --- a/src/shared/bus-util.c +++ b/src/shared/bus-util.c @@ -1613,36 +1613,40 @@ int bus_property_get_rlimit( void *userdata, sd_bus_error *error) { + const char *is_soft; struct rlimit *rl; uint64_t u; rlim_t x; - const char *is_soft; assert(bus); assert(reply); assert(userdata); is_soft = endswith(property, "Soft"); + rl = *(struct rlimit**) userdata; if (rl) x = is_soft ? rl->rlim_cur : rl->rlim_max; else { struct rlimit buf = {}; + const char *s, *p; int z; - const char *s; + /* Chop off "Soft" suffix */ s = is_soft ? strndupa(property, is_soft - property) : property; - z = rlimit_from_string(strstr(s, "Limit")); + /* Skip over any prefix, such as "Default" */ + assert_se(p = strstr(s, "Limit")); + + z = rlimit_from_string(p + 5); assert(z >= 0); - getrlimit(z, &buf); + (void) getrlimit(z, &buf); x = is_soft ? buf.rlim_cur : buf.rlim_max; } - /* rlim_t might have different sizes, let's map - * RLIMIT_INFINITY to (uint64_t) -1, so that it is the same on - * all archs */ + /* rlim_t might have different sizes, let's map RLIMIT_INFINITY to (uint64_t) -1, so that it is the same on all + * archs */ u = x == RLIM_INFINITY ? (uint64_t) -1 : (uint64_t) x; return sd_bus_message_append(reply, "t", u); diff --git a/src/test/test-rlimit-util.c b/src/test/test-rlimit-util.c index 09c35b4f8b..38de758cb6 100644 --- a/src/test/test-rlimit-util.c +++ b/src/test/test-rlimit-util.c @@ -42,6 +42,7 @@ int main(int argc, char *argv[]) { .rlim_cur = 10, .rlim_max = 5, }; + int i; log_parse_environment(); log_open(); @@ -53,10 +54,41 @@ int main(int argc, char *argv[]) { new.rlim_max = old.rlim_max; assert_se(setrlimit(RLIMIT_NOFILE, &new) >= 0); - assert_se(rlimit_from_string("LimitNOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string("NOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string("LimitNOFILE") == -1); + assert_se(rlimit_from_string("RLIMIT_NOFILE") == -1); + assert_se(rlimit_from_string("xxxNOFILE") == -1); assert_se(rlimit_from_string("DefaultLimitNOFILE") == -1); - assert_se(streq_ptr(rlimit_to_string(RLIMIT_NOFILE), "LimitNOFILE")); + assert_se(rlimit_from_string_harder("NOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string_harder("LimitNOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string_harder("RLIMIT_NOFILE") == RLIMIT_NOFILE); + assert_se(rlimit_from_string_harder("xxxNOFILE") == -1); + assert_se(rlimit_from_string_harder("DefaultLimitNOFILE") == -1); + + for (i = 0; i < _RLIMIT_MAX; i++) { + _cleanup_free_ char *prefixed = NULL; + const char *p; + + assert_se(p = rlimit_to_string(i)); + log_info("%i = %s", i, p); + + assert_se(rlimit_from_string(p) == i); + assert_se(rlimit_from_string_harder(p) == i); + + assert_se(prefixed = strjoin("Limit", p)); + + assert_se(rlimit_from_string(prefixed) < 0); + assert_se(rlimit_from_string_harder(prefixed) == i); + + prefixed = mfree(prefixed); + assert_se(prefixed = strjoin("RLIMIT_", p)); + + assert_se(rlimit_from_string(prefixed) < 0); + assert_se(rlimit_from_string_harder(prefixed) == i); + } + + assert_se(streq_ptr(rlimit_to_string(RLIMIT_NOFILE), "NOFILE")); assert_se(rlimit_to_string(-1) == NULL); assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); From 4f424df760117caa461252b89e9c6680eaee2568 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 3 May 2018 19:01:21 +0200 Subject: [PATCH 02/21] core: move config_parse_limit() to the generic conf-parser.[ch] That way we can use it in nspawn. Also, while we are at it, let's rename the call config_parse_rlimit(), i.e. insert the "r", to clarify what kind of limit this is about. --- src/core/load-fragment-gperf.gperf.m4 | 32 +++++++++---------- src/core/load-fragment.c | 44 +-------------------------- src/core/load-fragment.h | 1 - src/core/main.c | 32 +++++++++---------- src/shared/conf-parser.c | 40 ++++++++++++++++++++++++ src/shared/conf-parser.h | 1 + src/test/test-unit-file.c | 41 +++++++++++++------------ 7 files changed, 95 insertions(+), 96 deletions(-) diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 001837463b..3757a651b2 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -82,22 +82,22 @@ $1.RestrictNamespaces, config_parse_warn_compat, DISABLED_CO $1.RestrictRealtime, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.RestrictAddressFamilies, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.LockPersonality, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -$1.LimitCPU, config_parse_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit) -$1.LimitFSIZE, config_parse_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit) -$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) -$1.LimitSTACK, config_parse_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) -$1.LimitCORE, config_parse_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) -$1.LimitRSS, config_parse_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) -$1.LimitNOFILE, config_parse_limit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit) -$1.LimitAS, config_parse_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit) -$1.LimitNPROC, config_parse_limit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit) -$1.LimitMEMLOCK, config_parse_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) -$1.LimitLOCKS, config_parse_limit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit) -$1.LimitSIGPENDING, config_parse_limit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit) -$1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) -$1.LimitNICE, config_parse_limit, RLIMIT_NICE, offsetof($1, exec_context.rlimit) -$1.LimitRTPRIO, config_parse_limit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit) -$1.LimitRTTIME, config_parse_limit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit) +$1.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof($1, exec_context.rlimit) +$1.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit) +$1.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) +$1.LimitSTACK, config_parse_rlimit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) +$1.LimitCORE, config_parse_rlimit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) +$1.LimitRSS, config_parse_rlimit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) +$1.LimitNOFILE, config_parse_rlimit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit) +$1.LimitAS, config_parse_rlimit, RLIMIT_AS, offsetof($1, exec_context.rlimit) +$1.LimitNPROC, config_parse_rlimit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit) +$1.LimitMEMLOCK, config_parse_rlimit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) +$1.LimitLOCKS, config_parse_rlimit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit) +$1.LimitSIGPENDING, config_parse_rlimit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit) +$1.LimitMSGQUEUE, config_parse_rlimit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) +$1.LimitNICE, config_parse_rlimit, RLIMIT_NICE, offsetof($1, exec_context.rlimit) +$1.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof($1, exec_context.rlimit) +$1.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof($1, exec_context.rlimit) $1.ReadWriteDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_write_paths) $1.ReadOnlyDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.read_only_paths) $1.InaccessibleDirectories, config_parse_namespace_path_strv, 0, offsetof($1, exec_context.inaccessible_paths) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index e17987bf07..85e2b1f53f 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -45,7 +45,6 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" -#include "rlimit-util.h" #if HAVE_SECCOMP #include "seccomp-util.h" #endif @@ -1370,47 +1369,6 @@ int config_parse_capability_set( return 0; } -int config_parse_limit( - 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) { - - struct rlimit **rl = data, d = {}; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = rlimit_parse(ltype, rvalue, &d); - if (r == -EILSEQ) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Soft resource limit chosen higher than hard limit, ignoring: %s", rvalue); - return 0; - } - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } - - if (rl[ltype]) - *rl[ltype] = d; - else { - rl[ltype] = newdup(struct rlimit, &d, 1); - if (!rl[ltype]) - return log_oom(); - } - - return 0; -} - #if HAVE_SYSV_COMPAT int config_parse_sysv_priority(const char *unit, const char *filename, @@ -4914,7 +4872,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_log_level, "LEVEL" }, { config_parse_exec_secure_bits, "SECUREBITS" }, { config_parse_capability_set, "BOUNDINGSET" }, - { config_parse_limit, "LIMIT" }, + { config_parse_rlimit, "LIMIT" }, { config_parse_unit_deps, "UNIT [...]" }, { config_parse_exec, "PATH [ARGUMENT [...]]" }, { config_parse_service_type, "SERVICETYPE" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 6d9b13ce37..4af2898b44 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -43,7 +43,6 @@ int config_parse_exec_cpu_sched_prio(const char *unit, const char *filename, uns int config_parse_exec_cpu_affinity(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_exec_secure_bits(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_capability_set(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_limit(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_sysv_priority(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_kill_signal(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_exec_mount_flags(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); diff --git a/src/core/main.c b/src/core/main.c index 978434038e..30dd4a23e8 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -675,22 +675,22 @@ static int parse_config_file(void) { { "Manager", "DefaultStartLimitIntervalSec",config_parse_sec, 0, &arg_default_start_limit_interval }, { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_default_start_limit_burst }, { "Manager", "DefaultEnvironment", config_parse_environ, 0, &arg_default_environment }, - { "Manager", "DefaultLimitCPU", config_parse_limit, RLIMIT_CPU, arg_default_rlimit }, - { "Manager", "DefaultLimitFSIZE", config_parse_limit, RLIMIT_FSIZE, arg_default_rlimit }, - { "Manager", "DefaultLimitDATA", config_parse_limit, RLIMIT_DATA, arg_default_rlimit }, - { "Manager", "DefaultLimitSTACK", config_parse_limit, RLIMIT_STACK, arg_default_rlimit }, - { "Manager", "DefaultLimitCORE", config_parse_limit, RLIMIT_CORE, arg_default_rlimit }, - { "Manager", "DefaultLimitRSS", config_parse_limit, RLIMIT_RSS, arg_default_rlimit }, - { "Manager", "DefaultLimitNOFILE", config_parse_limit, RLIMIT_NOFILE, arg_default_rlimit }, - { "Manager", "DefaultLimitAS", config_parse_limit, RLIMIT_AS, arg_default_rlimit }, - { "Manager", "DefaultLimitNPROC", config_parse_limit, RLIMIT_NPROC, arg_default_rlimit }, - { "Manager", "DefaultLimitMEMLOCK", config_parse_limit, RLIMIT_MEMLOCK, arg_default_rlimit }, - { "Manager", "DefaultLimitLOCKS", config_parse_limit, RLIMIT_LOCKS, arg_default_rlimit }, - { "Manager", "DefaultLimitSIGPENDING", config_parse_limit, RLIMIT_SIGPENDING, arg_default_rlimit }, - { "Manager", "DefaultLimitMSGQUEUE", config_parse_limit, RLIMIT_MSGQUEUE, arg_default_rlimit }, - { "Manager", "DefaultLimitNICE", config_parse_limit, RLIMIT_NICE, arg_default_rlimit }, - { "Manager", "DefaultLimitRTPRIO", config_parse_limit, RLIMIT_RTPRIO, arg_default_rlimit }, - { "Manager", "DefaultLimitRTTIME", config_parse_limit, RLIMIT_RTTIME, arg_default_rlimit }, + { "Manager", "DefaultLimitCPU", config_parse_rlimit, RLIMIT_CPU, arg_default_rlimit }, + { "Manager", "DefaultLimitFSIZE", config_parse_rlimit, RLIMIT_FSIZE, arg_default_rlimit }, + { "Manager", "DefaultLimitDATA", config_parse_rlimit, RLIMIT_DATA, arg_default_rlimit }, + { "Manager", "DefaultLimitSTACK", config_parse_rlimit, RLIMIT_STACK, arg_default_rlimit }, + { "Manager", "DefaultLimitCORE", config_parse_rlimit, RLIMIT_CORE, arg_default_rlimit }, + { "Manager", "DefaultLimitRSS", config_parse_rlimit, RLIMIT_RSS, arg_default_rlimit }, + { "Manager", "DefaultLimitNOFILE", config_parse_rlimit, RLIMIT_NOFILE, arg_default_rlimit }, + { "Manager", "DefaultLimitAS", config_parse_rlimit, RLIMIT_AS, arg_default_rlimit }, + { "Manager", "DefaultLimitNPROC", config_parse_rlimit, RLIMIT_NPROC, arg_default_rlimit }, + { "Manager", "DefaultLimitMEMLOCK", config_parse_rlimit, RLIMIT_MEMLOCK, arg_default_rlimit }, + { "Manager", "DefaultLimitLOCKS", config_parse_rlimit, RLIMIT_LOCKS, arg_default_rlimit }, + { "Manager", "DefaultLimitSIGPENDING", config_parse_rlimit, RLIMIT_SIGPENDING, arg_default_rlimit }, + { "Manager", "DefaultLimitMSGQUEUE", config_parse_rlimit, RLIMIT_MSGQUEUE, arg_default_rlimit }, + { "Manager", "DefaultLimitNICE", config_parse_rlimit, RLIMIT_NICE, arg_default_rlimit }, + { "Manager", "DefaultLimitRTPRIO", config_parse_rlimit, RLIMIT_RTPRIO, arg_default_rlimit }, + { "Manager", "DefaultLimitRTTIME", config_parse_rlimit, RLIMIT_RTTIME, arg_default_rlimit }, { "Manager", "DefaultCPUAccounting", config_parse_bool, 0, &arg_default_cpu_accounting }, { "Manager", "DefaultIOAccounting", config_parse_bool, 0, &arg_default_io_accounting }, { "Manager", "DefaultIPAccounting", config_parse_bool, 0, &arg_default_ip_accounting }, diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 9b822dd49a..7e5fba4330 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -33,6 +33,7 @@ #include "syslog-util.h" #include "time-util.h" #include "utf8.h" +#include "rlimit-util.h" int config_item_table_lookup( const void *table, @@ -1214,3 +1215,42 @@ int config_parse_mtu( return 0; } + +int config_parse_rlimit( + 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) { + + struct rlimit **rl = data, d = {}; + int r; + + assert(rvalue); + assert(rl); + + r = rlimit_parse(ltype, rvalue, &d); + if (r == -EILSEQ) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Soft resource limit chosen higher than hard limit, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); + return 0; + } + + if (rl[ltype]) + *rl[ltype] = d; + else { + rl[ltype] = newdup(struct rlimit, &d, 1); + if (!rl[ltype]) + return log_oom(); + } + + return 0; +} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index 094b9cbc44..76adc9a88e 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -147,6 +147,7 @@ int config_parse_ifname(GENERIC_PARSER_ARGS); int config_parse_ip_port(GENERIC_PARSER_ARGS); int config_parse_join_controllers(GENERIC_PARSER_ARGS); int config_parse_mtu(GENERIC_PARSER_ARGS); +int config_parse_rlimit(GENERIC_PARSER_ARGS); typedef enum Disabled { DISABLED_CONFIGURATION, diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 45849ef87e..d6dec64fb8 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -16,6 +16,7 @@ #include "alloc-util.h" #include "all-units.h" #include "capability-util.h" +#include "conf-parser.h" #include "fd-util.h" #include "fileio.h" #include "hashmap.h" @@ -748,22 +749,22 @@ static void test_config_parse_capability_set(void) { static void test_config_parse_rlimit(void) { struct rlimit * rl[_RLIMIT_MAX] = {}; - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "55", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "55", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 55); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "55:66", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "55:66", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 55); assert_se(rl[RLIMIT_NOFILE]->rlim_max == 66); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity:infinity", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity:infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); @@ -772,86 +773,86 @@ static void test_config_parse_rlimit(void) { rl[RLIMIT_NOFILE]->rlim_max = 20; /* Invalid values don't change rl */ - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "10:20:30", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "10:20:30", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "wat:wat", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "wat:wat", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "66:wat", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "66:wat", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "200:100", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "200:100", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 10); assert_se(rl[RLIMIT_NOFILE]->rlim_max == 20); rl[RLIMIT_NOFILE] = mfree(rl[RLIMIT_NOFILE]); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "56", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "56", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == 56); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "57s", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "57s", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == 57); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "40s:1m", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "40s:1m", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == 40); assert_se(rl[RLIMIT_CPU]->rlim_max == 60); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "infinity", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "1234ms", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "1234ms", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == 2); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); rl[RLIMIT_CPU] = mfree(rl[RLIMIT_CPU]); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58:60", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58:60", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); assert_se(rl[RLIMIT_RTTIME]->rlim_max == 60); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s:123s", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s:123s", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC); assert_se(rl[RLIMIT_RTTIME]->rlim_max == 123 * USEC_PER_SEC); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity:infinity", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity:infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); - assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "2345ms", rl, NULL) >= 0); + assert_se(config_parse_rlimit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "2345ms", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 2345 * USEC_PER_MSEC); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); From 31ce987c2b2953d3167a2aad146fd0690f63e07c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 3 May 2018 19:05:59 +0200 Subject: [PATCH 03/21] rlimit-util: add a common destructor call for arrays of struct rlimit --- src/basic/rlimit-util.c | 10 ++++++++++ src/basic/rlimit-util.h | 2 ++ src/core/execute.c | 3 +-- src/core/manager.c | 5 ++--- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index 154b34b8c1..7bae9f0ad0 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -324,3 +324,13 @@ int rlimit_from_string_harder(const char *s) { return rlimit_from_string(s); } + +void rlimit_free_all(struct rlimit **rl) { + int i; + + if (!rl) + return; + + for (i = 0; i < _RLIMIT_MAX; i++) + rl[i] = mfree(rl[i]); +} diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h index 42f2e0e547..067e040c61 100644 --- a/src/basic/rlimit-util.h +++ b/src/basic/rlimit-util.h @@ -22,4 +22,6 @@ int rlimit_parse(int resource, const char *val, struct rlimit *ret); int rlimit_format(const struct rlimit *rl, char **ret); +void rlimit_free_all(struct rlimit **rl); + #define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim }) diff --git a/src/core/execute.c b/src/core/execute.c index d60e8f6a1a..3349bbaccb 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -3574,8 +3574,7 @@ void exec_context_done(ExecContext *c) { c->pass_environment = strv_free(c->pass_environment); c->unset_environment = strv_free(c->unset_environment); - for (l = 0; l < ELEMENTSOF(c->rlimit); l++) - c->rlimit[l] = mfree(c->rlimit[l]); + rlimit_free_all(c->rlimit); for (l = 0; l < 3; l++) { c->stdio_fdname[l] = mfree(c->stdio_fdname[l]); diff --git a/src/core/manager.c b/src/core/manager.c index a4cea85136..4a2f68b567 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -64,6 +64,7 @@ #include "path-util.h" #include "process-util.h" #include "ratelimit.h" +#include "rlimit-util.h" #include "rm-rf.h" #include "signal-util.h" #include "socket-util.h" @@ -1174,7 +1175,6 @@ static void manager_clear_jobs_and_units(Manager *m) { Manager* manager_free(Manager *m) { UnitType c; - int i; ExecDirectoryType dt; if (!m) @@ -1242,8 +1242,7 @@ Manager* manager_free(Manager *m) { free(m->switch_root); free(m->switch_root_init); - for (i = 0; i < _RLIMIT_MAX; i++) - m->rlimit[i] = mfree(m->rlimit[i]); + rlimit_free_all(m->rlimit); assert(hashmap_isempty(m->units_requiring_mounts_for)); hashmap_free(m->units_requiring_mounts_for); From 34a5df58da0bdc1a5ff81c563e73216943b1f96f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 3 May 2018 19:13:27 +0200 Subject: [PATCH 04/21] rlimit-util: introduce setrlimit_closest_all() This new call applies all configured resource limits in one. --- src/basic/rlimit-util.c | 26 ++++++++++++++++++++++++++ src/basic/rlimit-util.h | 1 + src/core/execute.c | 17 ++++++----------- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index 7bae9f0ad0..dccc4e60e1 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -42,6 +42,32 @@ int setrlimit_closest(int resource, const struct rlimit *rlim) { return 0; } +int setrlimit_closest_all(const struct rlimit *const *rlim, int *which_failed) { + int i, r; + + assert(rlim); + + /* On failure returns the limit's index that failed in *which_failed, but only if non-NULL */ + + for (i = 0; i < _RLIMIT_MAX; i++) { + if (!rlim[i]) + continue; + + r = setrlimit_closest(i, rlim[i]); + if (r < 0) { + if (which_failed) + *which_failed = i; + + return r; + } + } + + if (which_failed) + *which_failed = -1; + + return 0; +} + static int rlimit_parse_u64(const char *val, rlim_t *ret) { uint64_t u; int r; diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h index 067e040c61..d50fd38c2e 100644 --- a/src/basic/rlimit-util.h +++ b/src/basic/rlimit-util.h @@ -16,6 +16,7 @@ int rlimit_from_string(const char *s) _pure_; int rlimit_from_string_harder(const char *s) _pure_; int setrlimit_closest(int resource, const struct rlimit *rlim); +int setrlimit_closest_all(const struct rlimit * const *rlim, int *which_failed); int rlimit_parse_one(int resource, const char *val, rlim_t *ret); int rlimit_parse(int resource, const char *val, struct rlimit *ret); diff --git a/src/core/execute.c b/src/core/execute.c index 3349bbaccb..9781b7d920 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2727,7 +2727,7 @@ static int exec_child( #endif uid_t uid = UID_INVALID; gid_t gid = GID_INVALID; - int i, r, ngids = 0; + int r, ngids = 0; size_t n_fds; ExecDirectoryType dt; int secure_bits; @@ -3167,17 +3167,12 @@ static int exec_child( if (needs_sandboxing) { uint64_t bset; + int which_failed; - for (i = 0; i < _RLIMIT_MAX; i++) { - - if (!context->rlimit[i]) - continue; - - r = setrlimit_closest(i, context->rlimit[i]); - if (r < 0) { - *exit_status = EXIT_LIMITS; - return log_unit_error_errno(unit, r, "Failed to adjust resource limit RLIMIT_%s: %m", rlimit_to_string(i)); - } + r = setrlimit_closest_all((const struct rlimit* const *) context->rlimit, &which_failed); + if (r < 0) { + *exit_status = EXIT_LIMITS; + return log_unit_error_errno(unit, r, "Failed to adjust resource limit RLIMIT_%s: %m", rlimit_to_string(which_failed)); } /* Set the RTPRIO resource limit to 0, but only if nothing else was explicitly requested. */ From c7fc3c4cfe605cac52481fc1c4c0972b56c81f6b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 17:50:48 +0200 Subject: [PATCH 05/21] man: don't claim systemd-analyze was documented as part of the man-pages project It's our own command, we document it in our own set of man pages. --- man/systemd-nspawn.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 1948b0a802..755246f12d 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -722,8 +722,8 @@ Alter the system call filter applied to containers. Takes a space-separated list of system call names or group names (the latter prefixed with @, as listed by the - syscall-filter command of systemd-analyze1). Passed + syscall-filter command of + systemd-analyze1). Passed system calls will be permitted. The list may optionally be prefixed by ~, in which case all listed system calls are prohibited. If this command line option is used multiple times the configured lists are combined. If both a positive and a negative list (that is one system call list without and one with the From 1752d69a8bd2fed951b0298806440fbd2dbb07f8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 17:51:33 +0200 Subject: [PATCH 06/21] man: document what happens if --kill-signal= is not used in nspawn and --boot neither --- man/systemd-nspawn.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 755246f12d..713782b859 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -737,13 +737,13 @@ - Specify the process signal to send to the - container's PID 1 when nspawn itself receives SIGTERM, in - order to trigger an orderly shutdown of the - container. Defaults to SIGRTMIN+3 if - is used (on systemd-compatible init systems SIGRTMIN+3 - triggers an orderly shutdown). For a list of valid signals, see - signal7. + Specify the process signal to send to the container's PID 1 when nspawn itself receives + SIGTERM, in order to trigger an orderly shutdown of the container. Defaults to + SIGRTMIN+3 if is used (on systemd-compatible init systems + SIGRTMIN+3 triggers an orderly shutdown). If is not used and this + option is not specified the container's processes are terminated abrubtly via SIGKILL. For + a list of valid signals, see signal7. From 37bc14de5bd74a056d55ec14f63dc9ec999e974e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 17:54:59 +0200 Subject: [PATCH 07/21] basic: be more careful when closing fds based on RLIMIT_NOFILE Let's make sure we properly handle cases where RLIMIT_NOFILE is set to infinity, zero or values outside of the "int" range. --- src/basic/fd-util.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 4b3e7ed557..9536a50b74 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -199,15 +199,22 @@ int close_all_fds(const int except[], size_t n_except) { d = opendir("/proc/self/fd"); if (!d) { - int fd; struct rlimit rl; + int fd, max_fd; - /* When /proc isn't available (for example in chroots) - * the fallback is brute forcing through the fd + /* When /proc isn't available (for example in chroots) the fallback is brute forcing through the fd * table */ assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); - for (fd = 3; fd < (int) rl.rlim_max; fd ++) { + + if (rl.rlim_max == 0) + return -EINVAL; + + /* Let's take special care if the resource limit is set to unlimited, or actually larger than the range + * of 'int'. Let's avoid implicit overflows. */ + max_fd = (rl.rlim_max == RLIM_INFINITY || rl.rlim_max > INT_MAX) ? INT_MAX : (int) (rl.rlim_max - 1); + + for (fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) { int q; if (fd_in_set(fd, except, n_except)) From 114c55f2d52808c8c3027d3cf4f7e3453f0a28d6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 17:56:06 +0200 Subject: [PATCH 08/21] rlimit-util: tweak setrlimit_closest() a bit POSIX doesn't declare too clearly how RLIM_INFINITY is set. Let's hence filter it out explicitly early on, just as safety precaution should it be defined weirdly on some arch, for example negative or below the maximum value of the rlim_t type. --- src/basic/rlimit-util.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index dccc4e60e1..57f90474d8 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -33,6 +33,11 @@ int setrlimit_closest(int resource, const struct rlimit *rlim) { if (getrlimit(resource, &highest) < 0) return -errno; + /* If the hard limit is unbounded anyway, then the EPERM had other reasons, let's propagate the original EPERM + * then */ + if (highest.rlim_max == RLIM_INFINITY) + return -EPERM; + fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); From bf428efb0776d45f12ac81dc67463663f92b552f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 17:59:18 +0200 Subject: [PATCH 09/21] nspawn: add new --rlimit= switch, and always set resource limits explicitly for our container payloads This ensures we set the various resource limits of our container explicitly on each invocation so that we inherit less from our callers into the payload. By default resource limits are now set to the same values Linux generally passes to the host PID 1, thus minimizing needless differences between host and container environments. The limits are now also configurable using a new --rlimit= switch. This is preparation for teaching nspawn native OCI runtime support as OCI permits setting resource limits for container payloads, and it hence probably makes sense if we do too. --- man/systemd-nspawn.xml | 25 +++++++ man/systemd.nspawn.xml | 24 ++++++ src/nspawn/nspawn-gperf.gperf | 80 ++++++++++++-------- src/nspawn/nspawn-settings.c | 2 + src/nspawn/nspawn-settings.h | 39 +++++----- src/nspawn/nspawn.c | 136 +++++++++++++++++++++++++++++++++- 6 files changed, 252 insertions(+), 54 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 713782b859..c6b027c58f 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -734,6 +734,31 @@ capabilities are passed using the --capabilities=. + + + + Sets the specified POSIX resource limit for the container payload. Expects an assignment of the + form + LIMIT=SOFT:HARD + or LIMIT=VALUE, where + LIMIT should refer to a resource limit type, such as + RLIMIT_NOFILE or RLIMIT_NICE. The SOFT and + HARD fields should refer to the numeric soft and hard resource limit values. If the + second form is used, VALUE may specifiy a value that is used both as soft and hard + limit. In place of a numeric value the special string infinity may be used to turn off + resource limiting for the specific type of resource. This command line option may be used multiple times to + control limits on multiple limit types. If used multiple times for the same limit type, the last last use + wins. For details about resource limits see setrlimit2. By default + resource limits for the container's init process (PID 1) are set to the same values the Linux kernel originally + passed to the host init system. Note that some resource limits are enforced on resources counted per user, in + particular RLIMIT_NPROC. This means that unless user namespacing is deployed + (i.e. is used, see above), any limits set will be applied to the resource + usage of the same user on all local containers as well as the host. This means particular care needs to be + taken with these limits as they might be triggered by possibly less trusted code. Example: + --rlimit=RLIMIT_NOFILE=8192:16384. + + diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index b5c60a33e0..6bd7b33b34 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -278,6 +278,30 @@ details. + + LimitCPU= + LimitFSIZE= + LimitDATA= + LimitSTACK= + LimitCORE= + LimitRSS= + LimitNOFILE= + LimitAS= + LimitNPROC= + LimitMEMLOCK= + LimitLOCKS= + LimitSIGPENDING= + LimitMSGQUEUE= + LimitNICE= + LimitRTPRIO= + LimitRTTIME= + + Configures various types of resource limits applied to containers. This is equivalent to the + command line switch, and takes the same arguments. See + systemd-nspawn1 for + details. + + diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index ea66971fac..58184d412d 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -18,35 +18,51 @@ struct ConfigPerfItem; %struct-type %includes %% -Exec.Boot, config_parse_boot, 0, 0 -Exec.ProcessTwo, config_parse_pid2, 0, 0 -Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) -Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) -Exec.User, config_parse_string, 0, offsetof(Settings, user) -Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) -Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) -Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) -Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) -Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) -Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) -Exec.PivotRoot, config_parse_pivot_root, 0, 0 -Exec.PrivateUsers, config_parse_private_users, 0, 0 -Exec.NotifyReady, config_parse_bool, 0, offsetof(Settings, notify_ready) -Exec.SystemCallFilter, config_parse_syscall_filter,0, 0, -Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) -Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) -Files.Bind, config_parse_bind, 0, 0 -Files.BindReadOnly, config_parse_bind, 1, 0 -Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 -Files.Overlay, config_parse_overlay, 0, 0 -Files.OverlayReadOnly, config_parse_overlay, 1, 0 -Files.PrivateUsersChown, config_parse_tristate, 0, offsetof(Settings, userns_chown) -Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) -Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces) -Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan) -Network.IPVLAN, config_parse_strv, 0, offsetof(Settings, network_ipvlan) -Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) -Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 -Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) -Network.Zone, config_parse_network_zone, 0, 0 -Network.Port, config_parse_expose_port, 0, 0 +Exec.Boot, config_parse_boot, 0, 0 +Exec.ProcessTwo, config_parse_pid2, 0, 0 +Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) +Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) +Exec.User, config_parse_string, 0, offsetof(Settings, user) +Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) +Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) +Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) +Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) +Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) +Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) +Exec.PivotRoot, config_parse_pivot_root, 0, 0 +Exec.PrivateUsers, config_parse_private_users, 0, 0 +Exec.NotifyReady, config_parse_bool, 0, offsetof(Settings, notify_ready) +Exec.SystemCallFilter, config_parse_syscall_filter, 0, 0, +Exec.LimitCPU, config_parse_rlimit, RLIMIT_CPU, offsetof(Settings, rlimit) +Exec.LimitFSIZE, config_parse_rlimit, RLIMIT_FSIZE, offsetof(Settings, rlimit) +Exec.LimitDATA, config_parse_rlimit, RLIMIT_DATA, offsetof(Settings, rlimit) +Exec.LimitSTACK, config_parse_rlimit, RLIMIT_STACK, offsetof(Settings, rlimit) +Exec.LimitCORE, config_parse_rlimit, RLIMIT_CORE, offsetof(Settings, rlimit) +Exec.LimitRSS, config_parse_rlimit, RLIMIT_RSS, offsetof(Settings, rlimit) +Exec.LimitNOFILE, config_parse_rlimit, RLIMIT_NOFILE, offsetof(Settings, rlimit) +Exec.LimitAS, config_parse_rlimit, RLIMIT_AS, offsetof(Settings, rlimit) +Exec.LimitNPROC, config_parse_rlimit, RLIMIT_NPROC, offsetof(Settings, rlimit) +Exec.LimitMEMLOCK, config_parse_rlimit, RLIMIT_MEMLOCK, offsetof(Settings, rlimit) +Exec.LimitLOCKS, config_parse_rlimit, RLIMIT_LOCKS, offsetof(Settings, rlimit) +Exec.LimitSIGPENDING, config_parse_rlimit, RLIMIT_SIGPENDING, offsetof(Settings, rlimit) +Exec.LimitMSGQUEUE, config_parse_rlimit, RLIMIT_MSGQUEUE, offsetof(Settings, rlimit) +Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, offsetof(Settings, rlimit) +Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit) +Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) +Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) +Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) +Files.Bind, config_parse_bind, 0, 0 +Files.BindReadOnly, config_parse_bind, 1, 0 +Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 +Files.Overlay, config_parse_overlay, 0, 0 +Files.OverlayReadOnly, config_parse_overlay, 1, 0 +Files.PrivateUsersChown, config_parse_tristate, 0, offsetof(Settings, userns_chown) +Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) +Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces) +Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan) +Network.IPVLAN, config_parse_strv, 0, offsetof(Settings, network_ipvlan) +Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) +Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 +Network.Bridge, config_parse_ifname, 0, offsetof(Settings, network_bridge) +Network.Zone, config_parse_network_zone, 0, 0 +Network.Port, config_parse_expose_port, 0, 0 diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 487514d40a..19bf1d4b94 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -12,6 +12,7 @@ #include "nspawn-settings.h" #include "parse-util.h" #include "process-util.h" +#include "rlimit-util.h" #include "socket-util.h" #include "string-util.h" #include "strv.h" @@ -80,6 +81,7 @@ Settings* settings_free(Settings *s) { free(s->working_directory); strv_free(s->syscall_whitelist); strv_free(s->syscall_blacklist); + rlimit_free_all(s->rlimit); strv_free(s->network_interfaces); strv_free(s->network_macvlan); diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 731db87260..0e6ce61217 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -32,24 +32,26 @@ typedef enum UserNamespaceMode { } UserNamespaceMode; typedef enum SettingsMask { - SETTING_START_MODE = 1 << 0, - SETTING_ENVIRONMENT = 1 << 1, - SETTING_USER = 1 << 2, - SETTING_CAPABILITY = 1 << 3, - SETTING_KILL_SIGNAL = 1 << 4, - SETTING_PERSONALITY = 1 << 5, - SETTING_MACHINE_ID = 1 << 6, - SETTING_NETWORK = 1 << 7, - SETTING_EXPOSE_PORTS = 1 << 8, - SETTING_READ_ONLY = 1 << 9, - SETTING_VOLATILE_MODE = 1 << 10, - SETTING_CUSTOM_MOUNTS = 1 << 11, - SETTING_WORKING_DIRECTORY = 1 << 12, - SETTING_USERNS = 1 << 13, - SETTING_NOTIFY_READY = 1 << 14, - SETTING_PIVOT_ROOT = 1 << 15, - SETTING_SYSCALL_FILTER = 1 << 16, - _SETTINGS_MASK_ALL = (1 << 17) -1 + SETTING_START_MODE = UINT64_C(1) << 0, + SETTING_ENVIRONMENT = UINT64_C(1) << 1, + SETTING_USER = UINT64_C(1) << 2, + SETTING_CAPABILITY = UINT64_C(1) << 3, + SETTING_KILL_SIGNAL = UINT64_C(1) << 4, + SETTING_PERSONALITY = UINT64_C(1) << 5, + SETTING_MACHINE_ID = UINT64_C(1) << 6, + SETTING_NETWORK = UINT64_C(1) << 7, + SETTING_EXPOSE_PORTS = UINT64_C(1) << 8, + SETTING_READ_ONLY = UINT64_C(1) << 9, + SETTING_VOLATILE_MODE = UINT64_C(1) << 10, + SETTING_CUSTOM_MOUNTS = UINT64_C(1) << 11, + SETTING_WORKING_DIRECTORY = UINT64_C(1) << 12, + SETTING_USERNS = UINT64_C(1) << 13, + SETTING_NOTIFY_READY = UINT64_C(1) << 14, + SETTING_PIVOT_ROOT = UINT64_C(1) << 15, + SETTING_SYSCALL_FILTER = UINT64_C(1) << 16, + SETTING_RLIMIT_FIRST = UINT64_C(1) << 17, /* we define one bit per resource limit here */ + SETTING_RLIMIT_LAST = UINT64_C(1) << (17 + _RLIMIT_MAX - 1), + _SETTINGS_MASK_ALL = (UINT64_C(1) << (17 + _RLIMIT_MAX)) } SettingsMask; typedef struct Settings { @@ -71,6 +73,7 @@ typedef struct Settings { bool notify_ready; char **syscall_whitelist; char **syscall_blacklist; + struct rlimit *rlimit[_RLIMIT_MAX]; /* [Image] */ int read_only; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 12eaa6c0d7..8ba8e73bf7 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -81,6 +81,7 @@ #include "ptyfwd.h" #include "random-util.h" #include "raw-clone.h" +#include "rlimit-util.h" #include "rm-rf.h" #include "selinux-util.h" #include "signal-util.h" @@ -200,6 +201,7 @@ static void *arg_root_hash = NULL; static size_t arg_root_hash_size = 0; static char **arg_syscall_whitelist = NULL; static char **arg_syscall_blacklist = NULL; +static struct rlimit *arg_rlimit[_RLIMIT_MAX] = {}; static void help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" @@ -264,6 +266,7 @@ static void help(void) { " --drop-capability=CAP Drop the specified capability from the default set\n" " --system-call-filter=LIST|~LIST\n" " Permit/prohibit specific system calls\n" + " --rlimit=NAME=LIMIT Set a resource limit for the payload\n" " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" " host, try-guest, try-host\n" @@ -439,6 +442,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NOTIFY_READY, ARG_ROOT_HASH, ARG_SYSTEM_CALL_FILTER, + ARG_RLIMIT, }; static const struct option options[] = { @@ -492,6 +496,7 @@ static int parse_argv(int argc, char *argv[]) { { "notify-ready", required_argument, NULL, ARG_NOTIFY_READY }, { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, { "system-call-filter", required_argument, NULL, ARG_SYSTEM_CALL_FILTER }, + { "rlimit", required_argument, NULL, ARG_RLIMIT }, {} }; @@ -1094,6 +1099,41 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_RLIMIT: { + const char *eq; + char *name; + int rl; + + eq = strchr(optarg, '='); + if (!eq) { + log_error("--rlimit= expects an '=' assignment."); + return -EINVAL; + } + + name = strndup(optarg, eq - optarg); + if (!name) + return log_oom(); + + rl = rlimit_from_string_harder(name); + if (rl < 0) { + log_error("Unknown resource limit: %s", name); + return -EINVAL; + } + + if (!arg_rlimit[rl]) { + arg_rlimit[rl] = new0(struct rlimit, 1); + if (!arg_rlimit[rl]) + return log_oom(); + } + + r = rlimit_parse(rl, eq + 1, arg_rlimit[rl]); + if (r < 0) + return log_error_errno(r, "Failed to parse resource limit: %s", eq + 1); + + arg_settings_mask |= SETTING_RLIMIT_FIRST << rl; + break; + } + case '?': return -EINVAL; @@ -2282,7 +2322,6 @@ static int inner_child( NULL }; const char *exec_target; - _cleanup_strv_free_ char **env_use = NULL; int r; @@ -2559,10 +2598,10 @@ static int outer_child( FDSet *fds, int netns_fd) { + _cleanup_close_ int fd = -1; + int r, which_failed; pid_t pid; ssize_t l; - int r; - _cleanup_close_ int fd = -1; assert(barrier); assert(directory); @@ -2805,6 +2844,10 @@ static int outer_child( if (fd < 0) return fd; + r = setrlimit_closest_all((const struct rlimit *const*) arg_rlimit, &which_failed); + if (r < 0) + return log_error_errno(r, "Failed to apply resource limit RLIMIT_%s: %m", rlimit_to_string(which_failed)); + pid = raw_clone(SIGCHLD|CLONE_NEWNS| arg_clone_ns_flags | (arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0)); @@ -3046,7 +3089,7 @@ static int load_settings(void) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *p = NULL; const char *fn, *i; - int r; + int r, rl; /* If all settings are masked, there's no point in looking for * the settings file */ @@ -3256,6 +3299,21 @@ static int load_settings(void) { } } + for (rl = 0; rl < _RLIMIT_MAX; rl ++) { + if ((arg_settings_mask & (SETTING_RLIMIT_FIRST << rl))) + continue; + + if (!settings->rlimit[rl]) + continue; + + if (!arg_settings_trusted) { + log_warning("Ignoring Limit%s= setting, file '%s' is not trusted.", rlimit_to_string(rl), p); + continue; + } + + free_and_replace(arg_rlimit[rl], settings->rlimit[rl]); + } + return 0; } @@ -3767,6 +3825,71 @@ static int run(int master, return 1; /* loop again */ } +static int initialize_rlimits(void) { + + /* The default resource limits the kernel passes to PID 1, as per kernel 4.16. Let's pass our container payload + * the same values as the kernel originally passed to PID 1, in order to minimize differences between host and + * container execution environments. */ + + static const struct rlimit kernel_defaults[_RLIMIT_MAX] = { + [RLIMIT_AS] = { RLIM_INFINITY, RLIM_INFINITY }, + [RLIMIT_CORE] = { 0, RLIM_INFINITY }, + [RLIMIT_CPU] = { RLIM_INFINITY, RLIM_INFINITY }, + [RLIMIT_DATA] = { RLIM_INFINITY, RLIM_INFINITY }, + [RLIMIT_FSIZE] = { RLIM_INFINITY, RLIM_INFINITY }, + [RLIMIT_LOCKS] = { RLIM_INFINITY, RLIM_INFINITY }, + [RLIMIT_MEMLOCK] = { 65536, 65536 }, + [RLIMIT_MSGQUEUE] = { 819200, 819200 }, + [RLIMIT_NICE] = { 0, 0 }, + [RLIMIT_NOFILE] = { 1024, 4096 }, + [RLIMIT_RSS] = { RLIM_INFINITY, RLIM_INFINITY }, + [RLIMIT_RTPRIO] = { 0, 0 }, + [RLIMIT_RTTIME] = { RLIM_INFINITY, RLIM_INFINITY }, + [RLIMIT_STACK] = { 8388608, RLIM_INFINITY }, + + /* The kernel scales the default for RLIMIT_NPROC and RLIMIT_SIGPENDING based on the system's amount of + * RAM. To provide best compatibility we'll read these limits off PID 1 instead of hardcoding them + * here. This is safe as we know that PID 1 doesn't change these two limits and thus the original + * kernel's initialization should still be valid during runtime — at least if PID 1 is systemd. Note + * that PID 1 changes a number of other resource limits during early initialization which is why we + * don't read the other limits from PID 1 but prefer the static table above. */ + }; + + int rl; + + for (rl = 0; rl < _RLIMIT_MAX; rl++) { + + /* Let's only fill in what the user hasn't explicitly configured anyway */ + if ((arg_settings_mask & (SETTING_RLIMIT_FIRST << rl)) == 0) { + const struct rlimit *v; + struct rlimit buffer; + + if (IN_SET(rl, RLIMIT_NPROC, RLIMIT_SIGPENDING)) { + /* For these two let's read the limits off PID 1. See above for an explanation. */ + + if (prlimit(1, rl, NULL, &buffer) < 0) + return log_error_errno(errno, "Failed to read resource limit RLIMIT_%s of PID 1: %m", rlimit_to_string(rl)); + + v = &buffer; + } else + v = kernel_defaults + rl; + + arg_rlimit[rl] = newdup(struct rlimit, v, 1); + if (!arg_rlimit[rl]) + return log_oom(); + } + + if (DEBUG_LOGGING) { + _cleanup_free_ char *k = NULL; + + (void) rlimit_format(arg_rlimit[rl], &k); + log_debug("Setting RLIMIT_%s to %s.", rlimit_to_string(rl), k); + } + } + + return 0; +} + int main(int argc, char *argv[]) { _cleanup_free_ char *console = NULL; @@ -3799,6 +3922,10 @@ int main(int argc, char *argv[]) { if (r < 0) goto finish; + r = initialize_rlimits(); + if (r < 0) + goto finish; + r = determine_names(); if (r < 0) goto finish; @@ -4161,6 +4288,7 @@ finish: custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); expose_port_free_all(arg_expose_ports); free(arg_root_hash); + rlimit_free_all(arg_rlimit); return r < 0 ? EXIT_FAILURE : ret; } From 5a4ff98840fba01f9eb1bed23846ae8d1ca796b5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 18:18:11 +0200 Subject: [PATCH 10/21] conf-parser: shorten config_parse_string() by using free_and_strdup() and empty_to_null() --- src/shared/conf-parser.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 7e5fba4330..84d0c16996 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -708,7 +708,7 @@ int config_parse_string( void *data, void *userdata) { - char **s = data, *n; + char **s = data; assert(filename); assert(lvalue); @@ -720,16 +720,8 @@ int config_parse_string( return 0; } - if (isempty(rvalue)) - n = NULL; - else { - n = strdup(rvalue); - if (!n) - return log_oom(); - } - - free(*s); - *s = n; + if (free_and_strdup(s, empty_to_null(rvalue)) < 0) + return log_oom(); return 0; } From 3a9530e5f19565a9cadb7f20bd987c61e0e7c377 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 18:37:32 +0200 Subject: [PATCH 11/21] nspawn: make the hostname of the container explicitly configurable with a new --hostname= switch Previously, the container's hostname was exclusively initialized from the machine name configured with --machine=, i.e. the internal name and the external name used for and by the container was synchronized. This adds a new option --hostname= that optionally allows the internal name to deviate from the external name. This new option is mainly useful to ultimately implement the OCI runtime spec directly in nspawn, but it might be useful on its own for some other usecases too. --- man/systemd-nspawn.xml | 16 ++++++++++++++++ man/systemd.nspawn.xml | 9 +++++++++ src/nspawn/nspawn-gperf.gperf | 1 + src/nspawn/nspawn-settings.c | 30 ++++++++++++++++++++++++++++++ src/nspawn/nspawn-settings.h | 9 ++++++--- src/nspawn/nspawn.c | 30 ++++++++++++++++++++++++++++-- 6 files changed, 90 insertions(+), 5 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index c6b027c58f..16d65d2b29 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -373,6 +373,22 @@ instead. + + + + Controls the hostname to set within the container, if different from the machine name. Expects + a valid hostname as argument. If this option is used, the kernel hostname of the container will be set to this + value, otherwise it will be initialized to the machine name as controlled by the + option described above. The machine name is used for various aspect of identification of the container from the + outside, the kernel hostname configurable with this option is useful for the container to identify itself from + the inside. It is usually a good idea to keep both forms of identification synchronized, in order to avoid + confusion. It is hence recommended to avoid usage of this option, and use + exclusively. Note that regardless whether the container's hostname is initialized from the name set with + or the one set with , the container can later override + its kernel hostname freely on its own as well. + + + diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index 6bd7b33b34..3f8c325fd9 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -302,6 +302,15 @@ details. + + Hostname= + + Configures the kernel hostname set for the container. This is equivalent to the + command line switch, and takes the same argument. See + systemd-nspawn1 for + details. + + diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 58184d412d..f7d2e1a515 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -49,6 +49,7 @@ Exec.LimitMSGQUEUE, config_parse_rlimit, RLIMIT_MSGQUEUE, of Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, offsetof(Settings, rlimit) Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit) Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) +Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname) Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) Files.Bind, config_parse_bind, 0, 0 diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 19bf1d4b94..3757380e28 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "cap-list.h" #include "conf-parser.h" +#include "hostname-util.h" #include "nspawn-network.h" #include "nspawn-settings.h" #include "parse-util.h" @@ -82,6 +83,7 @@ Settings* settings_free(Settings *s) { strv_free(s->syscall_whitelist); strv_free(s->syscall_blacklist); rlimit_free_all(s->rlimit); + free(s->hostname); strv_free(s->network_interfaces); strv_free(s->network_macvlan); @@ -603,3 +605,31 @@ int config_parse_syscall_filter( return 0; } + +int config_parse_hostname( + 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) { + + char **s = data; + + assert(rvalue); + assert(s); + + if (!hostname_is_valid(rvalue, false)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid hostname, ignoring: %s", rvalue); + return 0; + } + + if (free_and_strdup(s, empty_to_null(rvalue)) < 0) + return log_oom(); + + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 0e6ce61217..3d3ee4c28c 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -49,9 +49,10 @@ typedef enum SettingsMask { SETTING_NOTIFY_READY = UINT64_C(1) << 14, SETTING_PIVOT_ROOT = UINT64_C(1) << 15, SETTING_SYSCALL_FILTER = UINT64_C(1) << 16, - SETTING_RLIMIT_FIRST = UINT64_C(1) << 17, /* we define one bit per resource limit here */ - SETTING_RLIMIT_LAST = UINT64_C(1) << (17 + _RLIMIT_MAX - 1), - _SETTINGS_MASK_ALL = (UINT64_C(1) << (17 + _RLIMIT_MAX)) + SETTING_HOSTNAME = UINT64_C(1) << 17, + SETTING_RLIMIT_FIRST = UINT64_C(1) << 18, /* we define one bit per resource limit here */ + SETTING_RLIMIT_LAST = UINT64_C(1) << (18 + _RLIMIT_MAX - 1), + _SETTINGS_MASK_ALL = (UINT64_C(1) << (18 + _RLIMIT_MAX)) - 1 } SettingsMask; typedef struct Settings { @@ -74,6 +75,7 @@ typedef struct Settings { char **syscall_whitelist; char **syscall_blacklist; struct rlimit *rlimit[_RLIMIT_MAX]; + char *hostname; /* [Image] */ int read_only; @@ -118,3 +120,4 @@ int config_parse_boot(const char *unit, const char *filename, unsigned line, con int config_parse_pid2(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_private_users(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_syscall_filter(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_hostname(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); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 8ba8e73bf7..7357576a2a 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -128,7 +128,8 @@ static char *arg_pivot_root_new = NULL; static char *arg_pivot_root_old = NULL; static char *arg_user = NULL; static sd_id128_t arg_uuid = {}; -static char *arg_machine = NULL; +static char *arg_machine = NULL; /* The name used by the host to refer to this */ +static char *arg_hostname = NULL; /* The name the payload sees by default */ static const char *arg_selinux_context = NULL; static const char *arg_selinux_apifs_context = NULL; static const char *arg_slice = NULL; @@ -223,6 +224,7 @@ static void help(void) { " Pivot root to given directory in the container\n" " -u --user=USER Run the command under specified user or uid\n" " -M --machine=NAME Set the machine name for the container\n" + " --hostname=NAME Override the hostname for the container\n" " --uuid=UUID Set a specific machine UUID for the container\n" " -S --slice=SLICE Place the container in the specified slice\n" " --property=NAME=VALUE Set scope unit property\n" @@ -443,6 +445,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_ROOT_HASH, ARG_SYSTEM_CALL_FILTER, ARG_RLIMIT, + ARG_HOSTNAME, }; static const struct option options[] = { @@ -466,6 +469,7 @@ static int parse_argv(int argc, char *argv[]) { { "overlay", required_argument, NULL, ARG_OVERLAY }, { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, { "machine", required_argument, NULL, 'M' }, + { "hostname", required_argument, NULL, ARG_HOSTNAME }, { "slice", required_argument, NULL, 'S' }, { "setenv", required_argument, NULL, 'E' }, { "selinux-context", required_argument, NULL, 'Z' }, @@ -701,6 +705,23 @@ static int parse_argv(int argc, char *argv[]) { } break; + case ARG_HOSTNAME: + if (isempty(optarg)) + arg_hostname = mfree(arg_hostname); + else { + if (!hostname_is_valid(optarg, false)) { + log_error("Invalid hostname: %s", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_hostname, optarg); + if (r < 0) + return log_oom(); + } + + arg_settings_mask |= SETTING_HOSTNAME; + break; + case 'Z': arg_selinux_context = optarg; break; @@ -1762,7 +1783,7 @@ static int setup_hostname(void) { if ((arg_clone_ns_flags & CLONE_NEWUTS) == 0) return 0; - if (sethostname_idempotent(arg_machine) < 0) + if (sethostname_idempotent(arg_hostname ?: arg_machine) < 0) return -errno; return 0; @@ -3314,6 +3335,10 @@ static int load_settings(void) { free_and_replace(arg_rlimit[rl], settings->rlimit[rl]); } + if ((arg_settings_mask & SETTING_HOSTNAME) == 0 && + settings->hostname) + free_and_replace(arg_hostname, settings->hostname); + return 0; } @@ -4274,6 +4299,7 @@ finish: free(arg_template); free(arg_image); free(arg_machine); + free(arg_hostname); free(arg_user); free(arg_pivot_root_new); free(arg_pivot_root_old); From 66edd96310515e8236f5b3da62f0a1f5143bcd83 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 19:35:48 +0200 Subject: [PATCH 12/21] nspawn: add a new --no-new-privileges= cmdline option to nspawn This simply controls the PR_SET_NO_NEW_PRIVS flag for the container. This too is primarily relevant to provide OCI runtime compaitiblity, but might have other uses too, in particular as it nicely complements the existing --capability= and --drop-capability= flags. --- man/systemd-nspawn.xml | 11 +++++++++++ man/systemd.nspawn.xml | 11 +++++++++++ src/nspawn/nspawn-gperf.gperf | 1 + src/nspawn/nspawn-settings.c | 1 + src/nspawn/nspawn-settings.h | 8 +++++--- src/nspawn/nspawn.c | 22 +++++++++++++++++++++- 6 files changed, 50 insertions(+), 4 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 16d65d2b29..7f2b755eab 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -733,6 +733,17 @@ above). + + + + Takes a boolean argument. Specifies the value of the PR_SET_NO_NEW_PRIVS + flag for the container payload. Defaults to off. When turned on the payload code of the container cannot + acquire new privileges, i.e. the "setuid" file bit as well as file system capabilities will not have an effect + anymore. See prctl2 for details + about this flag. + + diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index 3f8c325fd9..436f8cd0e9 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -222,6 +222,17 @@ all cases. + + NoNewPrivileges= + + Takes a boolean argument that controls the PR_SET_NO_NEW_PRIVS flag for + the container payload. This is equivalent to the + command line switch. See + systemd-nspawn1 for + details. + + + KillSignal= diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index f7d2e1a515..a3759674ef 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -50,6 +50,7 @@ Exec.LimitNICE, config_parse_rlimit, RLIMIT_NICE, of Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, offsetof(Settings, rlimit) Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname) +Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges) Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) Files.Bind, config_parse_bind, 0, 0 diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 3757380e28..11a2f41b96 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -36,6 +36,7 @@ int settings_load(FILE *f, const char *path, Settings **ret) { s->userns_mode = _USER_NAMESPACE_MODE_INVALID; s->uid_shift = UID_INVALID; s->uid_range = UID_INVALID; + s->no_new_privileges = -1; s->read_only = -1; s->volatile_mode = _VOLATILE_MODE_INVALID; diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 3d3ee4c28c..130331ee18 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -50,9 +50,10 @@ typedef enum SettingsMask { SETTING_PIVOT_ROOT = UINT64_C(1) << 15, SETTING_SYSCALL_FILTER = UINT64_C(1) << 16, SETTING_HOSTNAME = UINT64_C(1) << 17, - SETTING_RLIMIT_FIRST = UINT64_C(1) << 18, /* we define one bit per resource limit here */ - SETTING_RLIMIT_LAST = UINT64_C(1) << (18 + _RLIMIT_MAX - 1), - _SETTINGS_MASK_ALL = (UINT64_C(1) << (18 + _RLIMIT_MAX)) - 1 + SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18, + SETTING_RLIMIT_FIRST = UINT64_C(1) << 19, /* we define one bit per resource limit here */ + SETTING_RLIMIT_LAST = UINT64_C(1) << (19 + _RLIMIT_MAX - 1), + _SETTINGS_MASK_ALL = (UINT64_C(1) << (19 + _RLIMIT_MAX)) - 1 } SettingsMask; typedef struct Settings { @@ -76,6 +77,7 @@ typedef struct Settings { char **syscall_blacklist; struct rlimit *rlimit[_RLIMIT_MAX]; char *hostname; + int no_new_privileges; /* [Image] */ int read_only; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 7357576a2a..52c6ba1c0c 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -203,6 +203,7 @@ static size_t arg_root_hash_size = 0; static char **arg_syscall_whitelist = NULL; static char **arg_syscall_blacklist = NULL; static struct rlimit *arg_rlimit[_RLIMIT_MAX] = {}; +static bool arg_no_new_privileges = false; static void help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" @@ -446,6 +447,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_SYSTEM_CALL_FILTER, ARG_RLIMIT, ARG_HOSTNAME, + ARG_NO_NEW_PRIVILEGES, }; static const struct option options[] = { @@ -462,6 +464,7 @@ static int parse_argv(int argc, char *argv[]) { { "read-only", no_argument, NULL, ARG_READ_ONLY }, { "capability", required_argument, NULL, ARG_CAPABILITY }, { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, + { "no-new-privileges", required_argument, NULL, ARG_NO_NEW_PRIVILEGES }, { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, { "bind", required_argument, NULL, ARG_BIND }, { "bind-ro", required_argument, NULL, ARG_BIND_RO }, @@ -773,6 +776,15 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_NO_NEW_PRIVILEGES: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --no-new-privileges= argument: %s", optarg); + + arg_no_new_privileges = r; + arg_settings_mask |= SETTING_NO_NEW_PRIVILEGES; + break; + case 'j': arg_link_journal = LINK_GUEST; arg_link_journal_try = true; @@ -2463,6 +2475,10 @@ static int inner_child( if (r < 0) return r; + if (arg_no_new_privileges) + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) + return log_error_errno(errno, "Failed to disable new privileges: %m"); + /* LXC sets container=lxc, so follow the scheme here */ envp[n_env++] = strjoina("container=", arg_container_service_name); @@ -3339,6 +3355,10 @@ static int load_settings(void) { settings->hostname) free_and_replace(arg_hostname, settings->hostname); + if ((arg_settings_mask & SETTING_NO_NEW_PRIVILEGES) == 0 && + settings->no_new_privileges >= 0) + arg_no_new_privileges = settings->no_new_privileges; + return 0; } @@ -4229,7 +4249,7 @@ int main(int argc, char *argv[]) { assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) { + if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) < 0) { r = log_error_errno(errno, "Failed to become subreaper: %m"); goto finish; } From e9eb2c02f0f08dd1d1de57918b277193aa529832 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 20:26:38 +0200 Subject: [PATCH 13/21] basic: split parsing of the OOM score adjust value into its own function in parse-util.c And port config_parse_exec_oom_score_adjust() over to use it. While we are at it, let's also fix config_parse_exec_oom_score_adjust() to accept an empty string for turning off OOM score adjustments set earlier. --- src/basic/parse-util.c | 17 +++++++++++++++++ src/basic/parse-util.h | 2 ++ src/core/load-fragment.c | 35 ++++++++++++++++++++--------------- 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index fbb32f3dc5..07f43b94e5 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -676,3 +676,20 @@ int parse_dev(const char *s, dev_t *ret) { *ret = d; return 0; } + +int parse_oom_score_adjust(const char *s, int *ret) { + int r, v; + + assert(s); + assert(ret); + + r = safe_atoi(s, &v); + if (r < 0) + return r; + + if (v < OOM_SCORE_ADJ_MIN || v > OOM_SCORE_ADJ_MAX) + return -ERANGE; + + *ret = v; + return 0; +} diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 742063c9a4..2b75b938c7 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -118,3 +118,5 @@ int parse_percent(const char *p); int parse_nice(const char *p, int *ret); int parse_ip_port(const char *s, uint16_t *ret); + +int parse_oom_score_adjust(const char *s, int *ret); diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 85e2b1f53f..6c69f4a55f 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -506,16 +506,17 @@ int config_parse_exec_nice( return 0; } -int config_parse_exec_oom_score_adjust(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_exec_oom_score_adjust( + 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; int oa, r; @@ -525,14 +526,18 @@ int config_parse_exec_oom_score_adjust(const char* unit, assert(rvalue); assert(data); - r = safe_atoi(rvalue, &oa); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse the OOM score adjust value, ignoring: %s", rvalue); + if (isempty(rvalue)) { + c->oom_score_adjust_set = false; return 0; } - if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) { - log_syntax(unit, LOG_ERR, filename, line, 0, "OOM score adjust value out of range, ignoring: %s", rvalue); + r = parse_oom_score_adjust(rvalue, &oa); + if (r == -ERANGE) { + log_syntax(unit, LOG_ERR, filename, line, r, "OOM score adjust value out of range, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse the OOM score adjust value, ignoring: %s", rvalue); return 0; } From 9f8168eb233d00b10e7d8ae25d9fb4c9688b4bc6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 20:44:41 +0200 Subject: [PATCH 14/21] process-util: add new helper call for adjusting the OOM score And let's make use of it in execute.c --- src/basic/process-util.c | 9 +++++++++ src/basic/process-util.h | 2 ++ src/core/execute.c | 12 +++--------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 4180769ee8..ce56ce0735 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -1445,6 +1445,15 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret _exit(EXIT_FAILURE); } +int set_oom_score_adjust(int value) { + char t[DECIMAL_STR_MAX(int)]; + + sprintf(t, "%i", value); + + return write_string_file("/proc/self/oom_score_adj", t, + WRITE_STRING_FILE_VERIFY_ON_FAILURE|WRITE_STRING_FILE_DISABLE_BUFFER); +} + static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", [IOPRIO_CLASS_RT] = "realtime", diff --git a/src/basic/process-util.h b/src/basic/process-util.h index f8d1b5e3e8..0cec7c4c5c 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -173,6 +173,8 @@ static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { int fork_agent(const char *name, const int except[], size_t n_except, pid_t *pid, const char *path, ...); +int set_oom_score_adjust(int value); + #if SIZEOF_PID_T == 4 /* The highest possibly (theoretic) pid_t value on this architecture. */ #define PID_T_MAX ((pid_t) INT32_MAX) diff --git a/src/core/execute.c b/src/core/execute.c index 9781b7d920..9634ef98b1 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2916,15 +2916,9 @@ static int exec_child( } if (context->oom_score_adjust_set) { - char t[DECIMAL_STR_MAX(context->oom_score_adjust)]; - - /* When we can't make this change due to EPERM, then - * let's silently skip over it. User namespaces - * prohibit write access to this file, and we - * shouldn't trip up over that. */ - - sprintf(t, "%i", context->oom_score_adjust); - r = write_string_file("/proc/self/oom_score_adj", t, 0); + /* When we can't make this change due to EPERM, then let's silently skip over it. User namespaces + * prohibit write access to this file, and we shouldn't trip up over that. */ + r = set_oom_score_adjust(context->oom_score_adjust); if (IN_SET(r, -EPERM, -EACCES)) log_unit_debug_errno(unit, r, "Failed to adjust OOM setting, assuming containerized execution, ignoring: %m"); else if (r < 0) { From c818eef1cdce45be65c9298a7d759c3d49c109c7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 20:45:39 +0200 Subject: [PATCH 15/21] nspawn: properly handle and log about hostname setting errors --- src/nspawn/nspawn.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 52c6ba1c0c..a7b35abd6b 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1791,12 +1791,14 @@ static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *user } static int setup_hostname(void) { + int r; if ((arg_clone_ns_flags & CLONE_NEWUTS) == 0) return 0; - if (sethostname_idempotent(arg_hostname ?: arg_machine) < 0) - return -errno; + r = sethostname_idempotent(arg_hostname ?: arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to set hostname: %m"); return 0; } @@ -2453,7 +2455,7 @@ static int inner_child( if (r < 0) return log_error_errno(r, "drop_capabilities() failed: %m"); - setup_hostname(); + (void) setup_hostname(); if (arg_personality != PERSONALITY_INVALID) { r = safe_personality(arg_personality); From 81f345dfed63b660ee7b31e0f3737995cf78470f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 21:17:09 +0200 Subject: [PATCH 16/21] nspawn: add a new --oom-score-adjust= command line switch This is primarily useful in order to provide comprehensive OCI runtime compatibility with nspawn, but might have uses outside of it. --- man/systemd-nspawn.xml | 10 +++++++++ man/systemd.nspawn.xml | 9 ++++++++ src/nspawn/nspawn-gperf.gperf | 1 + src/nspawn/nspawn-settings.c | 39 +++++++++++++++++++++++++++++++++++ src/nspawn/nspawn-settings.h | 10 ++++++--- src/nspawn/nspawn.c | 32 ++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 3 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 7f2b755eab..22bb3b705b 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -786,6 +786,16 @@ --rlimit=RLIMIT_NOFILE=8192:16384. + + + + Changes the OOM ("Out Of Memory") score adjustment value for the container payload. This controls + /proc/self/oom_score_adj which influences the preference with which this container is + terminated when memory becomes scarce. For details see proc5. Takes an + integer in the range -1000…1000. + + diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index 436f8cd0e9..748a633a33 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -313,6 +313,15 @@ details. + + OOMScoreAdjust= + + Configures the OOM score adjustment value. This is equivalent to the + command line switch, and takes the same argument. See + systemd-nspawn1 for + details. + + Hostname= diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index a3759674ef..de00dbc243 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -51,6 +51,7 @@ Exec.LimitRTPRIO, config_parse_rlimit, RLIMIT_RTPRIO, of Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, offsetof(Settings, rlimit) Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname) Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges) +Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, 0 Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) Files.Bind, config_parse_bind, 0, 0 diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 11a2f41b96..c91ac73e2e 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -634,3 +634,42 @@ int config_parse_hostname( return 0; } + +int config_parse_oom_score_adjust( + 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) { + + Settings *settings = data; + int oa, r; + + assert(rvalue); + assert(settings); + + if (isempty(rvalue)) { + settings->oom_score_adjust_set = false; + return 0; + } + + r = parse_oom_score_adjust(rvalue, &oa); + if (r == -ERANGE) { + log_syntax(unit, LOG_ERR, filename, line, r, "OOM score adjust value out of range, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse the OOM score adjust value, ignoring: %s", rvalue); + return 0; + } + + settings->oom_score_adjust = oa; + settings->oom_score_adjust_set = true; + + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 130331ee18..7cf5be625b 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -51,9 +51,10 @@ typedef enum SettingsMask { SETTING_SYSCALL_FILTER = UINT64_C(1) << 16, SETTING_HOSTNAME = UINT64_C(1) << 17, SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18, - SETTING_RLIMIT_FIRST = UINT64_C(1) << 19, /* we define one bit per resource limit here */ - SETTING_RLIMIT_LAST = UINT64_C(1) << (19 + _RLIMIT_MAX - 1), - _SETTINGS_MASK_ALL = (UINT64_C(1) << (19 + _RLIMIT_MAX)) - 1 + SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19, + SETTING_RLIMIT_FIRST = UINT64_C(1) << 20, /* we define one bit per resource limit here */ + SETTING_RLIMIT_LAST = UINT64_C(1) << (20 + _RLIMIT_MAX - 1), + _SETTINGS_MASK_ALL = (UINT64_C(1) << (20 + _RLIMIT_MAX)) - 1 } SettingsMask; typedef struct Settings { @@ -78,6 +79,8 @@ typedef struct Settings { struct rlimit *rlimit[_RLIMIT_MAX]; char *hostname; int no_new_privileges; + int oom_score_adjust; + bool oom_score_adjust_set; /* [Image] */ int read_only; @@ -123,3 +126,4 @@ int config_parse_pid2(const char *unit, const char *filename, unsigned line, con int config_parse_private_users(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_syscall_filter(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_hostname(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_oom_score_adjust(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); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index a7b35abd6b..21d63987ec 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -204,6 +204,8 @@ static char **arg_syscall_whitelist = NULL; static char **arg_syscall_blacklist = NULL; static struct rlimit *arg_rlimit[_RLIMIT_MAX] = {}; static bool arg_no_new_privileges = false; +static int arg_oom_score_adjust = 0; +static bool arg_oom_score_adjust_set = false; static void help(void) { printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" @@ -270,6 +272,8 @@ static void help(void) { " --system-call-filter=LIST|~LIST\n" " Permit/prohibit specific system calls\n" " --rlimit=NAME=LIMIT Set a resource limit for the payload\n" + " --oom-score-adjust=VALUE\n" + " Adjust the OOM score value for the payload\n" " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" " host, try-guest, try-host\n" @@ -448,6 +452,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_RLIMIT, ARG_HOSTNAME, ARG_NO_NEW_PRIVILEGES, + ARG_OOM_SCORE_ADJUST, }; static const struct option options[] = { @@ -504,6 +509,7 @@ static int parse_argv(int argc, char *argv[]) { { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, { "system-call-filter", required_argument, NULL, ARG_SYSTEM_CALL_FILTER }, { "rlimit", required_argument, NULL, ARG_RLIMIT }, + { "oom-score-adjust", required_argument, NULL, ARG_OOM_SCORE_ADJUST }, {} }; @@ -1167,6 +1173,15 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_OOM_SCORE_ADJUST: + r = parse_oom_score_adjust(optarg, &arg_oom_score_adjust); + if (r < 0) + return log_error_errno(r, "Failed to parse --oom-score-adjust= parameter: %s", optarg); + + arg_oom_score_adjust_set = true; + arg_settings_mask |= SETTING_OOM_SCORE_ADJUST; + break; + case '?': return -EINVAL; @@ -2451,6 +2466,12 @@ static int inner_child( rtnl_socket = safe_close(rtnl_socket); } + if (arg_oom_score_adjust_set) { + r = set_oom_score_adjust(arg_oom_score_adjust); + if (r < 0) + return log_error_errno(r, "Failed to adjust OOM score: %m"); + } + r = drop_capabilities(); if (r < 0) return log_error_errno(r, "drop_capabilities() failed: %m"); @@ -3361,6 +3382,17 @@ static int load_settings(void) { settings->no_new_privileges >= 0) arg_no_new_privileges = settings->no_new_privileges; + if ((arg_settings_mask & SETTING_OOM_SCORE_ADJUST) == 0 && + settings->oom_score_adjust_set) { + + if (!arg_settings_trusted) + log_warning("Ignoring OOMScoreAdjust= setting, file '%s' is not trusted.", p); + else { + arg_oom_score_adjust = settings->oom_score_adjust; + arg_oom_score_adjust_set = true; + } + } + return 0; } From 50ebcf6cb79f709cc08c8a797df128a4a79fc8fd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 21:18:25 +0200 Subject: [PATCH 17/21] nspawn: show --help text in a pager The text is long enough now, and we do auto-paging for systemctl already, hence let's do it here too. --- src/nspawn/nspawn.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 21d63987ec..517c750eca 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -75,6 +75,7 @@ #include "nspawn-settings.h" #include "nspawn-setuid.h" #include "nspawn-stub-pid1.h" +#include "pager.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -208,6 +209,9 @@ static int arg_oom_score_adjust = 0; static bool arg_oom_score_adjust_set = false; static void help(void) { + + (void) pager_open(false, false); + printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" "Spawn a minimal namespace container for debugging, testing and building.\n\n" " -h --help Show this help\n" @@ -4318,6 +4322,8 @@ finish: if (pid > 0) (void) wait_for_terminate(pid, NULL); + pager_close(); + if (remove_directory && arg_directory) { int k; From d107bb7d63a641d003ad513c9d3b6306743a424e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 21:47:15 +0200 Subject: [PATCH 18/21] nspawn: add a new --cpu-affinity= switch Similar as the other options added before, this is primarily useful to provide comprehensive OCI runtime compatbility, but might be useful otherwise, too. --- man/systemd-nspawn.xml | 9 +++++++ man/systemd.nspawn.xml | 9 +++++++ src/nspawn/nspawn-gperf.gperf | 1 + src/nspawn/nspawn-settings.c | 51 +++++++++++++++++++++++++++++++++++ src/nspawn/nspawn-settings.h | 11 +++++--- src/nspawn/nspawn.c | 40 +++++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 3 deletions(-) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 22bb3b705b..9a0e02187f 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -796,6 +796,15 @@ integer in the range -1000…1000. + + + + Controls the CPU affinity of the container payload. Takes a comma separated list of CPU numbers + or number ranges (the latter's start and end value separated by dashes). See sched_setaffinity2 for + details. + + diff --git a/man/systemd.nspawn.xml b/man/systemd.nspawn.xml index 748a633a33..1780bfd79a 100644 --- a/man/systemd.nspawn.xml +++ b/man/systemd.nspawn.xml @@ -322,6 +322,15 @@ details. + + CPUAffinity= + + Configures the CPU affinity. This is equivalent to the command + line switch, and takes the same argument. See + systemd-nspawn1 for + details. + + Hostname= diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index de00dbc243..f8234e75d4 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -52,6 +52,7 @@ Exec.LimitRTTIME, config_parse_rlimit, RLIMIT_RTTIME, of Exec.Hostname, config_parse_hostname, 0, offsetof(Settings, hostname) Exec.NoNewPrivileges, config_parse_tristate, 0, offsetof(Settings, no_new_privileges) Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, 0 +Exec.CPUAffinity, config_parse_cpu_affinity, 0, 0 Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) Files.Bind, config_parse_bind, 0, 0 diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index c91ac73e2e..0acf718456 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "cap-list.h" #include "conf-parser.h" +#include "cpu-set-util.h" #include "hostname-util.h" #include "nspawn-network.h" #include "nspawn-settings.h" @@ -85,6 +86,7 @@ Settings* settings_free(Settings *s) { strv_free(s->syscall_blacklist); rlimit_free_all(s->rlimit); free(s->hostname); + s->cpuset = cpu_set_mfree(s->cpuset); strv_free(s->network_interfaces); strv_free(s->network_macvlan); @@ -673,3 +675,52 @@ int config_parse_oom_score_adjust( return 0; } + +int config_parse_cpu_affinity( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_cpu_free_ cpu_set_t *cpuset = NULL; + Settings *settings = data; + int ncpus; + + assert(rvalue); + assert(settings); + + ncpus = parse_cpu_set_and_warn(rvalue, &cpuset, unit, filename, line, lvalue); + if (ncpus < 0) + return ncpus; + + if (ncpus == 0) { + /* An empty assignment resets the CPU list */ + settings->cpuset = cpu_set_mfree(settings->cpuset); + settings->cpuset_ncpus = 0; + return 0; + } + + if (!settings->cpuset) { + settings->cpuset = TAKE_PTR(cpuset); + settings->cpuset_ncpus = (unsigned) ncpus; + return 0; + } + + if (settings->cpuset_ncpus < (unsigned) ncpus) { + CPU_OR_S(CPU_ALLOC_SIZE(settings->cpuset_ncpus), cpuset, settings->cpuset, cpuset); + CPU_FREE(settings->cpuset); + settings->cpuset = TAKE_PTR(cpuset); + settings->cpuset_ncpus = (unsigned) ncpus; + return 0; + } + + CPU_OR_S(CPU_ALLOC_SIZE((unsigned) ncpus), settings->cpuset, settings->cpuset, cpuset); + + return 0; +} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 7cf5be625b..c786bf8c86 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -7,6 +7,7 @@ Copyright 2015 Lennart Poettering ***/ +#include #include #include "sd-id128.h" @@ -52,9 +53,10 @@ typedef enum SettingsMask { SETTING_HOSTNAME = UINT64_C(1) << 17, SETTING_NO_NEW_PRIVILEGES = UINT64_C(1) << 18, SETTING_OOM_SCORE_ADJUST = UINT64_C(1) << 19, - SETTING_RLIMIT_FIRST = UINT64_C(1) << 20, /* we define one bit per resource limit here */ - SETTING_RLIMIT_LAST = UINT64_C(1) << (20 + _RLIMIT_MAX - 1), - _SETTINGS_MASK_ALL = (UINT64_C(1) << (20 + _RLIMIT_MAX)) - 1 + SETTING_CPU_AFFINITY = UINT64_C(1) << 20, + SETTING_RLIMIT_FIRST = UINT64_C(1) << 21, /* we define one bit per resource limit here */ + SETTING_RLIMIT_LAST = UINT64_C(1) << (21 + _RLIMIT_MAX - 1), + _SETTINGS_MASK_ALL = (UINT64_C(1) << (21 + _RLIMIT_MAX)) - 1 } SettingsMask; typedef struct Settings { @@ -81,6 +83,8 @@ typedef struct Settings { int no_new_privileges; int oom_score_adjust; bool oom_score_adjust_set; + cpu_set_t *cpuset; + unsigned cpuset_ncpus; /* [Image] */ int read_only; @@ -127,3 +131,4 @@ int config_parse_private_users(const char *unit, const char *filename, unsigned int config_parse_syscall_filter(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_hostname(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_oom_score_adjust(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_cpu_affinity(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); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 517c750eca..460f109656 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -43,6 +43,7 @@ #include "capability-util.h" #include "cgroup-util.h" #include "copy.h" +#include "cpu-set-util.h" #include "dev-setup.h" #include "dissect-image.h" #include "env-util.h" @@ -207,6 +208,8 @@ static struct rlimit *arg_rlimit[_RLIMIT_MAX] = {}; static bool arg_no_new_privileges = false; static int arg_oom_score_adjust = 0; static bool arg_oom_score_adjust_set = false; +static cpu_set_t *arg_cpuset = NULL; +static unsigned arg_cpuset_ncpus = 0; static void help(void) { @@ -278,6 +281,7 @@ static void help(void) { " --rlimit=NAME=LIMIT Set a resource limit for the payload\n" " --oom-score-adjust=VALUE\n" " Adjust the OOM score value for the payload\n" + " --cpu-affinity=CPUS Adjust the CPU affinity of the container\n" " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" " host, try-guest, try-host\n" @@ -457,6 +461,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_HOSTNAME, ARG_NO_NEW_PRIVILEGES, ARG_OOM_SCORE_ADJUST, + ARG_CPU_AFFINITY, }; static const struct option options[] = { @@ -514,6 +519,7 @@ static int parse_argv(int argc, char *argv[]) { { "system-call-filter", required_argument, NULL, ARG_SYSTEM_CALL_FILTER }, { "rlimit", required_argument, NULL, ARG_RLIMIT }, { "oom-score-adjust", required_argument, NULL, ARG_OOM_SCORE_ADJUST }, + { "cpu-affinity", required_argument, NULL, ARG_CPU_AFFINITY }, {} }; @@ -1186,6 +1192,22 @@ static int parse_argv(int argc, char *argv[]) { arg_settings_mask |= SETTING_OOM_SCORE_ADJUST; break; + case ARG_CPU_AFFINITY: { + _cleanup_cpu_free_ cpu_set_t *cpuset = NULL; + + r = parse_cpu_set(optarg, &cpuset); + if (r < 0) + return log_error_errno(r, "Failed to parse CPU affinity mask: %s", optarg); + + if (arg_cpuset) + CPU_FREE(arg_cpuset); + + arg_cpuset = TAKE_PTR(cpuset); + arg_cpuset_ncpus = r; + arg_settings_mask |= SETTING_CPU_AFFINITY; + break; + } + case '?': return -EINVAL; @@ -2476,6 +2498,10 @@ static int inner_child( return log_error_errno(r, "Failed to adjust OOM score: %m"); } + if (arg_cpuset) + if (sched_setaffinity(0, CPU_ALLOC_SIZE(arg_cpuset_ncpus), arg_cpuset) < 0) + return log_error_errno(errno, "Failed to set CPU affinity: %m"); + r = drop_capabilities(); if (r < 0) return log_error_errno(r, "drop_capabilities() failed: %m"); @@ -3397,6 +3423,19 @@ static int load_settings(void) { } } + if ((arg_settings_mask & SETTING_CPU_AFFINITY) == 0 && + settings->cpuset) { + + if (!arg_settings_trusted) + log_warning("Ignoring CPUAffinity= setting, file '%s' is not trusted.", p); + else { + if (arg_cpuset) + CPU_FREE(arg_cpuset); + arg_cpuset = TAKE_PTR(settings->cpuset); + arg_cpuset_ncpus = settings->cpuset_ncpus; + } + } + return 0; } @@ -4375,6 +4414,7 @@ finish: expose_port_free_all(arg_expose_ports); free(arg_root_hash); rlimit_free_all(arg_rlimit); + arg_cpuset = cpu_set_mfree(arg_cpuset); return r < 0 ? EXIT_FAILURE : ret; } From 5d9614077de26a294348bc9c872cd08da21c468d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 7 May 2018 21:57:00 +0200 Subject: [PATCH 19/21] nspawn: split out merging of settings object Let's separate the loading of the settings object and the merging into our arg_xyz fields into two. This will become particularly useful when we eventually are able to load settings from OCI runtime files in addition to .nspawn files. --- src/nspawn/nspawn.c | 395 ++++++++++++++++++++++---------------------- 1 file changed, 202 insertions(+), 193 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 460f109656..7d0cc117ce 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3174,12 +3174,212 @@ static int setup_sd_notify_parent(sd_event *event, int fd, pid_t *inner_child_pi return 0; } +static int merge_settings(Settings *settings, const char *path) { + int rl; + + assert(settings); + assert(path); + + /* Copy over bits from the settings, unless they have been explicitly masked by command line switches. Note + * that this steals the fields of the Settings* structure, and hence modifies it. */ + + if ((arg_settings_mask & SETTING_START_MODE) == 0 && + settings->start_mode >= 0) { + arg_start_mode = settings->start_mode; + strv_free_and_replace(arg_parameters, settings->parameters); + } + + if ((arg_settings_mask & SETTING_PIVOT_ROOT) == 0 && + settings->pivot_root_new) { + free_and_replace(arg_pivot_root_new, settings->pivot_root_new); + free_and_replace(arg_pivot_root_old, settings->pivot_root_old); + } + + if ((arg_settings_mask & SETTING_WORKING_DIRECTORY) == 0 && + settings->working_directory) + free_and_replace(arg_chdir, settings->working_directory); + + if ((arg_settings_mask & SETTING_ENVIRONMENT) == 0 && + settings->environment) + strv_free_and_replace(arg_setenv, settings->environment); + + if ((arg_settings_mask & SETTING_USER) == 0 && + settings->user) + free_and_replace(arg_user, settings->user); + + if ((arg_settings_mask & SETTING_CAPABILITY) == 0) { + uint64_t plus; + + plus = settings->capability; + if (settings_private_network(settings)) + plus |= (1ULL << CAP_NET_ADMIN); + + if (!arg_settings_trusted && plus != 0) { + if (settings->capability != 0) + log_warning("Ignoring Capability= setting, file %s is not trusted.", path); + } else + arg_caps_retain |= plus; + + arg_caps_retain &= ~settings->drop_capability; + } + + if ((arg_settings_mask & SETTING_KILL_SIGNAL) == 0 && + settings->kill_signal > 0) + arg_kill_signal = settings->kill_signal; + + if ((arg_settings_mask & SETTING_PERSONALITY) == 0 && + settings->personality != PERSONALITY_INVALID) + arg_personality = settings->personality; + + if ((arg_settings_mask & SETTING_MACHINE_ID) == 0 && + !sd_id128_is_null(settings->machine_id)) { + + if (!arg_settings_trusted) + log_warning("Ignoring MachineID= setting, file %s is not trusted.", path); + else + arg_uuid = settings->machine_id; + } + + if ((arg_settings_mask & SETTING_READ_ONLY) == 0 && + settings->read_only >= 0) + arg_read_only = settings->read_only; + + if ((arg_settings_mask & SETTING_VOLATILE_MODE) == 0 && + settings->volatile_mode != _VOLATILE_MODE_INVALID) + arg_volatile_mode = settings->volatile_mode; + + if ((arg_settings_mask & SETTING_CUSTOM_MOUNTS) == 0 && + settings->n_custom_mounts > 0) { + + if (!arg_settings_trusted) + log_warning("Ignoring TemporaryFileSystem=, Bind= and BindReadOnly= settings, file %s is not trusted.", path); + else { + custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); + arg_custom_mounts = TAKE_PTR(settings->custom_mounts); + arg_n_custom_mounts = settings->n_custom_mounts; + settings->n_custom_mounts = 0; + } + } + + if ((arg_settings_mask & SETTING_NETWORK) == 0 && + (settings->private_network >= 0 || + settings->network_veth >= 0 || + settings->network_bridge || + settings->network_zone || + settings->network_interfaces || + settings->network_macvlan || + settings->network_ipvlan || + settings->network_veth_extra)) { + + if (!arg_settings_trusted) + log_warning("Ignoring network settings, file %s is not trusted.", path); + else { + arg_network_veth = settings_network_veth(settings); + arg_private_network = settings_private_network(settings); + + strv_free_and_replace(arg_network_interfaces, settings->network_interfaces); + strv_free_and_replace(arg_network_macvlan, settings->network_macvlan); + strv_free_and_replace(arg_network_ipvlan, settings->network_ipvlan); + strv_free_and_replace(arg_network_veth_extra, settings->network_veth_extra); + + free_and_replace(arg_network_bridge, settings->network_bridge); + free_and_replace(arg_network_zone, settings->network_zone); + } + } + + if ((arg_settings_mask & SETTING_EXPOSE_PORTS) == 0 && + settings->expose_ports) { + + if (!arg_settings_trusted) + log_warning("Ignoring Port= setting, file %s is not trusted.", path); + else { + expose_port_free_all(arg_expose_ports); + arg_expose_ports = TAKE_PTR(settings->expose_ports); + } + } + + if ((arg_settings_mask & SETTING_USERNS) == 0 && + settings->userns_mode != _USER_NAMESPACE_MODE_INVALID) { + + if (!arg_settings_trusted) + log_warning("Ignoring PrivateUsers= and PrivateUsersChown= settings, file %s is not trusted.", path); + else { + arg_userns_mode = settings->userns_mode; + arg_uid_shift = settings->uid_shift; + arg_uid_range = settings->uid_range; + arg_userns_chown = settings->userns_chown; + } + } + + if ((arg_settings_mask & SETTING_NOTIFY_READY) == 0) + arg_notify_ready = settings->notify_ready; + + if ((arg_settings_mask & SETTING_SYSCALL_FILTER) == 0) { + + if (!arg_settings_trusted && !strv_isempty(arg_syscall_whitelist)) + log_warning("Ignoring SystemCallFilter= settings, file %s is not trusted.", path); + else { + strv_free_and_replace(arg_syscall_whitelist, settings->syscall_whitelist); + strv_free_and_replace(arg_syscall_blacklist, settings->syscall_blacklist); + } + } + + for (rl = 0; rl < _RLIMIT_MAX; rl ++) { + if ((arg_settings_mask & (SETTING_RLIMIT_FIRST << rl))) + continue; + + if (!settings->rlimit[rl]) + continue; + + if (!arg_settings_trusted) { + log_warning("Ignoring Limit%s= setting, file '%s' is not trusted.", rlimit_to_string(rl), path); + continue; + } + + free_and_replace(arg_rlimit[rl], settings->rlimit[rl]); + } + + if ((arg_settings_mask & SETTING_HOSTNAME) == 0 && + settings->hostname) + free_and_replace(arg_hostname, settings->hostname); + + if ((arg_settings_mask & SETTING_NO_NEW_PRIVILEGES) == 0 && + settings->no_new_privileges >= 0) + arg_no_new_privileges = settings->no_new_privileges; + + if ((arg_settings_mask & SETTING_OOM_SCORE_ADJUST) == 0 && + settings->oom_score_adjust_set) { + + if (!arg_settings_trusted) + log_warning("Ignoring OOMScoreAdjust= setting, file '%s' is not trusted.", path); + else { + arg_oom_score_adjust = settings->oom_score_adjust; + arg_oom_score_adjust_set = true; + } + } + + if ((arg_settings_mask & SETTING_CPU_AFFINITY) == 0 && + settings->cpuset) { + + if (!arg_settings_trusted) + log_warning("Ignoring CPUAffinity= setting, file '%s' is not trusted.", path); + else { + if (arg_cpuset) + CPU_FREE(arg_cpuset); + arg_cpuset = TAKE_PTR(settings->cpuset); + arg_cpuset_ncpus = settings->cpuset_ncpus; + } + } + + return 0; +} + static int load_settings(void) { _cleanup_(settings_freep) Settings *settings = NULL; _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *p = NULL; const char *fn, *i; - int r, rl; + int r; /* If all settings are masked, there's no point in looking for * the settings file */ @@ -3245,198 +3445,7 @@ static int load_settings(void) { if (r < 0) return r; - /* Copy over bits from the settings, unless they have been - * explicitly masked by command line switches. */ - - if ((arg_settings_mask & SETTING_START_MODE) == 0 && - settings->start_mode >= 0) { - arg_start_mode = settings->start_mode; - strv_free_and_replace(arg_parameters, settings->parameters); - } - - if ((arg_settings_mask & SETTING_PIVOT_ROOT) == 0 && - settings->pivot_root_new) { - free_and_replace(arg_pivot_root_new, settings->pivot_root_new); - free_and_replace(arg_pivot_root_old, settings->pivot_root_old); - } - - if ((arg_settings_mask & SETTING_WORKING_DIRECTORY) == 0 && - settings->working_directory) - free_and_replace(arg_chdir, settings->working_directory); - - if ((arg_settings_mask & SETTING_ENVIRONMENT) == 0 && - settings->environment) - strv_free_and_replace(arg_setenv, settings->environment); - - if ((arg_settings_mask & SETTING_USER) == 0 && - settings->user) - free_and_replace(arg_user, settings->user); - - if ((arg_settings_mask & SETTING_CAPABILITY) == 0) { - uint64_t plus; - - plus = settings->capability; - if (settings_private_network(settings)) - plus |= (1ULL << CAP_NET_ADMIN); - - if (!arg_settings_trusted && plus != 0) { - if (settings->capability != 0) - log_warning("Ignoring Capability= setting, file %s is not trusted.", p); - } else - arg_caps_retain |= plus; - - arg_caps_retain &= ~settings->drop_capability; - } - - if ((arg_settings_mask & SETTING_KILL_SIGNAL) == 0 && - settings->kill_signal > 0) - arg_kill_signal = settings->kill_signal; - - if ((arg_settings_mask & SETTING_PERSONALITY) == 0 && - settings->personality != PERSONALITY_INVALID) - arg_personality = settings->personality; - - if ((arg_settings_mask & SETTING_MACHINE_ID) == 0 && - !sd_id128_is_null(settings->machine_id)) { - - if (!arg_settings_trusted) - log_warning("Ignoring MachineID= setting, file %s is not trusted.", p); - else - arg_uuid = settings->machine_id; - } - - if ((arg_settings_mask & SETTING_READ_ONLY) == 0 && - settings->read_only >= 0) - arg_read_only = settings->read_only; - - if ((arg_settings_mask & SETTING_VOLATILE_MODE) == 0 && - settings->volatile_mode != _VOLATILE_MODE_INVALID) - arg_volatile_mode = settings->volatile_mode; - - if ((arg_settings_mask & SETTING_CUSTOM_MOUNTS) == 0 && - settings->n_custom_mounts > 0) { - - if (!arg_settings_trusted) - log_warning("Ignoring TemporaryFileSystem=, Bind= and BindReadOnly= settings, file %s is not trusted.", p); - else { - custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); - arg_custom_mounts = TAKE_PTR(settings->custom_mounts); - arg_n_custom_mounts = settings->n_custom_mounts; - settings->n_custom_mounts = 0; - } - } - - if ((arg_settings_mask & SETTING_NETWORK) == 0 && - (settings->private_network >= 0 || - settings->network_veth >= 0 || - settings->network_bridge || - settings->network_zone || - settings->network_interfaces || - settings->network_macvlan || - settings->network_ipvlan || - settings->network_veth_extra)) { - - if (!arg_settings_trusted) - log_warning("Ignoring network settings, file %s is not trusted.", p); - else { - arg_network_veth = settings_network_veth(settings); - arg_private_network = settings_private_network(settings); - - strv_free_and_replace(arg_network_interfaces, settings->network_interfaces); - strv_free_and_replace(arg_network_macvlan, settings->network_macvlan); - strv_free_and_replace(arg_network_ipvlan, settings->network_ipvlan); - strv_free_and_replace(arg_network_veth_extra, settings->network_veth_extra); - - free_and_replace(arg_network_bridge, settings->network_bridge); - free_and_replace(arg_network_zone, settings->network_zone); - } - } - - if ((arg_settings_mask & SETTING_EXPOSE_PORTS) == 0 && - settings->expose_ports) { - - if (!arg_settings_trusted) - log_warning("Ignoring Port= setting, file %s is not trusted.", p); - else { - expose_port_free_all(arg_expose_ports); - arg_expose_ports = TAKE_PTR(settings->expose_ports); - } - } - - if ((arg_settings_mask & SETTING_USERNS) == 0 && - settings->userns_mode != _USER_NAMESPACE_MODE_INVALID) { - - if (!arg_settings_trusted) - log_warning("Ignoring PrivateUsers= and PrivateUsersChown= settings, file %s is not trusted.", p); - else { - arg_userns_mode = settings->userns_mode; - arg_uid_shift = settings->uid_shift; - arg_uid_range = settings->uid_range; - arg_userns_chown = settings->userns_chown; - } - } - - if ((arg_settings_mask & SETTING_NOTIFY_READY) == 0) - arg_notify_ready = settings->notify_ready; - - if ((arg_settings_mask & SETTING_SYSCALL_FILTER) == 0) { - - if (!arg_settings_trusted && !strv_isempty(arg_syscall_whitelist)) - log_warning("Ignoring SystemCallFilter= settings, file %s is not trusted.", p); - else { - strv_free_and_replace(arg_syscall_whitelist, settings->syscall_whitelist); - strv_free_and_replace(arg_syscall_blacklist, settings->syscall_blacklist); - } - } - - for (rl = 0; rl < _RLIMIT_MAX; rl ++) { - if ((arg_settings_mask & (SETTING_RLIMIT_FIRST << rl))) - continue; - - if (!settings->rlimit[rl]) - continue; - - if (!arg_settings_trusted) { - log_warning("Ignoring Limit%s= setting, file '%s' is not trusted.", rlimit_to_string(rl), p); - continue; - } - - free_and_replace(arg_rlimit[rl], settings->rlimit[rl]); - } - - if ((arg_settings_mask & SETTING_HOSTNAME) == 0 && - settings->hostname) - free_and_replace(arg_hostname, settings->hostname); - - if ((arg_settings_mask & SETTING_NO_NEW_PRIVILEGES) == 0 && - settings->no_new_privileges >= 0) - arg_no_new_privileges = settings->no_new_privileges; - - if ((arg_settings_mask & SETTING_OOM_SCORE_ADJUST) == 0 && - settings->oom_score_adjust_set) { - - if (!arg_settings_trusted) - log_warning("Ignoring OOMScoreAdjust= setting, file '%s' is not trusted.", p); - else { - arg_oom_score_adjust = settings->oom_score_adjust; - arg_oom_score_adjust_set = true; - } - } - - if ((arg_settings_mask & SETTING_CPU_AFFINITY) == 0 && - settings->cpuset) { - - if (!arg_settings_trusted) - log_warning("Ignoring CPUAffinity= setting, file '%s' is not trusted.", p); - else { - if (arg_cpuset) - CPU_FREE(arg_cpuset); - arg_cpuset = TAKE_PTR(settings->cpuset); - arg_cpuset_ncpus = settings->cpuset_ncpus; - } - } - - return 0; + return merge_settings(settings, p); } static int run(int master, From 53e87b5afec7c9fe935eba920f1e0966834862ee Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Apr 2018 11:23:01 +0200 Subject: [PATCH 20/21] path-util: one more empty_or_root() change --- src/basic/path-util.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 2ecb143014..460b7929bc 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -685,11 +685,12 @@ int parse_path_argument_and_warn(const char *path, bool suppress_root, char **ar return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path); path_kill_slashes(p); - if (suppress_root && path_equal(p, "/")) + if (suppress_root && empty_or_root(p)) p = mfree(p); free(*arg); *arg = p; + return 0; } From 919f5ae0c795e30ce628fc2f841b0ddcc1cd2c73 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 8 May 2018 08:03:58 -0700 Subject: [PATCH 21/21] nspawn: voidify more things --- src/nspawn/nspawn.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 7d0cc117ce..b5d9de8da0 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3869,20 +3869,20 @@ static int run(int master, "STATUS=Container running.\n" "X_NSPAWN_LEADER_PID=" PID_FMT, *pid); if (!arg_notify_ready) - sd_notify(false, "READY=1\n"); + (void) sd_notify(false, "READY=1\n"); if (arg_kill_signal > 0) { /* Try to kill the init system on SIGINT or SIGTERM */ - sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(*pid)); - sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(*pid)); + (void) sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(*pid)); + (void) sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(*pid)); } else { /* Immediately exit */ - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); } /* Exit when the child exits */ - sd_event_add_signal(event, NULL, SIGCHLD, on_sigchld, PID_TO_PTR(*pid)); + (void) sd_event_add_signal(event, NULL, SIGCHLD, on_sigchld, PID_TO_PTR(*pid)); if (arg_expose_ports) { r = expose_port_watch_rtnl(event, rtnl_socket_pair[0], on_address_change, exposed, &rtnl);