[systemd-commits] 7 commits - fixme .gitignore Makefile.am man/systemd.special.xml.in src/automount.c src/conf-parser.c src/dbus-automount.c src/dbus-automount.h src/dbus.c src/dbus-device.c src/dbus-device.h src/dbus-execute.h src/dbus.h src/dbus-job.c src/dbus-job.h src/dbus-manager.c src/dbus-manager.h src/dbus-mount.c src/dbus-mount.h src/dbus-path.c src/dbus-path.h src/dbus-service.c src/dbus-service.h src/dbus-snapshot.c src/dbus-snapshot.h src/dbus-socket.c src/dbus-socket.h src/dbus-swap.c src/dbus-swap.h src/dbus-target.c src/dbus-target.h src/dbus-timer.c src/dbus-timer.h src/dbus-unit.c src/dbus-unit.h src/load-fragment.c src/logger.c src/main.c src/manager.c src/manager.h src/mount.c src/path.c src/path.h src/ratelimit.c src/sd-daemon.c src/service.c src/socket.c src/socket.h src/timer.c src/timer.h src/unit.c src/unit.h src/util.c src/util.h src/utmp-wtmp.c

Lennart Poettering lennart at kemper.freedesktop.org
Mon May 24 10:00:27 PDT 2010


 .gitignore                 |    1 
 Makefile.am                |   24 +
 fixme                      |    2 
 man/systemd.special.xml.in |    2 
 src/automount.c            |    5 
 src/conf-parser.c          |    4 
 src/dbus-automount.c       |   27 +-
 src/dbus-automount.h       |    2 
 src/dbus-device.c          |   27 +-
 src/dbus-device.h          |    2 
 src/dbus-execute.h         |   34 +-
 src/dbus-job.c             |   35 +-
 src/dbus-job.h             |    2 
 src/dbus-manager.c         |  137 +++++-----
 src/dbus-manager.h         |    2 
 src/dbus-mount.c           |   41 +--
 src/dbus-mount.h           |    2 
 src/dbus-path.c            |   52 ++++
 src/dbus-path.h            |   33 ++
 src/dbus-service.c         |   53 ++--
 src/dbus-service.h         |    2 
 src/dbus-snapshot.c        |   29 +-
 src/dbus-snapshot.h        |    2 
 src/dbus-socket.c          |   45 +--
 src/dbus-socket.h          |    2 
 src/dbus-swap.c            |   29 +-
 src/dbus-swap.h            |    2 
 src/dbus-target.c          |   25 +
 src/dbus-target.h          |    2 
 src/dbus-timer.c           |   52 ++++
 src/dbus-timer.h           |   33 ++
 src/dbus-unit.c            |    2 
 src/dbus-unit.h            |   98 +++----
 src/dbus.c                 |   32 ++
 src/dbus.h                 |   34 +-
 src/load-fragment.c        |  162 ++++++++++++
 src/logger.c               |   22 -
 src/main.c                 |   39 ++-
 src/manager.c              |    4 
 src/manager.h              |    2 
 src/mount.c                |   16 +
 src/path.c                 |  578 +++++++++++++++++++++++++++++++++++++++++++++
 src/path.h                 |   85 ++++++
 src/ratelimit.c            |    8 
 src/sd-daemon.c            |   21 +
 src/service.c              |    7 
 src/socket.c               |    4 
 src/socket.h               |    8 
 src/timer.c                |  434 +++++++++++++++++++++++++++++++++
 src/timer.h                |   45 +++
 src/unit.c                 |   18 -
 src/unit.h                 |   12 
 src/util.c                 |   87 ++++++
 src/util.h                 |   11 
 src/utmp-wtmp.c            |   22 -
 55 files changed, 2108 insertions(+), 353 deletions(-)

New commits:
commit 0c565ed702d2b0ee4a584e9348b13a78fb002972
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon May 24 19:00:13 2010 +0200

    service: interpret suse/debian style X-Start-Before/X-Start-After

