diff --git a/man/udevadm.xml b/man/udevadm.xml index 66d58a4d0c..76c54c3784 100644 --- a/man/udevadm.xml +++ b/man/udevadm.xml @@ -229,7 +229,82 @@ + + + + + When used with , only show devices of or not of the given + subsystem respectively. + + + + + + + + When used with , only show devices matching or not matching the + given attribute respectively. + + + + + + + + When used with , only show devices matching the given property + and value. + + + + + + + + When used with , only show devices with the given tag. + + + + + + + + When used with , only show devices with the given + /sys path. + + + + + + + + When used with , only show devices with the given name in + /dev. + + + + + + + + When used with , only show devices with the given parent + device. + + + + + + + + + When used with , only show devices that are initialized or not + initialized respectively. + + + + + + diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index ae7251cbcc..6a483fa632 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -15,12 +15,15 @@ #include "device-enumerator-private.h" #include "device-private.h" #include "device-util.h" +#include "devnum-util.h" #include "dirent-util.h" #include "errno-util.h" #include "fd-util.h" #include "fileio.h" #include "glyph-util.h" +#include "json.h" #include "pager.h" +#include "parse-argument.h" #include "sort-util.h" #include "static-destruct.h" #include "string-table.h" @@ -35,6 +38,7 @@ typedef enum ActionType { ACTION_ATTRIBUTE_WALK, ACTION_DEVICE_ID_FILE, ACTION_TREE, + ACTION_EXPORT, } ActionType; typedef enum QueryType { @@ -52,6 +56,7 @@ static bool arg_value = false; static const char *arg_export_prefix = NULL; static usec_t arg_wait_for_initialization_timeout = 0; PagerFlags arg_pager_flags = 0; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; /* Put a limit on --tree descent level to not exhaust our stack */ #define TREE_DEPTH_MAX 64 @@ -260,6 +265,39 @@ static int print_record(sd_device *device, const char *prefix) { return 0; } +static int record_to_json(sd_device *device, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + const char *str; + int r; + + assert(device); + assert(ret); + + /* We don't show any shorthand fields here as done in print_record() except for SYSNAME and SYSNUM as + * all the other ones have a matching property which will already be included. */ + + if (sd_device_get_sysname(device, &str) >= 0) { + r = json_variant_set_field_string(&v, "SYSNAME", str); + if (r < 0) + return r; + } + + if (sd_device_get_sysnum(device, &str) >= 0) { + r = json_variant_set_field_string(&v, "SYSNUM", str); + if (r < 0) + return r; + } + + FOREACH_DEVICE_PROPERTY(device, key, val) { + r = json_variant_set_field_string(&v, key, val); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + static int stat_device(const char *name, bool export, const char *prefix) { struct stat statbuf; @@ -280,18 +318,11 @@ static int stat_device(const char *name, bool export, const char *prefix) { return 0; } -static int export_devices(void) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; +static int export_devices(sd_device_enumerator *e) { sd_device *d; int r; - r = sd_device_enumerator_new(&e); - if (r < 0) - return log_oom(); - - r = sd_device_enumerator_allow_uninitialized(e); - if (r < 0) - return log_error_errno(r, "Failed to set allowing uninitialized flag: %m"); + assert(e); r = device_enumerator_scan_devices(e); if (r < 0) @@ -300,7 +331,17 @@ static int export_devices(void) { pager_open(arg_pager_flags); FOREACH_DEVICE_AND_SUBSYSTEM(e, d) - (void) print_record(d, NULL); + if (arg_json_format_flags & JSON_FORMAT_OFF) + (void) print_record(d, NULL); + else { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = record_to_json(d, &v); + if (r < 0) + return r; + + (void) json_variant_dump(v, arg_json_format_flags, stdout, NULL); + } return 0; } @@ -466,7 +507,19 @@ static int query_device(QueryType query, sd_device* device) { return 0; case QUERY_ALL: - return print_record(device, NULL); + if (arg_json_format_flags & JSON_FORMAT_OFF) + return print_record(device, NULL); + else { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = record_to_json(device, &v); + if (r < 0) + return r; + + (void) json_variant_dump(v, arg_json_format_flags, stdout, NULL); + } + + return 0; default: assert_not_reached(); @@ -499,7 +552,24 @@ static int help(void) { " -c --cleanup-db Clean up the udev database\n" " -w --wait-for-initialization[=SECONDS]\n" " Wait for device to be initialized\n" - " --no-pager Do not pipe output into a pager\n", + " --no-pager Do not pipe output into a pager\n" + " --json=pretty|short|off Generate JSON output\n" + " --subsystem-match=SUBSYSTEM\n" + " Query devices matching a subsystem\n" + " --subsystem-nomatch=SUBSYSTEM\n" + " Query devices not matching a subsystem\n" + " --attr-match=FILE[=VALUE]\n" + " Query devices that match an attribute\n" + " --attr-nomatch=FILE[=VALUE]\n" + " Query devices that do not match an attribute\n" + " --property-match=KEY=VALUE\n" + " Query devices with matching properties\n" + " --tag-match=TAG Query devices with a matching tag\n" + " --sysname-match=NAME Query devices with this /sys path\n" + " --name-match=NAME Query devices with this /dev name\n" + " --parent-match=NAME Query devices with this parent device\n" + " --initialized-match Query devices that are already initialized\n" + " --initialized-nomatch Query devices that are not initialized yet\n", program_invocation_short_name); return 0; @@ -662,7 +732,49 @@ static int print_tree(sd_device* below) { return 0; } +static int ensure_device_enumerator(sd_device_enumerator **e) { + int r; + + assert(e); + + if (*e) + return 0; + + r = sd_device_enumerator_new(e); + if (r < 0) + return log_error_errno(r, "Failed to create device enumerator: %m"); + + r = sd_device_enumerator_allow_uninitialized(*e); + if (r < 0) + return log_error_errno(r, "Failed to allow uninitialized devices: %m"); + + return 0; +} + +static int parse_key_value_argument(const char *s, char **key, char **value) { + _cleanup_free_ char *k = NULL, *v = NULL; + int r; + + assert(s); + assert(key); + assert(value); + + r = extract_many_words(&s, "=", EXTRACT_DONT_COALESCE_SEPARATORS, &k, &v, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse key/value pair %s: %m", s); + if (r < 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing '=' in key/value pair %s.", s); + + if (!filename_is_valid(k)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid key name", k); + + free_and_replace(*key, k); + free_and_replace(*value, v); + return 0; +} + int info_main(int argc, char *argv[], void *userdata) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; _cleanup_strv_free_ char **devices = NULL; _cleanup_free_ char *name = NULL; int c, r, ret; @@ -671,26 +783,50 @@ int info_main(int argc, char *argv[], void *userdata) { ARG_PROPERTY = 0x100, ARG_VALUE, ARG_NO_PAGER, + ARG_JSON, + ARG_SUBSYSTEM_MATCH, + ARG_SUBSYSTEM_NOMATCH, + ARG_ATTR_MATCH, + ARG_ATTR_NOMATCH, + ARG_PROPERTY_MATCH, + ARG_TAG_MATCH, + ARG_SYSNAME_MATCH, + ARG_NAME_MATCH, + ARG_PARENT_MATCH, + ARG_INITIALIZED_MATCH, + ARG_INITIALIZED_NOMATCH, }; static const struct option options[] = { - { "attribute-walk", no_argument, NULL, 'a' }, - { "tree", no_argument, NULL, 't' }, - { "cleanup-db", no_argument, NULL, 'c' }, - { "device-id-of-file", required_argument, NULL, 'd' }, - { "export", no_argument, NULL, 'x' }, - { "export-db", no_argument, NULL, 'e' }, - { "export-prefix", required_argument, NULL, 'P' }, - { "help", no_argument, NULL, 'h' }, - { "name", required_argument, NULL, 'n' }, - { "path", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "query", required_argument, NULL, 'q' }, - { "root", no_argument, NULL, 'r' }, - { "value", no_argument, NULL, ARG_VALUE }, - { "version", no_argument, NULL, 'V' }, - { "wait-for-initialization", optional_argument, NULL, 'w' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "attribute-walk", no_argument, NULL, 'a' }, + { "tree", no_argument, NULL, 't' }, + { "cleanup-db", no_argument, NULL, 'c' }, + { "device-id-of-file", required_argument, NULL, 'd' }, + { "export", no_argument, NULL, 'x' }, + { "export-db", no_argument, NULL, 'e' }, + { "export-prefix", required_argument, NULL, 'P' }, + { "help", no_argument, NULL, 'h' }, + { "name", required_argument, NULL, 'n' }, + { "path", required_argument, NULL, 'p' }, + { "property", required_argument, NULL, ARG_PROPERTY }, + { "query", required_argument, NULL, 'q' }, + { "root", no_argument, NULL, 'r' }, + { "value", no_argument, NULL, ARG_VALUE }, + { "version", no_argument, NULL, 'V' }, + { "wait-for-initialization", optional_argument, NULL, 'w' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "json", required_argument, NULL, ARG_JSON }, + { "subsystem-match", required_argument, NULL, ARG_SUBSYSTEM_MATCH }, + { "subsystem-nomatch", required_argument, NULL, ARG_SUBSYSTEM_NOMATCH }, + { "attr-match", required_argument, NULL, ARG_ATTR_MATCH }, + { "attr-nomatch", required_argument, NULL, ARG_ATTR_NOMATCH }, + { "property-match", required_argument, NULL, ARG_PROPERTY_MATCH }, + { "tag-match", required_argument, NULL, ARG_TAG_MATCH }, + { "sysname-match", required_argument, NULL, ARG_SYSNAME_MATCH }, + { "name-match", required_argument, NULL, ARG_NAME_MATCH }, + { "parent-match", required_argument, NULL, ARG_PARENT_MATCH }, + { "initialized-match", no_argument, NULL, ARG_INITIALIZED_MATCH }, + { "initialized-nomatch", no_argument, NULL, ARG_INITIALIZED_NOMATCH }, {} }; @@ -761,7 +897,8 @@ int info_main(int argc, char *argv[], void *userdata) { action = ACTION_TREE; break; case 'e': - return export_devices(); + action = ACTION_EXPORT; + break; case 'c': cleanup_db(); return 0; @@ -787,6 +924,111 @@ int info_main(int argc, char *argv[], void *userdata) { case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; + + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + case ARG_SUBSYSTEM_MATCH: + case ARG_SUBSYSTEM_NOMATCH: + r = ensure_device_enumerator(&e); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_subsystem(e, optarg, c == ARG_SUBSYSTEM_MATCH); + if (r < 0) + return log_error_errno(r, "Failed to add%s subsystem match '%s': %m", + c == ARG_SUBSYSTEM_MATCH ? "" : " negative", optarg); + + break; + + case ARG_ATTR_MATCH: + case ARG_ATTR_NOMATCH: { + _cleanup_free_ char *k = NULL, *v = NULL; + + r = ensure_device_enumerator(&e); + if (r < 0) + return r; + + r = parse_key_value_argument(optarg, &k, &v); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_sysattr(e, k, v, c == ARG_ATTR_MATCH); + if (r < 0) + return log_error_errno(r, "Failed to add%s sysattr match '%s=%s': %m", + c == ARG_ATTR_MATCH ? "" : " negative", k, v); + break; + } + + case ARG_PROPERTY_MATCH: { + _cleanup_free_ char *k = NULL, *v = NULL; + + r = ensure_device_enumerator(&e); + if (r < 0) + return r; + + r = parse_key_value_argument(optarg, &k, &v); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_property_required(e, k, v); + if (r < 0) + return log_error_errno(r, "Failed to add property match '%s=%s': %m", k, v); + break; + } + + case ARG_TAG_MATCH: + r = ensure_device_enumerator(&e); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_tag(e, optarg); + if (r < 0) + return log_error_errno(r, "Failed to add tag match '%s': %m", optarg); + break; + + case ARG_SYSNAME_MATCH: + r = ensure_device_enumerator(&e); + if (r < 0) + return r; + + r = sd_device_enumerator_add_match_sysname(e, optarg); + if (r < 0) + return log_error_errno(r, "Failed to add sysname match '%s': %m", optarg); + break; + + case ARG_NAME_MATCH: + case ARG_PARENT_MATCH: { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + + r = find_device(optarg, c == ARG_NAME_MATCH ? "/dev" : "/sys", &dev); + if (r < 0) + return log_error_errno(r, "Failed to open the device '%s': %m", optarg); + + r = ensure_device_enumerator(&e); + if (r < 0) + return r; + + r = device_enumerator_add_match_parent_incremental(e, dev); + if (r < 0) + return log_error_errno(r, "Failed to add parent match '%s': %m", optarg); + break; + } + + case ARG_INITIALIZED_MATCH: + case ARG_INITIALIZED_NOMATCH: + r = ensure_device_enumerator(&e); + if (r < 0) + return r; + + r = device_enumerator_add_match_is_initialized(e, c == ARG_INITIALIZED_MATCH ? MATCH_INITIALIZED_YES : MATCH_INITIALIZED_NO); + if (r < 0) + return log_error_errno(r, "Failed to set initialized filter: %m"); + break; + case '?': return -EINVAL; default: @@ -801,6 +1043,14 @@ int info_main(int argc, char *argv[], void *userdata) { return stat_device(name, arg_export, arg_export_prefix); } + if (action == ACTION_EXPORT) { + r = ensure_device_enumerator(&e); + if (r < 0) + return r; + + return export_devices(e); + } + r = strv_extend_strv(&devices, argv + optind, false); if (r < 0) return log_error_errno(r, "Failed to build argument list: %m"); diff --git a/test/units/testsuite-17.10.sh b/test/units/testsuite-17.10.sh index 2df4dcfb29..b4724653f3 100755 --- a/test/units/testsuite-17.10.sh +++ b/test/units/testsuite-17.10.sh @@ -57,6 +57,9 @@ udevadm info --property DEVNAME --value /sys/class/net/$netdev udevadm info --property HELLO /sys/class/net/$netdev udevadm info -p class/net/$netdev udevadm info -p /class/net/$netdev +udevadm info --json=off -p class/net/$netdev +udevadm info --json=pretty -p class/net/$netdev | jq . +udevadm info --json=short -p class/net/$netdev | jq . udevadm info -n null udevadm info -q all /sys/class/net/$netdev udevadm info -q name /dev/null @@ -74,6 +77,20 @@ udevadm info -x -q path /sys/class/net/$netdev udevadm info -P TEST_ /sys/class/net/$netdev udevadm info -d /dev/null udevadm info -e >/dev/null +udevadm info -e --json=off >/dev/null +udevadm info -e --json=pretty | jq . >/dev/null +udevadm info -e --json=short | jq . >/dev/null +udevadm info -e --subsystem-match acpi >/dev/null +udevadm info -e --subsystem-nomatch acpi >/dev/null +udevadm info -e --attr-match ifindex=2 >/dev/null +udevadm info -e --attr-nomatch ifindex=2 >/dev/null +udevadm info -e --property-match SUBSYSTEM=acpi >/dev/null +udevadm info -e --tag-match systemd >/dev/null +udevadm info -e --sysname-match lo >/dev/null +udevadm info -e --name-match /sys/class/net/$netdev >/dev/null +udevadm info -e --parent-match /sys/class/net/$netdev >/dev/null +udevadm info -e --initialized-match >/dev/null +udevadm info -e --initialized-nomatch >/dev/null # udevadm info -c udevadm info -w /sys/class/net/$netdev udevadm info --wait-for-initialization=5 /sys/class/net/$netdev