dd65c9
From 6e00430563108b98230abd7407ac54fde61ae93c Mon Sep 17 00:00:00 2001
dd65c9
From: Jan Synacek <jsynacek@redhat.com>
dd65c9
Date: Tue, 26 Sep 2017 12:34:19 +0200
dd65c9
Subject: [PATCH] support ranges when parsing CPUAffinity
dd65c9
dd65c9
The functionality was implemented in https://github.com/systemd/systemd/pull/1699/.
dd65c9
However, it is not backportable without considerable code changes.
dd65c9
dd65c9
Implement parse_range() and parse_cpu_set_and_warn() from the upstream master
dd65c9
branch and use them in appropriate places. Also introduce relevant tests.
dd65c9
dd65c9
Resolves: #1493976
dd65c9
---
23b3cf
 src/core/load-fragment.c |  49 ++-----
23b3cf
 src/core/main.c          |  48 +------
23b3cf
 src/shared/util.c        |  91 ++++++++++++
dd65c9
 src/shared/util.h        |   9 ++
23b3cf
 src/test/test-util.c     | 296 +++++++++++++++++++++++++++++++++++++++
dd65c9
 5 files changed, 417 insertions(+), 76 deletions(-)
dd65c9
dd65c9
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
dd65c9
index 0c0fa0f50..a10e1903a 100644
dd65c9
--- a/src/core/load-fragment.c
dd65c9
+++ b/src/core/load-fragment.c
dd65c9
@@ -884,50 +884,29 @@ int config_parse_exec_cpu_affinity(const char *unit,
dd65c9
                                    void *userdata) {
dd65c9
 
dd65c9
         ExecContext *c = data;
dd65c9
-        const char *word, *state;
dd65c9
-        size_t l;
dd65c9
+        _cleanup_cpu_free_ cpu_set_t *cpuset = NULL;
dd65c9
+        int ncpus;
dd65c9
 
dd65c9
         assert(filename);
dd65c9
         assert(lvalue);
dd65c9
         assert(rvalue);
dd65c9
         assert(data);
dd65c9
 
dd65c9
-        if (isempty(rvalue)) {
dd65c9
-                /* An empty assignment resets the CPU list */
dd65c9
-                if (c->cpuset)
dd65c9
-                        CPU_FREE(c->cpuset);
dd65c9
-                c->cpuset = NULL;
dd65c9
-                return 0;
dd65c9
-        }
dd65c9
-
dd65c9
-        FOREACH_WORD_QUOTED(word, l, rvalue, state) {
dd65c9
-                _cleanup_free_ char *t = NULL;
dd65c9
-                int r;
dd65c9
-                unsigned cpu;
dd65c9
-
dd65c9
-                t = strndup(word, l);
dd65c9
-                if (!t)
dd65c9
-                        return log_oom();
dd65c9
-
dd65c9
-                r = safe_atou(t, &cpu);
dd65c9
+        ncpus = parse_cpu_set_and_warn(rvalue, &cpuset, unit, filename, line, lvalue);
dd65c9
+        if (ncpus < 0)
dd65c9
+                return ncpus;
dd65c9
 
dd65c9
-                if (!c->cpuset) {
dd65c9
-                        c->cpuset = cpu_set_malloc(&c->cpuset_ncpus);
dd65c9
-                        if (!c->cpuset)
dd65c9
-                                return log_oom();
dd65c9
-                }
dd65c9
+        if (c->cpuset)
dd65c9
+                CPU_FREE(c->cpuset);
dd65c9
 
dd65c9
-                if (r < 0 || cpu >= c->cpuset_ncpus) {
dd65c9
-                        log_syntax(unit, LOG_ERR, filename, line, ERANGE,
dd65c9
-                                   "Failed to parse CPU affinity '%s', ignoring: %s", t, rvalue);
dd65c9
-                        return 0;
dd65c9
-                }
dd65c9
-
dd65c9
-                CPU_SET_S(cpu, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset);
dd65c9
+        if (ncpus == 0)
dd65c9
+                /* An empty assignment resets the CPU list */
dd65c9
+                c->cpuset = NULL;
dd65c9
+        else {
dd65c9
+                c->cpuset = cpuset;
dd65c9
+                cpuset = NULL;
dd65c9
         }
dd65c9
-        if (!isempty(state))
dd65c9
-                log_syntax(unit, LOG_WARNING, filename, line, EINVAL,
dd65c9
-                           "Trailing garbage, ignoring.");
dd65c9
+        c->cpuset_ncpus = ncpus;
dd65c9
 
dd65c9
         return 0;
dd65c9
 }
dd65c9
diff --git a/src/core/main.c b/src/core/main.c
dd65c9
index 66393ed6a..5554ef468 100644
dd65c9
--- a/src/core/main.c
dd65c9
+++ b/src/core/main.c
dd65c9
@@ -438,49 +438,15 @@ static int config_parse_cpu_affinity2(
dd65c9
                 void *data,
dd65c9
                 void *userdata) {
dd65c9
 
dd65c9
-        const char *word, *state;
dd65c9
-        size_t l;
dd65c9
-        cpu_set_t *c = NULL;
dd65c9
-        unsigned ncpus = 0;
dd65c9
-
dd65c9
-        assert(filename);
dd65c9
-        assert(lvalue);
dd65c9
-        assert(rvalue);
dd65c9
+        _cleanup_cpu_free_ cpu_set_t *c = NULL;
dd65c9
+        int ncpus;
dd65c9
 
dd65c9
-        FOREACH_WORD_QUOTED(word, l, rvalue, state) {
dd65c9
-                char *t;
dd65c9
-                int r;
dd65c9
-                unsigned cpu;
dd65c9
-
dd65c9
-                if (!(t = strndup(word, l)))
dd65c9
-                        return log_oom();
dd65c9
-
dd65c9
-                r = safe_atou(t, &cpu);
dd65c9
-                free(t);
dd65c9
-
dd65c9
-                if (!c)
dd65c9
-                        if (!(c = cpu_set_malloc(&ncpus)))
dd65c9
-                                return log_oom();
dd65c9
+        ncpus = parse_cpu_set_and_warn(rvalue, &c, unit, filename, line, lvalue);
dd65c9
+        if (ncpus < 0)
dd65c9
+                return ncpus;
dd65c9
 
dd65c9
-                if (r < 0 || cpu >= ncpus) {
dd65c9
-                        log_syntax(unit, LOG_ERR, filename, line, -r,
dd65c9
-                                   "Failed to parse CPU affinity '%s'", rvalue);
dd65c9
-                        CPU_FREE(c);
dd65c9
-                        return -EBADMSG;
dd65c9
-                }
dd65c9
-
dd65c9
-                CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c);
dd65c9
-        }
dd65c9
-        if (!isempty(state))
dd65c9
-                log_syntax(unit, LOG_ERR, filename, line, EINVAL,
dd65c9
-                           "Trailing garbage, ignoring.");
dd65c9
-
dd65c9
-        if (c) {
dd65c9
-                if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0)
dd65c9
-                        log_unit_warning(unit, "Failed to set CPU affinity: %m");
dd65c9
-
dd65c9
-                CPU_FREE(c);
dd65c9
-        }
dd65c9
+        if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0)
dd65c9
+                log_warning_errno(errno, "Failed to set CPU affinity: %m");
dd65c9
 
