Merge pull request #26980 from ldv-alt/udevadm-verify

udevadm verify: add more checks
This commit is contained in:
Yu Watanabe
2023-03-28 13:38:12 +09:00
committed by GitHub
7 changed files with 269 additions and 168 deletions
+2 -2
View File
@@ -41,7 +41,7 @@ KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ATTRS{nsid}=="?*", ENV{ID_NSI
# obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*",\
KERNEL=="nvme*[0-9]n*[0-9]", ENV{DEVTYPE}=="disk", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}_$env{ID_NSID}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}"
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{serial}=="?*", ENV{ID_SERIAL_SHORT}="$attr{serial}"
@@ -51,7 +51,7 @@ KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ATTRS{nsid}=="?*"
# obsolete symlink that might get overridden on adding a new nvme controller, kept for backward compatibility
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*",\
KERNEL=="nvme*[0-9]n*[0-9]p*[0-9]", ENV{DEVTYPE}=="partition", ENV{ID_MODEL}=="?*", ENV{ID_SERIAL_SHORT}=="?*", ENV{ID_NSID}=="?*", \
OPTIONS="string_escape=replace", ENV{ID_SERIAL}="$env{ID_MODEL}_$env{ID_SERIAL_SHORT}_$env{ID_NSID}", SYMLINK+="disk/by-id/nvme-$env{ID_SERIAL}-part%n"
# virtio-blk
+1 -1
View File
@@ -2,7 +2,7 @@
ACTION=="remove", GOTO="camera_end"
SUBSYSTEM=="video4linux", ENV{ID_BUS}="usb" , \
SUBSYSTEM=="video4linux", ENV{ID_BUS}="usb", \
IMPORT{builtin}="hwdb 'camera:usb:v$env{ID_VENDOR_ID}p$env{ID_MODEL_ID}:name:$attr{name}:'", \
GOTO="camera_end"
+1 -1
View File
@@ -27,7 +27,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
fflush(f);
assert_se(rules = udev_rules_new(RESOLVE_NAME_EARLY));
r = udev_rules_parse_file(rules, filename, NULL);
r = udev_rules_parse_file(rules, filename, /* extra_checks = */ false, NULL);
log_info_errno(r, "Parsing %s: %m", filename);
assert_se(r >= 0 || /* OK */
r == -ENOBUFS); /* line length exceeded */
+206 -140
View File
@@ -1069,6 +1069,51 @@ static UdevRuleOperatorType parse_operator(const char *op) {
return _OP_TYPE_INVALID;
}
static void check_token_delimiters(UdevRuleLine *rule_line, const char *line) {
assert(rule_line);
size_t n_comma = 0;
bool ws_before_comma = false, ws_after_comma = false;
const char *p;
for (p = line; !isempty(p); ++p) {
if (*p == ',')
++n_comma;
else if (strchr(WHITESPACE, *p)) {
if (n_comma > 0)
ws_after_comma = true;
else
ws_before_comma = true;
} else
break;
}
if (line == rule_line->line) {
/* this is the first token of the rule */
if (n_comma > 0)
log_line_warning(rule_line, "Stray leading comma.");
} else if (isempty(p)) {
/* there are no more tokens in the rule */
if (n_comma > 0)
log_line_warning(rule_line, "Stray trailing comma.");
} else {
/* single comma is expected */
if (n_comma == 0)
log_line_warning(rule_line, "A comma between tokens is expected.");
else if (n_comma > 1)
log_line_warning(rule_line, "More than one comma between tokens.");
/* whitespace after comma is expected */
if (n_comma > 0) {
if (ws_before_comma)
log_line_warning(rule_line, "Stray whitespace before comma.");
if (!ws_after_comma)
log_line_warning(rule_line, "Whitespace after comma is expected.");
} else if (!ws_before_comma && !ws_after_comma)
log_line_warning(rule_line, "Whitespace between tokens is expected.");
}
}
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;
@@ -1140,7 +1185,7 @@ static void sort_tokens(UdevRuleLine *rule_line) {
}
}
static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned line_nr) {
static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned line_nr, bool extra_checks) {
_cleanup_(udev_rule_line_freep) UdevRuleLine *rule_line = NULL;
_cleanup_free_ char *line = NULL;
char *p;
@@ -1172,6 +1217,9 @@ static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned
char *key, *attr, *value;
UdevRuleOperatorType op;
if (extra_checks)
check_token_delimiters(rule_line, p);
r = parse_line(&p, &key, &attr, &op, &value);
if (r < 0)
return log_line_error_errno(rule_line, r, "Invalid key/value pair, ignoring.");
@@ -1184,7 +1232,7 @@ static int rule_add_line(UdevRuleFile *rule_file, const char *line_str, unsigned
}
if (rule_line->type == 0) {
log_line_warning(rule_line, "The line takes no effect, ignoring.");
log_line_warning(rule_line, "The line has no effect, ignoring.");
return 0;
}
@@ -1216,7 +1264,7 @@ static void rule_resolve_goto(UdevRuleFile *rule_file) {
line->goto_label = NULL;
if ((line->type & ~(LINE_HAS_LABEL|LINE_IS_REFERENCED)) == 0) {
log_line_notice(line, "The line takes no effect any more, dropping");
log_line_notice(line, "The line has no effect any more, dropping.");
/* LINE_IS_REFERENCED implies LINE_HAS_LABEL */
if (line->type & LINE_HAS_LABEL)
udev_rule_line_clear_tokens(line);
@@ -1227,7 +1275,155 @@ static void rule_resolve_goto(UdevRuleFile *rule_file) {
}
}
int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile **ret) {
static bool token_data_is_string(UdevRuleTokenType type) {
return IN_SET(type, TK_M_ENV,
TK_M_CONST,
TK_M_ATTR,
TK_M_SYSCTL,
TK_M_PARENTS_ATTR,
TK_A_SECLABEL,
TK_A_ENV,
TK_A_ATTR,
TK_A_SYSCTL);
}
static bool token_type_and_data_eq(const UdevRuleToken *a, const UdevRuleToken *b) {
assert(a);
assert(b);
return a->type == b->type &&
(token_data_is_string(a->type) ? streq_ptr(a->data, b->data) : (a->data == b->data));
}
static bool nulstr_eq(const char *a, const char *b) {
NULSTR_FOREACH(i, a)
if (!nulstr_contains(b, i))
return false;
NULSTR_FOREACH(i, b)
if (!nulstr_contains(a, i))
return false;
return true;
}
static bool token_type_and_value_eq(const UdevRuleToken *a, const UdevRuleToken *b) {
assert(a);
assert(b);
if (a->type != b->type ||
a->match_type != b->match_type)
return false;
/* token value is ignored for certain match types */
if (IN_SET(a->match_type, MATCH_TYPE_EMPTY, MATCH_TYPE_SUBSYSTEM))
return true;
return type_has_nulstr_value(a->type) ? nulstr_eq(a->value, b->value) :
streq_ptr(a->value, b->value);
}
static bool conflicting_op(UdevRuleOperatorType a, UdevRuleOperatorType b) {
return (a == OP_MATCH && b == OP_NOMATCH) ||
(a == OP_NOMATCH && b == OP_MATCH);
}
/* test whether all fields besides UdevRuleOperatorType of two tokens match */
static bool tokens_eq(const UdevRuleToken *a, const UdevRuleToken *b) {
assert(a);
assert(b);
return a->attr_subst_type == b->attr_subst_type &&
a->attr_match_remove_trailing_whitespace == b->attr_match_remove_trailing_whitespace &&
token_type_and_value_eq(a, b) &&
token_type_and_data_eq(a, b);
}
static bool nulstr_tokens_conflict(const UdevRuleToken *a, const UdevRuleToken *b) {
assert(a);
assert(b);
if (!(a->type == b->type &&
type_has_nulstr_value(a->type) &&
a->op == b->op &&
a->op == OP_MATCH &&
a->match_type == b->match_type &&
a->attr_subst_type == b->attr_subst_type &&
a->attr_match_remove_trailing_whitespace == b->attr_match_remove_trailing_whitespace &&
token_type_and_data_eq(a, b)))
return false;
if (a->match_type == MATCH_TYPE_PLAIN) {
NULSTR_FOREACH(i, a->value)
if (nulstr_contains(b->value, i))
return false;
return true;
}
if (a->match_type == MATCH_TYPE_GLOB) {
NULSTR_FOREACH(i, a->value) {
size_t i_n = strcspn(i, GLOB_CHARS);
if (i_n == 0)
return false;
NULSTR_FOREACH(j, b->value) {
size_t j_n = strcspn(j, GLOB_CHARS);
if (j_n == 0 || strneq(i, j, MIN(i_n, j_n)))
return false;
}
}
return true;
}
return false;
}
static void udev_check_unused_labels(UdevRuleLine *line) {
assert(line);
if (FLAGS_SET(line->type, LINE_HAS_LABEL) &&
!FLAGS_SET(line->type, LINE_IS_REFERENCED))
log_line_warning(line, "LABEL=\"%s\" is unused.", line->label);
}
static void udev_check_conflicts_duplicates(UdevRuleLine *line) {
assert(line);
bool conflicts = false, duplicates = false;
LIST_FOREACH(tokens, token, line->tokens)
LIST_FOREACH(tokens, i, token->tokens_next) {
bool new_conflicts = false, new_duplicates = false;
if (tokens_eq(token, i)) {
if (!duplicates && token->op == i->op)
new_duplicates = true;
if (!conflicts && conflicting_op(token->op, i->op))
new_conflicts = true;
} else if (!conflicts && nulstr_tokens_conflict(token, i))
new_conflicts = true;
else
continue;
if (new_duplicates) {
duplicates = new_duplicates;
log_line_warning(line, "duplicate expressions");
}
if (new_conflicts) {
conflicts = new_conflicts;
log_line_error(line, "conflicting match expressions, the line has no effect");
}
if (conflicts && duplicates)
return;
}
}
static void udev_check_rule_line(UdevRuleLine *line) {
udev_check_unused_labels(line);
udev_check_conflicts_duplicates(line);
}
int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret) {
_cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL;
_cleanup_free_ char *continuation = NULL, *name = NULL;
_cleanup_fclose_ FILE *f = NULL;
@@ -1326,7 +1522,7 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile *
if (ignore_line)
log_file_error(rule_file, line_nr, "Line is too long, ignored");
else if (len > 0)
(void) rule_add_line(rule_file, line, line_nr);
(void) rule_add_line(rule_file, line, line_nr, extra_checks);
continuation = mfree(continuation);
ignore_line = false;
@@ -1338,6 +1534,10 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile *
rule_resolve_goto(rule_file);
if (extra_checks)
LIST_FOREACH(rule_lines, line, rule_file->rule_lines)
udev_check_rule_line(line);
if (ret)
*ret = rule_file;
@@ -1345,143 +1545,9 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile *
return 1;
}
static bool token_data_is_string(UdevRuleTokenType type) {
return IN_SET(type, TK_M_ENV,
TK_M_CONST,
TK_M_ATTR,
TK_M_SYSCTL,
TK_M_PARENTS_ATTR,
TK_A_SECLABEL,
TK_A_ENV,
TK_A_ATTR,
TK_A_SYSCTL);
}
static bool token_type_and_data_eq(const UdevRuleToken *a, const UdevRuleToken *b) {
assert(a);
assert(b);
return a->type == b->type &&
(token_data_is_string(a->type) ? streq_ptr(a->data, b->data) : (a->data == b->data));
}
static bool nulstr_eq(const char *a, const char *b) {
NULSTR_FOREACH(i, a)
if (!nulstr_contains(b, i))
return false;
NULSTR_FOREACH(i, b)
if (!nulstr_contains(a, i))
return false;
return true;
}
static bool token_type_and_value_eq(const UdevRuleToken *a, const UdevRuleToken *b) {
assert(a);
assert(b);
if (a->type != b->type ||
a->match_type != b->match_type)
return false;
/* token value is ignored for certain match types */
if (IN_SET(a->match_type, MATCH_TYPE_EMPTY, MATCH_TYPE_SUBSYSTEM))
return true;
return type_has_nulstr_value(a->type) ? nulstr_eq(a->value, b->value) :
streq_ptr(a->value, b->value);
}
static bool conflicting_op(UdevRuleOperatorType a, UdevRuleOperatorType b) {
return (a == OP_MATCH && b == OP_NOMATCH) ||
(a == OP_NOMATCH && b == OP_MATCH);
}
/* test whether all fields besides UdevRuleOperatorType of two tokens match */
static bool tokens_eq(const UdevRuleToken *a, const UdevRuleToken *b) {
assert(a);
assert(b);
return a->attr_subst_type == b->attr_subst_type &&
a->attr_match_remove_trailing_whitespace == b->attr_match_remove_trailing_whitespace &&
token_type_and_value_eq(a, b) &&
token_type_and_data_eq(a, b);
}
static bool nulstr_tokens_conflict(const UdevRuleToken *a, const UdevRuleToken *b) {
assert(a);
assert(b);
if (!(a->type == b->type &&
type_has_nulstr_value(a->type) &&
a->op == b->op &&
a->op == OP_MATCH &&
a->match_type == b->match_type &&
a->match_type == MATCH_TYPE_PLAIN &&
a->attr_subst_type == b->attr_subst_type &&
a->attr_match_remove_trailing_whitespace == b->attr_match_remove_trailing_whitespace &&
token_type_and_data_eq(a, b)))
return false;
NULSTR_FOREACH(i, a->value)
if (nulstr_contains(b->value, i))
return false;
return true;
}
static void udev_check_unused_labels(UdevRuleLine *line) {
assert(line);
if (FLAGS_SET(line->type, LINE_HAS_LABEL) &&
!FLAGS_SET(line->type, LINE_IS_REFERENCED))
log_line_warning(line, "LABEL=\"%s\" is unused.", line->label);
}
static void udev_check_conflicts_duplicates(UdevRuleLine *line) {
assert(line);
bool conflicts = false, duplicates = false;
LIST_FOREACH(tokens, token, line->tokens)
LIST_FOREACH(tokens, i, token->tokens_next) {
bool new_conflicts = false, new_duplicates = false;
if (tokens_eq(token, i)) {
if (!duplicates && token->op == i->op)
new_duplicates = true;
if (!conflicts && conflicting_op(token->op, i->op))
new_conflicts = true;
} else if (!conflicts && nulstr_tokens_conflict(token, i))
new_conflicts = true;
else
continue;
if (new_duplicates) {
duplicates = new_duplicates;
log_line_warning(line, "duplicate expressions");
}
if (new_conflicts) {
conflicts = new_conflicts;
log_line_error(line, "conflicting match expressions, the line takes no effect");
}
if (conflicts && duplicates)
return;
}
}
static void udev_check_rule_line(UdevRuleLine *line) {
udev_check_unused_labels(line);
udev_check_conflicts_duplicates(line);
}
unsigned udev_rule_file_get_issues(UdevRuleFile *rule_file) {
assert(rule_file);
LIST_FOREACH(rule_lines, line, rule_file->rule_lines)
udev_check_rule_line(line);
return rule_file->issues;
}
@@ -1513,7 +1579,7 @@ int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing
return log_debug_errno(r, "Failed to enumerate rules files: %m");
STRV_FOREACH(f, files) {
r = udev_rules_parse_file(rules, *f, NULL);
r = udev_rules_parse_file(rules, *f, /* extra_checks = */ false, NULL);
if (r < 0)
log_debug_errno(r, "Failed to read rules file %s, ignoring: %m", *f);
}
+1 -1
View File
@@ -18,7 +18,7 @@ typedef enum {
_ESCAPE_TYPE_INVALID = -EINVAL,
} UdevRuleEscapeType;
int udev_rules_parse_file(UdevRules *rules, const char *filename, UdevRuleFile **ret);
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);
int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing);
+1 -1
View File
@@ -104,7 +104,7 @@ static int verify_rules_file(UdevRules *rules, const char *fname) {
UdevRuleFile *file;
int r;
r = udev_rules_parse_file(rules, fname, &file);
r = udev_rules_parse_file(rules, fname, /* extra_checks = */ true, &file);
if (r < 0)
return log_error_errno(r, "Failed to parse rules file %s: %m", fname);
if (r == 0) /* empty file. */
+57 -22
View File
@@ -132,7 +132,7 @@ cp "${workdir}/default_output_1_fail" "${exo}"
assert_1 "${rules}"
{
printf 'RUN+="/bin/true"%8175s\\\n' ' '
printf 'RUN+="/bin/true",%8174s\\\n' ' '
printf 'RUN+="/bin/false"%8174s\\\n' ' '
echo
} >"${rules}"
@@ -173,7 +173,7 @@ EOF
test_syntax_error '=' 'Invalid key/value pair, ignoring.'
test_syntax_error 'ACTION{a}=="b"' 'Invalid attribute for ACTION.'
test_syntax_error 'ACTION:="b"' 'Invalid operator for ACTION.'
test_syntax_error 'ACTION=="b"' 'The line takes no effect, ignoring.'
test_syntax_error 'ACTION=="b"' 'The line has no effect, ignoring.'
test_syntax_error 'DEVPATH{a}=="b"' 'Invalid attribute for DEVPATH.'
test_syntax_error 'DEVPATH:="b"' 'Invalid operator for DEVPATH.'
test_syntax_error 'KERNEL{a}=="b"' 'Invalid attribute for KERNEL.'
@@ -201,7 +201,7 @@ test_syntax_error 'TAGS{a}=="b"' 'Invalid attribute for TAGS.'
test_syntax_error 'TAGS:="a"' 'Invalid operator for TAGS.'
test_syntax_error 'SUBSYSTEM{a}=="b"' 'Invalid attribute for SUBSYSTEM.'
test_syntax_error 'SUBSYSTEM:="b"' 'Invalid operator for SUBSYSTEM.'
test_syntax_error 'SUBSYSTEM=="bus" NAME="b"' '"bus" must be specified as "subsystem".'
test_syntax_error 'SUBSYSTEM=="bus", NAME="b"' '"bus" must be specified as "subsystem".'
test_syntax_error 'SUBSYSTEMS{a}=="b"' 'Invalid attribute for SUBSYSTEMS.'
test_syntax_error 'SUBSYSTEMS:="b"' 'Invalid operator for SUBSYSTEMS.'
test_syntax_error 'DRIVER{a}=="b"' 'Invalid attribute for DRIVER.'
@@ -219,20 +219,20 @@ test_syntax_error 'SYSCTL{a}-="b"' 'Invalid operator for SYSCTL.'
test_syntax_error 'SYSCTL{a}+="b"' "SYSCTL key takes '==', '!=', or '=' operator, assuming '='."
test_syntax_error 'SYSCTL{a}="%?"' 'Invalid value "%?" for SYSCTL (char 1: invalid substitution type), ignoring.'
test_syntax_error 'ATTRS=""' 'Invalid attribute for ATTRS.'
test_syntax_error 'ATTRS{%}=="b" NAME="b"' 'Invalid attribute "%" for ATTRS (char 1: invalid substitution type), ignoring.'
test_syntax_error 'ATTRS{%}=="b", NAME="b"' 'Invalid attribute "%" for ATTRS (char 1: invalid substitution type), ignoring.'
test_syntax_error 'ATTRS{a}-="b"' 'Invalid operator for ATTRS.'
test_syntax_error 'ATTRS{device/}!="a" NAME="b"' "'device' link may not be available in future kernels."
test_syntax_error 'ATTRS{../}!="a" NAME="b"' 'Direct reference to parent sysfs directory, may break in future kernels.'
test_syntax_error 'ATTRS{device/}!="a", NAME="b"' "'device' link may not be available in future kernels."
test_syntax_error 'ATTRS{../}!="a", NAME="b"' 'Direct reference to parent sysfs directory, may break in future kernels.'
test_syntax_error 'TEST{a}=="b"' "Failed to parse mode 'a': Invalid argument"
test_syntax_error 'TEST{0}=="%" NAME="b"' 'Invalid value "%" for TEST (char 1: invalid substitution type), ignoring.'
test_syntax_error 'TEST{0}=="%", NAME="b"' 'Invalid value "%" for TEST (char 1: invalid substitution type), ignoring.'
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=="%", NAME="b"' 'Invalid value "%" for PROGRAM (char 1: invalid substitution type), ignoring.'
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}=="%", NAME="b"' 'Invalid value "%" for IMPORT (char 1: invalid substitution type), ignoring.'
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.'
@@ -241,7 +241,7 @@ test_syntax_error 'OPTIONS-="b"' 'Invalid operator for OPTIONS.'
test_syntax_error 'OPTIONS!="b"' 'Invalid operator for OPTIONS.'
test_syntax_error 'OPTIONS+="link_priority=a"' "Failed to parse link priority 'a': Invalid argument"
test_syntax_error 'OPTIONS:="log_level=a"' "Failed to parse log level 'a': Invalid argument"
test_syntax_error 'OPTIONS="a" NAME="b"' "Invalid value for OPTIONS key, ignoring: 'a'"
test_syntax_error 'OPTIONS="a", NAME="b"' "Invalid value for OPTIONS key, ignoring: 'a'"
test_syntax_error 'OWNER{a}="b"' 'Invalid attribute for OWNER.'
test_syntax_error 'OWNER-="b"' 'Invalid operator for OWNER.'
test_syntax_error 'OWNER!="b"' 'Invalid operator for OWNER.'
@@ -268,38 +268,51 @@ test_syntax_error 'RUN="%"' 'Invalid value "%" for RUN (char 1: invalid substitu
test_syntax_error 'RUN{builtin}+="foo"' "Unknown builtin command 'foo', ignoring"
test_syntax_error 'GOTO{a}="b"' 'Invalid attribute for GOTO.'
test_syntax_error 'GOTO=="b"' 'Invalid operator for GOTO.'
test_syntax_error 'NAME="a" GOTO="b"' 'GOTO="b" has no matching label, ignoring'
test_syntax_error 'GOTO="a" GOTO="b"
test_syntax_error 'NAME="a", GOTO="b"' 'GOTO="b" has no matching label, ignoring'
test_syntax_error 'GOTO="a", GOTO="b"
LABEL="a"' 'Contains multiple GOTO keys, ignoring GOTO="b".'
test_syntax_error 'LABEL{a}="b"' 'Invalid attribute for LABEL.'
test_syntax_error 'LABEL=="b"' 'Invalid operator for LABEL.'
test_syntax_error 'LABEL="b"' 'LABEL="b" is unused.'
test_syntax_error 'a="b"' "Invalid key 'a'"
test_syntax_error 'KERNEL=="", KERNEL=="?*", NAME="a"' 'conflicting match expressions, the line takes no effect'
test_syntax_error 'KERNEL=="abc", KERNEL!="abc", NAME="b"' 'conflicting match expressions, the line takes no effect'
test_syntax_error 'KERNEL=="|a|b", KERNEL!="b|a|", NAME="c"' 'conflicting match expressions, the line takes no effect'
test_syntax_error 'KERNEL=="a|b", KERNEL=="c|d|e", NAME="f"' 'conflicting match expressions, the line takes no effect'
test_syntax_error 'KERNEL=="", KERNEL=="?*", NAME="a"' 'conflicting match expressions, the line has no effect'
test_syntax_error 'KERNEL=="abc", KERNEL!="abc", NAME="b"' 'conflicting match expressions, the line has no effect'
test_syntax_error 'KERNEL=="|a|b", KERNEL!="b|a|", NAME="c"' 'conflicting match expressions, the line has no effect'
test_syntax_error 'KERNEL=="a|b", KERNEL=="c|d|e", NAME="f"' 'conflicting match expressions, the line has no effect'
# shellcheck disable=SC2016
test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}!="?*" ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
'conflicting match expressions, the line takes no effect'
test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}!="?*", ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
'conflicting match expressions, the line has no effect'
test_syntax_error 'ACTION=="a*", ACTION=="bc*", NAME="d"' 'conflicting match expressions, the line has no effect'
test_syntax_error 'ACTION=="a*|bc*", ACTION=="d*|ef*", NAME="g"' 'conflicting match expressions, the line has no effect'
test_syntax_error 'KERNEL!="", KERNEL=="?*", NAME="a"' 'duplicate expressions'
test_syntax_error 'KERNEL=="|a|b", KERNEL=="b|a|", NAME="c"' 'duplicate expressions'
# shellcheck disable=SC2016
test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}=="?*" ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
test_syntax_error 'ENV{DISKSEQ}=="?*", ENV{DEVTYPE}!="partition", ENV{DISKSEQ}=="?*", ENV{ID_IGNORE_DISKSEQ}!="1", SYMLINK+="disk/by-diskseq/$env{DISKSEQ}"' \
'duplicate expressions'
test_syntax_error ',ACTION=="a", NAME="b"' 'Stray leading comma.'
test_syntax_error ' ,ACTION=="a", NAME="b"' 'Stray leading comma.'
test_syntax_error ', ACTION=="a", NAME="b"' 'Stray leading comma.'
test_syntax_error 'ACTION=="a", NAME="b",' 'Stray trailing comma.'
test_syntax_error 'ACTION=="a", NAME="b", ' 'Stray trailing comma.'
test_syntax_error 'ACTION=="a" NAME="b"' 'A comma between tokens is expected.'
test_syntax_error 'ACTION=="a",, NAME="b"' 'More than one comma between tokens.'
test_syntax_error 'ACTION=="a" , NAME="b"' 'Stray whitespace before comma.'
test_syntax_error 'ACTION=="a",NAME="b"' 'Whitespace after comma is expected.'
cat >"${rules}" <<'EOF'
KERNEL=="a|b", KERNEL=="a|c", NAME="d"
KERNEL=="a|b", KERNEL!="a|c", NAME="d"
KERNEL!="a", KERNEL!="b", NAME="c"
KERNEL=="|a", KERNEL=="|b", NAME="c"
KERNEL=="*", KERNEL=="a*", NAME="b"
KERNEL=="a*", KERNEL=="c*|ab*", NAME="d"
EOF
assert_0 "${rules}"
echo 'GOTO="a"' >"${rules}"
cat >"${exp}" <<EOF
${rules}:1 GOTO="a" has no matching label, ignoring
${rules}:1 The line takes no effect any more, dropping
${rules}:1 The line has no effect any more, dropping.
${rules}: udev rules check failed
EOF
cp "${workdir}/default_output_1_fail" "${exo}"
@@ -330,7 +343,7 @@ EOF
cat >"${exp}" <<EOF
${rules}:2 Contains multiple LABEL keys, ignoring LABEL="a".
${rules}:1 GOTO="a" has no matching label, ignoring
${rules}:1 The line takes no effect any more, dropping
${rules}:1 The line has no effect any more, dropping.
${rules}:2 LABEL="b" is unused.
${rules}: udev rules check failed
EOF
@@ -342,7 +355,29 @@ KERNEL!="", KERNEL=="?*", KERNEL=="", NAME="a"
EOF
cat >"${exp}" <<EOF
${rules}:1 duplicate expressions
${rules}:1 conflicting match expressions, the line takes no effect
${rules}:1 conflicting match expressions, the line has no effect
${rules}: udev rules check failed
EOF
cp "${workdir}/default_output_1_fail" "${exo}"
assert_1 "${rules}"
cat >"${rules}" <<'EOF'
ACTION=="a"NAME="b"
EOF
cat >"${exp}" <<EOF
${rules}:1 A comma between tokens is expected.
${rules}:1 Whitespace between tokens is expected.
${rules}: udev rules check failed
EOF
cp "${workdir}/default_output_1_fail" "${exo}"
assert_1 "${rules}"
cat >"${rules}" <<'EOF'
ACTION=="a" ,NAME="b"
EOF
cat >"${exp}" <<EOF
${rules}:1 Stray whitespace before comma.
${rules}:1 Whitespace after comma is expected.
${rules}: udev rules check failed
EOF
cp "${workdir}/default_output_1_fail" "${exo}"