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"