[systemd-commits] 8 commits - Makefile.am man/busctl.xml src/core src/libsystemd src/shared src/test

Lennart Poettering lennart at kemper.freedesktop.org
Wed Nov 19 15:10:37 PST 2014


 Makefile.am                               |    4 
 man/busctl.xml                            |   36 -
 src/core/dbus-manager.c                   |    4 
 src/libsystemd/sd-bus/bus-dump.c          |   93 +-
 src/libsystemd/sd-bus/bus-dump.h          |    2 
 src/libsystemd/sd-bus/bus-objects.c       |    5 
 src/libsystemd/sd-bus/busctl-introspect.c |  793 ++++++++++++++++++++++++
 src/libsystemd/sd-bus/busctl-introspect.h |   34 +
 src/libsystemd/sd-bus/busctl.c            |  967 +++++++++++-------------------
 src/libsystemd/sd-bus/test-bus-creds.c    |    4 
 src/shared/hashmap.c                      |    2 
 src/test/test-unit-name.c                 |    2 
 src/test/test-util.c                      |    1 
 13 files changed, 1306 insertions(+), 641 deletions(-)

New commits:
commit 73fc23c0641d3659330f44cf1a6ea112d6a51708
Author: Lennart Poettering <lennart at poettering.net>
Date:   Thu Nov 20 00:00:22 2014 +0100

    busctl: pass error output to stdout (rather than stderr) when generate tree for all objects
    
    This is a ton more useful when some services fail, since we continue
    crawling then and output everything to a pager.

diff --git a/src/libsystemd/sd-bus/busctl.c b/src/libsystemd/sd-bus/busctl.c
index 32ffbce..33be4e8 100644
--- a/src/libsystemd/sd-bus/busctl.c
+++ b/src/libsystemd/sd-bus/busctl.c
@@ -318,7 +318,7 @@ static int on_path(const char *path, void *userdata) {
         return 0;
 }
 
-static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths) {
+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,
         };
@@ -330,7 +330,10 @@ static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *p
 
         r = sd_bus_call_method(bus, service, path, "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, "");
         if (r < 0) {
-                log_error("Failed to introspect object %s of service %s: %s", path, service, bus_error_message(&error, r));
+                if (many)
+                        printf("Failed to introspect object %s of service %s: %s\n", path, service, bus_error_message(&error, r));
+                else
+                        log_error("Failed to introspect object %s of service %s: %s", path, service, bus_error_message(&error, r));
                 return r;
         }
 
@@ -341,7 +344,7 @@ static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *p
         return parse_xml_introspect(path, xml, &ops, paths);
 }
 
-static int tree_one(sd_bus *bus, const char *service, const char *prefix) {
+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;
@@ -381,7 +384,7 @@ static int tree_one(sd_bus *bus, const char *service, const char *prefix) {
                     set_contains(failed, p))
                         continue;
 
-                q = find_nodes(bus, service, p, paths);
+                q = find_nodes(bus, service, p, paths, many);
                 if (q < 0) {
                         if (r >= 0)
                                 r = q;
@@ -397,6 +400,8 @@ static int tree_one(sd_bus *bus, const char *service, const char *prefix) {
                 p = NULL;
         }
 
+        pager_open_if_enabled();
+
         l = set_get_strv(done);
         if (!l)
                 return log_oom();
@@ -442,25 +447,25 @@ static int tree(sd_bus *bus, char **argv) {
 
                         printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_highlight_off());
 
-                        q = tree_one(bus, *i, NULL);
+                        q = tree_one(bus, *i, NULL, true);
                         if (q < 0 && r >= 0)
                                 r = q;
 
                         not_first = true;
                 }
         } else {
-                pager_open_if_enabled();
-
                 STRV_FOREACH(i, argv+1) {
                         int q;
 
                         if (i > argv+1)
                                 printf("\n");
 
-                        if (argv[2])
+                        if (argv[2]) {
+                                pager_open_if_enabled();
                                 printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_highlight_off());
+                        }
 
-                        q = tree_one(bus, *i, NULL);
+                        q = tree_one(bus, *i, NULL, !!argv[2]);
                         if (q < 0 && r >= 0)
                                 r = q;
                 }

commit d0b2babf5264429e6ac0870b2c7cb13eacba2b86
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Nov 19 23:59:26 2014 +0100

    busctl: improve output of service creds

diff --git a/src/libsystemd/sd-bus/bus-dump.c b/src/libsystemd/sd-bus/bus-dump.c
index 28fcdda..8814070 100644
--- a/src/libsystemd/sd-bus/bus-dump.c
+++ b/src/libsystemd/sd-bus/bus-dump.c
@@ -121,7 +121,7 @@ int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) {
                 if (m->monotonic != 0 || m->realtime != 0 || m->seqnum != 0)
                         fputs("\n", f);
 
-                bus_creds_dump(&m->creds, f);
+                bus_creds_dump(&m->creds, f, true);
         }
 
         r = sd_bus_message_rewind(m, !(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY));
