]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
udev: move several functions from udev-util.c to relevant udevd source files
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 18 Jul 2023 07:10:36 +0000 (16:10 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 31 Jul 2023 14:23:00 +0000 (23:23 +0900)
The functions are only used by udevd (and relevant tests), hence it is
not necessary to be in src/shared.

14 files changed:
src/shared/udev-util.c
src/shared/udev-util.h
src/test/test-udev-util.c
src/udev/fuzz-udev-rule-parse-value.c
src/udev/meson.build
src/udev/test-udev-format.c [new file with mode: 0644]
src/udev/test-udev-rules.c [new file with mode: 0644]
src/udev/test-udevd.c [new file with mode: 0644]
src/udev/udev-format.c
src/udev/udev-format.h
src/udev/udev-rules.c
src/udev/udev-rules.h
src/udev/udevd.c
src/udev/udevd.h

index fddf096d76a7b63e9bb03a73d6b71d829268bfb7..abf3c5e955a28e07837a224729bcd88929557baa 100644 (file)
@@ -10,7 +10,6 @@
 #include "device-util.h"
 #include "env-file.h"
 #include "errno-util.h"
-#include "escape.h"
 #include "fd-util.h"
 #include "id128-util.h"
 #include "log.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "signal-util.h"
-#include "socket-util.h"
 #include "stat-util.h"
 #include "string-table.h"
 #include "string-util.h"
-#include "strxcpyx.h"
 #include "udev-util.h"
 #include "utf8.h"
 
@@ -337,62 +334,6 @@ void log_device_uevent(sd_device *device, const char *str) {
                          sd_id128_is_null(event_id) ? "" : SD_ID128_TO_UUID_STRING(event_id));
 }
 