dd65c9
         return 0;
dd65c9
 }
dd65c9
diff --git a/src/shared/util.c b/src/shared/util.c
dd65c9
index bbb457759..39359fcc8 100644
dd65c9
--- a/src/shared/util.c
dd65c9
+++ b/src/shared/util.c
dd65c9
@@ -2727,6 +2727,43 @@ int parse_size(const char *t, off_t base, off_t *size) {
dd65c9
         return 0;
dd65c9
 }
dd65c9
 
dd65c9
+int parse_range(const char *t, unsigned *lower, unsigned *upper) {
dd65c9
+        _cleanup_free_ char *word = NULL;
dd65c9
+        unsigned l, u;
dd65c9
+        int r;
dd65c9
+
dd65c9
+        assert(lower);
dd65c9
+        assert(upper);
dd65c9
+
dd65c9
+        /* Extract the lower bound. */
dd65c9
+        r = extract_first_word(&t, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS);
dd65c9
+        if (r < 0)
dd65c9
+                return r;
dd65c9
+        if (r == 0)
dd65c9
+                return -EINVAL;
dd65c9
+
dd65c9
+        r = safe_atou(word, &l);
dd65c9
+        if (r < 0)
dd65c9
+                return r;
dd65c9
+
dd65c9
+        /* Check for the upper bound and extract it if needed */
dd65c9
+        if (!t)
dd65c9
+                /* Single number with no dashes. */
dd65c9
+                u = l;
dd65c9
+        else if (!*t)
dd65c9
+                /* Trailing dash is an error. */
dd65c9
+                return -EINVAL;
dd65c9
+        else {
dd65c9
+                r = safe_atou(t, &u);
dd65c9
+                if (r < 0)
dd65c9
+                        return r;
dd65c9
+        }
dd65c9
+
dd65c9
+        *lower = l;
dd65c9
+        *upper = u;
dd65c9
+        return 0;
dd65c9
+}
dd65c9
+
dd65c9
 int make_stdio(int fd) {
dd65c9
         int r, s, t;
dd65c9
 
dd65c9
@@ -3460,6 +3497,60 @@ cpu_set_t* cpu_set_malloc(unsigned *ncpus) {
dd65c9
         }
dd65c9
 }
