]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
udevadm-test: allow to dump result in json format
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 2 Feb 2025 03:07:48 +0000 (12:07 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 7 Feb 2025 16:39:35 +0000 (01:39 +0900)
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.

man/udevadm.xml
shell-completion/bash/udevadm
shell-completion/zsh/_udevadm
src/udev/udev-dump.c
src/udev/udev-dump.h
src/udev/udev-rules.c
src/udev/udevadm-test.c
test/units/TEST-17-UDEV.10.sh

index 80184608f95fb31fff595b46399b68b2d3ae65e4..c9e58e9cf57a787b722aa69110b3dcd4bbffd03d 100644 (file)
             <xi:include href="version-info.xml" xpointer="v258"/>
           </listitem>
         </varlistentry>
+
+        <xi:include href="standard-options.xml" xpointer="json" />
       </variablelist>
     </refsect2>
 
index e9d11d32a3d462b0c62bc9b1007679e2a86e09e9..34b24eccb876350281b286ed0b537865468164fa 100644 (file)
@@ -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]}"
index ac112e751fe4e5c0f363064f71bc7093e00ef886..71f75a549c08a8eb6141bd3a5a043107c9c77100 100644 (file)
@@ -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'
 }
 
index b13779e5f79a737820b5d95f01e20f04fc16f31f..d8329898055ee04d17b8d16d0f8c512cc90e6fbe 100644 (file)
@@ -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;
 }
index 514f8267a7a2d78ae9b57b39d41a303f596a21f9..f64499b6531574a1aaf6bf84bcee9b6dd8665d68 100644 (file)
@@ -3,8 +3,10 @@
 
 #include <stdio.h>
 
+#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);
index fc1af5d8492327fe471437f6a6015707d4e07b65..32a4461deb401b5cea2a835f58e9419f9deb6de8 100644 (file)
@@ -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);
index df22d9717b131097bcd893bc1b827a0700598b83..40031e2119d177c6b6d996f9e83ff0676c9fc279 100644 (file)
@@ -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;
index b8f5648a47348f39c38ece1dda8fa5cf1f054ed7..796d8dc5d272a4913ed46be1aa0dae929ede4d45 100755 (executable)
@@ -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"