diff options
Diffstat (limited to 'src/busctl')
-rw-r--r-- | src/busctl/busctl-introspect.c | 728 | ||||
-rw-r--r-- | src/busctl/busctl-introspect.h | 15 | ||||
-rw-r--r-- | src/busctl/busctl.c | 2593 | ||||
-rw-r--r-- | src/busctl/meson.build | 12 | ||||
-rw-r--r-- | src/busctl/test-busctl-introspect.c | 364 |
5 files changed, 3712 insertions, 0 deletions
diff --git a/src/busctl/busctl-introspect.c b/src/busctl/busctl-introspect.c new file mode 100644 index 0000000..6002af5 --- /dev/null +++ b/src/busctl/busctl-introspect.c @@ -0,0 +1,728 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "busctl-introspect.h" +#include "path-util.h" +#include "string-util.h" +#include "util.h" +#include "xml.h" + +#define NODE_DEPTH_MAX 16 + +typedef struct Context { + const XMLIntrospectOps *ops; + void *userdata; + + char *interface_name; + uint64_t interface_flags; + + char *member_name; + char *member_signature; + char *member_result; + uint64_t member_flags; + bool member_writable; + + const char *current; + void *xml_state; +} Context; + +static void context_reset_member(Context *c) { + free(c->member_name); + free(c->member_signature); + free(c->member_result); + + c->member_name = c->member_signature = c->member_result = NULL; + c->member_flags = 0; + c->member_writable = false; +} + +static void context_reset_interface(Context *c) { + c->interface_name = mfree(c->interface_name); + c->interface_flags = 0; + + context_reset_member(c); +} + +static int parse_xml_annotation(Context *context, uint64_t *flags) { + + enum { + STATE_ANNOTATION, + STATE_NAME, + STATE_VALUE + } state = STATE_ANNOTATION; + + _cleanup_free_ char *field = NULL, *value = NULL; + + assert(context); + + for (;;) { + _cleanup_free_ char *name = NULL; + + int t; + + t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); + if (t < 0) { + log_error("XML parse error."); + return t; + } + + if (t == XML_END) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Premature end of XML data."); + + switch (state) { + + case STATE_ANNOTATION: + + if (t == XML_ATTRIBUTE_NAME) { + + if (streq_ptr(name, "name")) + state = STATE_NAME; + + else if (streq_ptr(name, "value")) + state = STATE_VALUE; + + else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected <annotation> attribute %s.", + name); + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "annotation"))) { + + if (flags) { + if (streq_ptr(field, "org.freedesktop.DBus.Deprecated")) { + + if (streq_ptr(value, "true")) + *flags |= SD_BUS_VTABLE_DEPRECATED; + + } else if (streq_ptr(field, "org.freedesktop.DBus.Method.NoReply")) { + + if (streq_ptr(value, "true")) + *flags |= SD_BUS_VTABLE_METHOD_NO_REPLY; + + } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) { + + if (streq_ptr(value, "const")) + *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) | SD_BUS_VTABLE_PROPERTY_CONST; + else if (streq_ptr(value, "invalidates")) + *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION; + else if (streq_ptr(value, "false")) + *flags = *flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION); + } + } + + return 0; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <annotation>. (1)"); + + break; + + case STATE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + free_and_replace(field, name); + + state = STATE_ANNOTATION; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <annotation>. (2)"); + + break; + + case STATE_VALUE: + + if (t == XML_ATTRIBUTE_VALUE) { + free_and_replace(value, name); + + state = STATE_ANNOTATION; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <annotation>. (3)"); + + break; + + default: + assert_not_reached(); + } + } +} + +static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) { + + enum { + STATE_NODE, + STATE_NODE_NAME, + STATE_INTERFACE, + STATE_INTERFACE_NAME, + STATE_METHOD, + STATE_METHOD_NAME, + STATE_METHOD_ARG, + STATE_METHOD_ARG_NAME, + STATE_METHOD_ARG_TYPE, + STATE_METHOD_ARG_DIRECTION, + STATE_SIGNAL, + STATE_SIGNAL_NAME, + STATE_SIGNAL_ARG, + STATE_SIGNAL_ARG_NAME, + STATE_SIGNAL_ARG_TYPE, + STATE_SIGNAL_ARG_DIRECTION, + STATE_PROPERTY, + STATE_PROPERTY_NAME, + STATE_PROPERTY_TYPE, + STATE_PROPERTY_ACCESS, + } state = STATE_NODE; + + _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL; + const char *np = ASSERT_PTR(prefix); + int r; + + assert(context); + + if (n_depth > NODE_DEPTH_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "<node> depth too high."); + + for (;;) { + _cleanup_free_ char *name = NULL; + int t; + + t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); + if (t < 0) { + log_error("XML parse error."); + return t; + } + + if (t == XML_END) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Premature end of XML data."); + + switch (state) { + + case STATE_NODE: + if (t == XML_ATTRIBUTE_NAME) { + + if (streq_ptr(name, "name")) + state = STATE_NODE_NAME; + else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected <node> attribute %s.", name); + + } else if (t == XML_TAG_OPEN) { + + if (streq_ptr(name, "interface")) + state = STATE_INTERFACE; + else if (streq_ptr(name, "node")) { + + r = parse_xml_node(context, np, n_depth+1); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected <node> tag %s.", name); + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) { + + if (context->ops->on_path) { + r = context->ops->on_path(node_path ?: np, context->userdata); + if (r < 0) + return r; + } + + return 0; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <node>. (1)"); + + break; + + case STATE_NODE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + free(node_path); + + if (name[0] == '/') + node_path = TAKE_PTR(name); + else { + node_path = path_join(prefix, name); + if (!node_path) + return log_oom(); + } + + np = node_path; + state = STATE_NODE; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <node>. (2)"); + + break; + + case STATE_INTERFACE: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_INTERFACE_NAME; + else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected <interface> attribute %s.", + name); + + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "method")) + state = STATE_METHOD; + else if (streq_ptr(name, "signal")) + state = STATE_SIGNAL; + else if (streq_ptr(name, "property")) { + context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE; + state = STATE_PROPERTY; + } else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->interface_flags); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected <interface> tag %s.", name); + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) { + + if (n_depth == 0) { + if (context->ops->on_interface) { + r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_interface(context); + } + + state = STATE_NODE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <interface>. (1)"); + + break; + + case STATE_INTERFACE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + if (n_depth == 0) + free_and_replace(context->interface_name, name); + + state = STATE_INTERFACE; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <interface>. (2)"); + + break; + + case STATE_METHOD: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_METHOD_NAME; + else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected <method> attribute %s", + name); + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "arg")) + state = STATE_METHOD_ARG; + else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected <method> tag %s.", + name); + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) { + + if (n_depth == 0) { + if (context->ops->on_method) { + r = context->ops->on_method(context->interface_name, context->member_name, context->member_signature, context->member_result, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <method> (1)."); + + break; + + case STATE_METHOD_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + if (n_depth == 0) + free_and_replace(context->member_name, name); + + state = STATE_METHOD; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <method> (2)."); + + break; + + case STATE_METHOD_ARG: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_METHOD_ARG_NAME; + else if (streq_ptr(name, "type")) + state = STATE_METHOD_ARG_TYPE; + else if (streq_ptr(name, "direction")) + state = STATE_METHOD_ARG_DIRECTION; + else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected method <arg> attribute %s.", + name); + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, NULL); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected method <arg> tag %s.", + name); + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { + + if (n_depth == 0) { + + if (argument_type) { + if (!argument_direction || streq(argument_direction, "in")) { + if (!strextend(&context->member_signature, argument_type)) + return log_oom(); + } else if (streq(argument_direction, "out")) { + if (!strextend(&context->member_result, argument_type)) + return log_oom(); + } else + log_error("Unexpected method <arg> direction value '%s'.", argument_direction); + } + + argument_type = mfree(argument_type); + argument_direction = mfree(argument_direction); + } + + state = STATE_METHOD; + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in method <arg>. (1)"); + + break; + + case STATE_METHOD_ARG_NAME: + + if (t == XML_ATTRIBUTE_VALUE) + state = STATE_METHOD_ARG; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in method <arg>. (2)"); + + break; + + case STATE_METHOD_ARG_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + free_and_replace(argument_type, name); + + state = STATE_METHOD_ARG; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in method <arg>. (3)"); + + break; + + case STATE_METHOD_ARG_DIRECTION: + + if (t == XML_ATTRIBUTE_VALUE) { + free_and_replace(argument_direction, name); + + state = STATE_METHOD_ARG; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in method <arg>. (4)"); + + break; + + case STATE_SIGNAL: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_SIGNAL_NAME; + else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected <signal> attribute %s.", + name); + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "arg")) + state = STATE_SIGNAL_ARG; + else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected <signal> tag %s.", + name); + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) { + + if (n_depth == 0) { + if (context->ops->on_signal) { + r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <signal>. (1)"); + + break; + + case STATE_SIGNAL_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + if (n_depth == 0) + free_and_replace(context->member_name, name); + + state = STATE_SIGNAL; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <signal>. (2)"); + + break; + + case STATE_SIGNAL_ARG: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_SIGNAL_ARG_NAME; + else if (streq_ptr(name, "type")) + state = STATE_SIGNAL_ARG_TYPE; + else if (streq_ptr(name, "direction")) + state = STATE_SIGNAL_ARG_DIRECTION; + else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected signal <arg> attribute %s.", + name); + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, NULL); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected signal <arg> tag %s.", + name); + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { + + if (argument_type) { + if (!argument_direction || streq(argument_direction, "out")) { + if (!strextend(&context->member_signature, argument_type)) + return log_oom(); + } else + log_error("Unexpected signal <arg> direction value '%s'.", argument_direction); + + argument_type = mfree(argument_type); + } + + state = STATE_SIGNAL; + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in signal <arg> (1)."); + + break; + + case STATE_SIGNAL_ARG_NAME: + + if (t == XML_ATTRIBUTE_VALUE) + state = STATE_SIGNAL_ARG; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in signal <arg> (2)."); + + break; + + case STATE_SIGNAL_ARG_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + free_and_replace(argument_type, name); + + state = STATE_SIGNAL_ARG; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in signal <arg> (3)."); + + break; + + case STATE_SIGNAL_ARG_DIRECTION: + + if (t == XML_ATTRIBUTE_VALUE) { + free_and_replace(argument_direction, name); + + state = STATE_SIGNAL_ARG; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in signal <arg>. (4)"); + + break; + + case STATE_PROPERTY: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_PROPERTY_NAME; + else if (streq_ptr(name, "type")) + state = STATE_PROPERTY_TYPE; + else if (streq_ptr(name, "access")) + state = STATE_PROPERTY_ACCESS; + else + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), + "Unexpected <property> attribute %s.", + name); + } else if (t == XML_TAG_OPEN) { + + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected <property> tag %s.", + name); + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) { + + if (n_depth == 0) { + if (context->ops->on_property) { + r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <property>. (1)"); + + break; + + case STATE_PROPERTY_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + if (n_depth == 0) + free_and_replace(context->member_name, name); + + state = STATE_PROPERTY; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <property>. (2)"); + + break; + + case STATE_PROPERTY_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + if (n_depth == 0) + free_and_replace(context->member_signature, name); + + state = STATE_PROPERTY; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <property>. (3)"); + + break; + + case STATE_PROPERTY_ACCESS: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (streq(name, "readwrite") || streq(name, "write")) + context->member_writable = true; + + state = STATE_PROPERTY; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected token in <property>. (4)"); + + break; + } + } +} + +int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) { + Context context = { + .ops = ops, + .userdata = userdata, + .current = xml, + }; + + int r; + + assert(prefix); + assert(xml); + assert(ops); + + for (;;) { + _cleanup_free_ char *name = NULL; + + r = xml_tokenize(&context.current, &name, &context.xml_state, NULL); + if (r < 0) { + log_error("XML parse error"); + goto finish; + } + + if (r == XML_END) { + r = 0; + break; + } + + if (r == XML_TAG_OPEN) { + + if (streq(name, "node")) { + r = parse_xml_node(&context, prefix, 0); + if (r < 0) + goto finish; + } else { + log_error("Unexpected tag '%s' in introspection data.", name); + r = -EBADMSG; + goto finish; + } + } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token."); + r = -EBADMSG; + goto finish; + } + } + +finish: + context_reset_interface(&context); + + return r; +} diff --git a/src/busctl/busctl-introspect.h b/src/busctl/busctl-introspect.h new file mode 100644 index 0000000..720a0df --- /dev/null +++ b/src/busctl/busctl-introspect.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +typedef struct XMLIntrospectOps { + int (*on_path)(const char *path, void *userdata); + int (*on_interface)(const char *name, uint64_t flags, void *userdata); + int (*on_method)(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata); + int (*on_signal)(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata); + int (*on_property)(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata); +} XMLIntrospectOps; + +int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata); diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c new file mode 100644 index 0000000..ad69edf --- /dev/null +++ b/src/busctl/busctl.c @@ -0,0 +1,2593 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <getopt.h> + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-signature.h" +#include "bus-type.h" +#include "bus-util.h" +#include "busctl-introspect.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-table.h" +#include "glyph-util.h" +#include "json.h" +#include "log.h" +#include "main-func.h" +#include "os-util.h" +#include "pager.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-util.h" +#include "pretty-print.h" +#include "set.h" +#include "sort-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "verbs.h" +#include "version.h" + +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; +static PagerFlags arg_pager_flags = 0; +static bool arg_legend = true; +static int arg_full = -1; +static const char *arg_address = NULL; +static bool arg_unique = false; +static bool arg_acquired = false; +static bool arg_activatable = false; +static bool arg_show_machine = false; +static char **arg_matches = NULL; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static const char *arg_host = NULL; +static bool arg_user = false; +static size_t arg_snaplen = 4096; +static bool arg_list = false; +static bool arg_quiet = false; +static bool arg_verbose = false; +static bool arg_xml_interface = false; +static bool arg_expect_reply = true; +static bool arg_auto_start = true; +static bool arg_allow_interactive_authorization = true; +static bool arg_augment_creds = true; +static bool arg_watch_bind = false; +static usec_t arg_timeout = 0; +static const char *arg_destination = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep); + +#define NAME_IS_ACQUIRED INT_TO_PTR(1) +#define NAME_IS_ACTIVATABLE INT_TO_PTR(2) + +static int json_transform_message(sd_bus_message *m, JsonVariant **ret); + +static int acquire_bus(bool set_monitor, sd_bus **ret) { + _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL; + int r; + + r = sd_bus_new(&bus); + if (r < 0) + return log_error_errno(r, "Failed to allocate bus: %m"); + + (void) sd_bus_set_description(bus, "busctl"); + + if (set_monitor) { + r = sd_bus_set_monitor(bus, true); + if (r < 0) + return log_error_errno(r, "Failed to set monitor mode: %m"); + + r = sd_bus_negotiate_creds(bus, true, _SD_BUS_CREDS_ALL); + if (r < 0) + return log_error_errno(r, "Failed to enable credentials: %m"); + + r = sd_bus_negotiate_timestamp(bus, true); + if (r < 0) + return log_error_errno(r, "Failed to enable timestamps: %m"); + + r = sd_bus_negotiate_fds(bus, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fds: %m"); + } + + r = sd_bus_set_bus_client(bus, true); + if (r < 0) + return log_error_errno(r, "Failed to set bus client: %m"); + + r = sd_bus_set_watch_bind(bus, arg_watch_bind); + if (r < 0) + return log_error_errno(r, "Failed to set watch-bind setting to '%s': %m", + yes_no(arg_watch_bind)); + + if (arg_address) + r = sd_bus_set_address(bus, arg_address); + else + switch (arg_transport) { + + case BUS_TRANSPORT_LOCAL: + if (arg_user) + r = bus_set_address_user(bus); + else + r = bus_set_address_system(bus); + break; + + case BUS_TRANSPORT_REMOTE: + r = bus_set_address_system_remote(bus, arg_host); + break; + + case BUS_TRANSPORT_MACHINE: + r = bus_set_address_machine(bus, arg_user, arg_host); + break; + + default: + assert_not_reached(); + } + + if (r < 0) + return bus_log_address_error(r, arg_transport); + + r = sd_bus_start(bus); + if (r < 0) + return bus_log_connect_error(r, arg_transport); + + *ret = TAKE_PTR(bus); + + return 0; +} + +static int list_bus_names(int argc, char **argv, void *userdata) { + _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_hashmap_free_ Hashmap *names = NULL; + _cleanup_(table_unrefp) Table *table = NULL; + char *k; + void *v; + int r; + + enum { + COLUMN_ACTIVATABLE, + COLUMN_NAME, + COLUMN_PID, + COLUMN_PROCESS, + COLUMN_USER, + COLUMN_CONNECTION, + COLUMN_UNIT, + COLUMN_SESSION, + COLUMN_DESCRIPTION, + COLUMN_MACHINE, + }; + + if (!arg_unique && !arg_acquired && !arg_activatable) + arg_unique = arg_acquired = arg_activatable = true; + + r = acquire_bus(false, &bus); + if (r < 0) + return r; + + r = sd_bus_list_names(bus, + (arg_acquired || arg_unique) ? &acquired : NULL, + arg_activatable ? &activatable : NULL); + if (r < 0) + return log_error_errno(r, "Failed to list names: %m"); + + names = hashmap_new(&string_hash_ops); + if (!names) + return log_oom(); + + STRV_FOREACH(i, acquired) { + r = hashmap_put(names, *i, NAME_IS_ACQUIRED); + if (r < 0) + return log_error_errno(r, "Failed to add to hashmap: %m"); + } + + STRV_FOREACH(i, activatable) { + r = hashmap_put(names, *i, NAME_IS_ACTIVATABLE); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to add to hashmap: %m"); + } + + table = table_new("activatable", + "name", + "pid", + "process", + "user", + "connection", + "unit", + "session", + "description", + "machine"); + if (!table) + return log_oom(); + + if (arg_full > 0) + table_set_width(table, 0); + + r = table_set_align_percent(table, table_get_cell(table, 0, COLUMN_PID), 100); + if (r < 0) + return log_error_errno(r, "Failed to set alignment: %m"); + + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + + r = table_set_sort(table, (size_t) COLUMN_NAME); + if (r < 0) + return log_error_errno(r, "Failed to set sort column: %m"); + + if (arg_show_machine) + r = table_set_display(table, (size_t) COLUMN_NAME, + (size_t) COLUMN_PID, + (size_t) COLUMN_PROCESS, + (size_t) COLUMN_USER, + (size_t) COLUMN_CONNECTION, + (size_t) COLUMN_UNIT, + (size_t) COLUMN_SESSION, + (size_t) COLUMN_DESCRIPTION, + (size_t) COLUMN_MACHINE); + else + r = table_set_display(table, (size_t) COLUMN_NAME, + (size_t) COLUMN_PID, + (size_t) COLUMN_PROCESS, + (size_t) COLUMN_USER, + (size_t) COLUMN_CONNECTION, + (size_t) COLUMN_UNIT, + (size_t) COLUMN_SESSION, + (size_t) COLUMN_DESCRIPTION); + + if (r < 0) + return log_error_errno(r, "Failed to set columns to display: %m"); + + HASHMAP_FOREACH_KEY(v, k, names) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + if (v == NAME_IS_ACTIVATABLE) { + r = table_add_many( + table, + TABLE_INT, PTR_TO_INT(v), + TABLE_STRING, k, + TABLE_EMPTY, + TABLE_EMPTY, + TABLE_EMPTY, + TABLE_STRING, "(activatable)", TABLE_SET_COLOR, ansi_grey(), + TABLE_EMPTY, + TABLE_EMPTY, + TABLE_EMPTY, + TABLE_EMPTY); + if (r < 0) + return table_log_add_error(r); + + continue; + } + + assert(v == NAME_IS_ACQUIRED); + + if (!arg_unique && k[0] == ':') + continue; + + if (!arg_acquired && k[0] != ':') + continue; + + r = table_add_many(table, + TABLE_INT, PTR_TO_INT(v), + TABLE_STRING, k); + if (r < 0) + return table_log_add_error(r); + + r = sd_bus_get_name_creds( + bus, k, + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | + SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM| + SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_SESSION| + SD_BUS_CREDS_DESCRIPTION, &creds); + if (r < 0) { + log_debug_errno(r, "Failed to acquire credentials of service %s, ignoring: %m", k); + + r = table_fill_empty(table, COLUMN_MACHINE); + } else { + const char *unique = NULL, *session = NULL, *unit = NULL, *cn = NULL; + pid_t pid; + uid_t uid; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r >= 0) { + const char *comm = NULL; + + (void) sd_bus_creds_get_comm(creds, &comm); + + r = table_add_many(table, + TABLE_PID, pid, + TABLE_STRING, strna(comm)); + } else + r = table_add_many(table, TABLE_EMPTY, TABLE_EMPTY); + if (r < 0) + return table_log_add_error(r); + + r = sd_bus_creds_get_euid(creds, &uid); + if (r >= 0) { + _cleanup_free_ char *u = NULL; + + u = uid_to_name(uid); + if (!u) + return log_oom(); + + r = table_add_cell(table, NULL, TABLE_STRING, u); + } else + r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); + if (r < 0) + return table_log_add_error(r); + + (void) sd_bus_creds_get_unique_name(creds, &unique); + (void) sd_bus_creds_get_unit(creds, &unit); + (void) sd_bus_creds_get_session(creds, &session); + (void) sd_bus_creds_get_description(creds, &cn); + + r = table_add_many( + table, + TABLE_STRING, unique, + TABLE_STRING, unit, + TABLE_STRING, session, + TABLE_STRING, cn); + } + if (r < 0) + return table_log_add_error(r); + + if (arg_show_machine) { + sd_id128_t mid; + + r = sd_bus_get_name_machine_id(bus, k, &mid); + if (r < 0) + log_debug_errno(r, "Failed to acquire credentials of service %s, ignoring: %m", k); + else { + r = table_add_cell(table, NULL, TABLE_ID128, &mid); + if (r < 0) + return table_log_add_error(r); + + continue; /* line fully filled, no need to fill the remainder below */ + } + } + + r = table_fill_empty(table, 0); + if (r < 0) + return log_error_errno(r, "Failed to fill line: %m"); + } + + return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); +} + +static void print_subtree(const char *prefix, const char *path, char **l) { + /* We assume the list is sorted. Let's first skip over the + * entry we are looking at. */ + for (;;) { + if (!*l) + return; + + if (!streq(*l, path)) + break; + + l++; + } + + const char + *vertical = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_VERTICAL)), + *space = strjoina(prefix, special_glyph(SPECIAL_GLYPH_TREE_SPACE)); + + for (;;) { + bool has_more = false; + char **n; + + if (!*l || !path_startswith(*l, path)) + break; + + n = l + 1; + for (;;) { + if (!*n || !path_startswith(*n, path)) + break; + + if (!path_startswith(*n, *l)) { + has_more = true; + break; + } + + n++; + } + + printf("%s%s%s\n", + prefix, + special_glyph(has_more ? SPECIAL_GLYPH_TREE_BRANCH : SPECIAL_GLYPH_TREE_RIGHT), + *l); + + print_subtree(has_more ? vertical : space, *l, l); + l = n; + } +} + +static void print_tree(char **l) { + if (arg_list) + strv_print(l); + else if (strv_isempty(l)) + printf("No objects discovered.\n"); + else if (streq(l[0], "/") && !l[1]) + printf("Only root object discovered.\n"); + else + print_subtree("", "/", l); +} + +static int on_path(const char *path, void *userdata) { + Set *paths = ASSERT_PTR(userdata); + int r; + + r = set_put_strdup(&paths, path); + if (r < 0) + return log_oom(); + + return 0; +} + +static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths) { + static const XMLIntrospectOps ops = { + .on_path = on_path, + }; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *xml; + int r; + + r = sd_bus_call_method(bus, service, path, + "org.freedesktop.DBus.Introspectable", "Introspect", + &error, &reply, NULL); + if (r < 0) { + printf("%sFailed to introspect object %s of service %s: %s%s\n", + ansi_highlight_red(), + path, service, bus_error_message(&error, r), + ansi_normal()); + return r; + } + + r = sd_bus_message_read(reply, "s", &xml); + if (r < 0) + return bus_log_parse_error(r); + + return parse_xml_introspect(path, xml, &ops, paths); +} + +static int tree_one(sd_bus *bus, const char *service) { + _cleanup_set_free_ Set *paths = NULL, *done = NULL, *failed = NULL; + _cleanup_free_ char **l = NULL; + int r; + + r = set_put_strdup(&paths, "/"); + if (r < 0) + return log_oom(); + + for (;;) { + _cleanup_free_ char *p = NULL; + int q; + + p = set_steal_first(paths); + if (!p) + break; + + if (set_contains(done, p) || + set_contains(failed, p)) + continue; + + q = find_nodes(bus, service, p, paths); + if (q < 0 && r >= 0) + r = q; + + q = set_ensure_consume(q < 0 ? &failed : &done, &string_hash_ops_free, TAKE_PTR(p)); + assert(q != 0); + if (q < 0) + return log_oom(); + } + + pager_open(arg_pager_flags); + + l = set_get_strv(done); + if (!l) + return log_oom(); + + strv_sort(l); + print_tree(l); + + fflush(stdout); + + return r; +} + +static int tree(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + /* Do superficial verification of arguments before even opening the bus */ + STRV_FOREACH(i, strv_skip(argv, 1)) + if (!sd_bus_service_name_is_valid(*i)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid bus service name: %s", *i); + + if (!arg_unique && !arg_acquired) + arg_acquired = true; + + r = acquire_bus(false, &bus); + if (r < 0) + return r; + + if (argc <= 1) { + _cleanup_strv_free_ char **names = NULL; + bool not_first = false; + + r = sd_bus_list_names(bus, &names, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get name list: %m"); + + pager_open(arg_pager_flags); + + STRV_FOREACH(i, names) { + int q; + + if (!arg_unique && (*i)[0] == ':') + continue; + + if (!arg_acquired && (*i)[0] == ':') + continue; + + if (not_first) + printf("\n"); + + printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); + + q = tree_one(bus, *i); + if (q < 0 && r >= 0) + r = q; + + not_first = true; + } + } else + STRV_FOREACH(i, strv_skip(argv, 1)) { + int q; + + if (i > argv+1) + printf("\n"); + + if (argv[2]) { + pager_open(arg_pager_flags); + printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); + } + + q = tree_one(bus, *i); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + +static int format_cmdline(sd_bus_message *m, FILE *f, bool needs_space) { + int r; + + for (;;) { + const char *contents = NULL; + char type; + union { + uint8_t u8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double d64; + const char *string; + int i; + } basic; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return r; + if (r == 0) + return needs_space; + + if (bus_type_is_container(type) > 0) { + + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) + return r; + + if (type == SD_BUS_TYPE_ARRAY) { + unsigned n = 0; + + /* count array entries */ + for (;;) { + + r = sd_bus_message_skip(m, contents); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + + r = sd_bus_message_rewind(m, false); + if (r < 0) + return r; + + if (needs_space) + fputc(' ', f); + + fprintf(f, "%u", n); + needs_space = true; + + } else if (type == SD_BUS_TYPE_VARIANT) { + + if (needs_space) + fputc(' ', f); + + fprintf(f, "%s", contents); + needs_space = true; + } + + r = format_cmdline(m, f, needs_space); + if (r < 0) + return r; + + needs_space = r > 0; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + continue; + } + + r = sd_bus_message_read_basic(m, type, &basic); + if (r < 0) + return r; + + if (needs_space) + fputc(' ', f); + + switch (type) { + case SD_BUS_TYPE_BYTE: + fprintf(f, "%u", basic.u8); + break; + + case SD_BUS_TYPE_BOOLEAN: + fputs(true_false(basic.i), f); + break; + + case SD_BUS_TYPE_INT16: + fprintf(f, "%i", basic.s16); + break; + + case SD_BUS_TYPE_UINT16: + fprintf(f, "%u", basic.u16); + break; + + case SD_BUS_TYPE_INT32: + fprintf(f, "%i", basic.s32); + break; + + case SD_BUS_TYPE_UINT32: + fprintf(f, "%u", basic.u32); + break; + + case SD_BUS_TYPE_INT64: + fprintf(f, "%" PRIi64, basic.s64); + break; + + case SD_BUS_TYPE_UINT64: + fprintf(f, "%" PRIu64, basic.u64); + break; + + case SD_BUS_TYPE_DOUBLE: + fprintf(f, "%g", basic.d64); + break; + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + _cleanup_free_ char *b = NULL; + + b = cescape(basic.string); + if (!b) + return -ENOMEM; + + fprintf(f, "\"%s\"", b); + break; + } + + case SD_BUS_TYPE_UNIX_FD: + fprintf(f, "%i", basic.i); + break; + + default: + assert_not_reached(); + } + + needs_space = true; + } +} + +typedef struct Member { + const char *type; + char *interface; + char *name; + char *signature; + char *result; + char *value; + bool writable; + uint64_t flags; +} Member; + +static void member_hash_func(const Member *m, struct siphash *state) { + uint64_t arity = 1; + + assert(m); + assert(m->type); + + string_hash_func(m->type, state); + + arity += !!m->name + !!m->interface; + + uint64_hash_func(&arity, state); + + if (m->name) + string_hash_func(m->name, state); + + if (m->signature) + string_hash_func(m->signature, state); + + if (m->interface) + string_hash_func(m->interface, state); +} + +static int member_compare_func(const Member *x, const Member *y) { + int d; + + assert(x); + assert(y); + assert(x->type); + assert(y->type); + + d = strcmp_ptr(x->interface, y->interface); + if (d != 0) + return d; + + d = strcmp(x->type, y->type); + if (d != 0) + return d; + + d = strcmp_ptr(x->name, y->name); + if (d != 0) + return d; + + return strcmp_ptr(x->signature, y->signature); +} + +static int member_compare_funcp(Member * const *a, Member * const *b) { + return member_compare_func(*a, *b); +} + +static Member* member_free(Member *m) { + if (!m) + return NULL; + + free(m->interface); + free(m->name); + free(m->signature); + free(m->result); + free(m->value); + return mfree(m); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(Member*, member_free); + +static Set* member_set_free(Set *s) { + return set_free_with_destructor(s, member_free); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, member_set_free); + +static int on_interface(const char *interface, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m = NULL; + Set *members = ASSERT_PTR(userdata); + int r; + + assert(interface); + + m = new(Member, 1); + if (!m) + return log_oom(); + + *m = (Member) { + .type = "interface", + .flags = flags, + }; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Invalid introspection data: duplicate interface '%s'.", interface); + if (r < 0) + return log_oom(); + + m = NULL; + return 0; +} + +static int on_method(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m = NULL; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new(Member, 1); + if (!m) + return log_oom(); + + *m = (Member) { + .type = "method", + .flags = flags, + }; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->result, result); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Invalid introspection data: duplicate method '%s' on interface '%s'.", name, interface); + if (r < 0) + return log_oom(); + + m = NULL; + return 0; +} + +static int on_signal(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m = NULL; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new(Member, 1); + if (!m) + return log_oom(); + + *m = (Member) { + .type = "signal", + .flags = flags, + }; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Invalid introspection data: duplicate signal '%s' on interface '%s'.", name, interface); + if (r < 0) + return log_oom(); + + m = NULL; + return 0; +} + +static int on_property(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m = NULL; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new(Member, 1); + if (!m) + return log_oom(); + + *m = (Member) { + .type = "property", + .flags = flags, + .writable = writable, + }; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Invalid introspection data: duplicate property '%s' on interface '%s'.", name, interface); + if (r < 0) + return log_oom(); + + m = NULL; + return 0; +} + +DEFINE_PRIVATE_HASH_OPS(member_hash_ops, Member, member_hash_func, member_compare_func); + +static int introspect(int argc, char **argv, void *userdata) { + static const XMLIntrospectOps ops = { + .on_interface = on_interface, + .on_method = on_method, + .on_signal = on_signal, + .on_property = on_property, + }; + + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_xml = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(member_set_freep) Set *members = NULL; + unsigned name_width, type_width, signature_width, result_width; + Member *m; + const char *xml; + int r; + + r = acquire_bus(false, &bus); + if (r < 0) + return r; + + members = set_new(&member_hash_ops); + if (!members) + return log_oom(); + + r = sd_bus_call_method(bus, argv[1], argv[2], + "org.freedesktop.DBus.Introspectable", "Introspect", + &error, &reply_xml, NULL); + if (r < 0) + return log_error_errno(r, "Failed to introspect object %s of service %s: %s", + argv[2], argv[1], bus_error_message(&error, r)); + + r = sd_bus_message_read(reply_xml, "s", &xml); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_xml_interface) { + /* Just dump the received XML and finish */ + pager_open(arg_pager_flags); + puts(xml); + return 0; + } + + /* First, get list of all properties */ + r = parse_xml_introspect(argv[2], xml, &ops, members); + if (r < 0) + return r; + + /* Second, find the current values for them */ + SET_FOREACH(m, members) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + + if (!streq(m->type, "property")) + continue; + + if (m->value) + continue; + + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + r = sd_bus_call_method(bus, argv[1], argv[2], + "org.freedesktop.DBus.Properties", "GetAll", + &error, &reply, "s", m->interface); + if (r < 0) + return log_error_errno(r, "Failed to get all properties on interface %s: %s", + m->interface, bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, 'a', "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + Member *z; + _cleanup_free_ char *buf = NULL, *signature = NULL; + _cleanup_fclose_ FILE *mf = NULL; + size_t sz = 0; + const char *name, *contents; + char type; + + r = sd_bus_message_enter_container(reply, 'e', "sv"); + if (r < 0) + return bus_log_parse_error(r); + + if (r == 0) + break; + + r = sd_bus_message_read(reply, "s", &name); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'v', NULL); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_peek_type(reply, &type, &contents); + if (r <= 0) + return bus_log_parse_error(r == 0 ? EINVAL : r); + + if (type == SD_BUS_TYPE_STRUCT_BEGIN) + signature = strjoin(CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END)); + else if (type == SD_BUS_TYPE_DICT_ENTRY_BEGIN) + signature = strjoin(CHAR_TO_STR(SD_BUS_TYPE_DICT_ENTRY_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_DICT_ENTRY_END)); + else if (contents) + signature = strjoin(CHAR_TO_STR(type), contents); + else + signature = strdup(CHAR_TO_STR(type)); + if (!signature) + return log_oom(); + + mf = open_memstream_unlocked(&buf, &sz); + if (!mf) + return log_oom(); + + r = format_cmdline(reply, mf, false); + if (r < 0) + return bus_log_parse_error(r); + + mf = safe_fclose(mf); + + z = set_get(members, &((Member) { + .type = "property", + .interface = m->interface, + .signature = signature, + .name = (char*) name })); + if (z) + free_and_replace(z->value, buf); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + name_width = strlen("NAME"); + type_width = strlen("TYPE"); + signature_width = strlen("SIGNATURE"); + result_width = strlen("RESULT/VALUE"); + + Member **sorted = newa(Member*, set_size(members)); + size_t k = 0; + + SET_FOREACH(m, members) { + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + if (m->interface) + name_width = MAX(name_width, strlen(m->interface)); + if (m->name) + name_width = MAX(name_width, strlen(m->name) + 1); + if (m->type) + type_width = MAX(type_width, strlen(m->type)); + if (m->signature) + signature_width = MAX(signature_width, strlen(m->signature)); + if (m->result) + result_width = MAX(result_width, strlen(m->result)); + if (m->value) + result_width = MAX(result_width, strlen(m->value)); + + sorted[k++] = m; + } + + if (result_width > 40 && arg_full <= 0) + result_width = 40; + + typesafe_qsort(sorted, k, member_compare_funcp); + + pager_open(arg_pager_flags); + + if (arg_legend) + printf("%-*s %-*s %-*s %-*s %s\n", + (int) name_width, "NAME", + (int) type_width, "TYPE", + (int) signature_width, "SIGNATURE", + (int) result_width, "RESULT/VALUE", + "FLAGS"); + + for (size_t j = 0; j < k; j++) { + _cleanup_free_ char *ellipsized = NULL; + const char *rv; + bool is_interface; + + m = sorted[j]; + + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + is_interface = streq(m->type, "interface"); + + if (argv[3] && is_interface) + continue; + + if (m->value) { + ellipsized = ellipsize(m->value, result_width, 100); + if (!ellipsized) + return log_oom(); + + rv = ellipsized; + } else + rv = empty_to_dash(m->result); + + printf("%s%s%-*s%s %-*s %-*s %-*s%s%s%s%s%s%s\n", + is_interface ? ansi_highlight() : "", + is_interface ? "" : ".", + - !is_interface + (int) name_width, + empty_to_dash(streq_ptr(m->type, "interface") ? m->interface : m->name), + is_interface ? ansi_normal() : "", + (int) type_width, empty_to_dash(m->type), + (int) signature_width, empty_to_dash(m->signature), + (int) result_width, rv, + (m->flags & SD_BUS_VTABLE_DEPRECATED) ? " deprecated" : (m->flags || m->writable ? "" : " -"), + (m->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) ? " no-reply" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_CONST) ? " const" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) ? " emits-change" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) ? " emits-invalidation" : "", + m->writable ? " writable" : ""); + } + + return 0; +} + +static int message_dump(sd_bus_message *m, FILE *f) { + return sd_bus_message_dump(m, f, SD_BUS_MESSAGE_DUMP_WITH_HEADER); +} + +static int message_pcap(sd_bus_message *m, FILE *f) { + return bus_message_pcap_frame(m, arg_snaplen, f); +} + +static int message_json(sd_bus_message *m, FILE *f) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL; + char e[2]; + int r; + usec_t ts; + + r = json_transform_message(m, &v); + if (r < 0) + return r; + + e[0] = m->header->endian; + e[1] = 0; + + ts = m->realtime; + if (ts == 0) + ts = now(CLOCK_REALTIME); + + r = json_build(&w, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_STRING(bus_message_type_to_string(m->header->type))), + JSON_BUILD_PAIR("endian", JSON_BUILD_STRING(e)), + JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(m->header->flags)), + JSON_BUILD_PAIR("version", JSON_BUILD_INTEGER(m->header->version)), + JSON_BUILD_PAIR("cookie", JSON_BUILD_INTEGER(BUS_MESSAGE_COOKIE(m))), + JSON_BUILD_PAIR_CONDITION(m->reply_cookie != 0, "reply_cookie", JSON_BUILD_INTEGER(m->reply_cookie)), + JSON_BUILD_PAIR("timestamp-realtime", JSON_BUILD_UNSIGNED(ts)), + JSON_BUILD_PAIR_CONDITION(m->sender, "sender", JSON_BUILD_STRING(m->sender)), + JSON_BUILD_PAIR_CONDITION(m->destination, "destination", JSON_BUILD_STRING(m->destination)), + JSON_BUILD_PAIR_CONDITION(m->path, "path", JSON_BUILD_STRING(m->path)), + JSON_BUILD_PAIR_CONDITION(m->interface, "interface", JSON_BUILD_STRING(m->interface)), + JSON_BUILD_PAIR_CONDITION(m->member, "member", JSON_BUILD_STRING(m->member)), + JSON_BUILD_PAIR_CONDITION(m->monotonic != 0, "monotonic", JSON_BUILD_INTEGER(m->monotonic)), + JSON_BUILD_PAIR_CONDITION(m->realtime != 0, "realtime", JSON_BUILD_INTEGER(m->realtime)), + JSON_BUILD_PAIR_CONDITION(m->seqnum != 0, "seqnum", JSON_BUILD_INTEGER(m->seqnum)), + JSON_BUILD_PAIR_CONDITION(m->error.name, "error_name", JSON_BUILD_STRING(m->error.name)), + JSON_BUILD_PAIR("payload", JSON_BUILD_VARIANT(v)))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + json_variant_dump(w, arg_json_format_flags, f, NULL); + return 0; +} + +static int monitor(int argc, char **argv, int (*dump)(sd_bus_message *m, FILE *f)) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + uint32_t flags = 0; + const char *unique_name; + bool is_monitor = false; + int r; + + r = acquire_bus(true, &bus); + if (r < 0) + return r; + + /* upgrade connection; it's not used for anything else after this call */ + r = sd_bus_message_new_method_call(bus, + &message, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus.Monitoring", + "BecomeMonitor"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(message, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(i, argv+1) { + _cleanup_free_ char *m = NULL; + + if (!sd_bus_service_name_is_valid(*i)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid service name '%s'", *i); + + m = strjoin("sender='", *i, "'"); + if (!m) + return log_oom(); + + r = sd_bus_message_append_basic(message, 's', m); + if (r < 0) + return bus_log_create_error(r); + + free(m); + m = strjoin("destination='", *i, "'"); + if (!m) + return log_oom(); + + r = sd_bus_message_append_basic(message, 's', m); + if (r < 0) + return bus_log_create_error(r); + } + + STRV_FOREACH(i, arg_matches) { + r = sd_bus_message_append_basic(message, 's', *i); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(message); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_basic(message, 'u', &flags); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, message, arg_timeout, &error, NULL); + if (r < 0) + return log_error_errno(r, "Call to org.freedesktop.DBus.Monitoring.BecomeMonitor failed: %s", + bus_error_message(&error, r)); + + r = sd_bus_get_unique_name(bus, &unique_name); + if (r < 0) + return log_error_errno(r, "Failed to get unique name: %m"); + + log_info("Monitoring bus message stream."); + + for (;;) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) + return log_error_errno(r, "Failed to process bus: %m"); + + if (m) { + if (!is_monitor) { + const char *name; + + /* wait until we lose our unique name */ + if (sd_bus_message_is_signal(m, "org.freedesktop.DBus", "NameLost") <= 0) + continue; + + r = sd_bus_message_read(m, "s", &name); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, unique_name)) + is_monitor = true; + + continue; + } + + dump(m, stdout); + fflush(stdout); + + if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected") > 0) { + log_info("Connection terminated, exiting."); + return 0; + } + + continue; + } + + if (r > 0) + continue; + + r = sd_bus_wait(bus, UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Failed to wait for bus: %m"); + } +} + +static int verb_monitor(int argc, char **argv, void *userdata) { + return monitor(argc, argv, (arg_json_format_flags & JSON_FORMAT_OFF) ? message_dump : message_json); +} + +static int verb_capture(int argc, char **argv, void *userdata) { + _cleanup_free_ char *osname = NULL; + static const char info[] = + "busctl (systemd) " STRINGIFY(PROJECT_VERSION) " (Git " GIT_VERSION ")"; + int r; + + if (isatty(fileno(stdout)) > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Refusing to write message data to console, please redirect output to a file."); + + r = parse_os_release(NULL, "PRETTY_NAME", &osname); + if (r < 0) + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_INFO, r, + "Failed to read os-release file, ignoring: %m"); + bus_pcap_header(arg_snaplen, osname, info, stdout); + + r = monitor(argc, argv, message_pcap); + if (r < 0) + return r; + + r = fflush_and_check(stdout); + if (r < 0) + return log_error_errno(r, "Couldn't write capture file: %m"); + + return r; +} + +static int status(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + int r; + + r = acquire_bus(false, &bus); + if (r < 0) + return r; + + pager_open(arg_pager_flags); + + if (!isempty(argv[1])) { + r = parse_pid(argv[1], &pid); + if (r < 0) + r = sd_bus_get_name_creds( + bus, + argv[1], + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, + &creds); + else + r = sd_bus_creds_new_from_pid( + &creds, + pid, + _SD_BUS_CREDS_ALL); + } else { + const char *scope, *address; + sd_id128_t bus_id; + + r = sd_bus_get_address(bus, &address); + if (r >= 0) + printf("BusAddress=%s%s%s\n", ansi_highlight(), address, ansi_normal()); + + r = sd_bus_get_scope(bus, &scope); + if (r >= 0) + printf("BusScope=%s%s%s\n", ansi_highlight(), scope, ansi_normal()); + + r = sd_bus_get_bus_id(bus, &bus_id); + if (r >= 0) + printf("BusID=%s" SD_ID128_FORMAT_STR "%s\n", + ansi_highlight(), SD_ID128_FORMAT_VAL(bus_id), ansi_normal()); + + r = sd_bus_get_owner_creds( + bus, + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, + &creds); + } + + if (r < 0) + return log_error_errno(r, "Failed to get credentials: %m"); + + bus_creds_dump(creds, NULL, false); + return 0; +} + +static int message_append_cmdline(sd_bus_message *m, const char *signature, char ***x) { + char **p; + int r; + + assert(m); + assert(signature); + assert(x); + + p = *x; + + for (;;) { + const char *v; + char t; + + t = *signature; + v = *p; + + if (t == 0) + break; + if (!v) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Too few parameters for signature."); + + signature++; + p++; + + switch (t) { + + case SD_BUS_TYPE_BOOLEAN: + + r = parse_boolean(v); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as boolean: %m", v); + + r = sd_bus_message_append_basic(m, t, &r); + break; + + case SD_BUS_TYPE_BYTE: { + uint8_t z; + + r = safe_atou8(v, &z); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as byte (unsigned 8bit integer): %m", v); + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT16: { + int16_t z; + + r = safe_atoi16(v, &z); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as signed 16bit integer: %m", v); + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT16: { + uint16_t z; + + r = safe_atou16(v, &z); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as unsigned 16bit integer: %m", v); + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT32: { + int32_t z; + + r = safe_atoi32(v, &z); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as signed 32bit integer: %m", v); + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t z; + + r = safe_atou32(v, &z); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as unsigned 32bit integer: %m", v); + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT64: { + int64_t z; + + r = safe_atoi64(v, &z); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as signed 64bit integer: %m", v); + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t z; + + r = safe_atou64(v, &z); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as unsigned 64bit integer: %m", v); + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_DOUBLE: { + double z; + + r = safe_atod(v, &z); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' as double precision floating point: %m", v); + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + + r = sd_bus_message_append_basic(m, t, v); + break; + + case SD_BUS_TYPE_ARRAY: { + uint32_t n; + size_t k; + + r = safe_atou32(v, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' number of array entries: %m", v); + + r = signature_element_length(signature, &k); + if (r < 0) + return log_error_errno(r, "Invalid array signature: %m"); + + { + char s[k + 1]; + memcpy(s, signature, k); + s[k] = 0; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return bus_log_create_error(r); + + for (unsigned i = 0; i < n; i++) { + r = message_append_cmdline(m, s, &p); + if (r < 0) + return r; + } + } + + signature += k; + + r = sd_bus_message_close_container(m); + break; + } + + case SD_BUS_TYPE_VARIANT: + r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, v); + if (r < 0) + return bus_log_create_error(r); + + r = message_append_cmdline(m, v, &p); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + signature--; + p--; + + r = signature_element_length(signature, &k); + if (r < 0 || k < 2) { + if (r >= 0 && k < 2) + r = -ERANGE; + return log_error_errno(r, "Invalid struct/dict entry signature: %m"); + } + + { + char s[k-1]; + memcpy(s, signature + 1, k - 2); + s[k - 2] = 0; + + const char ctype = t == SD_BUS_TYPE_STRUCT_BEGIN ? + SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY; + r = sd_bus_message_open_container(m, ctype, s); + if (r < 0) + return bus_log_create_error(r); + + r = message_append_cmdline(m, s, &p); + if (r < 0) + return r; + } + + signature += k; + + r = sd_bus_message_close_container(m); + break; + } + + case SD_BUS_TYPE_UNIX_FD: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "UNIX file descriptor not supported as type."); + + default: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown signature type %c.", t); + } + + if (r < 0) + return bus_log_create_error(r); + } + + *x = p; + return 0; +} + +static int json_transform_one(sd_bus_message *m, JsonVariant **ret); + +static int json_transform_array_or_struct(sd_bus_message *m, JsonVariant **ret) { + JsonVariant **elements = NULL; + size_t n_elements = 0; + int r; + + assert(m); + assert(ret); + + for (;;) { + r = sd_bus_message_at_end(m, false); + if (r < 0) { + bus_log_parse_error(r); + goto finish; + } + if (r > 0) + break; + + if (!GREEDY_REALLOC(elements, n_elements + 1)) { + r = log_oom(); + goto finish; + } + + r = json_transform_one(m, elements + n_elements); + if (r < 0) + goto finish; + + n_elements++; + } + + r = json_variant_new_array(ret, elements, n_elements); + +finish: + json_variant_unref_many(elements, n_elements); + free(elements); + + return r; +} + +static int json_transform_variant(sd_bus_message *m, const char *contents, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *value = NULL; + int r; + + assert(m); + assert(contents); + assert(ret); + + r = json_transform_one(m, &value); + if (r < 0) + return r; + + r = json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("type", JSON_BUILD_STRING(contents)), + JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(value)))); + if (r < 0) + return log_oom(); + + return r; +} + +static int json_transform_dict_array(sd_bus_message *m, JsonVariant **ret) { + JsonVariant **elements = NULL; + size_t n_elements = 0; + int r; + + assert(m); + assert(ret); + + for (;;) { + const char *contents; + char type; + + r = sd_bus_message_at_end(m, false); + if (r < 0) { + bus_log_parse_error(r); + goto finish; + } + if (r > 0) + break; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return r; + + assert(type == 'e'); + + if (!GREEDY_REALLOC(elements, n_elements + 2)) { + r = log_oom(); + goto finish; + } + + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) { + bus_log_parse_error(r); + goto finish; + } + + r = json_transform_one(m, elements + n_elements); + if (r < 0) + goto finish; + + n_elements++; + + r = json_transform_one(m, elements + n_elements); + if (r < 0) + goto finish; + + n_elements++; + + r = sd_bus_message_exit_container(m); + if (r < 0) { + bus_log_parse_error(r); + goto finish; + } + } + + r = json_variant_new_object(ret, elements, n_elements); + +finish: + json_variant_unref_many(elements, n_elements); + free(elements); + + return r; +} + +static int json_transform_one(sd_bus_message *m, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + const char *contents; + char type; + int r; + + assert(m); + assert(ret); + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return bus_log_parse_error(r); + + switch (type) { + + case SD_BUS_TYPE_BYTE: { + uint8_t b; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_unsigned(&v, b); + if (r < 0) + return log_error_errno(r, "Failed to transform byte: %m"); + + break; + } + + case SD_BUS_TYPE_BOOLEAN: { + int b; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_boolean(&v, b); + if (r < 0) + return log_error_errno(r, "Failed to transform boolean: %m"); + + break; + } + + case SD_BUS_TYPE_INT16: { + int16_t b; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_integer(&v, b); + if (r < 0) + return log_error_errno(r, "Failed to transform int16: %m"); + + break; + } + + case SD_BUS_TYPE_UINT16: { + uint16_t b; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_unsigned(&v, b); + if (r < 0) + return log_error_errno(r, "Failed to transform uint16: %m"); + + break; + } + + case SD_BUS_TYPE_INT32: { + int32_t b; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_integer(&v, b); + if (r < 0) + return log_error_errno(r, "Failed to transform int32: %m"); + + break; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t b; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_unsigned(&v, b); + if (r < 0) + return log_error_errno(r, "Failed to transform uint32: %m"); + + break; + } + + case SD_BUS_TYPE_INT64: { + int64_t b; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_integer(&v, b); + if (r < 0) + return log_error_errno(r, "Failed to transform int64: %m"); + + break; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t b; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_unsigned(&v, b); + if (r < 0) + return log_error_errno(r, "Failed to transform uint64: %m"); + + break; + } + + case SD_BUS_TYPE_DOUBLE: { + double d; + + r = sd_bus_message_read_basic(m, type, &d); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_real(&v, d); + if (r < 0) + return log_error_errno(r, "Failed to transform double: %m"); + + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + const char *s; + + r = sd_bus_message_read_basic(m, type, &s); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_string(&v, s); + if (r < 0) + return log_error_errno(r, "Failed to transform double: %m"); + + break; + } + + case SD_BUS_TYPE_UNIX_FD: + r = sd_bus_message_read_basic(m, type, NULL); + if (r < 0) + return bus_log_parse_error(r); + + r = json_variant_new_null(&v); + if (r < 0) + return log_error_errno(r, "Failed to transform fd: %m"); + + break; + + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_VARIANT: + case SD_BUS_TYPE_STRUCT: + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) + return bus_log_parse_error(r); + + if (type == SD_BUS_TYPE_VARIANT) + r = json_transform_variant(m, contents, &v); + else if (type == SD_BUS_TYPE_ARRAY && contents[0] == '{') + r = json_transform_dict_array(m, &v); + else + r = json_transform_array_or_struct(m, &v); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + break; + + default: + assert_not_reached(); + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int json_transform_message(sd_bus_message *m, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + const char *type; + int r; + + assert(m); + assert(ret); + + assert_se(type = sd_bus_message_get_signature(m, false)); + + r = json_transform_array_or_struct(m, &v); + if (r < 0) + return r; + + r = json_build(ret, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("type", JSON_BUILD_STRING(type)), + JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(v)))); + if (r < 0) + return log_oom(); + + return 0; +} + +static int call(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + int r; + + r = acquire_bus(false, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], argv[3], argv[4]); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_expect_reply(m, arg_expect_reply); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_auto_start(m, arg_auto_start); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_allow_interactive_authorization); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(argv[5])) { + char **p; + + p = argv+6; + + r = message_append_cmdline(m, argv[5], &p); + if (r < 0) + return r; + + if (*p) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Too many parameters for signature."); + } + + if (!arg_expect_reply) { + r = sd_bus_send(bus, m, NULL); + if (r < 0) + return log_error_errno(r, "Failed to send message: %m"); + + return 0; + } + + r = sd_bus_call(bus, m, arg_timeout, &error, &reply); + if (r < 0) + return log_error_errno(r, "Call failed: %s", bus_error_message(&error, r)); + + r = sd_bus_message_is_empty(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (r == 0 && !arg_quiet) { + + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + r = json_transform_message(reply, &v); + if (r < 0) + return r; + + json_variant_dump(v, arg_json_format_flags, NULL, NULL); + + } else if (arg_verbose) { + pager_open(arg_pager_flags); + + r = sd_bus_message_dump(reply, stdout, 0); + if (r < 0) + return r; + } else { + + fputs(sd_bus_message_get_signature(reply, true), stdout); + fputc(' ', stdout); + + r = format_cmdline(reply, stdout, false); + if (r < 0) + return bus_log_parse_error(r); + + fputc('\n', stdout); + } + } + + return 0; +} + +static int emit_signal(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + r = acquire_bus(false, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_signal(bus, &m, argv[1], argv[2], argv[3]); + if (r < 0) + return bus_log_create_error(r); + + if (arg_destination) { + r = sd_bus_message_set_destination(m, arg_destination); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_set_auto_start(m, arg_auto_start); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(argv[4])) { + char **p; + + p = argv+5; + + r = message_append_cmdline(m, argv[4], &p); + if (r < 0) + return r; + + if (*p) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Too many parameters for signature."); + } + + r = sd_bus_send(bus, m, NULL); + if (r < 0) + return log_error_errno(r, "Failed to send signal: %m"); + + return 0; +} + +static int get_property(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + r = acquire_bus(false, &bus); + if (r < 0) + return r; + + STRV_FOREACH(i, argv + 4) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *contents = NULL; + char type; + + r = sd_bus_call_method(bus, argv[1], argv[2], + "org.freedesktop.DBus.Properties", "Get", + &error, &reply, "ss", argv[3], *i); + if (r < 0) + return log_error_errno(r, "Failed to get property %s on interface %s: %s", + *i, argv[3], + bus_error_message(&error, r)); + + r = sd_bus_message_peek_type(reply, &type, &contents); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'v', contents); + if (r < 0) + return bus_log_parse_error(r); + + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + r = json_transform_variant(reply, contents, &v); + if (r < 0) + return r; + + json_variant_dump(v, arg_json_format_flags, NULL, NULL); + + } else if (arg_verbose) { + pager_open(arg_pager_flags); + + r = sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY); + if (r < 0) + return r; + } else { + fputs(contents, stdout); + fputc(' ', stdout); + + r = format_cmdline(reply, stdout, false); + if (r < 0) + return bus_log_parse_error(r); + + fputc('\n', stdout); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + return 0; +} + +static int set_property(int argc, char **argv, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char **p; + int r; + + r = acquire_bus(false, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], + "org.freedesktop.DBus.Properties", "Set"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ss", argv[3], argv[4]); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', argv[5]); + if (r < 0) + return bus_log_create_error(r); + + p = argv + 6; + r = message_append_cmdline(m, argv[5], &p); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + if (*p) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many parameters for signature."); + + r = sd_bus_call(bus, m, arg_timeout, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to set property %s on interface %s: %s", + argv[4], argv[3], + bus_error_message(&error, r)); + + return 0; +} + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("busctl", "1", &link); + if (r < 0) + return log_oom(); + + pager_open(arg_pager_flags); + + printf("%s [OPTIONS...] COMMAND ...\n\n" + "%sIntrospect the D-Bus IPC bus.%s\n" + "\nCommands:\n" + " list List bus names\n" + " status [SERVICE] Show bus service, process or bus owner credentials\n" + " monitor [SERVICE...] Show bus traffic\n" + " capture [SERVICE...] Capture bus traffic as pcap\n" + " tree [SERVICE...] Show object tree of service\n" + " introspect SERVICE OBJECT [INTERFACE]\n" + " call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n" + " Call a method\n" + " emit OBJECT INTERFACE SIGNAL [SIGNATURE [ARGUMENT...]]\n" + " Emit a signal\n" + " get-property SERVICE OBJECT INTERFACE PROPERTY...\n" + " Get property value\n" + " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" + " Set property value\n" + " help Show this help\n" + "\nOptions:\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " -l --full Do not ellipsize output\n" + " --system Connect to system bus\n" + " --user Connect to user bus\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --address=ADDRESS Connect to bus specified by address\n" + " --show-machine Show machine ID column in list\n" + " --unique Only show unique names\n" + " --acquired Only show acquired names\n" + " --activatable Only show activatable names\n" + " --match=MATCH Only show matching messages\n" + " --size=SIZE Maximum length of captured packet\n" + " --list Don't show tree, but simple object path list\n" + " -q --quiet Don't show method call reply\n" + " --verbose Show result values in long format\n" + " --json=MODE Output as JSON\n" + " -j Same as --json=pretty on tty, --json=short otherwise\n" + " --xml-interface Dump the XML description in introspect command\n" + " --expect-reply=BOOL Expect a method call reply\n" + " --auto-start=BOOL Auto-start destination service\n" + " --allow-interactive-authorization=BOOL\n" + " Allow interactive authorization for operation\n" + " --timeout=SECS Maximum time to wait for method call completion\n" + " --augment-creds=BOOL Extend credential data with data read from /proc/$PID\n" + " --watch-bind=BOOL Wait for bus AF_UNIX socket to be bound in the file\n" + " system\n" + " --destination=SERVICE Destination service of a signal\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int verb_help(int argc, char **argv, void *userdata) { + return help(); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_SYSTEM, + ARG_USER, + ARG_ADDRESS, + ARG_MATCH, + ARG_SHOW_MACHINE, + ARG_UNIQUE, + ARG_ACQUIRED, + ARG_ACTIVATABLE, + ARG_SIZE, + ARG_LIST, + ARG_VERBOSE, + ARG_XML_INTERFACE, + ARG_EXPECT_REPLY, + ARG_AUTO_START, + ARG_ALLOW_INTERACTIVE_AUTHORIZATION, + ARG_TIMEOUT, + ARG_AUGMENT_CREDS, + ARG_WATCH_BIND, + ARG_JSON, + ARG_DESTINATION, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "full", no_argument, NULL, 'l' }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "address", required_argument, NULL, ARG_ADDRESS }, + { "show-machine", no_argument, NULL, ARG_SHOW_MACHINE }, + { "unique", no_argument, NULL, ARG_UNIQUE }, + { "acquired", no_argument, NULL, ARG_ACQUIRED }, + { "activatable", no_argument, NULL, ARG_ACTIVATABLE }, + { "match", required_argument, NULL, ARG_MATCH }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "size", required_argument, NULL, ARG_SIZE }, + { "list", no_argument, NULL, ARG_LIST }, + { "quiet", no_argument, NULL, 'q' }, + { "verbose", no_argument, NULL, ARG_VERBOSE }, + { "xml-interface", no_argument, NULL, ARG_XML_INTERFACE }, + { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY }, + { "auto-start", required_argument, NULL, ARG_AUTO_START }, + { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION }, + { "timeout", required_argument, NULL, ARG_TIMEOUT }, + { "augment-creds", required_argument, NULL, ARG_AUGMENT_CREDS }, + { "watch-bind", required_argument, NULL, ARG_WATCH_BIND }, + { "json", required_argument, NULL, ARG_JSON }, + { "destination", required_argument, NULL, ARG_DESTINATION }, + {}, + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:qjl", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case 'l': + arg_full = true; + break; + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_ADDRESS: + arg_address = optarg; + break; + + case ARG_SHOW_MACHINE: + arg_show_machine = true; + break; + + case ARG_UNIQUE: + arg_unique = true; + break; + + case ARG_ACQUIRED: + arg_acquired = true; + break; + + case ARG_ACTIVATABLE: + arg_activatable = true; + break; + + case ARG_MATCH: + if (strv_extend(&arg_matches, optarg) < 0) + return log_oom(); + break; + + case ARG_SIZE: { + uint64_t sz; + + r = parse_size(optarg, 1024, &sz); + if (r < 0) + return log_error_errno(r, "Failed to parse size '%s': %m", optarg); + + if ((uint64_t) (size_t) sz != sz) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), + "Size out of range."); + + arg_snaplen = (size_t) sz; + break; + } + + case ARG_LIST: + arg_list = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERBOSE: + arg_verbose = true; + break; + + case ARG_XML_INTERFACE: + arg_xml_interface = true; + break; + + case ARG_EXPECT_REPLY: + r = parse_boolean_argument("--expect-reply=", optarg, &arg_expect_reply); + if (r < 0) + return r; + break; + + case ARG_AUTO_START: + r = parse_boolean_argument("--auto-start=", optarg, &arg_auto_start); + if (r < 0) + return r; + break; + + case ARG_ALLOW_INTERACTIVE_AUTHORIZATION: + r = parse_boolean_argument("--allow-interactive-authorization=", optarg, + &arg_allow_interactive_authorization); + if (r < 0) + return r; + break; + + case ARG_TIMEOUT: + r = parse_sec(optarg, &arg_timeout); + if (r < 0) + return log_error_errno(r, "Failed to parse --timeout= parameter '%s': %m", optarg); + + break; + + case ARG_AUGMENT_CREDS: + r = parse_boolean_argument("--augment-creds=", optarg, &arg_augment_creds); + if (r < 0) + return r; + break; + + case ARG_WATCH_BIND: + r = parse_boolean_argument("--watch-bind=", optarg, &arg_watch_bind); + if (r < 0) + return r; + break; + + case 'j': + arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; + break; + + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; + + case ARG_DESTINATION: + arg_destination = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (arg_full < 0) + arg_full = terminal_is_dumb(); + + return 1; +} + +static int busctl_main(int argc, char *argv[]) { + static const Verb verbs[] = { + { "list", VERB_ANY, 1, VERB_DEFAULT, list_bus_names }, + { "status", VERB_ANY, 2, 0, status }, + { "monitor", VERB_ANY, VERB_ANY, 0, verb_monitor }, + { "capture", VERB_ANY, VERB_ANY, 0, verb_capture }, + { "tree", VERB_ANY, VERB_ANY, 0, tree }, + { "introspect", 3, 4, 0, introspect }, + { "call", 5, VERB_ANY, 0, call }, + { "emit", 4, VERB_ANY, 0, emit_signal }, + { "get-property", 5, VERB_ANY, 0, get_property }, + { "set-property", 6, VERB_ANY, 0, set_property }, + { "help", VERB_ANY, VERB_ANY, 0, verb_help }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + return busctl_main(argc, argv); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/busctl/meson.build b/src/busctl/meson.build new file mode 100644 index 0000000..295dc09 --- /dev/null +++ b/src/busctl/meson.build @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +busctl_sources = files( + 'busctl-introspect.c', + 'busctl-introspect.h', + 'busctl.c') + +tests += [ + [files('test-busctl-introspect.c', + 'busctl-introspect.c', + 'busctl-introspect.h')], +] diff --git a/src/busctl/test-busctl-introspect.c b/src/busctl/test-busctl-introspect.c new file mode 100644 index 0000000..859ca71 --- /dev/null +++ b/src/busctl/test-busctl-introspect.c @@ -0,0 +1,364 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "busctl-introspect.h" +#include "set.h" +#include "strv.h" +#include "tests.h" + +static const char *xml_root = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Peer\">\n" + " <method name=\"Ping\"/>\n" + " <method name=\"GetMachineId\">\n" + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Properties\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" + " </method>\n" + " <signal name=\"PropertiesChanged\">\n" + " <arg type=\"s\" name=\"interface\"/>\n" + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" + " </signal>\n" + " </interface>\n" + " <node name=\"org\"/>\n" + "</node>\n"; + +static const char *xml_org = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Peer\">\n" + " <method name=\"Ping\"/>\n" + " <method name=\"GetMachineId\">\n" + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Properties\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" + " </method>\n" + " <signal name=\"PropertiesChanged\">\n" + " <arg type=\"s\" name=\"interface\"/>\n" + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" + " </signal>\n" + " </interface>\n" + " <node name=\"freedesktop\"/>\n" + "</node>\n"; + +static const char *xml_org_freedesktop = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Peer\">\n" + " <method name=\"Ping\"/>\n" + " <method name=\"GetMachineId\">\n" + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Properties\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" + " </method>\n" + " <signal name=\"PropertiesChanged\">\n" + " <arg type=\"s\" name=\"interface\"/>\n" + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" + " </signal>\n" + " </interface>\n" + " <node name=\"LogControl1\"/>\n" + " <node name=\"network1\"/>\n" + "</node>\n"; + +static const char *xml_org_freedesktop_LogControl1 = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Peer\">\n" + " <method name=\"Ping\"/>\n" + " <method name=\"GetMachineId\">\n" + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Properties\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" + " </method>\n" + " <signal name=\"PropertiesChanged\">\n" + " <arg type=\"s\" name=\"interface\"/>\n" + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" + " </signal>\n" + " </interface>\n" + "<interface name=\"org.freedesktop.LogControl1\">\n" + " <property name=\"LogLevel\" type=\"s\" access=\"readwrite\">\n" + " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>\n" + " <annotation name=\"org.freedesktop.systemd1.Privileged\" value=\"true\"/>\n" + " </property>\n" + " <property name=\"LogTarget\" type=\"s\" access=\"readwrite\">\n" + " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>\n" + " <annotation name=\"org.freedesktop.systemd1.Privileged\" value=\"true\"/>\n" + " </property>\n" + " <property name=\"SyslogIdentifier\" type=\"s\" access=\"read\">\n" + " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>\n" + " </property>\n" + " </interface>\n" + "</node>\n"; + +static const char *xml_org_freedesktop_network1 = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Peer\">\n" + " <method name=\"Ping\"/>\n" + " <method name=\"GetMachineId\">\n" + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Properties\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" + " </method>\n" + " <signal name=\"PropertiesChanged\">\n" + " <arg type=\"s\" name=\"interface\"/>\n" + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" + " </signal>\n" + " </interface>\n" + " <node name=\"network\"/>\n" + "</node>\n"; + +static const char *xml_org_freedesktop_network1_network = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Peer\">\n" + " <method name=\"Ping\"/>\n" + " <method name=\"GetMachineId\">\n" + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Properties\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" + " </method>\n" + " <signal name=\"PropertiesChanged\">\n" + " <arg type=\"s\" name=\"interface\"/>\n" + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" + " </signal>\n" + " </interface>\n" + " <node name=\"0\"/>\n" + " <node name=\"1\"/>\n" + " <node name=\"2\"/>\n" + " <node name=\"3\"/>\n" + " <node name=\"4\"/>\n" + " <node name=\"5\"/>\n" + " <node name=\"6\"/>\n" + " <node name=\"7\"/>\n" + " <node name=\"8\"/>\n" + " <node name=\"9\"/>\n" + " <node name=\"10\"/>\n" + " <node name=\"11\"/>\n" + " <node name=\"12\"/>\n" + " <node name=\"13\"/>\n" + " <node name=\"14\"/>\n" + " <node name=\"15\"/>\n" + " <node name=\"16\"/>\n" + " <node name=\"17\"/>\n" + " <node name=\"18\"/>\n" + " <node name=\"19\"/>\n" + " <node name=\"20\"/>\n" + "</node>\n"; + +static const char *xml_org_freedesktop_network1_network_unsigned = + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n\"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Peer\">\n" + " <method name=\"Ping\"/>\n" + " <method name=\"GetMachineId\">\n" + " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus.Properties\">\n" + " <method name=\"Get\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"out\" type=\"v\"/>\n" + " </method>\n" + " <method name=\"GetAll\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" + " </method>\n" + " <method name=\"Set\">\n" + " <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"property\" direction=\"in\" type=\"s\"/>\n" + " <arg name=\"value\" direction=\"in\" type=\"v\"/>\n" + " </method>\n" + " <signal name=\"PropertiesChanged\">\n" + " <arg type=\"s\" name=\"interface\"/>\n" + " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" + " <arg type=\"as\" name=\"invalidated_properties\"/>\n" + " </signal>\n" + " </interface>\n" + " <node name=\"hoge\"/>\n" + "</node>\n"; + +static int on_path(const char *path, void *userdata) { + Set *paths = userdata; + + assert_se(paths); + assert_se(set_put_strdup(&paths, path) >= 0); + + return 0; +} + +TEST(introspect_on_path) { + static const XMLIntrospectOps ops = { + .on_path = on_path, + }; + _cleanup_strv_free_ char **expected = NULL; + _cleanup_set_free_ Set *paths = NULL; + _cleanup_free_ char **l = NULL; + + assert_se(set_put_strdup(&paths, "/") > 0); + + log_debug("/* parse_xml_introspect(\"/\") */"); + assert_se(parse_xml_introspect("/", xml_root, &ops, paths) >= 0); + log_debug("/* parse_xml_introspect(\"/org\") */"); + assert_se(parse_xml_introspect("/org", xml_org, &ops, paths) >= 0); + log_debug("/* parse_xml_introspect(\"/org/freedesktop\") */"); + assert_se(parse_xml_introspect("/org/freedesktop", xml_org_freedesktop, &ops, paths) >= 0); + log_debug("/* parse_xml_introspect(\"/org/freedesktop/LogControl1\") */"); + assert_se(parse_xml_introspect("/org/freedesktop/LogControl1", xml_org_freedesktop_LogControl1, &ops, paths) >= 0); + log_debug("/* parse_xml_introspect(\"/org/freedesktop/network1\") */"); + assert_se(parse_xml_introspect("/org/freedesktop/network1", xml_org_freedesktop_network1, &ops, paths) >= 0); + log_debug("/* parse_xml_introspect(\"/org/freedesktop/network1/network\") */"); + assert_se(parse_xml_introspect("/org/freedesktop/network1/network", xml_org_freedesktop_network1_network, &ops, paths) >= 0); + for (unsigned i = 0; i <= 20; i++) { + _cleanup_free_ char *path = NULL; + + assert_se(asprintf(&path, "/org/freedesktop/network1/network/%u", i) >= 0); + log_debug("/* parse_xml_introspect(\"%s\") */", path); + assert_se(parse_xml_introspect(path, xml_org_freedesktop_network1_network_unsigned, &ops, paths) >= 0); + } + + assert_se(l = set_get_strv(paths)); + strv_sort(l); + + assert_se(strv_extend(&expected, "/") >= 0); + assert_se(strv_extend(&expected, "/org") >= 0); + assert_se(strv_extend(&expected, "/org/freedesktop") >= 0); + assert_se(strv_extend(&expected, "/org/freedesktop/LogControl1") >= 0); + assert_se(strv_extend(&expected, "/org/freedesktop/network1") >= 0); + assert_se(strv_extend(&expected, "/org/freedesktop/network1/network") >= 0); + for (unsigned i = 0; i <= 20; i++) { + assert_se(strv_extendf(&expected, "/org/freedesktop/network1/network/%u", i) >= 0); + assert_se(strv_extendf(&expected, "/org/freedesktop/network1/network/%u/hoge", i) >= 0); + } + + strv_sort(expected); + assert_se(strv_equal(l, expected)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); |