@@ -278,6 +278,7 @@ 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;
@@ -294,7 +295,7 @@ static void dump_capabilities(
         if (r < 0)
                 return;
 
-        fprintf(f, "  %s=", name);
+        fprintf(f, "%s%s=%s", terse ? "  " : "", name, terse ? "" : ansi_highlight());
         last_cap = cap_last_cap();
 
         for (;;) {
@@ -304,7 +305,7 @@ static void dump_capabilities(
                         if (n > 0)
                                 fputc(' ', f);
                         if (n % 4 == 3)
-                                fputs("\n          ", f);
+                                fprintf(f, terse ? "\n          " : "\n        ");
 
                         t = cap_to_name(i);
                         fprintf(f, "%s", t);
@@ -320,14 +321,18 @@ static void dump_capabilities(
         }
 
         fputs("\n", f);
+
+        if (!terse)
+                fputs(ansi_highlight_off(), f);
 }
 
-int bus_creds_dump(sd_bus_creds *c, FILE *f) {
+int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) {
         bool audit_sessionid_is_set = false, audit_loginuid_is_set = false;
         const char *u = NULL, *uu = NULL, *s = NULL, *sl = NULL;
         uid_t owner, audit_loginuid;
         uint32_t audit_sessionid;
         char **cmdline = NULL, **well_known = NULL;
+        const char *prefix, *color, *suffix;
         int r;
 
         assert(c);
@@ -335,45 +340,59 @@ int bus_creds_dump(sd_bus_creds *c, FILE *f) {
         if (!f)
                 f = stdout;
 
+        if (terse) {
+                prefix = "  ";
+                suffix = "";
+                color = "";
+        } else {
+                const char *off;
+
+                prefix = "";
+                color = ansi_highlight();
+
+                off = ansi_highlight_off();
+                suffix = strappenda(off, "\n");
+        }
+
         if (c->mask & SD_BUS_CREDS_PID)
-                fprintf(f, "  PID="PID_FMT, c->pid);
+                fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix);
         if (c->mask & SD_BUS_CREDS_PID_STARTTIME)
-                fprintf(f, "  PIDStartTime="USEC_FMT, c->pid_starttime);
+                fprintf(f, "%sPIDStartTime=%s"USEC_FMT"%s", prefix, color, c->pid_starttime, suffix);
         if (c->mask & SD_BUS_CREDS_TID)
-                fprintf(f, "  TID="PID_FMT, c->tid);
+                fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix);
         if (c->mask & SD_BUS_CREDS_UID)
-                fprintf(f, "  UID="UID_FMT, c->uid);
+                fprintf(f, "%sUID=%s"UID_FMT"%s", prefix, color, c->uid, suffix);
         r = sd_bus_creds_get_owner_uid(c, &owner);
         if (r >= 0)
-                fprintf(f, "  OwnerUID="UID_FMT, owner);
+                fprintf(f, "%sOwnerUID=%s"UID_FMT"%s", prefix, color, owner, suffix);
         if (c->mask & SD_BUS_CREDS_GID)
-                fprintf(f, "  GID="GID_FMT, c->gid);
+                fprintf(f, "%sGID=%s"GID_FMT"%s", prefix, color, c->gid, suffix);
 
-        if ((c->mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_PID_STARTTIME|SD_BUS_CREDS_TID|SD_BUS_CREDS_UID|SD_BUS_CREDS_GID)) || r >= 0)
+        if (terse && ((c->mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_PID_STARTTIME|SD_BUS_CREDS_TID|SD_BUS_CREDS_UID|SD_BUS_CREDS_GID)) || r >= 0))
                 fputs("\n", f);
 
         if (c->mask & SD_BUS_CREDS_EXE)
-                fprintf(f, "  Exe=%s", c->exe);
+                fprintf(f, "%sExe=%s%s%s", prefix, color, c->exe, suffix);
         if (c->mask & SD_BUS_CREDS_COMM)
-                fprintf(f, "  Comm=%s", c->comm);
+                fprintf(f, "%sComm=%s%s%s", prefix, color, c->comm, suffix);
         if (c->mask & SD_BUS_CREDS_TID_COMM)
-                fprintf(f, "  TIDComm=%s", c->tid_comm);
+                fprintf(f, "%sTIDComm=%s%s%s", prefix, color, c->tid_comm, suffix);
 
-        if (c->mask & (SD_BUS_CREDS_EXE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM))
+        if (terse && (c->mask & (SD_BUS_CREDS_EXE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM)))
                 fputs("\n", f);
 
         if (c->mask & SD_BUS_CREDS_SELINUX_CONTEXT)
-                fprintf(f, "  Label=%s", c->label);
+                fprintf(f, "%sLabel=%s%s%s", prefix, color, c->label, suffix);
         if (c->mask & SD_BUS_CREDS_DESCRIPTION)
-                fprintf(f, "  Description=%s", c->description);
+                fprintf(f, "%sDescription=%s%s%s", prefix, color, c->description, suffix);
 
-        if (c->mask & (SD_BUS_CREDS_SELINUX_CONTEXT|SD_BUS_CREDS_DESCRIPTION))
+        if (terse && (c->mask & (SD_BUS_CREDS_SELINUX_CONTEXT|SD_BUS_CREDS_DESCRIPTION)))
                 fputs("\n", f);
 
         if (sd_bus_creds_get_cmdline(c, &cmdline) >= 0) {
                 char **i;
 
-                fputs("  CommandLine={", f);
+                fprintf(f, "%sCommandLine=%s", prefix, color);
                 STRV_FOREACH(i, cmdline) {
                         if (i != cmdline)
                                 fputc(' ', f);
@@ -381,46 +400,46 @@ int bus_creds_dump(sd_bus_creds *c, FILE *f) {
                         fputs(*i, f);
                 }
 
-                fputs("}\n", f);
+                fprintf(f, "%s", suffix);
         }
 
         if (c->mask & SD_BUS_CREDS_CGROUP)
-                fprintf(f, "  CGroup=%s", c->cgroup);
+                fprintf(f, "%sCGroup=%s%s%s", prefix, color, c->cgroup, suffix);
         sd_bus_creds_get_unit(c, &u);
         if (u)
-                fprintf(f, "  Unit=%s", u);
+                fprintf(f, "%sUnit=%s%s%s", prefix, color, u, suffix);
         sd_bus_creds_get_user_unit(c, &uu);
         if (uu)
-                fprintf(f, "  UserUnit=%s", uu);
+                fprintf(f, "%sUserUnit=%s%s%s", prefix, color, uu, suffix);
         sd_bus_creds_get_slice(c, &sl);
         if (sl)
-                fprintf(f, "  Slice=%s", sl);
+                fprintf(f, "%sSlice=%s%s%s", prefix, color, sl, suffix);
         sd_bus_creds_get_session(c, &s);
         if (s)
-                fprintf(f, "  Session=%s", s);
+                fprintf(f, "%sSession=%s%s%s", prefix, color, s, suffix);
 
-        if ((c->mask & SD_BUS_CREDS_CGROUP) || u || uu || sl || s)
+        if (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || u || uu || sl || s))
                 fputs("\n", f);
 
         if (sd_bus_creds_get_audit_login_uid(c, &audit_loginuid) >= 0) {
                 audit_loginuid_is_set = true;
-                fprintf(f, "  AuditLoginUID="UID_FMT, audit_loginuid);
+                fprintf(f, "%sAuditLoginUID=%s"UID_FMT"%s", prefix, color, audit_loginuid, suffix);
         }
         if (sd_bus_creds_get_audit_session_id(c, &audit_sessionid) >= 0) {
                 audit_sessionid_is_set = true;
-                fprintf(f, "  AuditSessionID=%"PRIu32, audit_sessionid);
+                fprintf(f, "%sAuditSessionID=%s%"PRIu32"%s", prefix, color, audit_sessionid, suffix);
         }
 
