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
+ options
+ file
+ …
+
+
+ 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
options
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
+
+#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.