summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSven Eden <sven.eden@prydeworx.com>2018-10-24 07:43:37 +0200
committerSven Eden <sven.eden@prydeworx.com>2018-11-08 08:02:57 +0100
commit3ab9309e18658e22c1322eef168871d26cd49fb3 (patch)
tree0bc4b954e1475b00ee962c46779fda824bb9488d
parent00b618db56b496823d74a0cc61537138ad6cb367 (diff)
Prep v240 : Add busctl to be shipped with elogind.
(emersion): > sd-bus is shipped with elogind, so it makes sense to ship the > busctl command, too. This is not only a nice helper tool to take a closer look at what is happening on the dbus, it will also prove to be usefull if something like issue #59 happens ever again. There we had to use dbus-send directly to dissect the bus traffic. Bug: #86 Closes: #86 Signed-off-by: Sven Eden <sven.eden@prydeworx.com>
-rw-r--r--cb/elogind.cbp15
-rw-r--r--meson.build24
-rw-r--r--src/basic/meson.build2
-rw-r--r--src/basic/string-util.h2
-rw-r--r--src/basic/xml.c238
-rw-r--r--src/basic/xml.h14
-rw-r--r--src/busctl/busctl-introspect.c763
-rw-r--r--src/busctl/busctl-introspect.h14
-rw-r--r--src/busctl/busctl.c2033
-rw-r--r--src/libelogind/meson.build2
-rw-r--r--src/libelogind/sd-bus/bus-dump.c591
-rw-r--r--src/libelogind/sd-bus/bus-dump.h22
-rw-r--r--src/shared/bus-util.c2
13 files changed, 3707 insertions, 15 deletions
diff --git a/cb/elogind.cbp b/cb/elogind.cbp
index 00c0b04..a005d7e 100644
--- a/cb/elogind.cbp
+++ b/cb/elogind.cbp
@@ -429,6 +429,17 @@
<Option compilerVar="CC" />
</Unit>
<Unit filename="../src/basic/xattr-util.h" />
+ <Unit filename="../src/basic/xml.c">
+ <Option compilerVar="CC" />
+ </Unit>
+ <Unit filename="../src/basic/xml.h" />
+ <Unit filename="../src/busctl/busctl-introspect.c">
+ <Option compilerVar="CC" />
+ </Unit>
+ <Unit filename="../src/busctl/busctl-introspect.h" />
+ <Unit filename="../src/busctl/busctl.c">
+ <Option compilerVar="CC" />
+ </Unit>
<Unit filename="../src/cgroups-agent/cgroups-agent.c">
<Option compilerVar="CC" />
<Option target="all" />
@@ -502,6 +513,10 @@
<Option target="all" />
<Option target="clean" />
</Unit>
+ <Unit filename="../src/libelogind/sd-bus/bus-dump.c">
+ <Option compilerVar="CC" />
+ </Unit>
+ <Unit filename="../src/libelogind/sd-bus/bus-dump.h" />
<Unit filename="../src/libelogind/sd-bus/bus-error.c">
<Option compilerVar="CC" />
<Option target="all" />
diff --git a/meson.build b/meson.build
index a0d8666..c5c35df 100644
--- a/meson.build
+++ b/meson.build
@@ -2690,17 +2690,19 @@ executable('elogind-cgroups-agent',
# install_rpath : rootlibexecdir,
# install : true)
# public_programs += [exe]
-#
-# exe = executable('busctl',
-# 'src/busctl/busctl.c',
-# 'src/busctl/busctl-introspect.c',
-# 'src/busctl/busctl-introspect.h',
-# include_directories : includes,
-# link_with : [libshared],
-# install_rpath : rootlibexecdir,
-# install : true)
-# public_programs += [exe]
-#
+#endif // 0
+
+exe = executable('busctl',
+ 'src/busctl/busctl.c',
+ 'src/busctl/busctl-introspect.c',
+ 'src/busctl/busctl-introspect.h',
+ include_directories : includes,
+ link_with : [libshared],
+ install_rpath : rootlibexecdir,
+ install : true)
+public_programs += [exe]
+
+#if 0 /// UNNEEDED by elogind
# if conf.get('ENABLE_SYSUSERS') == 1
# exe = executable('systemd-sysusers',
# 'src/sysusers/sysusers.c',
diff --git a/src/basic/meson.build b/src/basic/meson.build
index af146b5..9b36a1e 100644
--- a/src/basic/meson.build
+++ b/src/basic/meson.build
@@ -367,6 +367,8 @@ basic_sources = files('''
virt.h
xattr-util.c
xattr-util.h
+ xml.c
+ xml.h
'''.split())
#endif // 0
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index 61d0d03..f0f73dd 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -52,11 +52,9 @@ static inline const char *empty_to_null(const char *p) {
return isempty(p) ? NULL : p;
}
-#if 0 /// UNNEEDED by elogind
static inline const char *empty_to_dash(const char *str) {
return isempty(str) ? "-" : str;
}
-#endif // 0
static inline char *startswith(const char *s, const char *prefix) {
size_t l;
diff --git a/src/basic/xml.c b/src/basic/xml.c
new file mode 100644
index 0000000..cb34d87
--- /dev/null
+++ b/src/basic/xml.c
@@ -0,0 +1,238 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "macro.h"
+#include "string-util.h"
+#include "xml.h"
+
+enum {
+ STATE_NULL,
+ STATE_TEXT,
+ STATE_TAG,
+ STATE_ATTRIBUTE,
+};
+
+static void inc_lines(unsigned *line, const char *s, size_t n) {
+ const char *p = s;
+
+ if (!line)
+ return;
+
+ for (;;) {
+ const char *f;
+
+ f = memchr(p, '\n', n);
+ if (!f)
+ return;
+
+ n -= (f - p) + 1;
+ p = f + 1;
+ (*line)++;
+ }
+}
+
+/* We don't actually do real XML here. We only read a simplistic
+ * subset, that is a bit less strict that XML and lacks all the more
+ * complex features, like entities, or namespaces. However, we do
+ * support some HTML5-like simplifications */
+
+int xml_tokenize(const char **p, char **name, void **state, unsigned *line) {
+ const char *c, *e, *b;
+ char *ret;
+ int t;
+
+ assert(p);
+ assert(*p);
+ assert(name);
+ assert(state);
+
+ t = PTR_TO_INT(*state);
+ c = *p;
+
+ if (t == STATE_NULL) {
+ if (line)
+ *line = 1;
+ t = STATE_TEXT;
+ }
+
+ for (;;) {
+ if (*c == 0)
+ return XML_END;
+
+ switch (t) {
+
+ case STATE_TEXT: {
+ int x;
+
+ e = strchrnul(c, '<');
+ if (e > c) {
+ /* More text... */
+ ret = strndup(c, e - c);
+ if (!ret)
+ return -ENOMEM;
+
+ inc_lines(line, c, e - c);
+
+ *name = ret;
+ *p = e;
+ *state = INT_TO_PTR(STATE_TEXT);
+
+ return XML_TEXT;
+ }
+
+ assert(*e == '<');
+ b = c + 1;
+
+ if (startswith(b, "!--")) {
+ /* A comment */
+ e = strstr(b + 3, "-->");
+ if (!e)
+ return -EINVAL;
+
+ inc_lines(line, b, e + 3 - b);
+
+ c = e + 3;
+ continue;
+ }
+
+ if (*b == '?') {
+ /* Processing instruction */
+
+ e = strstr(b + 1, "?>");
+ if (!e)
+ return -EINVAL;
+
+ inc_lines(line, b, e + 2 - b);
+
+ c = e + 2;
+ continue;
+ }
+
+ if (*b == '!') {
+ /* DTD */
+
+ e = strchr(b + 1, '>');
+ if (!e)
+ return -EINVAL;
+
+ inc_lines(line, b, e + 1 - b);
+
+ c = e + 1;
+ continue;
+ }
+
+ if (*b == '/') {
+ /* A closing tag */
+ x = XML_TAG_CLOSE;
+ b++;
+ } else
+ x = XML_TAG_OPEN;
+
+ e = strpbrk(b, WHITESPACE "/>");
+ if (!e)
+ return -EINVAL;
+
+ ret = strndup(b, e - b);
+ if (!ret)
+ return -ENOMEM;
+
+ *name = ret;
+ *p = e;
+ *state = INT_TO_PTR(STATE_TAG);
+
+ return x;
+ }
+
+ case STATE_TAG:
+
+ b = c + strspn(c, WHITESPACE);
+ if (*b == 0)
+ return -EINVAL;
+
+ inc_lines(line, c, b - c);
+
+ e = b + strcspn(b, WHITESPACE "=/>");
+ if (e > b) {
+ /* An attribute */
+
+ ret = strndup(b, e - b);
+ if (!ret)
+ return -ENOMEM;
+
+ *name = ret;
+ *p = e;
+ *state = INT_TO_PTR(STATE_ATTRIBUTE);
+
+ return XML_ATTRIBUTE_NAME;
+ }
+
+ if (startswith(b, "/>")) {
+ /* An empty tag */
+
+ *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */
+ *p = b + 2;
+ *state = INT_TO_PTR(STATE_TEXT);
+
+ return XML_TAG_CLOSE_EMPTY;
+ }
+
+ if (*b != '>')
+ return -EINVAL;
+
+ c = b + 1;
+ t = STATE_TEXT;
+ continue;
+
+ case STATE_ATTRIBUTE:
+
+ if (*c == '=') {
+ c++;
+
+ if (IN_SET(*c, '\'', '\"')) {
+ /* Tag with a quoted value */
+
+ e = strchr(c+1, *c);
+ if (!e)
+ return -EINVAL;
+
+ inc_lines(line, c, e - c);
+
+ ret = strndup(c+1, e - c - 1);
+ if (!ret)
+ return -ENOMEM;
+
+ *name = ret;
+ *p = e + 1;
+ *state = INT_TO_PTR(STATE_TAG);
+
+ return XML_ATTRIBUTE_VALUE;
+
+ }
+
+ /* Tag with a value without quotes */
+
+ b = strpbrk(c, WHITESPACE ">");
+ if (!b)
+ b = c;
+
+ ret = strndup(c, b - c);
+ if (!ret)
+ return -ENOMEM;
+
+ *name = ret;
+ *p = b;
+ *state = INT_TO_PTR(STATE_TAG);
+ return XML_ATTRIBUTE_VALUE;
+ }
+
+ t = STATE_TAG;
+ continue;
+ }
+
+ }
+
+ assert_not_reached("Bad state");
+}
diff --git a/src/basic/xml.h b/src/basic/xml.h
new file mode 100644
index 0000000..8da2ff5
--- /dev/null
+++ b/src/basic/xml.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+enum {
+ XML_END,
+ XML_TEXT,
+ XML_TAG_OPEN,
+ XML_TAG_CLOSE,
+ XML_TAG_CLOSE_EMPTY,
+ XML_ATTRIBUTE_NAME,
+ XML_ATTRIBUTE_VALUE,
+};
+
+int xml_tokenize(const char **p, char **name, void **state, unsigned *line);
diff --git a/src/busctl/busctl-introspect.c b/src/busctl/busctl-introspect.c
new file mode 100644
index 0000000..4af3481
--- /dev/null
+++ b/src/busctl/busctl-introspect.c
@@ -0,0 +1,763 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "busctl-introspect.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) {
+ log_error("Premature end of XML data.");
+ return -EBADMSG;
+ }
+
+ 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 {
+ log_error("Unexpected <annotation> attribute %s.", name);
+ return -EBADMSG;
+ }
+
+ } 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)) {
+ log_error("Unexpected token in <annotation>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(field, name);
+
+ state = STATE_ANNOTATION;
+ } else {
+ log_error("Unexpected token in <annotation>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_VALUE:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(value, name);
+
+ state = STATE_ANNOTATION;
+ } else {
+ log_error("Unexpected token in <annotation>. (3)");
+ return -EINVAL;
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Bad state");
+ }
+ }
+}
+
+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 = prefix;
+ int r;
+
+ assert(context);
+ assert(prefix);
+
+ if (n_depth > NODE_DEPTH_MAX) {
+ log_error("<node> depth too high.");
+ return -EINVAL;
+ }
+
+ 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) {
+ log_error("Premature end of XML data.");
+ return -EBADMSG;
+ }
+
+ switch (state) {
+
+ case STATE_NODE:
+ if (t == XML_ATTRIBUTE_NAME) {
+
+ if (streq_ptr(name, "name"))
+ state = STATE_NODE_NAME;
+ else {
+ log_error("Unexpected <node> attribute %s.", name);
+ return -EBADMSG;
+ }
+
+ } 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 {
+ log_error("Unexpected <node> tag %s.", name);
+ return -EBADMSG;
+ }
+
+ } 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 ? node_path : np, context->userdata);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+
+ } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+ log_error("Unexpected token in <node>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_NODE_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+
+ free(node_path);
+
+ if (name[0] == '/')
+ node_path = TAKE_PTR(name);
+ else {
+
+ if (endswith(prefix, "/"))
+ node_path = strappend(prefix, name);
+ else
+ node_path = strjoin(prefix, "/", name);
+ if (!node_path)
+ return log_oom();
+ }
+
+ np = node_path;
+ state = STATE_NODE;
+ } else {
+ log_error("Unexpected token in <node>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_INTERFACE:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_INTERFACE_NAME;
+ else {
+ log_error("Unexpected <interface> attribute %s.", name);
+ return -EBADMSG;
+ }
+
+ } 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 {
+ log_error("Unexpected <interface> tag %s.", name);
+ return -EINVAL;
+ }
+ } 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)) {
+ log_error("Unexpected token in <interface>. (1)");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected token in <interface>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_METHOD_NAME;
+ else {
+ log_error("Unexpected <method> attribute %s", name);
+ return -EBADMSG;
+ }
+ } 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 {
+ log_error("Unexpected <method> tag %s.", name);
+ return -EINVAL;
+ }
+ } 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)) {
+ log_error("Unexpected token in <method> (1).");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected token in <method> (2).");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected method <arg> attribute %s.", name);
+ return -EBADMSG;
+ }
+ } else if (t == XML_TAG_OPEN) {
+ if (streq_ptr(name, "annotation")) {
+ r = parse_xml_annotation(context, NULL);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected method <arg> tag %s.", name);
+ return -EINVAL;
+ }
+ } 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, NULL))
+ return log_oom();
+ } else if (streq(argument_direction, "out")) {
+ if (!strextend(&context->member_result, argument_type, NULL))
+ 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)) {
+ log_error("Unexpected token in method <arg>. (1)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD_ARG_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE)
+ state = STATE_METHOD_ARG;
+ else {
+ log_error("Unexpected token in method <arg>. (2)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD_ARG_TYPE:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(argument_type, name);
+
+ state = STATE_METHOD_ARG;
+ } else {
+ log_error("Unexpected token in method <arg>. (3)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_METHOD_ARG_DIRECTION:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(argument_direction, name);
+
+ state = STATE_METHOD_ARG;
+ } else {
+ log_error("Unexpected token in method <arg>. (4)");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL:
+
+ if (t == XML_ATTRIBUTE_NAME) {
+ if (streq_ptr(name, "name"))
+ state = STATE_SIGNAL_NAME;
+ else {
+ log_error("Unexpected <signal> attribute %s.", name);
+ return -EBADMSG;
+ }
+ } 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 {
+ log_error("Unexpected <signal> tag %s.", name);
+ return -EINVAL;
+ }
+ } 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)) {
+ log_error("Unexpected token in <signal>. (1)");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected token in <signal>. (2)");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected signal <arg> attribute %s.", name);
+ return -EBADMSG;
+ }
+ } else if (t == XML_TAG_OPEN) {
+ if (streq_ptr(name, "annotation")) {
+ r = parse_xml_annotation(context, NULL);
+ if (r < 0)
+ return r;
+ } else {
+ log_error("Unexpected signal <arg> tag %s.", name);
+ return -EINVAL;
+ }
+ } 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, NULL))
+ 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)) {
+ log_error("Unexpected token in signal <arg> (1).");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL_ARG_NAME:
+
+ if (t == XML_ATTRIBUTE_VALUE)
+ state = STATE_SIGNAL_ARG;
+ else {
+ log_error("Unexpected token in signal <arg> (2).");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL_ARG_TYPE:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(argument_type, name);
+
+ state = STATE_SIGNAL_ARG;
+ } else {
+ log_error("Unexpected token in signal <arg> (3).");
+ return -EINVAL;
+ }
+
+ break;
+
+ case STATE_SIGNAL_ARG_DIRECTION:
+
+ if (t == XML_ATTRIBUTE_VALUE) {
+ free_and_replace(argument_direction, name);
+
+ state = STATE_SIGNAL_ARG;
+ } else {
+ log_error("Unexpected token in signal <arg>. (4)");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected <property> attribute %s.", name);
+ return -EBADMSG;
+ }
+ } 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 {
+ log_error("Unexpected <property> tag %s.", name);
+ return -EINVAL;
+ }
+
+ } 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)) {
+ log_error("Unexpected token in <property>. (1)");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected token in <property>. (2)");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected token in <property>. (3)");
+ return -EINVAL;
+ }
+
+ 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 {
+ log_error("Unexpected token in <property>. (4)");
+ return -EINVAL;
+ }
+
+ 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..b17800e
--- /dev/null
+++ b/src/busctl/busctl-introspect.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.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..e2ae474
--- /dev/null
+++ b/src/busctl/busctl.c
@@ -0,0 +1,2033 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <getopt.h>
+#include <stdio_ext.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "bus-dump.h"
+#include "bus-internal.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 "locale-util.h"
+#include "log.h"
+#include "pager.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "set.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "user-util.h"
+#include "util.h"
+#include "verbs.h"
+
+static bool arg_no_pager = false;
+static bool arg_legend = true;
+static 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_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;
+
+#define NAME_IS_ACQUIRED INT_TO_PTR(1)
+#define NAME_IS_ACTIVATABLE INT_TO_PTR(2)
+
+static int acquire_bus(bool set_monitor, sd_bus **ret) {
+ _cleanup_(sd_bus_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");
+
+ 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 0 /// elogind does not support user buses
+ if (arg_user) {
+ bus->is_user = true;
+ r = bus_set_address_user(bus);
+ } else {
+ bus->is_system = true;
+#endif // 0
+ r = bus_set_address_system(bus);
+#if 0 /// elogind does not support user buses
+ }
+#endif // 0
+ break;
+
+ case BUS_TRANSPORT_REMOTE:
+ r = bus_set_address_system_remote(bus, arg_host);
+ break;
+
+ case BUS_TRANSPORT_MACHINE:
+ r = bus_set_address_system_machine(bus, arg_host);
+ break;
+
+ default:
+ assert_not_reached("Hmm, unknown transport type.");
+ }
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to set address: %m");
+
+ r = sd_bus_start(bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to bus: %m");
+
+ *ret = TAKE_PTR(bus);
+
+ return 0;
+}
+
+static int list_bus_names(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL;
+ _cleanup_free_ char **merged = NULL;
+ _cleanup_hashmap_free_ Hashmap *names = NULL;
+ char **i;
+ int r;
+ size_t max_i = 0;
+ unsigned n = 0;
+ void *v;
+ char *k;
+ Iterator iterator;
+
+ 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");
+
+ (void) pager_open(arg_no_pager, false);
+
+ names = hashmap_new(&string_hash_ops);
+ if (!names)
+ return log_oom();
+
+ STRV_FOREACH(i, acquired) {
+ max_i = MAX(max_i, strlen(*i));
+
+ 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) {
+ max_i = MAX(max_i, strlen(*i));
+
+ r = hashmap_put(names, *i, NAME_IS_ACTIVATABLE);
+ if (r < 0 && r != -EEXIST)
+ return log_error_errno(r, "Failed to add to hashmap: %m");
+ }
+
+ merged = new(char*, hashmap_size(names) + 1);
+ if (!merged)
+ return log_oom();
+
+ HASHMAP_FOREACH_KEY(v, k, names, iterator)
+ merged[n++] = k;
+
+ merged[n] = NULL;
+ strv_sort(merged);
+
+ if (arg_legend) {
+ printf("%-*s %*s %-*s %-*s %-*s %-*s %-*s %-*s",
+ (int) max_i, "NAME", 10, "PID", 15, "PROCESS", 16, "USER", 13, "CONNECTION", 25, "UNIT", 10, "SESSION", 19, "DESCRIPTION");
+
+ if (arg_show_machine)
+ puts(" MACHINE");
+ else
+ putchar('\n');
+ }
+
+ STRV_FOREACH(i, merged) {
+ _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+ sd_id128_t mid;
+
+ if (hashmap_get(names, *i) == NAME_IS_ACTIVATABLE) {
+ /* Activatable */
+
+ printf("%-*s", (int) max_i, *i);
+ printf(" - - - (activatable) - - ");
+ if (arg_show_machine)
+ puts(" -");
+ else
+ putchar('\n');
+ continue;
+
+ }
+
+ if (!arg_unique && (*i)[0] == ':')
+ continue;
+
+ if (!arg_acquired && (*i)[0] != ':')
+ continue;
+
+ printf("%-*s", (int) max_i, *i);
+
+ r = sd_bus_get_name_creds(
+ bus, *i,
+ (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) {
+#if 0 /// elogind does not support systemd units
+ const char *unique, *session, *unit, *cn;
+#else
+ const char *unique, *session, *cn;
+#endif // 0
+ pid_t pid;
+ uid_t uid;
+
+ r = sd_bus_creds_get_pid(creds, &pid);
+ if (r >= 0) {
+ const char *comm = NULL;
+
+ sd_bus_creds_get_comm(creds, &comm);
+
+ printf(" %10lu %-15s", (unsigned long) pid, strna(comm));
+ } else
+ fputs(" - - ", stdout);
+
+ 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();
+
+ if (strlen(u) > 16)
+ u[16] = 0;
+
+ printf(" %-16s", u);
+ } else
+ fputs(" - ", stdout);
+
+ r = sd_bus_creds_get_unique_name(creds, &unique);
+ if (r >= 0)
+ printf(" %-13s", unique);
+ else
+ fputs(" - ", stdout);
+
+#if 0 /// elogind does not support systemd units
+ r = sd_bus_creds_get_unit(creds, &unit);
+ if (r >= 0) {
+ _cleanup_free_ char *e;
+
+ e = ellipsize(unit, 25, 100);
+ if (!e)
+ return log_oom();
+
+ printf(" %-25s", e);
+ } else
+ fputs(" - ", stdout);
+#endif // 0
+
+ r = sd_bus_creds_get_session(creds, &session);
+ if (r >= 0)
+ printf(" %-10s", session);
+ else
+ fputs(" - ", stdout);
+
+ r = sd_bus_creds_get_description(creds, &cn);
+ if (r >= 0)
+ printf(" %-19s", cn);
+ else
+ fputs(" - ", stdout);
+
+ } else
+ printf(" - - - - - - - ");
+
+ if (arg_show_machine) {
+ r = sd_bus_get_name_machine_id(bus, *i, &mid);
+ if (r >= 0) {
+ char m[SD_ID128_STRING_MAX];
+ printf(" %s\n", sd_id128_to_string(mid, m));
+ } else
+ puts(" -");
+ } else
+ putchar('\n');
+ }
+
+ return 0;
+}
+
+static void print_subtree(const char *prefix, const char *path, char **l) {
+ const char *vertical, *space;
+ char **n;
+
+ /* 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++;
+ }
+
+ vertical = strjoina(prefix, special_glyph(TREE_VERTICAL));
+ space = strjoina(prefix, special_glyph(TREE_SPACE));
+
+ for (;;) {
+ bool has_more = false;
+
+ 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 ? TREE_BRANCH : TREE_RIGHT), *l);
+
+ print_subtree(has_more ? vertical : space, *l, l);
+ l = n;
+ }
+}
+
+static void print_tree(const char *prefix, char **l) {
+
+ prefix = strempty(prefix);
+
+ if (arg_list) {
+ char **i;
+
+ STRV_FOREACH(i, l)
+ printf("%s%s\n", prefix, *i);
+ return;
+ }
+
+ if (strv_isempty(l)) {
+ printf("No objects discovered.\n");
+ return;
+ }
+
+ if (streq(l[0], "/") && !l[1]) {
+ printf("Only root object discovered.\n");
+ return;
+ }
+
+ print_subtree(prefix, "/", l);
+}
+
+static int on_path(const char *path, void *userdata) {
+ Set *paths = userdata;
+ int r;
+
+ assert(paths);
+
+ 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, bool many) {
+ 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, "");
+ if (r < 0) {
+ if (many)
+ printf("Failed to introspect object %s of service %s: %s\n", path, service, bus_error_message(&error, r));
+ else
+ log_error_errno(r, "Failed to introspect object %s of service %s: %s", path, service, bus_error_message(&error, r));
+ 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, const char *prefix, bool many) {
+ _cleanup_set_free_free_ Set *paths = NULL, *done = NULL, *failed = NULL;
+ _cleanup_free_ char **l = NULL;
+ char *m;
+ int r;
+
+ paths = set_new(&string_hash_ops);
+ if (!paths)
+ return log_oom();
+
+ done = set_new(&string_hash_ops);
+ if (!done)
+ return log_oom();
+
+ failed = set_new(&string_hash_ops);
+ if (!failed)
+ return log_oom();
+
+ m = strdup("/");
+ if (!m)
+ return log_oom();
+
+ r = set_put(paths, m);
+ if (r < 0) {
+ free(m);
+ 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, many);
+ if (q < 0) {
+ if (r >= 0)
+ r = q;
+
+ q = set_put(failed, p);
+ } else
+ q = set_put(done, p);
+
+ if (q < 0)
+ return log_oom();
+
+ assert(q != 0);
+ p = NULL;
+ }
+
+ (void) pager_open(arg_no_pager, false);
+
+ l = set_get_strv(done);
+ if (!l)
+ return log_oom();
+
+ strv_sort(l);
+ print_tree(prefix, l);
+
+ fflush(stdout);
+
+ return r;
+}
+
+static int tree(int argc, char **argv, void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ char **i;
+ int r = 0;
+
+ 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");
+
+ (void) pager_open(arg_no_pager, false);
+
+ 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, NULL, true);
+ if (q < 0 && r >= 0)
+ r = q;
+
+ not_first = true;
+ }
+ } else {
+ STRV_FOREACH(i, argv+1) {
+ int q;
+
+ if (i > argv+1)
+ printf("\n");
+
+ if (argv[2]) {
+ (void) pager_open(arg_no_pager, false);
+ printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal());
+ }
+
+ q = tree_one(bus, *i, NULL, !!argv[2]);
+ 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("Unknown basic type.");
+ }
+
+ 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 void *p, struct siphash *state) {
+ const Member *m = p;
+ 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->interface)
+ string_hash_func(m->interface, state);
+}
+
+static int member_compare_func(const void *a, const void *b) {
+ const Member *x = a, *y = b;
+ 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;
+
+ return strcmp_ptr(x->name, y->name);
+}
+
+static int member_compare_funcp(const void *a, const void *b) {
+ const Member *const * x = (const Member *const *) a, * const *y = (const Member *const *) b;
+
+ return member_compare_func(*x, *y);
+}
+
+static void member_free(Member *m) {
+ if (!m)
+ return;
+
+ free(m->interface);
+ free(m->name);
+ free(m->signature);
+ free(m->result);
+ free(m->value);
+ free(m);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Member*, member_free);
+
+static void member_set_free(Set *s) {
+ 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;
+ Set *members = userdata;
+ int r;
+
+ assert(interface);
+ assert(members);
+
+ m = new0(Member, 1);
+ if (!m)
+ return log_oom();
+
+ m->type = "interface";
+ m->flags = flags;
+
+ r = free_and_strdup(&m->interface, interface);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(members, m);
+ if (r <= 0) {
+ log_error("Duplicate interface");
+ return -EINVAL;
+ }
+
+ 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;
+ Set *members = userdata;
+ int r;
+
+ assert(interface);
+ assert(name);
+
+ m = new0(Member, 1);
+ if (!m)
+ return log_oom();
+
+ m->type = "method";
+ m->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) {
+ log_error("Duplicate method");
+ return -EINVAL;
+ }
+
+ 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;
+ Set *members = userdata;
+ int r;
+
+ assert(interface);
+ assert(name);
+
+ m = new0(Member, 1);
+ if (!m)
+ return log_oom();
+
+ m->type = "signal";
+ m->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) {
+ log_error("Duplicate signal");
+ return -EINVAL;
+ }
+
+ 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;
+ Set *members = userdata;
+ int r;
+
+ assert(interface);
+ assert(name);
+
+ m = new0(Member, 1);
+ if (!m)
+ return log_oom();
+
+ m->type = "property";
+ m->flags = flags;
+ m->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) {
+ log_error("Duplicate property");
+ return -EINVAL;
+ }
+
+ m = NULL;
+ return 0;
+}
+
+static int introspect(int argc, char **argv, void *userdata) {
+ static const struct hash_ops member_hash_ops = {
+ .hash = member_hash_func,
+ .compare = member_compare_func,
+ };
+
+ 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, j, k = 0;
+ Member *m, **sorted = NULL;
+ Iterator i;
+ 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, "");
+ 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);
+
+ /* 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, i) {
+ _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, "%s", 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;
+ _cleanup_fclose_ FILE *mf = NULL;
+ size_t sz = 0;
+ const char *name;
+
+ 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);
+
+ mf = open_memstream(&buf, &sz);
+ if (!mf)
+ return log_oom();
+
+ (void) __fsetlocking(mf, FSETLOCKING_BYCALLER);
+
+ 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,
+ .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);
+ }
+
+ (void) pager_open(arg_no_pager, false);
+
+ name_width = STRLEN("NAME");
+ type_width = STRLEN("TYPE");
+ signature_width = STRLEN("SIGNATURE");
+ result_width = STRLEN("RESULT/VALUE");
+
+ sorted = newa(Member*, set_size(members));
+
+ SET_FOREACH(m, members, i) {
+
+ 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)
+ result_width = 40;
+
+ qsort(sorted, k, sizeof(Member*), member_compare_funcp);
+
+ 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 (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 bus_message_dump(m, f, 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 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;
+ char **i;
+ 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 (!service_name_is_valid(*i)) {
+ log_error("Invalid service name '%s'", *i);
+ return -EINVAL;
+ }
+
+ 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, "%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 (!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 log_error_errno(r, "Failed to read lost name: %m");
+
+ if (streq(name, unique_name))
+ is_monitor = true;
+
+ continue;
+ }
+
+ if (m) {
+ 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_t) -1);
+ 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, message_dump);
+}
+
+static int verb_capture(int argc, char **argv, void *userdata) {
+ int r;
+
+ if (isatty(fileno(stdout)) > 0) {
+ log_error("Refusing to write message data to console, please redirect output to a file.");
+ return -EINVAL;
+ }
+
+ bus_pcap_header(arg_snaplen, 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;
+
+ 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) {
+ log_error("Too few parameters for signature.");
+ return -EINVAL;
+ }
+
+ signature++;
+ p++;
+
+ switch (t) {
+
+ case SD_BUS_TYPE_BOOLEAN:
+
+ r = parse_boolean(v);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse as boolean: %s", 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 as byte (unsigned 8bit integer): %s", 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 as signed 16bit integer: %s", 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 as unsigned 16bit integer: %s", 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 as signed 32bit integer: %s", 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 as unsigned 32bit integer: %s", 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 as signed 64bit integer: %s", 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 as unsigned 64bit integer: %s", 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 as double precision floating point: %s", 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 number of array entries: %s", v);
+
+ r = signature_element_length(signature, &k);
+ if (r < 0)
+ return log_error_errno(r, "Invalid array signature.");
+
+ {
+ unsigned i;
+ 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 (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)
+ return log_error_errno(r, "Invalid struct/dict entry signature.");
+
+ {
+ char s[k-1];
+ memcpy(s, signature + 1, k - 2);
+ s[k - 2] = 0;
+
+ r = sd_bus_message_open_container(m, t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, 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:
+ log_error("UNIX file descriptor not supported as type.");
+ return -EINVAL;
+
+ default:
+ log_error("Unknown signature type %c.", t);
+ return -EINVAL;
+ }
+
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ *x = p;
+ 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) {
+ log_error("Too many parameters for signature.");
+ return -EINVAL;
+ }
+ }
+
+ 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, "%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 (arg_verbose) {
+ (void) pager_open(arg_no_pager, false);
+
+ r = 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 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;
+ char **i;
+ 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, "%s", 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 (arg_verbose) {
+ (void) pager_open(arg_no_pager, false);
+
+ r = bus_message_dump(reply, stdout, 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) {
+ log_error("Too many parameters for signature.");
+ return -EINVAL;
+ }
+
+ r = sd_bus_call(bus, m, arg_timeout, &error, NULL);
+ if (r < 0)
+ return log_error_errno(r, "%s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int help(void) {
+ printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+ "Introspect the bus.\n\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"
+ " --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"
+ " --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\n"
+ "Commands:\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"
+ " 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"
+ , program_invocation_short_name);
+
+ 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_EXPECT_REPLY,
+ ARG_AUTO_START,
+ ARG_ALLOW_INTERACTIVE_AUTHORIZATION,
+ ARG_TIMEOUT,
+ ARG_AUGMENT_CREDS,
+ ARG_WATCH_BIND,
+ };
+
+ 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 },
+ { "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 },
+ { "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 },
+ {},
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hH:M:q", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_NO_PAGER:
+ arg_no_pager = true;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ 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", optarg);
+
+ if ((uint64_t) (size_t) sz != sz) {
+ log_error("Size out of range.");
+ return -E2BIG;
+ }
+
+ 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_EXPECT_REPLY:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --expect-reply= parameter: %s", optarg);
+
+ arg_expect_reply = r;
+ break;
+
+ case ARG_AUTO_START:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --auto-start= parameter: %s", optarg);
+
+ arg_auto_start = r;
+ break;
+
+ case ARG_ALLOW_INTERACTIVE_AUTHORIZATION:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --allow-interactive-authorization= parameter: %s", optarg);
+
+ arg_allow_interactive_authorization = 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", optarg);
+
+ break;
+
+ case ARG_AUGMENT_CREDS:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --augment-creds= parameter: %s", optarg);
+
+ arg_augment_creds = r;
+ break;
+
+ case ARG_WATCH_BIND:
+ r = parse_boolean(optarg);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --watch-bind= parameter: %s", optarg);
+
+ arg_watch_bind = r;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ 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 },
+ { "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);
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ r = busctl_main(argc, argv);
+
+finish:
+ /* make sure we terminate the bus connection first, and then close the
+ * pager, see issue #3543 for the details. */
+ pager_close();
+
+ arg_matches = strv_free(arg_matches);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/libelogind/meson.build b/src/libelogind/meson.build
index 98e19f9..8a3f8a7 100644
--- a/src/libelogind/meson.build
+++ b/src/libelogind/meson.build
@@ -92,6 +92,8 @@ libelogind_sources = files('''
sd-bus/bus-convenience.c
sd-bus/bus-creds.c
sd-bus/bus-creds.h
+ sd-bus/bus-dump.c
+ sd-bus/bus-dump.h
sd-bus/bus-error.c
sd-bus/bus-error.h
sd-bus/bus-gvariant.c
diff --git a/src/libelogind/sd-bus/bus-dump.c b/src/libelogind/sd-bus/bus-dump.c
new file mode 100644
index 0000000..635403b
--- /dev/null
+++ b/src/libelogind/sd-bus/bus-dump.c
@@ -0,0 +1,591 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+***/
+
+#include "alloc-util.h"
+#include "bus-dump.h"
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "bus-type.h"
+#include "cap-list.h"
+#include "capability-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "locale-util.h"
+#include "macro.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "util.h"
+
+static char *indent(unsigned level, unsigned flags) {
+ char *p;
+ unsigned n, i = 0;
+
+ n = 0;
+
+ if (flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0)
+ level -= 1;
+
+ if (flags & BUS_MESSAGE_DUMP_WITH_HEADER)
+ n += 2;
+
+ p = new(char, n + level*8 + 1);
+ if (!p)
+ return NULL;
+
+ if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) {
+ p[i++] = ' ';
+ p[i++] = ' ';
+ }
+
+ memset(p + i, ' ', level*8);
+ p[i + level*8] = 0;
+
+ return p;
+}
+
+int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) {
+ unsigned level = 1;
+ int r;
+
+ assert(m);
+
+ if (!f)
+ f = stdout;
+
+ if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) {
+ fprintf(f,
+ "%s%s%s Type=%s%s%s Endian=%c Flags=%u Version=%u Priority=%"PRIi64,
+ m->header->type == SD_BUS_MESSAGE_METHOD_ERROR ? ansi_highlight_red() :
+ m->header->type == SD_BUS_MESSAGE_METHOD_RETURN ? ansi_highlight_green() :
+ m->header->type != SD_BUS_MESSAGE_SIGNAL ? ansi_highlight() : "", special_glyph(TRIANGULAR_BULLET), ansi_normal(),
+ ansi_highlight(), bus_message_type_to_string(m->header->type), ansi_normal(),
+ m->header->endian,
+ m->header->flags,
+ m->header->version,
+ m->priority);
+
+ /* Display synthetic message serial number in a more readable
+ * format than (uint32_t) -1 */
+ if (BUS_MESSAGE_COOKIE(m) == 0xFFFFFFFFULL)
+ fprintf(f, " Cookie=-1");
+ else
+ fprintf(f, " Cookie=%" PRIu64, BUS_MESSAGE_COOKIE(m));
+
+ if (m->reply_cookie != 0)
+ fprintf(f, " ReplyCookie=%" PRIu64, m->reply_cookie);
+
+ fputs("\n", f);
+
+ if (m->sender)
+ fprintf(f, " Sender=%s%s%s", ansi_highlight(), m->sender, ansi_normal());
+ if (m->destination)
+ fprintf(f, " Destination=%s%s%s", ansi_highlight(), m->destination, ansi_normal());
+ if (m->path)
+ fprintf(f, " Path=%s%s%s", ansi_highlight(), m->path, ansi_normal());
+ if (m->interface)
+ fprintf(f, " Interface=%s%s%s", ansi_highlight(), m->interface, ansi_normal());
+ if (m->member)
+ fprintf(f, " Member=%s%s%s", ansi_highlight(), m->member, ansi_normal());
+
+ if (m->sender || m->destination || m->path || m->interface || m->member)
+ fputs("\n", f);
+
+ if (sd_bus_error_is_set(&m->error))
+ fprintf(f,
+ " ErrorName=%s%s%s"
+ " ErrorMessage=%s\"%s\"%s\n",
+ ansi_highlight_red(), strna(m->error.name), ansi_normal(),
+ ansi_highlight_red(), strna(m->error.message), ansi_normal());
+
+ if (m->monotonic != 0)
+ fprintf(f, " Monotonic="USEC_FMT, m->monotonic);
+ if (m->realtime != 0)
+ fprintf(f, " Realtime="USEC_FMT, m->realtime);
+ if (m->seqnum != 0)
+ fprintf(f, " SequenceNumber=%"PRIu64, m->seqnum);
+
+ if (m->monotonic != 0 || m->realtime != 0 || m->seqnum != 0)
+ fputs("\n", f);
+
+ bus_creds_dump(&m->creds, f, true);
+ }
+
+ r = sd_bus_message_rewind(m, !(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY));
+ if (r < 0)
+ return log_error_errno(r, "Failed to rewind: %m");
+
+ if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) {
+ _cleanup_free_ char *prefix = NULL;
+
+ prefix = indent(0, flags);
+ if (!prefix)
+ return log_oom();
+
+ fprintf(f, "%sMESSAGE \"%s\" {\n", prefix, strempty(m->root_container.signature));
+ }
+
+ for (;;) {
+ _cleanup_free_ char *prefix = NULL;
+ 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 log_error_errno(r, "Failed to peek type: %m");
+
+ if (r == 0) {
+ if (level <= 1)
+ break;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to exit container: %m");
+
+ level--;
+
+ prefix = indent(level, flags);
+ if (!prefix)
+ return log_oom();
+
+ fprintf(f, "%s};\n", prefix);
+ continue;
+ }
+
+ prefix = indent(level, flags);
+ if (!prefix)
+ return log_oom();
+
+ if (bus_type_is_container(type) > 0) {
+ r = sd_bus_message_enter_container(m, type, contents);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enter container: %m");
+
+ if (type == SD_BUS_TYPE_ARRAY)
+ fprintf(f, "%sARRAY \"%s\" {\n", prefix, contents);
+ else if (type == SD_BUS_TYPE_VARIANT)
+ fprintf(f, "%sVARIANT \"%s\" {\n", prefix, contents);
+ else if (type == SD_BUS_TYPE_STRUCT)
+ fprintf(f, "%sSTRUCT \"%s\" {\n", prefix, contents);
+ else if (type == SD_BUS_TYPE_DICT_ENTRY)
+ fprintf(f, "%sDICT_ENTRY \"%s\" {\n", prefix, contents);
+
+ level++;
+
+ continue;
+ }
+
+ r = sd_bus_message_read_basic(m, type, &basic);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get basic: %m");
+
+ assert(r > 0);
+
+ switch (type) {
+
+ case SD_BUS_TYPE_BYTE:
+ fprintf(f, "%sBYTE %s%u%s;\n", prefix, ansi_highlight(), basic.u8, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_BOOLEAN:
+ fprintf(f, "%sBOOLEAN %s%s%s;\n", prefix, ansi_highlight(), true_false(basic.i), ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_INT16:
+ fprintf(f, "%sINT16 %s%i%s;\n", prefix, ansi_highlight(), basic.s16, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_UINT16:
+ fprintf(f, "%sUINT16 %s%u%s;\n", prefix, ansi_highlight(), basic.u16, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_INT32:
+ fprintf(f, "%sINT32 %s%i%s;\n", prefix, ansi_highlight(), basic.s32, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_UINT32:
+ fprintf(f, "%sUINT32 %s%u%s;\n", prefix, ansi_highlight(), basic.u32, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_INT64:
+ fprintf(f, "%sINT64 %s%"PRIi64"%s;\n", prefix, ansi_highlight(), basic.s64, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_UINT64:
+ fprintf(f, "%sUINT64 %s%"PRIu64"%s;\n", prefix, ansi_highlight(), basic.u64, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_DOUBLE:
+ fprintf(f, "%sDOUBLE %s%g%s;\n", prefix, ansi_highlight(), basic.d64, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_STRING:
+ fprintf(f, "%sSTRING \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_OBJECT_PATH:
+ fprintf(f, "%sOBJECT_PATH \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_SIGNATURE:
+ fprintf(f, "%sSIGNATURE \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal());
+ break;
+
+ case SD_BUS_TYPE_UNIX_FD:
+ fprintf(f, "%sUNIX_FD %s%i%s;\n", prefix, ansi_highlight(), basic.i, ansi_normal());
+ break;
+
+ default:
+ assert_not_reached("Unknown basic type.");
+ }
+ }
+
+ if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) {
+ _cleanup_free_ char *prefix = NULL;
+
+ prefix = indent(0, flags);
+ if (!prefix)
+ return log_oom();
+
+ fprintf(f, "%s};\n\n", prefix);
+ }
+
+ return 0;
+}
+
+static void dump_capabilities(
+ sd_bus_creds *c,
+ FILE *f,
+ const char *name,
+ bool terse,
+ int (*has)(sd_bus_creds *c, int capability)) {
+
+ unsigned long i, last_cap;
+ unsigned n = 0;
+ int r;
+
+ assert(c);
+ assert(f);
+ assert(name);
+ assert(has);
+
+ i = 0;
+ r = has(c, i);
+ if (r < 0)
+ return;
+
+ fprintf(f, "%s%s=%s", terse ? " " : "", name, terse ? "" : ansi_highlight());
+ last_cap = cap_last_cap();
+
+ for (;;) {
+ if (r > 0) {
+
+ if (n > 0)
+ fputc(' ', f);
+ if (n % 4 == 3)
+ fprintf(f, terse ? "\n " : "\n ");
+
+ fprintf(f, "%s", strna(capability_to_name(i)));
+ n++;
+ }
+
+ i++;
+
+ if (i > last_cap)
+ break;
+
+ r = has(c, i);
+ }
+
+ fputs("\n", f);
+
+ if (!terse)
+ fputs(ansi_normal(), f);
+}
+
+int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) {
+ uid_t owner, audit_loginuid;
+ uint32_t audit_sessionid;
+ char **cmdline = NULL, **well_known = NULL;
+ const char *prefix, *color, *suffix, *s;
+ int r, q, v, w, z;
+
+ assert(c);
+
+ if (!f)
+ f = stdout;
+
+ if (terse) {
+ prefix = " ";
+ suffix = "";
+ color = "";
+ } else {
+ const char *off;
+
+ prefix = "";
+ color = ansi_highlight();
+
+ off = ansi_normal();
+ suffix = strjoina(off, "\n");
+ }
+
+ if (c->mask & SD_BUS_CREDS_PID)
+ fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix);
+ if (c->mask & SD_BUS_CREDS_TID)
+ fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix);
+ if (c->mask & SD_BUS_CREDS_PPID) {
+ if (c->ppid == 0)
+ fprintf(f, "%sPPID=%sn/a%s", prefix, color, suffix);
+ else
+ fprintf(f, "%sPPID=%s"PID_FMT"%s", prefix, color, c->ppid, suffix);
+ }
+ if (c->mask & SD_BUS_CREDS_TTY)
+ fprintf(f, "%sTTY=%s%s%s", prefix, color, strna(c->tty), suffix);
+
+ if (terse && ((c->mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID|SD_BUS_CREDS_TTY))))
+ fputs("\n", f);
+
+ if (c->mask & SD_BUS_CREDS_UID)
+ fprintf(f, "%sUID=%s"UID_FMT"%s", prefix, color, c->uid, suffix);
+ if (c->mask & SD_BUS_CREDS_EUID)
+ fprintf(f, "%sEUID=%s"UID_FMT"%s", prefix, color, c->euid, suffix);
+ if (c->mask & SD_BUS_CREDS_SUID)
+ fprintf(f, "%sSUID=%s"UID_FMT"%s", prefix, color, c->suid, suffix);
+ if (c->mask & SD_BUS_CREDS_FSUID)
+ fprintf(f, "%sFSUID=%s"UID_FMT"%s", prefix, color, c->fsuid, suffix);
+ r = sd_bus_creds_get_owner_uid(c, &owner);
+ if (r >= 0)
+ fprintf(f, "%sOwnerUID=%s"UID_FMT"%s", prefix, color, owner, suffix);
+ if (c->mask & SD_BUS_CREDS_GID)
+ fprintf(f, "%sGID=%s"GID_FMT"%s", prefix, color, c->gid, suffix);
+ if (c->mask & SD_BUS_CREDS_EGID)
+ fprintf(f, "%sEGID=%s"GID_FMT"%s", prefix, color, c->egid, suffix);
+ if (c->mask & SD_BUS_CREDS_SGID)
+ fprintf(f, "%sSGID=%s"GID_FMT"%s", prefix, color, c->sgid, suffix);
+ if (c->mask & SD_BUS_CREDS_FSGID)
+ fprintf(f, "%sFSGID=%s"GID_FMT"%s", prefix, color, c->fsgid, suffix);
+
+ if (c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) {
+ unsigned i;
+
+ fprintf(f, "%sSupplementaryGIDs=%s", prefix, color);
+ for (i = 0; i < c->n_supplementary_gids; i++)
+ fprintf(f, "%s" GID_FMT, i > 0 ? " " : "", c->supplementary_gids[i]);
+ fprintf(f, "%s", suffix);
+ }
+
+ if (terse && ((c->mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID|
+ SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID|
+ SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) || r >= 0))
+ fputs("\n", f);
+
+ if (c->mask & SD_BUS_CREDS_COMM)
+ fprintf(f, "%sComm=%s%s%s", prefix, color, c->comm, suffix);
+ if (c->mask & SD_BUS_CREDS_TID_COMM)
+ fprintf(f, "%sTIDComm=%s%s%s", prefix, color, c->tid_comm, suffix);
+ if (c->mask & SD_BUS_CREDS_EXE)
+ fprintf(f, "%sExe=%s%s%s", prefix, color, strna(c->exe), suffix);
+
+ if (terse && (c->mask & (SD_BUS_CREDS_EXE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM)))
+ fputs("\n", f);
+
+ r = sd_bus_creds_get_cmdline(c, &cmdline);
+ if (r >= 0) {
+ char **i;
+
+ fprintf(f, "%sCommandLine=%s", prefix, color);
+ STRV_FOREACH(i, cmdline) {
+ if (i != cmdline)
+ fputc(' ', f);
+
+ fputs(*i, f);
+ }
+
+ fprintf(f, "%s", suffix);
+ } else if (r != -ENODATA)
+ fprintf(f, "%sCommandLine=%sn/a%s", prefix, color, suffix);
+
+ if (c->mask & SD_BUS_CREDS_SELINUX_CONTEXT)
+ fprintf(f, "%sLabel=%s%s%s", prefix, color, c->label, suffix);
+ if (c->mask & SD_BUS_CREDS_DESCRIPTION)
+ fprintf(f, "%sDescription=%s%s%s", prefix, color, c->description, suffix);
+
+ if (terse && (c->mask & (SD_BUS_CREDS_SELINUX_CONTEXT|SD_BUS_CREDS_DESCRIPTION)))
+ fputs("\n", f);
+
+ if (c->mask & SD_BUS_CREDS_CGROUP)
+ fprintf(f, "%sCGroup=%s%s%s", prefix, color, c->cgroup, suffix);
+ s = NULL;
+#if 0 /// elogind does not support systemd units
+ r = sd_bus_creds_get_unit(c, &s);
+ if (r != -ENODATA)
+ fprintf(f, "%sUnit=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+#endif // 0
+ v = sd_bus_creds_get_slice(c, &s);
+ if (v != -ENODATA)
+ fprintf(f, "%sSlice=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+#if 0 /// elogind does not support systemd units
+ q = sd_bus_creds_get_user_unit(c, &s);
+ if (q != -ENODATA)
+ fprintf(f, "%sUserUnit=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+#endif // 0
+ w = sd_bus_creds_get_user_slice(c, &s);
+ if (w != -ENODATA)
+ fprintf(f, "%sUserSlice=%s%s%s", prefix, color, strna(s), suffix);
+ s = NULL;
+ z = sd_bus_creds_get_session(c, &s);
+ if (z != -ENODATA)
+ fprintf(f, "%sSession=%s%s%s", prefix, color, strna(s), suffix);
+
+ if (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || r != -ENODATA || q != -ENODATA || v != -ENODATA || w != -ENODATA || z != -ENODATA))
+ fputs("\n", f);
+
+ r = sd_bus_creds_get_audit_login_uid(c, &audit_loginuid);
+ if (r >= 0)
+ fprintf(f, "%sAuditLoginUID=%s"UID_FMT"%s", prefix, color, audit_loginuid, suffix);
+ else if (r != -ENODATA)
+ fprintf(f, "%sAuditLoginUID=%sn/a%s", prefix, color, suffix);
+ q = sd_bus_creds_get_audit_session_id(c, &audit_sessionid);
+ if (q >= 0)
+ fprintf(f, "%sAuditSessionID=%s%"PRIu32"%s", prefix, color, audit_sessionid, suffix);
+ else if (q != -ENODATA)
+ fprintf(f, "%sAuditSessionID=%sn/a%s", prefix, color, suffix);
+
+ if (terse && (r != -ENODATA || q != -ENODATA))
+ fputs("\n", f);
+
+ if (c->mask & SD_BUS_CREDS_UNIQUE_NAME)
+ fprintf(f, "%sUniqueName=%s%s%s", prefix, color, c->unique_name, suffix);
+
+ if (sd_bus_creds_get_well_known_names(c, &well_known) >= 0) {
+ char **i;
+
+ fprintf(f, "%sWellKnownNames=%s", prefix, color);
+ STRV_FOREACH(i, well_known) {
+ if (i != well_known)
+ fputc(' ', f);
+
+ fputs(*i, f);
+ }
+
+ fprintf(f, "%s", suffix);
+ }
+
+ if (terse && (c->mask & SD_BUS_CREDS_UNIQUE_NAME || well_known))
+ fputc('\n', f);
+
+ dump_capabilities(c, f, "EffectiveCapabilities", terse, sd_bus_creds_has_effective_cap);
+ dump_capabilities(c, f, "PermittedCapabilities", terse, sd_bus_creds_has_permitted_cap);
+ dump_capabilities(c, f, "InheritableCapabilities", terse, sd_bus_creds_has_inheritable_cap);
+ dump_capabilities(c, f, "BoundingCapabilities", terse, sd_bus_creds_has_bounding_cap);
+
+ return 0;
+}
+
+/*
+ * For details about the file format, see:
+ *
+ * http://wiki.wireshark.org/Development/LibpcapFileFormat
+ */
+
+typedef struct _packed_ pcap_hdr_s {
+ uint32_t magic_number; /* magic number */
+ uint16_t version_major; /* major version number */
+ uint16_t version_minor; /* minor version number */
+ int32_t thiszone; /* GMT to local correction */
+ uint32_t sigfigs; /* accuracy of timestamps */
+ uint32_t snaplen; /* max length of captured packets, in octets */
+ uint32_t network; /* data link type */
+} pcap_hdr_t ;
+
+typedef struct _packed_ pcaprec_hdr_s {
+ uint32_t ts_sec; /* timestamp seconds */
+ uint32_t ts_usec; /* timestamp microseconds */
+ uint32_t incl_len; /* number of octets of packet saved in file */
+ uint32_t orig_len; /* actual length of packet */
+} pcaprec_hdr_t;
+
+int bus_pcap_header(size_t snaplen, FILE *f) {
+
+ pcap_hdr_t hdr = {
+ .magic_number = 0xa1b2c3d4U,
+ .version_major = 2,
+ .version_minor = 4,
+ .thiszone = 0, /* UTC */
+ .sigfigs = 0,
+ .network = 231, /* D-Bus */
+ };
+
+ if (!f)
+ f = stdout;
+
+ assert(snaplen > 0);
+ assert((size_t) (uint32_t) snaplen == snaplen);
+
+ hdr.snaplen = (uint32_t) snaplen;
+
+ fwrite(&hdr, 1, sizeof(hdr), f);
+
+ return fflush_and_check(f);
+}
+
+int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) {
+ struct bus_body_part *part;
+ pcaprec_hdr_t hdr = {};
+ struct timeval tv;
+ unsigned i;
+ size_t w;
+
+ if (!f)
+ f = stdout;
+
+ assert(m);
+ assert(snaplen > 0);
+ assert((size_t) (uint32_t) snaplen == snaplen);
+
+ if (m->realtime != 0)
+ timeval_store(&tv, m->realtime);
+ else
+ assert_se(gettimeofday(&tv, NULL) >= 0);
+
+ hdr.ts_sec = tv.tv_sec;
+ hdr.ts_usec = tv.tv_usec;
+ hdr.orig_len = BUS_MESSAGE_SIZE(m);
+ hdr.incl_len = MIN(hdr.orig_len, snaplen);
+
+ /* write the pcap header */
+ fwrite(&hdr, 1, sizeof(hdr), f);
+
+ /* write the dbus header */
+ w = MIN(BUS_MESSAGE_BODY_BEGIN(m), snaplen);
+ fwrite(m->header, 1, w, f);
+ snaplen -= w;
+
+ /* write the dbus body */
+ MESSAGE_FOREACH_PART(part, i, m) {
+ if (snaplen <= 0)
+ break;
+
+ w = MIN(part->size, snaplen);
+ fwrite(part->data, 1, w, f);
+ snaplen -= w;
+ }
+
+ return fflush_and_check(f);
+}
diff --git a/src/libelogind/sd-bus/bus-dump.h b/src/libelogind/sd-bus/bus-dump.h
new file mode 100644
index 0000000..8e47411
--- /dev/null
+++ b/src/libelogind/sd-bus/bus-dump.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/***
+***/
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "sd-bus.h"
+
+enum {
+ BUS_MESSAGE_DUMP_WITH_HEADER = 1,
+ BUS_MESSAGE_DUMP_SUBTREE_ONLY = 2,
+};
+
+int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags);
+
+int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse);
+
+int bus_pcap_header(size_t snaplen, FILE *f);
+int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f);
diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c
index 641a470..7c37648 100644
--- a/src/shared/bus-util.c
+++ b/src/shared/bus-util.c
@@ -1484,12 +1484,10 @@ int bus_log_parse_error(int r) {
return log_error_errno(r, "Failed to parse bus message: %m");
}
-#if 0 /// UNNEEDED by elogind
int bus_log_create_error(int r) {
return log_error_errno(r, "Failed to create bus message: %m");
}
-#endif // 0
#if 0 /// UNNEEDED by elogind
/**
* bus_path_encode_unique() - encode unique object path