-        if (audit_loginuid_is_set || audit_sessionid_is_set)
+        if (terse && (audit_loginuid_is_set || audit_sessionid_is_set))
                 fputs("\n", f);
 
         if (c->mask & SD_BUS_CREDS_UNIQUE_NAME)
-                fprintf(f, "  UniqueName=%s", c->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;
 
-                fputs("  WellKnownNames={", f);
+                fprintf(f, "%sWellKnownNames=%s", prefix, color);
                 STRV_FOREACH(i, well_known) {
                         if (i != well_known)
                                 fputc(' ', f);
@@ -428,16 +447,16 @@ int bus_creds_dump(sd_bus_creds *c, FILE *f) {
                         fputs(*i, f);
                 }
 
-                fputc('}', f);
+                fprintf(f, "%s", suffix);
         }
 
-        if (c->mask & SD_BUS_CREDS_UNIQUE_NAME || well_known)
+        if (terse && (c->mask & SD_BUS_CREDS_UNIQUE_NAME || well_known))
                 fputc('\n', f);
 
-        dump_capabilities(c, f, "EffectiveCapabilities", sd_bus_creds_has_effective_cap);
-        dump_capabilities(c, f, "PermittedCapabilities", sd_bus_creds_has_permitted_cap);
-        dump_capabilities(c, f, "InheritableCapabilities", sd_bus_creds_has_inheritable_cap);
-        dump_capabilities(c, f, "BoundingCapabilities", sd_bus_creds_has_bounding_cap);
+        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;
 }
diff --git a/src/libsystemd/sd-bus/bus-dump.h b/src/libsystemd/sd-bus/bus-dump.h
index 360c844..d2522ed 100644
--- a/src/libsystemd/sd-bus/bus-dump.h
+++ b/src/libsystemd/sd-bus/bus-dump.h
@@ -33,7 +33,7 @@ enum {
 
 int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags);
 
-int bus_creds_dump(sd_bus_creds *c, FILE *f);
+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/libsystemd/sd-bus/busctl.c b/src/libsystemd/sd-bus/busctl.c
index bb89ab9..32ffbce 100644
--- a/src/libsystemd/sd-bus/busctl.c
+++ b/src/libsystemd/sd-bus/busctl.c
@@ -938,7 +938,7 @@ static int status(sd_bus *bus, char *argv[]) {
                 return r;
         }
 
-        bus_creds_dump(creds, NULL);
+        bus_creds_dump(creds, NULL, false);
         return 0;
 }
 
diff --git a/src/libsystemd/sd-bus/test-bus-creds.c b/src/libsystemd/sd-bus/test-bus-creds.c
index c4894e8..ff2602b 100644
--- a/src/libsystemd/sd-bus/test-bus-creds.c
+++ b/src/libsystemd/sd-bus/test-bus-creds.c
@@ -31,7 +31,7 @@ int main(int argc, char *argv[]) {
         r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL);
         assert_se(r >= 0);
 
-        bus_creds_dump(creds, NULL);
+        bus_creds_dump(creds, NULL, true);
 
         creds = sd_bus_creds_unref(creds);
 
@@ -39,7 +39,7 @@ int main(int argc, char *argv[]) {
         if (r != -EACCES) {
                 assert_se(r >= 0);
                 putchar('\n');
-                bus_creds_dump(creds, NULL);
+                bus_creds_dump(creds, NULL, true);
         }
 
         return 0;

commit 0171da06ef8bb19c175c5aa8aff8cf95f3de7dc1
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Nov 19 21:12:54 2014 +0100

    busctl: add new "introspect" verb for introspecting objects

diff --git a/man/busctl.xml b/man/busctl.xml
index 730a334..ccbd832 100644
--- a/man/busctl.xml
+++ b/man/busctl.xml
@@ -186,13 +186,10 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
       </varlistentry>
 
       <varlistentry>
-        <term><command>tree</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
+        <term><command>status</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg></term>
 
-        <listitem><para>Shows an object tree of one or more
-        services. If <replaceable>SERVICE</replaceable> is specified,
-        show object tree of the specified services only. Otherwise,
-        show all object trees of all services on the bus that acquired
-        at least one well-known name.</para></listitem>
+        <listitem><para>Show process information and credentials of a
+        bus service.</para></listitem>
       </varlistentry>
 
       <varlistentry>
@@ -201,7 +198,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
         <listitem><para>Dump messages being exchanged. If
         <replaceable>SERVICE</replaceable> is specified, show messages
         to or from this endpoint. Otherwise, show all messages on the
-        bus.</para></listitem>
+        bus. Use Ctrl-C to terminate dump.</para></listitem>
       </varlistentry>
 
       <varlistentry>
@@ -218,24 +215,35 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
       </varlistentry>
 
       <varlistentry>
-        <term><command>status</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg></term>
+        <term><command>tree</command> <arg choice="opt" rep="repeat"><replaceable>SERVICE</replaceable></arg></term>
 
-        <listitem><para>Show process information and credentials of a
-        bus service.</para></listitem>
+        <listitem><para>Shows an object tree of one or more
+        services. If <replaceable>SERVICE</replaceable> is specified,
+        show object tree of the specified services only. Otherwise,
+        show all object trees of all services on the bus that acquired
+        at least one well-known name.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>introspect</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg></term>
+
+        <listitem><para>Show interfaces, methods, properties and
+        signals of the specified object (identified by its path) on
+        the specified service.</para></listitem>
       </varlistentry>
 
       <varlistentry>
-        <term><command>call</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>METHOD</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>PARAMETERS</replaceable></arg></arg></term>
+        <term><command>call</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="plain"><replaceable>INTERFACE</replaceable></arg> <arg choice="plain"><replaceable>METHOD</replaceable></arg> <arg choice="opt"><replaceable>SIGNATURE</replaceable> <arg choice="opt" rep="repeat"><replaceable>ARGUMENT</replaceable></arg></arg></term>
 
         <listitem><para>Invoke a method and show the response. Takes a
         service name, object path, interface name and method name. If
         parameters shall be passed to the method call a signature
-        string is required, followed by the individual parameters,
-        individually formatted as textual arguments.</para></listitem>
+        string is required, followed by the individual arguments,
+        individually formatted as textual parameters.</para></listitem>
       </varlistentry>
 
       <varlistentry>
-        <term><command>get-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="opt"><replaceable>INTERFACE</replaceable> <arg choice="opt" rep="repeat"><replaceable>PROPERTIES</replaceable></arg></arg></term>
+        <term><command>get-property</command> <arg choice="plain"><replaceable>SERVICE</replaceable></arg> <arg choice="plain"><replaceable>OBJECT</replaceable></arg> <arg choice="opt"><replaceable>INTERFACE</replaceable> <arg choice="opt" rep="repeat"><replaceable>PROPERTY</replaceable></arg></arg></term>
 
         <listitem><para>Retrieve the current value one or more object
         properties. Takes a service name and object path. Optionally
diff --git a/src/libsystemd/sd-bus/busctl-introspect.c b/src/libsystemd/sd-bus/busctl-introspect.c
index 0417028..15c10da 100644
--- a/src/libsystemd/sd-bus/busctl-introspect.c
+++ b/src/libsystemd/sd-bus/busctl-introspect.c
@@ -38,11 +38,30 @@ typedef struct Context {
         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) {
+        free(c->interface_name);
+        c->interface_name = NULL;
+        c->interface_flags = 0;
+
+        context_reset_member(c);
+}
+
 static int parse_xml_annotation(Context *context, uint64_t *flags) {
 
         enum {
@@ -105,11 +124,11 @@ static int parse_xml_annotation(Context *context, uint64_t *flags) {
                                         } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) {
 
                                                 if (streq_ptr(value, "const"))
-                                                        *flags |= SD_BUS_VTABLE_PROPERTY_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 |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
+                                                        *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 |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
+                                                        *flags = *flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION);
                                         }
                                 }
 
