]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Rework cpu affinity parsing
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Tue, 21 May 2019 06:45:19 +0000 (08:45 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 29 May 2019 08:20:42 +0000 (10:20 +0200)
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.

14 files changed:
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment.c
src/core/main.c
src/nspawn/nspawn-oci.c
src/nspawn/nspawn-settings.c
src/nspawn/nspawn-settings.h
src/nspawn/nspawn.c
src/shared/bus-unit-util.c
src/shared/cpu-set-util.c
src/shared/cpu-set-util.h
src/test/test-cpu-set-util.c
src/test/test-sizeof.c

index 5a56aad5927933318016606f8233cb756dc6ea21..326e451b9a4f03b76441886019c0a68ffbc6079f 100644 (file)
@@ -219,7 +219,7 @@ static int property_get_cpu_affinity(
         assert(reply);
         assert(c);
 
-        return sd_bus_message_append_array(reply, 'y', c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus));
+        return sd_bus_message_append_array(reply, 'y', c->cpu_set.set, c->cpu_set.allocated);
 }
 
 static int property_get_timer_slack_nsec(
@@ -1566,37 +1566,22 @@ int bus_exec_context_set_transient_property(
 
                 if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
                         if (n == 0) {
-                                c->cpuset = cpu_set_mfree(c->cpuset);
-                                c->cpuset_ncpus = 0;
+                                cpu_set_reset(&c->cpu_set);
                                 unit_write_settingf(u, flags, name, "%s=", name);
                         } else {
                                 _cleanup_free_ char *str = NULL;
-                                size_t ncpus;
+                                const CPUSet set = {(cpu_set_t*) a, n};
 
-                                str = cpu_set_to_string(a, n);
+                                str = cpu_set_to_string(&set);
                                 if (!str)
                                         return -ENOMEM;
 
-                                ncpus = CPU_SIZE_TO_NUM(n);
-
-                                if (!c->cpuset || c->cpuset_ncpus < ncpus) {
-                                        cpu_set_t *cpuset;
-
-                                        cpuset = CPU_ALLOC(ncpus);
-                                        if (!cpuset)
-                                                return -ENOMEM;
-
-                                        CPU_ZERO_S(n, cpuset);
-                                        if (c->cpuset) {
-                                                CPU_OR_S(CPU_ALLOC_SIZE(c->cpuset_ncpus), cpuset, c->cpuset, (cpu_set_t*) a);
-                                                CPU_FREE(c->cpuset);
-                                        } else
-                                                CPU_OR_S(n, cpuset, cpuset, (cpu_set_t*) a);
-
-                                        c->cpuset = cpuset;
-                                        c->cpuset_ncpus = ncpus;
-                                } else
-                                        CPU_OR_S(n, c->cpuset, c->cpuset, (cpu_set_t*) a);
+                                /* We forego any optimizations here, and always create the structure using
+                                 * cpu_set_add_all(), because we don't want to care if the existing size we
+                                 * got over dbus is appropriate. */
+                                r = cpu_set_add_all(&c->cpu_set, &set);
+                                if (r < 0)
+                                        return r;
 
                                 unit_write_settingf(u, flags, name, "%s=%s", name, str);
                         }
index a8b6c9287357df41ef9001d8c49aa2cb65eda5ff..01d36c1831d963da1ea5c702f103556cc7d98429 100644 (file)
@@ -3144,8 +3144,8 @@ static int exec_child(
                 }
         }
 
-        if (context->cpuset)
-                if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) {
+        if (context->cpu_set.set)
+                if (sched_setaffinity(0, context->cpu_set.allocated, context->cpu_set.set) < 0) {
                         *exit_status = EXIT_CPUAFFINITY;
                         return log_unit_error_errno(unit, errno, "Failed to set up CPU affinity: %m");
                 }
@@ -3896,7 +3896,7 @@ void exec_context_done(ExecContext *c) {
         c->temporary_filesystems = NULL;
         c->n_temporary_filesystems = 0;
 
-        c->cpuset = cpu_set_mfree(c->cpuset);
+        cpu_set_reset(&c->cpu_set);
 
         c->utmp_id = mfree(c->utmp_id);
         c->selinux_context = mfree(c->selinux_context);
@@ -4328,10 +4328,10 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                         prefix, yes_no(c->cpu_sched_reset_on_fork));
         }
 
