From: Yu Watanabe Date: Tue, 18 Jul 2023 07:10:36 +0000 (+0900) Subject: udev: move several functions from udev-util.c to relevant udevd source files X-Git-Tag: v255-rc1~870^2~8 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=02267291815a524956fdd25f143f19781e69bfa9;p=thirdparty%2Fsystemd.git udev: move several functions from udev-util.c to relevant udevd source files The functions are only used by udevd (and relevant tests), hence it is not necessary to be in src/shared. --- diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index fddf096d76a..abf3c5e955a 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -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" @@ -18,11 +17,9 @@ #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 "[/]" 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; diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index c848cf6177a..97c14fb9bb6 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -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); diff --git a/src/test/test-udev-util.c b/src/test/test-udev-util.c index 4be3694e9eb..cb80c69c968 100644 --- a/src/test/test-udev-util.c +++ b/src/test/test-udev-util.c @@ -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); diff --git a/src/udev/fuzz-udev-rule-parse-value.c b/src/udev/fuzz-udev-rule-parse-value.c index 404d0cd142c..c7b85044cd5 100644 --- a/src/udev/fuzz-udev-rule-parse-value.c +++ b/src/udev/fuzz-udev-rule-parse-value.c @@ -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)); diff --git a/src/udev/meson.build b/src/udev/meson.build index e9a3e5bcec7..1828c584707 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -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 index 00000000000..d8e38082fdc --- /dev/null +++ b/src/udev/test-udev-format.c @@ -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 index 00000000000..b62b08b56ec --- /dev/null +++ b/src/udev/test-udev-rules.c @@ -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 index 00000000000..58c88853a7a --- /dev/null +++ b/src/udev/test-udevd.c @@ -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); diff --git a/src/udev/udev-format.c b/src/udev/udev-format.c index b33d8646c67..b17b754b5e1 100644 --- a/src/udev/udev-format.c +++ b/src/udev/udev-format.c @@ -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 "[/]" 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; +} diff --git a/src/udev/udev-format.h b/src/udev/udev-format.h index 0dbdd561b1c..9914dc03b2d 100644 --- a/src/udev/udev-format.h +++ b/src/udev/udev-format.h @@ -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); diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index c4f2a7ffbb2..52d3f354fa9 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -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, diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index 9b477fb2359..075f8b5ba06 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -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); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index a2b164e933a..4fd6db80474 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -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; diff --git a/src/udev/udevd.h b/src/udev/udevd.h index 583e8952255..891196c474e 100644 --- a/src/udev/udevd.h +++ b/src/udev/udevd.h @@ -2,3 +2,5 @@ #pragma once int run_udevd(int argc, char *argv[]); + +bool devpath_conflict(const char *a, const char *b);