From 91518d20ddf0376808544576d0ef0883cedc67d4 Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Fri, 20 Nov 2015 12:54:10 +0100 Subject: core: support ranges for RLIMIT options The new parser supports: - specify both limits to the same value - specify both limits the size or time specific suffixes are supported, for example LimitRTTIME=1sec LimitAS=4G:16G The patch introduces parse_rlimit_range() and rlim type (size, sec, usec, etc.) specific parsers. No code is duplicated now. The patch also sync docs for DefaultLimitXXX= and LimitXXX=. References: https://github.com/systemd/systemd/issues/1769 --- man/systemd-system.conf.xml | 27 ++++-- man/systemd.exec.xml | 5 +- src/core/load-fragment.c | 229 ++++++++++++++++++++++---------------------- src/test/test-unit-file.c | 30 ++++++ 4 files changed, 171 insertions(+), 120 deletions(-) diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index ead52951da..edc6df914a 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -354,13 +354,26 @@ These settings control various default resource limits for units. See setrlimit2 - for details. Use the string infinity to - configure no limit on a specific resource. The multiplicative suffixes - K (=1024), M (=1024*1024) and so on for G, T, P and E may be used for - resource limits measured in bytes (e.g. DefaultLimitAS=16G). These - settings may be overridden in individual units using the corresponding - LimitXXX= directives. Note that these resource limits are only - defaults for units, they are not applied to PID 1 + for details. The resource limit is possible to specify in two formats, + to set soft and hard limits to the same value, + or to set both limits individually (e.g. DefaultLimitAS=4G:16G). + Use the string infinity to + configure no limit on a specific resource. The multiplicative + suffixes K (=1024), M (=1024*1024) and so on for G, T, P and E + may be used for resource limits measured in bytes + (e.g. DefaultLimitAS=16G). For the limits referring to time values, + the usual time units ms, s, min, h and so on may be used (see + systemd.time7 + for details). Note that if no time unit is specified for + DefaultLimitCPU= the default unit of seconds is + implied, while for DefaultLimitRTTIME= the default + unit of microseconds is implied. Also, note that the effective + granularity of the limits might influence their + enforcement. For example, time limits specified for + DefaultLimitCPU= will be rounded up implicitly to + multiples of 1s. These settings may be overridden in individual units + using the corresponding LimitXXX= directives. Note that these resource + limits are only defaults for units, they are not applied to PID 1 itself. diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index ecc3a8973c..5f98ef163c 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -630,7 +630,10 @@ These settings set both soft and hard limits of various resources for executed processes. See setrlimit2 - for details. Use the string infinity to + for details. The resource limit is possible to specify in two formats, + to set soft and hard limits to the same value, + or to set both limits individually (e.g. LimitAS=4G:16G). + Use the string infinity to configure no limit on a specific resource. The multiplicative suffixes K (=1024), M (=1024*1024) and so on for G, T, P and E may be used for resource limits measured in bytes diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index d5cf476fda..3cb2cffedf 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1089,46 +1089,107 @@ int config_parse_bounding_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; - rlim_t v; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); +static int rlim_parse_u64(const char *val, rlim_t *res) { + int r = 0; - rl += ltype; - - if (streq(rvalue, "infinity")) - v = RLIM_INFINITY; + if (streq(val, "infinity")) + *res = RLIM_INFINITY; else { uint64_t u; /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */ assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); - r = safe_atou64(rvalue, &u); + r = safe_atou64(val, &u); if (r >= 0 && u >= (uint64_t) RLIM_INFINITY) r = -ERANGE; - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } + if (r == 0) + *res = (rlim_t) u; + } + return r; +} + +static int rlim_parse_size(const char *val, rlim_t *res) { + int r = 0; + + if (streq(val, "infinity")) + *res = RLIM_INFINITY; + else { + uint64_t u; + + r = parse_size(val, 1024, &u); + if (r >= 0 && u >= (uint64_t) RLIM_INFINITY) + r = -ERANGE; + if (r == 0) + *res = (rlim_t) u; + } + return r; +} + +static int rlim_parse_sec(const char *val, rlim_t *res) { + int r = 0; + + if (streq(val, "infinity")) + *res = RLIM_INFINITY; + else { + usec_t t; + + r = parse_sec(val, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + *res = RLIM_INFINITY; + else + *res = (rlim_t) (DIV_ROUND_UP(t, USEC_PER_SEC)); + + } + return r; +} + +static int rlim_parse_usec(const char *val, rlim_t *res) { + int r = 0; + + if (streq(val, "infinity")) + *res = RLIM_INFINITY; + else { + usec_t t; + + r = parse_time(val, &t, 1); + if (r < 0) + return r; + if (t == USEC_INFINITY) + *res = RLIM_INFINITY; + else + *res = (rlim_t) t; + } + return r; +} - v = (rlim_t) u; +static int parse_rlimit_range( + const char *unit, + const char *filename, + unsigned line, + const char *value, + struct rlimit **rl, + int (*rlim_parser)(const char *, rlim_t *)) { + + rlim_t soft, hard; + _cleanup_free_ char *sword = NULL, *hword = NULL; + int nwords, r; + + assert(value); + + /* or */ + nwords = extract_many_words(&value, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &sword, &hword, NULL); + r = nwords < 0 ? nwords : nwords == 0 ? -EINVAL : 0; + + if (r == 0) + r = rlim_parser(sword, &soft); + if (r == 0 && nwords == 2) + r = rlim_parser(hword, &hard); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", value); + return 0; } if (!*rl) { @@ -1136,12 +1197,12 @@ int config_parse_limit( if (!*rl) return log_oom(); } - - (*rl)->rlim_cur = (*rl)->rlim_max = v; + (*rl)->rlim_cur = soft; + (*rl)->rlim_max = nwords == 2 ? hard : soft; return 0; } -int config_parse_bytes_limit( +int config_parse_limit( const char *unit, const char *filename, unsigned line, @@ -1154,8 +1215,6 @@ int config_parse_bytes_limit( void *userdata) { struct rlimit **rl = data; - rlim_t bytes; - int r; assert(filename); assert(lvalue); @@ -1163,31 +1222,30 @@ int config_parse_bytes_limit( assert(data); rl += ltype; + return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_u64); +} - if (streq(rvalue, "infinity")) - bytes = RLIM_INFINITY; - else { - uint64_t u; - - r = parse_size(rvalue, 1024, &u); - if (r >= 0 && u >= (uint64_t) RLIM_INFINITY) - r = -ERANGE; - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } +int config_parse_bytes_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) { - bytes = (rlim_t) u; - } + struct rlimit **rl = data; - if (!*rl) { - *rl = new(struct rlimit, 1); - if (!*rl) - return log_oom(); - } + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); - (*rl)->rlim_cur = (*rl)->rlim_max = bytes; - return 0; + rl += ltype; + return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_size); } int config_parse_sec_limit( @@ -1203,8 +1261,6 @@ int config_parse_sec_limit( void *userdata) { struct rlimit **rl = data; - rlim_t seconds; - int r; assert(filename); assert(lvalue); @@ -1212,35 +1268,9 @@ int config_parse_sec_limit( assert(data); rl += ltype; - - if (streq(rvalue, "infinity")) - seconds = RLIM_INFINITY; - else { - usec_t t; - - r = parse_sec(rvalue, &t); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } - - if (t == USEC_INFINITY) - seconds = RLIM_INFINITY; - else - seconds = (rlim_t) (DIV_ROUND_UP(t, USEC_PER_SEC)); - } - - if (!*rl) { - *rl = new(struct rlimit, 1); - if (!*rl) - return log_oom(); - } - - (*rl)->rlim_cur = (*rl)->rlim_max = seconds; - return 0; + return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_sec); } - int config_parse_usec_limit( const char *unit, const char *filename, @@ -1254,8 +1284,6 @@ int config_parse_usec_limit( void *userdata) { struct rlimit **rl = data; - rlim_t useconds; - int r; assert(filename); assert(lvalue); @@ -1263,33 +1291,10 @@ int config_parse_usec_limit( assert(data); rl += ltype; + return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_usec); +} - if (streq(rvalue, "infinity")) - useconds = RLIM_INFINITY; - else { - usec_t t; - - r = parse_time(rvalue, &t, 1); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } - - if (t == USEC_INFINITY) - useconds = RLIM_INFINITY; - else - useconds = (rlim_t) t; - } - - if (!*rl) { - *rl = new(struct rlimit, 1); - if (!*rl) - return log_oom(); - } - (*rl)->rlim_cur = (*rl)->rlim_max = useconds; - return 0; -} #ifdef HAVE_SYSV_COMPAT int config_parse_sysv_priority(const char *unit, diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index c3973a316e..854f60be80 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -680,11 +680,21 @@ static void test_config_parse_rlimit(void) { 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(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(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(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); + rl[RLIMIT_NOFILE] = mfree(rl[RLIMIT_NOFILE]); assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "56", rl, NULL) >= 0); @@ -697,6 +707,11 @@ static void test_config_parse_rlimit(void) { assert_se(rl[RLIMIT_CPU]->rlim_cur == 57); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); + assert_se(config_parse_sec_limit(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_sec_limit(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); @@ -714,16 +729,31 @@ static void test_config_parse_rlimit(void) { assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + assert_se(config_parse_usec_limit(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_usec_limit(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_usec_limit(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_usec_limit(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_usec_limit(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_usec_limit(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); -- cgit v1.2.3-54-g00ecf