From: Yu Watanabe Date: Sun, 2 Feb 2025 03:07:48 +0000 (+0900) Subject: udevadm-test: allow to dump result in json format X-Git-Tag: v258-rc1~1386^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ac722389a777adbed7722175365bbd933bbe6831;p=thirdparty%2Fsystemd.git 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. --- diff --git a/man/udevadm.xml b/man/udevadm.xml index 80184608f95..c9e58e9cf57 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 e9d11d32a3d..34b24eccb87 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 ac112e751fe..71f75a549c0 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 b13779e5f79..d8329898055 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 514f8267a7a..f64499b6531 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 fc1af5d8492..32a4461deb4 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 df22d9717b1..40031e2119d 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 b8f5648a473..796d8dc5d27 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"