Merge pull request #26980 from ldv-alt/udevadm-verify
udevadm verify: add more checks
This commit is contained 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
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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}"
|
||||
|
||||
Reference in New Issue
Block a user