a19bc6
From c56c1f6c2b683d6f20a7e8caeecec6c3cb76798f Mon Sep 17 00:00:00 2001
a19bc6
From: Lukas Nykryn <lnykryn@redhat.com>
a19bc6
Date: Tue, 3 Jan 2017 14:21:25 +0100
a19bc6
Subject: [PATCH] core: make parsing of RLIMIT_NICE aware of actual nice levels
a19bc6
a19bc6
RHEL-only
a19bc6
(most of code taken from 29857001854a02c292f1f3b324e7a66831e859c8)
a19bc6
a19bc6
Resolves: #1409588
a19bc6
---
a19bc6
 man/systemd.exec.xml                  |  7 +++-
a19bc6
 src/core/load-fragment-gperf.gperf.m4 |  2 +-
a19bc6
 src/core/load-fragment.c              | 72 +++++++++++++++++++++++++++++++++++
a19bc6
 src/core/load-fragment.h              |  1 +
a19bc6
 src/core/main.c                       |  2 +-
a19bc6
 5 files changed, 81 insertions(+), 3 deletions(-)
a19bc6
a19bc6
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
181b3f
index 0cd469cd9..c5199d3a5 100644
a19bc6
--- a/man/systemd.exec.xml
a19bc6
+++ b/man/systemd.exec.xml
a19bc6
@@ -575,7 +575,12 @@
a19bc6
         granularity of the limits might influence their
a19bc6
         enforcement. For example, time limits specified for
a19bc6
         <varname>LimitCPU=</varname> will be rounded up implicitly to
a19bc6
-        multiples of 1s.</para>
a19bc6
+        multiples of 1s. For <varname>LimitNICE=</varname> the value
a19bc6
+        may be specified in two syntaxes: if prefixed with <literal>+</literal>
a19bc6
+        or <literal>-</literal>, the value is understood as regular Linux
a19bc6
+        nice value in the range -20..19. If not prefixed like this the value
a19bc6
+        is understood as raw resource limit parameter in the range 0..40 (with 0 being
a19bc6
+        equivalent to 1).</para>
a19bc6
 
a19bc6
         <para>Note that most process resource limits configured with
a19bc6
         these options are per-process, and processes may fork in order
a19bc6
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
181b3f
index 45d1ead45..f3a6e13d9 100644
a19bc6
--- a/src/core/load-fragment-gperf.gperf.m4
a19bc6
+++ b/src/core/load-fragment-gperf.gperf.m4
a19bc6
@@ -71,7 +71,7 @@ $1.LimitMEMLOCK,                 config_parse_bytes_limit,           RLIMIT_MEML
a19bc6
 $1.LimitLOCKS,                   config_parse_limit,                 RLIMIT_LOCKS,                  offsetof($1, exec_context.rlimit)
a19bc6
 $1.LimitSIGPENDING,              config_parse_limit,                 RLIMIT_SIGPENDING,             offsetof($1, exec_context.rlimit)
a19bc6
 $1.LimitMSGQUEUE,                config_parse_bytes_limit,           RLIMIT_MSGQUEUE,               offsetof($1, exec_context.rlimit)
a19bc6
-$1.LimitNICE,                    config_parse_limit,                 RLIMIT_NICE,                   offsetof($1, exec_context.rlimit)
a19bc6
+$1.LimitNICE,                    config_parse_nice_limit,            RLIMIT_NICE,                   offsetof($1, exec_context.rlimit)
a19bc6
 $1.LimitRTPRIO,                  config_parse_limit,                 RLIMIT_RTPRIO,                 offsetof($1, exec_context.rlimit)
a19bc6
 $1.LimitRTTIME,                  config_parse_usec_limit,            RLIMIT_RTTIME,                 offsetof($1, exec_context.rlimit)
a19bc6
 $1.ReadWriteDirectories,         config_parse_namespace_path_strv,   0,                             offsetof($1, exec_context.read_write_dirs)
a19bc6
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
181b3f
index 705641971..3a3c456da 100644
a19bc6
--- a/src/core/load-fragment.c
a19bc6
+++ b/src/core/load-fragment.c
a19bc6
@@ -1154,6 +1154,56 @@ static int rlim_parse_usec(const char *val, rlim_t *res) {
a19bc6
         return r;
a19bc6
 }
a19bc6
 
