diff --git a/man/udevadm.xml b/man/udevadm.xml index 80184608f9..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. + @@ -930,6 +933,8 @@ + + @@ -1291,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" +] + + 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/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); 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..5237ae3090 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; } @@ -241,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) @@ -723,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"); } @@ -817,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"); } diff --git a/src/udev/udev-dump.c b/src/udev/udev-dump.c index 1dcc141042..d832989805 100644 --- a/src/udev/udev-dump.c +++ b/src/udev/udev-dump.c @@ -3,8 +3,11 @@ #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 "json-util.h" +#include "parse-util.h" #include "udev-builtin.h" #include "udev-dump.h" #include "udev-event.h" @@ -28,12 +31,368 @@ 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; 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 +409,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(uid)) - (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)) { @@ -127,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 3c0133d55e..32a4461deb 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); + (void) dump_event(event, SD_JSON_FORMAT_OFF, 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; diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index 7129cdabab..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': @@ -100,6 +112,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 +140,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 +174,16 @@ 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(""); - dump_event(event, NULL); + maybe_insert_empty_line(); + 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"