1abbee
From 81a95ec724b7b874f850cb0f32f1981ccc4fb062 Mon Sep 17 00:00:00 2001
1abbee
From: Karel Zak <kzak@redhat.com>
1abbee
Date: Fri, 20 Nov 2015 12:54:10 +0100
1abbee
Subject: [PATCH] core: support <soft:hard> ranges for RLIMIT options
1abbee
1abbee
The new parser supports:
1abbee
1abbee
 <value>       - specify both limits to the same value
1abbee
 <soft:hard>   - specify both limits
1abbee
1abbee
the size or time specific suffixes are supported, for example
1abbee
1abbee
  LimitRTTIME=1sec
1abbee
  LimitAS=4G:16G
1abbee
1abbee
The patch introduces parse_rlimit_range() and rlim type (size, sec,
1abbee
usec, etc.) specific parsers. No code is duplicated now.
1abbee
1abbee
The patch also sync docs for DefaultLimitXXX= and LimitXXX=.
1abbee
1abbee
References: https://github.com/systemd/systemd/issues/1769
1abbee
1abbee
Cherry-picked from: 91518d20ddf0376808544576d0ef0883cedc67d4
1abbee
Resolves: #1351415
1abbee
---
1abbee
 man/systemd-system.conf.xml |  27 ++-
1abbee
 man/systemd.exec.xml        |   5 +-
23b3cf
 src/core/load-fragment.c    | 243 ++++++++++---------
23b3cf
 src/shared/util.c           | 467 ++++++++++++++++++++++++++++++++++++
1abbee
 src/shared/util.h           |  14 ++
1abbee
 src/test/test-unit-file.c   |  31 +++
1abbee
 6 files changed, 667 insertions(+), 120 deletions(-)
1abbee
1abbee
diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml
181b3f
index b7d9cdee0..39d19bc71 100644
1abbee
--- a/man/systemd-system.conf.xml
1abbee
+++ b/man/systemd-system.conf.xml
1abbee
@@ -326,13 +326,26 @@
1abbee
         <listitem><para>These settings control various default
1abbee
         resource limits for units. See
1abbee
         <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry>
1abbee
-        for details. Use the string <varname>infinity</varname> to
1abbee
-        configure no limit on a specific resource. The multiplicative suffixes
1abbee
-        K (=1024), M (=1024*1024) and so on for G, T, P and E may be used for
1abbee
-        resource limits measured in bytes (e.g. DefaultLimitAS=16G). These
1abbee
-        settings may be overridden in individual units using the corresponding
1abbee
-        LimitXXX= directives. Note that these resource limits are only
1abbee
-        defaults for units, they are not applied to PID 1
1abbee
+        for details. The resource limit is possible to specify in two formats,
1abbee
+        <option>value</option> to set soft and hard limits to the same value,
1abbee
+        or <option>soft:hard</option> to set both limits individually (e.g. DefaultLimitAS=4G:16G).
1abbee
+        Use the string <varname>infinity</varname> to
1abbee
+        configure no limit on a specific resource. The multiplicative
1abbee
+        suffixes K (=1024), M (=1024*1024) and so on for G, T, P and E
1abbee
+        may be used for resource limits measured in bytes
1abbee
+        (e.g. DefaultLimitAS=16G). For the limits referring to time values,
1abbee
+        the usual time units ms, s, min, h and so on may be used (see
1abbee
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
1abbee
+        for details). Note that if no time unit is specified for
1abbee
+        <varname>DefaultLimitCPU=</varname> the default unit of seconds is
1abbee
+        implied, while for <varname>DefaultLimitRTTIME=</varname> the default
1abbee
+        unit of microseconds is implied. Also, note that the effective
1abbee
+        granularity of the limits might influence their
1abbee
+        enforcement. For example, time limits specified for
1abbee
+        <varname>DefaultLimitCPU=</varname> will be rounded up implicitly to
1abbee
+        multiples of 1s. These  settings may be overridden in individual units
1abbee
+        using the corresponding LimitXXX= directives. Note that these resource
1abbee
+        limits are only defaults for units, they are not applied to PID 1
1abbee
         itself.</para></listitem>
1abbee
       </varlistentry>
1abbee
     </variablelist>
1abbee
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
181b3f
index cfdcc3d17..0cd469cd9 100644
1abbee
--- a/man/systemd.exec.xml
1abbee
+++ b/man/systemd.exec.xml
1abbee
@@ -558,7 +558,10 @@
1abbee
         <listitem><para>These settings set both soft and hard limits
1abbee
         of various resources for executed processes. See
1abbee
         <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry>
1abbee
-        for details. Use the string <varname>infinity</varname> to
1abbee
+        for details. The resource limit is possible to specify in two formats,
1abbee
+        <option>value</option> to set soft and hard limits to the same value,
1abbee
+        or <option>soft:hard</option> to set both limits individually (e.g. LimitAS=4G:16G).
1abbee
+        Use the string <varname>infinity</varname> to
1abbee
         configure no limit on a specific resource. The multiplicative
1abbee
         suffixes K (=1024), M (=1024*1024) and so on for G, T, P and E
1abbee
         may be used for resource limits measured in bytes
