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:
@@ -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>
|
||||
|
||||
@@ -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
@@ -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'],
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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 },
|
||||
{}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Executable
+207
@@ -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
|
||||
Reference in New Issue
Block a user