-int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) {
-        char *i, *j;
-        bool is_escaped;
-
-        /* value must be double quotated */
-        is_escaped = str[0] == 'e';
-        str += is_escaped;
-        if (str[0] != '"')
-                return -EINVAL;
-
-        if (!is_escaped) {
-                /* unescape double quotation '\"'->'"' */
-                for (j = str, i = str + 1; *i != '"'; i++, j++) {
-                        if (*i == '\0')
-                                return -EINVAL;
-                        if (i[0] == '\\' && i[1] == '"')
-                                i++;
-                        *j = *i;
-                }
-                j[0] = '\0';
-                /*
-                 * The return value must be terminated by two subsequent NULs
-                 * so it could be safely interpreted as nulstr.
-                 */
-                j[1] = '\0';
-        } else {
-                _cleanup_free_ char *unescaped = NULL;
-                ssize_t l;
-
-                /* find the end position of value */
-                for (i = str + 1; *i != '"'; i++) {
-                        if (i[0] == '\\')
-                                i++;
-                        if (*i == '\0')
-                                return -EINVAL;
-                }
-                i[0] = '\0';
-
-                l = cunescape_length(str + 1, i - (str + 1), 0, &unescaped);
-                if (l < 0)
-                        return l;
-
-                assert(l <= i - (str + 1));
-                memcpy(str, unescaped, l + 1);
-                /*
-                 * The return value must be terminated by two subsequent NULs
-                 * so it could be safely interpreted as nulstr.
-                 */
-                str[l + 1] = '\0';
-        }
-
-        *ret_value = str;
-        *ret_endpos = i + 1;
-        return 0;
-}
-
 size_t udev_replace_whitespace(const char *str, char *to, size_t len) {
         bool is_space = false;
         size_t i, j;
@@ -435,22 +376,6 @@ size_t udev_replace_whitespace(const char *str, char *to, size_t len) {
         return j;
 }
 
-size_t udev_replace_ifname(char *str) {
-        size_t replaced = 0;
-
-        assert(str);
-
-        /* See ifname_valid_full(). */
-
-        for (char *p = str; *p != '\0'; p++)
-                if (!ifname_valid_char(*p)) {
-                        *p = '_';
-                        replaced++;
-                }
-
-        return replaced;
-}
-
 size_t udev_replace_chars(char *str, const char *allow) {
         size_t i = 0, replaced = 0;
 
@@ -495,83 +420,6 @@ size_t udev_replace_chars(char *str, const char *allow) {
         return replaced;
 }
 
-int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value) {
-        _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
-        _cleanup_free_ char *temp = NULL;
-        char *subsys, *sysname, *attr;
-        const char *val;
-        int r;
-
-        assert(string);
-        assert(result);
-
-        /* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */
-
-        if (string[0] != '[')
-                return -EINVAL;
-
-        temp = strdup(string);
-        if (!temp)
-                return -ENOMEM;
-
-        subsys = &temp[1];
-
-        sysname = strchr(subsys, '/');
-        if (!sysname)
-                return -EINVAL;
-        sysname[0] = '\0';
-        sysname = &sysname[1];
-
-        attr = strchr(sysname, ']');
-        if (!attr)
-                return -EINVAL;
-        attr[0] = '\0';
-        attr = &attr[1];
-        if (attr[0] == '/')
-                attr = &attr[1];
-        if (attr[0] == '\0')
-                attr = NULL;
-
-        if (read_value && !attr)
-                return -EINVAL;
-
-        r = sd_device_new_from_subsystem_sysname(&dev, subsys, sysname);
-        if (r < 0)
-                return r;
-
-        if (read_value) {
-                r = sd_device_get_sysattr_value(dev, attr, &val);
-                if (r < 0 && !ERRNO_IS_PRIVILEGE(r) && r != -ENOENT)
-                        return r;
-                if (r >= 0)
-                        strscpy(result, maxsize, val);
-                else
-                        result[0] = '\0';
-                log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result);
-        } else {
-                r = sd_device_get_syspath(dev, &val);
-                if (r < 0)
-                        return r;
-
-                strscpyl(result, maxsize, val, attr ? "/" : NULL, attr ?: NULL, NULL);
-                log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, strempty(attr), result);
-        }
-        return 0;
-}
-
-bool devpath_conflict(const char *a, const char *b) {
-        /* This returns true when two paths are equivalent, or one is a child of another. */
-
-        if (!a || !b)
-                return false;
-
-        for (; *a != '\0' && *b != '\0'; a++, b++)
-                if (*a != *b)
-                        return false;
-
-        return *a == '/' || *b == '/' || *a == *b;
-}
-
 int udev_queue_is_empty(void) {
         return access("/run/udev/queue", F_OK) < 0 ?
                 (errno == ENOENT ? true : -errno) : false;
index c848cf6177a66e32378b5b8a5d9e419d20650060..97c14fb9bb6c18a238362d9affe20dd602d73fbe 100644 (file)
@@ -39,13 +39,8 @@ bool device_for_action(sd_device *dev, sd_device_action_t action);
 
 void log_device_uevent(sd_device *device, const char *str);
 
-int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos);
 size_t udev_replace_whitespace(const char *str, char *to, size_t len);
-size_t udev_replace_ifname(char *str);
 size_t udev_replace_chars(char *str, const char *allow);
-int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value);
-
-bool devpath_conflict(const char *a, const char *b);
 
 int udev_queue_is_empty(void);
 
index 4be3694e9ebb1cd3633c267f804a7021e0c835a3..cb80c69c968aeda925378741c5e003b87fa71c57 100644 (file)
@@ -8,76 +8,6 @@
 #include "tests.h"
 #include "udev-util.h"
 
