diff --git a/rules.d/60-persistent-storage.rules.in b/rules.d/60-persistent-storage.rules.in index 76621a467d..51589686af 100644 --- a/rules.d/60-persistent-storage.rules.in +++ b/rules.d/60-persistent-storage.rules.in @@ -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 diff --git a/rules.d/70-camera.rules b/rules.d/70-camera.rules index b1053f2cbb..b87096ece0 100644 --- a/rules.d/70-camera.rules +++ b/rules.d/70-camera.rules @@ -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" diff --git a/src/udev/fuzz-udev-rules.c b/src/udev/fuzz-udev-rules.c index 1d487b94ee..aff176eb1b 100644 --- a/src/udev/fuzz-udev-rules.c +++ b/src/udev/fuzz-udev-rules.c @@ -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 */ diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 4859a142da..f7a1bb3bb0 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -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); } diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index cecc285b2c..9b477fb235 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -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); diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 2e947ef830..48aeb8f7e7 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -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. */ diff --git a/test/units/testsuite-17.11.sh b/test/units/testsuite-17.11.sh index 88172acde6..ba21c9ab87 100755 --- a/test/units/testsuite-17.11.sh +++ b/test/units/testsuite-17.11.sh @@ -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}" <"${exp}" <"${exp}" <"${rules}" <<'EOF' +ACTION=="a"NAME="b" +EOF +cat >"${exp}" <"${rules}" <<'EOF' +ACTION=="a" ,NAME="b" +EOF +cat >"${exp}" <