1abbee
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
181b3f
index 8afe9d7e8..d307f1c74 100644
1abbee
--- a/src/core/load-fragment.c
1abbee
+++ b/src/core/load-fragment.c
1abbee
@@ -1075,81 +1075,108 @@ int config_parse_bounding_set(const char *unit,
1abbee
         return 0;
1abbee
 }
1abbee
 
1abbee
-int config_parse_limit(const char *unit,
1abbee
-                       const char *filename,
1abbee
-                       unsigned line,
1abbee
-                       const char *section,
1abbee
-                       unsigned section_line,
1abbee
-                       const char *lvalue,
1abbee
-                       int ltype,
1abbee
-                       const char *rvalue,
1abbee
-                       void *data,
1abbee
-                       void *userdata) {
1abbee
 
1abbee
-        struct rlimit **rl = data;
1abbee
-        unsigned long long u;
1abbee
+static int rlim_parse_u64(const char *val, rlim_t *res) {
1abbee
+        int r = 0;
1abbee
 
1abbee
-        assert(filename);
1abbee
-        assert(lvalue);
1abbee
-        assert(rvalue);
1abbee
-        assert(data);
1abbee
+        if (streq(val, "infinity"))
1abbee
+                *res = RLIM_INFINITY;
1abbee
+        else {
1abbee
+                uint64_t u;
1abbee
 
1abbee
-        rl += ltype;
1abbee
+                /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */
1abbee
+                assert_cc(sizeof(rlim_t) == sizeof(uint64_t));
1abbee
+
1abbee
+                r = safe_atou64(val, &u);
1abbee
+                if (r >= 0 && u >= (uint64_t) RLIM_INFINITY)
1abbee
+                        r = -ERANGE;
1abbee
+                if (r == 0)
1abbee
+                        *res = (rlim_t) u;
1abbee
+        }
1abbee
+        return r;
1abbee
+}
1abbee
 
1abbee
-        if (streq(rvalue, "infinity"))
1abbee
-                u = (unsigned long long) RLIM_INFINITY;
1abbee
+static int rlim_parse_size(const char *val, rlim_t *res) {
1abbee
+        int r = 0;
1abbee
+
1abbee
+        if (streq(val, "infinity"))
1abbee
+                *res = RLIM_INFINITY;
1abbee
         else {
1abbee
-                int r;
1abbee
+                off_t u;
1abbee
 
1abbee
-                r = safe_atollu(rvalue, &u);
1abbee
-                if (r < 0) {
1abbee
-                        log_syntax(unit, LOG_ERR, filename, line, -r,
1abbee
-                                   "Failed to parse resource value, ignoring: %s", rvalue);
1abbee
-                        return 0;
1abbee
-                }
1abbee
+                r = parse_size(val, 1024, &u);
1abbee
+                if (r >= 0 && u >= (off_t) RLIM_INFINITY)
1abbee
+                        r = -ERANGE;
1abbee
+                if (r == 0)
1abbee
+                        *res = (rlim_t) u;
1abbee
         }
1abbee
+        return r;
1abbee
+}
1abbee
 
1abbee
-        if (!*rl) {
1abbee
-                *rl = new(struct rlimit, 1);
1abbee
-                if (!*rl)
1abbee
-                        return log_oom();
1abbee
-        }
1abbee
+static int rlim_parse_sec(const char *val, rlim_t *res) {
1abbee
+        int r = 0;
1abbee
 
1abbee
-        (*rl)->rlim_cur = (*rl)->rlim_max = (rlim_t) u;
1abbee
-        return 0;
1abbee
+        if (streq(val, "infinity"))
1abbee
+                *res = RLIM_INFINITY;
1abbee
+        else {
1abbee
+                usec_t t;
1abbee
+
1abbee
+                r = parse_sec(val, &t);
1abbee
+                if (r < 0)
1abbee
+                        return r;
1abbee
+                if (t == USEC_INFINITY)
1abbee
+                        *res = RLIM_INFINITY;
1abbee
+                else
1abbee
+                        *res = (rlim_t) (DIV_ROUND_UP(t, USEC_PER_SEC));
1abbee
+
1abbee
+        }
1abbee
+        return r;
1abbee
 }
1abbee
 
1abbee
-int config_parse_bytes_limit(const char *unit,
1abbee
-                       const char *filename,
1abbee
-                       unsigned line,
1abbee
-                       const char *section,
1abbee
-                       unsigned section_line,
1abbee
-                       const char *lvalue,
1abbee
-                       int ltype,
1abbee
-                       const char *rvalue,
1abbee
-                       void *data,
1abbee
-                       void *userdata) {
1abbee
+static int rlim_parse_usec(const char *val, rlim_t *res) {
1abbee
+        int r = 0;
1abbee
 
1abbee
-        struct rlimit **rl = data;
1abbee
-        uint64_t bytes;
1abbee
+        if (streq(val, "infinity"))
1abbee
+                *res = RLIM_INFINITY;
1abbee
+        else {
1abbee
+                usec_t t;
1abbee
 
1abbee
-        assert(filename);
1abbee
-        assert(lvalue);
1abbee
-        assert(rvalue);
1abbee
-        assert(data);
1abbee
+                r = parse_time(val, &t, 1);
1abbee
+                if (r < 0)
1abbee
+                        return r;
1abbee
+                if (t == USEC_INFINITY)
1abbee
+                        *res = RLIM_INFINITY;
1abbee
+                else
1abbee
+                        *res = (rlim_t) t;
1abbee
+        }
1abbee
+        return r;
1abbee
+}
1abbee
 
1abbee
-        rl += ltype;
1abbee
+static int parse_rlimit_range(
1abbee
+                const char *unit,
1abbee
+                const char *filename,
1abbee
+                unsigned line,
1abbee
+                const char *value,
1abbee
+                struct rlimit **rl,
1abbee
+                int (*rlim_parser)(const char *, rlim_t *)) {
1abbee
 
1abbee
-        if (streq(rvalue, "infinity"))
1abbee
-                bytes = (uint64_t) RLIM_INFINITY;
1abbee
-        else {
1abbee
-                int r;
1abbee
+        rlim_t soft, hard;
1abbee
+        _cleanup_free_ char *sword = NULL, *hword = NULL;
1abbee
+        int nwords, r;
1abbee
 
1abbee
-                r = parse_size(rvalue, 1024, &bytes);
1abbee
-                if (r < 0) {
1abbee
-                        log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue);
1abbee
-                        return 0;
1abbee
-                }
1abbee
+        assert(value);
1abbee
+
1abbee
+        /* <value> or <soft:hard> */
1abbee
+        nwords = extract_many_words(&value, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &sword, &hword, NULL);
1abbee
+        r = nwords < 0 ? nwords : nwords == 0 ? -EINVAL : 0;
1abbee
+
1abbee
+        if (r == 0)
1abbee
+                r = rlim_parser(sword, &soft);
1abbee
+        if (r == 0 && nwords == 2)
1abbee
+                r = rlim_parser(hword, &hard);
1abbee
+        if (r < 0) {
1abbee
+                log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", value);
1abbee
+                return 0;
1abbee
         }
1abbee
 
1abbee
         if (!*rl) {
1abbee
@@ -1157,12 +1184,12 @@ int config_parse_bytes_limit(const char *unit,
1abbee
                 if (!*rl)
1abbee
                         return log_oom();
1abbee
         }
1abbee
-
1abbee
-        (*rl)->rlim_cur = (*rl)->rlim_max = (rlim_t) bytes;
1abbee
+        (*rl)->rlim_cur = soft;
1abbee
+        (*rl)->rlim_max = nwords == 2 ? hard : soft;
1abbee
         return 0;
1abbee
 }
1abbee
 
1abbee
-int config_parse_sec_limit(
1abbee
+int config_parse_limit(
1abbee
                 const char *unit,
1abbee
                 const char *filename,
1abbee
                 unsigned line,
1abbee
@@ -1175,8 +1202,6 @@ int config_parse_sec_limit(
1abbee
                 void *userdata) {
1abbee
 
1abbee
         struct rlimit **rl = data;
1abbee
-        rlim_t seconds;
1abbee
-        int r;
1abbee
 
1abbee
         assert(filename);
1abbee
         assert(lvalue);
1abbee
@@ -1184,36 +1209,33 @@ int config_parse_sec_limit(
1abbee
         assert(data);
1abbee
 
1abbee
         rl += ltype;
1abbee
+        return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_u64);
1abbee
+}
1abbee
 
1abbee
-        if (streq(rvalue, "infinity"))
1abbee
-                seconds = RLIM_INFINITY;
1abbee
-        else {
1abbee
-                usec_t t;
1abbee
-
1abbee
-                r = parse_sec(rvalue, &t);
1abbee
-                if (r < 0) {
1abbee
-                        log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue);
1abbee
-                        return 0;
1abbee
-                }
1abbee
+int config_parse_bytes_limit(
1abbee
+                const char *unit,
1abbee
+                const char *filename,
1abbee
+                unsigned line,
1abbee
+                const char *section,
1abbee
+                unsigned section_line,
1abbee
+                const char *lvalue,
1abbee
+                int ltype,
1abbee
+                const char *rvalue,
1abbee
+                void *data,
1abbee
+                void *userdata) {
1abbee
 
1abbee
-                if (t == USEC_INFINITY)
1abbee
-                        seconds = RLIM_INFINITY;
1abbee
-                else
1abbee
-                        seconds = (rlim_t) (DIV_ROUND_UP(t, USEC_PER_SEC));
1abbee
-        }
1abbee
+        struct rlimit **rl = data;
1abbee
 
1abbee
-        if (!*rl) {
1abbee
-                *rl = new(struct rlimit, 1);
1abbee
-                if (!*rl)
1abbee
-                        return log_oom();
1abbee
-        }
1abbee
+        assert(filename);
1abbee
+        assert(lvalue);
1abbee
+        assert(rvalue);
1abbee
+        assert(data);
1abbee
 
1abbee
-        (*rl)->rlim_cur = (*rl)->rlim_max = seconds;
1abbee
-        return 0;
1abbee
+        rl += ltype;
1abbee
+        return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_size);
1abbee
 }
1abbee
 
1abbee
-
1abbee
-int config_parse_usec_limit(
1abbee
+int config_parse_sec_limit(
1abbee
                 const char *unit,
1abbee
                 const char *filename,
1abbee
                 unsigned line,
1abbee
@@ -1226,8 +1248,6 @@ int config_parse_usec_limit(
1abbee
                 void *userdata) {
1abbee
 
1abbee
         struct rlimit **rl = data;
1abbee
-        rlim_t useconds;
1abbee
-        int r;
1abbee
 
1abbee
         assert(filename);
1abbee
         assert(lvalue);
1abbee
@@ -1235,34 +1255,33 @@ int config_parse_usec_limit(
1abbee
         assert(data);
1abbee
 
1abbee
         rl += ltype;
1abbee
+        return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_sec);
1abbee
+}
1abbee
 
1abbee
-        if (streq(rvalue, "infinity"))
1abbee
-                useconds = RLIM_INFINITY;
1abbee
-        else {
1abbee
-                usec_t t;
1abbee
-
1abbee
-                r = parse_time(rvalue, &t, 1);
1abbee
-                if (r < 0) {
1abbee
-                        log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue);
1abbee
-                        return 0;
1abbee
-                }
1abbee
+int config_parse_usec_limit(
1abbee
+                const char *unit,
1abbee
+                const char *filename,
1abbee
+                unsigned line,
1abbee
+                const char *section,
1abbee
+                unsigned section_line,
1abbee
+                const char *lvalue,
1abbee
+                int ltype,
1abbee
+                const char *rvalue,
1abbee
+                void *data,
1abbee
+                void *userdata) {
1abbee
 
1abbee
-                if (t == USEC_INFINITY)
1abbee
-                        useconds = RLIM_INFINITY;
1abbee
-                else
1abbee
-                        useconds = (rlim_t) t;
1abbee
-        }
1abbee
+        struct rlimit **rl = data;
1abbee
 
1abbee
-        if (!*rl) {
1abbee
-                *rl = new(struct rlimit, 1);
1abbee
-                if (!*rl)
1abbee
-                        return log_oom();
1abbee
-        }
1abbee
+        assert(filename);
1abbee
+        assert(lvalue);
1abbee
+        assert(rvalue);
1abbee
+        assert(data);
1abbee
 
1abbee
-        (*rl)->rlim_cur = (*rl)->rlim_max = useconds;
1abbee
-        return 0;
1abbee
+        rl += ltype;
1abbee
+        return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_usec);
1abbee
 }
1abbee
 
1abbee
+
1abbee
 #ifdef HAVE_SYSV_COMPAT
1abbee
 int config_parse_sysv_priority(const char *unit,
1abbee
                                const char *filename,
1abbee
diff --git a/src/shared/util.c b/src/shared/util.c
181b3f
index 036677eb4..f75ed9dd4 100644
1abbee
--- a/src/shared/util.c
1abbee
+++ b/src/shared/util.c
1abbee
@@ -93,6 +93,7 @@
1abbee
 #include "virt.h"
1abbee
 #include "def.h"
1abbee
 #include "sparse-endian.h"
1abbee
+#include "conf-parser.h"
1abbee
 
1abbee
 int saved_argc = 0;
1abbee
 char **saved_argv = NULL;
1abbee
@@ -100,6 +101,8 @@ char **saved_argv = NULL;
1abbee
 static volatile unsigned cached_columns = 0;
1abbee
 static volatile unsigned cached_lines = 0;
1abbee
 
1abbee
+bool unichar_is_valid(int32_t ch);
1abbee
+
1abbee
 size_t page_size(void) {
1abbee
         static thread_local size_t pgsz = 0;
1abbee
         long r;
1abbee
@@ -1365,6 +1368,207 @@ char *cescape(const char *s) {
1abbee
         return r;
1abbee
 }
1abbee
 
1abbee
+bool unichar_is_valid(int32_t ch) {
1abbee
+
1abbee
+        if (ch >= 0x110000) /* End of unicode space */
1abbee
+                return false;
1abbee
+        if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */
1abbee
+                return false;
1abbee
+        if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */
1abbee
+                return false;
1abbee
+        if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */
1abbee
+                return false;
1abbee
+
1abbee
+        return true;
1abbee
+}
1abbee
+
1abbee
+int cunescape_one(const char *p, size_t length, int32_t *ret, bool *eight_bit) {
1abbee
+        int r = 1;
1abbee
+
1abbee
+        assert(p);
1abbee
+        assert(*p);
1abbee
+        assert(ret);
1abbee
+
1abbee
+        /* Unescapes C style. Returns the unescaped character in ret.
1abbee
+         * Sets *eight_bit to true if the escaped sequence either fits in
1abbee
+         * one byte in UTF-8 or is a non-unicode literal byte and should
1abbee
+         * instead be copied directly.
1abbee
+         */
1abbee
+
1abbee
+        if (length != (size_t) -1 && length < 1)
1abbee
+                return -EINVAL;
1abbee
+
1abbee
+        switch (p[0]) {
1abbee
+
1abbee
+        case 'a':
1abbee
+                *ret = '\a';
1abbee
+                break;
1abbee
+        case 'b':
1abbee
+                *ret = '\b';
1abbee
+                break;
1abbee
+        case 'f':
1abbee
+                *ret = '\f';
1abbee
+                break;
1abbee
+        case 'n':
1abbee
+                *ret = '\n';
1abbee
+                break;
1abbee
+        case 'r':
1abbee
+                *ret = '\r';
1abbee
+                break;
1abbee
+        case 't':
1abbee
+                *ret = '\t';
1abbee
+                break;
1abbee
+        case 'v':
1abbee
+                *ret = '\v';
1abbee
+                break;
1abbee
+        case '\\':
1abbee
+                *ret = '\\';
1abbee
+                break;
1abbee
+        case '"':
1abbee
+                *ret = '"';
1abbee
+                break;
1abbee
+        case '\'':
1abbee
+                *ret = '\'';
1abbee
+                break;
1abbee
+
1abbee
+        case 's':
1abbee
+                /* This is an extension of the XDG syntax files */
1abbee
+                *ret = ' ';
1abbee
+                break;
1abbee
+
1abbee
+        case 'x': {
1abbee
+                /* hexadecimal encoding */
1abbee
+                int a, b;
1abbee
+
1abbee
+                if (length != (size_t) -1 && length < 3)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                a = unhexchar(p[1]);
1abbee
+                if (a < 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                b = unhexchar(p[2]);
1abbee
+                if (b < 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                /* Don't allow NUL bytes */
1abbee
+                if (a == 0 && b == 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                *ret = (a << 4U) | b;
1abbee
+                *eight_bit = true;
1abbee
+                r = 3;
1abbee
+                break;
1abbee
+        }
1abbee
+
1abbee
+        case 'u': {
1abbee
+                /* C++11 style 16bit unicode */
1abbee
+
1abbee
+                int a[4];
1abbee
+                unsigned i;
1abbee
+                uint32_t c;
1abbee
+
1abbee
+                if (length != (size_t) -1 && length < 5)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                for (i = 0; i < 4; i++) {
1abbee
+                        a[i] = unhexchar(p[1 + i]);
1abbee
+                        if (a[i] < 0)
1abbee
+                                return a[i];
1abbee
+                }
1abbee
+
1abbee
+                c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
1abbee
+
1abbee
+                /* Don't allow 0 chars */
1abbee
+                if (c == 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                *ret = c;
1abbee
+                r = 5;
1abbee
+                break;
1abbee
+        }
1abbee
+
1abbee
+        case 'U': {
1abbee
+                /* C++11 style 32bit unicode */
1abbee
+
1abbee
+                int a[8];
1abbee
+                unsigned i;
1abbee
+                int32_t c;
1abbee
+
1abbee
+                if (length != (size_t) -1 && length < 9)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                for (i = 0; i < 8; i++) {
1abbee
+                        a[i] = unhexchar(p[1 + i]);
1abbee
+                        if (a[i] < 0)
1abbee
+                                return a[i];
1abbee
+                }
1abbee
+
1abbee
+                c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) |
1abbee
+                    ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] <<  8U) | ((uint32_t) a[6] <<  4U) |  (uint32_t) a[7];
1abbee
+
1abbee
+                /* Don't allow 0 chars */
1abbee
+                if (c == 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                /* Don't allow invalid code points */
1abbee
+                if (!unichar_is_valid(c))
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                *ret = c;
1abbee
+                r = 9;
1abbee
+                break;
1abbee
+        }
1abbee
+
1abbee
+        case '0':
1abbee
+        case '1':
1abbee
+        case '2':
1abbee
+        case '3':
1abbee
+        case '4':
1abbee
+        case '5':
1abbee
+        case '6':
1abbee
+        case '7': {
1abbee
+                /* octal encoding */
1abbee
+                int a, b, c;
1abbee
+                int32_t m;
1abbee
+
1abbee
+                if (length != (size_t) -1 && length < 3)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                a = unoctchar(p[0]);
1abbee
+                if (a < 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                b = unoctchar(p[1]);
1abbee
+                if (b < 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                c = unoctchar(p[2]);
1abbee
+                if (c < 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                /* don't allow NUL bytes */
1abbee
+                if (a == 0 && b == 0 && c == 0)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                /* Don't allow bytes above 255 */
1abbee
+                m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c;
1abbee
+                if (m > 255)
1abbee
+                        return -EINVAL;
1abbee
+
1abbee
+                *ret = m;
1abbee
+                *eight_bit = true;
1abbee
+                r = 3;
1abbee
+                break;
1abbee
+        }
1abbee
+
1abbee
+        default:
1abbee
+                return -EINVAL;
1abbee
+        }
1abbee
+
1abbee
+        return r;
1abbee
+}
1abbee
+
1abbee
 char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix) {
1abbee
         char *r, *t;
1abbee
         const char *f;
1abbee
@@ -8207,3 +8411,266 @@ bool colors_enabled(void) {
1abbee
 
1abbee
         return parse_boolean(colors) != 0;
1abbee
 }
1abbee
+
1abbee
+int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) {
1abbee
+        _cleanup_free_ char *s = NULL;
1abbee
+        size_t allocated = 0, sz = 0;
1abbee
+        char c;
1abbee
+        int r;
1abbee
+
1abbee
+        char quote = 0;                 /* 0 or ' or " */
1abbee
+        bool backslash = false;         /* whether we've just seen a backslash */
1abbee
+
1abbee
+        assert(p);
1abbee
+        assert(ret);
1abbee
+
1abbee
+        /* Bail early if called after last value or with no input */
1abbee
+        if (!*p)
1abbee
+                goto finish_force_terminate;
1abbee
+        c = **p;
1abbee
+
1abbee
+        if (!separators)
1abbee
+                separators = WHITESPACE;
1abbee
+
1abbee
+        /* Parses the first word of a string, and returns it in
1abbee
+         * *ret. Removes all quotes in the process. When parsing fails
1abbee
+         * (because of an uneven number of quotes or similar), leaves
1abbee
+         * the pointer *p at the first invalid character. */
1abbee
+
1abbee
+        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS)
1abbee
+                if (!GREEDY_REALLOC(s, allocated, sz+1))
1abbee
+                        return -ENOMEM;
1abbee
+
1abbee
+        for (;; (*p)++, c = **p) {
1abbee
+                if (c == 0)
1abbee
+                        goto finish_force_terminate;
1abbee
+                else if (strchr(separators, c)) {
1abbee
+                        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
1abbee
+                                (*p)++;
1abbee
+                                goto finish_force_next;
1abbee
+                        }
1abbee
+                } else {
1abbee
+                        /* We found a non-blank character, so we will always
1abbee
+                         * want to return a string (even if it is empty),
1abbee
+                         * allocate it here. */
1abbee
+                        if (!GREEDY_REALLOC(s, allocated, sz+1))
1abbee
+                                return -ENOMEM;
1abbee
+                        break;
1abbee
+                }
1abbee
+        }
1abbee
+
1abbee
+        for (;; (*p)++, c = **p) {
1abbee
+                if (backslash) {
1abbee
+                        if (!GREEDY_REALLOC(s, allocated, sz+7))
1abbee
+                                return -ENOMEM;
1abbee
+
1abbee
+                        if (c == 0) {
1abbee
+                                if ((flags & EXTRACT_CUNESCAPE_RELAX) &&
1abbee
+                                    (!quote || flags & EXTRACT_RELAX)) {
1abbee
+                                        /* If we find an unquoted trailing backslash and we're in
1abbee
+                                         * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the
1abbee
+                                         * output.
1abbee
+                                         *
1abbee
+                                         * Unbalanced quotes will only be allowed in EXTRACT_RELAX
1abbee
+                                         * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them.
1abbee
+                                         */
1abbee
+                                        s[sz++] = '\\';
1abbee
+                                        goto finish_force_terminate;
1abbee
+                                }
1abbee
+                                if (flags & EXTRACT_RELAX)
1abbee
+                                        goto finish_force_terminate;
1abbee
+                                return -EINVAL;
1abbee
+                        }
1abbee
+
1abbee
+                        if (flags & EXTRACT_CUNESCAPE) {
1abbee
+                                bool eight_bit = false;
1abbee
+                                int32_t u;
1abbee
+
1abbee
+                                r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
1abbee
+                                if (r < 0) {
1abbee
+                                        if (flags & EXTRACT_CUNESCAPE_RELAX) {
1abbee
+                                                s[sz++] = '\\';
1abbee
+                                                s[sz++] = c;
1abbee
+                                        } else
1abbee
+                                                return -EINVAL;
1abbee
+                                } else {
1abbee
+                                        (*p) += r - 1;
1abbee
+
1abbee
+                                        if (eight_bit)
1abbee
+                                                s[sz++] = u;
1abbee
+                                        else
1abbee
+                                                sz += utf8_encode_unichar(s + sz, u);
1abbee
+                                }
1abbee
+                        } else
1abbee
+                                s[sz++] = c;
1abbee
+
1abbee
+                        backslash = false;
1abbee
+
1abbee
+                } else if (quote) {     /* inside either single or double quotes */
1abbee
+                        for (;; (*p)++, c = **p) {
1abbee
+                                if (c == 0) {
1abbee
+                                        if (flags & EXTRACT_RELAX)
1abbee
+                                                goto finish_force_terminate;
1abbee
+                                        return -EINVAL;
1abbee
+                                } else if (c == quote) {        /* found the end quote */
1abbee
+                                        quote = 0;
1abbee
+                                        break;
1abbee
+                                } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
1abbee
+                                        backslash = true;
1abbee
+                                        break;
1abbee
+                                } else {
1abbee
+                                        if (!GREEDY_REALLOC(s, allocated, sz+2))
1abbee
+                                                return -ENOMEM;
1abbee
+
1abbee
+                                        s[sz++] = c;
1abbee
+                                }
1abbee
+                        }
1abbee
+
1abbee
+                } else {
1abbee
+                        for (;; (*p)++, c = **p) {
1abbee
+                                if (c == 0)
1abbee
+                                        goto finish_force_terminate;
1abbee
+                                else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES)) {
1abbee
+                                        quote = c;
1abbee
+                                        break;
1abbee
+                                } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) {
1abbee
+                                        backslash = true;
1abbee
+                                        break;
1abbee
+                                } else if (strchr(separators, c)) {
1abbee
+                                        if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
1abbee
+                                                (*p)++;
1abbee
+                                                goto finish_force_next;
1abbee
+                                        }
1abbee
+                                        /* Skip additional coalesced separators. */
1abbee
+                                        for (;; (*p)++, c = **p) {
1abbee
+                                                if (c == 0)
1abbee
+                                                        goto finish_force_terminate;
1abbee
+                                                if (!strchr(separators, c))
1abbee
+                                                        break;
1abbee
+                                        }
1abbee
+                                        goto finish;
1abbee
+
1abbee
+                                } else {
1abbee
+                                        if (!GREEDY_REALLOC(s, allocated, sz+2))
1abbee
+                                                return -ENOMEM;
1abbee
+
1abbee
+                                        s[sz++] = c;
1abbee
+                                }
1abbee
+                        }
1abbee
+                }
1abbee
+        }
1abbee
+
1abbee
+finish_force_terminate:
1abbee
+        *p = NULL;
1abbee
+finish:
1abbee
+        if (!s) {
1abbee
+                *p = NULL;
1abbee
+                *ret = NULL;
1abbee
+                return 0;
1abbee
+        }
1abbee
+
1abbee
+finish_force_next:
1abbee
+        s[sz] = 0;
1abbee
+        *ret = s;
1abbee
+        s = NULL;
1abbee
+
1abbee
+        return 1;
1abbee
+}
1abbee
+
1abbee
+int extract_first_word_and_warn(
1abbee
+                const char **p,
1abbee
+                char **ret,
1abbee
+                const char *separators,
1abbee
+                ExtractFlags flags,
1abbee
+                const char *unit,
1abbee
+                const char *filename,
1abbee
+                unsigned line,
1abbee
+                const char *rvalue) {
1abbee
+
1abbee
+        /* Try to unquote it, if it fails, warn about it and try again
1abbee
+         * but this time using EXTRACT_CUNESCAPE_RELAX to keep the
1abbee
+         * backslashes verbatim in invalid escape sequences. */
1abbee
+
1abbee
+        const char *save;
1abbee
+        int r;
1abbee
+
1abbee
+        save = *p;
1abbee
+        r = extract_first_word(p, ret, separators, flags);
1abbee
+        if (r >= 0)
1abbee
+                return r;
1abbee
+
1abbee
+        if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) {
1abbee
+
1abbee
+                /* Retry it with EXTRACT_CUNESCAPE_RELAX. */
1abbee
+                *p = save;
1abbee
+                r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX);
1abbee
+                if (r >= 0) {
1abbee
+                        /* It worked this time, hence it must have been an invalid escape sequence we could correct. */
1abbee
+                        log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid escape sequences in line, correcting: \"%s\"", rvalue);
1abbee
+                        return r;
1abbee
+                }
1abbee
+
1abbee
+                /* If it's still EINVAL; then it must be unbalanced quoting, report this. */
1abbee
+                if (r == -EINVAL)
1abbee
+                        return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue);
1abbee
+        }
1abbee
+
1abbee
+        /* Can be any error, report it */
1abbee
+        return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue);
1abbee
+}
1abbee
+
1abbee
+int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) {
1abbee
+        va_list ap;
1abbee
+        char **l;
1abbee
+        int n = 0, i, c, r;
1abbee
+
1abbee
+        /* Parses a number of words from a string, stripping any
1abbee
+         * quotes if necessary. */
1abbee
+
1abbee
+        assert(p);
1abbee
+
1abbee
+        /* Count how many words are expected */
1abbee
+        va_start(ap, flags);
1abbee
+        for (;;) {
1abbee
+                if (!va_arg(ap, char **))
1abbee
+                        break;
1abbee
+                n++;
1abbee
+        }
1abbee
+        va_end(ap);
1abbee
+
1abbee
+        if (n <= 0)
1abbee
+                return 0;
1abbee
+
1abbee
+        /* Read all words into a temporary array */
1abbee
+        l = newa0(char*, n);
1abbee
+        for (c = 0; c < n; c++) {
1abbee
+
1abbee
+                r = extract_first_word(p, &l[c], separators, flags);
1abbee
+                if (r < 0) {
1abbee
+                        int j;
1abbee
+
1abbee
+                        for (j = 0; j < c; j++)
1abbee
+                                free(l[j]);
1abbee
+
1abbee
+                        return r;
1abbee
+                }
1abbee
+
1abbee
+                if (r == 0)
1abbee
+                        break;
1abbee
+        }
1abbee
+
1abbee
+        /* If we managed to parse all words, return them in the passed
1abbee
+         * in parameters */
1abbee
+        va_start(ap, flags);
1abbee
+        for (i = 0; i < n; i++) {
1abbee
+                char **v;
1abbee
+
1abbee
+                v = va_arg(ap, char **);
1abbee
+                assert(v);
1abbee
+
1abbee
+                *v = l[i];
1abbee
+        }
1abbee
+        va_end(ap);
1abbee
+
1abbee
+        return c;
1abbee
+}
1abbee
diff --git a/src/shared/util.h b/src/shared/util.h
181b3f
index a441e44ff..be04524cc 100644
1abbee
--- a/src/shared/util.h
1abbee
+++ b/src/shared/util.h
1abbee
@@ -315,6 +315,7 @@ int undecchar(char c) _const_;
1abbee
 char *cescape(const char *s);