-static void test_udev_rule_parse_value_one(const char *in, const char *expected_value, int expected_retval) {
-        _cleanup_free_ char *str = NULL;
-        char *value = UINT_TO_PTR(0x12345678U);
-        char *endpos = UINT_TO_PTR(0x87654321U);
-
-        log_info("/* %s (%s, %s, %d) */", __func__, in, strnull(expected_value), expected_retval);
-
-        assert_se(str = strdup(in));
-        assert_se(udev_rule_parse_value(str, &value, &endpos) == expected_retval);
-        if (expected_retval < 0) {
-                /* not modified on failure */
-                assert_se(value == UINT_TO_PTR(0x12345678U));
-                assert_se(endpos == UINT_TO_PTR(0x87654321U));
-        } else {
-                assert_se(streq_ptr(value, expected_value));
-                assert_se(endpos == str + strlen(in));
-                /*
-                 * The return value must be terminated by two subsequent NULs
-                 * so it could be safely interpreted as nulstr.
-                 */
-                assert_se(value[strlen(value) + 1] == '\0');
-        }
-}
-
-TEST(udev_rule_parse_value) {
-        /* input: "valid operand"
-         * parsed: valid operand
-         * use the following command to help generate textual C strings:
-         * python3 -c 'import json; print(json.dumps(input()))' */
-        test_udev_rule_parse_value_one("\"valid operand\"", "valid operand", 0);
-        /* input: "va'l\'id\"op\"erand"
-         * parsed: va'l\'id"op"erand */
-        test_udev_rule_parse_value_one("\"va'l\\'id\\\"op\\\"erand\"", "va'l\\'id\"op\"erand", 0);
-        test_udev_rule_parse_value_one("no quotes", NULL, -EINVAL);
-        test_udev_rule_parse_value_one("\"\\\\a\\b\\x\\y\"", "\\\\a\\b\\x\\y", 0);
-        test_udev_rule_parse_value_one("\"reject\0nul\"", NULL, -EINVAL);
-        /* input: e"" */
-        test_udev_rule_parse_value_one("e\"\"", "", 0);
-        /* input: e"1234" */
-        test_udev_rule_parse_value_one("e\"1234\"", "1234", 0);
-        /* input: e"\"" */
-        test_udev_rule_parse_value_one("e\"\\\"\"", "\"", 0);
-        /* input: e"\ */
-        test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL);
-        /* input: e"\" */
-        test_udev_rule_parse_value_one("e\"\\\"", NULL, -EINVAL);
-        /* input: e"\\" */
-        test_udev_rule_parse_value_one("e\"\\\\\"", "\\", 0);
-        /* input: e"\\\" */
-        test_udev_rule_parse_value_one("e\"\\\\\\\"", NULL, -EINVAL);
-        /* input: e"\\\"" */
-        test_udev_rule_parse_value_one("e\"\\\\\\\"\"", "\\\"", 0);
-        /* input: e"\\\\" */
-        test_udev_rule_parse_value_one("e\"\\\\\\\\\"", "\\\\", 0);
-        /* input: e"operand with newline\n" */
-        test_udev_rule_parse_value_one("e\"operand with newline\\n\"", "operand with newline\n", 0);
-        /* input: e"single\rcharacter\t\aescape\bsequence" */
-        test_udev_rule_parse_value_one(
-                "e\"single\\rcharacter\\t\\aescape\\bsequence\"", "single\rcharacter\t\aescape\bsequence", 0);
-        /* input: e"reject\invalid escape sequence" */
-        test_udev_rule_parse_value_one("e\"reject\\invalid escape sequence", NULL, -EINVAL);
-        /* input: e"\ */
-        test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL);
-        /* input: "s\u1d1c\u1d04\u029c \u1d1c\u0274\u026a\u1d04\u1d0f\u1d05\u1d07 \U0001d568\U0001d560\U0001d568" */
-        test_udev_rule_parse_value_one(
-                "e\"s\\u1d1c\\u1d04\\u029c \\u1d1c\\u0274\\u026a\\u1d04\\u1d0f\\u1d05\\u1d07 \\U0001d568\\U0001d560\\U0001d568\"",
-                "s\xe1\xb4\x9c\xe1\xb4\x84\xca\x9c \xe1\xb4\x9c\xc9\xb4\xc9\xaa\xe1\xb4\x84\xe1\xb4\x8f\xe1\xb4\x85\xe1\xb4\x87 \xf0\x9d\x95\xa8\xf0\x9d\x95\xa0\xf0\x9d\x95\xa8",
-                0);
-}
-
 static void test_udev_replace_whitespace_one_len(const char *str, size_t len, const char *expected) {
         _cleanup_free_ char *result = NULL;
         int r;
@@ -130,47 +60,4 @@ TEST(udev_replace_whitespace) {
         test_udev_replace_whitespace_one_len("    hoge   hoge    ", 0, "");
 }
 
-static void test_udev_resolve_subsys_kernel_one(const char *str, bool read_value, int retval, const char *expected) {
-        char result[PATH_MAX] = "";
-        int r;
-
-        r = udev_resolve_subsys_kernel(str, result, sizeof(result), read_value);
-        log_info("\"%s\" → expect: \"%s\", %d, actual: \"%s\", %d", str, strnull(expected), retval, result, r);
-        assert_se(r == retval);
-        if (r >= 0)
-                assert_se(streq(result, expected));
-}
-
-TEST(udev_resolve_subsys_kernel) {
-        test_udev_resolve_subsys_kernel_one("hoge", false, -EINVAL, NULL);
-        test_udev_resolve_subsys_kernel_one("[hoge", false, -EINVAL, NULL);
-        test_udev_resolve_subsys_kernel_one("[hoge/foo", false, -EINVAL, NULL);
-        test_udev_resolve_subsys_kernel_one("[hoge/]", false, -EINVAL, NULL);
-
-        test_udev_resolve_subsys_kernel_one("[net/lo]", false, 0, "/sys/devices/virtual/net/lo");
-        test_udev_resolve_subsys_kernel_one("[net/lo]/", false, 0, "/sys/devices/virtual/net/lo");
-        test_udev_resolve_subsys_kernel_one("[net/lo]hoge", false, 0, "/sys/devices/virtual/net/lo/hoge");
-        test_udev_resolve_subsys_kernel_one("[net/lo]/hoge", false, 0, "/sys/devices/virtual/net/lo/hoge");
-
-        test_udev_resolve_subsys_kernel_one("[net/lo]", true, -EINVAL, NULL);
-        test_udev_resolve_subsys_kernel_one("[net/lo]/", true, -EINVAL, NULL);
-        test_udev_resolve_subsys_kernel_one("[net/lo]hoge", true, 0, "");
-        test_udev_resolve_subsys_kernel_one("[net/lo]/hoge", true, 0, "");
-        test_udev_resolve_subsys_kernel_one("[net/lo]address", true, 0, "00:00:00:00:00:00");
-        test_udev_resolve_subsys_kernel_one("[net/lo]/address", true, 0, "00:00:00:00:00:00");
-}
-
-TEST(devpath_conflict) {
-        assert_se(!devpath_conflict(NULL, NULL));
-        assert_se(!devpath_conflict(NULL, "/devices/pci0000:00/0000:00:1c.4"));
-        assert_se(!devpath_conflict("/devices/pci0000:00/0000:00:1c.4", NULL));
-        assert_se(!devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:00.0"));
-        assert_se(!devpath_conflict("/devices/virtual/net/veth99", "/devices/virtual/net/veth999"));
-
-        assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:1c.4"));
-        assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0"));
-        assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1",
-                                   "/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1/nvme0n1p1"));
-}
-
 DEFINE_TEST_MAIN(LOG_INFO);
