summaryrefslogtreecommitdiffstats
path: root/src/udev
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2024-09-15 06:49:32 +0200
committerYu Watanabe <watanabe.yu+github@gmail.com>2024-09-15 16:09:26 +0200
commit5f5c5c48b9edd3f2aa634d0d8e40ff77e116879e (patch)
tree726c2a179ea1dcbfa296be9348d7becceb9dd35d /src/udev
parentudev-rules: embed UdevRuleToken.attr_match_remove_trailing_whitespace flag in... (diff)
downloadsystemd-5f5c5c48b9edd3f2aa634d0d8e40ff77e116879e.tar.xz
systemd-5f5c5c48b9edd3f2aa634d0d8e40ff77e116879e.zip
udev-rules: support case insensitive match
This introduces 'i' prefix for match string. When specified, string or pattern will match case-insensitively. Closes #34359. Co-authored-by: Ryan Wilson <ryantimwilson@meta.com>
Diffstat (limited to 'src/udev')
-rw-r--r--src/udev/fuzz-udev-rule-parse-value.c3
-rw-r--r--src/udev/test-udev-rules.c60
-rw-r--r--src/udev/udev-rules.c170
-rw-r--r--src/udev/udev-rules.h2
4 files changed, 146 insertions, 89 deletions
diff --git a/src/udev/fuzz-udev-rule-parse-value.c b/src/udev/fuzz-udev-rule-parse-value.c
index 1817c15b3b..57951bd461 100644
--- a/src/udev/fuzz-udev-rule-parse-value.c
+++ b/src/udev/fuzz-udev-rule-parse-value.c
@@ -11,6 +11,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
int r;
char *value = UINT_TO_PTR(0x12345678U);
char *endpos = UINT_TO_PTR(0x87654321U);
+ bool is_case_sensitive;
fuzz_setup_logging();
@@ -18,7 +19,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
memcpy(str, data, size);
str[size] = '\0';
- r = udev_rule_parse_value(str, &value, &endpos);
+ r = udev_rule_parse_value(str, &value, &endpos, &is_case_sensitive);
if (r < 0) {
/* not modified on failure */
assert_se(value == UINT_TO_PTR(0x12345678U));
diff --git a/src/udev/test-udev-rules.c b/src/udev/test-udev-rules.c
index b62b08b56e..9e8ae87a8c 100644
--- a/src/udev/test-udev-rules.c
+++ b/src/udev/test-udev-rules.c
@@ -4,15 +4,16 @@
#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) {
+static void test_udev_rule_parse_value_one(const char *in, const char *expected_value, bool expected_case_insensitive, int expected_retval) {
_cleanup_free_ char *str = NULL;
char *value = UINT_TO_PTR(0x12345678U);
char *endpos = UINT_TO_PTR(0x87654321U);
+ bool i;
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);
+ assert_se(udev_rule_parse_value(str, &value, &endpos, &i) == expected_retval);
if (expected_retval < 0) {
/* not modified on failure */
assert_se(value == UINT_TO_PTR(0x12345678U));
@@ -25,6 +26,7 @@ static void test_udev_rule_parse_value_one(const char *in, const char *expected_
* so it could be safely interpreted as nulstr.
*/
assert_se(value[strlen(value) + 1] == '\0');
+ assert_se(i == expected_case_insensitive);
}
}
@@ -33,45 +35,61 @@ TEST(udev_rule_parse_value) {
* 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);
+ test_udev_rule_parse_value_one("\"valid operand\"", "valid operand", /* case_insensitive = */ false, 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);
+ test_udev_rule_parse_value_one("\"va'l\\'id\\\"op\\\"erand\"", "va'l\\'id\"op\"erand", /* case_insensitive = */ false, 0);
+ test_udev_rule_parse_value_one("no quotes", NULL, /* case_insensitive = */ false, -EINVAL);
+ test_udev_rule_parse_value_one("\"\\\\a\\b\\x\\y\"", "\\\\a\\b\\x\\y", /* case_insensitive = */ false, 0);
+ test_udev_rule_parse_value_one("\"reject\0nul\"", NULL, /* case_insensitive = */ false, -EINVAL);
/* input: e"" */
- test_udev_rule_parse_value_one("e\"\"", "", 0);
+ test_udev_rule_parse_value_one("e\"\"", "", /* case_insensitive = */ false, 0);
/* input: e"1234" */
- test_udev_rule_parse_value_one("e\"1234\"", "1234", 0);
+ test_udev_rule_parse_value_one("e\"1234\"", "1234", /* case_insensitive = */ false, 0);
/* input: e"\"" */
- test_udev_rule_parse_value_one("e\"\\\"\"", "\"", 0);
+ test_udev_rule_parse_value_one("e\"\\\"\"", "\"", /* case_insensitive = */ false, 0);
/* input: e"\ */
- test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL);
+ test_udev_rule_parse_value_one("e\"\\", NULL, /* case_insensitive = */ false, -EINVAL);
/* input: e"\" */
- test_udev_rule_parse_value_one("e\"\\\"", NULL, -EINVAL);
+ test_udev_rule_parse_value_one("e\"\\\"", NULL, /* case_insensitive = */ false, -EINVAL);
/* input: e"\\" */
- test_udev_rule_parse_value_one("e\"\\\\\"", "\\", 0);
+ test_udev_rule_parse_value_one("e\"\\\\\"", "\\", /* case_insensitive = */ false, 0);
/* input: e"\\\" */
- test_udev_rule_parse_value_one("e\"\\\\\\\"", NULL, -EINVAL);
+ test_udev_rule_parse_value_one("e\"\\\\\\\"", NULL, /* case_insensitive = */ false, -EINVAL);
/* input: e"\\\"" */
- test_udev_rule_parse_value_one("e\"\\\\\\\"\"", "\\\"", 0);
+ test_udev_rule_parse_value_one("e\"\\\\\\\"\"", "\\\"", /* case_insensitive = */ false, 0);
/* input: e"\\\\" */
- test_udev_rule_parse_value_one("e\"\\\\\\\\\"", "\\\\", 0);
+ test_udev_rule_parse_value_one("e\"\\\\\\\\\"", "\\\\", /* case_insensitive = */ false, 0);
/* input: e"operand with newline\n" */
- test_udev_rule_parse_value_one("e\"operand with newline\\n\"", "operand with newline\n", 0);
+ test_udev_rule_parse_value_one("e\"operand with newline\\n\"", "operand with newline\n", /* case_insensitive = */ false, 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);
+ "e\"single\\rcharacter\\t\\aescape\\bsequence\"", "single\rcharacter\t\aescape\bsequence", /* case_insensitive = */ false, 0);
/* input: e"reject\invalid escape sequence" */
- test_udev_rule_parse_value_one("e\"reject\\invalid escape sequence", NULL, -EINVAL);
+ test_udev_rule_parse_value_one("e\"reject\\invalid escape sequence", NULL, /* case_insensitive = */ false, -EINVAL);
/* input: e"\ */
- test_udev_rule_parse_value_one("e\"\\", NULL, -EINVAL);
+ test_udev_rule_parse_value_one("e\"\\", NULL, /* case_insensitive = */ false, -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);
+ /* case_insensitive = */ false, 0);
+ /* input: i"ABCD1234" */
+ test_udev_rule_parse_value_one("i\"ABCD1234\"", "ABCD1234", /* case_insensitive = */ true, 0);
+ /* input: i"ABCD1234" */
+ test_udev_rule_parse_value_one("e\"ABCD1234\"", "ABCD1234", /* case_insensitive = */ false, 0);
+ /* input: ei"\\"ABCD1234 */
+ test_udev_rule_parse_value_one("ei\"\\\\ABCD1234\"", "\\ABCD1234", /* case_insensitive = */ true, 0);
+ /* input: ie"\\"ABCD1234 */
+ test_udev_rule_parse_value_one("ie\"\\\\ABCD1234\"", "\\ABCD1234", /* case_insensitive = */ true, 0);
+ /* input: i */
+ test_udev_rule_parse_value_one("i", NULL, /* case_insensitive = */ false, -EINVAL);
+ /* input: ee"" */
+ test_udev_rule_parse_value_one("ee\"\"", NULL, /* case_insensitive = */ false, -EINVAL);
+ /* input: iei"" */
+ test_udev_rule_parse_value_one("iei\"\"", NULL, /* case_insensitive = */ false, -EINVAL);
+ /* input: a"" */
+ test_udev_rule_parse_value_one("a\"\"", NULL, /* case_insensitive = */ false, -EINVAL);
}
DEFINE_TEST_MAIN(LOG_DEBUG);
diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
index 26c5edc977..85ae1c2163 100644
--- a/src/udev/udev-rules.c
+++ b/src/udev/udev-rules.c
@@ -66,6 +66,7 @@ typedef enum {
_MATCH_TYPE_MASK = (1 << 5) - 1,
MATCH_REMOVE_TRAILING_WHITESPACE = 1 << 5, /* Remove trailing whitespaces in attribute */
+ MATCH_CASE_INSENSITIVE = 1 << 6, /* string or pattern is matched case-insensitively */
_MATCH_TYPE_INVALID = -EINVAL,
} UdevRuleMatchType;
@@ -299,6 +300,7 @@ struct UdevRules {
#define log_line_invalid_op(line, key) _log_line_invalid_token(line, key, "operator")
#define log_line_invalid_attr(line, key) _log_line_invalid_token(line, key, "attribute")
+#define log_line_invalid_prefix(line, key) _log_line_invalid_token(line, key, "prefix 'i'")
#define log_line_invalid_attr_format(line, key, attr, offset, hint) \
log_line_error_errno(line, SYNTHETIC_ERRNO(EINVAL), \
@@ -492,7 +494,7 @@ static bool type_has_nulstr_value(UdevRuleTokenType type) {
return type < TK_M_TEST || type == TK_M_RESULT;
}
-static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type, UdevRuleOperatorType op, char *value, void *data) {
+static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type, UdevRuleOperatorType op, char *value, void *data, bool is_case_insensitive) {
_cleanup_(udev_rule_token_freep) UdevRuleToken *token = NULL;
UdevRuleMatchType match_type = _MATCH_TYPE_INVALID;
UdevRuleSubstituteType subst_type = _SUBST_TYPE_INVALID;
@@ -567,6 +569,8 @@ static int rule_line_add_token(UdevRuleLine *rule_line, UdevRuleTokenType type,
subst_type = rule_get_substitution_type(data);
}
+ SET_FLAG(match_type, MATCH_CASE_INSENSITIVE, is_case_insensitive);
+
token = new(UdevRuleToken, 1);
if (!token)
return -ENOMEM;
@@ -625,7 +629,7 @@ static int check_attr_format_and_warn(UdevRuleLine *line, const char *key, const
return 0;
}
-static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, UdevRuleOperatorType op, char *value) {
+static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, UdevRuleOperatorType op, char *value, bool is_case_insensitive) {
ResolveNameTiming resolve_name_timing = LINE_GET_RULES(rule_line)->resolve_name_timing;
bool is_match = IN_SET(op, OP_MATCH, OP_NOMATCH);
int r;
@@ -633,35 +637,39 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
assert(key);
assert(value);
+ if (!is_match && is_case_insensitive)
+ return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
+ "Invalid prefix 'i' for '%s'. The 'i' prefix can be specified only for '==' or '!=' operator.", key);
+
if (streq(key, "ACTION")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_ACTION, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_ACTION, op, value, NULL, is_case_insensitive);
} else if (streq(key, "DEVPATH")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_DEVPATH, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_DEVPATH, op, value, NULL, is_case_insensitive);
} else if (streq(key, "KERNEL")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_KERNEL, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_KERNEL, op, value, NULL, is_case_insensitive);
} else if (streq(key, "SYMLINK")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match) {
check_value_format_and_warn(rule_line, key, value, false);
- r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_A_DEVLINK, op, value, NULL, /* is_case_insensitive = */ false);
} else
- r = rule_line_add_token(rule_line, TK_M_DEVLINK, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_DEVLINK, op, value, NULL, is_case_insensitive);
} else if (streq(key, "NAME")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
@@ -681,9 +689,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
"Ignoring NAME=\"\", as udev will not delete any network interfaces.");
check_value_format_and_warn(rule_line, key, value, false);
- r = rule_line_add_token(rule_line, TK_A_NAME, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_A_NAME, op, value, NULL, /* is_case_insensitive = */ false);
} else
- r = rule_line_add_token(rule_line, TK_M_NAME, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_NAME, op, value, NULL, is_case_insensitive);
} else if (streq(key, "ENV")) {
if (isempty(attr))
return log_line_invalid_attr(rule_line, key);
@@ -701,15 +709,15 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
check_value_format_and_warn(rule_line, key, value, false);
- r = rule_line_add_token(rule_line, TK_A_ENV, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_A_ENV, op, value, attr, /* is_case_insensitive = */ false);
} else
- r = rule_line_add_token(rule_line, TK_M_ENV, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_M_ENV, op, value, attr, is_case_insensitive);
} else if (streq(key, "CONST")) {
if (isempty(attr) || !STR_IN_SET(attr, "arch", "virt"))
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_CONST, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_M_CONST, op, value, attr, is_case_insensitive);
} else if (streq(key, "TAG")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
@@ -721,9 +729,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (!is_match) {
check_value_format_and_warn(rule_line, key, value, true);
- r = rule_line_add_token(rule_line, TK_A_TAG, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_A_TAG, op, value, NULL, /* is_case_insensitive = */ false);
} else
- r = rule_line_add_token(rule_line, TK_M_TAG, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_TAG, op, value, NULL, is_case_insensitive);
} else if (streq(key, "SUBSYSTEM")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
@@ -733,14 +741,14 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (STR_IN_SET(value, "bus", "class"))
log_line_warning(rule_line, "\"%s\" must be specified as \"subsystem\".", value);
- r = rule_line_add_token(rule_line, TK_M_SUBSYSTEM, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_SUBSYSTEM, op, value, NULL, is_case_insensitive);
} else if (streq(key, "DRIVER")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_DRIVER, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_DRIVER, op, value, NULL, is_case_insensitive);
} else if (streq(key, "ATTR")) {
r = check_attr_format_and_warn(rule_line, key, attr);
if (r < 0)
@@ -754,9 +762,9 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (!is_match) {
check_value_format_and_warn(rule_line, key, value, false);
- r = rule_line_add_token(rule_line, TK_A_ATTR, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_A_ATTR, op, value, attr, /* is_case_insensitive = */ false);
} else
- r = rule_line_add_token(rule_line, TK_M_ATTR, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_M_ATTR, op, value, attr, is_case_insensitive);
} else if (streq(key, "SYSCTL")) {
r = check_attr_format_and_warn(rule_line, key, attr);
if (r < 0)
@@ -770,30 +778,30 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (!is_match) {
check_value_format_and_warn(rule_line, key, value, false);
- r = rule_line_add_token(rule_line, TK_A_SYSCTL, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_A_SYSCTL, op, value, attr, /* is_case_insensitive = */ false);
} else
- r = rule_line_add_token(rule_line, TK_M_SYSCTL, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_M_SYSCTL, op, value, attr, is_case_insensitive);
} else if (streq(key, "KERNELS")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_PARENTS_KERNEL, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_PARENTS_KERNEL, op, value, NULL, is_case_insensitive);
} else if (streq(key, "SUBSYSTEMS")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_PARENTS_SUBSYSTEM, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_PARENTS_SUBSYSTEM, op, value, NULL, is_case_insensitive);
} else if (streq(key, "DRIVERS")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_PARENTS_DRIVER, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_PARENTS_DRIVER, op, value, NULL, is_case_insensitive);
} else if (streq(key, "ATTRS")) {
r = check_attr_format_and_warn(rule_line, key, attr);
if (r < 0)
@@ -806,14 +814,14 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (strstr(attr, "../"))
log_line_warning(rule_line, "Direct reference to parent sysfs directory, may break in future kernels.");
- r = rule_line_add_token(rule_line, TK_M_PARENTS_ATTR, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_M_PARENTS_ATTR, op, value, attr, is_case_insensitive);
} else if (streq(key, "TAGS")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_PARENTS_TAG, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_PARENTS_TAG, op, value, NULL, is_case_insensitive);
} else if (streq(key, "TEST")) {
mode_t mode = MODE_INVALID;
@@ -825,8 +833,10 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
check_value_format_and_warn(rule_line, key, value, true);
if (!is_match)
return log_line_invalid_op(rule_line, key);
+ if (is_case_insensitive)
+ return log_line_invalid_prefix(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_TEST, op, value, MODE_TO_PTR(mode));
+ r = rule_line_add_token(rule_line, TK_M_TEST, op, value, MODE_TO_PTR(mode), is_case_insensitive);
} else if (streq(key, "PROGRAM")) {
if (attr)
return log_line_invalid_attr(rule_line, key);
@@ -835,8 +845,10 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
return log_line_invalid_op(rule_line, key);
if (!is_match)
op = OP_MATCH;
+ if (is_case_insensitive)
+ return log_line_invalid_prefix(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_PROGRAM, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_PROGRAM, op, value, NULL, /* is_case_insensitive */ false);
} else if (streq(key, "IMPORT")) {
if (isempty(attr))
return log_line_invalid_attr(rule_line, key);
@@ -845,18 +857,20 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
return log_line_invalid_op(rule_line, key);
if (!is_match)
op = OP_MATCH;
+ if (is_case_insensitive)
+ return log_line_invalid_prefix(rule_line, key);
if (streq(attr, "file"))
- r = rule_line_add_token(rule_line, TK_M_IMPORT_FILE, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_IMPORT_FILE, op, value, NULL, /* is_case_insensitive = */ false);
else if (streq(attr, "program")) {
UdevBuiltinCommand cmd;
cmd = udev_builtin_lookup(value);
if (cmd >= 0) {
log_line_debug(rule_line, "Found builtin command '%s' for %s, replacing attribute.", value, key);
- r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd));
+ r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false);
} else
- r = rule_line_add_token(rule_line, TK_M_IMPORT_PROGRAM, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_IMPORT_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false);
} else if (streq(attr, "builtin")) {
UdevBuiltinCommand cmd;
@@ -864,13 +878,13 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (cmd < 0)
return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
"Unknown builtin command: %s", value);
- r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd));
+ r = rule_line_add_token(rule_line, TK_M_IMPORT_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false);
} else if (streq(attr, "db"))
- r = rule_line_add_token(rule_line, TK_M_IMPORT_DB, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_IMPORT_DB, op, value, NULL, /* is_case_insensitive = */ false);
else if (streq(attr, "cmdline"))
- r = rule_line_add_token(rule_line, TK_M_IMPORT_CMDLINE, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_IMPORT_CMDLINE, op, value, NULL, /* is_case_insensitive = */ false);
else if (streq(attr, "parent"))
- r = rule_line_add_token(rule_line, TK_M_IMPORT_PARENT, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_IMPORT_PARENT, op, value, NULL, /* is_case_insensitive = */ false);
else
return log_line_invalid_attr(rule_line, key);
} else if (streq(key, "RESULT")) {
@@ -879,7 +893,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (!is_match)
return log_line_invalid_op(rule_line, key);
- r = rule_line_add_token(rule_line, TK_M_RESULT, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_M_RESULT, op, value, NULL, is_case_insensitive);
} else if (streq(key, "OPTIONS")) {
char *tmp;
@@ -891,24 +905,24 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
op = OP_ASSIGN;
if (streq(value, "string_escape=none"))
- r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_NONE, op, NULL, NULL);
+ r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_NONE, op, NULL, NULL, /* is_case_insensitive = */ false);
else if (streq(value, "string_escape=replace"))
- r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_REPLACE, op, NULL, NULL);
+ r = rule_line_add_token(rule_line, TK_A_OPTIONS_STRING_ESCAPE_REPLACE, op, NULL, NULL, /* is_case_insensitive = */ false);
else if (streq(value, "db_persist"))
- r = rule_line_add_token(rule_line, TK_A_OPTIONS_DB_PERSIST, op, NULL, NULL);
+ r = rule_line_add_token(rule_line, TK_A_OPTIONS_DB_PERSIST, op, NULL, NULL, /* is_case_insensitive = */ false);
else if (streq(value, "watch"))
- r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(1));
+ r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(1), /* is_case_insensitive = */ false);
else if (streq(value, "nowatch"))
- r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(0));
+ r = rule_line_add_token(rule_line, TK_A_OPTIONS_INOTIFY_WATCH, op, NULL, INT_TO_PTR(0), /* is_case_insensitive = */ false);
else if ((tmp = startswith(value, "static_node=")))
- r = rule_line_add_token(rule_line, TK_A_OPTIONS_STATIC_NODE, op, tmp, NULL);
+ r = rule_line_add_token(rule_line, TK_A_OPTIONS_STATIC_NODE, op, tmp, NULL, /* is_case_insensitive = */ false);
else if ((tmp = startswith(value, "link_priority="))) {
int prio;
r = safe_atoi(tmp, &prio);
if (r < 0)
return log_line_error_errno(rule_line, r, "Failed to parse link priority '%s': %m", tmp);
- r = rule_line_add_token(rule_line, TK_A_OPTIONS_DEVLINK_PRIORITY, op, NULL, INT_TO_PTR(prio));
+ r = rule_line_add_token(rule_line, TK_A_OPTIONS_DEVLINK_PRIORITY, op, NULL, INT_TO_PTR(prio), /* is_case_insensitive = */ false);
} else if ((tmp = startswith(value, "log_level="))) {
int level;
@@ -919,7 +933,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (level < 0)
return log_line_error_errno(rule_line, level, "Failed to parse log level '%s': %m", tmp);
}
- r = rule_line_add_token(rule_line, TK_A_OPTIONS_LOG_LEVEL, op, NULL, INT_TO_PTR(level));
+ r = rule_line_add_token(rule_line, TK_A_OPTIONS_LOG_LEVEL, op, NULL, INT_TO_PTR(level), /* is_case_insensitive = */ false);
} else {
log_line_warning(rule_line, "Invalid value for OPTIONS key, ignoring: '%s'", value);
return 0;
@@ -937,17 +951,17 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
}
if (parse_uid(value, &uid) >= 0)
- r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid));
+ r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false);
else if (resolve_name_timing == RESOLVE_NAME_EARLY &&
rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) {
r = rule_resolve_user(rule_line, value, &uid);
if (r < 0)
return log_line_error_errno(rule_line, r, "Failed to resolve user name '%s': %m", value);
- r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid));
+ r = rule_line_add_token(rule_line, TK_A_OWNER_ID, op, NULL, UID_TO_PTR(uid), /* is_case_insensitive = */ false);
} else if (resolve_name_timing != RESOLVE_NAME_NEVER) {
check_value_format_and_warn(rule_line, key, value, true);
- r = rule_line_add_token(rule_line, TK_A_OWNER, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_A_OWNER, op, value, NULL, /* is_case_insensitive = */ false);
} else {
log_line_debug(rule_line, "User name resolution is disabled, ignoring %s=\"%s\".", key, value);
return 0;
@@ -965,17 +979,17 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
}
if (parse_gid(value, &gid) >= 0)
- r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid));
+ r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false);
else if (resolve_name_timing == RESOLVE_NAME_EARLY &&
rule_get_substitution_type(value) == SUBST_TYPE_PLAIN) {
r = rule_resolve_group(rule_line, value, &gid);
if (r < 0)
return log_line_error_errno(rule_line, r, "Failed to resolve group name '%s': %m", value);
- r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid));
+ r = rule_line_add_token(rule_line, TK_A_GROUP_ID, op, NULL, GID_TO_PTR(gid), /* is_case_insensitive = */ false);
} else if (resolve_name_timing != RESOLVE_NAME_NEVER) {
check_value_format_and_warn(rule_line, key, value, true);
- r = rule_line_add_token(rule_line, TK_A_GROUP, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_A_GROUP, op, value, NULL, /* is_case_insensitive = */ false);
} else {
log_line_debug(rule_line, "Resolving group name is disabled, ignoring GROUP=\"%s\".", value);
return 0;
@@ -993,10 +1007,10 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
}
if (parse_mode(value, &mode) >= 0)
- r = rule_line_add_token(rule_line, TK_A_MODE_ID, op, NULL, MODE_TO_PTR(mode));
+ r = rule_line_add_token(rule_line, TK_A_MODE_ID, op, NULL, MODE_TO_PTR(mode), /* is_case_insensitive = */ false);
else {
check_value_format_and_warn(rule_line, key, value, true);
- r = rule_line_add_token(rule_line, TK_A_MODE, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_A_MODE, op, value, NULL, /* is_case_insensitive = */ false);
}
} else if (streq(key, "SECLABEL")) {
if (isempty(attr))
@@ -1009,13 +1023,13 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
op = OP_ASSIGN;
}
- r = rule_line_add_token(rule_line, TK_A_SECLABEL, op, value, attr);
+ r = rule_line_add_token(rule_line, TK_A_SECLABEL, op, value, attr, /* is_case_insensitive = */ false);
} else if (streq(key, "RUN")) {
if (is_match || op == OP_REMOVE)
return log_line_invalid_op(rule_line, key);
check_value_format_and_warn(rule_line, key, value, true);
if (!attr || streq(attr, "program"))
- r = rule_line_add_token(rule_line, TK_A_RUN_PROGRAM, op, value, NULL);
+ r = rule_line_add_token(rule_line, TK_A_RUN_PROGRAM, op, value, NULL, /* is_case_insensitive = */ false);
else if (streq(attr, "builtin")) {
UdevBuiltinCommand cmd;
@@ -1023,7 +1037,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude
if (cmd < 0)
return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL),
"Unknown builtin command '%s', ignoring.", value);
- r = rule_line_add_token(rule_line, TK_A_RUN_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd));
+ r = rule_line_add_token(rule_line, TK_A_RUN_BUILTIN, op, value, UDEV_BUILTIN_CMD_TO_PTR(cmd), /* is_case_insensitive = */ false);
} else
return log_line_invalid_attr(rule_line, key);
} else if (streq(key, "GOTO")) {
@@ -1123,13 +1137,30 @@ static void check_token_delimiters(UdevRuleLine *rule_line, const char *line) {
}
}
-int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) {
+int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos, bool *ret_is_case_insensitive) {
char *i, *j;
- bool is_escaped;
+ bool is_escaped = false, is_case_insensitive = false;
+
+ assert(str);
+ assert(ret_value);
+ assert(ret_endpos);
+ assert(ret_is_case_insensitive);
+
+ /* check if string is prefixed with:
+ * - "e" for escaped
+ * - "i" for case insensitive match
+ *
+ * Note both e and i can be set but do not allow duplicates ("eei", "eii"). */
+ for (const char *k = str; *k != '"' && k < str + 2; k++)
+ if (*k == 'e' && !is_escaped)
+ is_escaped = true;
+ else if (*k == 'i' && !is_case_insensitive)
+ is_case_insensitive = true;
+ else
+ return -EINVAL;
/* value must be double quotated */
- is_escaped = str[0] == 'e';
- str += is_escaped;
+ str += is_escaped + is_case_insensitive;
if (str[0] != '"')
return -EINVAL;
@@ -1176,10 +1207,11 @@ int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos) {
*ret_value = str;
*ret_endpos = i + 1;
+ *ret_is_case_insensitive = is_case_insensitive;
return 0;
}
-static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOperatorType *ret_op, char **ret_value) {
+static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOperatorType *ret_op, char **ret_value, bool *ret_is_case_insensitive) {
char *key_begin, *key_end, *attr, *tmp;
UdevRuleOperatorType op;
int r;
@@ -1189,6 +1221,7 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper
assert(ret_key);
assert(ret_op);
assert(ret_value);
+ assert(ret_is_case_insensitive);
key_begin = skip_leading_chars(*line, WHITESPACE ",");
@@ -1223,7 +1256,7 @@ static int parse_line(char **line, char **ret_key, char **ret_attr, UdevRuleOper
tmp += op == OP_ASSIGN ? 1 : 2;
tmp = skip_leading_chars(tmp, NULL);
- r = udev_rule_parse_value(tmp, ret_value, line);
+ r = udev_rule_parse_value(tmp, ret_value, line, ret_is_case_insensitive);
if (r < 0)
return r;
@@ -1295,17 +1328,18 @@ static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned
for (p = rule_line->line; !isempty(p); ) {
char *key, *attr, *value;
UdevRuleOperatorType op;
+ bool is_case_insensitive;
if (extra_checks)
check_token_delimiters(rule_line, p);
- r = parse_line(&p, &key, &attr, &op, &value);
+ r = parse_line(&p, &key, &attr, &op, &value, &is_case_insensitive);
if (r < 0)
return log_line_error_errno(rule_line, r, "Invalid key/value pair, ignoring.");
if (r == 0)
break;
- r = parse_token(rule_line, key, attr, op, value);
+ r = parse_token(rule_line, key, attr, op, value, is_case_insensitive);
if (r < 0)
return r;
}
@@ -1698,7 +1732,7 @@ bool udev_rules_should_reload(UdevRules *rules) {
static bool token_match_string(UdevRuleToken *token, const char *str) {
const char *value;
- bool match = false;
+ bool match = false, case_insensitive;
assert(token);
assert(token->value);
@@ -1706,13 +1740,17 @@ static bool token_match_string(UdevRuleToken *token, const char *str) {
str = strempty(str);
value = token->value;
+ case_insensitive = FLAGS_SET(token->match_type, MATCH_CASE_INSENSITIVE);
switch (token->match_type & _MATCH_TYPE_MASK) {
case MATCH_TYPE_EMPTY:
match = isempty(str);
break;
case MATCH_TYPE_SUBSYSTEM:
- match = STR_IN_SET(str, "subsystem", "class", "bus");
+ if (case_insensitive)
+ match = STRCASE_IN_SET(str, "subsystem", "class", "bus");
+ else
+ match = STR_IN_SET(str, "subsystem", "class", "bus");
break;
case MATCH_TYPE_PLAIN_WITH_EMPTY:
if (isempty(str)) {
@@ -1722,7 +1760,7 @@ static bool token_match_string(UdevRuleToken *token, const char *str) {
_fallthrough_;
case MATCH_TYPE_PLAIN:
NULSTR_FOREACH(i, value)
- if (streq(i, str)) {
+ if (case_insensitive ? strcaseeq(i, str) : streq(i, str)) {
match = true;
break;
}
@@ -1735,7 +1773,7 @@ static bool token_match_string(UdevRuleToken *token, const char *str) {
_fallthrough_;
case MATCH_TYPE_GLOB:
NULSTR_FOREACH(i, value)
- if ((fnmatch(i, str, 0) == 0)) {
+ if ((fnmatch(i, str, case_insensitive ? FNM_CASEFOLD : 0) == 0)) {
match = true;
break;
}
diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h
index ceb454da8d..61d517d9da 100644
--- a/src/udev/udev-rules.h
+++ b/src/udev/udev-rules.h
@@ -29,7 +29,7 @@ typedef enum ResolveNameTiming {
_RESOLVE_NAME_TIMING_INVALID = -EINVAL,
} ResolveNameTiming;
-int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos);
+int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos, bool *ret_is_case_insensitive);
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);