diff --git a/src/service.c b/src/service.c
index 2f699cd..d557394 100644
--- a/src/service.c
+++ b/src/service.c
@@ -516,7 +516,9 @@ static int service_load_sysv_path(Service *s, const char *path) {
                                 }
 
                         } else if (startswith(t, "Required-Start:") ||
-                                   startswith(t, "Should-Start:")) {
+                                   startswith(t, "Should-Start:") ||
+                                   startswith(t, "X-Start-Before:") ||
+                                   startswith(t, "X-Start-After:")) {
                                 char *i, *w;
                                 size_t z;
 
@@ -539,7 +541,7 @@ static int service_load_sysv_path(Service *s, const char *path) {
                                         if (r == 0)
                                                 continue;
 
-                                        r = unit_add_dependency_by_name(u, UNIT_AFTER, m, NULL, true);
+                                        r = unit_add_dependency_by_name(u, startswith(t, "X-Start-Before:") ? UNIT_BEFORE : UNIT_AFTER, m, NULL, true);
                                         free(m);
 
                                         if (r < 0)
commit 8640e111358257bbdd19582c0cac6166e87bd277
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon May 24 18:59:46 2010 +0200

    sd-daemon: set FD_CLOEXEC by default

diff --git a/src/sd-daemon.c b/src/sd-daemon.c
index eec4722..2e1bf32 100644
--- a/src/sd-daemon.c
+++ b/src/sd-daemon.c
@@ -28,6 +28,7 @@
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <sys/un.h>
+#include <sys/fcntl.h>
 #include <netinet/in.h>
 #include <stdlib.h>
 #include <errno.h>
@@ -41,7 +42,7 @@ int sd_listen_fds(int unset_environment) {
 #ifdef DISABLE_SYSTEMD
         return 0;
 #else
-        int r;
+        int r, fd;
         const char *e;
         char *p = NULL;
         unsigned long l;
@@ -88,6 +89,24 @@ int sd_listen_fds(int unset_environment) {
                 goto finish;
         }
 
+
+        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
+                int flags;
+
+                if ((flags = fcntl(fd, F_GETFD)) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+
+                if (flags & FD_CLOEXEC)
+                        continue;
+
+                if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+                        r = -errno;
+                        goto finish;
+                }
+        }
+
         r = (int) l;
 
 finish:
commit 8efe3c0114758f229273f17944fd6f2952b10c52
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon May 24 18:59:13 2010 +0200

    main: don't try to mount api dirs if we are not root

diff --git a/src/main.c b/src/main.c
index d7d3995..7a829ce 100644
--- a/src/main.c
+++ b/src/main.c
@@ -582,8 +582,9 @@ int main(int argc, char *argv[]) {
 
         /* Mount /proc, /sys and friends, so that /proc/cmdline and
          * /proc/$PID/fd is available. */
-        if (mount_setup() < 0)
-                goto finish;
+        if (geteuid() == 0)
+                if (mount_setup() < 0)
+                        goto finish;
 
         /* Reset all signal handlers. */
         assert_se(reset_all_signal_handlers() == 0);
commit 1e89ced119b1b80d21fca778fb20eb818b6f20c5
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon May 24 18:58:43 2010 +0200

    man: fix minor typo

diff --git a/fixme b/fixme
index 7abf795..6b6f7f7 100644
--- a/fixme
+++ b/fixme
@@ -66,8 +66,6 @@
 
 * introduce exit.target for session instances
 
-* use _PATH_XXX
-
 Regularly:
 
 * look for close() vs. close_nointr() vs. close_nointr_nofail()
diff --git a/man/systemd.special.xml.in b/man/systemd.special.xml.in
index da474b7..daa9358 100644
--- a/man/systemd.special.xml.in
+++ b/man/systemd.special.xml.in
@@ -313,7 +313,7 @@
                                         all SysV init script service
                                         units with an LSB header
                                         referring to the
-                                        <literal>$remote-fs</literal>
+                                        <literal>$remote_fs</literal>
                                         facility.</para>
                                 </listitem>
                         </varlistentry>
commit 01f78473b104d28db0fa813414092bc6358ae521
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon May 24 05:25:33 2010 +0200

    path: add .path unit type for monitoring files

diff --git a/Makefile.am b/Makefile.am
index b404bbe..9b0e04f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -77,7 +77,8 @@ interface_DATA = \
 	org.freedesktop.systemd1.Mount.xml \
 	org.freedesktop.systemd1.Automount.xml \
 	org.freedesktop.systemd1.Snapshot.xml \
-	org.freedesktop.systemd1.Swap.xml
+	org.freedesktop.systemd1.Swap.xml \
+	org.freedesktop.systemd1.Path.xml
 
 dist_systemunit_DATA = \
 	units/emergency.service \
@@ -189,6 +190,7 @@ COMMON_SOURCES = \
         src/snapshot.c \
         src/socket.c \
         src/timer.c \
+	src/path.c \
         src/load-dropin.c \
         src/execute.c \
         src/dbus.c \
@@ -205,6 +207,7 @@ COMMON_SOURCES = \
 	src/dbus-snapshot.c \
 	src/dbus-device.c \
 	src/dbus-execute.c \
+	src/dbus-path.c \
 	src/cgroup.c \
 	src/mount-setup.c \
 	src/hostname-setup.c \
diff --git a/src/automount.c b/src/automount.c
index 3268046..d83f3ed 100644
--- a/src/automount.c
+++ b/src/automount.c
@@ -562,12 +562,15 @@ static int automount_start(Unit *u) {
 
         assert(a);
 
+        assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_MAINTAINANCE);
+
         if (path_is_mount_point(a->where)) {
                 log_error("Path %s is already a mount point, refusing start for %s", a->where, u->meta.id);
                 return -EEXIST;
         }
 
-        assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_MAINTAINANCE);
+        if (a->mount->meta.load_state != UNIT_LOADED)
+                return -ENOENT;
 
         a->failure = false;
         automount_enter_waiting(a);
diff --git a/src/conf-parser.c b/src/conf-parser.c
index 6994211..20f7641 100644
--- a/src/conf-parser.c
+++ b/src/conf-parser.c
@@ -337,6 +337,8 @@ int config_parse_path(
         if (!(n = strdup(rvalue)))
                 return -ENOMEM;
 
+        path_kill_slashes(n);
+
         free(*s);
         *s = n;
 
@@ -441,6 +443,8 @@ int config_parse_path_strv(
                         goto fail;
                 }
 
+                path_kill_slashes(n[k]);
+
                 k++;
         }
 
diff --git a/src/dbus-path.c b/src/dbus-path.c
new file mode 100644
index 0000000..ed1dc26
--- /dev/null
+++ b/src/dbus-path.c
@@ -0,0 +1,52 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-path.h"
+#include "dbus-execute.h"
+
+#define BUS_PATH_INTERFACE                                             \
+        " <interface name=\"org.freedesktop.systemd1.Path\">\n"        \
+        "  <property name=\"Unit\" type=\"s\"  access=\"read\"/>\n"     \
+        " </interface>\n"                                               \
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_PATH_INTERFACE                                             \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_path_interface[] = BUS_PATH_INTERFACE;
+
+DBusHandlerResult bus_path_message_handler(Unit *u, DBusMessage *message) {
+        const BusProperty properties[] = {
+                BUS_UNIT_PROPERTIES,
+                { "org.freedesktop.systemd1.Path", "Unit", bus_property_append_string, "s", &u->path.unit->meta.id },
+                { NULL, NULL, NULL, NULL, NULL }
+        };
+
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
+}
diff --git a/src/dbus-path.h b/src/dbus-path.h
new file mode 100644
index 0000000..15f5869
--- /dev/null
+++ b/src/dbus-path.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbuspathhfoo
+#define foodbuspathhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_path_message_handler(Unit *u, DBusMessage *message);
+
+extern const char bus_path_interface[];
+
+#endif
diff --git a/src/dbus.c b/src/dbus.c
index 6dd495d..f5bbc58 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -41,6 +41,7 @@
 #include "dbus-snapshot.h"
 #include "dbus-swap.h"
 #include "dbus-timer.h"
+#include "dbus-path.h"
 
 static const char bus_properties_interface[] = BUS_PROPERTIES_INTERFACE;
 static const char bus_introspectable_interface[] = BUS_INTROSPECTABLE_INTERFACE;
@@ -60,6 +61,7 @@ const char *const bus_interface_table[] = {
         "org.freedesktop.systemd1.Snapshot",   bus_snapshot_interface,
         "org.freedesktop.systemd1.Swap",       bus_swap_interface,
         "org.freedesktop.systemd1.Timer",      bus_timer_interface,
+        "org.freedesktop.systemd1.Path",       bus_path_interface,
         NULL
 };
 
diff --git a/src/load-fragment.c b/src/load-fragment.c
index 889e621..a706880 100644
--- a/src/load-fragment.c
+++ b/src/load-fragment.c
@@ -213,6 +213,8 @@ static int config_parse_listen(
                         free(p);
                         return -ENOMEM;
                 }
+
+                path_kill_slashes(p->path);
         } else {
                 p->type = SOCKET_SOCKET;
 
@@ -450,6 +452,8 @@ static int config_parse_exec(
         nce->argv = n;
         nce->path = path;
 
+        path_kill_slashes(nce->path);
+
         exec_command_append_list(e, nce);
 
         return 0;
@@ -1068,6 +1072,77 @@ static int config_parse_timer_unit(
         return 0;
 }
 
+static int config_parse_path_spec(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Path *p = data;
+        PathSpec *s;
+        PathType b;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((b = path_type_from_string(lvalue)) < 0) {
+                log_error("[%s:%u] Failed to parse path type: %s", filename, line, lvalue);
+                return -EINVAL;
+        }
+
+        if (!path_is_absolute(rvalue)) {
+                log_error("[%s:%u] Path is not absolute: %s", filename, line, rvalue);
+                return -EINVAL;
+        }
+
+        if (!(s = new0(PathSpec, 1)))
+                return -ENOMEM;
+
+        if (!(s->path = strdup(rvalue))) {
+                free(s);
+                return -ENOMEM;
+        }
+
+        path_kill_slashes(s->path);
+
+        s->type = b;
+        s->inotify_fd = -1;
+
+        LIST_PREPEND(PathSpec, spec, p->specs, s);
+
+        return 0;
+}
+
+static int config_parse_path_unit(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Path *t = data;
+        int r;
+
+        if (endswith(rvalue, ".path")) {
+                log_error("[%s:%u] Unit cannot be of type path: %s", filename, line, rvalue);
+                return -EINVAL;
+        }
+
+        if ((r = manager_load_unit(t->meta.manager, rvalue, NULL, &t->unit)) < 0) {
+                log_error("[%s:%u] Failed to load unit: %s", filename, line, rvalue);
+                return r;
+        }
+
+        return 0;
+}
+
 #define FOLLOW_MAX 8
 
 static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
@@ -1269,7 +1344,8 @@ static int load_from_path(Unit *u, const char *path) {
                 [UNIT_MOUNT]     = "Mount",
                 [UNIT_AUTOMOUNT] = "Automount",
                 [UNIT_SNAPSHOT]  = "Snapshot",
-                [UNIT_SWAP]      = "Swap"
+                [UNIT_SWAP]      = "Swap",
+                [UNIT_PATH]      = "Path"
         };
 
 #define EXEC_CONTEXT_CONFIG_ITEMS(context, section) \
@@ -1386,15 +1462,20 @@ static int load_from_path(Unit *u, const char *path) {
 
                 { "Where",                  config_parse_path,            &u->automount.where,                             "Automount" },
 
-                { "What",                   config_parse_path,            &u->swap.parameters_fragment.what,               "Swap" },
-                { "Priority",               config_parse_int,             &u->swap.parameters_fragment.priority,           "Swap" },
+                { "What",                   config_parse_path,            &u->swap.parameters_fragment.what,               "Swap"    },
+                { "Priority",               config_parse_int,             &u->swap.parameters_fragment.priority,           "Swap"    },
+
+                { "OnActive",               config_parse_timer,           &u->timer,                                       "Timer"   },
+                { "OnBoot",                 config_parse_timer,           &u->timer,                                       "Timer"   },
+                { "OnStartup",              config_parse_timer,           &u->timer,                                       "Timer"   },
+                { "OnUnitActive",           config_parse_timer,           &u->timer,                                       "Timer"   },
+                { "OnUnitInactive",         config_parse_timer,           &u->timer,                                       "Timer"   },
+                { "Unit",                   config_parse_timer_unit,      &u->timer,                                       "Timer"   },
 
-                { "OnActive",               config_parse_timer,           &u->timer,                                       "Timer" },
-                { "OnBoot",                 config_parse_timer,           &u->timer,                                       "Timer" },
-                { "OnStartup",              config_parse_timer,           &u->timer,                                       "Timer" },
-                { "OnUnitActive",           config_parse_timer,           &u->timer,                                       "Timer" },
-                { "OnUnitInactive",         config_parse_timer,           &u->timer,                                       "Timer" },
-                { "Unit",                   config_parse_timer_unit,      &u->timer,                                       "Timer" },
+                { "PathExists",             config_parse_path_spec,       &u->path,                                        "Path"    },
+                { "PathChanged",            config_parse_path_spec,       &u->path,                                        "Path"    },
+                { "DirectoryNotEmpty",      config_parse_path_spec,       &u->path,                                        "Path"    },
+                { "Unit",                   config_parse_path_unit,       &u->path,                                        "Path"    },
 
                 { NULL, NULL, NULL, NULL }
         };
diff --git a/src/mount.c b/src/mount.c
index 01fc2df..dfe4f87 100644
--- a/src/mount.c
+++ b/src/mount.c
@@ -167,6 +167,19 @@ static int mount_add_swap_links(Mount *m) {
         return 0;
 }
 
+static int mount_add_path_links(Mount *m) {
+        Meta *other;
+        int r;
+
+        assert(m);
+
+        LIST_FOREACH(units_per_type, other, m->meta.manager->units_per_type[UNIT_PATH])
+                if ((r = path_add_one_mount_link((Path*) other, m)) < 0)
+                        return r;
+
+        return 0;
+}
+
 static int mount_add_automount_links(Mount *m) {
         Meta *other;
         int r;
@@ -341,6 +354,9 @@ static int mount_load(Unit *u) {
                 if ((r = mount_add_swap_links(m)) < 0)
                         return r;
 
+                if ((r = mount_add_path_links(m)) < 0)
+                        return r;
+
                 if ((r = mount_add_automount_links(m)) < 0)
                         return r;
 
diff --git a/src/path.c b/src/path.c
new file mode 100644
index 0000000..b40a829
--- /dev/null
+++ b/src/path.c
@@ -0,0 +1,578 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/inotify.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "unit.h"
+#include "unit-name.h"
+#include "path.h"
+#include "dbus-path.h"
+
+static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = {
+        [PATH_DEAD] = UNIT_INACTIVE,
+        [PATH_WAITING] = UNIT_ACTIVE,
+        [PATH_RUNNING] = UNIT_ACTIVE,
+        [PATH_MAINTAINANCE] = UNIT_INACTIVE
+};
+
+static void path_done(Unit *u) {
+        Path *p = PATH(u);
+        PathSpec *s;
+
+        assert(p);
+
+        while ((s = p->specs)) {
+                LIST_REMOVE(PathSpec, spec, p->specs, s);
+                free(s);
+        }
+}
+
+int path_add_one_mount_link(Path *p, Mount *m) {
+        PathSpec *s;
+        int r;
+
+        assert(p);
+        assert(m);
+
+        if (p->meta.load_state != UNIT_LOADED ||
+            m->meta.load_state != UNIT_LOADED)
+                return 0;
+
+        LIST_FOREACH(spec, s, p->specs) {
+
+                if (!path_startswith(s->path, m->where))
+                        continue;
+
+                if ((r = unit_add_dependency(UNIT(m), UNIT_BEFORE, UNIT(p), true)) < 0)
+                        return r;
+
+                if ((r = unit_add_dependency(UNIT(p), UNIT_REQUIRES, UNIT(m), true)) < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int path_add_mount_links(Path *p) {
+        Meta *other;
+        int r;
+
+        assert(p);
+
+        LIST_FOREACH(units_per_type, other, p->meta.manager->units_per_type[UNIT_MOUNT])
+                if ((r = path_add_one_mount_link(p, (Mount*) other)) < 0)
+                        return r;
+
+        return 0;
+}
+
+static int path_verify(Path *p) {
+        assert(p);
+
+        if (UNIT(p)->meta.load_state != UNIT_LOADED)
+                return 0;
+
+        if (!p->specs) {
+                log_error("%s lacks path setting. Refusing.", p->meta.id);
+                return -EINVAL;
+        }
+
+        return 0;
+}
+
+static int path_load(Unit *u) {
+        Path *p = PATH(u);
+        int r;
+
+        assert(u);
+        assert(u->meta.load_state == UNIT_STUB);
+
+        if ((r = unit_load_fragment_and_dropin(u)) < 0)
+                return r;
+
+        if (u->meta.load_state == UNIT_LOADED) {
+
+                if (!p->unit)
+                        if ((r = unit_load_related_unit(u, ".service", &p->unit)))
+                                return r;
+
+                if ((r = unit_add_dependency(u, UNIT_BEFORE, p->unit, true)) < 0)
+                        return r;
+
+                if ((r = path_add_mount_links(p)) < 0)
+                        return r;
+        }
+
+        return path_verify(p);
+}
+
+static void path_dump(Unit *u, FILE *f, const char *prefix) {
+        Path *p = PATH(u);
+        const char *prefix2;
+        char *p2;
+        PathSpec *s;
+
+        p2 = strappend(prefix, "\t");
+        prefix2 = p2 ? p2 : prefix;
+
+        fprintf(f,
+                "%sPath State: %s\n"
+                "%sUnit: %s\n",
+                prefix, path_state_to_string(p->state),
+                prefix, p->unit->meta.id);
+
+        LIST_FOREACH(spec, s, p->specs)
+                fprintf(f,
+                        "%s%s: %s\n",
+                        prefix,
+                        path_type_to_string(s->type),
+                        s->path);
+
+        free(p2);
+}
+
+static void path_unwatch_one(Path *p, PathSpec *s) {
+
+        if (s->inotify_fd < 0)
+                return;
+
+        unit_unwatch_fd(UNIT(p), &s->watch);
+
+        close_nointr_nofail(s->inotify_fd);
+        s->inotify_fd = -1;
+}
+
+static int path_watch_one(Path *p, PathSpec *s) {
+        static const int flags_table[_PATH_TYPE_MAX] = {
+                [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF,
+                [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO,
+                [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_CREATE|IN_MOVED_TO
+        };
+
+        bool exists = false;
+        char *k;
+        int r;
+
+        assert(p);
+        assert(s);
+
+        path_unwatch_one(p, s);
+
+        if (!(k = strdup(s->path)))
+                return -ENOMEM;
+
+        if ((s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC)) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if (unit_watch_fd(UNIT(p), s->inotify_fd, EPOLLIN, &s->watch) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if ((s->primary_wd = inotify_add_watch(s->inotify_fd, k, flags_table[s->type])) >= 0)
+                exists = true;
+
+        for (;;) {
+                int flags;
+                char *slash;
+
+                /* This assumes the path was passed through path_kill_slashes()! */
+                if (!(slash = strrchr(k, '/')))
+                        break;
+
+                *slash = 0;
+
+                flags = IN_DELETE_SELF|IN_MOVE_SELF;
+                if (!exists)
+                        flags |= IN_CREATE | IN_MOVED_TO | IN_ATTRIB;
+
+                if (inotify_add_watch(s->inotify_fd, k, flags) >= 0)
+                        exists = true;
+        }
+
+        return 0;
+
+fail:
+        free(k);
+
+        path_unwatch_one(p, s);
+        return r;
+}
+
+static void path_unwatch(Path *p) {
+        PathSpec *s;
+
+        assert(p);
+
+        LIST_FOREACH(spec, s, p->specs)
+                path_unwatch_one(p, s);
+}
+
+static int path_watch(Path *p) {
+        int r;
+        PathSpec *s;
+
+        assert(p);
+
+        LIST_FOREACH(spec, s, p->specs)
+                if ((r = path_watch_one(p, s)) < 0)
+                        return r;
+
+        return 0;
+}
+
+static void path_set_state(Path *p, PathState state) {
+        PathState old_state;
+        assert(p);
+
+        old_state = p->state;
+        p->state = state;
+
+        if (state != PATH_WAITING)
+                path_unwatch(p);
+
+        if (state != old_state)
+                log_debug("%s changed %s -> %s",
+                          p->meta.id,
+                          path_state_to_string(old_state),
+                          path_state_to_string(state));
+
+        unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state]);
+}
+
+static void path_enter_waiting(Path *p, bool initial);
+
+static int path_coldplug(Unit *u) {
+        Path *p = PATH(u);
+
+        assert(p);
+        assert(p->state == PATH_DEAD);
+
+        if (p->deserialized_state != p->state) {
+
+                if (p->deserialized_state == PATH_WAITING ||
+                    p->deserialized_state == PATH_RUNNING)
+                        path_enter_waiting(p, true);
+                else
+                        path_set_state(p, p->deserialized_state);
+        }
+
+        return 0;
+}
+
+static void path_enter_dead(Path *p, bool success) {
+        assert(p);
+
+        if (!success)
+                p->failure = true;
+
+        path_set_state(p, p->failure ? PATH_MAINTAINANCE : PATH_DEAD);
+}
+
+static void path_enter_running(Path *p) {
+        int r;
+        assert(p);
+
+        if ((r = manager_add_job(UNIT(p)->meta.manager, JOB_START, p->unit, JOB_REPLACE, true, NULL)) < 0)
+                goto fail;
+
+        path_set_state(p, PATH_RUNNING);
+        return;
+
+fail:
+        log_warning("%s failed to queue unit startup job: %s", p->meta.id, strerror(-r));
+        path_enter_dead(p, false);
+}
+
+
+static void path_enter_waiting(Path *p, bool initial) {
+        PathSpec *s;
+        int r;
+        bool good = false;
+
+        LIST_FOREACH(spec, s, p->specs) {
+
+                switch (s->type) {
+
+                case PATH_EXISTS:
+                        good = access(s->path, F_OK) >= 0;
+                        break;
+
+                case PATH_DIRECTORY_NOT_EMPTY:
+                        good = dir_is_empty(s->path) == 0;
+                        break;
+
+                case PATH_CHANGED: {
+                        bool b;
+
+                        b = access(s->path, F_OK) >= 0;
+                        good = !initial && b != s->previous_exists;
+                        s->previous_exists = b;
+                        break;
+                }
+
+                default:
+                        ;
+                }
+
+                if (good)
+                        break;
+        }
+
+        if (good) {
+                path_enter_running(p);
+                return;
+        }
+
+        if ((r = path_watch(p)) < 0)
+                goto fail;
+
+        path_set_state(p, PATH_WAITING);
+        return;
+
+fail:
+        log_warning("%s failed to enter waiting state: %s", p->meta.id, strerror(-r));
+        path_enter_dead(p, false);
+}
+
+static int path_start(Unit *u) {
+        Path *p = PATH(u);
+
+        assert(p);
+        assert(p->state == PATH_DEAD || p->state == PATH_MAINTAINANCE);
+
+        if (p->unit->meta.load_state != UNIT_LOADED)
+                return -ENOENT;
+
+        p->failure = false;
+path_enter_waiting(p, true);
+        return 0;
+}
+
+static int path_stop(Unit *u) {
+        Path *p = PATH(u);
+
+        assert(p);
+        assert(p->state == PATH_WAITING || p->state == PATH_RUNNING);
+
+        path_enter_dead(p, true);
+        return 0;
+}
+
+static int path_serialize(Unit *u, FILE *f, FDSet *fds) {
+        Path *p = PATH(u);
+
+        assert(u);
+        assert(f);
+        assert(fds);
+
+        unit_serialize_item(u, f, "state", path_state_to_string(p->state));
+
+        return 0;
+}
+
+static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+        Path *p = PATH(u);
+
+        assert(u);
+        assert(key);
+        assert(value);
+        assert(fds);
+
+        if (streq(key, "state")) {
+                PathState state;
+
+                if ((state = path_state_from_string(value)) < 0)
+                        log_debug("Failed to parse state value %s", value);
+                else
+                        p->deserialized_state = state;
+        } else
+                log_debug("Unknown serialization key '%s'", key);
+
+        return 0;
+}
+
+static UnitActiveState path_active_state(Unit *u) {
+        assert(u);
+
+        return state_translation_table[PATH(u)->state];
+}
+
+static const char *path_sub_state_to_string(Unit *u) {
+        assert(u);
+
+        return path_state_to_string(PATH(u)->state);
+}
+
+static void path_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
+        Path *p = PATH(u);
+        int l;
+        ssize_t k;
+        struct inotify_event *buf = NULL;
+        PathSpec *s;
+
+        assert(p);
+        assert(fd >= 0);
+
+        if (p->state != PATH_WAITING)
+                return;
+
+        log_debug("inotify wakeup on %s.", u->meta.id);
+
+        if (events != EPOLLIN) {
+                log_error("Got Invalid poll event on inotify.");
+                goto fail;
+        }
+
+        LIST_FOREACH(spec, s, p->specs)
+                if (s->inotify_fd == fd)
+                        break;
+
+        if (!s) {
+                log_error("Got event on unknown fd.");
+                goto fail;
+        }
+
+        if (ioctl(fd, FIONREAD, &l) < 0) {
+                log_error("FIONREAD failed: %s", strerror(errno));
+                goto fail;
+        }
+
+        if (!(buf = malloc(l))) {
+                log_error("Failed to allocate buffer: %s", strerror(-ENOMEM));
+                goto fail;
+        }
+
+        if ((k = read(fd, buf, l)) < 0) {
+                log_error("Failed to read inotify event: %s", strerror(-errno));
+                goto fail;
+        }
+
+        if ((size_t) k < sizeof(struct inotify_event) ||
+            (size_t) k < sizeof(struct inotify_event) + buf->len) {
+                log_error("inotify event too small.");
+                goto fail;
+        }
+
+        if (s->type == PATH_CHANGED && s->primary_wd == buf->wd)
+                path_enter_running(p);
+        else
+                path_enter_waiting(p, false);
+
+        free(buf);
+
+        return;
+
+fail:
+        free(buf);
+        path_enter_dead(p, false);
+}
+
+void path_unit_notify(Unit *u, UnitActiveState new_state) {
+        char *n;
+        int r;
+        Iterator i;
+
+        if (u->meta.type == UNIT_PATH)
+                return;
+
+        SET_FOREACH(n, u->meta.names, i) {
+                char *k;
+                Unit *t;
+                Path *p;
+
+                if (!(k = unit_name_change_suffix(n, ".path"))) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                t = manager_get_unit(u->meta.manager, k);
+                free(k);
+
+                if (!t)
+                        continue;
+
+                if (t->meta.load_state != UNIT_LOADED)
+                        continue;
+
+                p = PATH(t);
+
+                if (p->unit != u)
+                        continue;
+
+                if (p->state == PATH_RUNNING && new_state == UNIT_INACTIVE) {
+                        log_debug("%s got notified about unit deactivation.", p->meta.id);
+                        path_enter_waiting(p, false);
+                }
+        }
+
+        return;
+
+fail:
+        log_error("Failed find path unit: %s", strerror(-r));
+}
+
+static const char* const path_state_table[_PATH_STATE_MAX] = {
+        [PATH_DEAD] = "dead",
+        [PATH_WAITING] = "waiting",
+        [PATH_RUNNING] = "running",
+        [PATH_MAINTAINANCE] = "maintainance"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(path_state, PathState);
+
+static const char* const path_type_table[_PATH_TYPE_MAX] = {
+        [PATH_EXISTS] = "PathExists",
+        [PATH_CHANGED] = "PathChanged",
+        [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(path_type, PathType);
+
+const UnitVTable path_vtable = {
+        .suffix = ".path",
+
+        .done = path_done,
+        .load = path_load,
+
+        .coldplug = path_coldplug,
+
+        .dump = path_dump,
+
+        .start = path_start,
+        .stop = path_stop,
+
+        .serialize = path_serialize,
+        .deserialize_item = path_deserialize_item,
+
+        .active_state = path_active_state,
+        .sub_state_to_string = path_sub_state_to_string,
+
+        .fd_event = path_fd_event,
+
+        .bus_message_handler = bus_path_message_handler
+};
diff --git a/src/path.h b/src/path.h
new file mode 100644
index 0000000..21a7dc4
--- /dev/null
+++ b/src/path.h
@@ -0,0 +1,85 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foopathhfoo
+#define foopathhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+typedef struct Path Path;
+
+#include "unit.h"
+#include "mount.h"
+
+typedef enum PathState {
+        PATH_DEAD,
+        PATH_WAITING,
+        PATH_RUNNING,
+        PATH_MAINTAINANCE,
+        _PATH_STATE_MAX,
+        _PATH_STATE_INVALID = -1
+} PathState;
+
+typedef enum PathType {
+        PATH_EXISTS,
+        PATH_DIRECTORY_NOT_EMPTY,
+        PATH_CHANGED,
+        _PATH_TYPE_MAX,
+        _PATH_TYPE_INVALID = -1
+} PathType;
+
+typedef struct PathSpec {
+        PathType type;
+        char *path;
+
+        int inotify_fd;
+        int primary_wd;
+        bool previous_exists;
+
+        Watch watch;
+
+        LIST_FIELDS(struct PathSpec, spec);
+} PathSpec;
+
+struct Path {
+        Meta meta;
+
+        LIST_HEAD(PathSpec, specs);
+
+        PathState state, deserialized_state;
+        Unit *unit;
+
+        bool failure;
+};
+
+void path_unit_notify(Unit *u, UnitActiveState new_state);
+
+/* Called from the mount code figure out if a mount is a dependency of
+ * any of the paths of this path object */
+int path_add_one_mount_link(Path *p, Mount *m);
+
+extern const UnitVTable path_vtable;
+
+const char* path_state_to_string(PathState i);
+PathState path_state_from_string(const char *s);
+
+const char* path_type_to_string(PathType i);
+PathType path_type_from_string(const char *s);
+
+#endif
diff --git a/src/service.c b/src/service.c
index 07b6e4e..2f699cd 100644
--- a/src/service.c
+++ b/src/service.c
@@ -1708,7 +1708,6 @@ static int service_start(Unit *u) {
         /* Make sure we don't enter a busy loop of some kind. */
         if (!ratelimit_test(&s->ratelimit)) {
                 log_warning("%s start request repeated too quickly, refusing to start.", u->meta.id);
-                service_enter_dead(s, false, true);
                 return -ECANCELED;
         }
 
diff --git a/src/socket.c b/src/socket.c
index ef7674f..9091517 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -58,7 +58,6 @@ static void socket_init(Unit *u) {
         assert(u);
         assert(u->meta.load_state == UNIT_STUB);
 
-        s->timer_watch.type = WATCH_INVALID;
         s->backlog = SOMAXCONN;
         s->timeout_usec = DEFAULT_TIMEOUT_USEC;
         s->directory_mode = 0755;
diff --git a/src/socket.h b/src/socket.h
index 5aa5f27..2067eb4 100644
--- a/src/socket.h
+++ b/src/socket.h
@@ -62,9 +62,7 @@ typedef enum SocketType {
         _SOCKET_FIFO_INVALID = -1
 } SocketType;
 
-typedef struct SocketPort SocketPort;
-
-struct SocketPort {
+typedef struct SocketPort {
         SocketType type;
         int fd;
 
@@ -72,8 +70,8 @@ struct SocketPort {
         char *path;
         Watch fd_watch;
 
-        LIST_FIELDS(SocketPort, port);
-};
+        LIST_FIELDS(struct SocketPort, port);
+} SocketPort;
 
 struct Socket {
         Meta meta;
diff --git a/src/timer.c b/src/timer.c
index e95b4d6..b025051 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -41,7 +41,6 @@ static void timer_init(Unit *u) {
         assert(u->meta.load_state == UNIT_STUB);
 
         t->next_elapse = (usec_t) -1;
-        t->timer_watch.type = WATCH_INVALID;
 }
 
 static void timer_done(Unit *u) {
@@ -274,8 +273,12 @@ static int timer_start(Unit *u) {
         Timer *t = TIMER(u);
 
         assert(t);
-        assert(t->state == TIMER_DEAD);
+        assert(t->state == TIMER_DEAD || t->state == TIMER_MAINTAINANCE);
+
+        if (t->unit->meta.load_state != UNIT_LOADED)
+                return -ENOENT;
 
+        t->failure = false;
         timer_enter_waiting(t, true);
         return 0;
 }
@@ -373,9 +376,12 @@ void timer_unit_notify(Unit *u, UnitActiveState new_state) {
                 if (!p)
                         continue;
 
+                if (p->meta.load_state != UNIT_LOADED)
+                        continue;
+
                 t = TIMER(p);
 
-                if (t->meta.load_state != UNIT_LOADED)
+                if (t->unit != u)
                         continue;
 
                 /* Reenable all timers that depend on unit state */
diff --git a/src/timer.h b/src/timer.h
index 69c5609..eb22688 100644
--- a/src/timer.h
+++ b/src/timer.h
@@ -61,11 +61,9 @@ struct Timer {
         Meta meta;
 
         LIST_HEAD(TimerValue, values);
-
-        TimerState state, deserialized_state;
-
         usec_t next_elapse;
 
+        TimerState state, deserialized_state;
         Unit *unit;
 
         Watch timer_watch;
diff --git a/src/unit.c b/src/unit.c
index 2af2685..57b3b77 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -48,7 +48,8 @@ const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = {
         [UNIT_MOUNT] = &mount_vtable,
         [UNIT_AUTOMOUNT] = &automount_vtable,
         [UNIT_SNAPSHOT] = &snapshot_vtable,
-        [UNIT_SWAP] = &swap_vtable
+        [UNIT_SWAP] = &swap_vtable,
+        [UNIT_PATH] = &path_vtable
 };
 
 Unit *unit_new(Manager *m) {
@@ -956,6 +957,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
                 u->meta.active_exit_timestamp = ts;
 
         timer_unit_notify(u, ns);
+        path_unit_notify(u, ns);
 
         if (u->meta.job) {
 
diff --git a/src/unit.h b/src/unit.h
index fd0defe..f585fa6 100644
--- a/src/unit.h
+++ b/src/unit.h
@@ -62,6 +62,7 @@ enum UnitType {
         UNIT_SNAPSHOT,
         UNIT_TIMER,
         UNIT_SWAP,
+        UNIT_PATH,
         _UNIT_TYPE_MAX,
         _UNIT_TYPE_INVALID = -1
 };
@@ -201,6 +202,7 @@ struct Meta {
 #include "automount.h"
 #include "snapshot.h"
 #include "swap.h"
+#include "path.h"
 
 union Unit {
         Meta meta;
@@ -213,6 +215,7 @@ union Unit {
         Automount automount;
         Snapshot snapshot;
         Swap swap;
+        Path path;
 };
 
 struct UnitVTable {
@@ -344,6 +347,7 @@ DEFINE_CAST(MOUNT, Mount);
 DEFINE_CAST(AUTOMOUNT, Automount);
 DEFINE_CAST(SNAPSHOT, Snapshot);
 DEFINE_CAST(SWAP, Swap);
+DEFINE_CAST(PATH, Path);
 
 Unit *unit_new(Manager *m);
 void unit_free(Unit *u);
diff --git a/src/util.c b/src/util.c
index a8ea4a9..7664df5 100644
--- a/src/util.c
+++ b/src/util.c
@@ -2092,6 +2092,35 @@ bool is_device_path(const char *path) {
                 path_startswith(path, "/sys/");
 }
 
+int dir_is_empty(const char *path) {
+        DIR *d;
+        int r;
+        struct dirent buf, *de;
+
+        if (!(d = opendir(path)))
+                return -errno;
+
+        for (;;) {
+                if ((r = readdir_r(d, &buf, &de)) > 0) {
+                        r = -r;
+                        break;
+                }
+
+                if (!de) {
+                        r = 1;
+                        break;
+                }
+
+                if (!ignore_file(de->d_name)) {
+                        r = 0;
+                        break;
+                }
+        }
+
+        closedir(d);
+        return r;
+}
+
 static const char *const ioprio_class_table[] = {
         [IOPRIO_CLASS_NONE] = "none",
         [IOPRIO_CLASS_RT] = "realtime",
diff --git a/src/util.h b/src/util.h
index b411df0..efc993c 100644
--- a/src/util.h
+++ b/src/util.h
@@ -247,6 +247,8 @@ int path_is_mount_point(const char *path);
 
 bool is_device_path(const char *path);
 
+int dir_is_empty(const char *path);
+
 extern char * __progname;
 
 const char *ioprio_class_to_string(int i);
commit 871d7de47c13ee6cd78b8eefdf9128be3c740ac0
Author: Lennart Poettering <lennart at poettering.net>
Date:   Mon May 24 01:45:54 2010 +0200

    timer: fully implement timer units

diff --git a/Makefile.am b/Makefile.am
index b3e9dfd..b404bbe 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -71,6 +71,7 @@ interface_DATA = \
 	org.freedesktop.systemd1.Unit.xml \
 	org.freedesktop.systemd1.Service.xml \
 	org.freedesktop.systemd1.Socket.xml \
+	org.freedesktop.systemd1.Timer.xml \
 	org.freedesktop.systemd1.Target.xml \
 	org.freedesktop.systemd1.Device.xml \
 	org.freedesktop.systemd1.Mount.xml \
@@ -196,6 +197,7 @@ COMMON_SOURCES = \
         src/dbus-job.c \
 	src/dbus-service.c \
 	src/dbus-socket.c \
+	src/dbus-timer.c \
 	src/dbus-target.c \
 	src/dbus-mount.c \
 	src/dbus-automount.c \
diff --git a/fixme b/fixme
index 6b6f7f7..7abf795 100644
--- a/fixme
+++ b/fixme
@@ -66,6 +66,8 @@
 
 * introduce exit.target for session instances
 
+* use _PATH_XXX
+
 Regularly:
 
 * look for close() vs. close_nointr() vs. close_nointr_nofail()
diff --git a/src/dbus-manager.c b/src/dbus-manager.c
index 6b658d1..6a32301 100644
--- a/src/dbus-manager.c
+++ b/src/dbus-manager.c
@@ -171,7 +171,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection  *connection
         const BusProperty properties[] = {
                 { "org.freedesktop.systemd1.Manager", "Version",       bus_property_append_string,    "s", PACKAGE_STRING     },
                 { "org.freedesktop.systemd1.Manager", "RunningAs",     bus_manager_append_running_as, "s", &m->running_as     },
-                { "org.freedesktop.systemd1.Manager", "BootTimestamp", bus_property_append_uint64,    "t", &m->boot_timestamp },
+                { "org.freedesktop.systemd1.Manager", "BootTimestamp", bus_property_append_uint64,    "t", &m->startup_timestamp.realtime },
                 { "org.freedesktop.systemd1.Manager", "LogLevel",      bus_manager_append_log_level,  "s", NULL               },
                 { "org.freedesktop.systemd1.Manager", "LogTarget",     bus_manager_append_log_target, "s", NULL               },
                 { "org.freedesktop.systemd1.Manager", "NNames",        bus_manager_append_n_names,    "u", NULL               },
diff --git a/src/dbus-timer.c b/src/dbus-timer.c
new file mode 100644
index 0000000..d572907
--- /dev/null
+++ b/src/dbus-timer.c
@@ -0,0 +1,52 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+
+#include "dbus-unit.h"
+#include "dbus-timer.h"
+#include "dbus-execute.h"
+
+#define BUS_TIMER_INTERFACE                                             \
+        " <interface name=\"org.freedesktop.systemd1.Timer\">\n"        \
+        "  <property name=\"Unit\" type=\"s\"  access=\"read\"/>\n"     \
+        " </interface>\n"                                               \
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_TIMER_INTERFACE                                             \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_timer_interface[] = BUS_TIMER_INTERFACE;
+
+DBusHandlerResult bus_timer_message_handler(Unit *u, DBusMessage *message) {
+        const BusProperty properties[] = {
+                BUS_UNIT_PROPERTIES,
+                { "org.freedesktop.systemd1.Timer", "Unit", bus_property_append_string, "s", &u->timer.unit->meta.id },
+                { NULL, NULL, NULL, NULL, NULL }
+        };
+
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
+}
diff --git a/src/dbus-timer.h b/src/dbus-timer.h
new file mode 100644
index 0000000..250e818
--- /dev/null
+++ b/src/dbus-timer.h
@@ -0,0 +1,33 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+#ifndef foodbustimerhfoo
+#define foodbustimerhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 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
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dbus/dbus.h>
+
+#include "unit.h"
+
+DBusHandlerResult bus_timer_message_handler(Unit *u, DBusMessage *message);
+
+extern const char bus_timer_interface[];
+
+#endif
diff --git a/src/dbus.c b/src/dbus.c
index 5caf1eb..6dd495d 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -40,6 +40,7 @@
 #include "dbus-automount.h"
 #include "dbus-snapshot.h"
 #include "dbus-swap.h"
+#include "dbus-timer.h"
 
 static const char bus_properties_interface[] = BUS_PROPERTIES_INTERFACE;
 static const char bus_introspectable_interface[] = BUS_INTROSPECTABLE_INTERFACE;
@@ -58,6 +59,7 @@ const char *const bus_interface_table[] = {
         "org.freedesktop.systemd1.Automount",  bus_automount_interface,
         "org.freedesktop.systemd1.Snapshot",   bus_snapshot_interface,
         "org.freedesktop.systemd1.Swap",       bus_swap_interface,
+        "org.freedesktop.systemd1.Timer",      bus_timer_interface,
         NULL
 };
 
diff --git a/src/load-fragment.c b/src/load-fragment.c
index 70b6923..889e621 100644
--- a/src/load-fragment.c
+++ b/src/load-fragment.c
@@ -1002,6 +1002,72 @@ static int config_parse_mount_flags(
         return 0;
 }
 
+static int config_parse_timer(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Timer *t = data;
+        usec_t u;
+        int r;
+        TimerValue *v;
+        TimerBase b;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if ((b = timer_base_from_string(lvalue)) < 0) {
+                log_error("[%s:%u] Failed to parse timer base: %s", filename, line, lvalue);
+                return -EINVAL;
+        }
+
+        if ((r = parse_usec(rvalue, &u)) < 0) {
+                log_error("[%s:%u] Failed to parse timer value: %s", filename, line, rvalue);
+                return r;
+        }
+
+        if (!(v = new0(TimerValue, 1)))
+                return -ENOMEM;
+
+        v->base = b;
+        v->value = u;
+
+        LIST_PREPEND(TimerValue, value, t->values, v);
+
+        return 0;
+}
+
+static int config_parse_timer_unit(
+                const char *filename,
+                unsigned line,
+                const char *section,
+                const char *lvalue,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        Timer *t = data;
+        int r;
+
+        if (endswith(rvalue, ".timer")) {
+                log_error("[%s:%u] Unit cannot be of type timer: %s", filename, line, rvalue);
+                return -EINVAL;
+        }
+
+        if ((r = manager_load_unit(t->meta.manager, rvalue, NULL, &t->unit)) < 0) {
+                log_error("[%s:%u] Failed to load unit: %s", filename, line, rvalue);
+                return r;
+        }
+
+        return 0;
+}
+
 #define FOLLOW_MAX 8
 
 static int open_follow(char **filename, FILE **_f, Set *names, char **_final) {
@@ -1161,6 +1227,8 @@ static void dump_items(FILE *f, const ConfigItem *items) {
                 { config_parse_path_strv,        "PATH [...]" },
                 { config_parse_mount_flags,      "MOUNTFLAG [...]" },
                 { config_parse_description,      "DESCRIPTION" },
+                { config_parse_timer,            "TIMER" },
+                { config_parse_timer_unit,       "NAME" },
         };
 
         assert(f);
@@ -1321,6 +1389,13 @@ static int load_from_path(Unit *u, const char *path) {
                 { "What",                   config_parse_path,            &u->swap.parameters_fragment.what,               "Swap" },
                 { "Priority",               config_parse_int,             &u->swap.parameters_fragment.priority,           "Swap" },
 
+                { "OnActive",               config_parse_timer,           &u->timer,                                       "Timer" },
+                { "OnBoot",                 config_parse_timer,           &u->timer,                                       "Timer" },
+                { "OnStartup",              config_parse_timer,           &u->timer,                                       "Timer" },
+                { "OnUnitActive",           config_parse_timer,           &u->timer,                                       "Timer" },
+                { "OnUnitInactive",         config_parse_timer,           &u->timer,                                       "Timer" },
+                { "Unit",                   config_parse_timer_unit,      &u->timer,                                       "Timer" },
+
                 { NULL, NULL, NULL, NULL }
         };
 
diff --git a/src/logger.c b/src/logger.c
index 2e036dd..5c7e4ee 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -89,7 +89,7 @@ struct Stream {
         LIST_FIELDS(Stream, stream);
 };
 
-static int stream_log(Stream *s, char *p, usec_t timestamp) {
+static int stream_log(Stream *s, char *p, usec_t ts) {
 
         char header_priority[16], header_time[64], header_pid[16];
         struct iovec iovec[5];
@@ -134,7 +134,7 @@ static int stream_log(Stream *s, char *p, usec_t timestamp) {
                 time_t t;
                 struct tm *tm;
 
-                t = (time_t) (timestamp / USEC_PER_SEC);
+                t = (time_t) (ts / USEC_PER_SEC);
                 if (!(tm = localtime(&t)))
                         return -EINVAL;
 
@@ -177,7 +177,7 @@ static int stream_log(Stream *s, char *p, usec_t timestamp) {
         return 0;
 }
 
-static int stream_line(Stream *s, char *p, usec_t timestamp) {
+static int stream_line(Stream *s, char *p, usec_t ts) {
         int r;
 
         assert(s);
@@ -236,13 +236,13 @@ static int stream_line(Stream *s, char *p, usec_t timestamp) {
                 return 0;
 
         case STREAM_RUNNING:
-                return stream_log(s, p, timestamp);
+                return stream_log(s, p, ts);
         }
 
         assert_not_reached("Unknown stream state");
 }
 
-static int stream_scan(Stream *s, usec_t timestamp) {
+static int stream_scan(Stream *s, usec_t ts) {
         char *p;
         size_t remaining;
         int r = 0;
@@ -259,7 +259,7 @@ static int stream_scan(Stream *s, usec_t timestamp) {
 
                 *newline = 0;
 
-                if ((r = stream_line(s, p, timestamp)) >= 0) {
+                if ((r = stream_line(s, p, ts)) >= 0) {
                         remaining -= newline-p+1;
                         p = newline+1;
                 }
@@ -273,7 +273,7 @@ static int stream_scan(Stream *s, usec_t timestamp) {
         return r;
 }
 
-static int stream_process(Stream *s, usec_t timestamp) {
+static int stream_process(Stream *s, usec_t ts) {
         ssize_t l;
         int r;
         assert(s);
@@ -292,7 +292,7 @@ static int stream_process(Stream *s, usec_t timestamp) {
                 return 0;
 
         s->length += l;
-        r = stream_scan(s, timestamp);
+        r = stream_scan(s, ts);
 
         if (r < 0)
                 return r;
@@ -501,10 +501,10 @@ static int process_event(Server *s, struct epoll_event *ev) {
                 }
 
         } else {
-                usec_t timestamp;
+                usec_t ts;
                 Stream *stream = ev->data.ptr;
 
-                timestamp = now(CLOCK_REALTIME);
+                ts = now(CLOCK_REALTIME);
 
                 if (!(ev->events & EPOLLIN)) {
                         log_info("Got invalid event from epoll. (2)");
@@ -512,7 +512,7 @@ static int process_event(Server *s, struct epoll_event *ev) {
                         return 0;
                 }
 
-                if ((r = stream_process(stream, timestamp)) <= 0) {
+                if ((r = stream_process(stream, ts)) <= 0) {
 
                         if (r < 0)
                                 log_info("Got error on stream: %s", strerror(-r));
diff --git a/src/manager.c b/src/manager.c
index 5d88875..2a773c6 100644
--- a/src/manager.c
+++ b/src/manager.c
@@ -361,7 +361,7 @@ int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) {
         if (!(m = new0(Manager, 1)))
                 return -ENOMEM;
 
-        m->boot_timestamp = now(CLOCK_REALTIME);
+        timestamp_get(&m->startup_timestamp);
 
         m->running_as = running_as;
         m->confirm_spawn = confirm_spawn;
@@ -2101,7 +2101,7 @@ void manager_write_utmp_reboot(Manager *m) {
         if (!manager_utmp_good(m))
                 return;
 
-        if ((r = utmp_put_reboot(m->boot_timestamp)) < 0) {
+        if ((r = utmp_put_reboot(m->startup_timestamp.realtime)) < 0) {
 
                 if (r != -ENOENT && r != -EROFS)
                         log_warning("Failed to write utmp/wtmp: %s", strerror(-r));
diff --git a/src/manager.h b/src/manager.h
index 7892300..9548b0f 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -179,7 +179,7 @@ struct Manager {
 
         char **environment;
 
-        usec_t boot_timestamp;
+        timestamp startup_timestamp;
 
         /* Data specific to the device subsystem */
         struct udev* udev;
diff --git a/src/ratelimit.c b/src/ratelimit.c
index 1e5ed03..fc0f71d 100644
--- a/src/ratelimit.c
+++ b/src/ratelimit.c
@@ -28,21 +28,21 @@
  * <hidave.darkstar at gmail.com>, which is licensed GPLv2. */
 
 bool ratelimit_test(RateLimit *r) {
-        usec_t timestamp;
+        usec_t ts;
 
-        timestamp = now(CLOCK_MONOTONIC);
+        ts = now(CLOCK_MONOTONIC);
 
         assert(r);
         assert(r->interval > 0);
         assert(r->burst > 0);
 
         if (r->begin <= 0 ||
-            r->begin + r->interval < timestamp) {
+            r->begin + r->interval < ts) {
 
                 if (r->n_missed > 0)
                         log_warning("%u events suppressed", r->n_missed);
 
-                r->begin = timestamp;
+                r->begin = ts;
 
                 /* Reset counters */
                 r->n_printed = 0;
diff --git a/src/socket.c b/src/socket.c
index 4647d31..ef7674f 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -1189,6 +1189,9 @@ static void socket_fd_event(Unit *u, int fd, uint32_t events, Watch *w) {
         assert(s);
         assert(fd >= 0);
 
+        if (s->state != SOCKET_LISTENING)
+                return;
+
         log_debug("Incoming traffic on %s", u->meta.id);
 
         if (events != EPOLLIN) {
diff --git a/src/timer.c b/src/timer.c
index 41aeb7f..e95b4d6 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -22,30 +22,442 @@
 #include <errno.h>
 
 #include "unit.h"
+#include "unit-name.h"
 #include "timer.h"
+#include "dbus-timer.h"
+
+static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = {
+        [TIMER_DEAD] = UNIT_INACTIVE,
+        [TIMER_WAITING] = UNIT_ACTIVE,
+        [TIMER_RUNNING] = UNIT_ACTIVE,
+        [TIMER_ELAPSED] = UNIT_ACTIVE,
+        [TIMER_MAINTAINANCE] = UNIT_INACTIVE
+};
+
+static void timer_init(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(u);
+        assert(u->meta.load_state == UNIT_STUB);
+
+        t->next_elapse = (usec_t) -1;
+        t->timer_watch.type = WATCH_INVALID;
+}
 
 static void timer_done(Unit *u) {
         Timer *t = TIMER(u);
+        TimerValue *v;
+
+        assert(t);
+
+        while ((v = t->values)) {
+                LIST_REMOVE(TimerValue, value, t->values, v);
+                free(v);
+        }
+
+        unit_unwatch_timer(u, &t->timer_watch);
+}
+
+static int timer_verify(Timer *t) {
+        assert(t);
+
+        if (UNIT(t)->meta.load_state != UNIT_LOADED)
+                return 0;
+
+        if (!t->values) {
+                log_error("%s lacks value setting. Refusing.", t->meta.id);
+                return -EINVAL;
+        }
+
+        return 0;
+}
+
+static int timer_load(Unit *u) {
+        Timer *t = TIMER(u);
+        int r;
+
+        assert(u);
+        assert(u->meta.load_state == UNIT_STUB);
+
+        if ((r = unit_load_fragment_and_dropin(u)) < 0)
+                return r;
+
+        if (u->meta.load_state == UNIT_LOADED) {
+
+                if (!t->unit)
+                        if ((r = unit_load_related_unit(u, ".service", &t->unit)))
+                                return r;
+
+                if ((r = unit_add_dependency(u, UNIT_BEFORE, t->unit, true)) < 0)
+                        return r;
+        }
+
+        return timer_verify(t);
+}
+
+static void timer_dump(Unit *u, FILE *f, const char *prefix) {
+        Timer *t = TIMER(u);
+        const char *prefix2;
+        char *p2;
+        TimerValue *v;
+        char
+                timespan1[FORMAT_TIMESPAN_MAX];
 
+        p2 = strappend(prefix, "\t");
+        prefix2 = p2 ? p2 : prefix;
+
+        fprintf(f,
+                "%sTimer State: %s\n"
+                "%sUnit: %s\n",
+                prefix, timer_state_to_string(t->state),
+                prefix, t->unit->meta.id);
+
+        LIST_FOREACH(value, v, t->values)
+                fprintf(f,
+                        "%s%s: %s\n",
+                        prefix,
+                        timer_base_to_string(v->base),
+                        strna(format_timespan(timespan1, sizeof(timespan1), v->value)));
+
+        free(p2);
+}
+
+static void timer_set_state(Timer *t, TimerState state) {
+        TimerState old_state;
         assert(t);
+
+        old_state = t->state;
+        t->state = state;
+
+        if (state != TIMER_WAITING)
+                unit_unwatch_timer(UNIT(t), &t->timer_watch);
+
+        if (state != old_state)
+                log_debug("%s changed %s -> %s",
+                          t->meta.id,
+                          timer_state_to_string(old_state),
+                          timer_state_to_string(state));
+
+        unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state]);
+}
+
+static void timer_enter_waiting(Timer *t, bool initial);
+
+static int timer_coldplug(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+        assert(t->state == TIMER_DEAD);
+
+        if (t->deserialized_state != t->state) {
+
+                if (t->deserialized_state == TIMER_WAITING ||
+                    t->deserialized_state == TIMER_RUNNING ||
+                    t->deserialized_state == TIMER_ELAPSED)
+                        timer_enter_waiting(t, false);
+                else
+                        timer_set_state(t, t->deserialized_state);
+        }
+
+        return 0;
+}
+
+static void timer_enter_dead(Timer *t, bool success) {
+        assert(t);
+
+        if (!success)
+                t->failure = true;
+
+        timer_set_state(t, t->failure ? TIMER_MAINTAINANCE : TIMER_DEAD);
+}
+
+static void timer_enter_waiting(Timer *t, bool initial) {
+        TimerValue *v;
+        usec_t base = 0, delay, n;
+        bool found = false;
+        int r;
+
+        n = now(CLOCK_MONOTONIC);
+
+        LIST_FOREACH(value, v, t->values) {
+
+                if (v->disabled)
+                        continue;
+
+                switch (v->base) {
+
+                case TIMER_ACTIVE:
+                        if (state_translation_table[t->state] == UNIT_ACTIVE) {
+                                base = t->meta.inactive_exit_timestamp.monotonic;
+                        } else
+                                base = n;
+                        break;
+
+                case TIMER_BOOT:
+                        /* CLOCK_MONOTONIC equals the uptime on Linux */
+                        base = 0;
+                        break;
+
+                case TIMER_STARTUP:
+                        base = t->meta.manager->startup_timestamp.monotonic;
+                        break;
+
+                case TIMER_UNIT_ACTIVE:
+
+                        if (t->unit->meta.inactive_exit_timestamp.monotonic <= 0)
+                                continue;
+
+                        base = t->unit->meta.inactive_exit_timestamp.monotonic;
+                        break;
+
+                case TIMER_UNIT_INACTIVE:
+
+                        if (t->unit->meta.inactive_enter_timestamp.monotonic <= 0)
+                                continue;
+
+                        base = t->unit->meta.inactive_enter_timestamp.monotonic;
+                        break;
+
+                default:
+                        assert_not_reached("Unknown timer base");
+                }
+
+                v->next_elapse = base + v->value;
+
+                if (!initial && v->next_elapse < n) {
+                        v->disabled = true;
+                        continue;
+                }
+
+                if (!found)
+                        t->next_elapse = v->next_elapse;
+                else
+                        t->next_elapse = MIN(t->next_elapse, v->next_elapse);
+
+                found = true;
+        }
+
+        if (!found) {
+                timer_set_state(t, TIMER_ELAPSED);
+                return;
+        }
+
+        delay = n < t->next_elapse ? t->next_elapse - n : 0;
+
+        if ((r = unit_watch_timer(UNIT(t), delay, &t->timer_watch)) < 0)
+                goto fail;
+
+        timer_set_state(t, TIMER_WAITING);
+        return;
+
+fail:
+        log_warning("%s failed to enter waiting state: %s", t->meta.id, strerror(-r));
+        timer_enter_dead(t, false);
+}
+
+static void timer_enter_running(Timer *t) {
+        int r;
+        assert(t);
+
+        if ((r = manager_add_job(UNIT(t)->meta.manager, JOB_START, t->unit, JOB_REPLACE, true, NULL)) < 0)
+                goto fail;
+
+        timer_set_state(t, TIMER_RUNNING);
+        return;
+
+fail:
+        log_warning("%s failed to queue unit startup job: %s", t->meta.id, strerror(-r));
+        timer_enter_dead(t, false);
+}
+
+static int timer_start(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+        assert(t->state == TIMER_DEAD);
+
+        timer_enter_waiting(t, true);
+        return 0;
+}
+
+static int timer_stop(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+        assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED);
+
+        timer_enter_dead(t, true);
+        return 0;
+}
+
+static int timer_serialize(Unit *u, FILE *f, FDSet *fds) {
+        Timer *t = TIMER(u);
+
+        assert(u);
+        assert(f);
+        assert(fds);
+
+        unit_serialize_item(u, f, "state", timer_state_to_string(t->state));
+
+        return 0;
+}
+
+static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) {
+        Timer *t = TIMER(u);
+
+        assert(u);
+        assert(key);
+        assert(value);
+        assert(fds);
+
+        if (streq(key, "state")) {
+                TimerState state;
+
+                if ((state = timer_state_from_string(value)) < 0)
+                        log_debug("Failed to parse state value %s", value);
+                else
+                        t->deserialized_state = state;
+        } else
+                log_debug("Unknown serialization key '%s'", key);
+
+        return 0;
 }
 
 static UnitActiveState timer_active_state(Unit *u) {
+        assert(u);
+
+        return state_translation_table[TIMER(u)->state];
+}
+
+static const char *timer_sub_state_to_string(Unit *u) {
+        assert(u);
+
+        return timer_state_to_string(TIMER(u)->state);
+}
+
+static void timer_timer_event(Unit *u, uint64_t elapsed, Watch *w) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+        assert(elapsed == 1);
 
-        static const UnitActiveState table[_TIMER_STATE_MAX] = {
-                [TIMER_DEAD] = UNIT_INACTIVE,
-                [TIMER_WAITING] = UNIT_ACTIVE,
-                [TIMER_RUNNING] = UNIT_ACTIVE
-        };
+        if (t->state != TIMER_WAITING)
+                return;
 
-        return table[TIMER(u)->state];
+        log_debug("Timer elapsed on %s", u->meta.id);
+        timer_enter_running(t);
 }
 
+void timer_unit_notify(Unit *u, UnitActiveState new_state) {
+        char *n;
+        int r;
+        Iterator i;
+
+        if (u->meta.type == UNIT_TIMER)
+                return;
+
+        SET_FOREACH(n, u->meta.names, i) {
+                char *k;
+                Unit *p;
+                Timer *t;
+                TimerValue *v;
+
+                if (!(k = unit_name_change_suffix(n, ".timer"))) {
+                        r = -ENOMEM;
+                        goto fail;
+                }
+
+                p = manager_get_unit(u->meta.manager, k);
+                free(k);
+
+                if (!p)
+                        continue;
+
+                t = TIMER(p);
+
+                if (t->meta.load_state != UNIT_LOADED)
+                        continue;
+
+                /* Reenable all timers that depend on unit state */
+                LIST_FOREACH(value, v, t->values)
+                        if (v->base == TIMER_UNIT_ACTIVE ||
+                            v->base == TIMER_UNIT_INACTIVE)
+                                v->disabled = false;
+
+                switch (t->state) {
+
+                case TIMER_WAITING:
+                case TIMER_ELAPSED:
+
+                        /* Recalculate sleep time */
+                        timer_enter_waiting(t, false);
+                        break;
+
+                case TIMER_RUNNING:
+
+                        if (new_state == UNIT_INACTIVE) {
+                                log_debug("%s got notified about unit deactivation.", t->meta.id);
+                                timer_enter_waiting(t, false);
+                        }
+
+                        break;
+
+                case TIMER_DEAD:
+                case TIMER_MAINTAINANCE:
+                        ;
+
+                default:
+                        assert_not_reached("Unknown timer state");
+                }
+        }
+
+        return;
+
+fail:
+        log_error("Failed find timer unit: %s", strerror(-r));
+}
+
+static const char* const timer_state_table[_TIMER_STATE_MAX] = {
+        [TIMER_DEAD] = "dead",
+        [TIMER_WAITING] = "waiting",
+        [TIMER_RUNNING] = "running",
+        [TIMER_ELAPSED] = "elapsed",
+        [TIMER_MAINTAINANCE] = "maintainance"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState);
+
+static const char* const timer_base_table[_TIMER_BASE_MAX] = {
+        [TIMER_ACTIVE] = "OnActive",
+        [TIMER_BOOT] = "OnBoot",
+        [TIMER_STARTUP] = "OnStartup",
+        [TIMER_UNIT_ACTIVE] = "OnUnitActive",
+        [TIMER_UNIT_INACTIVE] = "OnUnitInactive"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase);
+
 const UnitVTable timer_vtable = {
         .suffix = ".timer",
 
-        .load = unit_load_fragment_and_dropin,
+        .init = timer_init,
         .done = timer_done,
+        .load = timer_load,
+
+        .coldplug = timer_coldplug,
+
+        .dump = timer_dump,
+
+        .start = timer_start,
+        .stop = timer_stop,
+
+        .serialize = timer_serialize,
+        .deserialize_item = timer_deserialize_item,
+
+        .active_state = timer_active_state,
+        .sub_state_to_string = timer_sub_state_to_string,
+
+        .timer_event = timer_timer_event,
 
-        .active_state = timer_active_state
+        .bus_message_handler = bus_timer_message_handler
 };
diff --git a/src/timer.h b/src/timer.h
index 0ec3e0d..69c5609 100644
--- a/src/timer.h
+++ b/src/timer.h
@@ -30,20 +30,57 @@ typedef enum TimerState {
         TIMER_DEAD,
         TIMER_WAITING,
         TIMER_RUNNING,
-        _TIMER_STATE_MAX
+        TIMER_ELAPSED,
+        TIMER_MAINTAINANCE,
+        _TIMER_STATE_MAX,
+        _TIMER_STATE_INVALID = -1
 } TimerState;
 
+typedef enum TimerBase {
+        TIMER_ACTIVE,
+        TIMER_BOOT,
+        TIMER_STARTUP,
+        TIMER_UNIT_ACTIVE,
+        TIMER_UNIT_INACTIVE,
+        _TIMER_BASE_MAX,
+        _TIMER_BASE_INVALID = -1
+} TimerBase;
+
+typedef struct TimerValue {
+        TimerBase base;
+        usec_t value;
+
+        usec_t next_elapse;
+
+        bool disabled;
+
+        LIST_FIELDS(struct TimerValue, value);
+} TimerValue;
+
 struct Timer {
         Meta meta;
 
-        TimerState state;
+        LIST_HEAD(TimerValue, values);
+
+        TimerState state, deserialized_state;
 
-        clockid_t clock_id;
         usec_t next_elapse;
 
-        Service *service;
+        Unit *unit;
+
+        Watch timer_watch;
+
+        bool failure;
 };
 
+void timer_unit_notify(Unit *u, UnitActiveState new_state);
+
 extern const UnitVTable timer_vtable;
 
+const char *timer_state_to_string(TimerState i);
+TimerState timer_state_from_string(const char *s);
+
+const char *timer_base_to_string(TimerBase i);
+TimerBase timer_base_from_string(const char *s);
+
 #endif
diff --git a/src/unit.c b/src/unit.c
index b38be31..2af2685 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -600,10 +600,10 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                 prefix, strna(u->meta.instance),
                 prefix, unit_load_state_to_string(u->meta.load_state),
                 prefix, unit_active_state_to_string(unit_active_state(u)),
-                prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->meta.inactive_exit_timestamp)),
-                prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->meta.active_enter_timestamp)),
-                prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->meta.active_exit_timestamp)),
-                prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->meta.inactive_enter_timestamp)),
+                prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->meta.inactive_exit_timestamp.realtime)),
+                prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->meta.active_enter_timestamp.realtime)),
+                prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->meta.active_exit_timestamp.realtime)),
+                prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->meta.inactive_enter_timestamp.realtime)),
                 prefix, yes_no(unit_check_gc(u)),
                 prefix, yes_no(u->meta.only_by_dependency));
 
@@ -930,7 +930,7 @@ static void retroactively_stop_dependencies(Unit *u) {
 
 void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
         bool unexpected = false;
-        usec_t ts;
+        timestamp ts;
 
         assert(u);
         assert(os < _UNIT_ACTIVE_STATE_MAX);
@@ -943,7 +943,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
          * this function will be called too and the utmp code below
          * relies on that! */
 
-        ts = now(CLOCK_REALTIME);
+        timestamp_get(&ts);
 
         if (os == UNIT_INACTIVE && ns != UNIT_INACTIVE)
                 u->meta.inactive_exit_timestamp = ts;
@@ -955,6 +955,8 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
         else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns))
                 u->meta.active_exit_timestamp = ts;
 
+        timer_unit_notify(u, ns);
+
         if (u->meta.job) {
 
                 if (u->meta.job->state == JOB_WAITING)
diff --git a/src/unit.h b/src/unit.h
index d8be185..fd0defe 100644
--- a/src/unit.h
+++ b/src/unit.h
@@ -152,10 +152,10 @@ struct Meta {
          * the job for it */
         Job *job;
 
-        usec_t inactive_exit_timestamp;
-        usec_t active_enter_timestamp;
-        usec_t active_exit_timestamp;
-        usec_t inactive_enter_timestamp;
+        timestamp inactive_exit_timestamp;
+        timestamp active_enter_timestamp;
+        timestamp active_exit_timestamp;
+        timestamp inactive_enter_timestamp;
 
         /* Counterparts in the cgroup filesystem */
         CGroupBonding *cgroup_bondings;
diff --git a/src/util.c b/src/util.c
index 85a8e37..a8ea4a9 100644
--- a/src/util.c
+++ b/src/util.c
@@ -72,6 +72,15 @@ usec_t now(clockid_t clock_id) {
         return timespec_load(&ts);
 }
 
+timestamp* timestamp_get(timestamp *ts) {
+        assert(ts);
+
+        ts->realtime = now(CLOCK_REALTIME);
+        ts->monotonic = now(CLOCK_MONOTONIC);
+
+        return ts;
+}
+
 usec_t timespec_load(const struct timespec *ts) {
         assert(ts);
 
@@ -1398,6 +1407,55 @@ char *format_timestamp(char *buf, size_t l, usec_t t) {
         return buf;
 }
 
+char *format_timespan(char *buf, size_t l, usec_t t) {
+        static const struct {
+                const char *suffix;
+                usec_t usec;
+        } table[] = {
+                { "w", USEC_PER_WEEK },
+                { "d", USEC_PER_DAY },
+                { "h", USEC_PER_HOUR },
+                { "min", USEC_PER_MINUTE },
+                { "s", USEC_PER_SEC },
+                { "ms", USEC_PER_MSEC },
+                { "us", 1 },
+        };
+
+        unsigned i;
+        char *p = buf;
+
+        assert(buf);
+        assert(l > 0);
+
+        if (t == (usec_t) -1)
+                return NULL;
+
+        /* The result of this function can be parsed with parse_usec */
+
+        for (i = 0; i < ELEMENTSOF(table); i++) {
+                int k;
+                size_t n;
+
+                if (t < table[i].usec)
+                        continue;
+
+                if (l <= 1)
+                        break;
+
+                k = snprintf(p, l, "%s%llu%s", p > buf ? " " : "", (unsigned long long) (t / table[i].usec), table[i].suffix);
+                n = MIN((size_t) k, l);
+
+                l -= n;
+                p += n;
+
+                t %= table[i].usec;
+        }
+
+        *p = 0;
+
+        return buf;
+}
+
 bool fstype_is_network(const char *fstype) {
         static const char * const table[] = {
                 "cifs",
diff --git a/src/util.h b/src/util.h
index ccb9769..b411df0 100644
--- a/src/util.h
+++ b/src/util.h
@@ -32,6 +32,11 @@
 
 typedef uint64_t usec_t;
 
+typedef struct timestamp {
+        usec_t realtime;
+        usec_t monotonic;
+} timestamp;
+
 #define MSEC_PER_SEC  1000ULL
 #define USEC_PER_SEC  1000000ULL
 #define USEC_PER_MSEC 1000ULL
@@ -49,9 +54,12 @@ typedef uint64_t usec_t;
 #define NEWLINE "\n\r"
 
 #define FORMAT_TIMESTAMP_MAX 64
+#define FORMAT_TIMESPAN_MAX 64
 
 usec_t now(clockid_t clock);
 
+timestamp* timestamp_get(timestamp *ts);
+
 usec_t timespec_load(const struct timespec *ts);
 struct timespec *timespec_store(struct timespec *ts, usec_t u);
 
@@ -181,6 +189,7 @@ bool ignore_file(const char *filename);
 bool chars_intersect(const char *a, const char *b);
 
 char *format_timestamp(char *buf, size_t l, usec_t t);
+char *format_timespan(char *buf, size_t l, usec_t t);
 
 int make_stdio(int fd);
 
diff --git a/src/utmp-wtmp.c b/src/utmp-wtmp.c
index cb3f201..ba0273f 100644
--- a/src/utmp-wtmp.c
+++ b/src/utmp-wtmp.c
@@ -89,7 +89,7 @@ int utmp_get_runlevel(int *runlevel, int *previous) {
         return r;
 }
 
-static void init_entry(struct utmpx *store, usec_t timestamp) {
+static void init_entry(struct utmpx *store, usec_t t) {
         struct utsname uts;
 
         assert(store);
@@ -97,11 +97,11 @@ static void init_entry(struct utmpx *store, usec_t timestamp) {
         zero(*store);
         zero(uts);
 
-        if (timestamp <= 0)
-                timestamp = now(CLOCK_REALTIME);
+        if (t <= 0)
+                t = now(CLOCK_REALTIME);
 
-        store->ut_tv.tv_sec = timestamp / USEC_PER_SEC;
-        store->ut_tv.tv_usec = timestamp % USEC_PER_SEC;
+        store->ut_tv.tv_sec = t / USEC_PER_SEC;
+        store->ut_tv.tv_usec = t % USEC_PER_SEC;
 
         if (uname(&uts) >= 0)
                 strncpy(store->ut_host, uts.release, sizeof(store->ut_host));
@@ -162,10 +162,10 @@ static int write_entry_both(const struct utmpx *store) {
         return r;
 }
 
-int utmp_put_shutdown(usec_t timestamp) {
+int utmp_put_shutdown(usec_t t) {
         struct utmpx store;
 
-        init_entry(&store, timestamp);
+        init_entry(&store, t);
 
         store.ut_type = RUN_LVL;
         strncpy(store.ut_user, "shutdown", sizeof(store.ut_user));
@@ -173,10 +173,10 @@ int utmp_put_shutdown(usec_t timestamp) {
         return write_entry_both(&store);
 }
 
-int utmp_put_reboot(usec_t timestamp) {
+int utmp_put_reboot(usec_t t) {
         struct utmpx store;
 
-        init_entry(&store, timestamp);
+        init_entry(&store, t);
 
         store.ut_type = BOOT_TIME;
         strncpy(store.ut_user, "reboot", sizeof(store.ut_user));
@@ -184,7 +184,7 @@ int utmp_put_reboot(usec_t timestamp) {
         return write_entry_both(&store);
 }
 
-int utmp_put_runlevel(usec_t timestamp, int runlevel, int previous) {
+int utmp_put_runlevel(usec_t t, int runlevel, int previous) {
         struct utmpx store;
         int r;
 
@@ -204,7 +204,7 @@ int utmp_put_runlevel(usec_t timestamp, int runlevel, int previous) {
                         return 0;
         }
 
-        init_entry(&store, timestamp);
+        init_entry(&store, t);
 
         store.ut_type = RUN_LVL;
         store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8);
commit 4288f619215e3dda0b75113d78e4fb7ba219ed58
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sun May 23 03:45:33 2010 +0200

    dbus: automatically generate and install introspection files

diff --git a/.gitignore b/.gitignore
index 700a085..de52b6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+org.freedesktop.systemd1.*.xml
 test-ns
 test-loopback
 systemd-cgroups-agent
diff --git a/Makefile.am b/Makefile.am
index ffa9ad9..b3e9dfd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,6 +23,7 @@ udevrulesdir=@udevrulesdir@
 pkgsysconfdir=$(sysconfdir)/systemd
 systemunitdir=$(pkgdatadir)/system
 sessionunitdir=$(pkgdatadir)/session
+interfacedir=$(datadir)/dbus-1/interfaces
 
 AM_CPPFLAGS = \
         -include $(top_builddir)/config.h \
@@ -64,6 +65,19 @@ dist_dbuspolicy_DATA = \
 dist_udevrules_DATA = \
 	src/99-systemd.rules
 
+interface_DATA = \
+	org.freedesktop.systemd1.Manager.xml \
+	org.freedesktop.systemd1.Job.xml \
+	org.freedesktop.systemd1.Unit.xml \
+	org.freedesktop.systemd1.Service.xml \
+	org.freedesktop.systemd1.Socket.xml \
+	org.freedesktop.systemd1.Target.xml \
+	org.freedesktop.systemd1.Device.xml \
+	org.freedesktop.systemd1.Mount.xml \
+	org.freedesktop.systemd1.Automount.xml \
+	org.freedesktop.systemd1.Snapshot.xml \
+	org.freedesktop.systemd1.Swap.xml
+
 dist_systemunit_DATA = \
 	units/emergency.service \
 	units/getty.target \
@@ -426,6 +440,11 @@ CLEANFILES += \
 	man/systemd.special.html.in
 endif
 
+org.freedesktop.systemd1.%.xml: systemd
+	$(AM_V_GEN)./systemd --introspect=${@:.xml=} > $@
+
+CLEANFILES += $(interface_DATA)
+
 install-data-hook:
 	$(MKDIR_P) -m 0755 \
 		$(DESTDIR)$(systemunitdir) \
diff --git a/fixme b/fixme
index b6752d9..6b6f7f7 100644
--- a/fixme
+++ b/fixme
@@ -64,6 +64,8 @@
 
 * tcpwrap
 
+* introduce exit.target for session instances
+
 Regularly:
 
 * look for close() vs. close_nointr() vs. close_nointr_nofail()
diff --git a/src/dbus-automount.c b/src/dbus-automount.c
index 9003b74..285f666 100644
--- a/src/dbus-automount.c
+++ b/src/dbus-automount.c
@@ -22,16 +22,21 @@
 #include "dbus-unit.h"
 #include "dbus-automount.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        BUS_UNIT_INTERFACE
-        BUS_PROPERTIES_INTERFACE
-        " <interface name=\"org.freedesktop.systemd1.Automount\">"
-        "  <property name=\"Where\" type=\"s\" access=\"read\"/>"
-        " </interface>"
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_AUTOMOUNT_INTERFACE                                      \
+        " <interface name=\"org.freedesktop.systemd1.Automount\">\n" \
+        "  <property name=\"Where\" type=\"s\" access=\"read\"/>\n"  \
+        " </interface>\n"
+
+#define INTROSPECTION                                                \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                    \
+        "<node>\n"                                                   \
+        BUS_UNIT_INTERFACE                                           \
+        BUS_AUTOMOUNT_INTERFACE                                      \
+        BUS_PROPERTIES_INTERFACE                                     \
+        BUS_INTROSPECTABLE_INTERFACE                                 \
+        "</node>\n"
+
+const char bus_automount_interface[] = BUS_AUTOMOUNT_INTERFACE;
 
 DBusHandlerResult bus_automount_message_handler(Unit *u, DBusMessage *message) {
         const BusProperty properties[] = {
@@ -40,5 +45,5 @@ DBusHandlerResult bus_automount_message_handler(Unit *u, DBusMessage *message) {
                 { NULL, NULL, NULL, NULL, NULL }
         };
 
-        return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
 }
diff --git a/src/dbus-automount.h b/src/dbus-automount.h
index 947bf0f..5e0ee51 100644
--- a/src/dbus-automount.h
+++ b/src/dbus-automount.h
@@ -28,4 +28,6 @@
 
 DBusHandlerResult bus_automount_message_handler(Unit *u, DBusMessage *message);
 
+extern const char bus_automount_interface[];
+
 #endif
diff --git a/src/dbus-device.c b/src/dbus-device.c
index 8376478..0610ab8 100644
--- a/src/dbus-device.c
+++ b/src/dbus-device.c
@@ -22,16 +22,21 @@
 #include "dbus-unit.h"
 #include "dbus-device.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        BUS_UNIT_INTERFACE
-        BUS_PROPERTIES_INTERFACE
-        " <interface name=\"org.freedesktop.systemd1.Device\">"
-        "  <property name=\"SysFSPath\" type=\"s\" access=\"read\"/>"
-        " </interface>"
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_DEVICE_INTERFACE                                            \
+        " <interface name=\"org.freedesktop.systemd1.Device\">\n"       \
+        "  <property name=\"SysFSPath\" type=\"s\" access=\"read\"/>\n" \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_DEVICE_INTERFACE                                            \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_device_interface[] = BUS_DEVICE_INTERFACE;
 
 DBusHandlerResult bus_device_message_handler(Unit *u, DBusMessage *message) {
         const BusProperty properties[] = {
@@ -40,5 +45,5 @@ DBusHandlerResult bus_device_message_handler(Unit *u, DBusMessage *message) {
                 { NULL, NULL, NULL, NULL, NULL }
         };
 
-        return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
 }
diff --git a/src/dbus-device.h b/src/dbus-device.h
index f2850a6..55bb8f5 100644
--- a/src/dbus-device.h
+++ b/src/dbus-device.h
@@ -28,4 +28,6 @@
 
 DBusHandlerResult bus_device_message_handler(Unit *u, DBusMessage *message);
 
+extern const char bus_device_interface[];
+
 #endif
diff --git a/src/dbus-execute.h b/src/dbus-execute.h
index 25ecd98..6abae16 100644
--- a/src/dbus-execute.h
+++ b/src/dbus-execute.h
@@ -27,23 +27,23 @@
 #include "manager.h"
 
 #define BUS_EXEC_CONTEXT_INTERFACE                                      \
-        "  <property name=\"Environment\" type=\"as\" access=\"read\"/>" \
-        "  <property name=\"UMask\" type=\"u\" access=\"read\"/>"       \
-        "  <property name=\"WorkingDirectory\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"RootDirectory\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"CPUSchedulingResetOnFork\" type=\"b\" access=\"read\"/>" \
-        "  <property name=\"NonBlocking\" type=\"b\" access=\"read\"/>" \
-        "  <property name=\"StandardInput\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"StandardOutput\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"StandardError\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"TTYPath\" type=\"s\" access=\"read\"/>"     \
-        "  <property name=\"SyslogPriority\" type=\"i\" access=\"read\"/>" \
-        "  <property name=\"SyslogIdentifier\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"SecureBits\" type=\"i\" access=\"read\"/>"  \
-        "  <property name=\"CapabilityBoundingSetDrop\" type=\"t\" access=\"read\"/>" \
-        "  <property name=\"User\" type=\"s\" access=\"read\"/>"        \
-        "  <property name=\"Group\" type=\"s\" access=\"read\"/>"       \
-        "  <property name=\"SupplementaryGroups\" type=\"as\" access=\"read\"/>"
+        "  <property name=\"Environment\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"UMask\" type=\"u\" access=\"read\"/>\n"     \
+        "  <property name=\"WorkingDirectory\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"RootDirectory\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"CPUSchedulingResetOnFork\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"NonBlocking\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"StandardInput\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"StandardOutput\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"StandardError\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"TTYPath\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"SyslogPriority\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"SyslogIdentifier\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"SecureBits\" type=\"i\" access=\"read\"/>\n" \
+        "  <property name=\"CapabilityBoundingSetDrop\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"User\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Group\" type=\"s\" access=\"read\"/>\n"     \
+        "  <property name=\"SupplementaryGroups\" type=\"as\" access=\"read\"/>\n"
 
 #define BUS_EXEC_CONTEXT_PROPERTIES(interface, context)                 \
         { interface, "Environment",                   bus_property_append_strv,   "as",    (context).environment                   }, \
diff --git a/src/dbus-job.c b/src/dbus-job.c
index 34e64fc..7346252 100644
--- a/src/dbus-job.c
+++ b/src/dbus-job.c
@@ -25,20 +25,25 @@
 #include "log.h"
 #include "dbus-job.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        " <interface name=\"org.freedesktop.systemd1.Job\">"
-        "  <method name=\"Cancel\"/>"
-        "  <signal name=\"Changed\"/>"
-        "  <property name=\"Id\" type=\"u\" access=\"read\"/>"
-        "  <property name=\"Unit\" type=\"(so)\" access=\"read\"/>"
-        "  <property name=\"JobType\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"State\" type=\"s\" access=\"read\"/>"
-        " </interface>"
-        BUS_PROPERTIES_INTERFACE
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_JOB_INTERFACE                                             \
+        " <interface name=\"org.freedesktop.systemd1.Job\">\n"        \
+        "  <method name=\"Cancel\"/>\n"                               \
+        "  <signal name=\"Changed\"/>\n"                              \
+        "  <property name=\"Id\" type=\"u\" access=\"read\"/>\n"      \
+        "  <property name=\"Unit\" type=\"(so)\" access=\"read\"/>\n" \
+        "  <property name=\"JobType\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"State\" type=\"s\" access=\"read\"/>\n"   \
+        " </interface>\n"
+
+#define INTROSPECTION                                                 \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                     \
+        "<node>\n"                                                    \
+        BUS_JOB_INTERFACE                                             \
+        BUS_PROPERTIES_INTERFACE                                      \
+        BUS_INTROSPECTABLE_INTERFACE                                  \
+        "</node>\n"
+
+const char bus_job_interface[] = BUS_JOB_INTERFACE;
 
 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_state, job_state, JobState);
 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_job_append_type, job_type, JobType);
@@ -92,7 +97,7 @@ static DBusHandlerResult bus_job_message_dispatch(Job *j, DBusMessage *message)
                 job_free(j);
 
         } else
-                return bus_default_message_handler(j->manager, message, introspection, properties);
+                return bus_default_message_handler(j->manager, message, INTROSPECTION, properties);
 
         if (reply) {
                 if (!dbus_connection_send(m->api_bus, reply, NULL))
diff --git a/src/dbus-job.h b/src/dbus-job.h
index 4602358..2b79423 100644
--- a/src/dbus-job.h
+++ b/src/dbus-job.h
@@ -29,4 +29,6 @@ void bus_job_send_removed_signal(Job *j, bool success);
 
 extern const DBusObjectPathVTable bus_job_vtable;
 
+extern const char bus_job_interface[];
+
 #endif
diff --git a/src/dbus-manager.c b/src/dbus-manager.c
index 9833b6c..6b658d1 100644
--- a/src/dbus-manager.c
+++ b/src/dbus-manager.c
@@ -26,77 +26,82 @@
 #include "dbus-manager.h"
 #include "strv.h"
 
+#define BUS_MANAGER_INTERFACE                                           \
+        " <interface name=\"org.freedesktop.systemd1.Manager\">\n"      \
+        "  <method name=\"GetUnit\">\n"                                 \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"LoadUnit\">\n"                                \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"GetJob\">\n"                                  \
+        "   <arg name=\"id\" type=\"u\" direction=\"in\"/>\n"           \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"ClearJobs\"/>\n"                              \
+        "  <method name=\"ListUnits\">\n"                               \
+        "   <arg name=\"units\" type=\"a(sssssouso)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"ListJobs\">\n"                                \
+        "   <arg name=\"jobs\" type=\"a(usssoo)\" direction=\"out\"/>\n" \
+        "  </method>\n"                                                 \
+        "  <method name=\"Subscribe\"/>\n"                              \
+        "  <method name=\"Unsubscribe\"/>\n"                            \
+        "  <method name=\"Dump\"/>\n"                                   \
+        "  <method name=\"CreateSnapshot\">\n"                          \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg nane=\"cleanup\" type=\"b\" direction=\"in\"/>\n"      \
+        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>\n"        \
+        "  </method>\n"                                                 \
+        "  <method name=\"Reload\"/>\n"                                 \
+        "  <method name=\"Reexecute\"/>\n"                              \
+        "  <method name=\"Exit\"/>\n"                                   \
+        "  <method name=\"SetEnvironment\">\n"                          \
+        "   <arg name=\"names\" type=\"as\" direction=\"in\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <method name=\"UnsetEnvironment\">\n"                        \
+        "   <arg name=\"names\" type=\"as\" direction=\"in\"/>\n"       \
+        "  </method>\n"                                                 \
+        "  <signal name=\"UnitNew\">\n"                                 \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "   <arg name=\"unit\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"UnitRemoved\">\n"                             \
+        "   <arg name=\"id\" type=\"s\"/>\n"                            \
+        "   <arg name=\"unit\" type=\"o\"/>\n"                          \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"JobNew\">\n"                                  \
+        "   <arg name=\"id\" type=\"u\"/>\n"                            \
+        "   <arg name=\"job\" type=\"o\"/>\n"                           \
+        "  </signal>\n"                                                 \
+        "  <signal name=\"JobRemoved\">\n"                              \
+        "   <arg name=\"id\" type=\"u\"/>\n"                            \
+        "   <arg name=\"job\" type=\"o\"/>\n"                           \
+        "   <arg name=\"success\" type=\"b\"/>\n"                       \
+        "  </signal>"                                                   \
+        "  <property name=\"Version\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"RunningAs\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"BootTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"LogLevel\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"LogTarget\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"NNames\" type=\"u\" access=\"read\"/>\n"    \
+        "  <property name=\"NJobs\" type=\"u\" access=\"read\"/>\n"     \
+        "  <property name=\"Environment\" type=\"as\" access=\"read\"/>\n" \
+        " </interface>\n"
+
 #define INTROSPECTION_BEGIN                                             \
         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
-        "<node>"                                                        \
-        " <interface name=\"org.freedesktop.systemd1.Manager\">"        \
-        "  <method name=\"GetUnit\">"                                   \
-        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>"           \
-        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>"          \
-        "  </method>"                                                   \
-        "  <method name=\"LoadUnit\">"                                  \
-        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>"           \
-        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>"          \
-        "  </method>"                                                   \
-        "  <method name=\"GetJob\">"                                    \
-        "   <arg name=\"id\" type=\"u\" direction=\"in\"/>"             \
-        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>"           \
-        "  </method>"                                                   \
-        "  <method name=\"ClearJobs\"/>"                                \
-        "  <method name=\"ListUnits\">"                                 \
-        "   <arg name=\"units\" type=\"a(sssssouso)\" direction=\"out\"/>" \
-        "  </method>"                                                   \
-        "  <method name=\"ListJobs\">"                                  \
-        "   <arg name=\"jobs\" type=\"a(usssoo)\" direction=\"out\"/>"  \
-        "  </method>"                                                   \
-        "  <method name=\"Subscribe\"/>"                                \
-        "  <method name=\"Unsubscribe\"/>"                              \
-        "  <method name=\"Dump\"/>"                                     \
-        "  <method name=\"CreateSnapshot\">"                            \
-        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>"           \
-        "   <arg nane=\"cleanup\" type=\"b\" direction=\"in\"/>"        \
-        "   <arg name=\"unit\" type=\"o\" direction=\"out\"/>"          \
-        "  </method>"                                                   \
-        "  <method name=\"Reload\"/>"                                   \
-        "  <method name=\"Reexecute\"/>"                                \
-        "  <method name=\"Exit\"/>"                                     \
-        "  <method name=\"SetEnvironment\">"                            \
-        "   <arg name=\"names\" type=\"as\" direction=\"in\"/>"         \
-        "  </method>"                                                   \
-        "  <method name=\"UnsetEnvironment\">"                          \
-        "   <arg name=\"names\" type=\"as\" direction=\"in\"/>"         \
-        "  </method>"                                                   \
-        "  <signal name=\"UnitNew\">"                                   \
-        "   <arg name=\"id\" type=\"s\"/>"                              \
-        "   <arg name=\"unit\" type=\"o\"/>"                            \
-        "  </signal>"                                                   \
-        "  <signal name=\"UnitRemoved\">"                               \
-        "   <arg name=\"id\" type=\"s\"/>"                              \
-        "   <arg name=\"unit\" type=\"o\"/>"                            \
-        "  </signal>"                                                   \
-        "  <signal name=\"JobNew\">"                                    \
-        "   <arg name=\"id\" type=\"u\"/>"                              \
-        "   <arg name=\"job\" type=\"o\"/>"                             \
-        "  </signal>"                                                   \
-        "  <signal name=\"JobRemoved\">"                                \
-        "   <arg name=\"id\" type=\"u\"/>"                              \
-        "   <arg name=\"job\" type=\"o\"/>"                             \
-        "   <arg name=\"success\" type=\"b\"/>"                         \
-        "  </signal>"                                                   \
-        "  <property name=\"Version\" type=\"s\" access=\"read\"/>"     \
-        "  <property name=\"RunningAs\" type=\"s\" access=\"read\"/>"   \
-        "  <property name=\"BootTimestamp\" type=\"t\" access=\"read\"/>" \
-        "  <property name=\"LogLevel\" type=\"s\" access=\"read\"/>"    \
-        "  <property name=\"LogTarget\" type=\"s\" access=\"read\"/>"   \
-        "  <property name=\"NNames\" type=\"u\" access=\"read\"/>"      \
-        "  <property name=\"NJobs\" type=\"u\" access=\"read\"/>"       \
-        "  <property name=\"Environment\" type=\"as\" access=\"read\"/>" \
-        " </interface>"                                                 \
+        "<node>\n"                                                      \
+        BUS_MANAGER_INTERFACE                                           \
         BUS_PROPERTIES_INTERFACE                                        \
         BUS_INTROSPECTABLE_INTERFACE
 
 #define INTROSPECTION_END                                               \
-        "</node>"
+        "</node>\n"
+
+const char bus_manager_interface[] = BUS_MANAGER_INTERFACE;
 
 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_manager_append_running_as, manager_running_as, ManagerRunningAs);
 
diff --git a/src/dbus-manager.h b/src/dbus-manager.h
index 0acd2d0..996b680 100644
--- a/src/dbus-manager.h
+++ b/src/dbus-manager.h
@@ -26,4 +26,6 @@
 
 extern const DBusObjectPathVTable bus_manager_vtable;
 
+extern const char bus_manager_interface[];
+
 #endif
diff --git a/src/dbus-mount.c b/src/dbus-mount.c
index 500b773..cccfa60 100644
--- a/src/dbus-mount.c
+++ b/src/dbus-mount.c
@@ -25,23 +25,28 @@
 #include "dbus-mount.h"
 #include "dbus-execute.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        BUS_UNIT_INTERFACE
-        BUS_PROPERTIES_INTERFACE
-        " <interface name=\"org.freedesktop.systemd1.Mount\">"
-        "  <property name=\"Where\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"What\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"Options\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"Type\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>"
-        BUS_EXEC_CONTEXT_INTERFACE
-        "  <property name=\"KillMode\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>"
-        " </interface>"
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_MOUNT_INTERFACE                                             \
+        " <interface name=\"org.freedesktop.systemd1.Mount\">\n"        \
+        "  <property name=\"Where\" type=\"s\" access=\"read\"/>\n"     \
+        "  <property name=\"What\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Options\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"Type\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+        BUS_EXEC_CONTEXT_INTERFACE                                      \
+        "  <property name=\"KillMode\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_MOUNT_INTERFACE                                             \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_mount_interface[] = BUS_MOUNT_INTERFACE;
 
 static int bus_mount_append_what(Manager *n, DBusMessageIter *i, const char *property, void *data) {
         Mount *m = data;
@@ -130,5 +135,5 @@ DBusHandlerResult bus_mount_message_handler(Unit *u, DBusMessage *message) {
                 { NULL, NULL, NULL, NULL, NULL }
         };
 
-        return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
 }
diff --git a/src/dbus-mount.h b/src/dbus-mount.h
index b92867d..6d8d1a9 100644
--- a/src/dbus-mount.h
+++ b/src/dbus-mount.h
@@ -28,4 +28,6 @@
 
 DBusHandlerResult bus_mount_message_handler(Unit *u, DBusMessage *message);
 
+extern const char bus_mount_interface[];
+
 #endif
diff --git a/src/dbus-service.c b/src/dbus-service.c
index 24dd6c1..6286172 100644
--- a/src/dbus-service.c
+++ b/src/dbus-service.c
@@ -25,29 +25,34 @@
 #include "dbus-execute.h"
 #include "dbus-service.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        BUS_UNIT_INTERFACE
-        BUS_PROPERTIES_INTERFACE
-        " <interface name=\"org.freedesktop.systemd1.Service\">"
-        "  <property name=\"Type\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"Restart\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"PIDFile\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"RestartUSec\" type=\"t\" access=\"read\"/>"
-        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>"
-        BUS_EXEC_CONTEXT_INTERFACE
-        "  <property name=\"PermissionsStartOnly\" type=\"b\" access=\"read\"/>"
-        "  <property name=\"RootDirectoryStartOnly\" type=\"b\" access=\"read\"/>"
-        "  <property name=\"ValidNoProcess\" type=\"b\" access=\"read\"/>"
-        "  <property name=\"KillMode\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"MainPID\" type=\"u\" access=\"read\"/>"
-        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>"
-        "  <property name=\"SysVPath\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"BusName\" type=\"s\" access=\"read\"/>"
-        " </interface>"
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_SERVICE_INTERFACE                                           \
+        " <interface name=\"org.freedesktop.systemd1.Service\">\n"      \
+        "  <property name=\"Type\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Restart\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"PIDFile\" type=\"s\" access=\"read\"/>\n"   \
+        "  <property name=\"RestartUSec\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+        BUS_EXEC_CONTEXT_INTERFACE                                      \
+        "  <property name=\"PermissionsStartOnly\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"RootDirectoryStartOnly\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"ValidNoProcess\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"KillMode\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"MainPID\" type=\"u\" access=\"read\"/>\n"   \
+        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"SysVPath\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"BusName\" type=\"s\" access=\"read\"/>\n"   \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_SERVICE_INTERFACE                                           \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_service_interface[] = BUS_SERVICE_INTERFACE;
 
 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_type, service_type, ServiceType);
 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_service_append_restart, service_restart, ServiceRestart);
@@ -74,5 +79,5 @@ DBusHandlerResult bus_service_message_handler(Unit *u, DBusMessage *message) {
                 { NULL, NULL, NULL, NULL, NULL }
         };
 
-        return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
 }
diff --git a/src/dbus-service.h b/src/dbus-service.h
index f0a468e..ab1e02f 100644
--- a/src/dbus-service.h
+++ b/src/dbus-service.h
@@ -28,4 +28,6 @@
 
 DBusHandlerResult bus_service_message_handler(Unit *u, DBusMessage *message);
 
+extern const char bus_service_interface[];
+
 #endif
diff --git a/src/dbus-snapshot.c b/src/dbus-snapshot.c
index 8aeca15..15e51f0 100644
--- a/src/dbus-snapshot.c
+++ b/src/dbus-snapshot.c
@@ -22,17 +22,22 @@
 #include "dbus-unit.h"
 #include "dbus-snapshot.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        BUS_UNIT_INTERFACE
-        BUS_PROPERTIES_INTERFACE
-        " <interface name=\"org.freedesktop.systemd1.Snapshot\">"
-        "  <method name=\"Remove\"/>"
-        "  <property name=\"Cleanup\" type=\"b\" access=\"read\"/>"
-        " </interface>"
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_SNAPSHOT_INTERFACE                                          \
+        " <interface name=\"org.freedesktop.systemd1.Snapshot\">\n"     \
+        "  <method name=\"Remove\"/>\n"                                 \
+        "  <property name=\"Cleanup\" type=\"b\" access=\"read\"/>\n"   \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_SNAPSHOT_INTERFACE                                          \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_snapshot_interface[] = BUS_SNAPSHOT_INTERFACE;
 
 DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusMessage *message) {
         const BusProperty properties[] = {
@@ -54,7 +59,7 @@ DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusMessage *message) {
                         goto oom;
 
         } else
-                return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+                return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
 
         if (reply) {
                 if (!dbus_connection_send(u->meta.manager->api_bus, reply, NULL))
diff --git a/src/dbus-snapshot.h b/src/dbus-snapshot.h
index 5f28550..bf5a4d4 100644
--- a/src/dbus-snapshot.h
+++ b/src/dbus-snapshot.h
@@ -28,4 +28,6 @@
 
 DBusHandlerResult bus_snapshot_message_handler(Unit *u, DBusMessage *message);
 
+extern const char bus_snapshot_interface[];
+
 #endif
diff --git a/src/dbus-socket.c b/src/dbus-socket.c
index cb244a9..426af2b 100644
--- a/src/dbus-socket.c
+++ b/src/dbus-socket.c
@@ -25,25 +25,30 @@
 #include "dbus-socket.h"
 #include "dbus-execute.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        BUS_UNIT_INTERFACE
-        BUS_PROPERTIES_INTERFACE
-        " <interface name=\"org.freedesktop.systemd1.Socket\">"
-        "  <property name=\"BindIPv6Only\" type=\"b\" access=\"read\"/>"
-        "  <property name=\"Backlog\" type=\"u\" access=\"read\"/>"
-        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>"
-        BUS_EXEC_CONTEXT_INTERFACE
-        "  <property name=\"KillMode\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>"
-        "  <property name=\"BindToDevice\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>"
-        "  <property name=\"SocketMode\" type=\"u\" access=\"read\"/>"
-        "  <property name=\"Accept\" type=\"b\" access=\"read\"/>"
-        " </interface>"
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_SOCKET_INTERFACE                                            \
+        " <interface name=\"org.freedesktop.systemd1.Socket\">\n"       \
+        "  <property name=\"BindIPv6Only\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"Backlog\" type=\"u\" access=\"read\"/>\n"   \
+        "  <property name=\"TimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
+        BUS_EXEC_CONTEXT_INTERFACE                                      \
+        "  <property name=\"KillMode\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"BindToDevice\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"DirectoryMode\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"SocketMode\" type=\"u\" access=\"read\"/>\n" \
+        "  <property name=\"Accept\" type=\"b\" access=\"read\"/>\n"    \
+        " </interface>\n"                                               \
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_SOCKET_INTERFACE                                            \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_socket_interface[] = BUS_SOCKET_INTERFACE;
 
 static DEFINE_BUS_PROPERTY_APPEND_ENUM(bus_socket_append_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only);
 
@@ -64,5 +69,5 @@ DBusHandlerResult bus_socket_message_handler(Unit *u, DBusMessage *message) {
                 { NULL, NULL, NULL, NULL, NULL }
         };
 
-        return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
 }
diff --git a/src/dbus-socket.h b/src/dbus-socket.h
index 6a8f534..ab06322 100644
--- a/src/dbus-socket.h
+++ b/src/dbus-socket.h
@@ -28,4 +28,6 @@
 
 DBusHandlerResult bus_socket_message_handler(Unit *u, DBusMessage *message);
 
+extern const char bus_socket_interface[];
+
 #endif
diff --git a/src/dbus-swap.c b/src/dbus-swap.c
index e935e09..f6f8685 100644
--- a/src/dbus-swap.c
+++ b/src/dbus-swap.c
@@ -25,17 +25,22 @@
 #include "dbus-unit.h"
 #include "dbus-swap.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        BUS_UNIT_INTERFACE
-        BUS_PROPERTIES_INTERFACE
-        " <interface name=\"org.freedesktop.systemd1.Swap\">"
-        "  <property name=\"What\" type=\"s\" access=\"read\"/>"
-        "  <property name=\"Priority\" type=\"i\" access=\"read\"/>"
-        " </interface>"
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_SWAP_INTERFACE                                              \
+        " <interface name=\"org.freedesktop.systemd1.Swap\">\n"         \
+        "  <property name=\"What\" type=\"s\" access=\"read\"/>\n"      \
+        "  <property name=\"Priority\" type=\"i\" access=\"read\"/>\n"  \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_SWAP_INTERFACE                                              \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_swap_interface[] = BUS_SWAP_INTERFACE;
 
 static int bus_swap_append_priority(Manager *m, DBusMessageIter *i, const char *property, void *data) {
         Swap *s = data;
@@ -69,5 +74,5 @@ DBusHandlerResult bus_swap_message_handler(Unit *u, DBusMessage *message) {
                 { NULL, NULL, NULL, NULL, NULL }
         };
 
-        return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
 }
diff --git a/src/dbus-swap.h b/src/dbus-swap.h
index 3bef6ad..cbd1591 100644
--- a/src/dbus-swap.h
+++ b/src/dbus-swap.h
@@ -29,4 +29,6 @@
 
 DBusHandlerResult bus_swap_message_handler(Unit *u, DBusMessage *message);
 
+extern const char bus_swap_interface[];
+
 #endif
diff --git a/src/dbus-target.c b/src/dbus-target.c
index be984b9..45f0d31 100644
--- a/src/dbus-target.c
+++ b/src/dbus-target.c
@@ -22,15 +22,20 @@
 #include "dbus-unit.h"
 #include "dbus-target.h"
 
-static const char introspection[] =
-        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
-        "<node>"
-        BUS_UNIT_INTERFACE
-        BUS_PROPERTIES_INTERFACE
-        " <interface name=\"org.freedesktop.systemd1.Target\">"
-        " </interface>"
-        BUS_INTROSPECTABLE_INTERFACE
-        "</node>";
+#define BUS_TARGET_INTERFACE                                            \
+        " <interface name=\"org.freedesktop.systemd1.Target\">\n"       \
+        " </interface>\n"
+
+#define INTROSPECTION                                                   \
+        DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
+        "<node>\n"                                                      \
+        BUS_UNIT_INTERFACE                                              \
+        BUS_TARGET_INTERFACE                                            \
+        BUS_PROPERTIES_INTERFACE                                        \
+        BUS_INTROSPECTABLE_INTERFACE                                    \
+        "</node>\n"
+
+const char bus_target_interface[] = BUS_TARGET_INTERFACE;
 
 DBusHandlerResult bus_target_message_handler(Unit *u, DBusMessage *message) {
         const BusProperty properties[] = {
@@ -38,5 +43,5 @@ DBusHandlerResult bus_target_message_handler(Unit *u, DBusMessage *message) {
                 { NULL, NULL, NULL, NULL, NULL }
         };
 
-        return bus_default_message_handler(u->meta.manager, message, introspection, properties);
+        return bus_default_message_handler(u->meta.manager, message, INTROSPECTION, properties);
 }
diff --git a/src/dbus-target.h b/src/dbus-target.h
index f6a1ac5..d118441 100644
--- a/src/dbus-target.h
+++ b/src/dbus-target.h
@@ -28,4 +28,6 @@
 
 DBusHandlerResult bus_target_message_handler(Unit *u, DBusMessage *message);
 
+extern const char bus_target_interface[];
+
 #endif
diff --git a/src/dbus-unit.c b/src/dbus-unit.c
index ba428a8..87218cd 100644
--- a/src/dbus-unit.c
+++ b/src/dbus-unit.c
@@ -25,6 +25,8 @@
 #include "log.h"
 #include "dbus-unit.h"
 
+const char bus_unit_interface[] = BUS_UNIT_INTERFACE;
+
 int bus_unit_append_names(Manager *m, DBusMessageIter *i, const char *property, void *data) {
         char *t;
         Iterator j;
diff --git a/src/dbus-unit.h b/src/dbus-unit.h
index c5840d5..07641ee 100644
--- a/src/dbus-unit.h
+++ b/src/dbus-unit.h
@@ -27,54 +27,54 @@
 #include "manager.h"
 
 #define BUS_UNIT_INTERFACE \
-        " <interface name=\"org.freedesktop.systemd1.Unit\">"           \
-        "  <method name=\"Start\">"                                     \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>"           \
-        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>"           \
-        "  </method>"                                                   \
-        "  <method name=\"Stop\">"                                      \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>"           \
-        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>"           \
-        "  </method>"                                                   \
-        "  <method name=\"Restart\">"                                   \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>"           \
-        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>"           \
-        "  </method>"                                                   \
-        "  <method name=\"Reload\">"                                    \
-        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>"           \
-        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>"           \
-        "  </method>"                                                   \
-        "  <signal name=\"Changed\"/>"                                  \
-        "  <property name=\"Id\" type=\"s\" access=\"read\"/>"          \
-        "  <property name=\"Names\" type=\"as\" access=\"read\"/>"      \
-        "  <property name=\"Requires\" type=\"as\" access=\"read\"/>"   \
-        "  <property name=\"RequiresOverridable\" type=\"as\" access=\"read\"/>" \
-        "  <property name=\"Requisite\" type=\"as\" access=\"read\"/>"  \
-        "  <property name=\"RequisiteOverridable\" type=\"as\" access=\"read\"/>" \
-        "  <property name=\"Wants\" type=\"as\" access=\"read\"/>"      \
-        "  <property name=\"RequiredBy\" type=\"as\" access=\"read\"/>" \
-        "  <property name=\"RequiredByOverridable\" type=\"as\" access=\"read\"/>" \
-        "  <property name=\"WantedBy\" type=\"as\" access=\"read\"/>"   \
-        "  <property name=\"Conflicts\" type=\"as\" access=\"read\"/>"  \
-        "  <property name=\"Before\" type=\"as\" access=\"read\"/>"     \
-        "  <property name=\"After\" type=\"as\" access=\"read\"/>"      \
-        "  <property name=\"Description\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"LoadState\" type=\"s\" access=\"read\"/>"   \
-        "  <property name=\"ActiveState\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"SubState\" type=\"s\" access=\"read\"/>"    \
-        "  <property name=\"FragmentPath\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"InactiveExitTimestamp\" type=\"t\" access=\"read\"/>" \
-        "  <property name=\"ActiveEnterTimestamp\" type=\"t\" access=\"read\"/>" \
-        "  <property name=\"ActiveExitTimestamp\" type=\"t\" access=\"read\"/>" \
-        "  <property name=\"InactiveEnterTimestamp\" type=\"t\" access=\"read\"/>" \
-        "  <property name=\"CanReload\" type=\"b\" access=\"read\"/>"   \
-        "  <property name=\"CanStart\" type=\"b\" access=\"read\"/>"    \
-        "  <property name=\"Job\" type=\"(uo)\" access=\"read\"/>"      \
-        "  <property name=\"RecursiveStop\" type=\"b\" access=\"read\"/>" \
-        "  <property name=\"StopWhenUneeded\" type=\"b\" access=\"read\"/>" \
-        "  <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>" \
-        "  <property name=\"ControlGroups\" type=\"as\" access=\"read\"/>" \
-        " </interface>"
+        " <interface name=\"org.freedesktop.systemd1.Unit\">\n"         \
+        "  <method name=\"Start\">\n"                                   \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"Stop\">\n"                                    \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"Restart\">\n"                                 \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <method name=\"Reload\">\n"                                  \
+        "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
+        "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
+        "  </method>\n"                                                 \
+        "  <signal name=\"Changed\"/>\n"                                \
+        "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
+        "  <property name=\"Names\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"Requires\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"RequiresOverridable\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Requisite\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"RequisiteOverridable\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Wants\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"RequiredBy\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"RequiredByOverridable\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"WantedBy\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Conflicts\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"Before\" type=\"as\" access=\"read\"/>\n"   \
+        "  <property name=\"After\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"Description\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"LoadState\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"ActiveState\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"SubState\" type=\"s\" access=\"read\"/>\n"  \
+        "  <property name=\"FragmentPath\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"InactiveExitTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ActiveEnterTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"ActiveExitTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"InactiveEnterTimestamp\" type=\"t\" access=\"read\"/>\n" \
+        "  <property name=\"CanReload\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"CanStart\" type=\"b\" access=\"read\"/>\n"  \
+        "  <property name=\"Job\" type=\"(uo)\" access=\"read\"/>\n"    \
+        "  <property name=\"RecursiveStop\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"StopWhenUneeded\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \
+        "  <property name=\"ControlGroups\" type=\"as\" access=\"read\"/>\n" \
+        " </interface>\n"
 
 #define BUS_UNIT_PROPERTIES \
         { "org.freedesktop.systemd1.Unit", "Id",                   bus_property_append_string,     "s",    u->meta.id                        }, \
@@ -125,4 +125,6 @@ void bus_unit_send_removed_signal(Unit *u);
 
 extern const DBusObjectPathVTable bus_unit_vtable;
 
+extern const char bus_unit_interface[];
+
 #endif
diff --git a/src/dbus.c b/src/dbus.c
index 6ed659a..5caf1eb 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -32,6 +32,34 @@
 #include "dbus-unit.h"
 #include "dbus-job.h"
 #include "dbus-manager.h"
+#include "dbus-service.h"
+#include "dbus-socket.h"
+#include "dbus-target.h"
+#include "dbus-device.h"
+#include "dbus-mount.h"
+#include "dbus-automount.h"
+#include "dbus-snapshot.h"
+#include "dbus-swap.h"
+
+static const char bus_properties_interface[] = BUS_PROPERTIES_INTERFACE;
+static const char bus_introspectable_interface[] = BUS_INTROSPECTABLE_INTERFACE;
+
+const char *const bus_interface_table[] = {
+        "org.freedesktop.DBus.Properties",     bus_properties_interface,
+        "org.freedesktop.DBus.Introspectable", bus_introspectable_interface,
+        "org.freedesktop.systemd1.Manager",    bus_manager_interface,
+        "org.freedesktop.systemd1.Job",        bus_job_interface,
+        "org.freedesktop.systemd1.Unit",       bus_unit_interface,
+        "org.freedesktop.systemd1.Service",    bus_service_interface,
+        "org.freedesktop.systemd1.Socket",     bus_socket_interface,
+        "org.freedesktop.systemd1.Target",     bus_target_interface,
+        "org.freedesktop.systemd1.Device",     bus_device_interface,
+        "org.freedesktop.systemd1.Mount",      bus_mount_interface,
+        "org.freedesktop.systemd1.Automount",  bus_automount_interface,
+        "org.freedesktop.systemd1.Snapshot",   bus_snapshot_interface,
+        "org.freedesktop.systemd1.Swap",       bus_swap_interface,
+        NULL
+};
 
 static void api_bus_dispatch_status(DBusConnection *bus, DBusDispatchStatus status, void *data)  {
         Manager *m = data;
diff --git a/src/dbus.h b/src/dbus.h
index 51b71ea..3ad299e 100644
--- a/src/dbus.h
+++ b/src/dbus.h
@@ -37,24 +37,24 @@ typedef struct BusProperty {
 } BusProperty;
 
 #define BUS_PROPERTIES_INTERFACE                                        \
-        " <interface name=\"org.freedesktop.DBus.Properties\">"         \
-        "  <method name=\"Get\">"                                       \
-        "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"      \
-        "   <arg name=\"property\" direction=\"in\" type=\"s\"/>"       \
-        "   <arg name=\"value\" direction=\"out\" type=\"v\"/>"         \
-        "  </method>"                                                   \
-        "  <method name=\"GetAll\">"                                    \
-        "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>"      \
-        "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>" \
-        "  </method>"                                                   \
-        " </interface>"
+        " <interface name=\"org.freedesktop.DBus.Properties\">\n"         \
+        "  <method name=\"Get\">\n"                                       \
+        "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n"      \
+        "   <arg name=\"property\" direction=\"in\" type=\"s\"/>\n"       \
+        "   <arg name=\"value\" direction=\"out\" type=\"v\"/>\n"         \
+        "  </method>\n"                                                   \
+        "  <method name=\"GetAll\">\n"                                    \
+        "   <arg name=\"interface\" direction=\"in\" type=\"s\"/>\n"      \
+        "   <arg name=\"properties\" direction=\"out\" type=\"a{sv}\"/>\n" \
+        "  </method>\n"                                                   \
+        " </interface>\n"
 
 #define BUS_INTROSPECTABLE_INTERFACE                                    \
-        " <interface name=\"org.freedesktop.DBus.Introspectable\">"     \
-        "  <method name=\"Introspect\">"                                \
-        "   <arg name=\"data\" type=\"s\" direction=\"out\"/>"          \
-        "  </method>"                                                   \
-        " </interface>"
+        " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"     \
+        "  <method name=\"Introspect\">\n"                                \
+        "   <arg name=\"data\" type=\"s\" direction=\"out\"/>\n"          \
+        "  </method>\n"                                                   \
+        " </interface>\n"
 
 int bus_init_system(Manager *m);
 int bus_init_api(Manager *m);
@@ -104,4 +104,6 @@ int bus_property_append_uint64(Manager *m, DBusMessageIter *i, const char *prope
 
 int bus_parse_strv(DBusMessage *m, char ***_l);
 
+extern const char * const bus_interface_table[];
+
 #endif
diff --git a/src/main.c b/src/main.c
index 3b9719f..d7d3995 100644
--- a/src/main.c
+++ b/src/main.c
@@ -45,7 +45,8 @@ static enum {
         ACTION_RUN,
         ACTION_HELP,
         ACTION_TEST,
-        ACTION_DUMP_CONFIGURATION_ITEMS
+        ACTION_DUMP_CONFIGURATION_ITEMS,
+        ACTION_DONE
 } action = ACTION_RUN;
 
 static char *default_unit = NULL;
@@ -347,7 +348,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_TEST,
                 ARG_DUMP_CONFIGURATION_ITEMS,
                 ARG_CONFIRM_SPAWN,
-                ARG_DESERIALIZE
+                ARG_DESERIALIZE,
+                ARG_INTROSPECT
         };
 
         static const struct option options[] = {
@@ -360,6 +362,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "dump-configuration-items", no_argument,       NULL, ARG_DUMP_CONFIGURATION_ITEMS },
                 { "confirm-spawn",            no_argument,       NULL, ARG_CONFIRM_SPAWN            },
                 { "deserialize",              required_argument, NULL, ARG_DESERIALIZE              },
+                { "introspect",               optional_argument, NULL, ARG_INTROSPECT               },
                 { NULL,                       0,                 NULL, 0                            }
         };
 
@@ -444,6 +447,27 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_INTROSPECT: {
+                        const char * const * i = NULL;
+
+                        for (i = bus_interface_table; *i; i += 2)
+                                if (!optarg || streq(i[0], optarg)) {
+                                        fputs(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+                                              "<node>\n", stdout);
+                                        fputs(i[1], stdout);
+                                        fputs("</node>\n", stdout);
+
+                                        if (optarg)
+                                                break;
+                                }
+
+                        if (!i[0] && optarg)
+                                log_error("Unknown interface %s.", optarg);
+
+                        action = ACTION_DONE;
+                        break;
+                }
+
                 case 'h':
                         action = ACTION_HELP;
                         break;
@@ -478,7 +502,8 @@ static int help(void) {
                "     --running-as=AS             Set running as (init, system, session)\n"
                "     --test                      Determine startup sequence, dump it and exit\n"
                "     --dump-configuration-items  Dump understood unit configuration items\n"
-               "     --confirm-spawn             Ask for confirmation when spawning processes\n",
+               "     --confirm-spawn             Ask for confirmation when spawning processes\n"
+               "     --introspect[=INTERFACE]    Extract D-Bus interface data\n",
                __progname);
 
         return 0;
@@ -582,6 +607,9 @@ int main(int argc, char *argv[]) {
                 unit_dump_config_items(stdout);
                 retval = 0;
                 goto finish;
+        } else if (action == ACTION_DONE) {
+                retval = 0;
+                goto finish;
         }
 
         assert_se(action == ACTION_RUN || action == ACTION_TEST);


More information about the systemd-commits mailing list