]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
util: move percent/permille/permyriad parser into percent-util.[ch]
authorLennart Poettering <lennart@poettering.net>
Wed, 17 Feb 2021 14:23:15 +0000 (15:23 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 18 Feb 2021 21:36:34 +0000 (22:36 +0100)
A good chunk of parse-util.[ch] has been about parsing parts per
hundred/thousand/ten-thousand. Let's split that out into its own file.

No code changes, just some shuffling around.

16 files changed:
src/basic/meson.build
src/basic/parse-util.c
src/basic/parse-util.h
src/basic/percent-util.c [new file with mode: 0644]
src/basic/percent-util.h [new file with mode: 0644]
src/core/load-fragment.c
src/home/homectl.c
src/import/importd.c
src/login/logind-user.c
src/login/pam_systemd.c
src/network/tc/tc-util.c
src/shared/bus-unit-util.c
src/shared/conf-parser.c
src/test/meson.build
src/test/test-parse-util.c
src/test/test-percent-util.c [new file with mode: 0644]

index 0b0982d54313bb6aa32b194e8c15218dc6cbcd53..88639bc9b531ba31ef990e891fd7252363d4ce37 100644 (file)
@@ -176,6 +176,8 @@ basic_sources = files('''
         path-lookup.h
         path-util.c
         path-util.h
+        percent-util.c
+        percent-util.h
         prioq.c
         prioq.h
         proc-cmdline.c
index 97d224f16545c12e099da65a44327d847930f589..b79c885dfd21292bf10115f413f8ae93aa0ad7f6 100644 (file)
@@ -627,146 +627,6 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) {
         return 0;
 }
 
-static int parse_parts_value_whole(const char *p, const char *symbol) {
-        const char *pc, *n;
-        int r, v;
-
-        pc = endswith(p, symbol);
-        if (!pc)
-                return -EINVAL;
-
-        n = strndupa(p, pc - p);
-        r = safe_atoi(n, &v);
-        if (r < 0)
-                return r;
-        if (v < 0)
-                return -ERANGE;
-
-        return v;
-}
-
-static int parse_parts_value_with_tenths_place(const char *p, const char *symbol) {
-        const char *pc, *dot, *n;
-        int r, q, v;
-
-        pc = endswith(p, symbol);
-        if (!pc)
-                return -EINVAL;
-
-        dot = memchr(p, '.', pc - p);
-        if (dot) {
-                if (dot + 2 != pc)
-                        return -EINVAL;
-                if (dot[1] < '0' || dot[1] > '9')
-                        return -EINVAL;
-                q = dot[1] - '0';
-                n = strndupa(p, dot - p);
-        } else {
-                q = 0;
-                n = strndupa(p, pc - p);
-        }
-        r = safe_atoi(n, &v);
-        if (r < 0)
-                return r;
-        if (v < 0)
-                return -ERANGE;
-        if (v > (INT_MAX - q) / 10)
-                return -ERANGE;
-
-        v = v * 10 + q;
-        return v;
-}
-
-static int parse_parts_value_with_hundredths_place(const char *p, const char *symbol) {
-        const char *pc, *dot, *n;
-        int r, q, v;
-
-        pc = endswith(p, symbol);
-        if (!pc)
-                return -EINVAL;
-
-        dot = memchr(p, '.', pc - p);
-        if (dot) {
-                if (dot + 3 != pc)
-                        return -EINVAL;
-                if (dot[1] < '0' || dot[1] > '9' || dot[2] < '0' || dot[2] > '9')
-                        return -EINVAL;
-                q = (dot[1] - '0') * 10 + (dot[2] - '0');
-                n = strndupa(p, dot - p);
-        } else {
-                q = 0;
-                n = strndupa(p, pc - p);
-        }
-        r = safe_atoi(n, &v);
-        if (r < 0)
-                return r;
-        if (v < 0)
-                return -ERANGE;
-        if (v > (INT_MAX - q) / 100)
-                return -ERANGE;
-
-        v = v * 100 + q;
-        return v;
-}
-
-int parse_percent_unbounded(const char *p) {
-        return parse_parts_value_whole(p, "%");
-}
-
-int parse_percent(const char *p) {
-        int v;
-
-        v = parse_percent_unbounded(p);
-        if (v > 100)
-                return -ERANGE;
-
-        return v;
-}
-
-int parse_permille_unbounded(const char *p) {
-        const char *pm;
-
-        pm = endswith(p, "‰");
-        if (pm)
-                return parse_parts_value_whole(p, "‰");
-
-        return parse_parts_value_with_tenths_place(p, "%");
-}
-
-int parse_permille(const char *p) {
-        int v;
-
-        v = parse_permille_unbounded(p);
-        if (v > 1000)
-                return -ERANGE;
-
-        return v;
-}
-
-int parse_permyriad_unbounded(const char *p) {
-        const char *pm;
-
-        pm = endswith(p, "‱");
-        if (pm)
-                return parse_parts_value_whole(p, "‱");
-
-        pm = endswith(p, "‰");
-        if (pm)
-                return parse_parts_value_with_tenths_place(p, "‰");
-
-        return parse_parts_value_with_hundredths_place(p, "%");
-}
-
-int parse_permyriad(const char *p) {
-        int v;
-
-        v = parse_permyriad_unbounded(p);
-        if (v > 10000)
-                return -ERANGE;
-
-        return v;
-}
-
 int parse_nice(const char *p, int *ret) {
         int n, r;
 
index 29e04cf5628f70a823a91a5763aa918a057cba41..908202dafd25bf02ab9651f2ad57dea0ebc72b97 100644 (file)
@@ -127,15 +127,6 @@ int safe_atod(const char *s, double *ret_d);
 
 int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
 
-int parse_percent_unbounded(const char *p);
-int parse_percent(const char *p);
-
-int parse_permille_unbounded(const char *p);
-int parse_permille(const char *p);
-
-int parse_permyriad_unbounded(const char *p);
-int parse_permyriad(const char *p);
-
 int parse_nice(const char *p, int *ret);
 
 int parse_ip_port(const char *s, uint16_t *ret);
diff --git a/src/basic/percent-util.c b/src/basic/percent-util.c
new file mode 100644 (file)
index 0000000..f58a51d
--- /dev/null
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "percent-util.h"
+#include "string-util.h"
+#include "parse-util.h"
+
+static int parse_parts_value_whole(const char *p, const char *symbol) {
+        const char *pc, *n;
+        int r, v;
+
+        pc = endswith(p, symbol);
+        if (!pc)
+                return -EINVAL;
+
+        n = strndupa(p, pc - p);
+        r = safe_atoi(n, &v);
+        if (r < 0)
+                return r;
+        if (v < 0)
+                return -ERANGE;
+
+        return v;
+}
+
+static int parse_parts_value_with_tenths_place(const char *p, const char *symbol) {
+        const char *pc, *dot, *n;
+        int r, q, v;
+
+        pc = endswith(p, symbol);
+        if (!pc)
+                return -EINVAL;
+
+        dot = memchr(p, '.', pc - p);
+        if (dot) {
+                if (dot + 2 != pc)
+                        return -EINVAL;
+                if (dot[1] < '0' || dot[1] > '9')
+                        return -EINVAL;
+                q = dot[1] - '0';
+                n = strndupa(p, dot - p);
+        } else {
+                q = 0;
+                n = strndupa(p, pc - p);
+        }
+        r = safe_atoi(n, &v);
+        if (r < 0)
+                return r;
+        if (v < 0)
+                return -ERANGE;
+        if (v > (INT_MAX - q) / 10)
+                return -ERANGE;
+
+        v = v * 10 + q;
+        return v;
+}
+
+static int parse_parts_value_with_hundredths_place(const char *p, const char *symbol) {
+        const char *pc, *dot, *n;
+        int r, q, v;
+
+        pc = endswith(p, symbol);
+        if (!pc)
+                return -EINVAL;
+
+        dot = memchr(p, '.', pc - p);
+        if (dot) {
+                if (dot + 3 != pc)
+                        return -EINVAL;
+                if (dot[1] < '0' || dot[1] > '9' || dot[2] < '0' || dot[2] > '9')
+                        return -EINVAL;
+                q = (dot[1] - '0') * 10 + (dot[2] - '0');
+                n = strndupa(p, dot - p);
+        } else {
+                q = 0;
+                n = strndupa(p, pc - p);
+        }
+        r = safe_atoi(n, &v);
+        if (r < 0)
+                return r;
+        if (v < 0)
+                return -ERANGE;
+        if (v > (INT_MAX - q) / 100)
+                return -ERANGE;
+
+        v = v * 100 + q;
+        return v;
+}
+
+int parse_percent_unbounded(const char *p) {
+        return parse_parts_value_whole(p, "%");
+}
+
+int parse_percent(const char *p) {
+        int v;
+
+        v = parse_percent_unbounded(p);
+        if (v > 100)
+                return -ERANGE;
+
+        return v;
+}
+
+int parse_permille_unbounded(const char *p) {
+        const char *pm;
+
+        pm = endswith(p, "‰");
+        if (pm)
+                return parse_parts_value_whole(p, "‰");
+
+        return parse_parts_value_with_tenths_place(p, "%");
+}
+
+int parse_permille(const char *p) {
+        int v;
+
+        v = parse_permille_unbounded(p);
+        if (v > 1000)
+                return -ERANGE;
+
+        return v;
+}
+
+int parse_permyriad_unbounded(const char *p) {
+        const char *pm;
+
+        pm = endswith(p, "‱");
+        if (pm)
+                return parse_parts_value_whole(p, "‱");
+
+        pm = endswith(p, "‰");
+        if (pm)
+                return parse_parts_value_with_tenths_place(p, "‰");
+
+        return parse_parts_value_with_hundredths_place(p, "%");
+}
+
+int parse_permyriad(const char *p) {
+        int v;
+
+        v = parse_permyriad_unbounded(p);
+        if (v > 10000)
+                return -ERANGE;
+
+        return v;
+}
diff --git a/src/basic/percent-util.h b/src/basic/percent-util.h
new file mode 100644 (file)
index 0000000..26707e8
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int parse_percent_unbounded(const char *p);
+int parse_percent(const char *p);
+
+int parse_permille_unbounded(const char *p);
+int parse_permille(const char *p);
+
+int parse_permyriad_unbounded(const char *p);
+int parse_permyriad(const char *p);
index a1ab5f70eb48703b8f6b90fecb4445c6f3e0b346..c56c2ffc618199503345d5b5fb966ff767667b56 100644 (file)
@@ -46,6 +46,7 @@
 #include "nulstr-util.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "percent-util.h"
 #include "process-util.h"
 #if HAVE_SECCOMP
 #include "seccomp-util.h"
@@ -3842,6 +3843,7 @@ int config_parse_managed_oom_mode(
                 const char *rvalue,
                 void *data,
                 void *userdata) {
+
         ManagedOOMMode *mode = data, m;
         UnitType t;
 
@@ -3861,6 +3863,7 @@ int config_parse_managed_oom_mode(
                 log_syntax(unit, LOG_WARNING, filename, line, m, "Invalid syntax, ignoring: %s", rvalue);
                 return 0;
         }
+
         *mode = m;
         return 0;
 }
@@ -3876,6 +3879,7 @@ int config_parse_managed_oom_mem_pressure_limit(
                 const char *rvalue,
                 void *data,
                 void *userdata) {
+
         uint32_t *limit = data;
         UnitType t;
         int r;
index 98835327cdb7660dc8fe8706a28aa06d8c9d3aa5..dbe5c3af209df3be6774286c7bb267f1cb74df6c 100644 (file)
@@ -26,6 +26,7 @@
 #include "parse-argument.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "percent-util.h"
 #include "pkcs11-util.h"
 #include "pretty-print.h"
 #include "process-util.h"
index 9ac2f8dcfe2e0a6d74f6b32f37d5cf4d3b03cb7c..f9e8481a6d73b7ca882efe271370fd674c308e35 100644 (file)
@@ -22,6 +22,7 @@
 #include "mkdir.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "percent-util.h"
 #include "process-util.h"
 #include "service-util.h"
 #include "signal-util.h"
index 4406f36614cdb418dff73f09879898590c65c4ab..a2c468e8dd0c1b73431ce0cec259670eecb010d3 100644 (file)
 #include "label.h"
 #include "limits-util.h"
 #include "logind-dbus.h"
-#include "logind-user.h"
 #include "logind-user-dbus.h"
+#include "logind-user.h"
 #include "mkdir.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "percent-util.h"
 #include "rm-rf.h"
 #include "serialize.h"
 #include "special.h"
index fce27262f151f178f1308a833f53c759c7c088ab..3fc4f43d288a9ca98db1a22a5bf87876a902fd20 100644 (file)
@@ -34,6 +34,7 @@
 #include "pam-util.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "percent-util.h"
 #include "process-util.h"
 #include "rlimit-util.h"
 #include "socket-util.h"
index 1890e9616432b3344be60107bbf768989a260163..3781182946453cae19576dbdd2d0f1cbe4ed090d 100644 (file)
@@ -5,6 +5,7 @@
 #include "extract-word.h"
 #include "fileio.h"
 #include "parse-util.h"
+#include "percent-util.h"
 #include "tc-util.h"
 #include "time-util.h"
 
index a1a253e80185025f549a0bba27d9a215a3da5402..a6e5e741326e92aa96ce8916957abe2a4a5c32b4 100644 (file)
@@ -28,6 +28,7 @@
 #include "numa-util.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "percent-util.h"
 #include "process-util.h"
 #include "rlimit-util.h"
 #if HAVE_SECCOMP
index c3d7e536ec088cbd955c310240080f7e1ecea3b6..df6472d4becc2e41153e9fa89c511f8277c22f7c 100644 (file)
@@ -22,6 +22,7 @@
 #include "nulstr-util.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "percent-util.h"
 #include "process-util.h"
 #include "rlimit-util.h"
 #include "sd-id128.h"
index 5bf1ca3490f5dc401a02510192aaaa6a8a507fde..c567a4cfcbb63911f0ddccf93aabb558d58e61af 100644 (file)
@@ -547,6 +547,8 @@ tests += [
 
         [['src/test/test-bus-util.c']],
 
+        [['src/test/test-percent-util.c']],
+
         [['src/test/test-sd-hwdb.c']],
 
         [['src/test/test-sd-path.c']],
index 6e23efe13482870c8f9c9d26aa5b0186167cc4ba..756934acad557b53e4e53c6355890a370033b656 100644 (file)
@@ -718,144 +718,6 @@ static void test_safe_atod(void) {
         assert_se(r == -EINVAL);
 }
 
-static void test_parse_percent(void) {
-        assert_se(parse_percent("") == -EINVAL);
-        assert_se(parse_percent("foo") == -EINVAL);
-        assert_se(parse_percent("0") == -EINVAL);
-        assert_se(parse_percent("50") == -EINVAL);
-        assert_se(parse_percent("100") == -EINVAL);
-        assert_se(parse_percent("-1") == -EINVAL);
-        assert_se(parse_percent("0%") == 0);
-        assert_se(parse_percent("55%") == 55);
-        assert_se(parse_percent("100%") == 100);
-        assert_se(parse_percent("-7%") == -ERANGE);
-        assert_se(parse_percent("107%") == -ERANGE);
-        assert_se(parse_percent("%") == -EINVAL);
-        assert_se(parse_percent("%%") == -EINVAL);
-        assert_se(parse_percent("%1") == -EINVAL);
-        assert_se(parse_percent("1%%") == -EINVAL);
-        assert_se(parse_percent("3.2%") == -EINVAL);
-}
-
-static void test_parse_percent_unbounded(void) {
-        assert_se(parse_percent_unbounded("101%") == 101);
-        assert_se(parse_percent_unbounded("400%") == 400);
-}
-
-static void test_parse_permille(void) {
-        assert_se(parse_permille("") == -EINVAL);
-        assert_se(parse_permille("foo") == -EINVAL);
-        assert_se(parse_permille("0") == -EINVAL);
-        assert_se(parse_permille("50") == -EINVAL);
-        assert_se(parse_permille("100") == -EINVAL);
-        assert_se(parse_permille("-1") == -EINVAL);
-
-        assert_se(parse_permille("0‰") == 0);
-        assert_se(parse_permille("555‰") == 555);
-        assert_se(parse_permille("1000‰") == 1000);
-        assert_se(parse_permille("-7‰") == -ERANGE);
-        assert_se(parse_permille("1007‰") == -ERANGE);
-        assert_se(parse_permille("‰") == -EINVAL);
-        assert_se(parse_permille("‰‰") == -EINVAL);
-        assert_se(parse_permille("‰1") == -EINVAL);
-        assert_se(parse_permille("1‰‰") == -EINVAL);
-        assert_se(parse_permille("3.2‰") == -EINVAL);
-
-        assert_se(parse_permille("0%") == 0);
-        assert_se(parse_permille("55%") == 550);
-        assert_se(parse_permille("55.5%") == 555);
-        assert_se(parse_permille("100%") == 1000);
-        assert_se(parse_permille("-7%") == -ERANGE);
-        assert_se(parse_permille("107%") == -ERANGE);
-        assert_se(parse_permille("%") == -EINVAL);
-        assert_se(parse_permille("%%") == -EINVAL);
-        assert_se(parse_permille("%1") == -EINVAL);
-        assert_se(parse_permille("1%%") == -EINVAL);
-        assert_se(parse_permille("3.21%") == -EINVAL);
-}
-
-static void test_parse_permille_unbounded(void) {
-        assert_se(parse_permille_unbounded("1001‰") == 1001);
-        assert_se(parse_permille_unbounded("4000‰") == 4000);
-        assert_se(parse_permille_unbounded("2147483647‰") == 2147483647);
-        assert_se(parse_permille_unbounded("2147483648‰") == -ERANGE);
-        assert_se(parse_permille_unbounded("4294967295‰") == -ERANGE);
-        assert_se(parse_permille_unbounded("4294967296‰") == -ERANGE);
-
-        assert_se(parse_permille_unbounded("101%") == 1010);
-        assert_se(parse_permille_unbounded("400%") == 4000);
-        assert_se(parse_permille_unbounded("214748364.7%") == 2147483647);
-        assert_se(parse_permille_unbounded("214748364.8%") == -ERANGE);
-        assert_se(parse_permille_unbounded("429496729.5%") == -ERANGE);
-        assert_se(parse_permille_unbounded("429496729.6%") == -ERANGE);
-}
-
-static void test_parse_permyriad(void) {
-        assert_se(parse_permyriad("") == -EINVAL);
-        assert_se(parse_permyriad("foo") == -EINVAL);
-        assert_se(parse_permyriad("0") == -EINVAL);
-        assert_se(parse_permyriad("50") == -EINVAL);
-        assert_se(parse_permyriad("100") == -EINVAL);
-        assert_se(parse_permyriad("-1") == -EINVAL);
-
-        assert_se(parse_permyriad("0‱") == 0);
-        assert_se(parse_permyriad("555‱") == 555);
-        assert_se(parse_permyriad("1000‱") == 1000);
-        assert_se(parse_permyriad("-7‱") == -ERANGE);
-        assert_se(parse_permyriad("10007‱") == -ERANGE);
-        assert_se(parse_permyriad("‱") == -EINVAL);
-        assert_se(parse_permyriad("‱‱") == -EINVAL);
-        assert_se(parse_permyriad("‱1") == -EINVAL);
-        assert_se(parse_permyriad("1‱‱") == -EINVAL);
-        assert_se(parse_permyriad("3.2‱") == -EINVAL);
-
-        assert_se(parse_permyriad("0‰") == 0);
-        assert_se(parse_permyriad("555.5‰") == 5555);
-        assert_se(parse_permyriad("1000.0‰") == 10000);
-        assert_se(parse_permyriad("-7‰") == -ERANGE);
-        assert_se(parse_permyriad("1007‰") == -ERANGE);
-        assert_se(parse_permyriad("‰") == -EINVAL);
-        assert_se(parse_permyriad("‰‰") == -EINVAL);
-        assert_se(parse_permyriad("‰1") == -EINVAL);
-        assert_se(parse_permyriad("1‰‰") == -EINVAL);
-        assert_se(parse_permyriad("3.22‰") == -EINVAL);
-
-        assert_se(parse_permyriad("0%") == 0);
-        assert_se(parse_permyriad("55%") == 5500);
-        assert_se(parse_permyriad("55.53%") == 5553);
-        assert_se(parse_permyriad("100%") == 10000);
-        assert_se(parse_permyriad("-7%") == -ERANGE);
-        assert_se(parse_permyriad("107%") == -ERANGE);
-        assert_se(parse_permyriad("%") == -EINVAL);
-        assert_se(parse_permyriad("%%") == -EINVAL);
-        assert_se(parse_permyriad("%1") == -EINVAL);
-        assert_se(parse_permyriad("1%%") == -EINVAL);
-        assert_se(parse_permyriad("3.212%") == -EINVAL);
-}
-
-static void test_parse_permyriad_unbounded(void) {
-        assert_se(parse_permyriad_unbounded("1001‱") == 1001);
-        assert_se(parse_permyriad_unbounded("4000‱") == 4000);
-        assert_se(parse_permyriad_unbounded("2147483647‱") == 2147483647);
-        assert_se(parse_permyriad_unbounded("2147483648‱") == -ERANGE);
-        assert_se(parse_permyriad_unbounded("4294967295‱") == -ERANGE);
-        assert_se(parse_permyriad_unbounded("4294967296‱") == -ERANGE);
-
-        assert_se(parse_permyriad_unbounded("101‰") == 1010);
-        assert_se(parse_permyriad_unbounded("400‰") == 4000);
-        assert_se(parse_permyriad_unbounded("214748364.7‰") == 2147483647);
-        assert_se(parse_permyriad_unbounded("214748364.8‰") == -ERANGE);
-        assert_se(parse_permyriad_unbounded("429496729.5‰") == -ERANGE);
-        assert_se(parse_permyriad_unbounded("429496729.6‰") == -ERANGE);
-
-        assert_se(parse_permyriad_unbounded("99%") == 9900);
-        assert_se(parse_permyriad_unbounded("40%") == 4000);
-        assert_se(parse_permyriad_unbounded("21474836.47%") == 2147483647);
-        assert_se(parse_permyriad_unbounded("21474836.48%") == -ERANGE);
-        assert_se(parse_permyriad_unbounded("42949672.95%") == -ERANGE);
-        assert_se(parse_permyriad_unbounded("42949672.96%") == -ERANGE);
-}
-
 static void test_parse_nice(void) {
         int n;
 
@@ -1049,12 +911,6 @@ int main(int argc, char *argv[]) {
         test_safe_atoi64();
         test_safe_atoux64();
         test_safe_atod();
-        test_parse_percent();
-        test_parse_percent_unbounded();
-        test_parse_permille();
-        test_parse_permille_unbounded();
-        test_parse_permyriad();
-        test_parse_permyriad_unbounded();
         test_parse_nice();
         test_parse_dev();
         test_parse_errno();
diff --git a/src/test/test-percent-util.c b/src/test/test-percent-util.c
new file mode 100644 (file)
index 0000000..9d0ad90
--- /dev/null
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "percent-util.h"
+#include "tests.h"
+
+static void test_parse_percent(void) {
+        assert_se(parse_percent("") == -EINVAL);
+        assert_se(parse_percent("foo") == -EINVAL);
+        assert_se(parse_percent("0") == -EINVAL);
+        assert_se(parse_percent("50") == -EINVAL);
+        assert_se(parse_percent("100") == -EINVAL);
+        assert_se(parse_percent("-1") == -EINVAL);
+        assert_se(parse_percent("0%") == 0);
+        assert_se(parse_percent("55%") == 55);
+        assert_se(parse_percent("100%") == 100);
+        assert_se(parse_percent("-7%") == -ERANGE);
+        assert_se(parse_percent("107%") == -ERANGE);
+        assert_se(parse_percent("%") == -EINVAL);
+        assert_se(parse_percent("%%") == -EINVAL);
+        assert_se(parse_percent("%1") == -EINVAL);
+        assert_se(parse_percent("1%%") == -EINVAL);
+        assert_se(parse_percent("3.2%") == -EINVAL);
+}
+
+static void test_parse_percent_unbounded(void) {
+        assert_se(parse_percent_unbounded("101%") == 101);
+        assert_se(parse_percent_unbounded("400%") == 400);
+}
+
+static void test_parse_permille(void) {
+        assert_se(parse_permille("") == -EINVAL);
+        assert_se(parse_permille("foo") == -EINVAL);
+        assert_se(parse_permille("0") == -EINVAL);
+        assert_se(parse_permille("50") == -EINVAL);
+        assert_se(parse_permille("100") == -EINVAL);
+        assert_se(parse_permille("-1") == -EINVAL);
+
+        assert_se(parse_permille("0‰") == 0);
+        assert_se(parse_permille("555‰") == 555);
+        assert_se(parse_permille("1000‰") == 1000);
+        assert_se(parse_permille("-7‰") == -ERANGE);
+        assert_se(parse_permille("1007‰") == -ERANGE);
+        assert_se(parse_permille("‰") == -EINVAL);
+        assert_se(parse_permille("‰‰") == -EINVAL);
+        assert_se(parse_permille("‰1") == -EINVAL);
+        assert_se(parse_permille("1‰‰") == -EINVAL);
+        assert_se(parse_permille("3.2‰") == -EINVAL);
+
+        assert_se(parse_permille("0%") == 0);
+        assert_se(parse_permille("55%") == 550);
+        assert_se(parse_permille("55.5%") == 555);
+        assert_se(parse_permille("100%") == 1000);
+        assert_se(parse_permille("-7%") == -ERANGE);
+        assert_se(parse_permille("107%") == -ERANGE);
+        assert_se(parse_permille("%") == -EINVAL);
+        assert_se(parse_permille("%%") == -EINVAL);
+        assert_se(parse_permille("%1") == -EINVAL);
+        assert_se(parse_permille("1%%") == -EINVAL);
+        assert_se(parse_permille("3.21%") == -EINVAL);
+}
+
+static void test_parse_permille_unbounded(void) {
+        assert_se(parse_permille_unbounded("1001‰") == 1001);
+        assert_se(parse_permille_unbounded("4000‰") == 4000);
+        assert_se(parse_permille_unbounded("2147483647‰") == 2147483647);
+        assert_se(parse_permille_unbounded("2147483648‰") == -ERANGE);
+        assert_se(parse_permille_unbounded("4294967295‰") == -ERANGE);
+        assert_se(parse_permille_unbounded("4294967296‰") == -ERANGE);
+
+        assert_se(parse_permille_unbounded("101%") == 1010);
+        assert_se(parse_permille_unbounded("400%") == 4000);
+        assert_se(parse_permille_unbounded("214748364.7%") == 2147483647);
+        assert_se(parse_permille_unbounded("214748364.8%") == -ERANGE);
+        assert_se(parse_permille_unbounded("429496729.5%") == -ERANGE);
+        assert_se(parse_permille_unbounded("429496729.6%") == -ERANGE);
+}
+
+static void test_parse_permyriad(void) {
+        assert_se(parse_permyriad("") == -EINVAL);
+        assert_se(parse_permyriad("foo") == -EINVAL);
+        assert_se(parse_permyriad("0") == -EINVAL);
+        assert_se(parse_permyriad("50") == -EINVAL);
+        assert_se(parse_permyriad("100") == -EINVAL);
+        assert_se(parse_permyriad("-1") == -EINVAL);
+
+        assert_se(parse_permyriad("0‱") == 0);
+        assert_se(parse_permyriad("555‱") == 555);
+        assert_se(parse_permyriad("1000‱") == 1000);
+        assert_se(parse_permyriad("-7‱") == -ERANGE);
+        assert_se(parse_permyriad("10007‱") == -ERANGE);
+        assert_se(parse_permyriad("‱") == -EINVAL);
+        assert_se(parse_permyriad("‱‱") == -EINVAL);
+        assert_se(parse_permyriad("‱1") == -EINVAL);
+        assert_se(parse_permyriad("1‱‱") == -EINVAL);
+        assert_se(parse_permyriad("3.2‱") == -EINVAL);
+
+        assert_se(parse_permyriad("0‰") == 0);
+        assert_se(parse_permyriad("555.5‰") == 5555);
+        assert_se(parse_permyriad("1000.0‰") == 10000);
+        assert_se(parse_permyriad("-7‰") == -ERANGE);
+        assert_se(parse_permyriad("1007‰") == -ERANGE);
+        assert_se(parse_permyriad("‰") == -EINVAL);
+        assert_se(parse_permyriad("‰‰") == -EINVAL);
+        assert_se(parse_permyriad("‰1") == -EINVAL);
+        assert_se(parse_permyriad("1‰‰") == -EINVAL);
+        assert_se(parse_permyriad("3.22‰") == -EINVAL);
+
+        assert_se(parse_permyriad("0%") == 0);
+        assert_se(parse_permyriad("55%") == 5500);
+        assert_se(parse_permyriad("55.53%") == 5553);
+        assert_se(parse_permyriad("100%") == 10000);
+        assert_se(parse_permyriad("-7%") == -ERANGE);
+        assert_se(parse_permyriad("107%") == -ERANGE);
+        assert_se(parse_permyriad("%") == -EINVAL);
+        assert_se(parse_permyriad("%%") == -EINVAL);
+        assert_se(parse_permyriad("%1") == -EINVAL);
+        assert_se(parse_permyriad("1%%") == -EINVAL);
+        assert_se(parse_permyriad("3.212%") == -EINVAL);
+}
+
+static void test_parse_permyriad_unbounded(void) {
+        assert_se(parse_permyriad_unbounded("1001‱") == 1001);
+        assert_se(parse_permyriad_unbounded("4000‱") == 4000);
+        assert_se(parse_permyriad_unbounded("2147483647‱") == 2147483647);
+        assert_se(parse_permyriad_unbounded("2147483648‱") == -ERANGE);
+        assert_se(parse_permyriad_unbounded("4294967295‱") == -ERANGE);
+        assert_se(parse_permyriad_unbounded("4294967296‱") == -ERANGE);
+
+        assert_se(parse_permyriad_unbounded("101‰") == 1010);
+        assert_se(parse_permyriad_unbounded("400‰") == 4000);
+        assert_se(parse_permyriad_unbounded("214748364.7‰") == 2147483647);
+        assert_se(parse_permyriad_unbounded("214748364.8‰") == -ERANGE);
+        assert_se(parse_permyriad_unbounded("429496729.5‰") == -ERANGE);
+        assert_se(parse_permyriad_unbounded("429496729.6‰") == -ERANGE);
+
+        assert_se(parse_permyriad_unbounded("99%") == 9900);
+        assert_se(parse_permyriad_unbounded("40%") == 4000);
+        assert_se(parse_permyriad_unbounded("21474836.47%") == 2147483647);
+        assert_se(parse_permyriad_unbounded("21474836.48%") == -ERANGE);
+        assert_se(parse_permyriad_unbounded("42949672.95%") == -ERANGE);
+        assert_se(parse_permyriad_unbounded("42949672.96%") == -ERANGE);
+}
+
+int main(int argc, char *argv[]) {
+        test_setup_logging(LOG_DEBUG);
+
+        test_parse_percent();
+        test_parse_percent_unbounded();
+        test_parse_permille();
+        test_parse_permille_unbounded();
+        test_parse_permyriad();
+        test_parse_permyriad_unbounded();
+
+        return 0;
+}