@@ -182,7 +201,7 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
                 STATE_PROPERTY_ACCESS,
         } state = STATE_NODE;
 
-        _cleanup_free_ char *node_path = NULL;
+        _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL;
         const char *np = prefix;
         int r;
 
@@ -225,7 +244,6 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                                 if (streq_ptr(name, "interface"))
                                         state = STATE_INTERFACE;
-
                                 else if (streq_ptr(name, "node")) {
 
                                         r = parse_xml_node(context, np, n_depth+1);
@@ -297,9 +315,10 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
                                         state = STATE_METHOD;
                                 else if (streq_ptr(name, "signal"))
                                         state = STATE_SIGNAL;
-                                else if (streq_ptr(name, "property"))
+                                else if (streq_ptr(name, "property")) {
+                                        context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
                                         state = STATE_PROPERTY;
-                                else if (streq_ptr(name, "annotation")) {
+                                } else if (streq_ptr(name, "annotation")) {
                                         r = parse_xml_annotation(context, &context->interface_flags);
                                         if (r < 0)
                                                 return r;
@@ -308,11 +327,21 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
                                         return -EINVAL;
                                 }
                         } else if (t == XML_TAG_CLOSE_EMPTY ||
-                                   (t == XML_TAG_CLOSE && streq_ptr(name, "interface")))
+                                   (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)) {
+                        } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
                                 log_error("Unexpected token in <interface>. (1)");
                                 return -EINVAL;
                         }
@@ -321,9 +350,15 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_INTERFACE_NAME:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+                                if (n_depth == 0) {
+                                        free(context->interface_name);
+                                        context->interface_name = name;
+                                        name = NULL;
+                                }
+
                                 state = STATE_INTERFACE;
-                        else {
+                        } else {
                                 log_error("Unexpected token in <interface>. (2)");
                                 return -EINVAL;
                         }
@@ -351,11 +386,21 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
                                         return -EINVAL;
                                 }
                         } else if (t == XML_TAG_CLOSE_EMPTY ||
-                                   (t == XML_TAG_CLOSE && streq_ptr(name, "method")))
+                                   (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)) {
+                        } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
                                 log_error("Unexpected token in <method> (1).");
                                 return -EINVAL;
                         }
@@ -364,9 +409,16 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_METHOD_NAME:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+
+                                if (n_depth == 0) {
+                                        free(context->member_name);
+                                        context->member_name = name;
+                                        name = NULL;
+                                }
+
                                 state = STATE_METHOD;
-                        else {
+                        } else {
                                 log_error("Unexpected token in <method> (2).");
                                 return -EINVAL;
                         }
@@ -396,11 +448,27 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
                                         return -EINVAL;
                                 }
                         } else if (t == XML_TAG_CLOSE_EMPTY ||
-                                   (t == XML_TAG_CLOSE && streq_ptr(name, "arg")))
+                                   (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();
+                                                }
+                                        }
 
-                                state = STATE_METHOD;
+                                        free(argument_type);
+                                        free(argument_direction);
+                                        argument_type = argument_direction = NULL;
+                                }
 
-                        else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+                                state = STATE_METHOD;
+                        } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
                                 log_error("Unexpected token in method <arg>. (1)");
                                 return -EINVAL;
                         }
@@ -420,9 +488,13 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_METHOD_ARG_TYPE:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+                                free(argument_type);
+                                argument_type = name;
+                                name = NULL;
+
                                 state = STATE_METHOD_ARG;
-                        else {
+                        } else {
                                 log_error("Unexpected token in method <arg>. (3)");
                                 return -EINVAL;
                         }
@@ -431,9 +503,13 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_METHOD_ARG_DIRECTION:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+                                free(argument_direction);
+                                argument_direction = name;
+                                name = NULL;
+
                                 state = STATE_METHOD_ARG;
-                        else {
+                        } else {
                                 log_error("Unexpected token in method <arg>. (4)");
                                 return -EINVAL;
                         }
@@ -461,11 +537,21 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
                                         return -EINVAL;
                                 }
                         } else if (t == XML_TAG_CLOSE_EMPTY ||
-                                   (t == XML_TAG_CLOSE && streq_ptr(name, "signal")))
+                                   (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)) {
+                        } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
                                 log_error("Unexpected token in <signal>. (1)");
                                 return -EINVAL;
                         }
@@ -474,9 +560,16 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_SIGNAL_NAME:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+
+                                if (n_depth == 0) {
+                                        free(context->member_name);
+                                        context->member_name = name;
+                                        name = NULL;
+                                }
+
                                 state = STATE_SIGNAL;
