diff options
author | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2019-05-21 08:45:19 +0200 |
---|---|---|
committer | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2019-05-29 10:20:42 +0200 |
commit | 0985c7c4e22c8dbbea4398cf3453da45ebf63800 (patch) | |
tree | bbb08300ff3c022617628b8cbff223218b2ebc62 /src/shared/cpu-set-util.c | |
parent | shared/cpu-set-util: remove now-unused CPU_SIZE_TO_NUM() (diff) | |
download | systemd-0985c7c4e22c8dbbea4398cf3453da45ebf63800.tar.xz systemd-0985c7c4e22c8dbbea4398cf3453da45ebf63800.zip |
Rework cpu affinity parsing
The CPU_SET_S api is pretty bad. In particular, it has a parameter for the size
of the array, but operations which take two (CPU_EQUAL_S) or even three arrays
(CPU_{AND,OR,XOR}_S) still take just one size. This means that all arrays must
be of the same size, or buffer overruns will occur. This is exactly what our
code would do, if it received an array of unexpected size over the network.
("Unexpected" here means anything different from what cpu_set_malloc() detects
as the "right" size.)
Let's rework this, and store the size in bytes of the allocated storage area.
The code will now parse any number up to 8191, independently of what the current
kernel supports. This matches the kernel maximum setting for any architecture,
to make things more portable.
Fixes #12605.
Diffstat (limited to 'src/shared/cpu-set-util.c')
-rw-r--r-- | src/shared/cpu-set-util.c | 133 |
1 files changed, 108 insertions, 25 deletions
diff --git a/src/shared/cpu-set-util.c b/src/shared/cpu-set-util.c index aa5c4d110d..b8d13a2ba6 100644 --- a/src/shared/cpu-set-util.c +++ b/src/shared/cpu-set-util.c @@ -10,16 +10,17 @@ #include "extract-word.h" #include "log.h" #include "macro.h" +#include "memory-util.h" #include "parse-util.h" #include "string-util.h" -char* cpu_set_to_string(const cpu_set_t *set, size_t setsize) { +char* cpu_set_to_string(const CPUSet *a) { _cleanup_free_ char *str = NULL; size_t allocated = 0, len = 0; int i, r; - for (i = 0; (size_t) i < setsize * 8; i++) { - if (!CPU_ISSET_S(i, setsize, set)) + for (i = 0; (size_t) i < a->allocated * 8; i++) { + if (!CPU_ISSET_S(i, a->allocated, a->set)) continue; if (!GREEDY_REALLOC(str, allocated, len + 1 + DECIMAL_STR_MAX(int))) @@ -62,24 +63,74 @@ cpu_set_t* cpu_set_malloc(unsigned *ncpus) { } } -int parse_cpu_set_internal( +static int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus) { + size_t need; + + assert(cpu_set); + + need = CPU_ALLOC_SIZE(ncpus); + if (need > cpu_set->allocated) { + cpu_set_t *t; + + t = realloc(cpu_set->set, need); + if (!t) + return -ENOMEM; + + memzero((uint8_t*) t + cpu_set->allocated, need - cpu_set->allocated); + + cpu_set->set = t; + cpu_set->allocated = need; + } + + return 0; +} + +static int cpu_set_add(CPUSet *cpu_set, unsigned cpu) { + int r; + + if (cpu >= 8192) + /* As of kernel 5.1, CONFIG_NR_CPUS can be set to 8192 on PowerPC */ + return -ERANGE; + + r = cpu_set_realloc(cpu_set, cpu + 1); + if (r < 0) + return r; + + CPU_SET_S(cpu, cpu_set->allocated, cpu_set->set); + return 0; +} + +int cpu_set_add_all(CPUSet *a, const CPUSet *b) { + int r; + + /* Do this backwards, so if we fail, we fail before changing anything. */ + for (unsigned cpu_p1 = b->allocated * 8; cpu_p1 > 0; cpu_p1--) + if (CPU_ISSET_S(cpu_p1 - 1, b->allocated, b->set)) { + r = cpu_set_add(a, cpu_p1 - 1); + if (r < 0) + return r; + } + + return 0; +} + +int parse_cpu_set_full( const char *rvalue, - cpu_set_t **cpu_set, + CPUSet *cpu_set, bool warn, const char *unit, const char *filename, unsigned line, const char *lvalue) { - _cleanup_cpu_free_ cpu_set_t *c = NULL; + _cleanup_(cpu_set_reset) CPUSet c = {}; const char *p = rvalue; - unsigned ncpus = 0; - assert(rvalue); + assert(p); for (;;) { _cleanup_free_ char *word = NULL; - unsigned cpu, cpu_lower, cpu_upper; + unsigned cpu_lower, cpu_upper; int r; r = extract_first_word(&p, &word, WHITESPACE ",", EXTRACT_QUOTES); @@ -90,31 +141,63 @@ int parse_cpu_set_internal( if (r == 0) break; - if (!c) { - c = cpu_set_malloc(&ncpus); - if (!c) - return warn ? log_oom() : -ENOMEM; - } - r = parse_range(word, &cpu_lower, &cpu_upper); if (r < 0) return warn ? log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU affinity '%s'", word) : r; - if (cpu_lower >= ncpus || cpu_upper >= ncpus) - return warn ? log_syntax(unit, LOG_ERR, filename, line, EINVAL, "CPU out of range '%s' ncpus is %u", word, ncpus) : -EINVAL; if (cpu_lower > cpu_upper) { if (warn) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u, ignoring", word, cpu_lower, cpu_upper); - continue; + log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u, ignoring.", + word, cpu_lower, cpu_upper); + + /* Make sure something is allocated, to distinguish this from the empty case */ + r = cpu_set_realloc(&c, 1); + if (r < 0) + return r; } - for (cpu = cpu_lower; cpu <= cpu_upper; cpu++) - CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c); + for (unsigned cpu_p1 = MIN(cpu_upper, UINT_MAX-1) + 1; cpu_p1 > cpu_lower; cpu_p1--) { + r = cpu_set_add(&c, cpu_p1 - 1); + if (r < 0) + return warn ? log_syntax(unit, LOG_ERR, filename, line, r, + "Cannot add CPU %u to set: %m", cpu_p1 - 1) : r; + } } - /* On success, sets *cpu_set and returns ncpus for the system. */ - if (c) - *cpu_set = TAKE_PTR(c); + /* On success, transfer ownership to the output variable */ + *cpu_set = c; + c = (CPUSet) {}; + + return 0; +} + +int parse_cpu_set_extend( + const char *rvalue, + CPUSet *old, + bool warn, + const char *unit, + const char *filename, + unsigned line, + const char *lvalue) { + + _cleanup_(cpu_set_reset) CPUSet cpuset = {}; + int r; + + r = parse_cpu_set_full(rvalue, &cpuset, true, unit, filename, line, lvalue); + if (r < 0) + return r; + + if (!cpuset.set) { + /* An empty assignment resets the CPU list */ + cpu_set_reset(old); + return 0; + } + + if (!old->set) { + *old = cpuset; + cpuset = (CPUSet) {}; + return 0; + } - return (int) ncpus; + return cpu_set_add_all(old, &cpuset); } |