diff options
-rw-r--r-- | man/udevadm.xml | 36 | ||||
-rw-r--r-- | shell-completion/bash/udevadm | 24 | ||||
-rw-r--r-- | shell-completion/zsh/_udevadm | 9 | ||||
-rw-r--r-- | src/udev/meson.build | 1 | ||||
-rw-r--r-- | src/udev/udev-rules.c | 10 | ||||
-rw-r--r-- | src/udev/udev-rules.h | 1 | ||||
-rw-r--r-- | src/udev/udevadm-verify.c | 125 | ||||
-rw-r--r-- | src/udev/udevadm.c | 2 | ||||
-rw-r--r-- | src/udev/udevadm.h | 1 |
9 files changed, 208 insertions, 1 deletions
diff --git a/man/udevadm.xml b/man/udevadm.xml index 33af155021..95b8da2b0a 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -49,6 +49,11 @@ <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> <cmdsynopsis> @@ -738,6 +743,37 @@ </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> <arg choice="opt"><replaceable>device|syspath</replaceable></arg> diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm index 99d4aa55c9..b6e14e1d36 100644 --- a/shell-completion/bash/udevadm +++ b/shell-completion/bash/udevadm @@ -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 diff --git a/shell-completion/zsh/_udevadm b/shell-completion/zsh/_udevadm index f7c3384eae..074d367a9d 100644 --- a/shell-completion/zsh/_udevadm +++ b/shell-completion/zsh/_udevadm @@ -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' ) diff --git a/src/udev/meson.build b/src/udev/meson.build index 3f4ab00ac9..1cac581e7f 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -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', ) diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 840448d65c..c2e98e9a9e 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -1321,6 +1321,16 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename) { return 0; } +unsigned udev_check_current_rule_file(UdevRules *rules) { + assert(rules); + + UdevRuleFile *rule_file = rules->current_file; + if (!rule_file) + return 0; + + return rule_file->issues; +} + UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing) { assert(resolve_name_timing >= 0 && resolve_name_timing < _RESOLVE_NAME_TIMING_MAX); diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index 860fe7c8e4..44f30e1c13 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -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); diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c new file mode 100644 index 0000000000..8819b07238 --- /dev/null +++ b/src/udev/udevadm-verify.c @@ -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)); +} diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index b742c1a287..273d3c5b29 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -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 }, {} diff --git a/src/udev/udevadm.h b/src/udev/udevadm.h index 417611affe..7920a70d5b 100644 --- a/src/udev/udevadm.h +++ b/src/udev/udevadm.h @@ -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); |