From 7543ae05e3063e582218a60d57b0e4a2583b8058 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 8 Feb 2025 01:25:15 +0900 Subject: [PATCH 1/9] sd-device: introduce device_get_sysnum_unsigned() --- src/libsystemd/sd-device/device-private.h | 1 + src/libsystemd/sd-device/sd-device.c | 20 ++++++++++++++++++++ src/libsystemd/sd-device/test-sd-device.c | 2 ++ 3 files changed, 23 insertions(+) diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h index aa42cbb2a1..7d925aa581 100644 --- a/src/libsystemd/sd-device/device-private.h +++ b/src/libsystemd/sd-device/device-private.h @@ -18,6 +18,7 @@ int device_new_from_strv(sd_device **ret, char **strv); int device_opendir(sd_device *device, const char *subdir, DIR **ret); +int device_get_sysnum_unsigned(sd_device *device, unsigned *ret); int device_get_property_bool(sd_device *device, const char *key); int device_get_property_int(sd_device *device, const char *key, int *ret); int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value); diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 033a9eb875..ac441a7f38 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -1445,6 +1445,26 @@ _public_ int sd_device_get_sysnum(sd_device *device, const char **ret) { return 0; } +int device_get_sysnum_unsigned(sd_device *device, unsigned *ret) { + int r; + + assert(device); + + const char *s; + r = sd_device_get_sysnum(device, &s); + if (r < 0) + return r; + + unsigned n; + r = safe_atou_full(s, SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_WHITESPACE | 10, &n); + if (r < 0) + return r; + + if (ret) + *ret = n; + return 0; +} + _public_ int sd_device_get_action(sd_device *device, sd_device_action_t *ret) { assert_return(device, -EINVAL); diff --git a/src/libsystemd/sd-device/test-sd-device.c b/src/libsystemd/sd-device/test-sd-device.c index 230493ed18..b5ef0dca35 100644 --- a/src/libsystemd/sd-device/test-sd-device.c +++ b/src/libsystemd/sd-device/test-sd-device.c @@ -182,6 +182,8 @@ static void test_sd_device_one(sd_device *d) { assert_se(val < sysname + strlen(sysname)); assert_se(in_charset(val, DIGITS)); assert_se(!ascii_isdigit(val[-1])); + r = device_get_sysnum_unsigned(d, NULL); + ASSERT_TRUE(r >= 0 || r == -ERANGE); /* sysnum may be too large. */ } else assert_se(r == -ENOENT); From d083799a2c13a83e6b34438454a29653e6cd264b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 8 Feb 2025 01:31:04 +0900 Subject: [PATCH 2/9] udev: use device_get_sysnum_unsigned() where applicable --- src/udev/udev-builtin-net_id.c | 8 ++------ src/udev/udev-builtin-path_id.c | 15 ++++----------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index 44c202760e..9b3f62ee69 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -1237,7 +1237,7 @@ static int names_mac(UdevEvent *event, const char *prefix) { static int names_netdevsim(UdevEvent *event, const char *prefix) { sd_device *netdevsimdev, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); - const char *sysnum, *phys_port_name; + const char *phys_port_name; unsigned addr; int r; @@ -1252,14 +1252,10 @@ static int names_netdevsim(UdevEvent *event, const char *prefix) { if (r < 0) return r; - r = sd_device_get_sysnum(netdevsimdev, &sysnum); + r = device_get_sysnum_unsigned(netdevsimdev, &addr); if (r < 0) return log_device_debug_errno(netdevsimdev, r, "Failed to get device sysnum: %m"); - r = safe_atou(sysnum, &addr); - if (r < 0) - return log_device_debug_errno(netdevsimdev, r, "Failed to parse device sysnum: %m"); - r = device_get_sysattr_value_filtered(dev, "phys_port_name", &phys_port_name); if (r < 0) return log_device_debug_errno(dev, r, "Failed to get 'phys_port_name' attribute: %m"); diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index d6ea471482..f85ad9b435 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -59,25 +59,18 @@ static void path_prepend(char **path, const char *fmt, ...) { ** See drivers/scsi/scsi_scan.c::scsilun_to_int() for more details. */ static int format_lun_number(sd_device *dev, char **path) { - const char *sysnum; - unsigned long lun; + unsigned lun; int r; - r = sd_device_get_sysnum(dev, &sysnum); - if (r < 0) - return r; - if (!sysnum) - return -ENOENT; - - r = safe_atolu_full(sysnum, 10, &lun); + r = device_get_sysnum_unsigned(dev, &lun); if (r < 0) return r; if (lun < 256) /* address method 0, peripheral device addressing with bus id of zero */ - path_prepend(path, "lun-%lu", lun); + path_prepend(path, "lun-%u", lun); else /* handle all other lun addressing methods by using a variant of the original lun format */ - path_prepend(path, "lun-0x%04lx%04lx00000000", lun & 0xffff, (lun >> 16) & 0xffff); + path_prepend(path, "lun-0x%04x%04x00000000", lun & 0xffff, (lun >> 16) & 0xffff); return 0; } From 0d771855550208a77854478b2b8db3610d7be0dc Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 8 Feb 2025 01:32:46 +0900 Subject: [PATCH 3/9] udev: sd_device_get_sysnum() provides non-NULL result on success This drops unnecessary conditions. --- src/udev/udev-builtin-path_id.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index f85ad9b435..5237ae3090 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -234,7 +234,7 @@ static sd_device* handle_scsi_iscsi(sd_device *parent, char **path) { if (sd_device_get_sysattr_value(sessiondev, "targetname", &target) < 0) return NULL; - if (sd_device_get_sysnum(transportdev, &sysnum) < 0 || !sysnum) + if (sd_device_get_sysnum(transportdev, &sysnum) < 0) return NULL; connname = strjoina("connection", sysnum, ":0"); if (sd_device_new_from_subsystem_sysname(&conndev, "iscsi_connection", connname) < 0) @@ -716,7 +716,7 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[]) { } else if (device_in_subsystem(parent, "serio")) { const char *sysnum; - if (sd_device_get_sysnum(parent, &sysnum) >= 0 && sysnum) { + if (sd_device_get_sysnum(parent, &sysnum) >= 0) { path_prepend(&path, "serio-%s", sysnum); parent = skip_subsystem(parent, "serio"); } @@ -810,7 +810,7 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[]) { } else if (device_in_subsystem(parent, "spi")) { const char *sysnum; - if (sd_device_get_sysnum(parent, &sysnum) >= 0 && sysnum) { + if (sd_device_get_sysnum(parent, &sysnum) >= 0) { path_prepend(&path, "cs-%s", sysnum); parent = skip_subsystem(parent, "spi"); } From e66d4ea31ad3812fdd7a0f1cdcc3b941cedf542a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 7 Feb 2025 11:07:37 +0900 Subject: [PATCH 4/9] udev-dump: fix gid check Fixes a bug introduced by 03b6879f4d45c49264708aef872fd05af30ddcf0. --- src/udev/udev-dump.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/udev/udev-dump.c b/src/udev/udev-dump.c index 1dcc141042..2882ad1001 100644 --- a/src/udev/udev-dump.c +++ b/src/udev/udev-dump.c @@ -81,7 +81,7 @@ void dump_event(UdevEvent *event, FILE *f) { } gid_t gid = event->gid; - if (!gid_is_valid(uid)) + if (!gid_is_valid(gid)) (void) device_get_devnode_gid(dev, &gid); if (gid_is_valid(gid)) { _cleanup_free_ char *group = gid_to_name(gid); From fc30b1a0b0e98541e7d317810a53f69abc382865 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Fri, 7 Feb 2025 10:04:41 +0900 Subject: [PATCH 5/9] udev-dump: show more information This also reorders and renames entries. --- src/udev/udev-dump.c | 153 +++++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 56 deletions(-) diff --git a/src/udev/udev-dump.c b/src/udev/udev-dump.c index 2882ad1001..b13779e5f7 100644 --- a/src/udev/udev-dump.c +++ b/src/udev/udev-dump.c @@ -3,6 +3,7 @@ #include "ansi-color.h" #include "device-private.h" #include "device-util.h" +#include "devnum-util.h" #include "format-util.h" #include "fs-util.h" #include "udev-builtin.h" @@ -30,10 +31,100 @@ void event_cache_written_sysctl(UdevEvent *event, const char *attr, const char * void dump_event(UdevEvent *event, FILE *f) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + const char *subsys = NULL, *str; if (!f) f = stdout; + if (sd_device_get_devpath(dev, &str) >= 0) + fprintf(f, "%sDevice path:%s\n %s\n", ansi_highlight(), ansi_normal(), str); + + if (sd_device_get_sysname(dev, &str) >= 0) + fprintf(f, "%sDevice name:%s\n %s\n", ansi_highlight(), ansi_normal(), str); + + if (sd_device_get_sysnum(dev, &str) >= 0) + fprintf(f, "%sDevice number:%s\n %s\n", ansi_highlight(), ansi_normal(), str); + + if (sd_device_get_device_id(dev, &str) >= 0) + fprintf(f, "%sDevice ID:%s\n %s\n", ansi_highlight(), ansi_normal(), str); + + if (sd_device_get_subsystem(dev, &subsys) >= 0) { + const char *driver_subsys = NULL; + (void) sd_device_get_driver_subsystem(dev, &driver_subsys); + fprintf(f, "%sSubsystem:%s\n %s%s%s%s\n", ansi_highlight(), ansi_normal(), subsys, + driver_subsys ? " (" : "", + strempty(driver_subsys), + driver_subsys ? ")" : ""); + } + + if (sd_device_get_devtype(dev, &str) >= 0) + fprintf(f, "%sDevice type:%s\n %s\n", ansi_highlight(), ansi_normal(), str); + + if (sd_device_get_driver(dev, &str) >= 0) + fprintf(f, "%sDevice driver:%s\n %s\n", ansi_highlight(), ansi_normal(), str); + + if (sd_device_get_devname(dev, &str) >= 0) { + dev_t devnum; + if (sd_device_get_devnum(dev, &devnum) >= 0) + fprintf(f, "%sDevice node:%s\n %s (%s "DEVNUM_FORMAT_STR")\n", ansi_highlight(), ansi_normal(), str, + streq_ptr(subsys, "block") ? "block" : "char", + DEVNUM_FORMAT_VAL(devnum)); + + uid_t uid = event->uid; + if (!uid_is_valid(uid)) + (void) device_get_devnode_uid(dev, &uid); + if (uid_is_valid(uid)) { + _cleanup_free_ char *user = uid_to_name(uid); + fprintf(f, "%sDevice node owner user:%s\n %s (uid="UID_FMT")\n", ansi_highlight(), ansi_normal(), strna(user), uid); + } + + gid_t gid = event->gid; + if (!gid_is_valid(gid)) + (void) device_get_devnode_gid(dev, &gid); + if (gid_is_valid(gid)) { + _cleanup_free_ char *group = gid_to_name(gid); + fprintf(f, "%sDevice node owner group:%s\n %s (gid="GID_FMT")\n", ansi_highlight(), ansi_normal(), strna(group), gid); + } + + mode_t mode = event->mode; + if (mode == MODE_INVALID) + (void) device_get_devnode_mode(dev, &mode); + if (mode != MODE_INVALID) + fprintf(f, "%sDevice node permission:%s\n %04o\n", ansi_highlight(), ansi_normal(), mode); + + if (sd_device_get_devlink_first(dev)) { + int prio = 0; + (void) device_get_devlink_priority(dev, &prio); + fprintf(f, "%sDevice node symlinks:%s (priority=%i)\n", ansi_highlight(), ansi_normal(), prio); + FOREACH_DEVICE_DEVLINK(dev, devlink) + fprintf(f, " %s\n", devlink); + } + + if (!ordered_hashmap_isempty(event->seclabel_list)) { + const char *name, *label; + fprintf(f, "%sDevice node security label:%s\n", ansi_highlight(), ansi_normal()); + ORDERED_HASHMAP_FOREACH_KEY(label, name, event->seclabel_list) + fprintf(f, " %s : %s\n", name, label); + } + + fprintf(f, "%sInotify watch:%s\n %s\n", ansi_highlight(), ansi_normal(), enabled_disabled(event->inotify_watch)); + } + + int ifindex; + if (sd_device_get_ifindex(dev, &ifindex) >= 0) { + fprintf(f, "%sNetwork interface index:%s\n %i\n", ansi_highlight(), ansi_normal(), ifindex); + + if (!isempty(event->name)) + fprintf(f, "%sNetwork interface name:%s\n %s\n", ansi_highlight(), ansi_normal(), event->name); + + if (!strv_isempty(event->altnames)) { + bool space = true; + fprintf(f, "%sAlternative interface names:%s", ansi_highlight(), ansi_normal()); + fputstrv(f, strv_sort(event->altnames), "\n ", &space); + fputs("\n", f); + } + } + if (!hashmap_isempty(event->written_sysattrs)) { const char *key, *value; @@ -50,68 +141,18 @@ void dump_event(UdevEvent *event, FILE *f) { fprintf(f, " %s : %s\n", key, value); } - fprintf(f, "%sProperties:%s\n", ansi_highlight(), ansi_normal()); - FOREACH_DEVICE_PROPERTY(dev, key, value) - fprintf(f, " %s=%s\n", key, value); - if (sd_device_get_tag_first(dev)) { fprintf(f, "%sTags:%s\n", ansi_highlight(), ansi_normal()); FOREACH_DEVICE_TAG(dev, tag) fprintf(f, " %s\n", tag); } - if (sd_device_get_devnum(dev, NULL) >= 0) { - - if (sd_device_get_devlink_first(dev)) { - int prio = 0; - (void) device_get_devlink_priority(dev, &prio); - fprintf(f, "%sDevice node symlinks:%s (priority=%i)\n", ansi_highlight(), ansi_normal(), prio); - FOREACH_DEVICE_DEVLINK(dev, devlink) - fprintf(f, " %s\n", devlink); - } - - fprintf(f, "%sInotify watch:%s\n %s\n", ansi_highlight(), ansi_normal(), enabled_disabled(event->inotify_watch)); - - uid_t uid = event->uid; - if (!uid_is_valid(uid)) - (void) device_get_devnode_uid(dev, &uid); - if (uid_is_valid(uid)) { - _cleanup_free_ char *user = uid_to_name(uid); - fprintf(f, "%sDevice node owner:%s\n %s (uid="UID_FMT")\n", ansi_highlight(), ansi_normal(), strna(user), uid); - } - - gid_t gid = event->gid; - if (!gid_is_valid(gid)) - (void) device_get_devnode_gid(dev, &gid); - if (gid_is_valid(gid)) { - _cleanup_free_ char *group = gid_to_name(gid); - fprintf(f, "%sDevice node group:%s\n %s (gid="GID_FMT")\n", ansi_highlight(), ansi_normal(), strna(group), gid); - } - - mode_t mode = event->mode; - if (mode == MODE_INVALID) - (void) device_get_devnode_mode(dev, &mode); - if (mode != MODE_INVALID) - fprintf(f, "%sDevice node permission:%s\n %04o\n", ansi_highlight(), ansi_normal(), mode); - - if (!ordered_hashmap_isempty(event->seclabel_list)) { - const char *name, *label; - fprintf(f, "%sDevice node security label:%s\n", ansi_highlight(), ansi_normal()); - ORDERED_HASHMAP_FOREACH_KEY(label, name, event->seclabel_list) - fprintf(f, " %s : %s\n", name, label); - } - } - - if (sd_device_get_ifindex(dev, NULL) >= 0) { - if (!isempty(event->name)) - fprintf(f, "%sNetwork interface name:%s\n %s\n", ansi_highlight(), ansi_normal(), event->name); - - if (!strv_isempty(event->altnames)) { - bool space = true; - fprintf(f, "%sAlternative interface names:%s", ansi_highlight(), ansi_normal()); - fputstrv(f, event->altnames, "\n ", &space); - fputs("\n", f); - } + char **properties; + if (device_get_properties_strv(dev, &properties) >= 0 && !strv_isempty(properties)) { + bool space = true; + fprintf(f, "%sProperties:%s", ansi_highlight(), ansi_normal()); + fputstrv(f, strv_sort(properties), "\n ", &space); + fputs("\n", f); } if (!ordered_hashmap_isempty(event->run_list)) { From dc27464ab762a59bd112a8f46a6b213449efe963 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 2 Feb 2025 12:02:49 +0900 Subject: [PATCH 6/9] udev-rules: always use log_info() to dump current event status --- src/udev/udev-rules.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 3c0133d55e..fc1af5d849 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -2586,25 +2586,19 @@ static int udev_rule_apply_token_to_event( case TK_A_OPTIONS_DUMP: { log_event_info(event, token, "Dumping current state:"); - if (event->event_mode == EVENT_UDEV_WORKER) { - _cleanup_(memstream_done) MemStream m = {}; - FILE *f = memstream_init(&m); - if (!f) - return log_oom(); + _cleanup_(memstream_done) MemStream m = {}; + FILE *f = memstream_init(&m); + if (!f) + return log_oom(); - dump_event(event, f); + dump_event(event, f); - _cleanup_free_ char *buf = NULL; - r = memstream_finalize(&m, &buf, NULL); - if (r < 0) - log_event_warning_errno(event, token, r, "Failed to finalize memory stream, ignoring: %m"); - else - log_info("%s", buf); - } else { - puts("============================"); - dump_event(event, NULL); - puts("============================"); - } + _cleanup_free_ char *buf = NULL; + r = memstream_finalize(&m, &buf, NULL); + if (r < 0) + log_event_warning_errno(event, token, r, "Failed to finalize memory stream, ignoring: %m"); + else + log_info("%s", buf); log_event_info(event, token, "DONE"); return true; From 0879fa4bc3e78a9a17881631fb3584a1d5266aec Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 4 Feb 2025 12:58:16 +0900 Subject: [PATCH 7/9] udevadm-test: show guiding messages in stderr Then, only final results will be shown in stdout. --- src/udev/udevadm-test.c | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 7129cdabab..df22d9717b 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -100,6 +100,20 @@ static int parse_argv(int argc, char *argv[]) { return 1; } +static void maybe_insert_empty_line(void) { + if (log_get_max_level() < LOG_INFO) + return; + + LogTarget target = log_get_target(); + if (!IN_SET(log_get_target(), LOG_TARGET_CONSOLE, LOG_TARGET_CONSOLE_PREFIXED, LOG_TARGET_AUTO)) + return; + + if (target == LOG_TARGET_AUTO && stderr_is_journal()) + return; + + fputs("\n", stderr); +} + int test_main(int argc, char *argv[], void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; _cleanup_(udev_event_unrefp) UdevEvent *event = NULL; @@ -114,22 +128,24 @@ int test_main(int argc, char *argv[], void *userdata) { if (r <= 0) return r; - puts("This program is for debugging only, it does not run any program\n" - "specified by a RUN key. It may show incorrect results, because\n" - "some values may be different, or not available at a simulation run."); + log_info("This program is for debugging only, it does not run any program\n" + "specified by a RUN key. It may show incorrect results, because\n" + "some values may be different, or not available at a simulation run."); assert_se(sigprocmask(SIG_SETMASK, NULL, &sigmask_orig) >= 0); - puts("\nLoading builtins..."); + maybe_insert_empty_line(); + log_info("Loading builtins..."); udev_builtin_init(); UDEV_BUILTIN_DESTRUCTOR; - puts("Loading builtins done."); + log_info("Loading builtins done."); - puts("\nLoading udev rules files..."); + maybe_insert_empty_line(); + log_info("Loading udev rules files..."); r = udev_rules_load(&rules, arg_resolve_name_timing, arg_extra_rules_dir); if (r < 0) return log_error_errno(r, "Failed to read udev rules: %m"); - puts("Loading udev rules files done."); + log_info("Loading udev rules files done."); r = find_device_with_action(arg_syspath, arg_action, &dev); if (r < 0) @@ -146,12 +162,14 @@ int test_main(int argc, char *argv[], void *userdata) { assert_se(sigfillset(&mask) >= 0); assert_se(sigprocmask(SIG_SETMASK, &mask, &sigmask_orig) >= 0); - printf("\nProcessing udev rules%s...\n", arg_verbose ? "" : " (verbose logs can be shown by -v/--verbose)"); + maybe_insert_empty_line(); + log_info("Processing udev rules%s...", arg_verbose ? "" : " (verbose logs can be shown by -v/--verbose)"); udev_event_execute_rules(event, rules); - puts("Processing udev rules done."); + log_info("Processing udev rules done."); - puts(""); + maybe_insert_empty_line(); dump_event(event, NULL); + maybe_insert_empty_line(); return 0; } From ac722389a777adbed7722175365bbd933bbe6831 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sun, 2 Feb 2025 12:07:48 +0900 Subject: [PATCH 8/9] udevadm-test: allow to dump result in json format This adds --json=MODE option for 'udevadm test' command. When specified, all messages, except for the final result, will be written to stderr, and the final result is shown in JSON format to stdout. It may be useful for parsing the test result. --- man/udevadm.xml | 2 + shell-completion/bash/udevadm | 6 +- shell-completion/zsh/_udevadm | 1 + src/udev/udev-dump.c | 272 +++++++++++++++++++++++++++++++++- src/udev/udev-dump.h | 4 +- src/udev/udev-rules.c | 2 +- src/udev/udevadm-test.c | 30 +++- test/units/TEST-17-UDEV.10.sh | 4 + 8 files changed, 309 insertions(+), 12 deletions(-) diff --git a/man/udevadm.xml b/man/udevadm.xml index 80184608f9..c9e58e9cf5 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -930,6 +930,8 @@ + + diff --git a/shell-completion/bash/udevadm b/shell-completion/bash/udevadm index e9d11d32a3..34b24eccb8 100644 --- a/shell-completion/bash/udevadm +++ b/shell-completion/bash/udevadm @@ -99,7 +99,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 -D --extra-rules-dir' + [TEST_ARG]='-a --action -N --resolve-names -D --extra-rules-dir --json' [TEST_BUILTIN]='-a --action' [VERIFY_STANDALONE]='--no-summary --no-style' [VERIFY_ARG]='-N --resolve-names --root' @@ -263,6 +263,10 @@ _udevadm() { -D|--extra-rules-dir) comps='' compopt -o dirnames + ;; + --json) + comps=$( udevadm test --json help ) + ;; 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 ac112e751f..71f75a549c 100644 --- a/shell-completion/zsh/_udevadm +++ b/shell-completion/zsh/_udevadm @@ -92,6 +92,7 @@ _udevadm_test(){ '--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.]' \ + '--json=[Generate JSON output]:MODE:(pretty short off)' \ '*::devpath:_files -P /sys/ -W /sys' } diff --git a/src/udev/udev-dump.c b/src/udev/udev-dump.c index b13779e5f7..d832989805 100644 --- a/src/udev/udev-dump.c +++ b/src/udev/udev-dump.c @@ -6,6 +6,8 @@ #include "devnum-util.h" #include "format-util.h" #include "fs-util.h" +#include "json-util.h" +#include "parse-util.h" #include "udev-builtin.h" #include "udev-dump.h" #include "udev-event.h" @@ -29,7 +31,273 @@ void event_cache_written_sysctl(UdevEvent *event, const char *attr, const char * event_cache_written_value(&event->written_sysctls, attr, value); } -void dump_event(UdevEvent *event, FILE *f) { +static int dump_event_json(UdevEvent *event, sd_json_format_flags_t flags, FILE *f) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + const char *str; + int r; + + if (sd_device_get_devpath(dev, &str) >= 0) { + r = sd_json_variant_set_field_string(&v, "path", str); + if (r < 0) + return r; + } + + if (sd_device_get_sysname(dev, &str) >= 0) { + r = sd_json_variant_set_field_string(&v, "name", str); + if (r < 0) + return r; + } + + unsigned sysnum; + if (device_get_sysnum_unsigned(dev, &sysnum) >= 0) { + r = sd_json_variant_set_field_unsigned(&v, "number", sysnum); + if (r < 0) + return r; + } + + if (sd_device_get_device_id(dev, &str) >= 0) { + r = sd_json_variant_set_field_string(&v, "id", str); + if (r < 0) + return r; + } + + const char *subsys = NULL; + if (sd_device_get_subsystem(dev, &subsys) >= 0) { + r = sd_json_variant_set_field_string(&v, "subsystem", subsys); + if (r < 0) + return r; + } + + if (sd_device_get_driver_subsystem(dev, &str) >= 0) { + r = sd_json_variant_set_field_string(&v, "driverSubsystem", str); + if (r < 0) + return r; + } + + if (sd_device_get_devtype(dev, &str) >= 0) { + r = sd_json_variant_set_field_string(&v, "type", str); + if (r < 0) + return r; + } + + if (sd_device_get_driver(dev, &str) >= 0) { + r = sd_json_variant_set_field_string(&v, "driver", str); + if (r < 0) + return r; + } + + if (sd_device_get_devname(dev, &str) >= 0) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *node = NULL; + + r = sd_json_variant_set_field_string(&node, "path", str); + if (r < 0) + return r; + + r = sd_json_variant_set_field_string(&node, "type", streq_ptr(subsys, "block") ? "block" : "char"); + if (r < 0) + return r; + + dev_t devnum; + if (sd_device_get_devnum(dev, &devnum) >= 0) { + r = sd_json_variant_set_fieldb(&node, "rdev", JSON_BUILD_DEVNUM(devnum)); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *owner = NULL; + + uid_t uid = event->uid; + if (!uid_is_valid(uid)) + (void) device_get_devnode_uid(dev, &uid); + if (uid_is_valid(uid)) { + _cleanup_free_ char *user = uid_to_name(uid); + if (!user) + return -ENOMEM; + + r = sd_json_variant_set_field_unsigned(&owner, "uid", uid); + if (r < 0) + return r; + + r = sd_json_variant_set_field_string(&owner, "userName", user); + if (r < 0) + return r; + } + + gid_t gid = event->gid; + if (!gid_is_valid(gid)) + (void) device_get_devnode_gid(dev, &gid); + if (gid_is_valid(gid)) { + _cleanup_free_ char *group = gid_to_name(gid); + if (!group) + return -ENOMEM; + + r = sd_json_variant_set_field_unsigned(&owner, "gid", gid); + if (r < 0) + return r; + + r = sd_json_variant_set_field_string(&owner, "groupName", group); + if (r < 0) + return r; + } + + r = json_variant_set_field_non_null(&node, "owner", owner); + if (r < 0) + return r; + + mode_t mode = event->mode; + if (mode == MODE_INVALID) + (void) device_get_devnode_mode(dev, &mode); + if (mode != MODE_INVALID) { + char mode_str[STRLEN("0755")+1]; + xsprintf(mode_str, "%04o", mode & ~S_IFMT); + + r = sd_json_variant_set_field_string(&node, "mode", mode_str); + if (r < 0) + return r; + } + + _cleanup_strv_free_ char **links = NULL; + FOREACH_DEVICE_DEVLINK(dev, devlink) { + r = strv_extend(&links, devlink); + if (r < 0) + return r; + } + + if (!strv_isempty(links)) { + int prio = 0; + (void) device_get_devlink_priority(dev, &prio); + + r = sd_json_variant_set_field_integer(&node, "symlinkPriority", prio); + if (r < 0) + return r; + + r = sd_json_variant_set_field_strv(&node, "symlinks", strv_sort(links)); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *labels = NULL; + const char *name, *label; + ORDERED_HASHMAP_FOREACH_KEY(label, name, event->seclabel_list) { + r = sd_json_variant_append_arraybo( + &labels, + SD_JSON_BUILD_PAIR_STRING("name", name), + SD_JSON_BUILD_PAIR_STRING("label", label)); + if (r < 0) + return r; + } + + r = json_variant_set_field_non_null(&node, "securityLabels", labels); + if (r < 0) + return r; + + r = sd_json_variant_set_field_boolean(&node, "inotifyWatch", event->inotify_watch); + if (r < 0) + return r; + + r = json_variant_set_field_non_null(&v, "node", node); + if (r < 0) + return r; + } + + int ifindex; + if (sd_device_get_ifindex(dev, &ifindex) >= 0) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *netif = NULL; + + r = sd_json_variant_set_field_integer(&netif, "index", ifindex); + if (r < 0) + return r; + + if (!isempty(event->name)) { + r = sd_json_variant_set_field_string(&netif, "name", event->name); + if (r < 0) + return r; + } + + if (!strv_isempty(event->altnames)) { + r = sd_json_variant_set_field_strv(&netif, "alternativeNames", strv_sort(event->altnames)); + if (r < 0) + return r; + } + + r = json_variant_set_field_non_null(&v, "networkInterface", netif); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *sysattrs = NULL; + const char *key, *value; + HASHMAP_FOREACH_KEY(value, key, event->written_sysattrs) { + r = sd_json_variant_append_arraybo( + &sysattrs, + SD_JSON_BUILD_PAIR_STRING("path", key), + SD_JSON_BUILD_PAIR_STRING("value", value)); + if (r < 0) + return r; + } + + r = json_variant_set_field_non_null(&v, "sysfsAttributes", sysattrs); + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *sysctls = NULL; + HASHMAP_FOREACH_KEY(value, key, event->written_sysctls) { + r = sd_json_variant_append_arraybo( + &sysctls, + SD_JSON_BUILD_PAIR_STRING("path", key), + SD_JSON_BUILD_PAIR_STRING("value", value)); + if (r < 0) + return r; + } + + r = json_variant_set_field_non_null(&v, "sysctl", sysctls); + if (r < 0) + return r; + + _cleanup_strv_free_ char **tags = NULL; + FOREACH_DEVICE_TAG(dev, tag) { + r = strv_extend(&tags, tag); + if (r < 0) + return r; + } + + if (!strv_isempty(tags)) { + r = sd_json_variant_set_field_strv(&v, "tags", strv_sort(tags)); + if (r < 0) + return r; + } + + char **properties; + if (device_get_properties_strv(dev, &properties) >= 0 && !strv_isempty(properties)) { + r = sd_json_variant_set_field_strv(&v, "properties", strv_sort(properties)); + if (r < 0) + return r; + } + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *commands = NULL; + void *val; + const char *command; + ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { + r = sd_json_variant_append_arraybo( + &commands, + SD_JSON_BUILD_PAIR_STRING("type", PTR_TO_UDEV_BUILTIN_CMD(val) >= 0 ? "builtin" : "program"), + SD_JSON_BUILD_PAIR_STRING("command", command)); + if (r < 0) + return r; + } + + r = json_variant_set_field_non_null(&v, "queuedCommands", commands); + if (r < 0) + return r; + + return sd_json_variant_dump(v, flags, f, /* prefix = */ NULL); +} + +int dump_event(UdevEvent *event, sd_json_format_flags_t flags, FILE *f) { + if (sd_json_format_enabled(flags)) + return dump_event_json(event, flags, f); + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); const char *subsys = NULL, *str; @@ -168,4 +436,6 @@ void dump_event(UdevEvent *event, FILE *f) { fprintf(f, " RUN{program} : %s\n", command); } } + + return 0; } diff --git a/src/udev/udev-dump.h b/src/udev/udev-dump.h index 514f8267a7..f64499b653 100644 --- a/src/udev/udev-dump.h +++ b/src/udev/udev-dump.h @@ -3,8 +3,10 @@ #include +#include "sd-json.h" + typedef struct UdevEvent UdevEvent; void event_cache_written_sysattr(UdevEvent *event, const char *attr, const char *value); void event_cache_written_sysctl(UdevEvent *event, const char *attr, const char *value); -void dump_event(UdevEvent *event, FILE *f); +int dump_event(UdevEvent *event, sd_json_format_flags_t flags, FILE *f); diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index fc1af5d849..32a4461deb 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -2591,7 +2591,7 @@ static int udev_rule_apply_token_to_event( if (!f) return log_oom(); - dump_event(event, f); + (void) dump_event(event, SD_JSON_FORMAT_OFF, f); _cleanup_free_ char *buf = NULL; r = memstream_finalize(&m, &buf, NULL); diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index df22d9717b..40031e2119 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -25,6 +25,7 @@ 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 sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_extra_rules_dir, strv_freep); @@ -37,20 +38,26 @@ static int help(void) { " -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", + " -v --verbose Show verbose logs\n" + " --json=pretty|short|off Generate JSON output\n", program_invocation_short_name); return 0; } static int parse_argv(int argc, char *argv[]) { + enum { + ARG_JSON = 0x100, + }; + static const struct option options[] = { - { "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' }, + { "action", required_argument, NULL, 'a' }, + { "resolve-names", required_argument, NULL, 'N' }, + { "extra-rules-dir", required_argument, NULL, 'D' }, + { "verbose", no_argument, NULL, 'v' }, + { "json", required_argument, NULL, ARG_JSON }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, {} }; @@ -83,6 +90,11 @@ static int parse_argv(int argc, char *argv[]) { case 'v': arg_verbose = true; break; + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + break; case 'V': return print_version(); case 'h': @@ -168,7 +180,9 @@ int test_main(int argc, char *argv[], void *userdata) { log_info("Processing udev rules done."); maybe_insert_empty_line(); - dump_event(event, NULL); + r = dump_event(event, arg_json_format_flags, NULL); + if (r < 0) + return log_error_errno(r, "Failed to dump result: %m"); maybe_insert_empty_line(); return 0; diff --git a/test/units/TEST-17-UDEV.10.sh b/test/units/TEST-17-UDEV.10.sh index b8f5648a47..796d8dc5d2 100755 --- a/test/units/TEST-17-UDEV.10.sh +++ b/test/units/TEST-17-UDEV.10.sh @@ -153,6 +153,10 @@ udevadm test -N late /sys/class/net/$netdev udevadm test --resolve-names never /sys/class/net/$netdev (! udevadm test -N hello /sys/class/net/$netdev) udevadm test -v /sys/class/net/$netdev +udevadm test --json=off /sys/class/net/$netdev +udevadm test --json=pretty /sys/class/net/$netdev | jq . >/dev/null +udevadm test --json=short /sys/class/net/$netdev | jq . >/dev/null +udevadm test --json=help udevadm test -h # udevadm test-builtin path_id "$loopdev" From c26db0564086afb5cfcfdd00c1278b42e4d20649 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 4 Feb 2025 13:26:25 +0900 Subject: [PATCH 9/9] man/udevadm: add examples to get predictable interface name and persistent device node symlinks Closes #23661. --- man/udevadm.xml | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/man/udevadm.xml b/man/udevadm.xml index c9e58e9cf5..4e1255bf4d 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -879,7 +879,10 @@ devpath|file|unit - Simulate a udev event run for the given device, and print debug output. + + Simulate a udev event run for the given device, and print debug output. The option + may be useful for parsing the final result. See also Example section. + @@ -1293,6 +1296,36 @@ # udevadm lock -d /dev/sda1 dd if=fs.raw of=/dev/sda1 + + + Predict Network Interface Renaming + + To predict a network interface name, udevadm test can be used: + + $ udevadm test /sys/class/net/wlan0 --json=pretty 2>/dev/null | jq .networkInterface.name +"wlp59s0" + + + + Predict Symbolic links of a Device Node + + To predict symbolic links to a device node, udevadm test can be used: + + $ udevadm test /dev/nvme0n1p1 --json=pretty 2>/dev/null | jq .node.symlinks +[ + "/dev/disk/by-diskseq/1-part1", + "/dev/disk/by-id/nvme-WDC_PC_SN720_SDAQNTW-512G-1001_192290802247-part1", + "/dev/disk/by-id/nvme-WDC_PC_SN720_SDAQNTW-512G-1001_192290802247_1-part1", + "/dev/disk/by-id/nvme-eui.1922908022470001001b448b44ccb9d6-part1", + "/dev/disk/by-path/pci-0000:3c:00.0-nvme-1-part/by-partnum/1", + "/dev/disk/by-path/pci-0000:3c:00.0-nvme-1-part1" +] +$ udevadm test /dev/input/event3 --json=pretty 2>/dev/null | jq .node.symlinks +[ + "/dev/input/by-path/platform-i8042-serio-0-event-kbd" +] + +