-        if (c->cpuset) {
+        if (c->cpu_set.set) {
                 fprintf(f, "%sCPUAffinity:", prefix);
-                for (i = 0; i < c->cpuset_ncpus; i++)
-                        if (CPU_ISSET_S(i, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset))
+                for (i = 0; i < c->cpu_set.allocated * 8; i++)
+                        if (CPU_ISSET_S(i, c->cpu_set.allocated, c->cpu_set.set))
                                 fprintf(f, " %u", i);
                 fputs("\n", f);
         }
index 23bf3b546ad876d138ac373545edd28de6269462..7ddc36e6f3d5289789ae384344598e7a4f73f9e0 100644 (file)
@@ -14,6 +14,7 @@ typedef struct Manager Manager;
 #include <sys/capability.h>
 
 #include "cgroup-util.h"
+#include "cpu-set-util.h"
 #include "fdset.h"
 #include "list.h"
 #include "missing_resource.h"
@@ -172,8 +173,7 @@ struct ExecContext {
         int cpu_sched_policy;
         int cpu_sched_priority;
 
-        unsigned cpuset_ncpus;
-        cpu_set_t *cpuset;
+        CPUSet cpu_set;
 
         ExecInput std_input;
         ExecOutput std_output;
index 11a9a7bdebb62fc6372328b90184c3bef76f5464..bf414e62f1c03080061facf7290ea544374aed82 100644 (file)
@@ -1263,42 +1263,13 @@ int config_parse_exec_cpu_affinity(const char *unit,
                                    void *userdata) {
 
         ExecContext *c = data;
-        _cleanup_cpu_free_ cpu_set_t *cpuset = NULL;
-        int ncpus;
 
         assert(filename);
         assert(lvalue);
         assert(rvalue);
         assert(data);
 
-        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 */
-                c->cpuset = cpu_set_mfree(c->cpuset);
-                c->cpuset_ncpus = 0;
-                return 0;
-        }
-
-        if (!c->cpuset) {
-                c->cpuset = TAKE_PTR(cpuset);
-                c->cpuset_ncpus = (unsigned) ncpus;
-                return 0;
-        }
-
-        if (c->cpuset_ncpus < (unsigned) ncpus) {
-                CPU_OR_S(CPU_ALLOC_SIZE(c->cpuset_ncpus), cpuset, c->cpuset, cpuset);
-                CPU_FREE(c->cpuset);
-                c->cpuset = TAKE_PTR(cpuset);
-                c->cpuset_ncpus = (unsigned) ncpus;
-                return 0;
-        }
-
-        CPU_OR_S(CPU_ALLOC_SIZE((unsigned) ncpus), c->cpuset, c->cpuset, cpuset);
-
-        return 0;
+        return parse_cpu_set_extend(rvalue, &c->cpu_set, true, unit, filename, line, lvalue);
 }
 
 int config_parse_capability_set(
index e34da119846cbcb8a6f07bab14cf1163033be7cc..67a62a5ad27b10fd14d261597d2d87875941c890 100644 (file)
@@ -567,16 +567,18 @@ static int config_parse_cpu_affinity2(
                 void *data,
                 void *userdata) {
 
-        _cleanup_cpu_free_ cpu_set_t *c = NULL;
-        int ncpus;
+        _cleanup_(cpu_set_reset) CPUSet c = {};
+        int r;
 
-        ncpus = parse_cpu_set_and_warn(rvalue, &c, unit, filename, line, lvalue);
-        if (ncpus < 0)
-                return ncpus;
+        r = parse_cpu_set_full(rvalue, &c, true, unit, filename, line, lvalue);
+        if (r < 0)
+                return r;
 
-        if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0)
+        if (sched_setaffinity(0, c.allocated, c.set) < 0)
                 log_warning_errno(errno, "Failed to set CPU affinity: %m");
 
+        // FIXME: parsing and execution should be seperated.
+
         return 0;
 }
 
index 97323f31dd97b9fe0542b125fab0eda3ed44c52b..f5e52bef5ede94523d266618a4acb72d90bb41b0 100644 (file)
@@ -1266,8 +1266,7 @@ struct cpu_data {
         uint64_t shares;
         uint64_t quota;
         uint64_t period;
-        cpu_set_t *cpuset;
-        unsigned ncpus;
+        CPUSet cpu_set;
 };
 
 static int oci_cgroup_cpu_shares(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
@@ -1302,21 +1301,20 @@ static int oci_cgroup_cpu_quota(const char *name, JsonVariant *v, JsonDispatchFl
 
 static int oci_cgroup_cpu_cpus(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
         struct cpu_data *data = userdata;
-        cpu_set_t *set;
+        CPUSet set;
         const char *n;
-        int ncpus;
+        int r;
 
         assert(data);
 
         assert_se(n = json_variant_string(v));
 
-        ncpus = parse_cpu_set(n, &set);
-        if (ncpus < 0)
-                return json_log(v, flags, ncpus, "Failed to parse CPU set specification: %s", n);
+        r = parse_cpu_set(n, &set);
+        if (r < 0)
+                return json_log(v, flags, r, "Failed to parse CPU set specification: %s", n);
 
-        CPU_FREE(data->cpuset);
-        data->cpuset = set;
-        data->ncpus = ncpus;
+        cpu_set_reset(&data->cpu_set);
+        data->cpu_set = set;
 
         return 0;
 }
@@ -1345,13 +1343,12 @@ static int oci_cgroup_cpu(const char *name, JsonVariant *v, JsonDispatchFlags fl
 
         r = json_dispatch(v, table, oci_unexpected, flags, &data);
         if (r < 0) {
-                CPU_FREE(data.cpuset);
+                cpu_set_reset(&data.cpu_set);
                 return r;
         }
 
-        CPU_FREE(s->cpuset);
-        s->cpuset = data.cpuset;
-        s->cpuset_ncpus = data.ncpus;
+        cpu_set_reset(&s->cpu_set);
+        s->cpu_set = data.cpu_set;
 
         if (data.shares != UINT64_MAX) {
                 r = settings_allocate_properties(s);
index 476cb0779e7ec239fdd3b15c63f866de34b4a6fb..9ff37c6dbdd7661a043ca4d37cc1f9205d023039 100644 (file)
@@ -133,7 +133,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);
+        cpu_set_reset(&s->cpu_set);
 
         strv_free(s->network_interfaces);
         strv_free(s->network_macvlan);
@@ -803,41 +803,12 @@ int config_parse_cpu_affinity(
                 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;
+        return parse_cpu_set_extend(rvalue, &settings->cpu_set, true, unit, filename, line, lvalue);
 }
 
 DEFINE_CONFIG_PARSE_ENUM(config_parse_resolv_conf, resolv_conf_mode, ResolvConfMode, "Failed to parse resolv.conf mode");
index 64910c3ecc843a6fe07088792939447c67cd5055..f1a1a754660563a7deee862ebeede3d13a46e56e 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "capability-util.h"
 #include "conf-parser.h"
+#include "cpu-set-util.h"
 #include "macro.h"
 #include "missing_resource.h"
 #include "nspawn-expose-ports.h"
@@ -163,8 +164,7 @@ typedef struct Settings {
         int no_new_privileges;
         int oom_score_adjust;
         bool oom_score_adjust_set;
-        cpu_set_t *cpuset;
-        unsigned cpuset_ncpus;
+        CPUSet cpu_set;
         ResolvConfMode resolv_conf;
         LinkJournal link_journal;
         bool link_journal_try;
index 27829431ac92384780863a26b3b4848725345793..25184e11e9e30fc76d16075451475caaa5844265 100644 (file)
@@ -220,8 +220,7 @@ 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 CPUSet arg_cpu_set = {};
 static ResolvConfMode arg_resolv_conf = RESOLV_CONF_AUTO;
 static TimezoneMode arg_timezone = TIMEZONE_AUTO;
 static unsigned arg_console_width = (unsigned) -1, arg_console_height = (unsigned) -1;
@@ -259,7 +258,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_syscall_blacklist, strv_freep);
 #if HAVE_SECCOMP
 STATIC_DESTRUCTOR_REGISTER(arg_seccomp, seccomp_releasep);
 #endif
-STATIC_DESTRUCTOR_REGISTER(arg_cpuset, CPU_FREEp);
+STATIC_DESTRUCTOR_REGISTER(arg_cpu_set, cpu_set_reset);
 STATIC_DESTRUCTOR_REGISTER(arg_sysctl, strv_freep);
 
 static int help(void) {
@@ -1329,17 +1328,14 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_CPU_AFFINITY: {
-                        _cleanup_cpu_free_ cpu_set_t *cpuset = NULL;
+                        CPUSet cpuset;
 
                         r = parse_cpu_set(optarg, &cpuset);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse CPU affinity mask: %s", optarg);
+                                return log_error_errno(r, "Failed to parse CPU affinity mask %s: %m", optarg);
 
-                        if (arg_cpuset)
-                                CPU_FREE(arg_cpuset);
-
-                        arg_cpuset = TAKE_PTR(cpuset);
-                        arg_cpuset_ncpus = r;
+                        cpu_set_reset(&arg_cpu_set);
+                        arg_cpu_set = cpuset;
                         arg_settings_mask |= SETTING_CPU_AFFINITY;
                         break;
                 }
@@ -2922,8 +2918,8 @@ 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)
+        if (arg_cpu_set.set)
+                if (sched_setaffinity(0, arg_cpu_set.allocated, arg_cpu_set.set) < 0)
                         return log_error_errno(errno, "Failed to set CPU affinity: %m");
 
         (void) setup_hostname();
@@ -3869,15 +3865,14 @@ static int merge_settings(Settings *settings, const char *path) {
         }
 
         if ((arg_settings_mask & SETTING_CPU_AFFINITY) == 0 &&
-            settings->cpuset) {
+            settings->cpu_set.set) {
 
                 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;
+                        cpu_set_reset(&arg_cpu_set);
+                        arg_cpu_set = settings->cpu_set;
+                        settings->cpu_set = (CPUSet) {};
                 }
         }
 
index 2b425efc9c6b349acbb7bfb67193f12ba4346edc..e4889dc55d2d8c093ec60a931346d47143a61732 100644 (file)
@@ -992,13 +992,13 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
         }
 
         if (streq(field, "CPUAffinity")) {
-                _cleanup_cpu_free_ cpu_set_t *cpuset = NULL;
+                _cleanup_(cpu_set_reset) CPUSet cpuset = {};
 
                 r = parse_cpu_set(eq, &cpuset);
                 if (r < 0)
                         return log_error_errno(r, "Failed to parse %s value: %s", field, eq);
 
-                return bus_append_byte_array(m, field, cpuset, CPU_ALLOC_SIZE(r));
+                return bus_append_byte_array(m, field, cpuset.set, cpuset.allocated);
         }
 
         if (STR_IN_SET(field, "RestrictAddressFamilies", "SystemCallFilter")) {
index aa5c4d110d24b93c710f729e8ad9d6c7097b1095..b8d13a2ba62de3c3f3a2568529772f0fd4063f75 100644 (file)
 #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);
 }
index 9574b815829b0cc058015859ac2e3dd2a5cc894b..4c1c81fc59392295dd8572f91873161530971320 100644 (file)
@@ -8,23 +8,40 @@
 DEFINE_TRIVIAL_CLEANUP_FUNC(cpu_set_t*, CPU_FREE);
 #define _cleanup_cpu_free_ _cleanup_(CPU_FREEp)
 
-static inline cpu_set_t* cpu_set_mfree(cpu_set_t *p) {
-        if (p)
-                CPU_FREE(p);
-        return NULL;
-}
-
 cpu_set_t* cpu_set_malloc(unsigned *ncpus);
 
-char* cpu_set_to_string(const cpu_set_t *set, size_t setsize);
-int parse_cpu_set_internal(const char *rvalue, cpu_set_t **cpu_set, bool warn, const char *unit, const char *filename, unsigned line, const char *lvalue);
-
-static inline int parse_cpu_set_and_warn(const char *rvalue, cpu_set_t **cpu_set, const char *unit, const char *filename, unsigned line, const char *lvalue) {
-        assert(lvalue);
-
-        return parse_cpu_set_internal(rvalue, cpu_set, true, unit, filename, line, lvalue);
+/* This wraps the libc interface with a variable to keep the allocated size. */
+typedef struct CPUSet {
+        cpu_set_t *set;
+        size_t allocated; /* in bytes */
+} CPUSet;
+
+static inline void cpu_set_reset(CPUSet *a) {
+        assert((a->allocated > 0) == !!a->set);
+        if (a->set)
+                CPU_FREE(a->set);
+        *a = (CPUSet) {};
 }
 
-static inline int parse_cpu_set(const char *rvalue, cpu_set_t **cpu_set){
-        return parse_cpu_set_internal(rvalue, cpu_set, false, NULL, NULL, 0, NULL);
+int cpu_set_add_all(CPUSet *a, const CPUSet *b);
+
+char* cpu_set_to_string(const CPUSet *a);
+int parse_cpu_set_full(
+                const char *rvalue,
+                CPUSet *cpu_set,
+                bool warn,
+                const char *unit,
+                const char *filename, unsigned line,
+                const char *lvalue);
+int parse_cpu_set_extend(
+                const char *rvalue,
+                CPUSet *old,
+                bool warn,
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *lvalue);
+
+static inline int parse_cpu_set(const char *rvalue, CPUSet *cpu_set){
+        return parse_cpu_set_full(rvalue, cpu_set, false, NULL, NULL, 0, NULL);
 }
index ff5edb2a692e0e96672957f2be96cb2f0a3e71bd..8c17931f84e11bcdc5291d2bcc8bf5b10db3c030 100644 (file)
 #include "macro.h"
 
 static void test_parse_cpu_set(void) {
-        cpu_set_t *c = NULL;
+        CPUSet c = {};
         _cleanup_free_ char *str = NULL;
-        int ncpus;
         int cpu;
 
         /* Simple range (from CPUAffinity example) */
-        ncpus = parse_cpu_set_and_warn("1 2", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_ISSET_S(1, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(CPU_ISSET_S(2, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 2);
-
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+        assert_se(parse_cpu_set_full("1 2", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.set);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_ISSET_S(1, c.allocated, c.set));
+        assert_se(CPU_ISSET_S(2, c.allocated, c.set));
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 2);
+
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* A more interesting range */
-        ncpus = parse_cpu_set_and_warn("0 1 2 3 8 9 10 11", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
+        assert_se(parse_cpu_set_full("0 1 2 3 8 9 10 11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 8);
         for (cpu = 0; cpu < 4; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
         for (cpu = 8; cpu < 12; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* Quoted strings */
-        ncpus = parse_cpu_set_and_warn("8 '9' 10 \"11\"", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 4);
+        assert_se(parse_cpu_set_full("8 '9' 10 \"11\"", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 4);
         for (cpu = 8; cpu < 12; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* Use commas as separators */
-        ncpus = parse_cpu_set_and_warn("0,1,2,3 8,9,10,11", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
+        assert_se(parse_cpu_set_full("0,1,2,3 8,9,10,11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 8);
         for (cpu = 0; cpu < 4; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
         for (cpu = 8; cpu < 12; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* Commas with spaces (and trailing comma, space) */
-        ncpus = parse_cpu_set_and_warn("0, 1, 2, 3, 4, 5, 6, 7, ", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
+        assert_se(parse_cpu_set_full("0, 1, 2, 3, 4, 5, 6, 7, ", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 8);
         for (cpu = 0; cpu < 8; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* Ranges */
-        ncpus = parse_cpu_set_and_warn("0-3,8-11", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
+        assert_se(parse_cpu_set_full("0-3,8-11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 8);
         for (cpu = 0; cpu < 4; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
         for (cpu = 8; cpu < 12; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* Ranges with trailing comma, space */
-        ncpus = parse_cpu_set_and_warn("0-3  8-11, ", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
+        assert_se(parse_cpu_set_full("0-3  8-11, ", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 8);
         for (cpu = 0; cpu < 4; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
         for (cpu = 8; cpu < 12; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* Negative range (returns empty cpu_set) */
-        ncpus = parse_cpu_set_and_warn("3-0", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 0);
-        c = cpu_set_mfree(c);
+        assert_se(parse_cpu_set_full("3-0", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 0);
+        cpu_set_reset(&c);
 
         /* Overlapping ranges */
-        ncpus = parse_cpu_set_and_warn("0-7 4-11", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 12);
+        assert_se(parse_cpu_set_full("0-7 4-11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 12);
         for (cpu = 0; cpu < 12; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* Mix ranges and individual CPUs */
-        ncpus = parse_cpu_set_and_warn("0,1 4-11", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus >= 1024);
-        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 10);
-        assert_se(CPU_ISSET_S(0, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(CPU_ISSET_S(1, CPU_ALLOC_SIZE(ncpus), c));
+        assert_se(parse_cpu_set_full("0,1 4-11", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0);
+        assert_se(c.allocated >= sizeof(__cpu_mask) / 8);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 10);
+        assert_se(CPU_ISSET_S(0, c.allocated, c.set));
+        assert_se(CPU_ISSET_S(1, c.allocated, c.set));
         for (cpu = 4; cpu < 12; cpu++)
-                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
-        assert_se(str = cpu_set_to_string(c, CPU_ALLOC_SIZE(ncpus)));
+                assert_se(CPU_ISSET_S(cpu, c.allocated, c.set));
+        assert_se(str = cpu_set_to_string(&c));
         log_info("cpu_set_to_string: %s", str);
         str = mfree(str);
-        c = cpu_set_mfree(c);
+        cpu_set_reset(&c);
 
         /* Garbage */
-        ncpus = parse_cpu_set_and_warn("0 1 2 3 garbage", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus < 0);
-        assert_se(!c);
+        assert_se(parse_cpu_set_full("0 1 2 3 garbage", &c, true, NULL, "fake", 1, "CPUAffinity") == -EINVAL);
+        assert_se(!c.set);
+        assert_se(c.allocated == 0);
 
         /* Range with garbage */
-        ncpus = parse_cpu_set_and_warn("0-3 8-garbage", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus < 0);
-        assert_se(!c);
+        assert_se(parse_cpu_set_full("0-3 8-garbage", &c, true, NULL, "fake", 1, "CPUAffinity") == -EINVAL);
+        assert_se(!c.set);
+        assert_se(c.allocated == 0);
 
         /* Empty string */
-        c = NULL;
-        ncpus = parse_cpu_set_and_warn("", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus == 0);  /* empty string returns 0 */
-        assert_se(!c);
+        assert_se(parse_cpu_set_full("", &c, true, NULL, "fake", 1, "CPUAffinity") == 0);
+        assert_se(!c.set);                /* empty string returns NULL */
+        assert_se(c.allocated == 0);
 
         /* Runaway quoted string */
-        ncpus = parse_cpu_set_and_warn("0 1 2 3 \"4 5 6 7 ", &c, NULL, "fake", 1, "CPUAffinity");
-        assert_se(ncpus < 0);
-        assert_se(!c);
+        assert_se(parse_cpu_set_full("0 1 2 3 \"4 5 6 7 ", &c, true, NULL, "fake", 1, "CPUAffinity") == -EINVAL);
+        assert_se(!c.set);
+        assert_se(c.allocated == 0);
+
+        /* Maximum allocation */
+        assert_se(parse_cpu_set_full("8000-8191", &c, true, NULL, "fake", 1, "CPUAffinity") == 0);
+        assert_se(CPU_COUNT_S(c.allocated, c.set) == 192);
+        assert_se(str = cpu_set_to_string(&c));
+        log_info("cpu_set_to_string: %s", str);
+        str = mfree(str);
+        cpu_set_reset(&c);
 }
 
 int main(int argc, char *argv[]) {
+        log_info("CPU_ALLOC_SIZE(1) = %zu", CPU_ALLOC_SIZE(1));
+        log_info("CPU_ALLOC_SIZE(9) = %zu", CPU_ALLOC_SIZE(9));
+        log_info("CPU_ALLOC_SIZE(64) = %zu", CPU_ALLOC_SIZE(64));
+        log_info("CPU_ALLOC_SIZE(65) = %zu", CPU_ALLOC_SIZE(65));
+        log_info("CPU_ALLOC_SIZE(1024) = %zu", CPU_ALLOC_SIZE(1024));
+        log_info("CPU_ALLOC_SIZE(1025) = %zu", CPU_ALLOC_SIZE(1025));
+        log_info("CPU_ALLOC_SIZE(8191) = %zu", CPU_ALLOC_SIZE(8191));
+
         test_parse_cpu_set();
 
         return 0;
index 35b087653ef030d1d6cd5b6e56937bafd8f4540c..7fc16a62b656c5d497cecdd6c47fc88192dc7972 100644 (file)
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
+#include <sched.h>
 #include <stdio.h>
 #include <string.h>
 
@@ -65,6 +66,8 @@ int main(void) {
         info(uid_t);
         info(gid_t);
 
+        info(__cpu_mask);
+
         info(enum Enum);
         info(enum BigEnum);
         info(enum BigEnum2);