1abbee
 char *cunescape(const char *s);
1abbee
 char *cunescape_length(const char *s, size_t length);
1abbee
+int cunescape_one(const char *p, size_t length, int32_t *ret, bool *eight_bit);
1abbee
 char *cunescape_length_with_prefix(const char *s, size_t length, const char *prefix);
1abbee
 
1abbee
 char *xescape(const char *s, const char *bad);
1abbee
@@ -1082,3 +1083,16 @@ void sigkill_wait(pid_t *pid);
1abbee
 int syslog_parse_priority(const char **p, int *priority, bool with_facility);
1abbee
 
1abbee
 char *shell_maybe_quote(const char *s);
1abbee
+
1abbee
+typedef enum ExtractFlags {
1abbee
+        EXTRACT_RELAX                    = 1,
1abbee
+        EXTRACT_CUNESCAPE                = 2,
1abbee
+        EXTRACT_CUNESCAPE_RELAX          = 4,
1abbee
+        EXTRACT_QUOTES                   = 8,
1abbee
+        EXTRACT_DONT_COALESCE_SEPARATORS = 16,
1abbee
+        EXTRACT_RETAIN_ESCAPE            = 32,
1abbee
+} ExtractFlags;
1abbee
+
1abbee
+int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags);
1abbee
+int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue);
1abbee
+int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_;
1abbee
diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c
181b3f
index 87c81ccd7..931dfeda8 100644
1abbee
--- a/src/test/test-unit-file.c
1abbee
+++ b/src/test/test-unit-file.c
1abbee
@@ -554,11 +554,22 @@ static void test_config_parse_rlimit(void) {
1abbee
         assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 55);