-                        else {
+                        } else {
                                 log_error("Unexpected token in <signal>. (2)");
                                 return -EINVAL;
                         }
@@ -505,11 +598,18 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
                                         return -EINVAL;
                                 }
                         } else if (t == XML_TAG_CLOSE_EMPTY ||
-                                   (t == XML_TAG_CLOSE && streq_ptr(name, "arg")))
+                                   (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) {
 
-                                state = STATE_SIGNAL;
+                                if (argument_type) {
+                                        if (!strextend(&context->member_signature, argument_type, NULL))
+                                                return log_oom();
 
-                        else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
+                                        free(argument_type);
+                                        argument_type = NULL;
+                                }
+
+                                state = STATE_SIGNAL;
+                        } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
                                 log_error("Unexpected token in signal <arg> (1).");
                                 return -EINVAL;
                         }
@@ -529,9 +629,13 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_SIGNAL_ARG_TYPE:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+                                free(argument_type);
+                                argument_type = name;
+                                name = NULL;
+
                                 state = STATE_SIGNAL_ARG;
-                        else {
+                        } else {
                                 log_error("Unexpected token in signal <arg> (3).");
                                 return -EINVAL;
                         }
@@ -563,11 +667,21 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
                                 }
 
                         } else if (t == XML_TAG_CLOSE_EMPTY ||
-                                   (t == XML_TAG_CLOSE && streq_ptr(name, "property")))
+                                   (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)) {
+                        } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) {
                                 log_error("Unexpected token in <property>. (1)");
                                 return -EINVAL;
                         }
@@ -576,9 +690,15 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_PROPERTY_NAME:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+
+                                if (n_depth == 0) {
+                                        free(context->member_name);
+                                        context->member_name = name;
+                                        name = NULL;
+                                }
                                 state = STATE_PROPERTY;
-                        else {
+                        } else {
                                 log_error("Unexpected token in <property>. (2)");
                                 return -EINVAL;
                         }
@@ -587,9 +707,16 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_PROPERTY_TYPE:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+
+                                if (n_depth == 0) {
+                                        free(context->member_signature);
+                                        context->member_signature = name;
+                                        name = NULL;
+                                }
+
                                 state = STATE_PROPERTY;
-                        else {
+                        } else {
                                 log_error("Unexpected token in <property>. (3)");
                                 return -EINVAL;
                         }
@@ -598,9 +725,13 @@ static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth
 
                 case STATE_PROPERTY_ACCESS:
 
-                        if (t == XML_ATTRIBUTE_VALUE)
+                        if (t == XML_ATTRIBUTE_VALUE) {
+
+                                if (streq(name, "readwrite") || streq(name, "write"))
+                                        context->member_writable = true;
+
                                 state = STATE_PROPERTY;
-                        else {
+                        } else {
                                 log_error("Unexpected token in <property>. (4)");
                                 return -EINVAL;
                         }
@@ -629,27 +760,34 @@ int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospec
                 r = xml_tokenize(&context.current, &name, &context.xml_state, NULL);
                 if (r < 0) {
                         log_error("XML parse error");
-                        return r;
+                        goto finish;
                 }
 
-                if (r == XML_END)
+                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)
-                                        return r;
+                                        goto finish;
                         } else {
                                 log_error("Unexpected tag '%s' in introspection data.", name);
-                                return -EBADMSG;
+                                r = -EBADMSG;
+                                goto finish;
                         }
                 } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
                         log_error("Unexpected token.");
-                        return -EINVAL;
+                        r = -EBADMSG;
+                        goto finish;
                 }
         }
 
-        return 0;
+finish:
+        context_reset_interface(&context);
+
+        return r;
 }
diff --git a/src/libsystemd/sd-bus/busctl-introspect.h b/src/libsystemd/sd-bus/busctl-introspect.h
index 7c4cd6e..9bea437 100644
--- a/src/libsystemd/sd-bus/busctl-introspect.h
+++ b/src/libsystemd/sd-bus/busctl-introspect.h
@@ -25,11 +25,10 @@
 
 typedef struct XMLIntrospectOps {
         int (*on_path)(const char *path, void *userdata);
-        int (*on_interface)(const char *name, uint64_t flags);
-        int (*on_method)(const char *name, const char *signature, const char *result, uint64_t flags, void *userdata);
-        int (*on_signal)(const char *name, const char *signature, uint64_t flags, void *userdata);
-        int (*on_property)(const char *name, const char *signature, uint64_t flags);
-        int (*on_writable_property)(const char *name, const char *signature, uint64_t flags);
+        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/libsystemd/sd-bus/busctl.c b/src/libsystemd/sd-bus/busctl.c
index c58a97b..bb89ab9 100644
--- a/src/libsystemd/sd-bus/busctl.c
+++ b/src/libsystemd/sd-bus/busctl.c
@@ -319,7 +319,7 @@ static int on_path(const char *path, void *userdata) {
 }
 
 static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths) {
-        const XMLIntrospectOps ops = {
+        static const XMLIntrospectOps ops = {
                 .on_path = on_path,
         };
 
@@ -338,7 +338,6 @@ static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *p
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        /* fputs(xml, stdout); */
         return parse_xml_introspect(path, xml, &ops, paths);
 }
 
@@ -470,6 +469,353 @@ static int tree(sd_bus *bus, char **argv) {
         return r;
 }
 
+typedef struct Member {
+        const char *type;
+        char *interface;
+        char *name;
+        char *signature;
+        char *result;
+        bool writable;
+        uint64_t flags;
+} Member;
+
+static unsigned long member_hash_func(const void *p, const uint8_t hash_key[]) {
+        const Member *m = p;
+        unsigned long ul;
+
+        assert(m);
+        assert(m->type);
+
+        ul = string_hash_func(m->type, hash_key);
+
+        if (m->name)
+                ul ^= string_hash_func(m->name, hash_key);
+
+        if (m->interface)
+                ul ^= string_hash_func(m->interface, hash_key);
+
+        return ul;
+}
+
+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);
+
+        if (!x->interface && y->interface)
+                return -1;
+        if (x->interface && !y->interface)
+                return 1;
+        if (x->interface && y->interface) {
+                d = strcmp(x->interface, y->interface);
+                if (d != 0)
+                        return d;
+        }
+
+        d = strcmp(x->type, y->type);
+        if (d != 0)
+                return d;
+
+        if (!x->name && y->name)
+                return -1;
+        if (x->name && !y->name)
+                return 1;
+        if (x->name && y->name)
+                return strcmp(x->name, y->name);
+
+        return 0;
+}
+
+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);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Member*, member_free);
+
+static void member_set_free(Set *s) {
+        Member *m;
+
+        while ((m = set_steal_first(s)))
+                member_free(m);
+
+        set_free(s);
+}
+
+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 const char *strdash(const char *x) {
+        return isempty(x) ? "-" : x;
+}
+
+static int introspect(sd_bus *bus, char **argv) {
+        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_bus_message_unref_ sd_bus_message *reply = NULL;
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(member_set_freep) Set *members = NULL;
+        Iterator i;
+        Member *m;
+        const char *xml;
+        int r;
+        unsigned name_width,  type_width, signature_width, result_width;
+        Member **sorted = NULL;
+        unsigned k = 0, j;
+
+        if (strv_length(argv) != 3) {
+                log_error("Requires service and object path argument.");
+                return -EINVAL;
+        }
+
+        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, "");
+        if (r < 0) {
+                log_error("Failed to introspect object %s of service %s: %s", argv[2], argv[1], bus_error_message(&error, r));
+                return r;
+        }
+
+        r = sd_bus_message_read(reply, "s", &xml);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = parse_xml_introspect(argv[2], xml, &ops, members);
+        if (r < 0)
+                return r;
+
+        pager_open_if_enabled();
+
+        name_width = strlen("NAME");
+        type_width = strlen("TYPE");
+        signature_width = strlen("SIGNATURE");
+        result_width = strlen("RESULT");
+
+        sorted = newa(Member*, set_size(members));
+
+        SET_FOREACH(m, members, i) {
+                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));
+
+                sorted[k++] = m;
+        }
+
+        assert(k == set_size(members));
+        qsort(sorted, k, sizeof(Member*), member_compare_funcp);
+
+        printf("%-*s %-*s %-*s %-*s %s\n",
+               (int) name_width, "NAME",
+               (int) type_width, "TYPE",
+               (int) signature_width, "SIGNATURE",
+               (int) result_width, "RESULT",
+               "FLAGS");
+
+        for (j = 0; j < k; j++) {
+                bool is_interface;
+
+                m = sorted[j];
+
+                is_interface = streq(m->type, "interface");
+
+                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, strdash(streq_ptr(m->type, "interface") ? m->interface : m->name),
+                       is_interface ? ansi_highlight_off() : "",
+                       (int) type_width, strdash(m->type),
+                       (int) signature_width, strdash(m->signature),
+                       (int) result_width, strdash(m->result),
+                       (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);
 }
