chiark / gitweb /
notify: add minimal readiness/status protocol for spawned daemons
authorLennart Poettering <lennart@poettering.net>
Wed, 16 Jun 2010 03:10:31 +0000 (05:10 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 16 Jun 2010 03:10:31 +0000 (05:10 +0200)
18 files changed:
.gitignore
Makefile.am
fixme
src/cgroup.c
src/cgroup.h
src/dbus-service.c
src/initctl.c
src/logger.c
src/manager.c
src/manager.h
src/mount.c
src/sd-daemon.c
src/sd-daemon.h
src/service.c
src/service.h
src/socket.c
src/test-daemon.c [new file with mode: 0644]
src/unit.h

index ecc9f70..0ad1446 100644 (file)
@@ -1,3 +1,4 @@
+test-daemon
 systemd-install
 org.freedesktop.systemd1.*.xml
 test-ns
index d875786..e50ae82 100644 (file)
@@ -67,7 +67,8 @@ noinst_PROGRAMS = \
        test-engine \
        test-job-type \
        test-ns \
-       test-loopback
+       test-loopback \
+       test-daemon
 
 dist_dbuspolicy_DATA = \
        src/org.freedesktop.systemd1.conf
@@ -316,8 +317,10 @@ test_loopback_SOURCES = \
        src/test-loopback.c \
        src/loopback-setup.c
 
-test_loopback_CFLAGS = $(systemd_CFLAGS)
-test_loopback_LDADD = $(systemd_LDADD)
+test_daemon_SOURCES = \
+       $(BASIC_SOURCES) \
+       src/test-daemon.c \
+       src/sd-daemon.c
 
 systemd_logger_SOURCES = \
        $(BASIC_SOURCES) \
diff --git a/fixme b/fixme
index d1718ce..e39e725 100644 (file)
--- a/fixme
+++ b/fixme
@@ -1,4 +1,4 @@
-* timer
+* calendar time support in timer
 
 * enforce max number of concurrent connection limit in sockets.
 
@@ -49,8 +49,6 @@
    - bluetoothd (/var/run/sdp! @/org/bluez/audio!)
    - distccd
 
-* regnerate unit/sysv search paths on daemon reload
-
 * write utmp record a la upstart for processes
 
 * run PAM session stuff
index 330014d..108c4fc 100644 (file)
@@ -535,6 +535,37 @@ int cgroup_notify_empty(Manager *m, const char *group) {
         return 0;
 }
 
+Unit* cgroup_unit_by_pid(Manager *m, pid_t pid) {
+        CGroupBonding *l, *b;
+        char *group = NULL;
+        int r;
+
+        assert(m);
+
+        if (pid <= 1)
+                return NULL;
+
+        if ((r = cgroup_get_current_controller_path(pid, m->cgroup_controller, &group)))
+                return NULL;
+
+        l = hashmap_get(m->cgroup_bondings, group);
+        free(group);
+
+        if (!l)
+                return NULL;
+
+        LIST_FOREACH(by_path, b, l) {
+
+                if (!b->unit)
+                        continue;
+
+                if (b->only_us)
+                        return b->unit;
+        }
+
+        return NULL;
+}
+
 CGroupBonding *cgroup_bonding_find_list(CGroupBonding *first, const char *controller) {
         CGroupBonding *b;
 
index 26fac0a..67c7cc3 100644 (file)
@@ -76,4 +76,6 @@ int manager_shutdown_cgroup(Manager *m, bool delete);
 
 int cgroup_notify_empty(Manager *m, const char *group);
 
+Unit* cgroup_unit_by_pid(Manager *m, pid_t pid);
+
 #endif
index 6286172..f70a772 100644 (file)
@@ -41,7 +41,8 @@
         "  <property name=\"ControlPID\" type=\"u\" access=\"read\"/>\n" \
         "  <property name=\"SysVPath\" type=\"s\" access=\"read\"/>\n"  \
         "  <property name=\"BusName\" type=\"s\" access=\"read\"/>\n"   \
-        " </interface>\n"
+        "  <property name=\"StatusText\" type=\"s\" access=\"read\"/>\n" \
+       " </interface>\n"
 
 #define INTROSPECTION                                                   \
         DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE                       \
@@ -76,6 +77,7 @@ DBusHandlerResult bus_service_message_handler(Unit *u, DBusMessage *message) {
                 { "org.freedesktop.systemd1.Service", "ControlPID",             bus_property_append_pid,    "u", &u->service.control_pid },
                 { "org.freedesktop.systemd1.Service", "SysVPath",               bus_property_append_string, "s", u->service.sysv_path },
                 { "org.freedesktop.systemd1.Service", "BusName",                bus_property_append_string, "s", u->service.bus_name },
+                { "org.freedesktop.systemd1.Service", "StatusText",             bus_property_append_string, "s", u->service.status_text },
                 { NULL, NULL, NULL, NULL, NULL }
         };
 
index 34c3883..56ed5cd 100644 (file)
@@ -354,6 +354,10 @@ int main(int argc, char *argv[]) {
         if (server_init(&server, (unsigned) n) < 0)
                 return 2;
 
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing requests...");
+
         for (;;) {
                 struct epoll_event event;
                 int k;
@@ -378,6 +382,9 @@ int main(int argc, char *argv[]) {
         r = 0;
 
 fail:
+        sd_notify(false,
+                  "STATUS=Shutting down...");
+
         server_done(&server);
 
         log_info("systemd-initctl stopped as pid %llu", (unsigned long long) getpid());
index 5c7e4ee..48eee6c 100644 (file)
@@ -547,6 +547,10 @@ int main(int argc, char *argv[]) {
         if (server_init(&server, (unsigned) n) < 0)
                 return 3;
 
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing requests...");
+
         for (;;) {
                 struct epoll_event event;
                 int k;
@@ -571,6 +575,9 @@ int main(int argc, char *argv[]) {
         r = 0;
 
 fail:
+        sd_notify(false,
+                  "STATUS=Shutting down...");
+
         server_done(&server);
 
         log_info("systemd-logger stopped as pid %llu", (unsigned long long) getpid());
index 4dcdf2e..97d05b5 100644 (file)
 /* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */
 #define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC)
 
+/* Where clients shall send notification messages to */
+#define NOTIFY_SOCKET "/org/freedesktop/systemd1/notify"
+
+static int manager_setup_notify(Manager *m) {
+        union {
+                struct sockaddr sa;
+                struct sockaddr_un un;
+        } sa;
+        struct epoll_event ev;
+        char *ne[2], **t;
+        int one = 1;
+
+        assert(m);
+
+        m->notify_watch.type = WATCH_NOTIFY;
+        if ((m->notify_watch.fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+                log_error("Failed to allocate notification socket: %m");
+                return -errno;
+        }
+
+        zero(sa);
+        sa.sa.sa_family = AF_UNIX;
+
+        if (m->running_as == MANAGER_SESSION)
+                snprintf(sa.un.sun_path+1, sizeof(sa.un.sun_path)-1, NOTIFY_SOCKET "/%llu", random_ull());
+        else
+                strncpy(sa.un.sun_path+1, NOTIFY_SOCKET, sizeof(sa.un.sun_path)-1);
+
+        if (bind(m->notify_watch.fd, &sa.sa, sizeof(sa)) < 0) {
+                log_error("bind() failed: %m");
+                return -errno;
+        }
+
+        if (setsockopt(m->notify_watch.fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
+                log_error("SO_PASSCRED failed: %m");
+                return -errno;
+        }
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.ptr = &m->notify_watch;
+
+        if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0)
+                return -errno;
+
+        if (asprintf(&ne[0], "NOTIFY_SOCKET=@%s", sa.un.sun_path+1) < 0)
+                return -ENOMEM;
+
+        ne[1] = NULL;
+        t = strv_env_merge(m->environment, ne, NULL);
+        free(ne[0]);
+
+        if (!t)
+                return -ENOMEM;
+
+        strv_free(m->environment);
+        m->environment = t;
+
+        return 0;
+}
+
 static int enable_special_signals(Manager *m) {
         char fd;
 
@@ -177,6 +238,9 @@ int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) {
         if ((r = manager_setup_cgroup(m)) < 0)
                 goto fail;
 
+        if ((r = manager_setup_notify(m)) < 0)
+                goto fail;
+
         /* Try to connect to the busses, if possible. */
         if ((r = bus_init_system(m)) < 0 ||
             (r = bus_init_api(m)) < 0)
@@ -364,6 +428,8 @@ void manager_free(Manager *m) {
                 close_nointr_nofail(m->epoll_fd);
         if (m->signal_watch.fd >= 0)
                 close_nointr_nofail(m->signal_watch.fd);
+        if (m->notify_watch.fd >= 0)
+                close_nointr_nofail(m->notify_watch.fd);
 
         lookup_paths_free(&m->lookup_paths);
         strv_free(m->environment);
@@ -1521,12 +1587,82 @@ unsigned manager_dispatch_dbus_queue(Manager *m) {
         return n;
 }
 
+static int manager_process_notify_fd(Manager *m) {
+        ssize_t n;
+
+        assert(m);
+
+        for (;;) {
+                char buf[4096];
+                struct msghdr msghdr;
+                struct iovec iovec;
+                struct ucred *ucred;
+                union {
+                        struct cmsghdr cmsghdr;
+                        uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+                } control;
+                Unit *u;
+                char **tags;
+
+                zero(iovec);
+                iovec.iov_base = buf;
+                iovec.iov_len = sizeof(buf)-1;
+
+                zero(control);
+                zero(msghdr);
+                msghdr.msg_iov = &iovec;
+                msghdr.msg_iovlen = 1;
+                msghdr.msg_control = &control;
+                msghdr.msg_controllen = sizeof(control);
+
+                if ((n = recvmsg(m->notify_watch.fd, &msghdr, MSG_DONTWAIT)) <= 0) {
+                        if (n >= 0)
+                                return -EIO;
+
+                        if (errno == EAGAIN)
+                                break;
+
+                        return -errno;
+                }
+
+                if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
+                    control.cmsghdr.cmsg_level != SOL_SOCKET ||
+                    control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
+                    control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
+                        log_warning("Received notify message without credentials. Ignoring.");
+                        continue;
+                }
+
+                ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
+
+                if (!(u = hashmap_get(m->watch_pids, UINT32_TO_PTR(ucred->pid))))
+                        if (!(u = cgroup_unit_by_pid(m, ucred->pid))) {
+                                log_warning("Cannot find unit for notify message of PID %lu.", (unsigned long) ucred->pid);
+                                continue;
+                        }
+
+                char_array_0(buf);
+                if (!(tags = strv_split(buf, "\n\r")))
+                        return -ENOMEM;
+
+                log_debug("Got notification message for unit %s", u->meta.id);
+
+                if (UNIT_VTABLE(u)->notify_message)
+                        UNIT_VTABLE(u)->notify_message(u, tags);
+
+                strv_free(tags);
+        }
+
+        return 0;
+}
+
 static int manager_dispatch_sigchld(Manager *m) {
         assert(m);
 
         for (;;) {
                 siginfo_t si;
                 Unit *u;
+                int r;
 
                 zero(si);
 
@@ -1555,6 +1691,17 @@ static int manager_dispatch_sigchld(Manager *m) {
                         free(name);
                 }
 
+                /* Let's flush any message the dying child might still
+                 * have queued for us. This ensures that the process
+                 * still exists in /proc so that we can figure out
+                 * which cgroup and hence unit it belongs to. */
+                if ((r = manager_process_notify_fd(m)) < 0)
+                        return r;
+
+                /* And now figure out the unit this belongs to */
+                if (!(u = hashmap_get(m->watch_pids, UINT32_TO_PTR(si.si_pid))))
+                        u = cgroup_unit_by_pid(m, si.si_pid);
+
                 /* And now, we actually reap the zombie. */
                 if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) {
                         if (errno == EINTR)
@@ -1572,11 +1719,12 @@ static int manager_dispatch_sigchld(Manager *m) {
                           si.si_status,
                           strna(si.si_code == CLD_EXITED ? exit_status_to_string(si.si_status) : strsignal(si.si_status)));
 
-                if (!(u = hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid))))
+                if (!u)
                         continue;
 
                 log_debug("Child %llu belongs to %s", (long long unsigned) si.si_pid, u->meta.id);
 
+                hashmap_remove(m->watch_pids, UINT32_TO_PTR(si.si_pid));
                 UNIT_VTABLE(u)->sigchld_event(u, si.si_pid, si.si_code, si.si_status);
         }
 
@@ -1738,6 +1886,17 @@ static int process_event(Manager *m, struct epoll_event *ev) {
 
                 break;
 
+        case WATCH_NOTIFY:
+
+                /* An incoming daemon notification event? */
+                if (ev->events != EPOLLIN)
+                        return -EINVAL;
+
+                if ((r = manager_process_notify_fd(m)) < 0)
+                        return r;
+
+                break;
+
         case WATCH_FD:
 
                 /* Some fd event, to be dispatched to the units */
index d78bcf5..070a27f 100644 (file)
@@ -56,6 +56,7 @@ typedef enum ManagerRunningAs {
 enum WatchType {
         WATCH_INVALID,
         WATCH_SIGNAL,
+        WATCH_NOTIFY,
         WATCH_FD,
         WATCH_TIMER,
         WATCH_MOUNT,
@@ -171,6 +172,7 @@ struct Manager {
 
         Hashmap *watch_pids;  /* pid => Unit object n:1 */
 
+        Watch notify_watch;
         Watch signal_watch;
 
         int epoll_fd;
@@ -215,7 +217,6 @@ struct Manager {
         char *cgroup_hierarchy;
 
         usec_t gc_queue_timestamp;
-
         int gc_marker;
         unsigned n_in_gc_queue;
 
index 5577f16..a8f3d7b 100644 (file)
@@ -921,12 +921,14 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) {
         assert(m);
         assert(pid >= 0);
 
-        success = is_clean_exit(code, status);
-        m->failure = m->failure || !success;
+        if (pid != m->control_pid)
+                return;
 
-        assert(m->control_pid == pid);
         m->control_pid = 0;
 
+        success = is_clean_exit(code, status);
+        m->failure = m->failure || !success;
+
         if (m->control_command) {
                 exec_status_fill(&m->control_command->exec_status, pid, code, status);
                 m->control_command = NULL;
index 29bd204..0dad73f 100644 (file)
   SOFTWARE.
 ***/
 
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <errno.h>
 #include <unistd.h>
 #include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
 
 #include "sd-daemon.h"
 
 int sd_listen_fds(int unset_environment) {
 
-#ifdef DISABLE_SYSTEMD
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
         return 0;
 #else
         int r, fd;
@@ -317,3 +323,108 @@ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t
 
         return 1;
 }
+
+int sd_notify(int unset_environment, const char *state) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+        int fd = -1, r;
+        struct msghdr msghdr;
+        struct iovec iovec;
+        union sockaddr_union sockaddr;
+        struct ucred *ucred;
+        union {
+                struct cmsghdr cmsghdr;
+                uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+        } control;
+        const char *e;
+
+        if (!state) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if (!(e = getenv("NOTIFY_SOCKET"))) {
+                r = 0;
+                goto finish;
+        }
+
+        /* Must be an abstract socket, or an absolute path */
+        if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+                r = -EINVAL;
+                goto finish;
+        }
+
+        if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        memset(&sockaddr, 0, sizeof(sockaddr));
+        sockaddr.sa.sa_family = AF_UNIX;
+        strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
+
+        if (sockaddr.un.sun_path[0] == '@')
+                sockaddr.un.sun_path[0] = 0;
+
+        memset(&iovec, 0, sizeof(iovec));
+        iovec.iov_base = (char*) state;
+        iovec.iov_len = strlen(state);
+
+        memset(&control, 0, sizeof(control));
+        control.cmsghdr.cmsg_level = SOL_SOCKET;
+        control.cmsghdr.cmsg_type = SCM_CREDENTIALS;
+        control.cmsghdr.cmsg_len = CMSG_LEN(sizeof(struct ucred));
+
+        ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
+        ucred->pid = getpid();
+        ucred->uid = getuid();
+        ucred->gid = getgid();
+
+        memset(&msghdr, 0, sizeof(msghdr));
+        msghdr.msg_name = &sockaddr;
+        msghdr.msg_namelen = sizeof(struct sockaddr_un);
+        msghdr.msg_iov = &iovec;
+        msghdr.msg_iovlen = 1;
+        msghdr.msg_control = &control;
+        msghdr.msg_controllen = control.cmsghdr.cmsg_len;
+
+        if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+                r = -errno;
+                goto finish;
+        }
+
+        r = 0;
+
+finish:
+        if (unset_environment)
+                unsetenv("NOTIFY_SOCKET");
+
+        if (fd >= 0)
+                close(fd);
+
+        return r;
+#endif
+}
+
+int sd_notifyf(int unset_environment, const char *format, ...) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+        return 0;
+#else
+        va_list ap;
+        char *p = NULL;
+        int r;
+
+        va_start(ap, format);
+        r = vasprintf(&p, format, ap);
+        va_end(ap);
+
+        if (r < 0 || !p)
+                return -ENOMEM;
+
+        r = sd_notify(unset_environment, p);
+        free(p);
+
+        return r;
+#endif
+}
index 0d8de45..0277b0f 100644 (file)
   SOFTWARE.
 ***/
 
+#include <sys/types.h>
 #include <inttypes.h>
 
+#ifdef __cplusplus
+extern "C" {
+#endif
+
 /* Reference implementation of a few systemd related interfaces for
  * writing daemons. These interfaces are trivial to implement. To
  * simplify porting we provide this reference
@@ -111,4 +116,58 @@ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port
  * errno style error code on failure. */
 int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length);
 
+/* Informs systemd about changed daemon state. This takes a numeber of
+ * newline seperated environment-style variable assignments in a
+ * string. The following strings are known:
+ *
+ *    READY=1      Tells systemd that daemon startup is finished (only
+ *                 relevant for services of Type=notify). The passed
+ *                 argument is a boolean "1" or "0". Since there is
+ *                 little value in signalling non-readiness the only
+ *                 value daemons should send is "READY=1".
+ *
+ *    STATUS=...   Passes a status string back to systemd that
+ *                 describes the daemon state. This is free-from and
+ *                 can be used for various purposes: general state
+ *                 feedback, fsck-like programs could pass completion
+ *                 percentages and failing programs could pass a human
+ *                 readable error message. Example: "STATUS=Completed
+ *                 66% of file system check..."
+ *
+ *    ERRNO=...    If a daemon fails, the errno-style error code,
+ *                 formatted as string. Example: "ERRNO=2" for ENOENT.
+ *
+ *    BUSERROR=... If a daemon fails, the D-Bus error-style error
+ *                 code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+ *
+ *    MAINPID=...  The main pid of a daemon, in case systemd did not
+ *                 fork off the process itself. Example: "MAINPID=4711"
+ *
+ * See sd_notifyf() for more complete examples.
+ */
+int sd_notify(int unset_environment, const char *state);
+
+/* Similar to sd_send_state() but takes a format string.
+ *
+ * Example 1: A daemon could send the following after initialization:
+ *
+ * sd_notifyf(0, "READY=1\n"
+ *               "STATUS=Processing requests...\n"
+ *               "MAINPID=%lu",
+ *               (unsigned long) getpid());
+ *
+ * Example 2: A daemon could send the following shortly before
+ * exiting, on failure:
+ *
+ * sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+ *               "ERRNO=%i",
+ *               strerror(errno),
+ *               errno);
+ */
+int sd_notifyf(int unset_environment, const char *format, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
 #endif
index 547a555..6e35a4d 100644 (file)
@@ -149,6 +149,9 @@ static void service_done(Unit *u) {
         free(s->sysv_runlevels);
         s->sysv_runlevels = NULL;
 
+        free(s->status_text);
+        s->status_text = NULL;
+
         exec_context_done(&s->exec_context);
         exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX);
         s->control_command = NULL;
@@ -907,6 +910,10 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) {
                 fprintf(f, "%sSysVRunLevels: %s\n",
                         prefix, s->sysv_runlevels);
 
+        if (s->status_text)
+                fprintf(f, "%sStatus Text: %s\n",
+                        prefix, s->status_text);
+
         free(p2);
 }
 
@@ -1120,7 +1127,9 @@ static int service_coldplug(Unit *u) {
 
                 if ((s->deserialized_state == SERVICE_START &&
                      (s->type == SERVICE_FORKING ||
-                      s->type == SERVICE_DBUS)) ||
+                      s->type == SERVICE_DBUS ||
+                      s->type == SERVICE_FINISH ||
+                      s->type == SERVICE_NOTIFY)) ||
                     s->deserialized_state == SERVICE_START_POST ||
                     s->deserialized_state == SERVICE_RUNNING ||
                     s->deserialized_state == SERVICE_RELOAD ||
@@ -1541,7 +1550,7 @@ static void service_enter_start(Service *s) {
 
         if ((r = service_spawn(s,
                                s->exec_command[SERVICE_EXEC_START],
-                               s->type == SERVICE_FORKING || s->type == SERVICE_DBUS,
+                               s->type == SERVICE_FORKING || s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY,
                                true,
                                true,
                                true,
@@ -1569,13 +1578,15 @@ static void service_enter_start(Service *s) {
                 service_set_state(s, SERVICE_START);
 
         } else if (s->type == SERVICE_FINISH ||
-                   s->type == SERVICE_DBUS) {
+                   s->type == SERVICE_DBUS ||
+                   s->type == SERVICE_NOTIFY) {
 
                 /* For finishing services we wait until the start
                  * process exited, too, but it is our main process. */
 
                 /* For D-Bus services we know the main pid right away,
-                 * but wait for the bus name to appear on the bus. */
+                 * but wait for the bus name to appear on the
+                 * bus. Notify services are similar. */
 
                 s->main_pid = pid;
                 s->main_pid_known = true;
@@ -1946,7 +1957,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                 exec_status_fill(&s->main_exec_status, pid, code, status);
                 s->main_pid = 0;
 
-                if (s->type == SERVICE_SIMPLE || s->type == SERVICE_FINISH) {
+                if (s->type != SERVICE_FORKING) {
                         assert(s->exec_command[SERVICE_EXEC_START]);
                         s->exec_command[SERVICE_EXEC_START]->exec_status = s->main_exec_status;
                 }
@@ -1974,7 +1985,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                         service_enter_signal(s, SERVICE_FINAL_SIGTERM, false);
                                 break;
                         } else {
-                                assert(s->type == SERVICE_DBUS);
+                                assert(s->type == SERVICE_DBUS || s->type == SERVICE_NOTIFY);
 
                                 /* Fall through */
                         }
@@ -2101,8 +2112,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
                                 assert_not_reached("Uh, control process died at wrong time.");
                         }
                 }
-        } else
-                assert_not_reached("Got SIGCHLD for unkown PID");
+        }
 }
 
 static void service_timer_event(Unit *u, uint64_t elapsed, Watch* w) {
@@ -2195,6 +2205,57 @@ static void service_cgroup_notify_event(Unit *u) {
         }
 }
 
+static void service_notify_message(Unit *u, char **tags) {
+        Service *s = SERVICE(u);
+        const char *e;
+
+        assert(u);
+
+        log_debug("%s: Got message", u->meta.id);
+
+        /* Interpret MAINPID= */
+        if ((e = strv_find_prefix(tags, "MAINPID=")) &&
+            (s->state == SERVICE_START ||
+             s->state == SERVICE_START_POST ||
+             s->state == SERVICE_RUNNING ||
+             s->state == SERVICE_RELOAD)) {
+                unsigned long pid;
+
+                if (safe_atolu(e + 8, &pid) < 0 ||
+                    (unsigned long) (pid_t) pid != pid ||
+                    pid <= 1)
+                        log_warning("Failed to parse %s", e);
+                else {
+                        log_debug("%s: got %s", u->meta.id, e);
+                        s->main_pid = (pid_t) pid;
+                }
+        }
+
+        /* Interpret READY= */
+        if (s->type == SERVICE_NOTIFY &&
+            s->state == SERVICE_START &&
+            strv_find(tags, "READY=1")) {
+                log_debug("%s: got READY=1", u->meta.id);
+
+                service_enter_start_post(s);
+        }
+
+        /* Interpret STATUS= */
+        if ((e = strv_find_prefix(tags, "STATUS="))) {
+                char *t;
+
+                if (!(t = strdup(e+7))) {
+                        log_error("Failed to allocate string.");
+                        return;
+                }
+
+                log_debug("%s: got %s", u->meta.id, e);
+
+                free(s->status_text);
+                s->status_text = t;
+        }
+}
+
 static int service_enumerate(Manager *m) {
         char **p;
         unsigned i;
@@ -2456,7 +2517,8 @@ static const char* const service_type_table[_SERVICE_TYPE_MAX] = {
         [SERVICE_FORKING] = "forking",
         [SERVICE_SIMPLE] = "simple",
         [SERVICE_FINISH] = "finish",
-        [SERVICE_DBUS] = "dbus"
+        [SERVICE_DBUS] = "dbus",
+        [SERVICE_NOTIFY] = "notify"
 };
 
 DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType);
@@ -2502,6 +2564,7 @@ const UnitVTable service_vtable = {
         .timer_event = service_timer_event,
 
         .cgroup_notify_empty = service_cgroup_notify_event,
+        .notify_message = service_notify_message,
 
         .bus_name_owner_change = service_bus_name_owner_change,
         .bus_query_pid_done = service_bus_query_pid_done,
index 5242de5..d644e72 100644 (file)
@@ -60,6 +60,7 @@ typedef enum ServiceType {
         SERVICE_FORKING,  /* forks by itself (i.e. traditional daemons) */
         SERVICE_FINISH,   /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */
         SERVICE_DBUS,     /* we fork and wait until a specific D-Bus name appears on the bus */
+        SERVICE_NOTIFY,   /* we fork and wait until a daemon sends us a ready message with sd_notify() */
         _SERVICE_TYPE_MAX,
         _SERVICE_TYPE_INVALID = -1
 } ServiceType;
@@ -121,6 +122,8 @@ struct Service {
 
         char *bus_name;
 
+        char *status_text;
+
         RateLimit ratelimit;
 
         int socket_fd;
index 19f1d22..66131f8 100644 (file)
@@ -1228,12 +1228,14 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) {
         assert(s);
         assert(pid >= 0);
 
-        success = is_clean_exit(code, status);
-        s->failure = s->failure || !success;
+        if (pid != s->control_pid)
+                return;
 
-        assert(s->control_pid == pid);
         s->control_pid = 0;
 
+        success = is_clean_exit(code, status);
+        s->failure = s->failure || !success;
+
         if (s->control_command)
                 exec_status_fill(&s->control_command->exec_status, pid, code, status);
 
diff --git a/src/test-daemon.c b/src/test-daemon.c
new file mode 100644 (file)
index 0000000..8911b68
--- /dev/null
@@ -0,0 +1,37 @@
+/*-*- Mode: C; c-basic-offset: 8 -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2010 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <unistd.h>
+
+#include "sd-daemon.h"
+
+int main(int argc, char*argv[]) {
+
+        sd_notify(0, "STATUS=Starting up");
+        sleep(5);
+        sd_notify(0,
+                  "STATUS=Running\n"
+                  "READY=1");
+        sleep(10);
+        sd_notify(0, "STATUS=Quitting");
+
+        return 0;
+}
index 5e61f7c..1f8874f 100644 (file)
@@ -285,6 +285,9 @@ struct UnitVTable {
          * ran empty */
         void (*cgroup_notify_empty)(Unit *u);
 
+        /* Called whenever a process of this unit sends us a message */
+        void (*notify_message)(Unit *u, char **tags);
+
         /* Called whenever a name thus Unit registered for comes or
          * goes away. */
         void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner);