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

Implement a udev rules syntax checker in the form of
`udevadm verify [OPTIONS] FILE...` command that is based on
`udev_rules_parse_file` interface and would apply further checks
on top of it in the future.

Resolves: #26606
This commit is contained in:
Yu Watanabe
2023-03-09 13:05:57 +09:00
committed by GitHub
13 changed files with 498 additions and 10 deletions
+36
View File
@@ -48,6 +48,11 @@
<cmdsynopsis>
<command>udevadm test-builtin <optional>options</optional> <replaceable>command</replaceable> <replaceable>devpath</replaceable></command>
</cmdsynopsis>
<cmdsynopsis>
<command>udevadm verify</command>
<arg choice="opt" rep="repeat">options</arg>
<arg choice="plain" rep="repeat"><replaceable>file</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>udevadm wait <optional>options</optional> <replaceable>device|syspath</replaceable></command>
</cmdsynopsis>
@@ -737,6 +742,37 @@
</variablelist>
</refsect2>
<refsect2>
<title>udevadm verify
<arg choice="opt"><replaceable>options</replaceable></arg>
<arg choice="plain" rep="repeat"><replaceable>file</replaceable></arg>
…
</title>
<para>Verify syntactic and semantic correctness of udev rules files.</para>
<para>Positional arguments should be used to specify one or more files to check.</para>
<para>The exit status is <constant>0</constant> if all specified udev rules files
are syntactically and semantically correct, and a non-zero error code otherwise.</para>
<variablelist>
<varlistentry>
<term><option>-N</option></term>
<term><option>--resolve-names=<constant>early</constant>|<constant>never</constant></option></term>
<listitem>
<para>Specify when udevadm should resolve names of users
and groups. When set to <constant>early</constant> (the
default), names will be resolved when the rules are
parsed. When set to <constant>never</constant>, names will
never be resolved.</para>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
</variablelist>
</refsect2>
<refsect2>
<title>udevadm wait
<arg choice="opt"><replaceable>options</replaceable></arg>
+7
View File
@@ -3847,6 +3847,13 @@ udevadm = executable(
install_dir : rootbindir)
public_programs += udevadm
if want_tests != 'false'
test('udev-rules-check',
udevadm,
suite : 'dist-check',
args : ['verify', '--resolve-names=never', all_rules])
endif
if conf.get('ENABLE_REPART') == 1
exe = executable(
'systemd-repart',
@@ -106,6 +106,7 @@ ENV{DEVTYPE}=="partition", ENV{ID_PATH_ATA_COMPAT}=="?*", SYMLINK+="disk/by-path
KERNEL=="vd*[!0-9]", ENV{ID_PATH}=="pci-*", SYMLINK+="disk/by-path/virtio-$env{ID_PATH}"
KERNEL=="vd*[0-9]", ENV{ID_PATH}=="pci-*", SYMLINK+="disk/by-path/virtio-$env{ID_PATH}-part%n"
{% if HAVE_BLKID %}
# allow admin to disable probing the filesystem for slow devices like floppy disk drives
ENV{UDEV_DISABLE_PERSISTENT_STORAGE_BLKID_FLAG}=="1", GOTO="persistent_storage_blkid_probe_end"
@@ -120,6 +121,7 @@ KERNEL=="sr*", ENV{DISK_EJECT_REQUEST}!="?*", ENV{ID_CDROM_MEDIA_TRACK_COUNT_DAT
KERNEL!="sr*|mmcblk[0-9]boot[0-9]", IMPORT{builtin}="blkid"
LABEL="persistent_storage_blkid_probe_end"
{% endif %}
# by-label/by-uuid links (filesystem metadata)
ENV{ID_FS_USAGE}=="filesystem|other|crypto", ENV{ID_FS_UUID_ENC}=="?*", SYMLINK+="disk/by-uuid/$env{ID_FS_UUID_ENC}"
+1 -1
View File
@@ -15,7 +15,6 @@ rules = [
'60-input-id.rules',
'60-persistent-alsa.rules',
'60-persistent-input.rules',
'60-persistent-storage.rules',
'60-persistent-storage-tape.rules',
'60-persistent-v4l.rules',
'60-sensor.rules',
@@ -53,6 +52,7 @@ endforeach
rules_in = [
['50-udev-default.rules'],
['60-persistent-storage.rules'],
['64-btrfs.rules'],
['99-systemd.rules'],
+23 -1
View File
@@ -64,11 +64,12 @@ _udevadm() {
[MONITOR_ARG]='-s --subsystem-match -t --tag-match'
[TEST]='-a --action -N --resolve-names'
[TEST_BUILTIN]='-a --action'
[VERIFY]='-N --resolve-names'
[WAIT]='-t --timeout --initialized=no --removed --settle'
[LOCK]='-t --timeout -d --device -b --backing -p --print'
)
local verbs=(info trigger settle control monitor test-builtin test wait lock)
local verbs=(info trigger settle control monitor test-builtin test verify wait lock)
local builtins=(blkid btrfs hwdb input_id keyboard kmod net_id net_setup_link path_id usb_id uaccess)
for ((i=0; i < COMP_CWORD; i++)); do
@@ -247,6 +248,27 @@ _udevadm() {
fi
;;
'verify')
if __contains_word "$prev" ${OPTS[VERIFY]}; then
case $prev in
-N|--resolve-names)
comps='early never'
;;
*)
comps=''
;;
esac
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
return 0
fi
if [[ $cur = -* ]]; then
comps="${OPTS[COMMON]} ${OPTS[VERIFY]}"
else
comps=$( compgen -A file -- "$cur" )
fi
;;
'wait')
if __contains_word "$prev" ${OPTS[WAIT]}; then
case $prev in
+9
View File
@@ -104,6 +104,14 @@ _udevadm_test-builtin(){
fi
}
(( $+functions[_udevadm_verify] )) ||
_udevadm_verify(){
_arguments \
{-N+,--resolve-names=}'[When to resolve names.]:resolve:(early never)' \
{-h,--help}'[Print help text.]' \
'*::files:_files'
}
(( $+functions[_udevadm_wait] )) ||
_udevadm_wait(){
_arguments \
@@ -154,6 +162,7 @@ _udevadm_commands(){
'monitor:listen to kernel and udev events'
'test:test an event run'
'test-builtin:test a built-in command'
'verify:verify udev rules files'
'wait:wait for devices or device symlinks being created'
'lock:lock a block device and run a comand'
)
+1
View File
@@ -12,6 +12,7 @@ udevadm_sources = files(
'udevadm-test-builtin.c',
'udevadm-trigger.c',
'udevadm-util.c',
'udevadm-verify.c',
'udevadm-wait.c',
'udevd.c',
)
+83 -8
View File
@@ -137,6 +137,7 @@ typedef enum {
LINE_HAS_GOTO = 1 << 3, /* has GOTO= */
LINE_HAS_LABEL = 1 << 4, /* has LABEL= */
LINE_UPDATE_SOMETHING = 1 << 5, /* has other TK_A_* or TK_M_IMPORT tokens */
LINE_IS_REFERENCED = 1 << 6, /* is referenced by GOTO */
} UdevRuleLineType;
typedef struct UdevRuleFile UdevRuleFile;
@@ -172,6 +173,7 @@ struct UdevRuleLine {
struct UdevRuleFile {
char *filename;
UdevRuleLine *current_line;
unsigned issues;
LIST_HEAD(UdevRuleLine, rule_lines);
LIST_FIELDS(UdevRuleFile, rule_files);
};
@@ -190,12 +192,14 @@ struct UdevRules {
#define log_rule_full_errno_zerook(device, rules, level, error, fmt, ...) \
({ \
UdevRules *_r = (rules); \
int _lv = (level); \
UdevRuleFile *_f = _r ? _r->current_file : NULL; \
UdevRuleLine *_l = _f ? _f->current_line : NULL; \
const char *_n = _f ? _f->filename : NULL; \
\
rule_file_mark_issue(_f, _lv); \
log_device_full_errno_zerook( \
device, level, error, "%s:%u " fmt, \
device, _lv, error, "%s:%u " fmt, \
strna(_n), _l ? _l->line_number : 0, \
##__VA_ARGS__); \
})
@@ -254,6 +258,48 @@ struct UdevRules {
"Invalid value \"%s\" for %s (char %zu: %s), ignoring.", \
value, key, offset, hint)
#define log_line_full_errno_zerook(rule_file, line_nr, level, error, fmt, ...) \
({ \
UdevRuleFile *_f = (rule_file); \
int _lv = (level); \
const char *_n = _f ? _f->filename : NULL; \
\
rule_file_mark_issue(_f, _lv); \
log_device_full_errno_zerook( \
NULL, _lv, error, "%s:%u " fmt, \
strna(_n), line_nr, \
##__VA_ARGS__); \
})
#define log_line_full_errno(rule_file, line_nr, level, error, fmt, ...) \
({ \
int _error = (error); \
ASSERT_NON_ZERO(_error); \
log_line_full_errno_zerook( \
rule_file, line_nr, level, _error, \
fmt, ##__VA_ARGS__); \
})
#define log_line_full(rule_file, line_nr, level, ...) \
(void) log_line_full_errno_zerook(rule_file, line_nr, level, 0, __VA_ARGS__)
#define log_line_debug(rule_file, line_nr, ...) log_line_full(rule_file, line_nr, LOG_DEBUG, __VA_ARGS__)
#define log_line_info(rule_file, line_nr, ...) log_line_full(rule_file, line_nr, LOG_INFO, __VA_ARGS__)
#define log_line_notice(rule_file, line_nr, ...) log_line_full(rule_file, line_nr, LOG_NOTICE, __VA_ARGS__)
#define log_line_warning(rule_file, line_nr, ...) log_line_full(rule_file, line_nr, LOG_WARNING, __VA_ARGS__)
#define log_line_error(rule_file, line_nr, ...) log_line_full(rule_file, line_nr, LOG_ERR, __VA_ARGS__)
#define log_line_debug_errno(rule_file, line_nr, error, ...) log_line_full_errno(rule_file, line_nr, LOG_DEBUG, error, __VA_ARGS__)
#define log_line_info_errno(rule_file, line_nr, error, ...) log_line_full_errno(rule_file, line_nr, LOG_INFO, error, __VA_ARGS__)
#define log_line_notice_errno(rule_file, line_nr, error, ...) log_line_full_errno(rule_file, line_nr, LOG_NOTICE, error, __VA_ARGS__)
#define log_line_warning_errno(rule_file, line_nr, error, ...) log_line_full_errno(rule_file, line_nr, LOG_WARNING, error, __VA_ARGS__)
#define log_line_error_errno(rule_file, line_nr, error, ...) log_line_full_errno(rule_file, line_nr, LOG_ERR, error, __VA_ARGS__)
static void rule_file_mark_issue(UdevRuleFile *rule_file, uint8_t log_level) {
if (rule_file)
rule_file->issues |= (1U << log_level);
}
static void log_unknown_owner(sd_device *dev, UdevRules *rules, int error, const char *entity, const char *name) {
if (IN_SET(abs(error), ENOENT, ESRCH))
log_rule_error(dev, rules, "Unknown %s '%s', ignoring", entity, name);
@@ -1146,20 +1192,23 @@ static void rule_resolve_goto(UdevRuleFile *rule_file) {
LIST_FOREACH(rule_lines, i, line->rule_lines_next)
if (streq_ptr(i->label, line->goto_label)) {
line->goto_line = i;
SET_FLAG(i->type, LINE_IS_REFERENCED, true);
break;
}
if (!line->goto_line) {
log_error("%s:%u: GOTO=\"%s\" has no matching label, ignoring",
rule_file->filename, line->line_number, line->goto_label);
log_line_error(rule_file, line->line_number,
"GOTO=\"%s\" has no matching label, ignoring",
line->goto_label);
SET_FLAG(line->type, LINE_HAS_GOTO, false);
line->goto_label = NULL;
if ((line->type & ~LINE_HAS_LABEL) == 0) {
log_notice("%s:%u: The line takes no effect any more, dropping",
rule_file->filename, line->line_number);
if (line->type == LINE_HAS_LABEL)
if ((line->type & ~(LINE_HAS_LABEL|LINE_IS_REFERENCED)) == 0) {
log_line_notice(rule_file, line->line_number,
"The line takes no effect any more, dropping");
/* LINE_IS_REFERENCED implies LINE_HAS_LABEL */
if (line->type & LINE_HAS_LABEL)
udev_rule_line_clear_tokens(line);
else
udev_rule_line_free(line);
@@ -1230,6 +1279,7 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename) {
line_nr++;
line = skip_leading_chars(buf, NULL);
/* Lines beginning with '#' are ignored regardless of line continuation. */
if (line[0] == '#')
continue;
@@ -1263,7 +1313,7 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename) {
}
if (ignore_line)
log_error("%s:%u: Line is too long, ignored", filename, line_nr);
log_line_error(rule_file, line_nr, "Line is too long, ignored");
else if (len > 0)
(void) rule_add_line(rules, line, line_nr);
@@ -1271,10 +1321,35 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename) {
ignore_line = false;
}
if (continuation)
log_line_error(rule_file, line_nr,
"Unexpected EOF after line continuation, line ignored");
rule_resolve_goto(rule_file);
return 0;
}
static void udev_check_rule_line(UdevRuleFile *rule_file, const UdevRuleLine *line) {
/* check for unused labels */
if (FLAGS_SET(line->type, LINE_HAS_LABEL) &&
!FLAGS_SET(line->type, LINE_IS_REFERENCED))
log_line_warning(rule_file, line->line_number,
"LABEL=\"%s\" is unused.", line->label);
}
unsigned udev_check_current_rule_file(UdevRules *rules) {
assert(rules);
UdevRuleFile *rule_file = rules->current_file;
if (!rule_file)
return 0;
LIST_FOREACH(rule_lines, line, rule_file->rule_lines)
udev_check_rule_line(rule_file, line);
return rule_file->issues;
}
UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing) {
assert(resolve_name_timing >= 0 && resolve_name_timing < _RESOLVE_NAME_TIMING_MAX);
+1
View File
@@ -18,6 +18,7 @@ typedef enum {
} UdevRuleEscapeType;
int udev_rules_parse_file(UdevRules *rules, const char *filename);
unsigned udev_check_current_rule_file(UdevRules *rules);
UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing);
int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing);
UdevRules *udev_rules_free(UdevRules *rules);
+125
View File
@@ -0,0 +1,125 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <errno.h>
#include <getopt.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "log.h"
#include "pretty-print.h"
#include "strv.h"
#include "udev-rules.h"
#include "udevadm.h"
static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY;
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
r = terminal_urlify_man("udevadm", "8", &link);
if (r < 0)
return log_oom();
printf("%s verify [OPTIONS] FILE...\n"
"\n%sVerify udev rules files.%s\n\n"
" -h --help Show this help\n"
" -V --version Show package version\n"
" -N --resolve-names=early|never When to resolve names\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "resolve-names", required_argument, NULL, 'N' },
{}
};
int c;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hVN:", options, NULL)) >= 0)
switch (c) {
case 'h':
return help();
case 'V':
return print_version();
case 'N':
arg_resolve_name_timing = resolve_name_timing_from_string(optarg);
if (arg_resolve_name_timing < 0)
return log_error_errno(arg_resolve_name_timing,
"--resolve-names= takes \"early\" or \"never\"");
/*
* In the verifier "late" has the effect of "never",
* and "never" would generate irrelevant diagnostics,
* so map "never" to "late".
*/
if (arg_resolve_name_timing == RESOLVE_NAME_NEVER)
arg_resolve_name_timing = RESOLVE_NAME_LATE;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
if (optind == argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No rules file specified.");
return 1;
}
static int verify_rules_file(UdevRules *rules, const char *fname) {
int r;
r = udev_rules_parse_file(rules, fname);
if (r < 0)
return log_error_errno(r, "Failed to parse rules file %s: %m", fname);
unsigned issues = udev_check_current_rule_file(rules);
unsigned mask = (1U << LOG_ERR) | (1U << LOG_WARNING);
if (issues & mask)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: udev rules check failed", fname);
return 0;
}
static int verify_rules(UdevRules *rules, char **files) {
int r, rv = 0;
STRV_FOREACH(fp, files) {
r = verify_rules_file(rules, *fp);
if (r < 0 && rv >= 0)
rv = r;
}
return rv;
}
int verify_main(int argc, char *argv[], void *userdata) {
_cleanup_(udev_rules_freep) UdevRules *rules = NULL;
int r;
r = parse_argv(argc, argv);
if (r <= 0)
return r;
rules = udev_rules_new(arg_resolve_name_timing);
if (!rules)
return -ENOMEM;
return verify_rules(rules, strv_skip(argv, optind));
}
+2
View File
@@ -25,6 +25,7 @@ static int help(void) {
{ "monitor", "Listen to kernel and udev events" },
{ "test", "Test an event run" },
{ "test-builtin", "Test a built-in command" },
{ "verify", "Verify udev rules files" },
{ "wait", "Wait for device or device symlink" },
{ "lock", "Lock a block device" },
};
@@ -104,6 +105,7 @@ static int udevadm_main(int argc, char *argv[]) {
{ "test-builtin", VERB_ANY, VERB_ANY, 0, builtin_main },
{ "wait", VERB_ANY, VERB_ANY, 0, wait_main },
{ "lock", VERB_ANY, VERB_ANY, 0, lock_main },
{ "verify", VERB_ANY, VERB_ANY, 0, verify_main },
{ "version", VERB_ANY, VERB_ANY, 0, version_main },
{ "help", VERB_ANY, VERB_ANY, 0, help_main },
{}
+1
View File
@@ -13,6 +13,7 @@ int monitor_main(int argc, char *argv[], void *userdata);
int hwdb_main(int argc, char *argv[], void *userdata);
int test_main(int argc, char *argv[], void *userdata);
int builtin_main(int argc, char *argv[], void *userdata);
int verify_main(int argc, char *argv[], void *userdata);
int wait_main(int argc, char *argv[], void *userdata);
int lock_main(int argc, char *argv[], void *userdata);
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -ex
set -o pipefail
# Test for udevadm verify.
udevadm verify -h
udevadm verify --help
udevadm verify -V
udevadm verify --version
udevadm verify /dev/null
# No rules file specified.
(! udevadm verify)
# unrecognized option '--unknown'
(! udevadm verify --unknown)
# option requires an argument -- 'N'
(! udevadm verify -N)
# --resolve-names= takes "early" or "never"
(! udevadm verify -N now)
# option '--resolve-names' requires an argument
(! udevadm verify --resolve-names)
# --resolve-names= takes "early" or "never"
(! udevadm verify --resolve-names=now)
# Failed to parse rules file .: Is a directory
(! udevadm verify .)
# Failed to parse rules file .: Is a directory
(! udevadm verify /dev/null . /dev/null)
# UDEV_LINE_SIZE 16384
printf '%16383s\n' ' ' >sample.rules
udevadm verify sample.rules
# Failed to parse rules file sample.rules: No buffer space available
printf '%16384s\n' ' ' >sample.rules
(! udevadm verify sample.rules)
printf 'RUN+="/bin/true"%8175s\\\n' ' ' ' ' >sample.rules
echo >>sample.rules
udevadm verify sample.rules
printf 'RUN+="/bin/true"%8176s\\\n #\n' ' ' ' ' >sample.rules
echo >>sample.rules
cat >exp <<'EOF'
sample.rules:5 Line is too long, ignored
sample.rules: udev rules check failed
EOF
(! udevadm verify sample.rules 2>err)
diff exp err
printf '\\\n' >sample.rules
cat >exp <<'EOF'
sample.rules:1 Unexpected EOF after line continuation, line ignored
sample.rules: udev rules check failed
EOF
(! udevadm verify sample.rules 2>err)
diff exp err
test_syntax_error() {
local rule msg
rule="$1"; shift
msg="$1"; shift
printf '%s\n' "$rule" > sample.rules
cat >exp <<-EOF
sample.rules:1 $msg
sample.rules: udev rules check failed
EOF
(! udevadm verify sample.rules 2>err)
diff exp err
}
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 '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.'
test_syntax_error 'KERNEL:="b"' 'Invalid operator for KERNEL.'
test_syntax_error 'KERNELS{a}=="b"' 'Invalid attribute for KERNELS.'
test_syntax_error 'KERNELS:="b"' 'Invalid operator for KERNELS.'
test_syntax_error 'SYMLINK{a}=="b"' 'Invalid attribute for SYMLINK.'
test_syntax_error 'SYMLINK:="%?"' 'Invalid value "%?" for SYMLINK (char 1: invalid substitution type), ignoring.'
test_syntax_error 'NAME{a}=="b"' 'Invalid attribute for NAME.'
test_syntax_error 'NAME-="b"' 'Invalid operator for NAME.'
test_syntax_error 'NAME+="a"' "NAME key takes '==', '!=', '=', or ':=' operator, assuming '='."
test_syntax_error 'NAME:=""' 'Ignoring NAME="", as udev will not delete any network interfaces.'
test_syntax_error 'NAME="%k"' 'Ignoring NAME="%k", as it will take no effect.'
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 '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.'
test_syntax_error 'TAG{a}=="b"' 'Invalid attribute for TAG.'
test_syntax_error 'TAG:="a"' "TAG key takes '==', '!=', '=', or '+=' operator, assuming '='."
test_syntax_error 'TAG="%?"' 'Invalid value "%?" for TAG (char 1: invalid substitution type), ignoring.'
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 '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.'
test_syntax_error 'DRIVER:="b"' 'Invalid operator for DRIVER.'
test_syntax_error 'DRIVERS{a}=="b"' 'Invalid attribute for DRIVERS.'
test_syntax_error 'DRIVERS:="b"' 'Invalid operator for DRIVERS.'
test_syntax_error 'ATTR="b"' 'Invalid attribute for ATTR.'
test_syntax_error 'ATTR{%}="b"' 'Invalid attribute "%" for ATTR (char 1: invalid substitution type), ignoring.'
test_syntax_error 'ATTR{a}-="b"' 'Invalid operator for ATTR.'
test_syntax_error 'ATTR{a}+="b"' "ATTR key takes '==', '!=', or '=' operator, assuming '='."
test_syntax_error 'ATTR{a}="%?"' 'Invalid value "%?" for ATTR (char 1: invalid substitution type), ignoring.'
test_syntax_error 'SYSCTL=""' 'Invalid attribute for SYSCTL.'
test_syntax_error 'SYSCTL{%}="b"' 'Invalid attribute "%" for SYSCTL (char 1: invalid substitution type), ignoring.'
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{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 '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{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 '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{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.'
test_syntax_error 'OPTIONS{a}="b"' 'Invalid attribute for OPTIONS.'
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 '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.'
test_syntax_error 'OWNER+="0"' "OWNER key takes '=' or ':=' operator, assuming '='."
test_syntax_error 'OWNER="nosuchuser"' "Unknown user 'nosuchuser', ignoring"
test_syntax_error 'GROUP{a}="b"' 'Invalid attribute for GROUP.'
test_syntax_error 'GROUP-="b"' 'Invalid operator for GROUP.'
test_syntax_error 'GROUP!="b"' 'Invalid operator for GROUP.'
test_syntax_error 'GROUP+="0"' "GROUP key takes '=' or ':=' operator, assuming '='."
test_syntax_error 'GROUP="nosuchuser"' "Unknown group 'nosuchuser', ignoring"
test_syntax_error 'MODE{a}="b"' 'Invalid attribute for MODE.'
test_syntax_error 'MODE-="b"' 'Invalid operator for MODE.'
test_syntax_error 'MODE!="b"' 'Invalid operator for MODE.'
test_syntax_error 'MODE+="0"' "MODE key takes '=' or ':=' operator, assuming '='."
test_syntax_error 'MODE="%"' 'Invalid value "%" for MODE (char 1: invalid substitution type), ignoring.'
test_syntax_error 'SECLABEL="b"' 'Invalid attribute for SECLABEL.'
test_syntax_error 'SECLABEL{a}="%"' 'Invalid value "%" for SECLABEL (char 1: invalid substitution type), ignoring.'
test_syntax_error 'SECLABEL{a}!="b"' 'Invalid operator for SECLABEL.'
test_syntax_error 'SECLABEL{a}-="b"' 'Invalid operator for SECLABEL.'
test_syntax_error 'SECLABEL{a}:="b"' "SECLABEL key takes '=' or '+=' operator, assuming '='."
test_syntax_error 'RUN=="b"' 'Invalid operator for RUN.'
test_syntax_error 'RUN-="b"' 'Invalid operator for RUN.'
test_syntax_error 'RUN="%"' 'Invalid value "%" for RUN (char 1: invalid substitution type), ignoring.'
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"
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'"
echo 'GOTO="a"' >sample.rules
cat >exp <<'EOF'
sample.rules:1 GOTO="a" has no matching label, ignoring
sample.rules:1 The line takes no effect any more, dropping
sample.rules: udev rules check failed
EOF
(! udevadm verify sample.rules 2>err)
diff exp err
cat >sample.rules <<'EOF'
GOTO="a"
LABEL="a"
EOF
udevadm verify sample.rules
cat >sample.rules <<'EOF'
GOTO="b"
LABEL="b"
LABEL="b"
EOF
cat >exp <<'EOF'
sample.rules:3 LABEL="b" is unused.
sample.rules: udev rules check failed
EOF
(! udevadm verify sample.rules 2>err)
diff exp err
exit 0