[systemd-commits] 15 commits - fixme Makefile.am man/systemctl.xml man/systemd-install.xml man/systemd.unit.xml man/systemd.xml src/automount.c src/dbus-manager.c src/dbus-unit.c src/dbus-unit.h src/device.c src/device.h src/hashmap.c src/hashmap.h src/install.c src/job.c src/job.h src/load-fragment.c src/log.h src/main.c src/manager.c src/manager.h src/mount.c src/path.c src/path.h src/service.c src/service.h src/socket.c src/swap.c src/systemctl.c src/systemd-interfaces.vala src/timer.c src/unit.c src/unit.h src/util.c src/util.h units/getty at .service.m4

Lennart Poettering lennart at kemper.freedesktop.org
Tue Jul 20 12:09:19 PDT 2010


 Makefile.am                 |    3 
 fixme                       |   33 ++---
 man/systemctl.xml           |   28 ++++
 man/systemd-install.xml     |   21 +++
 man/systemd.unit.xml        |   37 ++++++
 man/systemd.xml             |   56 ++++++++-
 src/automount.c             |   13 ++
 src/dbus-manager.c          |   41 ++++++
 src/dbus-unit.c             |   44 +++++++
 src/dbus-unit.h             |   13 ++
 src/device.c                |  253 +++++++++++++++++++++++++-----------------
 src/device.h                |    6 +
 src/hashmap.c               |   27 ++++
 src/hashmap.h               |    1 
 src/install.c               |  240 ++++++++++++++++++++++++++++++++++++++++
 src/job.c                   |   70 +++++++++++
 src/job.h                   |    9 +
 src/load-fragment.c         |   17 ++
 src/log.h                   |    2 
 src/main.c                  |    4 
 src/manager.c               |   34 ++++-
 src/manager.h               |    9 +
 src/mount.c                 |   16 ++
 src/path.c                  |   13 ++
 src/path.h                  |   13 +-
 src/service.c               |   21 +++
 src/service.h               |   19 +--
 src/socket.c                |   18 ++-
 src/swap.c                  |   27 +++-
 src/systemctl.c             |  259 +++++++++++++++++++++++++++++++++++++-------
 src/systemd-interfaces.vala |    1 
 src/timer.c                 |   13 ++
 src/unit.c                  |   95 ++++++++++++----
 src/unit.h                  |   17 ++
 src/util.c                  |   32 +++++
 src/util.h                  |    2 
 units/getty at .service.m4     |    1 
 37 files changed, 1268 insertions(+), 240 deletions(-)

New commits:
commit 2cc59dbfe08e92496b242aed3f2b3cf130beb203
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jul 20 21:04:32 2010 +0200

    systemctl: always disable color when output goes into a file

diff --git a/fixme b/fixme
index 3fd02ed..3a17c62 100644
--- a/fixme
+++ b/fixme
@@ -39,7 +39,6 @@
 
 * In command lines, support both "$FOO" and $FOO
 * /etc must always take precedence even if we follow symlinks!
-* color aus bei stdout auf !tty
 
 * vielleicht implizit immer auf syslog dependen?
 
diff --git a/src/main.c b/src/main.c
index aaddce8..964bb9c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -836,7 +836,7 @@ int main(int argc, char *argv[]) {
                 return 1;
         }
 
-        log_show_color(true);
+        log_show_color(isatty(STDERR_FILENO) > 0);
         log_show_location(false);
         log_set_max_level(LOG_INFO);
 
