From 29959da4228413ad35faf9656c8304ebfd0c482d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 7 Jan 2025 16:58:37 +0900 Subject: [PATCH] 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. (cherry picked from commit 7f2175eabbea882d02e9df0e9fcb8e2f03a121da) Resolves: RHEL-75774 --- 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 eed84d27cc..9cbbdc254d 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -53,6 +53,11 @@ options file + + udevadm cat + options + file + udevadm wait options device|syspath @@ -1000,6 +1005,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 54d024c1b8..010d4cff8a 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 @@ -320,6 +322,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 63db11cea9..c9b43e231d 100644 --- a/shell-completion/zsh/_udevadm +++ b/shell-completion/zsh/_udevadm @@ -124,6 +124,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 171bbd2b70..62cd8014d2 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 be342b3468..68d310a8e5 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.