From c3d526d765ee41905661c034c348c915a2f49625 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 7 Jan 2025 17:39:12 +0900 Subject: [PATCH 1/6] shell-completion/udevadm: add net_driver Follow-up for 2b5b25f123ceb89b3ff45b2380db1c8a88b046d9. --- shell-completion/bash/udevadm | 2 +- shell-completion/zsh/_udevadm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm index 63631fdaeb..da7fa21d90 100644 --- a/shell-completion/bash/udevadm +++ b/shell-completion/bash/udevadm @@ -78,7 +78,7 @@ _udevadm() { ) 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) + local builtins=(blkid btrfs hwdb input_id keyboard kmod net_driver net_id net_setup_link path_id uaccess usb_id) for ((i=0; i < COMP_CWORD; i++)); do if __contains_word "${COMP_WORDS[i]}" "${verbs[@]}"; then diff --git a/shell-completion/zsh/_udevadm b/shell-completion/zsh/_udevadm index 06aa79f47b..6c8478da75 100644 --- a/shell-completion/zsh/_udevadm +++ b/shell-completion/zsh/_udevadm @@ -101,7 +101,7 @@ _udevadm_test-builtin(){ '(- *)'{-h,--help}'[Print help]' \ '(- *)'{-V,--version}'[Print version of the program]' \ '--action=[The action string.]:actions:(add change remove move online offline bind unbind)' \ - '*::builtins:(blkid btrfs hwdb input_id net_id net_setup_link kmod path_id usb_id uaccess)' + '*::builtins:(blkid btrfs hwdb input_id net_driver net_id net_setup_link kmod path_id uaccess usb_id)' elif (( CURRENT == 3 )); then _arguments \ '--action=[The action string.]:actions:(add change remove move online offline bind unbind)' \ From 86a08e70a83330bc003432cf0d00ff5341acf0a0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 7 Jan 2025 17:41:41 +0900 Subject: [PATCH 2/6] udev: sort builtins Then, 'udevadm test-builtin --help' lists builtins alphabetically. --- src/udev/udev-builtin.c | 2 +- src/udev/udev-builtin.h | 2 +- src/udev/udev-def.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index b190504a83..749589463d 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -25,10 +25,10 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = { [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link, [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, - [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, #if HAVE_ACL [UDEV_BUILTIN_UACCESS] = &udev_builtin_uaccess, #endif + [UDEV_BUILTIN_USB_ID] = &udev_builtin_usb_id, }; void udev_builtin_init(void) { diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h index 3b5f3bd120..83cf103ab5 100644 --- a/src/udev/udev-builtin.h +++ b/src/udev/udev-builtin.h @@ -47,10 +47,10 @@ extern const UdevBuiltin udev_builtin_net_driver; extern const UdevBuiltin udev_builtin_net_id; extern const UdevBuiltin udev_builtin_net_setup_link; extern const UdevBuiltin udev_builtin_path_id; -extern const UdevBuiltin udev_builtin_usb_id; #if HAVE_ACL extern const UdevBuiltin udev_builtin_uaccess; #endif +extern const UdevBuiltin udev_builtin_usb_id; void udev_builtin_init(void); void udev_builtin_exit(void); diff --git a/src/udev/udev-def.h b/src/udev/udev-def.h index ed231764bc..c157c487cf 100644 --- a/src/udev/udev-def.h +++ b/src/udev/udev-def.h @@ -50,10 +50,10 @@ typedef enum UdevBuiltinCommand { UDEV_BUILTIN_NET_ID, UDEV_BUILTIN_NET_LINK, UDEV_BUILTIN_PATH_ID, - UDEV_BUILTIN_USB_ID, #if HAVE_ACL UDEV_BUILTIN_UACCESS, #endif + UDEV_BUILTIN_USB_ID, _UDEV_BUILTIN_MAX, _UDEV_BUILTIN_INVALID = -EINVAL, } UdevBuiltinCommand; @@ -73,10 +73,10 @@ typedef enum UdevReloadFlags { UDEV_RELOAD_BUILTIN_NET_ID = 1u << UDEV_BUILTIN_NET_ID, UDEV_RELOAD_BUILTIN_NET_LINK = 1u << UDEV_BUILTIN_NET_LINK, UDEV_RELOAD_BUILTIN_PATH_ID = 1u << UDEV_BUILTIN_PATH_ID, - UDEV_RELOAD_BUILTIN_USB_ID = 1u << UDEV_BUILTIN_USB_ID, #if HAVE_ACL UDEV_RELOAD_BUILTIN_UACCESS = 1u << UDEV_BUILTIN_UACCESS, #endif + UDEV_RELOAD_BUILTIN_USB_ID = 1u << UDEV_BUILTIN_USB_ID, UDEV_RELOAD_KILL_WORKERS = 1u << (_UDEV_BUILTIN_MAX + 0), UDEV_RELOAD_RULES = 1u << (_UDEV_BUILTIN_MAX + 1), } UdevReloadFlags; From 8e0f023548fae141acc6e3fb2a2ae4de4a284960 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 11 Jan 2025 03:45:38 +0900 Subject: [PATCH 3/6] udev-rules: log the first line number when continued --- src/udev/udev-rules.c | 12 ++++++++---- test/units/TEST-17-UDEV.11.sh | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 6c5c0f4533..29c8c37c29 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -1631,10 +1631,8 @@ static void udev_check_rule_line(UdevRuleLine *line) { int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_checks, UdevRuleFile **ret) { _cleanup_(udev_rule_file_freep) UdevRuleFile *rule_file = NULL; - _cleanup_free_ char *continuation = NULL, *name = NULL; + _cleanup_free_ char *name = NULL; _cleanup_fclose_ FILE *f = NULL; - bool ignore_line = false; - unsigned line_nr = 0; struct stat st; int r; @@ -1685,6 +1683,9 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che LIST_APPEND(rule_files, rules->rule_files, rule_file); + _cleanup_free_ char *continuation = NULL; + unsigned line_nr = 0, current_line_nr = 0; + bool ignore_line = false; for (;;) { _cleanup_free_ char *buf = NULL; size_t len; @@ -1696,7 +1697,10 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che if (r == 0) break; - line_nr++; + current_line_nr++; + if (!continuation) + line_nr = current_line_nr; + line = skip_leading_chars(buf, NULL); /* Lines beginning with '#' are ignored regardless of line continuation. */ diff --git a/test/units/TEST-17-UDEV.11.sh b/test/units/TEST-17-UDEV.11.sh index 8413d3c189..c0d87b7151 100755 --- a/test/units/TEST-17-UDEV.11.sh +++ b/test/units/TEST-17-UDEV.11.sh @@ -174,7 +174,7 @@ assert_0 "${rules}" printf 'RUN+="/bin/true"%8176s\\\n #\n' ' ' ' ' >"${rules}" echo >>"${rules}" cat >"${exp}" < Date: Thu, 9 Jan 2025 10:27:11 +0900 Subject: [PATCH 4/6] udevadm-verify: chase specified paths Also, when a filename is specified, also search udev rules file in udev/rules.d directories. This also refuses non-existing files, and file neither nor a regular nor a directory, e.g. /dev/null. --- man/udevadm.xml | 28 +++++---- src/udev/udevadm-util.c | 109 ++++++++++++++++++++++++++++++++++ src/udev/udevadm-util.h | 1 + src/udev/udevadm-verify.c | 85 ++++++-------------------- test/units/TEST-17-UDEV.11.sh | 79 ++++++++++++------------ 5 files changed, 182 insertions(+), 120 deletions(-) diff --git a/man/udevadm.xml b/man/udevadm.xml index c325df9118..842b7b5ab5 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -954,9 +954,15 @@ Verify syntactic, semantic, and stylistic correctness of udev rules files. - 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. + Positional arguments can be used to specify one or more files to check. Each argument must be an + absolute path to a udev rules file or a directory that contains rules files, or a file name of udev + rules file (e.g. 99-systemd.rules). If a file name is specified, the file will be + searched in the udev/rules.d directories that are processed by + systemd-udevd, and searched in the current working directory if not found. 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 systemd-udevd. + See udev7 for more + details about the search paths. The exit status is 0 if all specified udev rules files are syntactically, semantically, and stylistically correct, @@ -980,8 +986,11 @@ - When looking for udev rules files located in udev/rules.d directories, - operate on files underneath the specified root path PATH. + When looking for udev rules files located in the udev/rules.d + directories, operate on files underneath the specified root path PATH. + When a file name is specified, and it is not found in the udev/rules.d + directories, the file will be searched in the specified root path + PATH, rather than the current working directory. @@ -1211,12 +1220,9 @@ See Also - - udev7 - - - systemd-udevd.service8 - + udev7 + systemd-udevd.service8 + udev.conf5 diff --git a/src/udev/udevadm-util.c b/src/udev/udevadm-util.c index 2a3e974d04..641adb4f97 100644 --- a/src/udev/udevadm-util.c +++ b/src/udev/udevadm-util.c @@ -5,6 +5,9 @@ #include "alloc-util.h" #include "bus-error.h" #include "bus-util.h" +#include "chase.h" +#include "conf-files.h" +#include "constants.h" #include "device-private.h" #include "path-util.h" #include "udev-ctrl.h" @@ -168,3 +171,109 @@ int udev_ping(usec_t timeout_usec, bool ignore_connection_failure) { return 1; /* received reply */ } + +static int search_rules_file_in_conf_dirs(const char *s, const char *root, char ***files) { + _cleanup_free_ char *filename = NULL; + int r; + + assert(s); + + if (!endswith(s, ".rules")) + filename = strjoin(s, ".rules"); + else + filename = strdup(s); + if (!filename) + return log_oom(); + + if (!filename_is_valid(filename)) + return 0; + + STRV_FOREACH(p, CONF_PATHS_STRV("udev/rules.d")) { + _cleanup_free_ char *path = NULL, *resolved = NULL; + + path = path_join(*p, filename); + if (!path) + return log_oom(); + + r = chase(path, root, CHASE_PREFIX_ROOT | CHASE_MUST_BE_REGULAR, &resolved, /* ret_fd = */ NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to chase \"%s\": %m", path); + + r = strv_consume(files, TAKE_PTR(resolved)); + if (r < 0) + return log_oom(); + + return 1; /* found */ + } + + return 0; +} + +static int search_rules_file(const char *s, const char *root, char ***files) { + int r; + + assert(s); + assert(files); + + /* If the input is a file name (e.g. 99-systemd.rules), then try to find it in udev/rules.d directories. */ + r = search_rules_file_in_conf_dirs(s, root, files); + if (r != 0) + return r; + + /* If not found, or if it is a path, then chase it. */ + struct stat st; + _cleanup_free_ char *resolved = NULL; + r = chase_and_stat(s, root, CHASE_PREFIX_ROOT, &resolved, &st); + if (r < 0) + return log_error_errno(r, "Failed to chase \"%s\": %m", s); + + r = stat_verify_regular(&st); + if (r == -EISDIR) { + _cleanup_strv_free_ char **files_in_dir = NULL; + + r = conf_files_list_strv(&files_in_dir, ".rules", root, 0, (const char* const*) STRV_MAKE_CONST(s)); + if (r < 0) + return log_error_errno(r, "Failed to enumerate rules files in '%s': %m", resolved); + + r = strv_extend_strv_consume(files, TAKE_PTR(files_in_dir), /* filter_duplicates = */ false); + if (r < 0) + return log_oom(); + + return 0; + } + if (r < 0) + return log_error_errno(r, "'%s' is neither a regular file nor a directory: %m", resolved); + + r = strv_consume(files, TAKE_PTR(resolved)); + if (r < 0) + return log_oom(); + + return 0; +} + +int search_rules_files(char * const *a, const char *root, char ***ret) { + _cleanup_strv_free_ char **files = NULL; + int r; + + assert(ret); + + if (strv_isempty(a)) { + r = conf_files_list_strv(&files, ".rules", root, 0, (const char* const*) CONF_PATHS_STRV("udev/rules.d")); + if (r < 0) + return log_error_errno(r, "Failed to enumerate rules files: %m"); + + if (root && strv_isempty(files)) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No rules files found in %s.", root); + + } else + STRV_FOREACH(s, a) { + r = search_rules_file(*s, root, &files); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(files); + return 0; +} diff --git a/src/udev/udevadm-util.h b/src/udev/udevadm-util.h index 4b58efbe97..0a8b31ada7 100644 --- a/src/udev/udevadm-util.h +++ b/src/udev/udevadm-util.h @@ -7,3 +7,4 @@ int find_device(const char *id, const char *prefix, sd_device **ret); int find_device_with_action(const char *id, sd_device_action_t action, sd_device **ret); int parse_device_action(const char *str, sd_device_action_t *action); int udev_ping(usec_t timeout, bool ignore_connection_failure); +int search_rules_files(char * const *a, const char *root, char ***ret); diff --git a/src/udev/udevadm-verify.c b/src/udev/udevadm-verify.c index 32202508f3..fb8cdee4f2 100644 --- a/src/udev/udevadm-verify.c +++ b/src/udev/udevadm-verify.c @@ -7,16 +7,15 @@ #include #include -#include "conf-files.h" -#include "constants.h" +#include "errno-util.h" #include "log.h" #include "parse-argument.h" #include "pretty-print.h" -#include "stat-util.h" #include "static-destruct.h" #include "strv.h" #include "udev-rules.h" #include "udevadm.h" +#include "udevadm-util.h" static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY; static char *arg_root = NULL; @@ -109,10 +108,6 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } - if (arg_root && optind < argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Combination of --root= and FILEs is not supported."); - return 1; } @@ -139,57 +134,20 @@ static int verify_rules_file(UdevRules *rules, const char *fname) { return 0; } -static int verify_rules_filelist(UdevRules *rules, char **files, size_t *fail_count, size_t *success_count, bool walk_dirs); - -static int verify_rules_dir(UdevRules *rules, const char *dir, size_t *fail_count, size_t *success_count) { - int r; - _cleanup_strv_free_ char **files = NULL; - - assert(rules); - assert(dir); - assert(fail_count); - assert(success_count); - - r = conf_files_list(&files, ".rules", NULL, 0, dir); - if (r < 0) - return log_error_errno(r, "Failed to enumerate rules files: %m"); - - return verify_rules_filelist(rules, files, fail_count, success_count, /* walk_dirs */ false); -} - -static int verify_rules_filelist(UdevRules *rules, char **files, size_t *fail_count, size_t *success_count, bool walk_dirs) { - int r, rv = 0; - - assert(rules); - assert(files); - assert(fail_count); - assert(success_count); - - STRV_FOREACH(fp, files) { - if (walk_dirs && is_dir(*fp, /* follow = */ true) > 0) - r = verify_rules_dir(rules, *fp, fail_count, success_count); - else { - r = verify_rules_file(rules, *fp); - if (r < 0) - ++(*fail_count); - else - ++(*success_count); - } - if (r < 0 && rv >= 0) - rv = r; - } - - return rv; -} - static int verify_rules(UdevRules *rules, char **files) { size_t fail_count = 0, success_count = 0; - int r; + int r, ret = 0; assert(rules); - assert(files); - r = verify_rules_filelist(rules, files, &fail_count, &success_count, /* walk_dirs */ true); + STRV_FOREACH(fp, files) { + r = verify_rules_file(rules, *fp); + if (r < 0) + ++fail_count; + else + ++success_count; + RET_GATHER(ret, r); + } if (arg_summary) printf("\n%s%zu udev rules files have been checked.%s\n" @@ -203,7 +161,7 @@ static int verify_rules(UdevRules *rules, char **files) { fail_count, fail_count > 0 ? ansi_normal() : ""); - return r; + return ret; } int verify_main(int argc, char *argv[], void *userdata) { @@ -218,19 +176,10 @@ 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; + _cleanup_strv_free_ char **files = NULL; + r = search_rules_files(strv_skip(argv, optind), arg_root, &files); + if (r < 0) + return r; - 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)); + return verify_rules(rules, files); } diff --git a/test/units/TEST-17-UDEV.11.sh b/test/units/TEST-17-UDEV.11.sh index c0d87b7151..ac99a80b0f 100755 --- a/test/units/TEST-17-UDEV.11.sh +++ b/test/units/TEST-17-UDEV.11.sh @@ -8,6 +8,8 @@ set -o pipefail # shellcheck source=test/units/util.sh . "$(dirname "$0")"/util.sh +PATH=/var/build:$PATH + # shellcheck disable=SC2317 cleanup() { cd / @@ -101,7 +103,6 @@ assert_0 -h assert_0 --help assert_0 -V assert_0 --version -assert_0 /dev/null # unrecognized option '--unknown' assert_1 --unknown @@ -116,13 +117,9 @@ assert_1 --resolve-names=now # Failed to parse rules file ./nosuchfile: No such file or directory assert_1 ./nosuchfile # Failed to parse rules file ./nosuchfile: No such file or directory -cat >"${exo}" <"${rules}" -echo "Failed to parse rules file ${rules}: No buffer space available" >"${exp}" +echo "Failed to parse rules file $(pwd)/${rules}: No buffer space available" >"${exp}" assert_1 "${rules}" { @@ -174,17 +169,17 @@ assert_0 "${rules}" printf 'RUN+="/bin/true"%8176s\\\n #\n' ' ' ' ' >"${rules}" echo >>"${rules}" cat >"${exp}" <"${rules}" cat >"${exp}" <"${rules}" cat >"${exp}" <"${rules}" cat >"${exp}" <"${rules}" cat >"${exp}" <"${exp}" <"${exp}" <"${rules}" <<'EOF' KERNEL!="", KERNEL=="?*", KERNEL=="", NAME="a" EOF cat >"${exp}" <"${rules}" <<'EOF' ACTION=="a"NAME="b" EOF cat >"${exp}" <"${rules}" <<'EOF' ACTION=="a" ,NAME="b" EOF cat >"${exp}" <"${workdir}/${exp}" +#sed "s|sample-[0-9]*.rules|${workdir}/${rules_dir}/&|" sample-*.exp >"${workdir}/${exp}" +cat sample-*.exp >"${workdir}/${exp}" cd - assert_1 --root="${workdir}" cd - # udevadm verify path/ -sed "s|sample-[0-9]*.rules|${workdir}/${rules_dir}/&|" sample-*.exp >"${workdir}/${exp}" +#sed "s|sample-[0-9]*.rules|${workdir}/${rules_dir}/&|" sample-*.exp >"${workdir}/${exp}" +cat sample-*.exp >"${workdir}/${exp}" cd - assert_1 "${rules_dir}" cd - From bbe1ba5e87a74fa0b56d88a7266040e534c527b0 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 9 Jan 2025 19:25:49 +0900 Subject: [PATCH 5/6] bash-completion/udevadm-verify: suggest found udev rules files This also fixes the issue that no suggestion is provided after a standalone option is specified. --- shell-completion/bash/udevadm | 50 ++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm index da7fa21d90..bc8dc5515b 100644 --- a/shell-completion/bash/udevadm +++ b/shell-completion/bash/udevadm @@ -46,6 +46,35 @@ __get_all_devices() { __get_all_device_units } +__get_root() { + local i + + for ((i=0; i < COMP_CWORD; i++)); do + if [[ "${COMP_WORDS[i]}" = --root=* ]]; then + echo "${COMP_WORDS[i]#--root=}" + break + fi + if (( i > 0 )) && [[ "${COMP_WORDS[i-1]}" == "--root" ]]; then + echo "${COMP_WORDS[i]}" + break + fi + done +} + +__get_udev_rules_files() { + local root=$( __get_root ) + + ls "$root"/usr/lib/udev/rules.d/*.rules \ + "$root"/usr/local/lib/udev/rules.d/*.rules \ + "$root"/run/udev/rules.d/*.rules \ + "$root"/etc/udev/rules.d/*.rules 2>/dev/null +} + +__get_udev_rules_names() { + local -a rules=( $( __get_udev_rules_files ) ) + printf '%s\n' "${rules[@]##*/}" +} + _udevadm() { local i verb comps builtin local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} @@ -72,7 +101,8 @@ _udevadm() { [TEST_STANDALONE]='-v --verbose' [TEST_ARG]='-a --action -N --resolve-names -D --extra-rules-dir' [TEST_BUILTIN]='-a --action' - [VERIFY]='-N --resolve-names --root --no-summary --no-style' + [VERIFY_STANDALONE]='--no-summary --no-style' + [VERIFY_ARG]='-N --resolve-names --root' [WAIT]='-t --timeout --initialized=no --removed --settle' [LOCK]='-t --timeout -d --device -b --backing -p --print' ) @@ -269,27 +299,27 @@ _udevadm() { ;; 'verify') - if __contains_word "$prev" ${OPTS[VERIFY]}; then + if __contains_word "$prev" ${OPTS[VERIFY_ARG]}; then case $prev in -N|--resolve-names) comps='early never' ;; --root) - comps=$(compgen -A directory -- "$cur" ) + comps='' compopt -o dirnames ;; *) comps='' ;; esac - COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) - return 0 - fi - - if [[ $cur = -* ]]; then - comps="${OPTS[COMMON]} ${OPTS[VERIFY]}" + elif [[ $cur = -* ]]; then + comps="${OPTS[COMMON]} ${OPTS[VERIFY_ARG]} ${OPTS[VERIFY_STANDALONE]}" + elif [[ $cur = */* ]]; then + comps=$( __get_udev_rules_files ) + compopt -o dirnames else - comps=$( compgen -A file -- "$cur" ) + comps=$( __get_udev_rules_names ) + compopt -o default fi ;; From 7f2175eabbea882d02e9df0e9fcb8e2f03a121da Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 7 Jan 2025 16:58:37 +0900 Subject: [PATCH 6/6] udevadm: introduce cat command This introduces 'udevadm cat' command, that shows udev rules files or udev.conf, which may be useful for debugging. Closes #35818. --- man/udevadm.xml | 64 ++++++++++++++++++++ shell-completion/bash/udevadm | 28 ++++++++- shell-completion/zsh/_udevadm | 11 ++++ src/udev/meson.build | 1 + src/udev/udevadm-cat.c | 110 ++++++++++++++++++++++++++++++++++ src/udev/udevadm.c | 2 + src/udev/udevadm.h | 1 + test/units/TEST-17-UDEV.10.sh | 9 +++ 8 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 src/udev/udevadm-cat.c diff --git a/man/udevadm.xml b/man/udevadm.xml index 842b7b5ab5..507ab49b48 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -53,6 +53,11 @@ options file + + udevadm cat + options + file + udevadm wait options device|syspath @@ -1019,6 +1024,65 @@ + + udevadm cat + <optional><replaceable>options</replaceable></optional> + <optional><replaceable>file</replaceable></optional> + … + + + Show udev rules files or udev.conf. + + Positional arguments can be used to specify one or more files to show. Each argument must be an + absolute path to a udev rules file or a directory that contains rules files, or a file name of udev + rules file (e.g. 99-systemd.rules). If a file name is specified, the file will be + searched in the udev/rules.d directories that are processed by + systemd-udevd, and searched in the current working directory if not found. 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 systemd-udevd. + See udev7 for more + details about the search paths. + + + + + + When looking for udev rules files located in the udev/rules.d + directories, operate on files underneath the specified root path PATH. + When a file name is specified, and it is not found in the udev/rules.d + directories, the file will be searched in the specified root path + PATH, rather than the current working directory. + + + + + + + + + Only print the "interesting" parts of the configuration files, skipping comments and empty + lines and section headers followed only by comments and empty lines. + + + + + + + + + Shows + udev.conf5 + files, rather than udev rules files. When specified, no udev rules file cannot be specified. + + + + + + + + + + udevadm wait <optional><replaceable>options</replaceable></optional> diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm index bc8dc5515b..02a025ff45 100644 --- a/shell-completion/bash/udevadm +++ b/shell-completion/bash/udevadm @@ -103,11 +103,13 @@ _udevadm() { [TEST_BUILTIN]='-a --action' [VERIFY_STANDALONE]='--no-summary --no-style' [VERIFY_ARG]='-N --resolve-names --root' + [CAT_STANDALONE]='--tldr --config' + [CAT_ARG]='--root' [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 verify wait lock) + local verbs=(info trigger settle control monitor test-builtin test verify cat wait lock) local builtins=(blkid btrfs hwdb input_id keyboard kmod net_driver net_id net_setup_link path_id uaccess usb_id) for ((i=0; i < COMP_CWORD; i++)); do @@ -323,6 +325,30 @@ _udevadm() { fi ;; + 'cat') + if __contains_word "$prev" ${OPTS[CAT_ARG]}; then + case $prev in + --root) + comps='' + compopt -o dirnames + ;; + *) + comps='' + ;; + esac + elif [[ $cur = -* ]]; then + comps="${OPTS[COMMON]} ${OPTS[CAT_ARG]} ${OPTS[CAT_STANDALONE]}" + elif __contains_word "--config" ${COMP_WORDS[*]}; then + comps="${OPTS[COMMON]} ${OPTS[CAT_ARG]} ${OPTS[CAT_STANDALONE]}" + elif [[ $cur = */* ]]; then + comps=$( __get_udev_rules_files ) + compopt -o dirnames + else + comps=$( __get_udev_rules_names ) + compopt -o default + 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 6c8478da75..7572b09f67 100644 --- a/shell-completion/zsh/_udevadm +++ b/shell-completion/zsh/_udevadm @@ -126,6 +126,17 @@ _udevadm_verify(){ '*::files:_files' } +(( $+functions[_udevadm_cat] )) || +_udevadm_cat(){ + _arguments \ + '(- *)'{-h,--help}'[Show help]' \ + '(- *)'{-V,--version}'[Show package version]' \ + '--root=[Operate on catalog hierarchy under specified directory]:directories:_directories' \ + --tldr'[Skip comments and empty lines.]' \ + --config'[Show udev.conf.]' \ + '*::files:_files' +} + (( $+functions[_udevadm_wait] )) || _udevadm_wait(){ _arguments \ diff --git a/src/udev/meson.build b/src/udev/meson.build index 8b40e02a8b..697d108141 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later udevadm_sources = files( + 'udevadm-cat.c', 'udevadm-control.c', 'udevadm-hwdb.c', 'udevadm-info.c', diff --git a/src/udev/udevadm-cat.c b/src/udev/udevadm-cat.c new file mode 100644 index 0000000000..2d7e86994d --- /dev/null +++ b/src/udev/udevadm-cat.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include <getopt.h> + +#include "log.h" +#include "parse-argument.h" +#include "pretty-print.h" +#include "static-destruct.h" +#include "strv.h" +#include "udevadm.h" +#include "udevadm-util.h" + +static char *arg_root = NULL; +static CatFlags arg_cat_flags = 0; +static bool arg_config = false; + +STATIC_DESTRUCTOR_REGISTER(arg_root, freep); + +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 cat [OPTIONS] [FILE...]\n" + "\n%sShow udev rules files.%s\n\n" + " -h --help Show this help\n" + " -V --version Show package version\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --tldr Skip comments and empty lines\n" + " --config Show udev.conf rather than udev rules files\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[]) { + enum { + ARG_ROOT = 0x100, + ARG_TLDR, + ARG_CONFIG, + }; + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "root", required_argument, NULL, ARG_ROOT }, + { "tldr", no_argument, NULL, ARG_TLDR }, + { "config", no_argument, NULL, ARG_CONFIG }, + {} + }; + + int r, 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 ARG_ROOT: + r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + if (r < 0) + return r; + break; + case ARG_TLDR: + arg_cat_flags = CAT_TLDR; + break; + case ARG_CONFIG: + arg_config = true; + break; + case '?': + return -EINVAL; + default: + assert_not_reached(); + } + + if (arg_config && optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Combination of --config and FILEs is not supported."); + + return 1; +} + +int cat_main(int argc, char *argv[], void *userdata) { + int r; + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (arg_config) + return conf_files_cat(arg_root, "udev/udev.conf", arg_cat_flags); + + _cleanup_strv_free_ char **files = NULL; + r = search_rules_files(strv_skip(argv, optind), arg_root, &files); + if (r < 0) + return r; + + /* udev rules file does not support dropin configs. So, we can safely pass multiple files as dropins. */ + return cat_files(/* file = */ NULL, /* dropins = */ files, arg_cat_flags); +} diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 30b6ddb728..ebf1e5190c 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -26,6 +26,7 @@ static int help(void) { { "test", "Test an event run" }, { "test-builtin", "Test a built-in command" }, { "verify", "Verify udev rules files" }, + { "cat", "Show udev rules files" }, { "wait", "Wait for device or device symlink" }, { "lock", "Lock a block device" }, }; @@ -97,6 +98,7 @@ static int help_main(int argc, char *argv[], void *userdata) { static int udevadm_main(int argc, char *argv[]) { static const Verb verbs[] = { + { "cat", VERB_ANY, VERB_ANY, 0, cat_main }, { "info", VERB_ANY, VERB_ANY, 0, info_main }, { "trigger", VERB_ANY, VERB_ANY, 0, trigger_main }, { "settle", VERB_ANY, VERB_ANY, 0, settle_main }, diff --git a/src/udev/udevadm.h b/src/udev/udevadm.h index 7920a70d5b..e39dbf655d 100644 --- a/src/udev/udevadm.h +++ b/src/udev/udevadm.h @@ -5,6 +5,7 @@ #include "macro.h" +int cat_main(int argc, char *argv[], void *userdata); int info_main(int argc, char *argv[], void *userdata); int trigger_main(int argc, char *argv[], void *userdata); int settle_main(int argc, char *argv[], void *userdata); diff --git a/test/units/TEST-17-UDEV.10.sh b/test/units/TEST-17-UDEV.10.sh index 8643e9e6bb..b8f5648a47 100755 --- a/test/units/TEST-17-UDEV.10.sh +++ b/test/units/TEST-17-UDEV.10.sh @@ -33,6 +33,15 @@ udevadm settle --timeout 30 udevadm -h +udevadm cat +udevadm cat 99-systemd +udevadm cat 99-systemd.rules +udevadm cat /usr/lib/udev/rules.d/99-systemd.rules +udevadm cat /usr/lib/udev/rules.d +(! udevadm cat /dev/null) +udevadm cat --config +udevadm cat -h + INVOCATION_ID=$(systemctl show --property InvocationID --value systemd-udevd.service) udevadm control -e # Wait for systemd-udevd.service being restarted.