diff --git a/src/systemctl.c b/src/systemctl.c
index bdfd0cd..d78294b 100644
--- a/src/systemctl.c
+++ b/src/systemctl.c
@@ -86,6 +86,18 @@ static enum dot {
 
 static bool private_bus = false;
 
+static const char *ansi_highlight(bool b) {
+        static int t = -1;
+
+        if (_unlikely_(t < 0))
+                t = isatty(STDOUT_FILENO) > 0;
+
+        if (!t)
+                return "";
+
+        return b ? ANSI_HIGHLIGHT_ON : ANSI_HIGHLIGHT_OFF;
+}
+
 static bool error_is_no_service(DBusError *error) {
 
         assert(error);
@@ -226,7 +238,7 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
                         int a = 0, b = 0;
 
                         if (streq(active_state, "maintenance"))
-                                fputs(ANSI_HIGHLIGHT_ON, stdout);
+                                fputs(ansi_highlight(true), stdout);
 
                         e = arg_full ? NULL : ellipsize(id, 45, 33);
                         printf("%-45s %-6s %-12s %-12s%n", e ? e : id, load_state, active_state, sub_state, &a);
@@ -245,7 +257,7 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
                         }
 
                         if (streq(active_state, "maintenance"))
-                                fputs(ANSI_HIGHLIGHT_OFF, stdout);
+                                fputs(ansi_highlight(false), stdout);
 
                         fputs("\n", stdout);
                         k++;
@@ -1422,18 +1434,25 @@ static void print_status_info(UnitStatusInfo *i) {
         if (i->fragment_path)
                 printf("\t  Loaded: %s (%s)\n", strna(i->load_state), i->fragment_path);
         else if (streq_ptr(i->load_state, "failed"))
-                printf("\t  Loaded: " ANSI_HIGHLIGHT_ON "%s" ANSI_HIGHLIGHT_OFF "\n", strna(i->load_state));
+                printf("\t  Loaded: %s%s%s\n",
+                       ansi_highlight(true),
+                       strna(i->load_state),
+                       ansi_highlight(false));
         else
                 printf("\t  Loaded: %s\n", strna(i->load_state));
 
         if (streq_ptr(i->active_state, "maintenance")) {
                         if (streq_ptr(i->active_state, i->sub_state))
-                                printf("\t  Active: " ANSI_HIGHLIGHT_ON "%s" ANSI_HIGHLIGHT_OFF "\n",
-                                       strna(i->active_state));
+                                printf("\t  Active: %s%s%s\n",
+                                       ansi_highlight(true),
+                                       strna(i->active_state),
+                                       ansi_highlight(false));
                         else
-                                printf("\t  Active: " ANSI_HIGHLIGHT_ON "%s (%s)" ANSI_HIGHLIGHT_OFF "\n",
+                                printf("\t  Active: %s%s (%s)%s\n",
+                                       ansi_highlight(true),
                                        strna(i->active_state),
-                                       strna(i->sub_state));
+                                       strna(i->sub_state),
+                                       ansi_highlight(false));
         } else {
                 if (streq_ptr(i->active_state, i->sub_state))
                         printf("\t  Active: %s\n",
@@ -1501,8 +1520,7 @@ static void print_status_info(UnitStatusInfo *i) {
                                         printf("status=%i", i->exit_status);
                                 else
                                         printf("signal=%s", signal_to_string(i->exit_status));
-                                printf(")");
-                        }
+                                printf(")");                        }
                 }
 
                 if (i->main_pid > 0 && i->control_pid > 0)
@@ -1540,7 +1558,9 @@ static void print_status_info(UnitStatusInfo *i) {
         }
 
         if (i->need_daemon_reload)
-                printf("\n" ANSI_HIGHLIGHT_ON "Warning:" ANSI_HIGHLIGHT_OFF " Unit file changed on disk, 'systemctl %s daemon-reload' recommended.\n",
+                printf("\n%sWarning:%s Unit file changed on disk, 'systemctl %s daemon-reload' recommended.\n",
+                       ansi_highlight(true),
+                       ansi_highlight(false),
                        arg_session ? "--session" : "--system");
 }
 
commit d8d5ab981a328dd60a39cb495cbf99ca73d76f61
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jul 20 20:54:33 2010 +0200

    manager: write serialization to /dev/.systemd/ instead of /dev/shm

diff --git a/fixme b/fixme
index ff00436..3fd02ed 100644
--- a/fixme
+++ b/fixme
@@ -39,13 +39,8 @@
 
 * In command lines, support both "$FOO" and $FOO
 * /etc must always take precedence even if we follow symlinks!
-* fix merging of device units
 * color aus bei stdout auf !tty
 
-getty before prefdm
-
-* /lib/init/rw
-
 * vielleicht implizit immer auf syslog dependen?
 
 * debian deadlock when partition auf noauto is.
diff --git a/src/main.c b/src/main.c
index d3c01b4..aaddce8 100644
--- a/src/main.c
+++ b/src/main.c
@@ -773,7 +773,7 @@ static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds) {
         assert(_f);
         assert(_fds);
 
-        if ((r = manager_open_serialization(&f)) < 0) {
+        if ((r = manager_open_serialization(m, &f)) < 0) {
                 log_error("Failed to create serialization faile: %s", strerror(-r));
                 goto fail;
         }
diff --git a/src/manager.c b/src/manager.c
index 8a9b9dd..bdb2b8b 100644
--- a/src/manager.c
+++ b/src/manager.c
@@ -2279,7 +2279,7 @@ void manager_dispatch_bus_query_pid_done(
         UNIT_VTABLE(u)->bus_query_pid_done(u, name, pid);
 }
 
-int manager_open_serialization(FILE **_f) {
+int manager_open_serialization(Manager *m, FILE **_f) {
         char *path;
         mode_t saved_umask;
         int fd;
@@ -2287,8 +2287,15 @@ int manager_open_serialization(FILE **_f) {
 
         assert(_f);
 
-        if (asprintf(&path, "/dev/shm/systemd-%u.dump-XXXXXX", (unsigned) getpid()) < 0)
-                return -ENOMEM;
+        if (m->running_as == MANAGER_SYSTEM) {
+                mkdir_p("/dev/.systemd", 0755);
+
+                if (asprintf(&path, "/dev/.systemd/dump-%lu-XXXXXX", (unsigned long) getpid()) < 0)
+                        return -ENOMEM;
+        } else {
+                if (asprintf(&path, "/tmp/systemd-dump-%lu-XXXXXX", (unsigned long) getpid()) < 0)
+                        return -ENOMEM;
+        }
 
         saved_umask = umask(0077);
         fd = mkostemp(path, O_RDWR|O_CLOEXEC);
@@ -2396,7 +2403,7 @@ int manager_reload(Manager *m) {
 
         assert(m);
 
-        if ((r = manager_open_serialization(&f)) < 0)
+        if ((r = manager_open_serialization(m, &f)) < 0)
                 return r;
 
         if (!(fds = fdset_new())) {
diff --git a/src/manager.h b/src/manager.h
index 7328724..a5bc920 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -239,7 +239,7 @@ void manager_write_utmp_runlevel(Manager *m, Unit *t);
 void manager_dispatch_bus_name_owner_changed(Manager *m, const char *name, const char* old_owner, const char *new_owner);
 void manager_dispatch_bus_query_pid_done(Manager *m, const char *name, pid_t pid);
 
-int manager_open_serialization(FILE **_f);
+int manager_open_serialization(Manager *m, FILE **_f);
 
 int manager_serialize(Manager *m, FILE *f, FDSet *fds);
 int manager_deserialize(Manager *m, FILE *f, FDSet *fds);
commit 36adffeab07c74470bc96417b17a72b53055ee42
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jul 20 20:42:46 2010 +0200

    fedora: make sure the gettys are run before X starts up

diff --git a/units/getty at .service.m4 b/units/getty at .service.m4
index b82f5aa..d008423 100644
--- a/units/getty at .service.m4
+++ b/units/getty at .service.m4
@@ -16,6 +16,7 @@ Description=Getty on %I
 Before=getty.target
 m4_ifdef(`TARGET_FEDORA',
 After=rc-local.service
+Before=prefdm.service
 )m4_dnl
 m4_ifdef(`TARGET_ARCH',
 After=rc-local.service
commit de0200fca558dcc4cfa785e46317ae9e6a879e04
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jul 20 20:40:49 2010 +0200

    socket: fix access mode verification of FIFOs

diff --git a/src/socket.c b/src/socket.c
index 2c9d693..b06ba09 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -686,7 +686,7 @@ static int fifo_address_create(
         }
 
         if (!S_ISFIFO(st.st_mode) ||
-            st.st_mode != (socket_mode & ~old_mask) ||
+            (st.st_mode & 0777) != (socket_mode & ~old_mask) ||
             st.st_uid != getuid() ||
             st.st_gid != getgid()) {
 
commit 8fe914ec81d9f57bcc083036f528b00119ed2e3b
Author: Lennart Poettering <lennart at poettering.net>
Date:   Tue Jul 20 20:33:19 2010 +0200

    device: do not merge devices
    
    Don't try to merge devices that have been created via dependencies when
    they appear in the system and can be recognized as the same.  Instead,
    simply continue to maintain them independently of each other, however
    with the same state cycle. Why? Because otherwise we'd have a hard time
    to seperate the dependencies after the devices are unplugged again and
    we hence cannot be sure anymore that next time the device is plugged in
    it will carry the same names.
    
    Example: if one depndency refers to dev-sda.device and another one to
    dev-by-id-xxxyyy.device we only learn at time of plug in of the device
    that it is actually the same device that was ment. In the moment the
    device is unplugged again we won't know anymore their relation to each
    other and the next time the harddisk is plugged it might even appear as
    dev-by-id-xxxyyy.device and dev-sdb.service. To ensure the dependencies
    continue to have the meaning they were intended to have let's hence keep
    the .device objects seperate all the time, even when they are plugged
    in.
    
    This patch also introduces a new Following= property which points from
    the various .device units of a specific device to the main .device unit
    for it. This can be used by the client side to figure out the relation
    of the .device units to each other and even filter units from display.

diff --git a/fixme b/fixme
index 29e0fde..ff00436 100644
--- a/fixme
+++ b/fixme
@@ -38,8 +38,11 @@
 * place /etc/inittab with explaining blurb.
 
 * In command lines, support both "$FOO" and $FOO
-
 * /etc must always take precedence even if we follow symlinks!
+* fix merging of device units
+* color aus bei stdout auf !tty
+
+getty before prefdm
 
 * /lib/init/rw
 
@@ -49,13 +52,17 @@
 
 * fingerprint.target, wireless.target, gps.target
 
-* fix merging of device units
-
 * set_put(), hashmap_put() return values checken. i.e. == 0 macht kein free()!
 
+* crash on missing hostname
+
+* fix merging in .swap units
+
 * pahole
 
-* color aus bei stdout auf !tty
+* io priority
+
+* network.target darf nm nicht unbedingt starten
 
 External:
 
diff --git a/man/systemctl.xml b/man/systemctl.xml
index 737bcbe..b8e00b6 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -116,6 +116,15 @@
                         </varlistentry>
 
                         <varlistentry>
+                                <term><option>--full</option></term>
+
+                                <listitem><para>Do not ellipsize unit
+                                names in the output of
+                                <command>list-units</command> and
+                                <command>list-jobs</command>.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
                                 <term><option>--fail</option></term>
 
                                 <listitem><para>If the requested
diff --git a/src/dbus-manager.c b/src/dbus-manager.c
index 969c430..63e8083 100644
--- a/src/dbus-manager.c
+++ b/src/dbus-manager.c
@@ -82,7 +82,7 @@
         "  <method name=\"ClearJobs\"/>\n"                              \
         "  <method name=\"ResetMaintenance\"/>\n"                       \
         "  <method name=\"ListUnits\">\n"                               \
-        "   <arg name=\"units\" type=\"a(sssssouso)\" direction=\"out\"/>\n" \
+        "   <arg name=\"units\" type=\"a(ssssssouso)\" direction=\"out\"/>\n" \
         "  </method>\n"                                                 \
         "  <method name=\"ListJobs\">\n"                                \
         "   <arg name=\"jobs\" type=\"a(usssoo)\" direction=\"out\"/>\n" \
@@ -405,12 +405,12 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
 
                 dbus_message_iter_init_append(reply, &iter);
 
-                if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(sssssouso)", &sub))
+                if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "(ssssssouso)", &sub))
                         goto oom;
 
                 HASHMAP_FOREACH_KEY(u, k, m->units, i) {
                         char *u_path, *j_path;
-                        const char *description, *load_state, *active_state, *sub_state, *sjob_type;
+                        const char *description, *load_state, *active_state, *sub_state, *sjob_type, *following;
                         DBusMessageIter sub2;
                         uint32_t job_id;
 
@@ -424,6 +424,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                         load_state = unit_load_state_to_string(u->meta.load_state);
                         active_state = unit_active_state_to_string(unit_active_state(u));
                         sub_state = unit_sub_state_to_string(u);
+                        following = u->meta.following ? u->meta.following->meta.id : "";
 
                         if (!(u_path = unit_dbus_path(u)))
                                 goto oom;
@@ -448,6 +449,7 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &load_state) ||
                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &active_state) ||
                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sub_state) ||
+                            !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &following) ||
                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_OBJECT_PATH, &u_path) ||
                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_UINT32, &job_id) ||
                             !dbus_message_iter_append_basic(&sub2, DBUS_TYPE_STRING, &sjob_type) ||
diff --git a/src/dbus-unit.c b/src/dbus-unit.c
index 2dcd032..bb25418 100644
--- a/src/dbus-unit.c
+++ b/src/dbus-unit.c
@@ -47,6 +47,23 @@ int bus_unit_append_names(Manager *m, DBusMessageIter *i, const char *property,
         return 0;
 }
 
+int bus_unit_append_following(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+        Unit *u = data;
+        const char *d;
+
+        assert(m);
+        assert(i);
+        assert(property);
+        assert(u);
+
+        d = u->meta.following ? u->meta.following->meta.id : "";
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_STRING, &d))
+                return -ENOMEM;
+
+        return 0;
+}
+
 int bus_unit_append_dependencies(Manager *m, DBusMessageIter *i, const char *property, void *data) {
         Unit *u;
         Iterator j;
diff --git a/src/dbus-unit.h b/src/dbus-unit.h
index e49f82d..0d17322 100644
--- a/src/dbus-unit.h
+++ b/src/dbus-unit.h
@@ -60,6 +60,7 @@
         "  <signal name=\"Changed\"/>\n"                                \
         "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
         "  <property name=\"Names\" type=\"as\" access=\"read\"/>\n"    \
+        "  <property name=\"Following\" type=\"s\" 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" \
@@ -97,6 +98,7 @@
 #define BUS_UNIT_PROPERTIES \
         { "org.freedesktop.systemd1.Unit", "Id",                   bus_property_append_string,     "s",    u->meta.id                        }, \
         { "org.freedesktop.systemd1.Unit", "Names",                bus_unit_append_names,          "as",   u                                 }, \
+        { "org.freedesktop.systemd1.Unit", "Following",            bus_unit_append_following,      "s",    u                                 }, \
         { "org.freedesktop.systemd1.Unit", "Requires",             bus_unit_append_dependencies,   "as",   u->meta.dependencies[UNIT_REQUIRES] }, \
         { "org.freedesktop.systemd1.Unit", "RequiresOverridable",  bus_unit_append_dependencies,   "as",   u->meta.dependencies[UNIT_REQUIRES_OVERRIDABLE] }, \
         { "org.freedesktop.systemd1.Unit", "Requisite",            bus_unit_append_dependencies,   "as",   u->meta.dependencies[UNIT_REQUISITE] }, \
@@ -131,6 +133,7 @@
         { "org.freedesktop.systemd1.Unit", "JobTimeoutUSec",       bus_property_append_usec,       "t",    &u->meta.job_timeout              }
 
 int bus_unit_append_names(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_following(Manager *m, DBusMessageIter *i, const char *property, void *data);
 int bus_unit_append_dependencies(Manager *m, DBusMessageIter *i, const char *property, void *data);
 int bus_unit_append_description(Manager *m, DBusMessageIter *i, const char *property, void *data);
 int bus_unit_append_load_state(Manager *m, DBusMessageIter *i, const char *property, void *data);
diff --git a/src/device.c b/src/device.c
index 39ab291..526e714 100644
--- a/src/device.c
+++ b/src/device.c
@@ -35,12 +35,40 @@ static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = {
         [DEVICE_PLUGGED] = UNIT_ACTIVE
 };
 
+static void device_unset_sysfs(Device *d) {
+        Device *first;
+
+        assert(d);
+
+        if (d->sysfs) {
+                /* Remove this unit from the chain of devices which share the
+                 * same sysfs path. */
+                first = hashmap_get(d->meta.manager->devices_by_sysfs, d->sysfs);
+                LIST_REMOVE(Device, same_sysfs, first, d);
+
+                if (first)
+                        hashmap_remove_and_replace(d->meta.manager->devices_by_sysfs, d->sysfs, first->sysfs, first);
+                else
+                        hashmap_remove(d->meta.manager->devices_by_sysfs, d->sysfs);
+
+                free(d->sysfs);
+                d->sysfs = NULL;
+        }
+
+        d->meta.following = NULL;
+}
+
 static void device_init(Unit *u) {
         Device *d = DEVICE(u);
 
         assert(d);
         assert(d->meta.load_state == UNIT_STUB);
 
+        /* In contrast to all other unit types we timeout jobs waiting
+         * for devices by default. This is because they otherwise wait
+         * indefinetely for plugged in devices, something which cannot
+         * happen for the other units since their operations time out
+         * anyway. */
         d->meta.job_timeout = DEFAULT_TIMEOUT_USEC;
 }
 
@@ -49,8 +77,7 @@ static void device_done(Unit *u) {
 
         assert(d);
 
-        free(d->sysfs);
-        d->sysfs = NULL;
+        device_unset_sysfs(d);
 }
 
 static void device_set_state(Device *d, DeviceState state) {
@@ -105,7 +132,7 @@ static const char *device_sub_state_to_string(Unit *u) {
         return device_state_to_string(DEVICE(u)->state);
 }
 
-static int device_add_escaped_name(Unit *u, const char *dn, bool make_id) {
+static int device_add_escaped_name(Unit *u, const char *dn) {
         char *e;
         int r;
 
@@ -117,10 +144,6 @@ static int device_add_escaped_name(Unit *u, const char *dn, bool make_id) {
                 return -ENOMEM;
 
         r = unit_add_name(u, e);
-
-        if (r >= 0 && make_id)
-                unit_choose_id(u, e);
-
         free(e);
 
         if (r < 0 && r != -EEXIST)
@@ -152,134 +175,108 @@ static int device_find_escape_name(Manager *m, const char *dn, Unit **_u) {
         return 0;
 }
 
-static int device_process_new_device(Manager *m, struct udev_device *dev, bool update_state) {
-        const char *dn, *wants, *sysfs, *model, *alias;
+static int device_update_unit(Manager *m, struct udev_device *dev, const char *path, bool main) {
+        const char *sysfs, *model;
         Unit *u = NULL;
         int r;
-        char *w, *state;
-        size_t l;
         bool delete;
-        struct udev_list_entry *item = NULL, *first = NULL;
 
         assert(m);
 
         if (!(sysfs = udev_device_get_syspath(dev)))
                 return -ENOMEM;
 
-        /* Check whether this entry is even relevant for us. */
-        dn = udev_device_get_devnode(dev);
-        wants = udev_device_get_property_value(dev, "SYSTEMD_WANTS");
-        alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS");
-
-        /* We allow exactly one alias to be configured a this time and
-         * it must be a path */
-
-        if (alias && !is_path(alias)) {
-                log_warning("SYSTEMD_ALIAS for %s is not a path, ignoring: %s", sysfs, alias);
-                alias = NULL;
-        }
-
-        if ((r = device_find_escape_name(m, sysfs, &u)) < 0)
+        if ((r = device_find_escape_name(m, path, &u)) < 0)
                 return r;
 
-        if (r == 0 && dn)
-                if ((r = device_find_escape_name(m, dn, &u)) < 0)
-                        return r;
-
-        if (r == 0) {
-                first = udev_device_get_devlinks_list_entry(dev);
-                udev_list_entry_foreach(item, first) {
-                        if ((r = device_find_escape_name(m, udev_list_entry_get_name(item), &u)) < 0)
-                                return r;
-
-                        if (r > 0)
-                                break;
-                }
-        }
-
-        if (r == 0 && alias)
-                if ((r = device_find_escape_name(m, alias, &u)) < 0)
-                        return r;
-
-        /* FIXME: this needs proper merging */
-
-        assert((r > 0) == !!u);
-
         /* If this is a different unit, then let's not merge things */
-        if (u && DEVICE(u)->sysfs && !path_equal(DEVICE(u)->sysfs, sysfs))
-                u = NULL;
+        if (u && DEVICE(u)->sysfs && !path_equal(DEVICE(u)->sysfs, sysfs)) {
+                log_error("Hmm, something's broken. Asked to create two devices with same name but different sysfs paths.");
+                return -EEXIST;
+        }
 
         if (!u) {
+                Device *first;
                 delete = true;
 
                 if (!(u = unit_new(m)))
                         return -ENOMEM;
 
-                if ((r = device_add_escaped_name(u, sysfs, true)) < 0)
+                if ((r = device_add_escaped_name(u, path)) < 0)
                         goto fail;
 
-                unit_add_to_load_queue(u);
-        } else
-                delete = false;
-
-        if (!(DEVICE(u)->sysfs))
                 if (!(DEVICE(u)->sysfs = strdup(sysfs))) {
                         r = -ENOMEM;
                         goto fail;
                 }
 
-        if (alias)
-                if ((r = device_add_escaped_name(u, alias, true)) < 0)
-                        goto fail;
+                unit_add_to_load_queue(u);
 
-        if (dn)
-                if ((r = device_add_escaped_name(u, dn, true)) < 0)
-                        goto fail;
+                if (!m->devices_by_sysfs)
+                        if (!(m->devices_by_sysfs = hashmap_new(string_hash_func, string_compare_func))) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
 
-        first = udev_device_get_devlinks_list_entry(dev);
-        udev_list_entry_foreach(item, first)
-                if ((r = device_add_escaped_name(u, udev_list_entry_get_name(item), false)) < 0)
+                first = hashmap_get(m->devices_by_sysfs, sysfs);
+                LIST_PREPEND(Device, same_sysfs, first, DEVICE(u));
+
+                if ((r = hashmap_replace(m->devices_by_sysfs, DEVICE(u)->sysfs, first)) < 0)
                         goto fail;
 
+        } else
+                delete = false;
+
         if ((model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE")) ||
             (model = udev_device_get_property_value(dev, "ID_MODEL"))) {
                 if ((r = unit_set_description(u, model)) < 0)
                         goto fail;
-        } else if (dn) {
-                if ((r = unit_set_description(u, dn)) < 0)
-                        goto fail;
         } else
-                if ((r = unit_set_description(u, sysfs)) < 0)
+                if ((r = unit_set_description(u, path)) < 0)
                         goto fail;
 
-        if (wants) {
-                FOREACH_WORD_QUOTED(w, l, wants, state) {
-                        char *e;
-
-                        if (!(e = strndup(w, l))) {
-                                r = -ENOMEM;
-                                goto fail;
+        if (main) {
+                /* The additional systemd udev properties we only
+                 * interpret for the main object */
+                const char *wants, *alias;
+
+                if ((alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"))) {
+                        if (!is_path(alias))
+                                log_warning("SYSTEMD_ALIAS for %s is not a path, ignoring: %s", sysfs, alias);
+                        else {
+                                if ((r = device_add_escaped_name(u, alias)) < 0)
+                                        goto fail;
                         }
+                }
 
-                        r = unit_add_dependency_by_name(u, UNIT_WANTS, e, NULL, true);
-                        free(e);
+                if ((wants = udev_device_get_property_value(dev, "SYSTEMD_WANTS"))) {
+                        char *state, *w;
+                        size_t l;
 
-                        if (r < 0)
-                                goto fail;
+                        FOREACH_WORD_QUOTED(w, l, wants, state) {
+                                char *e;
+
+                                if (!(e = strndup(w, l))) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+
+                                r = unit_add_dependency_by_name(u, UNIT_WANTS, e, NULL, true);
+                                free(e);
+
+                                if (r < 0)
+                                        goto fail;
+                        }
                 }
-        }
 
-        if (update_state) {
-                manager_dispatch_load_queue(u->meta.manager);
-                device_set_state(DEVICE(u), DEVICE_PLUGGED);
-        }
+                u->meta.following = NULL;
+        } else
+                device_find_escape_name(m, sysfs, &u->meta.following);
 
         unit_add_to_dbus_queue(u);
-
         return 0;
 
 fail:
-
         log_warning("Failed to load device unit: %s", strerror(-r));
 
         if (delete && u)
@@ -288,6 +285,50 @@ fail:
         return r;
 }
 
+static int device_process_new_device(Manager *m, struct udev_device *dev, bool update_state) {
+        const char *sysfs, *dn;
+        struct udev_list_entry *item = NULL, *first = NULL;
+
+        assert(m);
+
+        if (!(sysfs = udev_device_get_syspath(dev)))
+                return -ENOMEM;
+
+        /* Add the main unit named after the sysfs path */
+        device_update_unit(m, dev, sysfs, true);
+
+        /* Add an additional unit for the device node */
+        if ((dn = udev_device_get_devnode(dev)))
+                device_update_unit(m, dev, dn, false);
+
+        /* Add additional units for all symlinks */
+        first = udev_device_get_devlinks_list_entry(dev);
+        udev_list_entry_foreach(item, first) {
+                const char *p;
+
+                /* Don't bother with the /dev/block links */
+                p = udev_list_entry_get_name(item);
+
+                if (path_startswith(p, "/dev/block/") ||
+                    path_startswith(p, "/dev/char/"))
+                        continue;
+
+                device_update_unit(m, dev, p, false);
+        }
+
+        if (update_state) {
+                Device *d, *l;
+
+                manager_dispatch_load_queue(m);
+
+                l = hashmap_get(m->devices_by_sysfs, sysfs);
+                LIST_FOREACH(same_sysfs, d, l)
+                        device_set_state(d, DEVICE_PLUGGED);
+        }
+
+        return 0;
+}
+
 static int device_process_path(Manager *m, const char *path, bool update_state) {
         int r;
         struct udev_device *dev;
@@ -307,8 +348,6 @@ static int device_process_path(Manager *m, const char *path, bool update_state)
 
 static int device_process_removed_device(Manager *m, struct udev_device *dev) {
         const char *sysfs;
-        char *e;
-        Unit *u;
         Device *d;
 
         assert(m);
@@ -317,21 +356,12 @@ static int device_process_removed_device(Manager *m, struct udev_device *dev) {
         if (!(sysfs = udev_device_get_syspath(dev)))
                 return -ENOMEM;
 
-        assert(sysfs[0] == '/');
-        if (!(e = unit_name_from_path(sysfs, ".device")))
-                return -ENOMEM;
-
-        u = manager_get_unit(m, e);
-        free(e);
-
-        if (!u)
-                return 0;
-
-        d = DEVICE(u);
-        free(d->sysfs);
-        d->sysfs = NULL;
+        /* Remove all units of this sysfs path */
+        while ((d = hashmap_get(m->devices_by_sysfs, sysfs))) {
+                device_unset_sysfs(d);
+                device_set_state(d, DEVICE_DEAD);
+        }
 
-        device_set_state(d, DEVICE_DEAD);
         return 0;
 }
 
@@ -347,6 +377,9 @@ static void device_shutdown(Manager *m) {
                 udev_unref(m->udev);
                 m->udev = NULL;
         }
+
+        hashmap_free(m->devices_by_sysfs);
+        m->devices_by_sysfs = NULL;
 }
 
 static int device_enumerate(Manager *m) {
@@ -464,6 +497,7 @@ const UnitVTable device_vtable = {
         .no_instances = true,
         .no_snapshots = true,
         .no_isolate = true,
+        .no_alias = true,
 
         .init = device_init,
 
diff --git a/src/device.h b/src/device.h
index 654499c..5757c91 100644
--- a/src/device.h
+++ b/src/device.h
@@ -41,6 +41,12 @@ struct Device {
         DeviceState state;
 
         char *sysfs;
+
+        /* In order to be able to distuingish dependencies on
+        different device nodes we might end up creating multiple
+        devices for the same sysfs path. We chain them up here. */
+
+        LIST_FIELDS(struct Device, same_sysfs);
 };
 
 extern const UnitVTable device_vtable;
diff --git a/src/hashmap.c b/src/hashmap.c
index 5a993b6..a59b880 100644
--- a/src/hashmap.c
+++ b/src/hashmap.c
@@ -296,6 +296,33 @@ int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key,
         return 0;
 }
 
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+        struct hashmap_entry *e, *k;
+        unsigned old_hash, new_hash;
+
+        if (!h)
+                return -ENOENT;
+
+        old_hash = h->hash_func(old_key) % NBUCKETS;
+        if (!(e = hash_scan(h, old_hash, old_key)))
+                return -ENOENT;
+
+        new_hash = h->hash_func(new_key) % NBUCKETS;
+
+        if ((k = hash_scan(h, new_hash, new_key)))
+                if (e != k)
+                        remove_entry(h, k);
+
+        unlink_entry(h, e, old_hash);
+
+        e->key = new_key;
+        e->value = value;
+
+        link_entry(h, e, new_hash);
+
+        return 0;
+}
+
 void* hashmap_remove_value(Hashmap *h, const void *key, void *value) {
         struct hashmap_entry *e;
         unsigned hash;
diff --git a/src/hashmap.h b/src/hashmap.h
index 3ff3efe..8fb7f82 100644
--- a/src/hashmap.h
+++ b/src/hashmap.h
@@ -56,6 +56,7 @@ void* hashmap_get(Hashmap *h, const void *key);
 void* hashmap_remove(Hashmap *h, const void *key);
 void* hashmap_remove_value(Hashmap *h, const void *key, void *value);
 int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
+int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value);
 
 int hashmap_merge(Hashmap *h, Hashmap *other);
 void hashmap_move(Hashmap *h, Hashmap *other);
diff --git a/src/manager.h b/src/manager.h
index 96de120..7328724 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -146,6 +146,7 @@ struct Manager {
         struct udev* udev;
         struct udev_monitor* udev_monitor;
         Watch udev_watch;
+        Hashmap *devices_by_sysfs;
 
         /* Data specific to the mount subsystem */
         FILE *proc_self_mountinfo;
diff --git a/src/path.h b/src/path.h
index e0feeea..5c06de4 100644
--- a/src/path.h
+++ b/src/path.h
@@ -45,16 +45,18 @@ typedef enum PathType {
 } PathType;
 
 typedef struct PathSpec {
-        PathType type;
         char *path;
 
+        Watch watch;
+
+        LIST_FIELDS(struct PathSpec, spec);
+
+        PathType type;
         int inotify_fd;
         int primary_wd;
-        bool previous_exists;
 
-        Watch watch;
+        bool previous_exists;
 
-        LIST_FIELDS(struct PathSpec, spec);
 } PathSpec;
 
 struct Path {
@@ -62,9 +64,10 @@ struct Path {
 
         LIST_HEAD(PathSpec, specs);
 
-        PathState state, deserialized_state;
         Unit *unit;
 
+        PathState state, deserialized_state;
+
         bool failure;
 };
 
diff --git a/src/service.h b/src/service.h
index d254044..0ddaaa4 100644
--- a/src/service.h
+++ b/src/service.h
@@ -90,8 +90,6 @@ struct Service {
         ServiceType type;
         ServiceRestart restart;
 
-        NotifyAccess notify_access;
-
         /* If set we'll read the main daemon PID from this file */
         char *pid_file;
 
@@ -101,10 +99,6 @@ struct Service {
         ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX];
         ExecContext exec_context;
 
-        bool permissions_start_only;
-        bool root_directory_start_only;
-        bool valid_no_process;
-
         ServiceState state, deserialized_state;
 
         ExecStatus main_exec_status;
@@ -112,6 +106,11 @@ struct Service {
         ExecCommand *control_command;
         ServiceExecCommand control_command_id;
         pid_t main_pid, control_pid;
+
+        bool permissions_start_only;
+        bool root_directory_start_only;
+        bool valid_no_process;
+
         bool main_pid_known:1;
 
         /* If we shut down, remember why */
@@ -124,8 +123,11 @@ struct Service {
         bool got_socket_fd:1;
 
         bool sysv_has_lsb:1;
-        char *sysv_path;
+
+        int socket_fd;
         int sysv_start_priority;
+
+        char *sysv_path;
         char *sysv_runlevels;
 
         char *bus_name;
@@ -134,10 +136,11 @@ struct Service {
 
         RateLimit ratelimit;
 
-        int socket_fd;
         struct Socket *socket;
 
         Watch timer_watch;
+
+        NotifyAccess notify_access;
 };
 
 extern const UnitVTable service_vtable;
diff --git a/src/systemctl.c b/src/systemctl.c
index acb89a5..bdfd0cd 100644
--- a/src/systemctl.c
+++ b/src/systemctl.c
@@ -58,6 +58,7 @@ static bool arg_no_sync = false;
 static bool arg_no_wall = false;
 static bool arg_dry = false;
 static bool arg_quiet = false;
+static char arg_full = false;
 static char **arg_wall = NULL;
 static enum action {
         ACTION_INVALID,
@@ -192,7 +193,7 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
                 printf("%-45s %-6s %-12s %-12s %-15s %s\n", "UNIT", "LOAD", "ACTIVE", "SUB", "JOB", "DESCRIPTION");
 
         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
-                const char *id, *description, *load_state, *active_state, *sub_state, *unit_path, *job_type, *job_path, *dot;
+                const char *id, *description, *load_state, *active_state, *sub_state, *following, *unit_path, *job_type, *job_path, *dot;
                 uint32_t job_id;
 
                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
@@ -208,6 +209,7 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &load_state, true) < 0 ||
                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &active_state, true) < 0 ||
                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &sub_state, true) < 0 ||
+                    bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &following, true) < 0 ||
                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &unit_path, true) < 0 ||
                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_UINT32, &job_id, true) < 0 ||
                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &job_type, true) < 0 ||
@@ -219,14 +221,16 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
 
                 if ((!arg_type || ((dot = strrchr(id, '.')) &&
                                    streq(dot+1, arg_type))) &&
-                    (arg_all || !streq(active_state, "inactive") || job_id > 0)) {
-
+                    (arg_all || !(streq(active_state, "inactive") || following[0]) || job_id > 0)) {
+                        char *e;
                         int a = 0, b = 0;
 
                         if (streq(active_state, "maintenance"))
                                 fputs(ANSI_HIGHLIGHT_ON, stdout);
 
-                        printf("%-45s %-6s %-12s %-12s%n", id, load_state, active_state, sub_state, &a);
+                        e = arg_full ? NULL : ellipsize(id, 45, 33);
+                        printf("%-45s %-6s %-12s %-12s%n", e ? e : id, load_state, active_state, sub_state, &a);
+                        free(e);
 
                         if (job_id != 0)
                                 printf(" => %-12s%n", job_type, &b);
@@ -558,6 +562,7 @@ static int list_jobs(DBusConnection *bus, char **args, unsigned n) {
         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
                 const char *name, *type, *state, *job_path, *unit_path;
                 uint32_t id;
+                char *e;
 
                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
                         log_error("Failed to parse reply.");
@@ -578,7 +583,10 @@ static int list_jobs(DBusConnection *bus, char **args, unsigned n) {
                         goto finish;
                 }
 
-                printf("%4u %-45s %-17s %-7s\n", id, name, type, state);
+                e = arg_full ? NULL : ellipsize(name, 45, 33);
+                printf("%4u %-45s %-17s %-7s\n", id, e ? e : name, type, state);
+                free(e);
+
                 k++;
 
                 dbus_message_iter_next(&sub);
@@ -2895,6 +2903,7 @@ static int systemctl_help(void) {
                "  -t --type=TYPE     List only units of a particular type\n"
                "  -p --property=NAME Show only properties by this name\n"
                "  -a --all           Show all units/properties, including dead/empty ones\n"
+               "     --full          Don't ellipsize unit names.\n"
                "     --fail          When installing a new job, fail if conflicting jobs are\n"
                "                     pending\n"
                "     --system        Connect to system bus\n"
@@ -3022,7 +3031,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 ARG_NO_BLOCK,
                 ARG_NO_WALL,
                 ARG_ORDER,
-                ARG_REQUIRE
+                ARG_REQUIRE,
+                ARG_FULL
         };
 
         static const struct option options[] = {
@@ -3030,6 +3040,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                 { "type",      required_argument, NULL, 't'          },
                 { "property",  required_argument, NULL, 'p'          },
                 { "all",       no_argument,       NULL, 'a'          },
+                { "full",      no_argument,       NULL, ARG_FULL     },
                 { "fail",      no_argument,       NULL, ARG_FAIL     },
                 { "session",   no_argument,       NULL, ARG_SESSION  },
                 { "system",    no_argument,       NULL, ARG_SYSTEM   },
@@ -3099,6 +3110,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
                         arg_dot = DOT_REQUIRE;
                         break;
 
+                case ARG_FULL:
+                        arg_full = true;
+                        break;
+
                 case 'q':
                         arg_quiet = true;
                         break;
diff --git a/src/systemd-interfaces.vala b/src/systemd-interfaces.vala
index 7445479..612cb13 100644
--- a/src/systemd-interfaces.vala
+++ b/src/systemd-interfaces.vala
@@ -28,6 +28,7 @@ public interface Manager : DBus.Object {
                 string load_state;
                 string active_state;
                 string sub_state;
+                string following;
                 ObjectPath unit_path;
                 uint32 job_id;
                 string job_type;
diff --git a/src/unit.c b/src/unit.c
index 5807e4f..50f3b8f 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -625,6 +625,9 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
         SET_FOREACH(t, u->meta.names, i)
                 fprintf(f, "%s\tName: %s\n", prefix, t);
 
+        if (u->meta.following)
+                fprintf(f, "%s\tFollowing: %s\n", prefix, u->meta.following->meta.id);
+
         if (u->meta.fragment_path)
                 fprintf(f, "%s\tFragment Path: %s\n", prefix, u->meta.fragment_path);
 
diff --git a/src/unit.h b/src/unit.h
index 1295d9f..f117127 100644
--- a/src/unit.h
+++ b/src/unit.h
@@ -176,6 +176,9 @@ struct Meta {
         /* GC queue */
         LIST_FIELDS(Meta, gc_queue);
 
+        /* This follows another unit in state */
+        Unit *following;
+
         /* Used during GC sweeps */
         unsigned gc_marker;
 
diff --git a/src/util.c b/src/util.c
index 519d229..45de609 100644
--- a/src/util.c
+++ b/src/util.c
@@ -2912,6 +2912,38 @@ int running_in_chroot(void) {
                 a.st_ino != b.st_ino;
 }
 
+char *ellipsize(const char *s, unsigned length, unsigned percent) {
+        size_t l, x;
+        char *r;
+
+        assert(s);
+        assert(percent <= 100);
+        assert(length >= 3);
+
+        l = strlen(s);
+
+        if (l <= 3 || l <= length)
+                return strdup(s);
+
+        if (!(r = new0(char, length+1)))
+                return r;
+
+        x = (length * percent) / 100;
+
+        if (x > length - 3)
+                x = length - 3;
+
+        memcpy(r, s, x);
+        r[x] = '.';
+        r[x+1] = '.';
+        r[x+2] = '.';
+        memcpy(r + x + 3,
+               s + l - (length - x - 3),
+               length - x - 3);
+
+        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 e4b1f81..782adb8 100644
--- a/src/util.h
+++ b/src/util.h
@@ -334,6 +334,8 @@ int columns(void);
 
 int running_in_chroot(void);
 
+char *ellipsize(const char *s, unsigned length, unsigned percent);
+
 const char *ioprio_class_to_string(int i);
 int ioprio_class_from_string(const char *s);
 
commit 5632e3743db350a67478acc107d76cdf648a1f99
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sun Jul 18 04:58:01 2010 +0200

    systemctl: introduce reset-maintenance command

diff --git a/fixme b/fixme
index 30cfaac..29e0fde 100644
--- a/fixme
+++ b/fixme
@@ -47,8 +47,6 @@
 
 * debian deadlock when partition auf noauto is.
 
-* maintenance units müssen vergessen werden
-
 * fingerprint.target, wireless.target, gps.target
 
 * fix merging of device units
@@ -57,6 +55,8 @@
 
 * pahole
 
+* color aus bei stdout auf !tty
+
 External:
 
 * default.target must be %ghosted...
diff --git a/man/systemctl.xml b/man/systemctl.xml
index 42682b7..737bcbe 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -314,6 +314,25 @@
                                 properties of the job is
                                 shown.</para></listitem>
                         </varlistentry>
+
+                        <varlistentry>
+                                <term><command>reset-maintenance [NAME...]</command></term>
+
+                                <listitem><para>Reset maintenance
+                                state of the specified units, or if no
+                                unit name is passed of all units. When
+                                a unit fails in some way (i.e. process
+                                exiting with non-zero error code,
+                                terminating abnormally or timing out)
+                                it will automatically enter
+                                maintenance state and its exit codes
+                                and status is recorded for
+                                introspection by the administrator
+                                until the service is restarted or
+                                reset with this
+                                command.</para></listitem>
+                        </varlistentry>
+
                         <varlistentry>
                                 <term><command>load [NAME...]</command></term>
 
diff --git a/man/systemd.xml b/man/systemd.xml
index 25f24ce..c027b4f 100644
--- a/man/systemd.xml
+++ b/man/systemd.xml
@@ -226,7 +226,7 @@
                 <para>systemd provides a dependency system between
                 various entities called "units". Units encapsulate
                 various objects that are relevant for system boot-up
-                and maintainance. The majority of units are configured
+                and maintenance. The majority of units are configured
                 in unit configuration files, whose syntax and basic
                 set of options is described in
                 <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
diff --git a/src/automount.c b/src/automount.c
index e685c96..213b178 100644
--- a/src/automount.c
+++ b/src/automount.c
@@ -791,6 +791,17 @@ static void automount_shutdown(Manager *m) {
                 close_nointr_nofail(m->dev_autofs_fd);
 }
 
+static void automount_reset_maintenance(Unit *u) {
+        Automount *a = AUTOMOUNT(u);
+
+        assert(a);
+
+        if (a->state == AUTOMOUNT_MAINTENANCE)
+                automount_set_state(a, AUTOMOUNT_DEAD);
+
+        a->failure = false;
+}
+
 static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = {
         [AUTOMOUNT_DEAD] = "dead",
         [AUTOMOUNT_WAITING] = "waiting",
@@ -827,6 +838,8 @@ const UnitVTable automount_vtable = {
 
         .fd_event = automount_fd_event,
 
+        .reset_maintenance = automount_reset_maintenance,
+
         .bus_message_handler = bus_automount_message_handler,
 
         .shutdown = automount_shutdown
diff --git a/src/dbus-manager.c b/src/dbus-manager.c
index c717ccd..969c430 100644
--- a/src/dbus-manager.c
+++ b/src/dbus-manager.c
@@ -72,11 +72,15 @@
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
         "  </method>\n"                                                 \
+        "  <method name=\"ResetMaintenanceUnit\">\n"                    \
+        "   <arg name=\"name\" type=\"s\" direction=\"in\"/>\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=\"ResetMaintenance\"/>\n"                       \
         "  <method name=\"ListUnits\">\n"                               \
         "   <arg name=\"units\" type=\"a(sssssouso)\" direction=\"out\"/>\n" \
         "  </method>\n"                                                 \
@@ -362,6 +366,34 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
                 if (!(reply = dbus_message_new_method_return(message)))
                         goto oom;
 
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetMaintenance")) {
+
+                manager_reset_maintenance(m);
+
+                if (!(reply = dbus_message_new_method_return(message)))
+                        goto oom;
+
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ResetMaintenanceUnit")) {
+                const char *name;
+                Unit *u;
+
+                if (!dbus_message_get_args(
+                                    message,
+                                    &error,
+                                    DBUS_TYPE_STRING, &name,
+                                    DBUS_TYPE_INVALID))
+                        return bus_send_error_reply(m, connection, message, &error, -EINVAL);
+
+                if (!(u = manager_get_unit(m, name))) {
+                        dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name);
+                        return bus_send_error_reply(m, connection, message, &error, -ENOENT);
+                }
+
+                unit_reset_maintenance(u);
+
+                if (!(reply = dbus_message_new_method_return(message)))
+                        goto oom;
+
         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "ListUnits")) {
                 DBusMessageIter iter, sub;
                 Iterator i;
@@ -733,7 +765,6 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection,
         } else
                 return bus_default_message_handler(m, connection, message, NULL, properties);
 
-
         if (job_type != _JOB_TYPE_INVALID) {
                 const char *name, *smode;
                 JobMode mode;
diff --git a/src/dbus-unit.c b/src/dbus-unit.c
index ce34130..2dcd032 100644
--- a/src/dbus-unit.c
+++ b/src/dbus-unit.c
@@ -298,6 +298,13 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn
         } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ReloadOrTryRestart")) {
                 reload_if_possible = true;
                 job_type = JOB_TRY_RESTART;
+        } else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "ResetMaintenance")) {
+
+                unit_reset_maintenance(u);
+
+                if (!(reply = dbus_message_new_method_return(message)))
+                        goto oom;
+
         } else if (UNIT_VTABLE(u)->bus_message_handler)
                 return UNIT_VTABLE(u)->bus_message_handler(u, connection, message);
         else
diff --git a/src/dbus-unit.h b/src/dbus-unit.h
index 0b9c62f..e49f82d 100644
--- a/src/dbus-unit.h
+++ b/src/dbus-unit.h
@@ -56,6 +56,7 @@
         "   <arg name=\"mode\" type=\"s\" direction=\"in\"/>\n"         \
         "   <arg name=\"job\" type=\"o\" direction=\"out\"/>\n"         \
         "  </method>\n"                                                 \
+        "  <method name=\"ResetMaintenance\"/>\n"                       \
         "  <signal name=\"Changed\"/>\n"                                \
         "  <property name=\"Id\" type=\"s\" access=\"read\"/>\n"        \
         "  <property name=\"Names\" type=\"as\" access=\"read\"/>\n"    \
diff --git a/src/manager.c b/src/manager.c
index 7b2586f..8a9b9dd 100644
--- a/src/manager.c
+++ b/src/manager.c
@@ -2466,6 +2466,16 @@ bool manager_is_booting_or_shutting_down(Manager *m) {
         return false;
 }
 
+void manager_reset_maintenance(Manager *m) {
+        Unit *u;
+        Iterator i;
+
+        assert(m);
+
+        HASHMAP_FOREACH(u, m->units, i)
+                unit_reset_maintenance(u);
+}
+
 static const char* const manager_running_as_table[_MANAGER_RUNNING_AS_MAX] = {
         [MANAGER_SYSTEM] = "system",
         [MANAGER_SESSION] = "session"
diff --git a/src/manager.h b/src/manager.h
index c492ffa..96de120 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -247,6 +247,8 @@ int manager_reload(Manager *m);
 
 bool manager_is_booting_or_shutting_down(Manager *m);
 
+void manager_reset_maintenance(Manager *m);
+
 const char *manager_running_as_to_string(ManagerRunningAs i);
 ManagerRunningAs manager_running_as_from_string(const char *s);
 
diff --git a/src/mount.c b/src/mount.c
index bee3c9a..c1a1d51 100644
--- a/src/mount.c
+++ b/src/mount.c
@@ -1538,6 +1538,17 @@ finish:
         return r;
 }
 
+static void mount_reset_maintenance(Unit *u) {
+        Mount *m = MOUNT(u);
+
+        assert(m);
+
+        if (m->state == MOUNT_MAINTENANCE)
+                mount_set_state(m, MOUNT_DEAD);
+
+        m->failure = false;
+}
+
 static const char* const mount_state_table[_MOUNT_STATE_MAX] = {
         [MOUNT_DEAD] = "dead",
         [MOUNT_MOUNTING] = "mounting",
@@ -1595,6 +1606,8 @@ const UnitVTable mount_vtable = {
         .sigchld_event = mount_sigchld_event,
         .timer_event = mount_timer_event,
 
+        .reset_maintenance = mount_reset_maintenance,
+
         .bus_message_handler = bus_mount_message_handler,
 
         .enumerate = mount_enumerate,
diff --git a/src/path.c b/src/path.c
index a9fa377..f4c2094 100644
--- a/src/path.c
+++ b/src/path.c
@@ -560,6 +560,17 @@ fail:
         log_error("Failed find path unit: %s", strerror(-r));
 }
 
+static void path_reset_maintenance(Unit *u) {
+        Path *p = PATH(u);
+
+        assert(p);
+
+        if (p->state == PATH_MAINTENANCE)
+                path_set_state(p, PATH_DEAD);
+
+        p->failure = false;
+}
+
 static const char* const path_state_table[_PATH_STATE_MAX] = {
         [PATH_DEAD] = "dead",
         [PATH_WAITING] = "waiting",
@@ -598,5 +609,7 @@ const UnitVTable path_vtable = {
 
         .fd_event = path_fd_event,
 
+        .reset_maintenance = path_reset_maintenance,
+
         .bus_message_handler = bus_path_message_handler
 };
diff --git a/src/service.c b/src/service.c
index a5d1ebd..6b786d1 100644
--- a/src/service.c
+++ b/src/service.c
@@ -2734,6 +2734,17 @@ int service_set_socket_fd(Service *s, int fd, Socket *sock) {
         return 0;
 }
 
+static void service_reset_maintenance(Unit *u) {
+        Service *s = SERVICE(u);
+
+        assert(s);
+
+        if (s->state == SERVICE_MAINTENANCE)
+                service_set_state(s, SERVICE_DEAD);
+
+        s->failure = false;
+}
+
 static const char* const service_state_table[_SERVICE_STATE_MAX] = {
         [SERVICE_DEAD] = "dead",
         [SERVICE_START_PRE] = "start-pre",
@@ -2821,6 +2832,8 @@ const UnitVTable service_vtable = {
         .sigchld_event = service_sigchld_event,
         .timer_event = service_timer_event,
 
+        .reset_maintenance = service_reset_maintenance,
+
         .cgroup_notify_empty = service_cgroup_notify_event,
         .notify_message = service_notify_message,
 
diff --git a/src/socket.c b/src/socket.c
index 4cf21cf..2c9d693 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -1698,6 +1698,17 @@ void socket_connection_unref(Socket *s) {
         log_debug("%s: One connection closed, %u left.", s->meta.id, s->n_connections);
 }
 
+static void socket_reset_maintenance(Unit *u) {
+        Socket *s = SOCKET(u);
+
+        assert(s);
+
+        if (s->state == SOCKET_MAINTENANCE)
+                socket_set_state(s, SOCKET_DEAD);
+
+        s->failure = false;
+}
+
 static const char* const socket_state_table[_SOCKET_STATE_MAX] = {
         [SOCKET_DEAD] = "dead",
         [SOCKET_START_PRE] = "start-pre",
@@ -1750,5 +1761,7 @@ const UnitVTable socket_vtable = {
         .sigchld_event = socket_sigchld_event,
         .timer_event = socket_timer_event,
 
+        .reset_maintenance = socket_reset_maintenance,
+
         .bus_message_handler = bus_socket_message_handler
 };
diff --git a/src/swap.c b/src/swap.c
index de468a0..6765a4a 100644
--- a/src/swap.c
+++ b/src/swap.c
@@ -542,14 +542,6 @@ static void swap_shutdown(Manager *m) {
         }
 }
 
-static const char* const swap_state_table[_SWAP_STATE_MAX] = {
-        [SWAP_DEAD] = "dead",
-        [SWAP_ACTIVE] = "active",
-        [SWAP_MAINTENANCE] = "maintenance"
-};
-
-DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState);
-
 static int swap_enumerate(Manager *m) {
         int r;
         assert(m);
@@ -564,6 +556,23 @@ static int swap_enumerate(Manager *m) {
         return r;
 }
 
+static void swap_reset_maintenance(Unit *u) {
+        Swap *s = SWAP(u);
+
+        assert(s);
+
+        if (s->state == SWAP_MAINTENANCE)
+                swap_set_state(s, SWAP_DEAD);
+}
+
+static const char* const swap_state_table[_SWAP_STATE_MAX] = {
+        [SWAP_DEAD] = "dead",
+        [SWAP_ACTIVE] = "active",
+        [SWAP_MAINTENANCE] = "maintenance"
+};
+
+DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState);
+
 const UnitVTable swap_vtable = {
         .suffix = ".swap",
 
@@ -592,6 +601,8 @@ const UnitVTable swap_vtable = {
 
         .bus_message_handler = bus_swap_message_handler,
 
+        .reset_maintenance = swap_reset_maintenance,
+
         .enumerate = swap_enumerate,
         .shutdown = swap_shutdown
 };
diff --git a/src/systemctl.c b/src/systemctl.c
index 06b4cae..acb89a5 100644
--- a/src/systemctl.c
+++ b/src/systemctl.c
@@ -2639,10 +2639,11 @@ static int clear_jobs(DBusConnection *bus, char **args, unsigned n) {
                 assert(arg_action == ACTION_SYSTEMCTL);
 
                 method =
-                        streq(args[0], "clear-jobs")    ? "ClearJobs" :
-                        streq(args[0], "daemon-reload") ? "Reload" :
-                        streq(args[0], "daemon-reexec") ? "Reexecute" :
-                                                          "Exit";
+                        streq(args[0], "clear-jobs")        ? "ClearJobs" :
+                        streq(args[0], "daemon-reload")     ? "Reload" :
+                        streq(args[0], "daemon-reexec")     ? "Reexecute" :
+                        streq(args[0], "reset-maintenance") ? "ResetMaintenance" :
+                                                              "Exit";
         }
 
         if (!(m = dbus_message_new_method_call(
@@ -2682,6 +2683,63 @@ finish:
         return r;
 }
 
+static int reset_maintenance(DBusConnection *bus, char **args, unsigned n) {
+        DBusMessage *m = NULL, *reply = NULL;
+        unsigned i;
+        int r;
+        DBusError error;
+
+        assert(bus);
+        dbus_error_init(&error);
+
+        if (n <= 1)
+                return clear_jobs(bus, args, n);
+
+        for (i = 1; i < n; i++) {
+
+                if (!(m = dbus_message_new_method_call(
+                                      "org.freedesktop.systemd1",
+                                      "/org/freedesktop/systemd1",
+                                      "org.freedesktop.systemd1.Manager",
+                                      "ResetMaintenanceUnit"))) {
+                        log_error("Could not allocate message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!dbus_message_append_args(m,
+                                              DBUS_TYPE_STRING, args + i,
+                                              DBUS_TYPE_INVALID)) {
+                        log_error("Could not append arguments to message.");
+                        r = -ENOMEM;
+                        goto finish;
+                }
+
+                if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error))) {
+                        log_error("Failed to issue method call: %s", error.message);
+                        r = -EIO;
+                        goto finish;
+                }
+
+                dbus_message_unref(m);
+                dbus_message_unref(reply);
+                m = reply = NULL;
+        }
+
+        r = 0;
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        dbus_error_free(&error);
+
+        return r;
+}
+
 static int show_enviroment(DBusConnection *bus, char **args, unsigned n) {
         DBusMessage *m = NULL, *reply = NULL;
         DBusError error;
@@ -2832,7 +2890,7 @@ finish:
 static int systemctl_help(void) {
 
         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
-               "Send control commands to the systemd manager.\n\n"
+               "Send control commands to or query the systemd manager.\n\n"
                "  -h --help          Show this help\n"
                "  -t --type=TYPE     List only units of a particular type\n"
                "  -p --property=NAME Show only properties by this name\n"
@@ -2862,6 +2920,8 @@ static int systemctl_help(void) {
                "  status [NAME...]                Show status of one or more units\n"
                "  show [NAME...|JOB...]           Show properties of one or more\n"
                "                                  units/jobs/manager\n"
+               "  reset-maintenance [NAME...]     Reset maintenance state for all, one\n"
+               "                                  or more units\n"
                "  load [NAME...]                  Load one or more units\n"
                "  list-jobs                       List jobs\n"
                "  cancel [JOB...]                 Cancel one or more jobs\n"
@@ -3579,6 +3639,7 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[]) {
                 { "default",           EQUAL, 1, start_special   },
                 { "rescue",            EQUAL, 1, start_special   },
                 { "emergency",         EQUAL, 1, start_special   },
+                { "reset-maintenance", MORE,  1, reset_maintenance },
         };
 
         int left;
diff --git a/src/timer.c b/src/timer.c
index 1580478..cd6728a 100644
--- a/src/timer.c
+++ b/src/timer.c
@@ -450,6 +450,17 @@ fail:
         log_error("Failed find timer unit: %s", strerror(-r));
 }
 
+static void timer_reset_maintenance(Unit *u) {
+        Timer *t = TIMER(u);
+
+        assert(t);
+
+        if (t->state == TIMER_MAINTENANCE)
+                timer_set_state(t, TIMER_DEAD);
+
+        t->failure = false;
+}
+
 static const char* const timer_state_table[_TIMER_STATE_MAX] = {
         [TIMER_DEAD] = "dead",
         [TIMER_WAITING] = "waiting",
@@ -492,5 +503,7 @@ const UnitVTable timer_vtable = {
 
         .timer_event = timer_timer_event,
 
+        .reset_maintenance = timer_reset_maintenance,
+
         .bus_message_handler = bus_timer_message_handler
 };
diff --git a/src/unit.c b/src/unit.c
index 348d139..5807e4f 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -2071,6 +2071,13 @@ bool unit_need_daemon_reload(Unit *u) {
                 timespec_load(&st.st_mtim) != u->meta.fragment_mtime;
 }
 
+void unit_reset_maintenance(Unit *u) {
+        assert(u);
+
+        if (UNIT_VTABLE(u)->reset_maintenance)
+                UNIT_VTABLE(u)->reset_maintenance(u);
+}
+
 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
         [UNIT_SERVICE] = "service",
         [UNIT_TIMER] = "timer",
diff --git a/src/unit.h b/src/unit.h
index cfad3ca..1295d9f 100644
--- a/src/unit.h
+++ b/src/unit.h
@@ -290,6 +290,9 @@ struct UnitVTable {
         void (*sigchld_event)(Unit *u, pid_t pid, int code, int status);
         void (*timer_event)(Unit *u, uint64_t n_elapsed, Watch *w);
 
+        /* Reset maintenance state if we are in maintainance state */
+        void (*reset_maintenance)(Unit *u);
+
         /* Called whenever any of the cgroups this unit watches for
          * ran empty */
         void (*cgroup_notify_empty)(Unit *u);
@@ -467,6 +470,8 @@ void unit_status_printf(Unit *u, const char *format, ...);
 
 bool unit_need_daemon_reload(Unit *u);
 
+void unit_reset_maintenance(Unit *u);
+
 const char *unit_type_to_string(UnitType i);
 UnitType unit_type_from_string(const char *s);
 
commit b9975629f03a43d4c6b14fdb42eb8dd5a30af28f
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sun Jul 18 02:11:38 2010 +0200

    man: extend man pages a little

diff --git a/fixme b/fixme
index ef2b3f9..30cfaac 100644
--- a/fixme
+++ b/fixme
@@ -49,8 +49,6 @@
 
 * maintenance units müssen vergessen werden
 
-* maintenance muss dokumentiert werden, ebenso OnStartup= und JobTimeoutSec=
-
 * fingerprint.target, wireless.target, gps.target
 
 * fix merging of device units
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index a03df65..7284524 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -399,6 +399,15 @@
                         </varlistentry>
 
                         <varlistentry>
+                                <term><varname>OnFailure=</varname></term>
+
+                                <listitem><para>Lists one or more
+                                units that are activated when this
+                                unit fails (i.e. enters maintenance
+                                state).</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
                                 <term><varname>RecursiveStop=</varname></term>
 
                                 <listitem><para>Takes a boolean
@@ -495,6 +504,34 @@
                                 fails the unit will immediately fail
                                 too and the job is removed.</para></listitem>
                         </varlistentry>
+
+                        <varlistentry>
+                                <term><varname>JobTimeoutSec=</varname></term>
+
+                                <listitem><para>When clients are
+                                waiting for a job of this unit to
+                                complete, time out after the specified
+                                time. If this time limit is reached
+                                the job will be cancelled, the unit
+                                however will not change state or even
+                                enter maintenance mode. This value
+                                defaults to 0 (job timeouts disabled),
+                                except for device units. NB: this
+                                timeout is independent from any
+                                unit-specific timeout (for example,
+                                the timeout set with
+                                <varname>Timeout=</varname> in service
+                                units) as the job timeout has no effect
+                                on the unit itself, only on the job
+                                that might be pending for it. Or in
+                                other words: unit-specific timeouts
+                                are useful to abort unit state
+                                changes, and revert them. The job
+                                timeout set with this option however
+                                is useful to abort only the job waiting
+                                for the unit state to change.</para></listitem>
+                        </varlistentry>
+
                 </variablelist>
 
                 <para>Unit file may include a [Install] section, which
diff --git a/man/systemd.xml b/man/systemd.xml
index e74d71b..25f24ce 100644
--- a/man/systemd.xml
+++ b/man/systemd.xml
@@ -141,7 +141,7 @@
                                 <listitem><para>Tell systemd to run a
                                 system instance (resp. session
                                 instance), even if the process ID is
-                                not 1 (resp. is 1), i.e. system is not
+                                not 1 (resp. is 1), i.e. systemd is not
                                 (resp. is) run as init process.
                                 Normally it should not be necessary to
                                 pass these options, as systemd
@@ -232,12 +232,23 @@
                 <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
                 however some are created automatically from other
                 configuration or dynamically from system state. Units
-                may be active (meaning started, bound, plugged in, ...
-                depending on the unit type), or inactive (meaning
-                stopped, unbound, unplugged, ...), as well as in the
-                process of being activated or deactivated,
-                i.e. between the two states. The following unit types
-                are available:</para>
+                may be 'active' (meaning started, bound, plugged in,
+                ...  depending on the unit type, see below), or
+                'inactive' (meaning stopped, unbound, unplugged, ...),
+                as well as in the process of being activated or
+                deactivated, i.e. between the two states (these states
+                are called 'activating', 'deactivating'). A special
+                'maintenance' state is available as well which is very
+                similar to 'inactive' and is entered when the service
+                failed in some way (process returned error code on
+                exit, or crashed, or an operation timed out). If this
+                state is entered the cause will be logged, for later
+                reference. Note that the various unit types may have a
+                number of additional substates, which are mapped to
+                the five generalized unit states described
+                here.</para>
+
+                <para>The following unit types are available:</para>
 
                 <orderedlist>
                         <listitem><para>Service units, which control
@@ -304,6 +315,35 @@
                 list you may find in
                 <citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
 
+                <para>systemd knows various kinds of dependencies,
+                including positive and negative requirement
+                dependencies (i.e. <varname>Requires=</varname> and
+                <varname>Conflicts=</varname>) as well as ordering
+                dependencies (<varname>After=</varname> and
+                <varname>Before=</varname>). NB: ordering and
+                requirement dependencies are orthogonal. If only a
+                requirement dependency exists between two units
+                (e.g. <filename>foo.service</filename> requires
+                <filename>bar.service</filename>), but no ordering
+                dependency (e.g. <filename>foo.service</filename>
+                after <filename>bar.service</filename>) and both are
+                requested to start, they will be started in
+                parallel. It is a common pattern that both requirement
+                and ordering dependencies are placed between two
+                units. Also note that the majority of dependencies are
+                implicitly created and maintained by systemd. In most
+                cases it should be unnecessary to declare additional
+                dependencies manually, however it is possible to do
+                this.</para>
+
+                <para>Application programs and units (via
+                dependencies) may requests state changes of units. In
+                systemd, these requests are encapsulated as 'jobs' and
+                maintained in a job queue. Jobs may succeed or can
+                fail, their execution is ordered based on the ordering
+                dependencies of the units they have been scheduled
+                for.</para>
+
                 <para>On boot systemd activates the target unit
                 <filename>default.target</filename> whose job is to
                 activate on-boot services and other on-boot units by
commit 246756ca928adaee98edd0e67712e81d8f3255a6
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sun Jul 18 01:33:05 2010 +0200

    install: optionally remove all symlinks from configuration tree recursively

diff --git a/fixme b/fixme
index b80ea43..ef2b3f9 100644
--- a/fixme
+++ b/fixme
@@ -39,8 +39,6 @@
 
 * In command lines, support both "$FOO" and $FOO
 
-* systemd-install disable should recursively kill all symlinks
-
 * /etc must always take precedence even if we follow symlinks!
 
 * /lib/init/rw
@@ -51,12 +49,16 @@
 
 * maintenance units müssen vergessen werden
 
-* maintenance muss dokumentiert werden
+* maintenance muss dokumentiert werden, ebenso OnStartup= und JobTimeoutSec=
 
 * fingerprint.target, wireless.target, gps.target
 
 * fix merging of device units
 
+* set_put(), hashmap_put() return values checken. i.e. == 0 macht kein free()!
+
+* pahole
+
 External:
 
 * default.target must be %ghosted...
diff --git a/man/systemd-install.xml b/man/systemd-install.xml
index 03ed0c7..47dc63a 100644
--- a/man/systemd-install.xml
+++ b/man/systemd-install.xml
@@ -196,6 +196,27 @@
                                 the <command>realize</command> command
                                 is used.</para></listitem>
                         </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--all</option></term>
+
+                                <listitem><para>If set makes sure that
+                                all symlinks on the specified unit are
+                                removed from the configuration
+                                directory and its subdirectories, not
+                                just those specified in the
+                                <literal>[Install]</literal>
+                                section.</para></listitem>
+                        </varlistentry>
+
+                        <varlistentry>
+                                <term><option>--verbose</option></term>
+                                <term><option>-v</option></term>
+
+                                <listitem><para>Show what is done as
+                                it is done.</para></listitem>
+                        </varlistentry>
+
                 </variablelist>
 
                 <para>The following commands are understood:</para>
diff --git a/src/install.c b/src/install.c
index bd23a93..751d52b 100644
--- a/src/install.c
+++ b/src/install.c
@@ -25,6 +25,7 @@
 #include <errno.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <dirent.h>
 
 #include "log.h"
 #include "path-lookup.h"
@@ -36,6 +37,8 @@
 #include "sd-daemon.h"
 
 static bool arg_force = false;
+static bool arg_all = false;
+static bool arg_verbose = false;
 
 static enum {
         WHERE_SYSTEM,
@@ -67,7 +70,8 @@ typedef struct {
         char **wanted_by;
 } InstallInfo;
 
-Hashmap *will_install = NULL, *have_installed = NULL;
+static Hashmap *will_install = NULL, *have_installed = NULL;
+static Set *remove_symlinks_to = NULL;
 
 static int help(void) {
 
@@ -75,9 +79,12 @@ static int help(void) {
                "Install init system units.\n\n"
                "  -h --help           Show this help\n"
                "     --force          Override existing links\n"
+               "     --verbose        Show what is being done as it is done\n"
                "     --system         Install into system\n"
                "     --session        Install into session\n"
                "     --global         Install into all sessions\n"
+               "     --all            When disabling, remove all symlinks, not\n"
+               "                      just those listed in the [Install] section\n"
                "     --realize[=MODE] Start/stop/restart unit after installation\n"
                "                      Takes 'no', 'minimal', 'maybe' or 'yes'\n\n"
                "Commands:\n"
@@ -98,7 +105,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_SYSTEM,
                 ARG_GLOBAL,
                 ARG_FORCE,
-                ARG_REALIZE
+                ARG_REALIZE,
+                ARG_ALL
         };
 
         static const struct option options[] = {
@@ -108,6 +116,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "global",    no_argument,       NULL, ARG_GLOBAL  },
                 { "force",     no_argument,       NULL, ARG_FORCE   },
                 { "realize",   optional_argument, NULL, ARG_REALIZE },
+                { "all",       no_argument,       NULL, ARG_ALL     },
+                { "verbose",   no_argument,       NULL, 'v'         },
                 { NULL,        0,                 NULL, 0           }
         };
 
@@ -117,7 +127,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 1);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+        while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) {
 
                 switch (c) {
 
@@ -164,6 +174,14 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_ALL:
+                        arg_all = true;
+                        break;
+
+                case 'v':
+                        arg_verbose = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -280,6 +298,9 @@ static int daemon_reload(DBusConnection *bus) {
 
         dbus_error_init(&error);
 
+        if (arg_verbose)
+                log_info("Reloading daemon configuration.");
+
         if (!(m = dbus_message_new_method_call(
                               "org.freedesktop.systemd1",
                               "/org/freedesktop/systemd1",
@@ -429,6 +450,9 @@ static int install_info_run(DBusConnection *bus, InstallInfo *i, bool enabled) {
                         }
                 }
 
+                if (arg_verbose)
+                        log_info("Restarting %s.", i->name);
+
                 if (!(m = dbus_message_new_method_call(
                                       "org.freedesktop.systemd1",
                                       "/org/freedesktop/systemd1",
@@ -452,6 +476,9 @@ static int install_info_run(DBusConnection *bus, InstallInfo *i, bool enabled) {
         } else if (arg_action == ACTION_DISABLE ||
                    (arg_action == ACTION_REALIZE && !enabled)) {
 
+                if (arg_verbose)
+                        log_info("Stopping %s.", i->name);
+
                 if (!(m = dbus_message_new_method_call(
                                       "org.freedesktop.systemd1",
                                       "/org/freedesktop/systemd1",
@@ -527,6 +554,184 @@ static int config_parse_also(
         return 0;
 }
 
+static int mark_symlink_for_removal(const char *p) {
+        char *n;
+        int r;
+
+        assert(p);
+        assert(path_is_absolute(p));
+
+        if (!remove_symlinks_to)
+                return 0;
+
+        if (!(n = canonicalize_file_name(p)))
+                if (!(n = strdup(p)))
+                        return -ENOMEM;
+
+        path_kill_slashes(n);
+
+        if ((r = set_put(remove_symlinks_to, n)) < 0) {
+                free(n);
+                return r;
+        }
+
+        return 0;
+}
+
+static int remove_marked_symlinks_fd(int fd, const char *config_path, const char *root, bool *deleted) {
+        int r = 0;
+        DIR *d;
+        struct dirent *de;
+
+        assert(fd >= 0);
+        assert(root);
+        assert(deleted);
+
+        if (!(d = fdopendir(fd)))
+                return -errno;
+
+        rewinddir(d);
+
+        while ((de = readdir(d))) {
+                bool is_dir = false, is_link = false;
+
+                if (ignore_file(de->d_name))
+                        continue;
+
+                if (de->d_type == DT_LNK)
+                        is_link = true;
+                else if (de->d_type == DT_DIR)
+                        is_dir = true;
+                else if (de->d_type == DT_UNKNOWN) {
+                        struct stat st;
+
+                        if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+                                log_error("Failed to stat %s/%s: %m", root, de->d_name);
+
+                                if (r == 0)
+                                        r = -errno;
+                                continue;
+                        }
+
+                        is_link = S_ISLNK(st.st_mode);
+                        is_dir = S_ISDIR(st.st_mode);
+                } else
+                        continue;
+
+                if (is_dir) {
+                        int nfd, q;
+                        char *p;
+
+                        if ((nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW)) < 0) {
+                                log_error("Failed to open %s/%s: %m", root, de->d_name);
+
+                                if (r == 0)
+                                        r = -errno;
+                                continue;
+                        }
+
+                        if (asprintf(&p, "%s/%s", root, de->d_name) < 0) {
+                                log_error("Failed to allocate directory string.");
+                                close_nointr_nofail(nfd);
+                                r = -ENOMEM;
+                                break;
+                        }
+
+                        q = remove_marked_symlinks_fd(nfd, config_path, p, deleted);
+                        free(p);
+                        close_nointr_nofail(nfd);
+
+                        if (r == 0)
+                                q = r;
+
+                } else if (is_link) {
+                        char *p, *dest, *c;
+                        int q;
+
+                        if (asprintf(&p, "%s/%s", root, de->d_name) < 0) {
+                                log_error("Failed to allocate symlink string.");
+                                r = -ENOMEM;
+                                break;
+                        }
+
+                        if ((q = readlink_and_make_absolute(p, &dest)) < 0) {
+                                log_error("Cannot read symlink %s: %s", p, strerror(-q));
+                                free(p);
+
+                                if (r == 0)
+                                        r = q;
+                                continue;
+                        }
+
+                        if ((c = canonicalize_file_name(dest))) {
+                                /* This might fail if the destination
+                                 * is already removed */
+
+                                free(dest);
+                                dest = c;
+                        }
+
+                        path_kill_slashes(dest);
+                        if (set_get(remove_symlinks_to, dest)) {
+
+                                if (arg_verbose)
+                                        log_info("rm '%s'", p);
+
+                                if (unlink(p) < 0) {
+                                        log_error("Cannot unlink symlink %s: %m", p);
+
+                                        if (r == 0)
+                                                r = -errno;
+                                } else {
+                                        rmdir_parents(p, config_path);
+                                        path_kill_slashes(p);
+
+                                        if (!set_get(remove_symlinks_to, p)) {
+
+                                                if ((r = mark_symlink_for_removal(p)) < 0) {
+                                                        if (r == 0)
+                                                                r = q;
+                                                } else
+                                                        *deleted = true;
+                                        }
+                                }
+                        }
+
+                        free(p);
+                        free(dest);
+                }
+        }
+
+        return r;
+}
+
+static int remove_marked_symlinks(const char *config_path) {
+        int fd, r = 0;
+        bool deleted;
+
+        assert(config_path);
+
+        if (set_size(remove_symlinks_to) <= 0)
+                return 0;
+
+        if ((fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW)) < 0)
+                return -errno;
+
+        do {
+                int q;
+                deleted = false;
+
+                if ((q = remove_marked_symlinks_fd(fd, config_path, config_path, &deleted)) < 0) {
+                        if (r == 0)
+                                r = q;
+                }
+        } while (deleted);
+
+        close_nointr_nofail(fd);
+
+        return r;
+}
+
 static int create_symlink(const char *old_path, const char *new_path) {
         int r;
 
@@ -571,6 +776,9 @@ static int create_symlink(const char *old_path, const char *new_path) {
                 free(dest);
                 unlink(new_path);
 
+                if (arg_verbose)
+                        log_info("ln -s '%s' '%s'", old_path, new_path);
+
                 if (symlink(old_path, new_path) >= 0)
                         return 0;
 
@@ -580,6 +788,9 @@ static int create_symlink(const char *old_path, const char *new_path) {
         } else if (arg_action == ACTION_DISABLE) {
                 char *dest;
 
+                if ((r = mark_symlink_for_removal(old_path)) < 0)
+                        return r;
+
                 if ((r = readlink_and_make_absolute(new_path, &dest)) < 0) {
                         if (errno == ENOENT)
                                 return 0;
@@ -599,7 +810,15 @@ static int create_symlink(const char *old_path, const char *new_path) {
                         return 0;
                 }
 
+
                 free(dest);
+
+                if ((r = mark_symlink_for_removal(new_path)) < 0)
+                        return r;
+
+                if (arg_verbose)
+                        log_info("rm '%s'", new_path);
+
                 if (unlink(new_path) >= 0)
                         return 0;
 
@@ -771,6 +990,12 @@ static int install_info_apply(LookupPaths *paths, InstallInfo *i, const char *co
         if ((r = install_info_symlink_wants(i, config_path)) != 0)
                 return r;
 
+        if ((r = mark_symlink_for_removal(filename)) < 0)
+                return r;
+
+        if ((r = remove_marked_symlinks(config_path)) < 0)
+                return r;
+
         return 0;
 }
 
@@ -898,10 +1123,17 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
+        if (arg_all)
+                if (!(remove_symlinks_to = set_new(string_hash_func, string_compare_func))) {
+                        log_error("Failed to allocate symlink sets.");
+                        goto finish;
+                }
+
         for (j = optind; j < argc; j++)
                 if ((r = install_info_add(argv[j])) < 0)
                         goto finish;
 
+
         while ((i = hashmap_first(will_install))) {
                 assert_se(hashmap_move_one(have_installed, will_install, i->name) == 0);
 
@@ -923,6 +1155,8 @@ finish:
         install_info_hashmap_free(will_install);
         install_info_hashmap_free(have_installed);
 
+        set_free_free(remove_symlinks_to);
+
         lookup_paths_free(&paths);
 
         free(config_path);
commit 92abbefbefb0adafb6714c8d8f9d25bc8280a2f7
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sat Jul 17 04:17:30 2010 +0200

    execute: bump up log level of executed processes that failed

diff --git a/fixme b/fixme
index 1057305..b80ea43 100644
--- a/fixme
+++ b/fixme
@@ -55,6 +55,8 @@
 
 * fingerprint.target, wireless.target, gps.target
 
+* fix merging of device units
+
 External:
 
 * default.target must be %ghosted...
diff --git a/src/log.h b/src/log.h
index ed9b8c8..e5ea531 100644
--- a/src/log.h
+++ b/src/log.h
@@ -83,6 +83,8 @@ int log_dump_internal(
         const char *func,
         char *buffer);
 
+#define log_full(level, ...) log_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__)
+
 #define log_debug(...)   log_meta(LOG_DEBUG,   __FILE__, __LINE__, __func__, __VA_ARGS__)
 #define log_info(...)    log_meta(LOG_INFO,    __FILE__, __LINE__, __func__, __VA_ARGS__)
 #define log_notice(...)  log_meta(LOG_NOTICE,  __FILE__, __LINE__, __func__, __VA_ARGS__)
diff --git a/src/mount.c b/src/mount.c
index 5b0bc6b..bee3c9a 100644
--- a/src/mount.c
+++ b/src/mount.c
@@ -962,7 +962,8 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                 m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID;
         }
 
-        log_debug("%s control process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
+        log_full(success ? LOG_DEBUG : LOG_NOTICE,
+                 "%s mount process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
 
         /* Note that mount(8) returning and the kernel sending us a
          * mount table change event might happen out-of-order. If an
diff --git a/src/service.c b/src/service.c
index 1bfab50..a5d1ebd 100644
--- a/src/service.c
+++ b/src/service.c
@@ -2199,7 +2199,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                 success = true;
                 }
 
-                log_debug("%s: main process exited, code=%s, status=%i", u->meta.id, sigchld_code_to_string(code), status);
+                log_full(success ? LOG_DEBUG : LOG_NOTICE,
+                         "%s: main process exited, code=%s, status=%i", u->meta.id, sigchld_code_to_string(code), status);
                 s->failure = s->failure || !success;
 
                 /* The service exited, so the service is officially
@@ -2256,7 +2257,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
 
                 s->control_pid = 0;
 
-                log_debug("%s: control process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
+                log_full(success ? LOG_DEBUG : LOG_NOTICE,
+                         "%s: control process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
                 s->failure = s->failure || !success;
 
                 /* If we are shutting things down anyway we
@@ -2476,7 +2478,7 @@ static void service_notify_message(Unit *u, pid_t pid, char **tags) {
              s->state == SERVICE_RELOAD)) {
 
                 if (parse_pid(e + 8, &pid) < 0)
-                        log_warning("Failed to parse %s", e);
+                        log_warning("Failed to parse notification message %s", e);
                 else {
                         log_debug("%s: got %s", u->meta.id, e);
                         service_set_main_pid(s, pid);
diff --git a/src/socket.c b/src/socket.c
index 257a4e9..4cf21cf 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -1535,7 +1535,8 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                         success = true;
         }
 
-        log_debug("%s control process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
+        log_full(success ? LOG_DEBUG : LOG_NOTICE,
+                 "%s control process exited, code=%s status=%i", u->meta.id, sigchld_code_to_string(code), status);
         s->failure = s->failure || !success;
 
         if (s->control_command && s->control_command->command_next && success) {
commit faf919f1ebebdfc13f769bb6585e64e7ad4b301b
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sat Jul 17 04:09:28 2010 +0200

    job: timeout every job independently of the unit

diff --git a/fixme b/fixme
index 8e769a2..1057305 100644
--- a/fixme
+++ b/fixme
@@ -35,8 +35,6 @@
 
 * systemctl status $PID, systemctl stop $PID!
 
-* timeout waiting for mount devices?
-
 * place /etc/inittab with explaining blurb.
 
 * In command lines, support both "$FOO" and $FOO
diff --git a/src/dbus-unit.h b/src/dbus-unit.h
index e93d658..0b9c62f 100644
--- a/src/dbus-unit.h
+++ b/src/dbus-unit.h
@@ -90,6 +90,7 @@
         "  <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \
         "  <property name=\"ControlGroups\" type=\"as\" access=\"read\"/>\n" \
         "  <property name=\"NeedDaemonReload\" type=\"b\" access=\"read\"/>\n" \
+        "  <property name=\"JobTimeoutUSec\" type=\"t\" access=\"read\"/>\n" \
         " </interface>\n"
 
 #define BUS_UNIT_PROPERTIES \
@@ -125,7 +126,8 @@
         { "org.freedesktop.systemd1.Unit", "DefaultDependencies",  bus_property_append_bool,       "b",    &u->meta.default_dependencies     }, \
         { "org.freedesktop.systemd1.Unit", "DefaultControlGroup",  bus_unit_append_default_cgroup, "s",    u                                 }, \
         { "org.freedesktop.systemd1.Unit", "ControlGroups",        bus_unit_append_cgroups,        "as",   u                                 }, \
-        { "org.freedesktop.systemd1.Unit", "NeedDaemonReload",     bus_unit_append_need_daemon_reload, "b", u                                }
+        { "org.freedesktop.systemd1.Unit", "NeedDaemonReload",     bus_unit_append_need_daemon_reload, "b", u                                }, \
+        { "org.freedesktop.systemd1.Unit", "JobTimeoutUSec",       bus_property_append_usec,       "t",    &u->meta.job_timeout              }
 
 int bus_unit_append_names(Manager *m, DBusMessageIter *i, const char *property, void *data);
 int bus_unit_append_dependencies(Manager *m, DBusMessageIter *i, const char *property, void *data);
diff --git a/src/device.c b/src/device.c
index dc626d8..39ab291 100644
--- a/src/device.c
+++ b/src/device.c
@@ -35,6 +35,15 @@ static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = {
         [DEVICE_PLUGGED] = UNIT_ACTIVE
 };
 
+static void device_init(Unit *u) {
+        Device *d = DEVICE(u);
+
+        assert(d);
+        assert(d->meta.load_state == UNIT_STUB);
+
+        d->meta.job_timeout = DEFAULT_TIMEOUT_USEC;
+}
+
 static void device_done(Unit *u) {
         Device *d = DEVICE(u);
 
@@ -456,6 +465,8 @@ const UnitVTable device_vtable = {
         .no_snapshots = true,
         .no_isolate = true,
 
+        .init = device_init,
+
         .load = unit_load_fragment_and_dropin_optional,
         .done = device_done,
         .coldplug = device_coldplug,
diff --git a/src/job.c b/src/job.c
index 8cc9d74..2b422b4 100644
--- a/src/job.c
+++ b/src/job.c
@@ -21,6 +21,8 @@
 
 #include <assert.h>
 #include <errno.h>
+#include <sys/timerfd.h>
+#include <sys/epoll.h>
 
 #include "set.h"
 #include "unit.h"
@@ -46,6 +48,8 @@ Job* job_new(Manager *m, JobType type, Unit *unit) {
         j->type = type;
         j->unit = unit;
 
+        j->timer_watch.type = WATCH_INVALID;
+
         /* We don't link it here, that's what job_dependency() is for */
 
         return j;
@@ -76,6 +80,15 @@ void job_free(Job *j) {
         if (j->in_dbus_queue)
                 LIST_REMOVE(Job, dbus_queue, j->manager->dbus_job_queue, j);
 
+        if (j->timer_watch.type != WATCH_INVALID) {
+                assert(j->timer_watch.type == WATCH_JOB_TIMER);
+                assert(j->timer_watch.data.job == j);
+                assert(j->timer_watch.fd >= 0);
+
+                assert_se(epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_DEL, j->timer_watch.fd, NULL) >= 0);
+                close_nointr_nofail(j->timer_watch.fd);
+        }
+
         free(j->bus_client);
         free(j);
 }
@@ -472,8 +485,6 @@ int job_finish_and_invalidate(Job *j, bool success) {
 
                 j->state = JOB_WAITING;
                 j->type = JOB_START;
-
-                job_add_to_run_queue(j);
                 return 0;
         }
 
@@ -534,6 +545,53 @@ int job_finish_and_invalidate(Job *j, bool success) {
         return 0;
 }
 
+int job_start_timer(Job *j) {
+        struct itimerspec its;
+        struct epoll_event ev;
+        int fd, r;
+        assert(j);
+
+        if (j->unit->meta.job_timeout <= 0 ||
+            j->timer_watch.type == WATCH_JOB_TIMER)
+                return 0;
+
+        assert(j->timer_watch.type == WATCH_INVALID);
+
+        if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        zero(its);
+        timespec_store(&its.it_value, j->unit->meta.job_timeout);
+
+        if (timerfd_settime(fd, 0, &its, NULL) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        zero(ev);
+        ev.data.ptr = &j->timer_watch;
+        ev.events = EPOLLIN;
+
+        if (epoll_ctl(j->manager->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        j->timer_watch.type = WATCH_JOB_TIMER;
+        j->timer_watch.fd = fd;
+        j->timer_watch.data.job = j;
+
+        return 0;
+
+fail:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        return r;
+}
+
 void job_add_to_run_queue(Job *j) {
         assert(j);
         assert(j->installed);
@@ -571,6 +629,14 @@ char *job_dbus_path(Job *j) {
         return p;
 }
 
+void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w) {
+        assert(j);
+        assert(w == &j->timer_watch);
+
+        log_warning("Job %s/%s timed out.", j->unit->meta.id, job_type_to_string(j->type));
+        job_finish_and_invalidate(j, false);
+}
+
 static const char* const job_state_table[_JOB_STATE_MAX] = {
         [JOB_WAITING] = "waiting",
         [JOB_RUNNING] = "running"
diff --git a/src/job.h b/src/job.h
index 9c685f1..41d697e 100644
--- a/src/job.h
+++ b/src/job.h
@@ -102,6 +102,8 @@ struct Job {
         JobType type;
         JobState state;
 
+        Watch timer_watch;
+
         /* Note that this bus object is not ref counted here. */
         DBusConnection *bus;
         char *bus_client;
@@ -138,9 +140,14 @@ bool job_is_runnable(Job *j);
 void job_add_to_run_queue(Job *j);
 void job_add_to_dbus_queue(Job *j);
 
+int job_start_timer(Job *j);
+void job_timer_event(Job *j, uint64_t n_elapsed, Watch *w);
+
 int job_run_and_invalidate(Job *j);
 int job_finish_and_invalidate(Job *j, bool success);
 
+char *job_dbus_path(Job *j);
+
 const char* job_type_to_string(JobType t);
 JobType job_type_from_string(const char *s);
 
@@ -150,6 +157,4 @@ JobState job_state_from_string(const char *s);
 const char* job_mode_to_string(JobMode t);
 JobMode job_mode_from_string(const char *s);
 
-char *job_dbus_path(Job *j);
-
 #endif
diff --git a/src/load-fragment.c b/src/load-fragment.c
index a2974cb..5000810 100644
--- a/src/load-fragment.c
+++ b/src/load-fragment.c
@@ -1565,6 +1565,7 @@ static int load_from_path(Unit *u, const char *path) {
                 { "OnlyByDependency",       config_parse_bool,            &u->meta.only_by_dependency,                     "Unit"    },
                 { "DefaultDependencies",    config_parse_bool,            &u->meta.default_dependencies,                   "Unit"    },
                 { "IgnoreDependencyFailure",config_parse_bool,            &u->meta.ignore_dependency_failure,              "Unit"    },
+                { "JobTimeoutSec",          config_parse_usec,            &u->meta.job_timeout,                            "Unit"    },
 
                 { "PIDFile",                config_parse_path,            &u->service.pid_file,                            "Service" },
                 { "ExecStartPre",           config_parse_exec,            u->service.exec_command+SERVICE_EXEC_START_PRE,  "Service" },
diff --git a/src/manager.c b/src/manager.c
index 5884835..7b2586f 100644
--- a/src/manager.c
+++ b/src/manager.c
@@ -1124,6 +1124,7 @@ static int transaction_apply(Manager *m) {
 
                 job_add_to_run_queue(j);
                 job_add_to_dbus_queue(j);
+                job_start_timer(j);
         }
 
         /* As last step, kill all remaining job dependencies. */
@@ -2022,7 +2023,8 @@ static int process_event(Manager *m, struct epoll_event *ev) {
                 UNIT_VTABLE(w->data.unit)->fd_event(w->data.unit, w->fd, ev->events, w);
                 break;
 
-        case WATCH_TIMER: {
+        case WATCH_UNIT_TIMER:
+        case WATCH_JOB_TIMER: {
                 uint64_t v;
                 ssize_t k;
 
@@ -2035,7 +2037,10 @@ static int process_event(Manager *m, struct epoll_event *ev) {
                         return k < 0 ? -errno : -EIO;
                 }
 
-                UNIT_VTABLE(w->data.unit)->timer_event(w->data.unit, v, w);
+                if (w->type == WATCH_UNIT_TIMER)
+                        UNIT_VTABLE(w->data.unit)->timer_event(w->data.unit, v, w);
+                else
+                        job_timer_event(w->data.job, v, w);
                 break;
         }
 
diff --git a/src/manager.h b/src/manager.h
index 32fbacc..c492ffa 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -57,7 +57,8 @@ enum WatchType {
         WATCH_SIGNAL,
         WATCH_NOTIFY,
         WATCH_FD,
-        WATCH_TIMER,
+        WATCH_UNIT_TIMER,
+        WATCH_JOB_TIMER,
         WATCH_MOUNT,
         WATCH_UDEV,
         WATCH_DBUS_WATCH,
@@ -69,6 +70,7 @@ struct Watch {
         WatchType type;
         union {
                 union Unit *unit;
+                struct Job *job;
                 DBusWatch *bus_watch;
                 DBusTimeout *bus_timeout;
         } data;
diff --git a/src/unit.c b/src/unit.c
index 44dc811..348d139 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -587,7 +587,8 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                 timestamp1[FORMAT_TIMESTAMP_MAX],
                 timestamp2[FORMAT_TIMESTAMP_MAX],
                 timestamp3[FORMAT_TIMESTAMP_MAX],
-                timestamp4[FORMAT_TIMESTAMP_MAX];
+                timestamp4[FORMAT_TIMESTAMP_MAX],
+                timespan[FORMAT_TIMESPAN_MAX];
 
         assert(u);
         assert(u->meta.type >= 0);
@@ -627,6 +628,9 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
         if (u->meta.fragment_path)
                 fprintf(f, "%s\tFragment Path: %s\n", prefix, u->meta.fragment_path);
 
+        if (u->meta.job_timeout > 0)
+                fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->meta.job_timeout));
+
         for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) {
                 Unit *other;
 
@@ -1003,7 +1007,6 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
                          * failed previously due to EAGAIN. */
                         job_add_to_run_queue(u->meta.job);
 
-
                 /* Let's check whether this state change constitutes a
                  * finished job, or maybe cotradicts a running job and
                  * hence needs to invalidate jobs. */
@@ -1189,18 +1192,23 @@ int unit_watch_timer(Unit *u, usec_t delay, Watch *w) {
 
         assert(u);
         assert(w);
-        assert(w->type == WATCH_INVALID || (w->type == WATCH_TIMER && w->data.unit == u));
+        assert(w->type == WATCH_INVALID || (w->type == WATCH_UNIT_TIMER && w->data.unit == u));
 
         /* This will try to reuse the old timer if there is one */
 
-        if (w->type == WATCH_TIMER) {
+        if (w->type == WATCH_UNIT_TIMER) {
+                assert(w->data.unit == u);
+                assert(w->fd >= 0);
+
                 ours = false;
                 fd = w->fd;
-        } else {
+        } else if (w->type == WATCH_INVALID) {
+
                 ours = true;
                 if ((fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC)) < 0)
                         return -errno;
-        }
+        } else
+                assert_not_reached("Invalid watch type");
 
         zero(its);
 
@@ -1231,8 +1239,8 @@ int unit_watch_timer(Unit *u, usec_t delay, Watch *w) {
                         goto fail;
         }
 
+        w->type = WATCH_UNIT_TIMER;
         w->fd = fd;
-        w->type = WATCH_TIMER;
         w->data.unit = u;
 
         return 0;
@@ -1251,7 +1259,9 @@ void unit_unwatch_timer(Unit *u, Watch *w) {
         if (w->type == WATCH_INVALID)
                 return;
 
-        assert(w->type == WATCH_TIMER && w->data.unit == u);
+        assert(w->type == WATCH_UNIT_TIMER);
+        assert(w->data.unit == u);
+        assert(w->fd >= 0);
 
         assert_se(epoll_ctl(u->meta.manager->epoll_fd, EPOLL_CTL_DEL, w->fd, NULL) >= 0);
         close_nointr_nofail(w->fd);
diff --git a/src/unit.h b/src/unit.h
index 55fe0fa..cfad3ca 100644
--- a/src/unit.h
+++ b/src/unit.h
@@ -143,6 +143,7 @@ struct Meta {
         Set *dependencies[_UNIT_DEPENDENCY_MAX];
 
         char *description;
+
         char *fragment_path; /* if loaded from a config file this is the primary path to it */
         usec_t fragment_mtime;
 
@@ -150,6 +151,8 @@ struct Meta {
          * the job for it */
         Job *job;
 
+        usec_t job_timeout;
+
         dual_timestamp inactive_exit_timestamp;
         dual_timestamp active_enter_timestamp;
         dual_timestamp active_exit_timestamp;
commit 064f51fa29c1dfd7c0704feb969eb59119e57854
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sat Jul 17 04:07:49 2010 +0200

    unit: consider only_by_dependency setting when clients ask whether a unit is startable

diff --git a/fixme b/fixme
index 048ea21..8e769a2 100644
--- a/fixme
+++ b/fixme
@@ -55,7 +55,7 @@
 
 * maintenance muss dokumentiert werden
 
-* fingerprint.target, smartcard.target, wireless.target, gps.target
+* fingerprint.target, wireless.target, gps.target
 
 External:
 
diff --git a/src/dbus-unit.c b/src/dbus-unit.c
index 66b7ae8..ce34130 100644
--- a/src/dbus-unit.c
+++ b/src/dbus-unit.c
@@ -128,7 +128,8 @@ int bus_unit_append_can_start(Manager *m, DBusMessageIter *i, const char *proper
         assert(property);
         assert(u);
 
-        b = unit_can_start(u);
+        b = unit_can_start(u) &&
+                !u->meta.only_by_dependency;
 
         if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
                 return -ENOMEM;
commit d06428248aa80f72862b86297335ee01c31d918b
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sat Jul 17 00:59:03 2010 +0200

    systemctl: extend list-units output a little

diff --git a/src/systemctl.c b/src/systemctl.c
index 3806786..06b4cae 100644
--- a/src/systemctl.c
+++ b/src/systemctl.c
@@ -188,7 +188,8 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
 
         dbus_message_iter_recurse(&iter, &sub);
 
-        printf("%-45s %-6s %-12s %-12s %-15s %s\n", "UNIT", "LOAD", "ACTIVE", "SUB", "JOB", "DESCRIPTION");
+        if (isatty(STDOUT_FILENO))
+                printf("%-45s %-6s %-12s %-12s %-15s %s\n", "UNIT", "LOAD", "ACTIVE", "SUB", "JOB", "DESCRIPTION");
 
         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
                 const char *id, *description, *load_state, *active_state, *sub_state, *unit_path, *job_type, *job_path, *dot;
@@ -249,15 +250,18 @@ static int list_units(DBusConnection *bus, char **args, unsigned n) {
                 dbus_message_iter_next(&sub);
         }
 
-        printf("\nLOAD   = Load State, reflects whether the unit configuration was properly loaded.\n"
-               "ACTIVE = Active State, the high-level unit activation state, i.e. generalization of the substate.\n"
-               "SUB    = Substate, the low-level unit activation state, possible values depend on unit type.\n"
-               "JOB    = Job, shows scheduled jobs for the unit.\n");
+        if (isatty(STDOUT_FILENO)) {
 
-        if (arg_all)
-                printf("\n%u units listed.\n", k);
-        else
-                printf("\n%u units listed. Pass --all to see inactive units, too.\n", k);
+                printf("\nLOAD   = Load State, reflects whether the unit configuration was properly loaded.\n"
+                       "ACTIVE = Active State, the high-level unit activation state, i.e. generalization of the substate.\n"
+                       "SUB    = Substate, the low-level unit activation state, possible values depend on unit type.\n"
+                       "JOB    = Job, shows pending jobs for the unit.\n");
+
+                if (arg_all)
+                        printf("\n%u units listed.\n", k);
+                else
+                        printf("\n%u units listed. Pass --all to see inactive units, too.\n", k);
+        }
 
         r = 0;
 
commit 5de9682cd647f07f043254fde70e62e1aa837476
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sat Jul 17 00:58:47 2010 +0200

    unit: introduce OnFailure dependencies to activate units on failure of other units, as a way to implement an automatic rescue shell

diff --git a/src/dbus-unit.h b/src/dbus-unit.h
index d4af5a8..e93d658 100644
--- a/src/dbus-unit.h
+++ b/src/dbus-unit.h
@@ -70,6 +70,7 @@
         "  <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=\"OnFailure\" 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" \
@@ -105,6 +106,7 @@
         { "org.freedesktop.systemd1.Unit", "Conflicts",            bus_unit_append_dependencies,   "as",   u->meta.dependencies[UNIT_CONFLICTS] }, \
         { "org.freedesktop.systemd1.Unit", "Before",               bus_unit_append_dependencies,   "as",   u->meta.dependencies[UNIT_BEFORE] }, \
         { "org.freedesktop.systemd1.Unit", "After",                bus_unit_append_dependencies,   "as",   u->meta.dependencies[UNIT_AFTER]  }, \
+        { "org.freedesktop.systemd1.Unit", "OnFailure",            bus_unit_append_dependencies,   "as",   u->meta.dependencies[UNIT_ON_FAILURE] }, \
         { "org.freedesktop.systemd1.Unit", "Description",          bus_unit_append_description,    "s",    u                                 }, \
         { "org.freedesktop.systemd1.Unit", "LoadState",            bus_unit_append_load_state,     "s",    &u->meta.load_state               }, \
         { "org.freedesktop.systemd1.Unit", "ActiveState",          bus_unit_append_active_state,   "s",    u                                 }, \
diff --git a/src/unit.c b/src/unit.c
index e46182a..44dc811 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -987,9 +987,6 @@ 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;
 
-        if (ns != os && ns == UNIT_MAINTENANCE)
-                log_notice("Unit %s entered maintenance state.", u->meta.id);
-
         if (UNIT_IS_INACTIVE_OR_MAINTENANCE(ns))
                 cgroup_bonding_trim_list(u->meta.cgroup_bondings, true);
 
@@ -1072,6 +1069,16 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns) {
                         retroactively_stop_dependencies(u);
         }
 
+        if (ns != os && ns == UNIT_MAINTENANCE) {
+                Iterator i;
+                Unit *other;
+
+                SET_FOREACH(other, u->meta.dependencies[UNIT_ON_FAILURE], i)
+                        manager_add_job(u->meta.manager, JOB_START, other, JOB_REPLACE, true, NULL, NULL);
+
+                log_notice("Unit %s entered maintenance state.", u->meta.id);
+        }
+
         /* Some names are special */
         if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) {
                 if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) {
@@ -1294,6 +1301,7 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen
                 [UNIT_CONFLICTS] = UNIT_CONFLICTS,
                 [UNIT_BEFORE] = UNIT_AFTER,
                 [UNIT_AFTER] = UNIT_BEFORE,
+                [UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID,
                 [UNIT_REFERENCES] = UNIT_REFERENCED_BY,
                 [UNIT_REFERENCED_BY] = UNIT_REFERENCES
         };
@@ -1301,7 +1309,6 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen
 
         assert(u);
         assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX);
-        assert(inverse_table[d] != _UNIT_DEPENDENCY_INVALID);
         assert(other);
 
         /* We won't allow dependencies on ourselves. We will not
@@ -1317,10 +1324,13 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen
                     return -EINVAL;
         }
 
-        if ((r = set_ensure_allocated(&u->meta.dependencies[d], trivial_hash_func, trivial_compare_func)) < 0 ||
-            (r = set_ensure_allocated(&other->meta.dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0)
+        if ((r = set_ensure_allocated(&u->meta.dependencies[d], trivial_hash_func, trivial_compare_func)) < 0)
                 return r;
 
+        if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID)
+                if ((r = set_ensure_allocated(&other->meta.dependencies[inverse_table[d]], trivial_hash_func, trivial_compare_func)) < 0)
+                        return r;
+
         if (add_reference)
                 if ((r = set_ensure_allocated(&u->meta.dependencies[UNIT_REFERENCES], trivial_hash_func, trivial_compare_func)) < 0 ||
                     (r = set_ensure_allocated(&other->meta.dependencies[UNIT_REFERENCED_BY], trivial_hash_func, trivial_compare_func)) < 0)
@@ -1329,10 +1339,11 @@ int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_referen
         if ((q = set_put(u->meta.dependencies[d], other)) < 0)
                 return q;
 
-        if ((v = set_put(other->meta.dependencies[inverse_table[d]], u)) < 0) {
-                r = v;
-                goto fail;
-        }
+        if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID)
+                if ((v = set_put(other->meta.dependencies[inverse_table[d]], u)) < 0) {
+                        r = v;
+                        goto fail;
+                }
 
         if (add_reference) {
                 if ((w = set_put(u->meta.dependencies[UNIT_REFERENCES], other)) < 0) {
@@ -2097,7 +2108,8 @@ static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
         [UNIT_BEFORE] = "Before",
         [UNIT_AFTER] = "After",
         [UNIT_REFERENCES] = "References",
-        [UNIT_REFERENCED_BY] = "ReferencedBy"
+        [UNIT_REFERENCED_BY] = "ReferencedBy",
+        [UNIT_ON_FAILURE] = "OnFailure"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);
diff --git a/src/unit.h b/src/unit.h
index d3a0079..55fe0fa 100644
--- a/src/unit.h
+++ b/src/unit.h
@@ -114,6 +114,9 @@ enum UnitDependency {
         UNIT_BEFORE,                  /* inverse of 'before' is 'after' and vice versa */
         UNIT_AFTER,
 
+        /* On Failure */
+        UNIT_ON_FAILURE,
+
         /* Reference information for GC logic */
         UNIT_REFERENCES,              /* Inverse of 'references' is 'referenced_by' */
         UNIT_REFERENCED_BY,
commit 45fb0699c45d2e042e04a53e3ea00501e3f65f59
Author: Lennart Poettering <lennart at poettering.net>
Date:   Sat Jul 17 00:57:51 2010 +0200

    systemctl: warn when operating on service files that changed on disk but haven't been reloaded

diff --git a/fixme b/fixme
index c6978fb..048ea21 100644
--- a/fixme
+++ b/fixme
@@ -37,22 +37,12 @@
 
 * timeout waiting for mount devices?
 
-* default logic for serial getty, ck logging, ssh readahead
-
 * place /etc/inittab with explaining blurb.
 
-* OnFailure=foo.unit
-
-* default.target must be %ghosted...
-
 * In command lines, support both "$FOO" and $FOO
 
 * systemd-install disable should recursively kill all symlinks
 
-* in %post create all symlinks manually and use inittab data
-
-* check mtimes of dirs and unit files in systemctl
-
 * /etc must always take precedence even if we follow symlinks!
 
 * /lib/init/rw
@@ -69,6 +59,12 @@
 
 External:
 
+* default.target must be %ghosted...
+
+* in %post create all symlinks manually and use inittab data
+
+* default logic for serial getty, ck logging, ssh readahead
+
 * patch /etc/init.d/functions with:
 
   if [ $PPID -ne 1 && mountpoint /cgroup/systemd ] ; then echo "You suck!" ; fi
diff --git a/src/dbus-unit.c b/src/dbus-unit.c
index da7d1bd..66b7ae8 100644
--- a/src/dbus-unit.c
+++ b/src/dbus-unit.c
@@ -254,6 +254,23 @@ int bus_unit_append_cgroups(Manager *m, DBusMessageIter *i, const char *property
         return 0;
 }
 
+int bus_unit_append_need_daemon_reload(Manager *m, DBusMessageIter *i, const char *property, void *data) {
+        Unit *u = data;
+        dbus_bool_t b;
+
+        assert(m);
+        assert(i);
+        assert(property);
+        assert(u);
+
+        b = unit_need_daemon_reload(u);
+
+        if (!dbus_message_iter_append_basic(i, DBUS_TYPE_BOOLEAN, &b))
+                return -ENOMEM;
+
+        return 0;
+}
+
 static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *connection, DBusMessage *message) {
         DBusMessage *reply = NULL;
         Manager *m = u->meta.manager;
diff --git a/src/dbus-unit.h b/src/dbus-unit.h
index 1d11af3..d4af5a8 100644
--- a/src/dbus-unit.h
+++ b/src/dbus-unit.h
@@ -88,6 +88,7 @@
         "  <property name=\"DefaultDependencies\" type=\"b\" access=\"read\"/>\n" \
         "  <property name=\"DefaultControlGroup\" type=\"s\" access=\"read\"/>\n" \
         "  <property name=\"ControlGroups\" type=\"as\" access=\"read\"/>\n" \
+        "  <property name=\"NeedDaemonReload\" type=\"b\" access=\"read\"/>\n" \
         " </interface>\n"
 
 #define BUS_UNIT_PROPERTIES \
@@ -121,7 +122,8 @@
         { "org.freedesktop.systemd1.Unit", "OnlyByDependency",     bus_property_append_bool,       "b",    &u->meta.only_by_dependency       }, \
         { "org.freedesktop.systemd1.Unit", "DefaultDependencies",  bus_property_append_bool,       "b",    &u->meta.default_dependencies     }, \
         { "org.freedesktop.systemd1.Unit", "DefaultControlGroup",  bus_unit_append_default_cgroup, "s",    u                                 }, \
-        { "org.freedesktop.systemd1.Unit", "ControlGroups",        bus_unit_append_cgroups,        "as",   u                                 }
+        { "org.freedesktop.systemd1.Unit", "ControlGroups",        bus_unit_append_cgroups,        "as",   u                                 }, \
+        { "org.freedesktop.systemd1.Unit", "NeedDaemonReload",     bus_unit_append_need_daemon_reload, "b", u                                }
 
 int bus_unit_append_names(Manager *m, DBusMessageIter *i, const char *property, void *data);
 int bus_unit_append_dependencies(Manager *m, DBusMessageIter *i, const char *property, void *data);
@@ -134,6 +136,7 @@ int bus_unit_append_can_reload(Manager *m, DBusMessageIter *i, const char *prope
 int bus_unit_append_job(Manager *m, DBusMessageIter *i, const char *property, void *data);
 int bus_unit_append_default_cgroup(Manager *m, DBusMessageIter *i, const char *property, void *data);
 int bus_unit_append_cgroups(Manager *m, DBusMessageIter *i, const char *property, void *data);
+int bus_unit_append_need_daemon_reload(Manager *m, DBusMessageIter *i, const char *property, void *data);
 
 void bus_unit_send_change_signal(Unit *u);
 void bus_unit_send_removed_signal(Unit *u);
diff --git a/src/load-fragment.c b/src/load-fragment.c
index 8e4ec74..a2974cb 100644
--- a/src/load-fragment.c
+++ b/src/load-fragment.c
@@ -29,6 +29,7 @@
 #include <sys/prctl.h>
 #include <sys/mount.h>
 #include <linux/fs.h>
+#include <sys/stat.h>
 
 #include "unit.h"
 #include "strv.h"
@@ -1558,6 +1559,7 @@ static int load_from_path(Unit *u, const char *path) {
                 { "Conflicts",              config_parse_deps,            UINT_TO_PTR(UNIT_CONFLICTS),                     "Unit"    },
                 { "Before",                 config_parse_deps,            UINT_TO_PTR(UNIT_BEFORE),                        "Unit"    },
                 { "After",                  config_parse_deps,            UINT_TO_PTR(UNIT_AFTER),                         "Unit"    },
+                { "OnFailure",              config_parse_deps,            UINT_TO_PTR(UNIT_ON_FAILURE),                    "Unit"    },
                 { "RecursiveStop",          config_parse_bool,            &u->meta.recursive_stop,                         "Unit"    },
                 { "StopWhenUnneeded",       config_parse_bool,            &u->meta.stop_when_unneeded,                     "Unit"    },
                 { "OnlyByDependency",       config_parse_bool,            &u->meta.only_by_dependency,                     "Unit"    },
@@ -1653,6 +1655,7 @@ static int load_from_path(Unit *u, const char *path) {
         FILE *f = NULL;
         char *filename = NULL, *id = NULL;
         Unit *merged;
+        struct stat st;
 
         if (!u) {
                 /* Dirty dirty hack. */
@@ -1740,6 +1743,17 @@ static int load_from_path(Unit *u, const char *path) {
                 goto finish;
         }
 
+        zero(st);
+        if (fstat(fileno(f), &st) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        if (!S_ISREG(st.st_mode)) {
+                r = -ENOENT;
+                goto finish;
+        }
+
         /* Now, parse the file contents */
         if ((r = config_parse(filename, f, sections, items, false, u)) < 0)
                 goto finish;
@@ -1748,6 +1762,8 @@ static int load_from_path(Unit *u, const char *path) {
         u->meta.fragment_path = filename;
         filename = NULL;
 
+        u->meta.fragment_mtime = timespec_load(&st.st_mtim);
+
         u->meta.load_state = UNIT_LOADED;
         r = 0;
 
diff --git a/src/systemctl.c b/src/systemctl.c
index 1ded936..3806786 100644
--- a/src/systemctl.c
+++ b/src/systemctl.c
@@ -742,6 +742,77 @@ finish:
         return r;
 }
 
+static bool unit_need_daemon_reload(DBusConnection *bus, const char *unit) {
+        DBusMessage *m, *reply;
+        dbus_bool_t b = FALSE;
+        DBusMessageIter iter, sub;
+        const char
+                *interface = "org.freedesktop.systemd1.Unit",
+                *property = "NeedDaemonReload",
+                *path;
+
+        /* We ignore all errors here, since this is used to show a warning only */
+
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              "/org/freedesktop/systemd1",
+                              "org.freedesktop.systemd1.Manager",
+                              "GetUnit")))
+                goto finish;
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &unit,
+                                      DBUS_TYPE_INVALID))
+                goto finish;
+
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, NULL)))
+                goto finish;
+
+        if (!dbus_message_get_args(reply, NULL,
+                                   DBUS_TYPE_OBJECT_PATH, &path,
+                                   DBUS_TYPE_INVALID))
+                goto finish;
+
+        dbus_message_unref(m);
+        if (!(m = dbus_message_new_method_call(
+                              "org.freedesktop.systemd1",
+                              path,
+                              "org.freedesktop.DBus.Properties",
+                              "Get")))
+                goto finish;
+
+        if (!dbus_message_append_args(m,
+                                      DBUS_TYPE_STRING, &interface,
+                                      DBUS_TYPE_STRING, &property,
+                                      DBUS_TYPE_INVALID)) {
+                goto finish;
+        }
+
+        dbus_message_unref(reply);
+        if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, NULL)))
+                goto finish;
+
+        if (!dbus_message_iter_init(reply, &iter) ||
+            dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+                goto finish;
+
+        dbus_message_iter_recurse(&iter, &sub);
+
+        if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_BOOLEAN)
+                goto finish;
+
+        dbus_message_iter_get_basic(&sub, &b);
+
+finish:
+        if (m)
+                dbus_message_unref(m);
+
+        if (reply)
+                dbus_message_unref(reply);
+
+        return b;
+}
+
 typedef struct WaitData {
         Set *set;
         bool failed;
@@ -860,6 +931,7 @@ static int start_unit_one(
 
         DBusMessage *m = NULL, *reply = NULL;
         DBusError error;
+        const char *path;
         int r;
 
         assert(bus);
@@ -903,18 +975,21 @@ static int start_unit_one(
                 goto finish;
         }
 
+        if (!dbus_message_get_args(reply, &error,
+                                   DBUS_TYPE_OBJECT_PATH, &path,
+                                   DBUS_TYPE_INVALID)) {
+                log_error("Failed to parse reply: %s", error.message);
+                r = -EIO;
+                goto finish;
+        }
+
+        if (unit_need_daemon_reload(bus, name))
+                log_warning("Unit file of created job changed on disk, 'systemctl %s daemon-reload' recommended.",
+                            arg_session ? "--session" : "--system");
+
         if (!arg_no_block) {
-                const char *path;
                 char *p;
 
-                if (!dbus_message_get_args(reply, &error,
-                                           DBUS_TYPE_OBJECT_PATH, &path,
-                                           DBUS_TYPE_INVALID)) {
-                        log_error("Failed to parse reply: %s", error.message);
-                        r = -EIO;
-                        goto finish;
-                }
-
                 if (!(p = strdup(path))) {
                         log_error("Failed to duplicate path.");
                         r = -ENOMEM;
@@ -1287,6 +1362,8 @@ typedef struct UnitStatusInfo {
         const char *fragment_path;
         const char *default_control_group;
 
+        bool need_daemon_reload;
+
         /* Service */
         pid_t main_pid;
         pid_t control_pid;
@@ -1449,6 +1526,10 @@ static void print_status_info(UnitStatusInfo *i) {
 
                 show_cgroup_by_path(i->default_control_group, "\t\t  ", c);
         }
+
+        if (i->need_daemon_reload)
+                printf("\n" ANSI_HIGHLIGHT_ON "Warning:" ANSI_HIGHLIGHT_OFF " Unit file changed on disk, 'systemctl %s daemon-reload' recommended.\n",
+                       arg_session ? "--session" : "--system");
 }
 
 static int status_property(const char *name, DBusMessageIter *iter, UnitStatusInfo *i) {
@@ -1495,6 +1576,8 @@ static int status_property(const char *name, DBusMessageIter *iter, UnitStatusIn
 
                 if (streq(name, "Accept"))
                         i->accept = b;
+                else if (streq(name, "NeedDaemonReload"))
+                        i->need_daemon_reload = b;
 
                 break;
         }
diff --git a/src/unit.c b/src/unit.c
index f8be8b2..e46182a 100644
--- a/src/unit.c
+++ b/src/unit.c
@@ -27,6 +27,7 @@
 #include <sys/poll.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <sys/stat.h>
 
 #include "set.h"
 #include "unit.h"
@@ -606,7 +607,8 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                 "%s\tActive Enter Timestamp: %s\n"
                 "%s\tActive Exit Timestamp: %s\n"
                 "%s\tInactive Enter Timestamp: %s\n"
-                "%s\tGC Check Good: %s\n",
+                "%s\tGC Check Good: %s\n"
+                "%s\tNeed Daemon Reload: %s\n",
                 prefix, u->meta.id,
                 prefix, unit_description(u),
                 prefix, strna(u->meta.instance),
@@ -616,7 +618,8 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) {
                 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(unit_check_gc(u)),
+                prefix, yes_no(unit_need_daemon_reload(u)));
 
         SET_FOREACH(t, u->meta.names, i)
                 fprintf(f, "%s\tName: %s\n", prefix, t);
@@ -2029,6 +2032,24 @@ void unit_status_printf(Unit *u, const char *format, ...) {
         va_end(ap);
 }
 
+bool unit_need_daemon_reload(Unit *u) {
+        struct stat st;
+
+        assert(u);
+
+        if (!u->meta.fragment_path)
+                return false;
+
+        zero(st);
+        if (stat(u->meta.fragment_path, &st) < 0)
+                /* What, cannot access this anymore? */
+                return true;
+
+        return
+                u->meta.fragment_mtime &&
+                timespec_load(&st.st_mtim) != u->meta.fragment_mtime;
+}
+
 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
         [UNIT_SERVICE] = "service",
         [UNIT_TIMER] = "timer",
diff --git a/src/unit.h b/src/unit.h
index 5d68583..d3a0079 100644
--- a/src/unit.h
+++ b/src/unit.h
@@ -141,6 +141,7 @@ struct Meta {
 
         char *description;
         char *fragment_path; /* if loaded from a config file this is the primary path to it */
+        usec_t fragment_mtime;
 
         /* If there is something to do with this unit, then this is
          * the job for it */
@@ -458,6 +459,8 @@ int unit_coldplug(Unit *u);
 
 void unit_status_printf(Unit *u, const char *format, ...);
 
+bool unit_need_daemon_reload(Unit *u);
+
 const char *unit_type_to_string(UnitType i);
 UnitType unit_type_from_string(const char *s);
 
commit ceda54d93c9c16f24737412cfbc719e01c474ef6
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Jul 16 21:38:56 2010 +0200

    units: wire smartcard.target into Makefile

diff --git a/Makefile.am b/Makefile.am
index 9d52772..94ae6af 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -147,7 +147,8 @@ dist_systemunit_DATA = \
 	units/var-lock.mount \
 	units/var-run.mount \
 	units/printer.target \
-	units/bluetooth.target
+	units/bluetooth.target \
+	units/smartcard.target
 
 nodist_systemunit_DATA = \
 	units/sysinit.target \


More information about the systemd-commits mailing list