@@ -1021,13 +1367,14 @@ static int help(void) {
                "     --quiet              Don't show method call reply\n\n"
                "Commands:\n"
                "  list                    List bus names\n"
-               "  tree [SERVICE...]       Show object tree of service\n"
+               "  status SERVICE          Show service name status\n"
                "  monitor [SERVICE...]    Show bus traffic\n"
                "  capture [SERVICE...]    Capture bus traffic as pcap\n"
-               "  status SERVICE          Show service name status\n"
-               "  call SERVICE PATH INTERFACE METHOD [SIGNATURE [ARGUMENTS...]]\n"
+               "  tree [SERVICE...]       Show object tree of service\n"
+               "  introspect SERVICE PATH\n"
+               "  call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n"
                "                          Call a method\n"
-               "  get-property SERVICE PATH [INTERFACE [PROPERTY...]]\n"
+               "  get-property SERVICE OBJECT [INTERFACE [PROPERTY...]]\n"
                "                          Get property value\n"
                "  help                    Show this help\n"
                , program_invocation_short_name);
@@ -1197,6 +1544,9 @@ static int busctl_main(sd_bus *bus, int argc, char *argv[]) {
         if (streq(argv[optind], "tree"))
                 return tree(bus, argv + optind);
 
+        if (streq(argv[optind], "introspect"))
+                return introspect(bus, argv + optind);
+
         if (streq(argv[optind], "call"))
                 return call(bus, argv + optind);
 

commit 9b772efb41c2d9f743ba5e96804bdf89b12630d8
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Nov 19 20:52:47 2014 +0100

    sd-bus: refuse properties that claim to be both writable and constant at the same time

diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c
index 0ab1119..7981d65 100644
--- a/src/libsystemd/sd-bus/bus-objects.c
+++ b/src/libsystemd/sd-bus/bus-objects.c
@@ -1682,6 +1682,11 @@ static int add_object_vtable_internal(
                                 goto fail;
                         }
 
+                        if (v->flags & SD_BUS_VTABLE_PROPERTY_CONST) {
+                                r = -EINVAL;
+                                goto fail;
+                        }
+
                         /* Fall through */
 
                 case _SD_BUS_VTABLE_PROPERTY: {

commit 4c3f1641f13b7687a0dc234d3ae387b7c40494ff
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Nov 19 20:52:23 2014 +0100

    core: watchdog bus properties cannot be both writable and constant

diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
index c54abd3..1384e8f 100644
--- a/src/core/dbus-manager.c
+++ b/src/core/dbus-manager.c
@@ -1884,8 +1884,8 @@ const sd_bus_vtable bus_manager_vtable[] = {
         SD_BUS_PROPERTY("UnitPath", "as", NULL, offsetof(Manager, lookup_paths.unit_path), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("DefaultStandardOutput", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("DefaultStandardError", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST),
-        SD_BUS_WRITABLE_PROPERTY("RuntimeWatchdogUSec", "t", bus_property_get_usec, property_set_runtime_watchdog, offsetof(Manager, runtime_watchdog), SD_BUS_VTABLE_PROPERTY_CONST),
-        SD_BUS_WRITABLE_PROPERTY("ShutdownWatchdogUSec", "t", bus_property_get_usec, bus_property_set_usec, offsetof(Manager, shutdown_watchdog), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_WRITABLE_PROPERTY("RuntimeWatchdogUSec", "t", bus_property_get_usec, property_set_runtime_watchdog, offsetof(Manager, runtime_watchdog), 0),
+        SD_BUS_WRITABLE_PROPERTY("ShutdownWatchdogUSec", "t", bus_property_get_usec, bus_property_set_usec, offsetof(Manager, shutdown_watchdog), 0),
         SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Manager, cgroup_root), 0),
         SD_BUS_PROPERTY("SystemState", "s", property_get_system_state, 0, 0),
 

commit e1ba963fdf13f4057b9089e0c732e6c1eb69901e
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Nov 19 16:43:41 2014 +0100

    tests: fix minor memory leak

diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c
index 256e820..ab6c488 100644
--- a/src/test/test-unit-name.c
+++ b/src/test/test-unit-name.c
@@ -135,7 +135,7 @@ static int test_unit_printf(void) {
 #define expect(unit, pattern, expected)                                 \
         {                                                               \
                 char *e;                                                \
-                _cleanup_free_ char *t;                                 \
+                _cleanup_free_ char *t = NULL;                          \
                 assert_se(unit_full_printf(unit, pattern, &t) >= 0);    \
                 printf("result: %s\nexpect: %s\n", t, expected);        \
                 if ((e = endswith(expected, "*")))                      \
diff --git a/src/test/test-util.c b/src/test/test-util.c
index 01b0192..01e10c9 100644
--- a/src/test/test-util.c
+++ b/src/test/test-util.c
@@ -1217,6 +1217,7 @@ static void test_unquote_first_word(void) {
         p = original = "\'fooo";
         assert_se(unquote_first_word(&p, &t, true) > 0);
         assert_se(streq(t, "fooo"));
+        free(t);
         assert_se(p == original + 5);
 
         p = original = "yay\'foo\'bar";

commit 575ccc1b6900f1f8e5c32da71f42f4d855659622
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Nov 19 16:43:03 2014 +0100

    set: make set_consume() actually free the allocated string if the string already is in the set

diff --git a/src/shared/hashmap.c b/src/shared/hashmap.c
index 2bc3b38..5b329e0 100644
--- a/src/shared/hashmap.c
+++ b/src/shared/hashmap.c
@@ -1810,7 +1810,7 @@ int set_consume(Set *s, void *value) {
         int r;
 
         r = set_put(s, value);
-        if (r < 0)
+        if (r <= 0)
                 free(value);
 
         return r;

commit a1ad376761af16da46c9ad90fd8df41c8c5c0976
Author: Lennart Poettering <lennart at poettering.net>
Date:   Wed Nov 19 16:42:21 2014 +0100

    busctl: split out introspection parser from tree logic so that we can reuse it for a future "busctl introspect" command

diff --git a/Makefile.am b/Makefile.am
index 1aef242..3f9f3fa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2898,7 +2898,9 @@ test_resolve_CFLAGS = \
 	-pthread
 
 busctl_SOURCES = \
-	src/libsystemd/sd-bus/busctl.c
+	src/libsystemd/sd-bus/busctl.c \
+	src/libsystemd/sd-bus/busctl-introspect.c \
+	src/libsystemd/sd-bus/busctl-introspect.h
 
 busctl_LDADD = \
 	libsystemd-dump.la \
diff --git a/src/libsystemd/sd-bus/busctl-introspect.c b/src/libsystemd/sd-bus/busctl-introspect.c
new file mode 100644
index 0000000..0417028
--- /dev/null
+++ b/src/libsystemd/sd-bus/busctl-introspect.c
@@ -0,0 +1,655 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "util.h"
+#include "xml.h"
+#include "sd-bus-vtable.h"
+
+#include "busctl-introspect.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;
+
+        const char *current;
+        void *xml_state;
+} Context;
+
+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 |= SD_BUS_VTABLE_PROPERTY_CONST;
+                                                else if (streq_ptr(value, "invalidates"))
+                                                        *flags |= SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION;
+                                                else if (streq_ptr(value, "false"))
+                                                        *flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE;
+                                        }
+                                }
+
+                                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(field);
+                                field = name;
+                                name = NULL;
+
+                                state = STATE_ANNOTATION;
+                        } else {
+                                log_error("Unexpected token in <annotation>. (2)");
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case STATE_VALUE:
+
+                        if (t == XML_ATTRIBUTE_VALUE) {
+                                free(value);
+                                value = name;
+                                name = NULL;
+
+                                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_PROPERTY,
+                STATE_PROPERTY_NAME,
+                STATE_PROPERTY_TYPE,
+                STATE_PROPERTY_ACCESS,
+        } state = STATE_NODE;
+
+        _cleanup_free_ char *node_path = 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 = name;
+                                        name = NULL;
+                                } else {
+
+                                        if (endswith(prefix, "/"))
+                                                node_path = strappend(prefix, name);
+                                        else
+                                                node_path = strjoin(prefix, "/", name, NULL);
+                                        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"))
+                                        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")))
+
+                                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)
+                                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")))
+
+                                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)
+                                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")))
+
+                                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)
+                                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)
+                                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")))
+
+                                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)
+                                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 {
+                                        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")))
+
+                                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)
+                                state = STATE_SIGNAL_ARG;
+                        else {
+                                log_error("Unexpected token in signal <arg> (3).");
+                                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")))
+
+                                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)
+                                state = STATE_PROPERTY;
+                        else {
+                                log_error("Unexpected token in <property>. (2)");
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case STATE_PROPERTY_TYPE:
+
+                        if (t == XML_ATTRIBUTE_VALUE)
+                                state = STATE_PROPERTY;
+                        else {
+                                log_error("Unexpected token in <property>. (3)");
+                                return -EINVAL;
+                        }
+
+                        break;
+
+                case STATE_PROPERTY_ACCESS:
+
+                        if (t == XML_ATTRIBUTE_VALUE)
+                                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");
+                        return r;
+                }
+
+                if (r == XML_END)
+                        break;
+
+                if (r == XML_TAG_OPEN) {
+
+                        if (streq(name, "node")) {
+                                r = parse_xml_node(&context, prefix, 0);
+                                if (r < 0)
+                                        return r;
+                        } else {
+                                log_error("Unexpected tag '%s' in introspection data.", name);
+                                return -EBADMSG;
+                        }
+                } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
+                        log_error("Unexpected token.");
+                        return -EINVAL;
+                }
+        }
+
+        return 0;
+}
diff --git a/src/libsystemd/sd-bus/busctl-introspect.h b/src/libsystemd/sd-bus/busctl-introspect.h
new file mode 100644
index 0000000..7c4cd6e
--- /dev/null
+++ b/src/libsystemd/sd-bus/busctl-introspect.h
@@ -0,0 +1,35 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <inttypes.h>
+
+typedef struct XMLIntrospectOps {
+        int (*on_path)(const char *path, void *userdata);
+        int (*on_interface)(const char *name, uint64_t flags);
+        int (*on_method)(const char *name, const char *signature, const char *result, uint64_t flags, void *userdata);
+        int (*on_signal)(const char *name, const char *signature, uint64_t flags, void *userdata);
+        int (*on_property)(const char *name, const char *signature, uint64_t flags);
+        int (*on_writable_property)(const char *name, const char *signature, uint64_t flags);
+} XMLIntrospectOps;
+
+int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata);
diff --git a/src/libsystemd/sd-bus/busctl.c b/src/libsystemd/sd-bus/busctl.c
index 6d03692..c58a97b 100644
--- a/src/libsystemd/sd-bus/busctl.c
+++ b/src/libsystemd/sd-bus/busctl.c
@@ -35,6 +35,7 @@
 #include "bus-util.h"
 #include "bus-dump.h"
 #include "bus-signature.h"
