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"