dd65c9
 
dd65c9
+int parse_cpu_set_and_warn(
dd65c9
+                const char *rvalue,
dd65c9
+                cpu_set_t **cpu_set,
dd65c9
+                const char *unit,
dd65c9
+                const char *filename,
dd65c9
+                unsigned line,
dd65c9
+                const char *lvalue) {
dd65c9
+
dd65c9
+        const char *whole_rvalue = rvalue;
dd65c9
+        _cleanup_cpu_free_ cpu_set_t *c = NULL;
dd65c9
+        unsigned ncpus = 0;
dd65c9
+
dd65c9
+        assert(lvalue);
dd65c9
+        assert(rvalue);
dd65c9
+
dd65c9
+        for (;;) {
dd65c9
+                _cleanup_free_ char *word = NULL;
dd65c9
+                unsigned cpu, cpu_lower, cpu_upper;
dd65c9
+                int r;
dd65c9
+
dd65c9
+                r = extract_first_word(&rvalue, &word, WHITESPACE ",", EXTRACT_QUOTES);
dd65c9
+                if (r < 0)
dd65c9
+                        return log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue);
dd65c9
+                if (r == 0)
dd65c9
+                        break;
dd65c9
+
dd65c9
+                if (!c) {
dd65c9
+                        c = cpu_set_malloc(&ncpus);
dd65c9
+                        if (!c)
dd65c9
+                                return log_oom();
dd65c9
+                }
dd65c9
+
dd65c9
+                r = parse_range(word, &cpu_lower, &cpu_upper);
dd65c9
+                if (r < 0)
dd65c9
+                        return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU affinity '%s'", word);
dd65c9
+                if (cpu_lower >= ncpus || cpu_upper >= ncpus)
dd65c9
+                        return log_syntax(unit, LOG_ERR, filename, line, EINVAL, "CPU out of range '%s' ncpus is %u", word, ncpus);
dd65c9
+
dd65c9
+                if (cpu_lower > cpu_upper)
dd65c9
+                        log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u", word, cpu_lower, cpu_upper);
dd65c9
+                else
dd65c9
+                        for (cpu = cpu_lower; cpu <= cpu_upper; cpu++)
dd65c9
+                                CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c);
dd65c9
+        }
dd65c9
+
dd65c9
+        /* On success, sets *cpu_set and returns ncpus for the system. */
dd65c9
+        if (c) {
dd65c9
+                *cpu_set = c;
dd65c9
+                c = NULL;
dd65c9
+        }
dd65c9
+
dd65c9
+        return (int) ncpus;
dd65c9
+}
dd65c9
+
dd65c9
 int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) {
dd65c9
         static const char status_indent[] = "         "; /* "[" STATUS "] " */
dd65c9
         _cleanup_free_ char *s = NULL;
dd65c9
diff --git a/src/shared/util.h b/src/shared/util.h
dd65c9
index 80ad18c0a..526a6fe84 100644
dd65c9
--- a/src/shared/util.h
dd65c9
+++ b/src/shared/util.h
dd65c9
@@ -136,6 +136,11 @@ bool streq_ptr(const char *a, const char *b) _pure_;
dd65c9
 
dd65c9
 #define malloc0(n) (calloc((n), 1))
dd65c9
 
dd65c9
+static inline void *mfree(void *memory) {
dd65c9
+        free(memory);
dd65c9
+        return NULL;
dd65c9
+}
dd65c9
+
dd65c9
 static inline const char* yes_no(bool b) {
dd65c9
         return b ? "yes" : "no";
dd65c9
 }
dd65c9
@@ -195,6 +200,7 @@ void safe_close_pair(int p[]);
dd65c9
 void close_many(const int fds[], unsigned n_fd);
dd65c9
 
dd65c9
 int parse_size(const char *t, off_t base, off_t *size);
dd65c9
+int parse_range(const char *t, unsigned *lower, unsigned *upper);
dd65c9
 
dd65c9
 int parse_boolean(const char *v) _pure_;
dd65c9
 int parse_pid(const char *s, pid_t* ret_pid);
dd65c9
@@ -474,6 +480,7 @@ int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool hon
dd65c9
 int pipe_eof(int fd);
dd65c9
 
dd65c9
 cpu_set_t* cpu_set_malloc(unsigned *ncpus);
dd65c9
+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);
dd65c9
 
