diff --git a/man/udevadm.xml b/man/udevadm.xml index 01d662a076..c325df9118 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -888,6 +888,21 @@ + + + + + Also load udev rules files from the specified directory. This option can be specified + multiple times. It may be useful for debugging udev rules by copying some udev rules files to a + temporary directory, editing them, and specifying the directory with this option. Files found in + the specified directories have a higher priority than rules files in the default + udev/rules.d directories used by systemd-udevd. See + udev7 for more + details about the search paths. + + + + diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm index c8a08f68d7..63631fdaeb 100644 --- a/shell-completion/bash/udevadm +++ b/shell-completion/bash/udevadm @@ -70,7 +70,7 @@ _udevadm() { [MONITOR_STANDALONE]='-k --kernel -u --udev -p --property' [MONITOR_ARG]='-s --subsystem-match -t --tag-match' [TEST_STANDALONE]='-v --verbose' - [TEST_ARG]='-a --action -N --resolve-names' + [TEST_ARG]='-a --action -N --resolve-names -D --extra-rules-dir' [TEST_BUILTIN]='-a --action' [VERIFY]='-N --resolve-names --root --no-summary --no-style' [WAIT]='-t --timeout --initialized=no --removed --settle' @@ -228,6 +228,9 @@ _udevadm() { -N|--resolve-names) comps='early late never' ;; + -D|--extra-rules-dir) + comps='' + compopt -o dirnames esac elif [[ $cur = -* ]]; then comps="${OPTS[COMMON]} ${OPTS[TEST_ARG]} ${OPTS[TEST_STANDALONE]}" diff --git a/shell-completion/zsh/_udevadm b/shell-completion/zsh/_udevadm index 8a88aa694e..06aa79f47b 100644 --- a/shell-completion/zsh/_udevadm +++ b/shell-completion/zsh/_udevadm @@ -89,6 +89,7 @@ _udevadm_test(){ '(-)'{-V,--version}'[Show package version]' \ '--action=[The action string.]:actions:(add change remove move online offline bind unbind)' \ '--subsystem=[The subsystem string.]' \ + '(-D --extra-rules-dir=)'{-D,--extra-rules-dir=}'[Also load rules from the directory.]' \ '(-v --verbose)'{-v,--verbose}'[Show verbose logs.]' \ '*::devpath:_files -P /sys/ -W /sys' } diff --git a/src/udev/test-udev-rule-runner.c b/src/udev/test-udev-rule-runner.c index 9a04abf590..9ad446aa2d 100644 --- a/src/udev/test-udev-rule-runner.c +++ b/src/udev/test-udev-rule-runner.c @@ -136,7 +136,7 @@ static int run(int argc, char *argv[]) { usleep_safe(us); } - assert_se(udev_rules_load(&rules, RESOLVE_NAME_EARLY) == 0); + assert_se(udev_rules_load(&rules, RESOLVE_NAME_EARLY, /* extra = */ NULL) == 0); const char *syspath = strjoina("/sys", devpath); r = device_new_from_synthetic_event(&dev, syspath, action); diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index 2f97205c91..873a076a2d 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -284,7 +284,7 @@ void manager_reload(Manager *manager, bool force) { udev_builtin_reload(flags); if (FLAGS_SET(flags, UDEV_RELOAD_RULES)) { - r = udev_rules_load(&rules, manager->config.resolve_name_timing); + r = udev_rules_load(&rules, manager->config.resolve_name_timing, /* extra = */ NULL); if (r < 0) log_warning_errno(r, "Failed to read udev rules, using the previously loaded rules, ignoring: %m"); else @@ -1316,7 +1316,7 @@ int manager_main(Manager *manager) { udev_builtin_init(); - r = udev_rules_load(&manager->rules, manager->config.resolve_name_timing); + r = udev_rules_load(&manager->rules, manager->config.resolve_name_timing, /* extra = */ NULL); if (r < 0) return log_error_errno(r, "Failed to read udev rules: %m"); diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index d94fc6fdbd..071b158278 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -1778,16 +1778,26 @@ UdevRules* udev_rules_new(ResolveNameTiming resolve_name_timing) { return rules; } -int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing) { +int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing, char * const *extra) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; - _cleanup_strv_free_ char **files = NULL; + _cleanup_strv_free_ char **files = NULL, **directories = NULL; int r; rules = udev_rules_new(resolve_name_timing); if (!rules) return -ENOMEM; - r = conf_files_list_strv(&files, ".rules", NULL, 0, RULES_DIRS); + if (!strv_isempty(extra)) { + directories = strv_copy(extra); + if (!directories) + return -ENOMEM; + } + + r = strv_extend_strv(&directories, CONF_PATHS_STRV("udev/rules.d"), /* filter_duplicates = */ false); + if (r < 0) + return r; + + r = conf_files_list_strv(&files, ".rules", NULL, 0, (const char* const*) directories); if (r < 0) return log_debug_errno(r, "Failed to enumerate rules files: %m"); diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index 67d7e5b178..62dac5ba73 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -14,7 +14,7 @@ int udev_rule_parse_value(char *str, char **ret_value, char **ret_endpos, bool * 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); +int udev_rules_load(UdevRules **ret_rules, ResolveNameTiming resolve_name_timing, char * const *extra); UdevRules* udev_rules_free(UdevRules *rules); DEFINE_TRIVIAL_CLEANUP_FUNC(UdevRules*, udev_rules_free); #define udev_rules_free_and_replace(a, b) free_and_replace_full(a, b, udev_rules_free) diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 5ceb6a5f28..c3f56d2d81 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -10,6 +10,9 @@ #include "sd-device.h" #include "device-private.h" +#include "parse-argument.h" +#include "static-destruct.h" +#include "strv.h" #include "udev-builtin.h" #include "udev-dump.h" #include "udev-event.h" @@ -20,8 +23,11 @@ static sd_device_action_t arg_action = SD_DEVICE_ADD; static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY; static const char *arg_syspath = NULL; +static char **arg_extra_rules_dir = NULL; static bool arg_verbose = false; +STATIC_DESTRUCTOR_REGISTER(arg_extra_rules_dir, strv_freep); + static int help(void) { printf("%s test [OPTIONS] DEVPATH\n\n" @@ -30,6 +36,7 @@ static int help(void) { " -V --version Show package version\n" " -a --action=ACTION|help Set action string\n" " -N --resolve-names=early|late|never When to resolve names\n" + " -D --extra-rules-dir=DIR Also load rules from the directory\n" " -v --verbose Show verbose logs\n", program_invocation_short_name); @@ -38,17 +45,18 @@ static int help(void) { static int parse_argv(int argc, char *argv[]) { static const struct option options[] = { - { "action", required_argument, NULL, 'a' }, - { "resolve-names", required_argument, NULL, 'N' }, - { "verbose", no_argument, NULL, 'v' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, + { "action", required_argument, NULL, 'a' }, + { "resolve-names", required_argument, NULL, 'N' }, + { "extra-rules-dir", required_argument, NULL, 'D' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, {} }; int r, c; - while ((c = getopt_long(argc, argv, "a:N:vVh", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "a:N:D:vVh", options, NULL)) >= 0) switch (c) { case 'a': r = parse_device_action(optarg, &arg_action); @@ -63,6 +71,18 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--resolve-names= must be early, late or never"); break; + case 'D': { + _cleanup_free_ char *p = NULL; + + r = parse_path_argument(optarg, /* suppress_root = */ false, &p); + if (r < 0) + return r; + + r = strv_consume(&arg_extra_rules_dir, TAKE_PTR(p)); + if (r < 0) + return log_oom(); + break; + } case 'v': arg_verbose = true; break; @@ -108,7 +128,7 @@ int test_main(int argc, char *argv[], void *userdata) { puts("Loading builtins done."); puts("\nLoading udev rules files..."); - r = udev_rules_load(&rules, arg_resolve_name_timing); + r = udev_rules_load(&rules, arg_resolve_name_timing, arg_extra_rules_dir); if (r < 0) { log_error_errno(r, "Failed to read udev rules: %m"); goto out;