This adds -D/--extra-rules-dir=DIR switch for 'udevadm test' command.
When specified, udev rules files in the specified directory will be also
loaded. This may be useful for debugging udev rules by copying some udev
rules files to a temporary directory.
<xi:include href="version-info.xml" xpointer="v209"/>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-D <replaceable>DIR</replaceable></option></term>
+ <term><option>--extra-rules-dir=<replaceable>DIR</replaceable></option></term>
+ <listitem>
+ <para>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
+ <filename>udev/rules.d</filename> directories used by <command>systemd-udevd</command>. See
+ <citerefentry><refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum></citerefentry> for more
+ details about the search paths.</para>
+
+ <xi:include href="version-info.xml" xpointer="v258"/>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term><option>-v</option></term>
<term><option>--verbose</option></term>
[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'
-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]}"
'(-)'{-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'
}
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);
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
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");
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");
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)
#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"
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"
" -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);
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);
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;
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;