index 404d0cd142c83fcd098b5d6e9842727cbcf22675..c7b85044cd5c593f92b4f14c519a70d3518ec482 100644 (file)
@@ -4,7 +4,7 @@
 
 #include "alloc-util.h"
 #include "fuzz.h"
-#include "udev-util.h"
+#include "udev-rules.h"
 
 int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
         _cleanup_free_ char *str = NULL;
@@ -17,7 +17,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
         str[size] = '\0';
 
         r = udev_rule_parse_value(str, &value, &endpos);
-
         if (r < 0) {
                 /* not modified on failure */
                 assert_se(value == UINT_TO_PTR(0x12345678U));
index e9a3e5bcec755222cf3f65c87465c722b33be5ed..1828c584707aeedbab43955354dfda4820009cfd 100644 (file)
@@ -14,7 +14,6 @@ udevadm_sources = files(
         'udevadm-verify.c',
         'udevadm-wait.c',
         'udevadm.c',
-        'udevd.c',
 )
 
 libudevd_core_sources = files(
@@ -35,6 +34,7 @@ libudevd_core_sources = files(
         'udev-builtin-path_id.c',
         'udev-builtin-usb_id.c',
         'udev-builtin.c',
+        'udevd.c',
 )
 
 if conf.get('HAVE_KMOD') == 1
@@ -169,10 +169,6 @@ if install_sysconfdir
                                  mkdir_p.format(sysconfdir / 'udev/rules.d'))
 endif
 
-simple_fuzzers += files(
-        'fuzz-udev-rule-parse-value.c',
-)
-
 fuzzer_udev_base = {
         'link_with' : [libudevd_core, libshared],
         'dependencies' : [threads, libacl],
@@ -184,6 +180,10 @@ fuzzers += [
                 'includes' : udev_includes,
                 'base' : fuzzer_udev_base,
         },
+        {
+                'sources' : files('fuzz-udev-rule-parse-value.c'),
+                'base' : fuzzer_udev_base,
+        },
         {
                 'sources' : files('fuzz-udev-rules.c'),
                 'base' : fuzzer_udev_base,
@@ -212,12 +212,24 @@ tests += [
                 'sources' : files('test-udev-builtin.c'),
                 'base' : test_libudev_base,
         },
+        {
+                'sources' : files('test-udev-format.c'),
+                'base' : test_libudev_base,
+        },
         {
                 'sources' : files('test-udev-node.c'),
                 'base' : test_libudev_base,
         },
+        {
+                'sources' : files('test-udev-rules.c'),
+                'base' : test_libudev_base,
+        },
         {
                 'sources' : files('test-udev-spawn.c'),
                 'base' : test_libudev_base,
         },
+        {
+                'sources' : files('test-udevd.c'),
+                'base' : test_libudev_base,
+        },
 ]
diff --git a/src/udev/test-udev-format.c b/src/udev/test-udev-format.c
new file mode 100644 (file)
index 0000000..d8e3808
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "string-util.h"
+#include "tests.h"
+#include "udev-format.h"
+
+static void test_udev_resolve_subsys_kernel_one(const char *str, bool read_value, int retval, const char *expected) {
+        char result[PATH_MAX] = "";
+        int r;
+
+        r = udev_resolve_subsys_kernel(str, result, sizeof(result), read_value);
+        log_info("\"%s\" → expect: \"%s\", %d, actual: \"%s\", %d", str, strnull(expected), retval, result, r);
+        assert_se(r == retval);
+        if (r >= 0)
+                assert_se(streq(result, expected));
+}
+
+TEST(udev_resolve_subsys_kernel) {
+        test_udev_resolve_subsys_kernel_one("hoge", false, -EINVAL, NULL);
+        test_udev_resolve_subsys_kernel_one("[hoge", false, -EINVAL, NULL);
+        test_udev_resolve_subsys_kernel_one("[hoge/foo", false, -EINVAL, NULL);
+        test_udev_resolve_subsys_kernel_one("[hoge/]", false, -EINVAL, NULL);
+
+        test_udev_resolve_subsys_kernel_one("[net/lo]", false, 0, "/sys/devices/virtual/net/lo");
+        test_udev_resolve_subsys_kernel_one("[net/lo]/", false, 0, "/sys/devices/virtual/net/lo");
+        test_udev_resolve_subsys_kernel_one("[net/lo]hoge", false, 0, "/sys/devices/virtual/net/lo/hoge");
+        test_udev_resolve_subsys_kernel_one("[net/lo]/hoge", false, 0, "/sys/devices/virtual/net/lo/hoge");
+
+        test_udev_resolve_subsys_kernel_one("[net/lo]", true, -EINVAL, NULL);
+        test_udev_resolve_subsys_kernel_one("[net/lo]/", true, -EINVAL, NULL);
+        test_udev_resolve_subsys_kernel_one("[net/lo]hoge", true, 0, "");
+        test_udev_resolve_subsys_kernel_one("[net/lo]/hoge", true, 0, "");
+        test_udev_resolve_subsys_kernel_one("[net/lo]address", true, 0, "00:00:00:00:00:00");
+        test_udev_resolve_subsys_kernel_one("[net/lo]/address", true, 0, "00:00:00:00:00:00");
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/udev/test-udev-rules.c b/src/udev/test-udev-rules.c
new file mode 100644 (file)
index 0000000..b62b08b
--- /dev/null
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "string-util.h"
+#include "tests.h"
+#include "udev-rules.h"
+
+static void test_udev_rule_parse_value_one(const char *in, const char *expected_value, int expected_retval) {
+        _cleanup_free_ char *str = NULL;
+        char *value = UINT_TO_PTR(0x12345678U);
+        char *endpos = UINT_TO_PTR(0x87654321U);
+
+        log_info("/* %s (%s, %s, %d) */", __func__, in, strnull(expected_value), expected_retval);
+
+        assert_se(str = strdup(in));
+        assert_se(udev_rule_parse_value(str, &value, &endpos) == expected_retval);
+        if (expected_retval < 0) {
+                /* not modified on failure */
+                assert_se(value == UINT_TO_PTR(0x12345678U));
+                assert_se(endpos == UINT_TO_PTR(0x87654321U));
+        } else {
+                assert_se(streq_ptr(value, expected_value));
+                assert_se(endpos == str + strlen(in));
+                /*
+                 * The return value must be terminated by two subsequent NULs
+                 * so it could be safely interpreted as nulstr.
+                 */
+                assert_se(value[strlen(value) + 1] == '\0');
+        }
+}
+
+TEST(udev_rule_parse_value) {
+        /* input: "valid operand"
+         * parsed: valid operand
+         * use the following command to help generate textual C strings:
+         * python3 -c 'import json; print(json.dumps(input()))' */
+        test_udev_rule_parse_value_one("\"valid operand\"", "valid operand", 0);
+        /* input: "va'l\'id\"op\"erand"
+         * parsed: va'l\'id"op"erand */
+        test_udev_rule_parse_value_one("\"va'l\\'id\\\"op\\\"erand\"", "va'l\\'id\"op\"erand", 0);
+        test_udev_rule_parse_value_one("no quotes", NULL, -EINVAL);
+        test_udev_rule_parse_value_one("\"\\\\a\\b\\x\\y\"", "\\\\a\\b\\x\\y", 0);
+        test_udev_rule_parse_value_one("\"reject\0nul\"", NULL, -EINVAL);
+        /* input: e"" */
+        test_udev_rule_parse_value_one("e\"\"", "", 0);
+        /* input: e"1234" */
+        test_udev_rule_parse_value_one("e\"1234\"", "1234", 0);
+        /* input: e"\"" */
+        test_udev_rule_parse_value_one("e\"\\\"\"", "\"", 0);
+        /* input: e"\ */
+        test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL);
+        /* input: e"\" */
+        test_udev_rule_parse_value_one("e\"\\\"", NULL, -EINVAL);
+        /* input: e"\\" */
+        test_udev_rule_parse_value_one("e\"\\\\\"", "\\", 0);
+        /* input: e"\\\" */
+        test_udev_rule_parse_value_one("e\"\\\\\\\"", NULL, -EINVAL);
+        /* input: e"\\\"" */
+        test_udev_rule_parse_value_one("e\"\\\\\\\"\"", "\\\"", 0);
+        /* input: e"\\\\" */
+        test_udev_rule_parse_value_one("e\"\\\\\\\\\"", "\\\\", 0);
+        /* input: e"operand with newline\n" */
+        test_udev_rule_parse_value_one("e\"operand with newline\\n\"", "operand with newline\n", 0);
+        /* input: e"single\rcharacter\t\aescape\bsequence" */
+        test_udev_rule_parse_value_one(
+                "e\"single\\rcharacter\\t\\aescape\\bsequence\"", "single\rcharacter\t\aescape\bsequence", 0);
+        /* input: e"reject\invalid escape sequence" */
+        test_udev_rule_parse_value_one("e\"reject\\invalid escape sequence", NULL, -EINVAL);
+        /* input: e"\ */
+        test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL);
+        /* input: "s\u1d1c\u1d04\u029c \u1d1c\u0274\u026a\u1d04\u1d0f\u1d05\u1d07 \U0001d568\U0001d560\U0001d568" */
+        test_udev_rule_parse_value_one(
+                "e\"s\\u1d1c\\u1d04\\u029c \\u1d1c\\u0274\\u026a\\u1d04\\u1d0f\\u1d05\\u1d07 \\U0001d568\\U0001d560\\U0001d568\"",
+                "s\xe1\xb4\x9c\xe1\xb4\x84\xca\x9c \xe1\xb4\x9c\xc9\xb4\xc9\xaa\xe1\xb4\x84\xe1\xb4\x8f\xe1\xb4\x85\xe1\xb4\x87 \xf0\x9d\x95\xa8\xf0\x9d\x95\xa0\xf0\x9d\x95\xa8",
+                0);
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/udev/test-udevd.c b/src/udev/test-udevd.c
new file mode 100644 (file)
index 0000000..58c8885
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "tests.h"
+#include "udevd.h"
+
+TEST(devpath_conflict) {
+        assert_se(!devpath_conflict(NULL, NULL));
+        assert_se(!devpath_conflict(NULL, "/devices/pci0000:00/0000:00:1c.4"));
+        assert_se(!devpath_conflict("/devices/pci0000:00/0000:00:1c.4", NULL));
+        assert_se(!devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:00.0"));
+        assert_se(!devpath_conflict("/devices/virtual/net/veth99", "/devices/virtual/net/veth999"));
+
+        assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:1c.4"));
+        assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4", "/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0"));
+        assert_se(devpath_conflict("/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1",
+                                   "/devices/pci0000:00/0000:00:1c.4/0000:3c:00.0/nvme/nvme0/nvme0n1/nvme0n1p1"));
+}
+
+DEFINE_TEST_MAIN(LOG_DEBUG);
index b33d8646c6741c78d2c341e9666ea330d4149a7c..b17b754b5e17d7415d4d376d5a7c27d9603473e6 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 
 #include "device-util.h"
+#include "errno-util.h"
 #include "parse-util.h"
 #include "string-util.h"
 #include "strxcpyx.h"
@@ -481,3 +482,67 @@ int udev_check_format(const char *value, size_t *offset, const char **hint) {
 
         return 0;
 }
+
+int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value) {
+        _cleanup_(sd_device_unrefp) sd_device *dev = NULL;
+        _cleanup_free_ char *temp = NULL;
+        char *subsys, *sysname, *attr;
+        const char *val;
+        int r;
+
+        assert(string);
+        assert(result);
+
+        /* handle "[<SUBSYSTEM>/<KERNEL>]<attribute>" format */
+
+        if (string[0] != '[')
+                return -EINVAL;
+
+        temp = strdup(string);
+        if (!temp)
+                return -ENOMEM;
+
+        subsys = &temp[1];
+
+        sysname = strchr(subsys, '/');
+        if (!sysname)
+                return -EINVAL;
+        sysname[0] = '\0';
+        sysname = &sysname[1];
+
+        attr = strchr(sysname, ']');
+        if (!attr)
+                return -EINVAL;
+        attr[0] = '\0';
+        attr = &attr[1];
+        if (attr[0] == '/')
+                attr = &attr[1];
+        if (attr[0] == '\0')
+                attr = NULL;
+
+        if (read_value && !attr)
+                return -EINVAL;
+
+        r = sd_device_new_from_subsystem_sysname(&dev, subsys, sysname);
+        if (r < 0)
+                return r;
+
+        if (read_value) {
+                r = sd_device_get_sysattr_value(dev, attr, &val);
+                if (r < 0 && !ERRNO_IS_PRIVILEGE(r) && r != -ENOENT)
+                        return r;
+                if (r >= 0)
+                        strscpy(result, maxsize, val);
+                else
+                        result[0] = '\0';
+                log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result);
+        } else {
+                r = sd_device_get_syspath(dev, &val);
+                if (r < 0)
+                        return r;
+
+                strscpyl(result, maxsize, val, attr ? "/" : NULL, attr ?: NULL, NULL);
+                log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, strempty(attr), result);
+        }
+        return 0;
+}
index 0dbdd561b1c9e644e4939f89e33ee4d14ca777b6..9914dc03b2d133a524ba5f80e4764fef8e54f084 100644 (file)
@@ -16,3 +16,5 @@ size_t udev_event_apply_format(
                 bool replace_whitespace,
                 bool *ret_truncated);
 int udev_check_format(const char *value, size_t *offset, const char **hint);