dd65c9
 int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) _printf_(4,0);
dd65c9
 int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) _printf_(4,5);
dd65c9
@@ -692,6 +699,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, fclose);
dd65c9
 DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose);
dd65c9
 DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir);
dd65c9
 DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, endmntent);
dd65c9
+DEFINE_TRIVIAL_CLEANUP_FUNC(cpu_set_t*, CPU_FREE);
dd65c9
 
dd65c9
 #define _cleanup_free_ _cleanup_(freep)
dd65c9
 #define _cleanup_close_ _cleanup_(closep)
dd65c9
@@ -702,6 +710,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, endmntent);
dd65c9
 #define _cleanup_closedir_ _cleanup_(closedirp)
dd65c9
 #define _cleanup_endmntent_ _cleanup_(endmntentp)
dd65c9
 #define _cleanup_close_pair_ _cleanup_(close_pairp)
dd65c9
+#define _cleanup_cpu_free_ _cleanup_(CPU_FREEp)
dd65c9
 
dd65c9
 _malloc_  _alloc_(1, 2) static inline void *malloc_multiply(size_t a, size_t b) {
dd65c9
         if (_unlikely_(b != 0 && a > ((size_t) -1) / b))
dd65c9
diff --git a/src/test/test-util.c b/src/test/test-util.c
dd65c9
index 971f97d7c..fcf5416c0 100644
dd65c9
--- a/src/test/test-util.c
dd65c9
+++ b/src/test/test-util.c
dd65c9
@@ -689,6 +689,300 @@ static void test_parse_size(void) {
dd65c9
         assert_se(parse_size("-10B 20K", 1024, &bytes) == -ERANGE);
dd65c9
 }
dd65c9
 
dd65c9
+static void test_parse_range(void) {
dd65c9
+        unsigned lower, upper;
dd65c9
+
dd65c9
+        /* Successful cases */
dd65c9
+        assert_se(parse_range("111", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 111);
dd65c9
+        assert_se(upper == 111);
dd65c9
+
dd65c9
+        assert_se(parse_range("111-123", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 111);
dd65c9
+        assert_se(upper == 123);
dd65c9
+
dd65c9
+        assert_se(parse_range("123-111", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 123);
dd65c9
+        assert_se(upper == 111);
dd65c9
+
dd65c9
+        assert_se(parse_range("123-123", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 123);
dd65c9
+        assert_se(upper == 123);
dd65c9
+
dd65c9
+        assert_se(parse_range("0", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 0);
dd65c9
+        assert_se(upper == 0);
dd65c9
+
dd65c9
+        assert_se(parse_range("0-15", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 0);
dd65c9
+        assert_se(upper == 15);
dd65c9
+
dd65c9
+        assert_se(parse_range("15-0", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 15);
dd65c9
+        assert_se(upper == 0);
dd65c9
+
dd65c9
+        assert_se(parse_range("128-65535", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 128);
dd65c9
+        assert_se(upper == 65535);
dd65c9
+
dd65c9
+        assert_se(parse_range("1024-4294967295", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 1024);
dd65c9
+        assert_se(upper == 4294967295);
dd65c9
+
dd65c9
+        /* Leading whitespace is acceptable */
dd65c9
+        assert_se(parse_range(" 111", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 111);
dd65c9
+        assert_se(upper == 111);
dd65c9
+
dd65c9
+        assert_se(parse_range(" 111-123", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 111);
dd65c9
+        assert_se(upper == 123);
dd65c9
+
dd65c9
+        assert_se(parse_range("111- 123", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 111);
dd65c9
+        assert_se(upper == 123);
dd65c9
+
dd65c9
+        assert_se(parse_range("\t111-\t123", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 111);
dd65c9
+        assert_se(upper == 123);
dd65c9
+
dd65c9
+        assert_se(parse_range(" \t 111- \t 123", &lower, &upper) == 0);
dd65c9
+        assert_se(lower == 111);
dd65c9
+        assert_se(upper == 123);
dd65c9
+
dd65c9
+        /* Error cases, make sure they fail as expected */
dd65c9
+        lower = upper = 9999;
dd65c9
+        assert_se(parse_range("111garbage", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("garbage111", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("garbage", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111-123garbage", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111garbage-123", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        /* Empty string */
dd65c9
+        lower = upper = 9999;
dd65c9
+        assert_se(parse_range("", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        /* 111--123 will pass -123 to safe_atou which returns -ERANGE for negative */
dd65c9
+        assert_se(parse_range("111--123", &lower, &upper) == -ERANGE);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("-111-123", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111-123-", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111.4-123", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111-123.4", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111,4-123", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111-123,4", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        /* Error on trailing dash */
dd65c9
+        assert_se(parse_range("111-", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111-123-", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111--", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111- ", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        /* Whitespace is not a separator */
dd65c9
+        assert_se(parse_range("111 123", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111\t123", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111 \t 123", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        /* Trailing whitespace is invalid (from safe_atou) */
dd65c9
+        assert_se(parse_range("111 ", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111-123 ", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111 -123", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111 -123 ", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111\t-123\t", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        assert_se(parse_range("111 \t -123 \t ", &lower, &upper) == -EINVAL);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+
dd65c9
+        /* Out of the "unsigned" range, this is 1<<64 */
dd65c9
+        assert_se(parse_range("0-18446744073709551616", &lower, &upper) == -ERANGE);
dd65c9
+        assert_se(lower == 9999);
dd65c9
+        assert_se(upper == 9999);
dd65c9
+}
dd65c9
+
dd65c9
+static void test_parse_cpu_set(void) {
dd65c9
+        cpu_set_t *c = NULL;
dd65c9
+        int ncpus;
dd65c9
+        int cpu;
dd65c9
+
dd65c9
+        /* Simple range (from CPUAffinity example) */
dd65c9
+        ncpus = parse_cpu_set_and_warn("1 2", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_ISSET_S(1, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        assert_se(CPU_ISSET_S(2, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 2);
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* A more interesting range */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0 1 2 3 8 9 10 11", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
dd65c9
+        for (cpu = 0; cpu < 4; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        for (cpu = 8; cpu < 12; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Quoted strings */
dd65c9
+        ncpus = parse_cpu_set_and_warn("8 '9' 10 \"11\"", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 4);
dd65c9
+        for (cpu = 8; cpu < 12; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Use commas as separators */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0,1,2,3 8,9,10,11", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
dd65c9
+        for (cpu = 0; cpu < 4; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        for (cpu = 8; cpu < 12; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Commas with spaces (and trailing comma, space) */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0, 1, 2, 3, 4, 5, 6, 7, ", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
dd65c9
+        for (cpu = 0; cpu < 8; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Ranges */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0-3,8-11", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
dd65c9
+        for (cpu = 0; cpu < 4; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        for (cpu = 8; cpu < 12; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Ranges with trailing comma, space */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0-3  8-11, ", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 8);
dd65c9
+        for (cpu = 0; cpu < 4; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        for (cpu = 8; cpu < 12; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Negative range (returns empty cpu_set) */
dd65c9
+        ncpus = parse_cpu_set_and_warn("3-0", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 0);
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Overlapping ranges */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0-7 4-11", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 12);
dd65c9
+        for (cpu = 0; cpu < 12; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Mix ranges and individual CPUs */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0,1 4-11", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus >= 1024);
dd65c9
+        assert_se(CPU_COUNT_S(CPU_ALLOC_SIZE(ncpus), c) == 10);
dd65c9
+        assert_se(CPU_ISSET_S(0, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        assert_se(CPU_ISSET_S(1, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        for (cpu = 4; cpu < 12; cpu++)
dd65c9
+                assert_se(CPU_ISSET_S(cpu, CPU_ALLOC_SIZE(ncpus), c));
dd65c9
+        c = mfree(c);
dd65c9
+
dd65c9
+        /* Garbage */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0 1 2 3 garbage", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus < 0);
dd65c9
+        assert_se(!c);
dd65c9
+
dd65c9
+        /* Range with garbage */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0-3 8-garbage", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus < 0);
dd65c9
+        assert_se(!c);
dd65c9
+
dd65c9
+        /* Empty string */
dd65c9
+        c = NULL;
dd65c9
+        ncpus = parse_cpu_set_and_warn("", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus == 0);  /* empty string returns 0 */
dd65c9
+        assert_se(!c);
dd65c9
+
dd65c9
+        /* Runaway quoted string */
dd65c9
+        ncpus = parse_cpu_set_and_warn("0 1 2 3 \"4 5 6 7 ", &c, NULL, "fake", 1, "CPUAffinity");
dd65c9
+        assert_se(ncpus < 0);
dd65c9
+        assert_se(!c);
dd65c9
+}
dd65c9
+
dd65c9
 static void test_config_parse_iec_off(void) {
dd65c9
         off_t offset = 0;
dd65c9
         assert_se(config_parse_iec_off(NULL, "/this/file", 11, "Section", 22, "Size", 0, "4M", &offset, NULL) == 0);
dd65c9
@@ -1605,6 +1899,8 @@ int main(int argc, char *argv[]) {
dd65c9
         test_get_process_comm();
dd65c9
         test_protect_errno();
dd65c9
         test_parse_size();
dd65c9
+        test_parse_range();
dd65c9
+        test_parse_cpu_set();
dd65c9
         test_config_parse_iec_off();
dd65c9
         test_strextend();
dd65c9
         test_strrep();