1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-11 20:58:27 +03:00

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.
This commit is contained in:
Yu Watanabe 2025-02-02 12:07:48 +09:00
parent 0879fa4bc3
commit ac722389a7
8 changed files with 309 additions and 12 deletions

View File

@ -930,6 +930,8 @@
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="json" />
</variablelist>
</refsect2>

View 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]}"

View 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'
}

View 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;
}

View 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);

View 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);

View 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;

View File

@ -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"