1abbee
         assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max);
1abbee
 
1abbee
+
1abbee
+        assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "55:66", rl, NULL) >= 0);
1abbee
+        assert_se(rl[RLIMIT_NOFILE]);
1abbee
+        assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 55);
1abbee
+        assert_se(rl[RLIMIT_NOFILE]->rlim_max == 66);
1abbee
+
1abbee
         assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity", rl, NULL) >= 0);
1abbee
         assert_se(rl[RLIMIT_NOFILE]);
1abbee
         assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY);
1abbee
         assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max);
1abbee
 
1abbee
+        assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity:infinity", rl, NULL) >= 0);
1abbee
+        assert_se(rl[RLIMIT_NOFILE]);
1abbee
+        assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY);
1abbee
+        assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max);
1abbee
+
1abbee
         free(rl[RLIMIT_NOFILE]);
1abbee
         assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "56", rl, NULL) >= 0);
1abbee
         assert_se(rl[RLIMIT_CPU]);
1abbee
@@ -570,6 +581,11 @@ static void test_config_parse_rlimit(void) {
1abbee
         assert_se(rl[RLIMIT_CPU]->rlim_cur == 57);
1abbee
         assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max);
1abbee
 
1abbee
+        assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "40s:1m", rl, NULL) >= 0);
1abbee
+        assert_se(rl[RLIMIT_CPU]);
1abbee
+        assert_se(rl[RLIMIT_CPU]->rlim_cur == 40);
1abbee
+        assert_se(rl[RLIMIT_CPU]->rlim_max == 60);
1abbee
+
1abbee
         assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "infinity", rl, NULL) >= 0);