+#include "busctl-introspect.h"
 
 static bool arg_no_pager = false;
 static bool arg_legend = true;
@@ -304,550 +305,27 @@ static void print_tree(const char *prefix, char **l) {
         print_subtree(prefix, "/", l);
 }
 
-static int parse_xml_annotation(
-                const char **p,
-                void **xml_state) {
-
-
-        enum {
-                STATE_ANNOTATION,
-                STATE_NAME,
-                STATE_VALUE
-        } state = STATE_ANNOTATION;
-
-        for (;;) {
-                _cleanup_free_ char *name = NULL;
-
-                int t;
-
-                t = xml_tokenize(p, &name, 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")))
-
-                                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)
-                                state = STATE_ANNOTATION;
-                        else {
-                                log_error("Unexpected token in <annotation>. (2)");
-                                return -EINVAL;
-                        }
-
-                        break;
-
-                case STATE_VALUE:
-
-                        if (t == XML_ATTRIBUTE_VALUE)
-                                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(
-                const char *prefix,
-                Set *paths,
-                const char **p,
-                void **xml_state) {
-
-        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_PROPERTY,
-                STATE_PROPERTY_NAME,
-                STATE_PROPERTY_TYPE,
-                STATE_PROPERTY_ACCESS,
-        } state = STATE_NODE;
-
-        _cleanup_free_ char *node_path = NULL;
-        const char *np = prefix;
+static int on_path(const char *path, void *userdata) {
+        Set *paths = userdata;
         int r;
 
-        for (;;) {
-                _cleanup_free_ char *name = NULL;
-                int t;
-
-                t = xml_tokenize(p, &name, 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(np, paths, p, xml_state);
-                                        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 (paths) {
-                                        if (!node_path) {
-                                                node_path = strdup(np);
-                                                if (!node_path)
-                                                        return log_oom();
-                                        }
-
-                                        r = set_put(paths, node_path);
-                                        if (r < 0)
-                                                return log_oom();
-                                        else if (r > 0)
-                                                node_path = NULL;
-                                }
-
-                                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 = name;
-                                        name = NULL;
-                                } else {
-
-                                        if (endswith(prefix, "/"))
-                                                node_path = strappend(prefix, name);
-                                        else
-                                                node_path = strjoin(prefix, "/", name, NULL);
-                                        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"))
-                                        state = STATE_PROPERTY;
-                                else if (streq_ptr(name, "annotation")) {
-                                        r = parse_xml_annotation(p, xml_state);
-                                        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")))
-
-                                state = STATE_NODE;
+        assert(paths);
 
-                        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)
-                                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(p, xml_state);
-                                        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")))
-
-                                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)
-                                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(p, xml_state);
-                                        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")))
-
-                                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)
-                                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)
-                                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(p, xml_state);
-                                        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")))
-
-                                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)
-                                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 {
-                                        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(p, xml_state);
-                                        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")))
-
-                                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)
-                                state = STATE_SIGNAL_ARG;
-                        else {
-                                log_error("Unexpected token in signal <arg> (3).");
-                                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(p, xml_state);
-                                        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")))
-
-                                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)
-                                state = STATE_PROPERTY;
-                        else {
-                                log_error("Unexpected token in <property>. (2)");
-                                return -EINVAL;
-                        }
-
-                        break;
-
-                case STATE_PROPERTY_TYPE:
-
-                        if (t == XML_ATTRIBUTE_VALUE)
-                                state = STATE_PROPERTY;
-                        else {
-                                log_error("Unexpected token in <property>. (3)");
-                                return -EINVAL;
-                        }
-
-                        break;
-
-                case STATE_PROPERTY_ACCESS:
-
-                        if (t == XML_ATTRIBUTE_VALUE)
-                                state = STATE_PROPERTY;
-                        else {
-                                log_error("Unexpected token in <property>. (4)");
-                                return -EINVAL;
-                        }
+        r = set_put_strdup(paths, path);
+        if (r < 0)
+                return log_oom();
 
-                        break;
-                }
-        }
+        return 0;
 }
 
 static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths) {
+        const XMLIntrospectOps ops = {
+                .on_path = on_path,
+        };
+
         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
-        _cleanup_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
-        const char *xml, *p;
-        void *xml_state = NULL;
+        _cleanup_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, "");
@@ -861,37 +339,7 @@ static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *p
                 return bus_log_parse_error(r);
 
         /* fputs(xml, stdout); */
-
-        p = xml;
-        for (;;) {
-                _cleanup_free_ char *name = NULL;
-
-                r = xml_tokenize(&p, &name, &xml_state, NULL);
-                if (r < 0) {
-                        log_error("XML parse error");
-                        return r;
-                }
-
-                if (r == XML_END)
-                        break;
-
-                if (r == XML_TAG_OPEN) {
-
-                        if (streq(name, "node")) {
-                                r = parse_xml_node(path, paths, &p, &xml_state);
-                                if (r < 0)
-                                        return r;
-                        } else {
-                                log_error("Unexpected tag '%s' in introspection data.", name);
-                                return -EBADMSG;
-                        }
-                } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) {
-                        log_error("Unexpected token.");
-                        return -EINVAL;
-                }
-        }
-
-        return 0;
+        return parse_xml_introspect(path, xml, &ops, paths);
 }
 
 static int tree_one(sd_bus *bus, const char *service, const char *prefix) {



More information about the systemd-commits mailing list