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>
This commit is contained in:
Yu Watanabe 2024-09-15 13:49:32 +09:00
parent 68fdef46a7
commit 5f5c5c48b9
7 changed files with 167 additions and 89 deletions

View File

@ -141,6 +141,12 @@
For example, e"string\n" is parsed as 7 characters: 6 lowercase letters and a newline.
This can be useful for writing special characters when a kernel driver requires them.</para>
<para>The string can be prefixed with a lowercase i (i"string") to mark that the string or pattern
will match case-insensitively. For example, i"foo" will match
<literal>foo</literal>, <literal>FOO</literal>, <literal>FoO</literal> and so on. The prefix can be
used only for match (<literal>==</literal>) or unmatch (<literal>!=</literal>) rules, e.g.
<varname>ATTR{foo}==i"abcd"</varname>.</para>
<para>Please note that <constant>NUL</constant> is not allowed in either string variant.</para>
</refsect2>

View File

@ -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));

View File

@ -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);

View File

@ -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,12 +1740,16 @@ 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:
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:
@ -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;
}

View File

@ -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);

View File

@ -2313,6 +2313,17 @@ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \"printf %%s 'foo1 foo2' | grep 'foo1 f
SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sd*", SYMLINK+="blockdev"
KERNEL=="sda6", OPTIONS+="link_priority=10"
"""),
Rules.new(
"case insensitive match",
Device(
"/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
exp_links = ["ok"],
),
rules = r"""
KERNEL==i"SDA1", SUBSYSTEMS==i"SCSI", ATTRS{vendor}==i"a?a", SYMLINK+="ok"
"""),
]
def fork_and_run_udev(action: str, rules: Rules) -> None:

View File

@ -237,6 +237,8 @@ test_syntax_error 'ENV=="b"' 'Invalid attribute for ENV.'
test_syntax_error 'ENV{a}-="b"' 'Invalid operator for ENV.'
test_syntax_error 'ENV{a}:="b"' "ENV key takes '==', '!=', '=', or '+=' operator, assuming '='."
test_syntax_error 'ENV{ACTION}="b"' "Invalid ENV attribute. 'ACTION' cannot be set."
test_syntax_error 'ENV{a}=i"b"' "Invalid prefix 'i' for 'ENV'. The 'i' prefix can be specified only for '==' or '!=' operator."
test_syntax_error 'ENV{a}+=i"b"' "Invalid prefix 'i' for 'ENV'. The 'i' prefix can be specified only for '==' or '!=' operator."
test_syntax_error 'CONST=="b"' 'Invalid attribute for CONST.'
test_syntax_error 'CONST{a}=="b"' 'Invalid attribute for CONST.'
test_syntax_error 'CONST{arch}="b"' 'Invalid operator for CONST.'
@ -275,10 +277,12 @@ test_syntax_error 'TEST{0644}="b"' 'Invalid operator for TEST.'
test_syntax_error 'PROGRAM{a}=="b"' 'Invalid attribute for PROGRAM.'
test_syntax_error 'PROGRAM-="b"' 'Invalid operator for PROGRAM.'
test_syntax_error 'PROGRAM=="%", NAME="b"' 'Invalid value "%" for PROGRAM (char 1: invalid substitution type), ignoring.'
test_syntax_error 'PROGRAM==i"b"' "Invalid prefix 'i' for PROGRAM."
test_syntax_error 'IMPORT="b"' 'Invalid attribute for IMPORT.'
test_syntax_error 'IMPORT{a}="b"' 'Invalid attribute for IMPORT.'
test_syntax_error 'IMPORT{a}-="b"' 'Invalid operator for IMPORT.'
test_syntax_error 'IMPORT{file}=="%", NAME="b"' 'Invalid value "%" for IMPORT (char 1: invalid substitution type), ignoring.'
test_syntax_error 'IMPORT{file}==i"a", NAME="b"' "Invalid prefix 'i' for IMPORT."
test_syntax_error 'IMPORT{builtin}!="foo"' 'Unknown builtin command: foo'
test_syntax_error 'RESULT{a}=="b"' 'Invalid attribute for RESULT.'
test_syntax_error 'RESULT:="b"' 'Invalid operator for RESULT.'