1abbee
         assert_se(rl[RLIMIT_CPU]);
1abbee
         assert_se(rl[RLIMIT_CPU]->rlim_cur == RLIM_INFINITY);
1abbee
@@ -587,16 +603,31 @@ static void test_config_parse_rlimit(void) {
1abbee
         assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58);
1abbee
         assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max);
1abbee
 
1abbee
+        assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58:60", rl, NULL) >= 0);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]->rlim_max == 60);
1abbee
+
1abbee
         assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s", rl, NULL) >= 0);
1abbee
         assert_se(rl[RLIMIT_RTTIME]);
1abbee
         assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC);
1abbee
         assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max);
1abbee
 
1abbee
+        assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s:123s", rl, NULL) >= 0);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]->rlim_max == 123 * USEC_PER_SEC);
1abbee
+
1abbee
         assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity", rl, NULL) >= 0);
1abbee
         assert_se(rl[RLIMIT_RTTIME]);
1abbee
         assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY);
1abbee
         assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max);
1abbee
 
1abbee
+        assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity:infinity", rl, NULL) >= 0);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY);
1abbee
+        assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max);
1abbee
+
1abbee
         assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "2345ms", rl, NULL) >= 0);
1abbee
         assert_se(rl[RLIMIT_RTTIME]);
1abbee
         assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 2345 * USEC_PER_MSEC);