a19bc6
+static int rlim_parse_nice(const char *val, rlim_t *ret) {
a19bc6
+        uint64_t rl;
a19bc6
+        int r;
a19bc6
+
a19bc6
+        /* So, Linux is weird. The range for RLIMIT_NICE is 40..1, mapping to the nice levels -20..19. However, the
a19bc6
+         * RLIMIT_NICE limit defaults to 0 by the kernel, i.e. a value that maps to nice level 20, which of course is
a19bc6
+         * bogus and does not exist. In order to permit parsing the RLIMIT_NICE of 0 here we hence implement a slight
a19bc6
+         * asymmetry: when parsing as positive nice level we permit 0..19. When parsing as negative nice level, we
a19bc6
+         * permit -20..0. But when parsing as raw resource limit value then we also allow the special value 0.
a19bc6
+         *
a19bc6
+         * Yeah, Linux is quality engineering sometimes... */
a19bc6
+
a19bc6
+        if (val[0] == '+') {
a19bc6
+
a19bc6
+                /* Prefixed with "+": Parse as positive user-friendly nice value */
a19bc6
+                r = safe_atou64(val + 1, &rl);
a19bc6
+                if (r < 0)
a19bc6
+                        return r;
a19bc6
+
a19bc6
+                if (rl >= PRIO_MAX)
a19bc6
+                        return -ERANGE;
a19bc6
+
a19bc6
+                rl = 20 - rl;
a19bc6
+
a19bc6
+        } else if (val[0] == '-') {
a19bc6
+
a19bc6
+                /* Prefixed with "-": Parse as negative user-friendly nice value */
a19bc6
+                r = safe_atou64(val + 1, &rl);
a19bc6
+                if (r < 0)
a19bc6
+                        return r;
a19bc6
+
a19bc6
+                if (rl > (uint64_t) (-PRIO_MIN))
a19bc6
+                        return -ERANGE;
a19bc6
+
a19bc6
+                rl = 20 + rl;
a19bc6
+        } else {
a19bc6
+
a19bc6
+                /* Not prefixed: parse as raw resource limit value */
a19bc6
+                r = safe_atou64(val, &rl);
a19bc6
+                if (r < 0)
a19bc6
+                        return r;
a19bc6
+
a19bc6
+                if (rl > (uint64_t) (20 - PRIO_MIN))
a19bc6
+                        return -ERANGE;
a19bc6
+        }
a19bc6
+
a19bc6
+        *ret = (rlim_t) rl;
a19bc6
+        return 0;
a19bc6
+}
a19bc6
+
a19bc6
 static int parse_rlimit_range(
a19bc6
                 const char *unit,
a19bc6
                 const char *filename,
a19bc6
@@ -1286,6 +1336,28 @@ int config_parse_usec_limit(
a19bc6
         return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_usec);
a19bc6
 }
a19bc6
 
a19bc6
+int config_parse_nice_limit(
a19bc6
+                const char *unit,
a19bc6
+                const char *filename,
a19bc6
+                unsigned line,
a19bc6
+                const char *section,
a19bc6
+                unsigned section_line,
a19bc6
+                const char *lvalue,
a19bc6
+                int ltype,
a19bc6
+                const char *rvalue,
a19bc6
+                void *data,
a19bc6
+                void *userdata) {
a19bc6
+
a19bc6
+        struct rlimit **rl = data;
a19bc6
+
a19bc6
+        assert(filename);
a19bc6
+        assert(lvalue);
a19bc6
+        assert(rvalue);
a19bc6
+        assert(data);
a19bc6
+
a19bc6
+        rl += ltype;
a19bc6
+        return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_nice);
a19bc6
+}
a19bc6
 
a19bc6
 #ifdef HAVE_SYSV_COMPAT
a19bc6
 int config_parse_sysv_priority(const char *unit,
a19bc6
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
181b3f
index 611479612..7c69e5369 100644
a19bc6
--- a/src/core/load-fragment.h
a19bc6
+++ b/src/core/load-fragment.h
a19bc6
@@ -59,6 +59,7 @@ int config_parse_limit(const char *unit, const char *filename, unsigned line, co
a19bc6
 int config_parse_bytes_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_sec_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_usec_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
+int config_parse_nice_limit(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_sysv_priority(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_kill_signal(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
 int config_parse_exec_mount_flags(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata);
a19bc6
diff --git a/src/core/main.c b/src/core/main.c
181b3f
index 6f8367632..820cbc3e5 100644
a19bc6
--- a/src/core/main.c
a19bc6
+++ b/src/core/main.c
a19bc6
@@ -669,7 +669,7 @@ static int parse_config_file(void) {
a19bc6
                 { "Manager", "DefaultLimitLOCKS",         config_parse_limit,            0, &arg_default_rlimit[RLIMIT_LOCKS]      },
a19bc6
                 { "Manager", "DefaultLimitSIGPENDING",    config_parse_limit,            0, &arg_default_rlimit[RLIMIT_SIGPENDING] },
a19bc6
                 { "Manager", "DefaultLimitMSGQUEUE",      config_parse_bytes_limit,      0, &arg_default_rlimit[RLIMIT_MSGQUEUE]   },
a19bc6
-                { "Manager", "DefaultLimitNICE",          config_parse_limit,            0, &arg_default_rlimit[RLIMIT_NICE]       },
a19bc6
+                { "Manager", "DefaultLimitNICE",          config_parse_nice_limit,       0, &arg_default_rlimit[RLIMIT_NICE]       },
a19bc6
                 { "Manager", "DefaultLimitRTPRIO",        config_parse_limit,            0, &arg_default_rlimit[RLIMIT_RTPRIO]     },
a19bc6
                 { "Manager", "DefaultLimitRTTIME",        config_parse_limit,            0, &arg_default_rlimit[RLIMIT_RTTIME]     },
a19bc6
                 { "Manager", "DefaultCPUAccounting",      config_parse_bool,             0, &arg_default_cpu_accounting            },