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"