diff --git a/man/udevadm.xml b/man/udevadm.xml
index 95b8da2b0a..503ec81091 100644
--- a/man/udevadm.xml
+++ b/man/udevadm.xml
@@ -51,7 +51,7 @@
udevadm verify
options
- file
+ file
udevadm wait options device|syspath
@@ -745,13 +745,15 @@
udevadm verify
options
- file
+ file
…
Verify syntactic and semantic correctness of udev rules files.
- Positional arguments should be used to specify one or more files to check.
+ Positional arguments could be used to specify one or more files to check.
+ If no files are specified, the udev rules are read from the files located in
+ the same udev/rules.d directories that are processed by the udev daemon.
The exit status is 0 if all specified udev rules files
are syntactically and semantically correct, and a non-zero error code otherwise.
@@ -767,6 +769,11 @@
parsed. When set to never, names will
never be resolved.
+
+
+ When looking for udev rules files located in udev/rules.d directories,
+ operate on files underneath the specified root path PATH.
+
diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm
index b6e14e1d36..3c3c403153 100644
--- a/shell-completion/bash/udevadm
+++ b/shell-completion/bash/udevadm
@@ -64,7 +64,7 @@ _udevadm() {
[MONITOR_ARG]='-s --subsystem-match -t --tag-match'
[TEST]='-a --action -N --resolve-names'
[TEST_BUILTIN]='-a --action'
- [VERIFY]='-N --resolve-names'
+ [VERIFY]='-N --resolve-names --root'
[WAIT]='-t --timeout --initialized=no --removed --settle'
[LOCK]='-t --timeout -d --device -b --backing -p --print'
)
@@ -254,6 +254,10 @@ _udevadm() {
-N|--resolve-names)
comps='early never'
;;
+ --root)
+ comps=$(compgen -A directory -- "$cur" )
+ compopt -o dirnames
+ ;;
*)
comps=''
;;
diff --git a/shell-completion/zsh/_udevadm b/shell-completion/zsh/_udevadm
index 074d367a9d..e8414eef67 100644
--- a/shell-completion/zsh/_udevadm
+++ b/shell-completion/zsh/_udevadm
@@ -108,6 +108,7 @@ _udevadm_test-builtin(){
_udevadm_verify(){
_arguments \
{-N+,--resolve-names=}'[When to resolve names.]:resolve:(early never)' \
+ '--root=[Operate on catalog hierarchy under specified directory]:directories:_directories' \
{-h,--help}'[Print help text.]' \
'*::files:_files'
}
diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c
index 9d831a6004..5e7d8d390e 100644
--- a/src/udev/udev-rules.c
+++ b/src/udev/udev-rules.c
@@ -1014,6 +1014,9 @@ static int parse_token(UdevRules *rules, const char *key, char *attr, UdevRuleOp
return log_token_invalid_attr(rules, key);
if (op != OP_ASSIGN)
return log_token_invalid_op(rules, key);
+ if (FLAGS_SET(rule_line->type, LINE_HAS_LABEL))
+ log_token_warning(rules, "Contains multiple LABEL keys, ignoring LABEL=\"%s\".",
+ rule_line->label);
rule_line->label = value;
SET_FLAG(rule_line->type, LINE_HAS_LABEL, true);
diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c
index 8819b07238..26f317711e 100644
--- a/src/udev/udevadm-verify.c
+++ b/src/udev/udevadm-verify.c
@@ -7,13 +7,20 @@
#include
#include
+#include "conf-files.h"
+#include "constants.h"
#include "log.h"
+#include "parse-argument.h"
#include "pretty-print.h"
+#include "static-destruct.h"
#include "strv.h"
#include "udev-rules.h"
#include "udevadm.h"
static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY;
+static char *arg_root = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -23,11 +30,12 @@ static int help(void) {
if (r < 0)
return log_oom();
- printf("%s verify [OPTIONS] FILE...\n"
+ 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"
+ " --root=PATH Operate on an alternate filesystem root\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
ansi_highlight(),
@@ -38,14 +46,18 @@ static int help(void) {
}
static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_ROOT = 0x100,
+ };
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, 'V' },
- { "resolve-names", required_argument, NULL, 'N' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "resolve-names", required_argument, NULL, 'N' },
+ { "root", required_argument, NULL, ARG_ROOT },
{}
};
- int c;
+ int r, c;
assert(argc >= 0);
assert(argv);
@@ -69,14 +81,21 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_resolve_name_timing == RESOLVE_NAME_NEVER)
arg_resolve_name_timing = RESOLVE_NAME_LATE;
break;
+ case ARG_ROOT:
+ r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
case '?':
return -EINVAL;
default:
assert_not_reached();
}
- if (optind == argc)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No rules file specified.");
+ if (arg_root && optind < argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Combination of --root= and FILEs is not supported.");
return 1;
}
@@ -121,5 +140,19 @@ int verify_main(int argc, char *argv[], void *userdata) {
if (!rules)
return -ENOMEM;
+ if (optind == argc) {
+ const char* const* rules_dirs = STRV_MAKE_CONST(CONF_PATHS("udev/rules.d"));
+ _cleanup_strv_free_ char **files = NULL;
+
+ r = conf_files_list_strv(&files, ".rules", arg_root, 0, rules_dirs);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate rules files: %m");
+ if (arg_root && strv_isempty(files))
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+ "No rules files found in %s", arg_root);
+
+ return verify_rules(rules, files);
+ }
+
return verify_rules(rules, strv_skip(argv, optind));
}
diff --git a/test/units/testsuite-17.11.sh b/test/units/testsuite-17.11.sh
index ed5da16c07..363040c28c 100755
--- a/test/units/testsuite-17.11.sh
+++ b/test/units/testsuite-17.11.sh
@@ -5,70 +5,130 @@ set -o pipefail
# Test for udevadm verify.
-udevadm verify -h
-udevadm verify --help
-udevadm verify -V
-udevadm verify --version
-udevadm verify /dev/null
+# shellcheck source=test/units/assert.sh
+. "$(dirname "$0")"/assert.sh
+
+cleanup() {
+ cd /
+ rm -rf "${workdir}"
+ workdir=
+}
+
+workdir="$(mktemp -d)"
+trap cleanup EXIT
+cd "${workdir}"
+
+test_number=0
+rules=
+exp=
+err=
+next_test_number() {
+ : $((++test_number))
+
+ local num_str
+ num_str=$(printf %05d "${test_number}")
+
+ rules="sample-${num_str}.rules"
+ exp="sample-${num_str}.exp"
+ err="sample-${num_str}.err"
+}
+
+assert_0() {
+ assert_rc 0 udevadm verify "$@"
+ next_test_number
+}
+
+assert_1() {
+ if [ -f "${exp}" ]; then
+ set +e
+ udevadm verify "$@" 2>"${err}"
+ assert_eq "$?" 1
+ set -e
+ diff "${exp}" "${err}"
+ else
+ set +e
+ udevadm verify "$@"
+ assert_eq "$?" 1
+ set -e
+ fi
+ next_test_number
+}
+
+assert_0 -h
+assert_0 --help
+assert_0 -V
+assert_0 --version
+assert_0 /dev/null
-# No rules file specified.
-(! udevadm verify)
# unrecognized option '--unknown'
-(! udevadm verify --unknown)
+assert_1 --unknown
# option requires an argument -- 'N'
-(! udevadm verify -N)
+assert_1 -N
# --resolve-names= takes "early" or "never"
-(! udevadm verify -N now)
+assert_1 -N now
# option '--resolve-names' requires an argument
-(! udevadm verify --resolve-names)
+assert_1 --resolve-names
# --resolve-names= takes "early" or "never"
-(! udevadm verify --resolve-names=now)
+assert_1 --resolve-names=now
# Failed to parse rules file .: Is a directory
-(! udevadm verify .)
+assert_1 .
# Failed to parse rules file .: Is a directory
-(! udevadm verify /dev/null . /dev/null)
+assert_1 /dev/null . /dev/null
+
+rules_dir='etc/udev/rules.d'
+mkdir -p "${rules_dir}"
+# No rules files found in $PWD
+assert_1 --root="${workdir}"
+
+touch "${rules_dir}/empty.rules"
+assert_0 --root="${workdir}"
+
+# Combination of --root= and FILEs is not supported.
+assert_1 --root="${workdir}" /dev/null
+# No rules files found in nosuchdir
+assert_1 --root=nosuchdir
+
+cd "${rules_dir}"
# UDEV_LINE_SIZE 16384
-printf '%16383s\n' ' ' >sample.rules
-udevadm verify sample.rules
+printf '%16383s\n' ' ' >"${rules}"
+assert_0 "${rules}"
-# Failed to parse rules file sample.rules: No buffer space available
-printf '%16384s\n' ' ' >sample.rules
-(! udevadm verify sample.rules)
+# Failed to parse rules file ${rules}: No buffer space available
+printf '%16384s\n' ' ' >"${rules}"
+echo "Failed to parse rules file ${rules}: No buffer space available" >"${exp}"
+assert_1 "${rules}"
-printf 'RUN+="/bin/true"%8175s\\\n' ' ' ' ' >sample.rules
-echo >>sample.rules
-udevadm verify sample.rules
+printf 'RUN+="/bin/true"%8175s\\\n' ' ' ' ' >"${rules}"
+echo >>"${rules}"
+assert_0 "${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
+printf 'RUN+="/bin/true"%8176s\\\n #\n' ' ' ' ' >"${rules}"
+echo >>"${rules}"
+cat >"${exp}" <err)
-diff exp err
+assert_1 "${rules}"
-printf '\\\n' >sample.rules
-cat >exp <<'EOF'
-sample.rules:1 Unexpected EOF after line continuation, line ignored
-sample.rules: udev rules check failed
+printf '\\\n' >"${rules}"
+cat >"${exp}" <err)
-diff exp err
+assert_1 "${rules}"
test_syntax_error() {
- local rule msg
- rule="$1"; shift
- msg="$1"; shift
+ 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
+ printf '%s\n' "${rule}" >"${rules}"
+ cat >"${exp}" <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
+echo 'GOTO="a"' >"${rules}"
+cat >"${exp}" <err)
-diff exp err
+assert_1 "${rules}"
-cat >sample.rules <<'EOF'
+cat >"${rules}" <<'EOF'
GOTO="a"
LABEL="a"
EOF
-udevadm verify sample.rules
+assert_0 "${rules}"
-cat >sample.rules <<'EOF'
+cat >"${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
+cat >"${exp}" <err)
-diff exp err
+assert_1 "${rules}"
+
+cat >"${rules}" <<'EOF'
+GOTO="a"
+LABEL="a", LABEL="b"
+EOF
+cat >"${exp}" <"${workdir}/${exp}"
+cd -
+assert_1 --root="${workdir}"
exit 0