+
+int udev_resolve_subsys_kernel(const char *string, char *result, size_t maxsize, bool read_value);
index c4f2a7ffbb2cb763bda779a23dba34e1e390facb..52d3f354fa970a1fef5c531a292d56d319ee2187 100644 (file)
@@ -11,6 +11,7 @@
 #include "device-private.h"
 #include "device-util.h"
 #include "dirent-util.h"
+#include "escape.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-util.h"
@@ -23,6 +24,7 @@
 #include "parse-util.h"
 #include "path-util.h"
 #include "proc-cmdline.h"
+#include "socket-util.h"
 #include "stat-util.h"
 #include "strv.h"
 #include "strxcpyx.h"
@@ -1118,6 +1120,62 @@ static void check_token_delimiters(UdevRuleLine *rule_line, const char *line) {
         }
 }
 
+int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) {
+        char *i, *j;
+        bool is_escaped;
+
+        /* value must be double quotated */
+        is_escaped = str[0] == 'e';
+        str += is_escaped;
+        if (str[0] != '"')
+                return -EINVAL;
+
+        if (!is_escaped) {
+                /* unescape double quotation '\"'->'"' */
+                for (j = str, i = str + 1; *i != '"'; i++, j++) {
+                        if (*i == '\0')
+                                return -EINVAL;
+                        if (i[0] == '\\' && i[1] == '"')
+                                i++;
+                        *j = *i;
+                }
+                j[0] = '\0';
+                /*
+                 * The return value must be terminated by two subsequent NULs
+                 * so it could be safely interpreted as nulstr.
+                 */
+                j[1] = '\0';
+        } else {
+                _cleanup_free_ char *unescaped = NULL;
+                ssize_t l;
+
+                /* find the end position of value */
+                for (i = str + 1; *i != '"'; i++) {
+                        if (i[0] == '\\')
+                                i++;
+                        if (*i == '\0')
+                                return -EINVAL;
+                }
+                i[0] = '\0';
+
+                l = cunescape_length(str + 1, i - (str + 1), 0, &unescaped);
+                if (l < 0)
+                        return l;
+
+                assert(l <= i - (str + 1));
+                memcpy(str, unescaped, l + 1);
+                /*
+                 * The return value must be terminated by two subsequent NULs
+                 * so it could be safely interpreted as nulstr.
+                 */
+                str[l + 1] = '\0';
+        }
+
+        *ret_value = str;
+        *ret_endpos = i + 1;
+        return 0;
+}
+
 static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOperatorType *ret_op, char **ret_value) {
         char *key_begin, *key_end, *attr, *tmp;
         UdevRuleOperatorType op;
@@ -1850,6 +1908,22 @@ static int attr_subst_subdir(char attr[static UDEV_PATH_SIZE]) {
         return -ENOENT;
 }
 
+static size_t udev_replace_ifname(char *str) {
+        size_t replaced = 0;
+
+        assert(str);
+
+        /* See ifname_valid_full(). */
+
+        for (char *p = str; *p != '\0'; p++)
+                if (!ifname_valid_char(*p)) {
+                        *p = '_';
+                        replaced++;
+                }
+
+        return replaced;
+}
+
 static int udev_rule_apply_token_to_event(
                 UdevRuleToken *token,
                 sd_device *dev,
index 9b477fb235909f89fc9a350276b3b526c81f7374..075f8b5ba066e163013535058c9e710cc746166f 100644 (file)
@@ -18,6 +18,7 @@ typedef enum {
         _ESCAPE_TYPE_INVALID = -EINVAL,
 } UdevRuleEscapeType;
 
+int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos);
 int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret);
 unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file);
 UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing);
index a2b164e933a77487b80181958621a0645ea06abf..4fd6db80474783df6ef3095aae8254106ee07e78 100644 (file)
@@ -889,6 +889,19 @@ static int event_run(Event *event) {
         return 1; /* event is now processing. */
 }
 
+bool devpath_conflict(const char *a, const char *b) {
+        /* This returns true when two paths are equivalent, or one is a child of another. */
+
+        if (!a || !b)
+                return false;
+
+        for (; *a != '\0' && *b != '\0'; a++, b++)
+                if (*a != *b)
+                        return false;
+
+        return *a == '/' || *b == '/' || *a == *b;
+}
+
 static int event_is_blocked(Event *event) {
         Event *loop_event = NULL;
         int r;
index 583e8952255b9e3acd776904c111e1024205cf42..891196c474e20cdb2e594f6eb40872a1d57c9263 100644 (file)
@@ -2,3 +2,5 @@
 #pragma once
 
 int run_udevd(int argc, char *argv[]);
+
+bool devpath_conflict(const char *a, const char *b);