diff options
author | Lennart Poettering <lennart@poettering.net> | 2010-06-16 05:10:31 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2010-06-16 05:10:31 +0200 |
commit | 8c47c7325fa1ab72febf807f8831ff24c75fbf45 (patch) | |
tree | 3a116267ab7c0edbfdb32bbd2b6ea222436fcce3 | |
parent | 17586c16bac1d5ecf7d60ef57d18e82e36c288c1 (diff) |
notify: add minimal readiness/status protocol for spawned daemons
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile.am | 9 | ||||
-rw-r--r-- | fixme | 4 | ||||
-rw-r--r-- | src/cgroup.c | 31 | ||||
-rw-r--r-- | src/cgroup.h | 2 | ||||
-rw-r--r-- | src/dbus-service.c | 4 | ||||
-rw-r--r-- | src/initctl.c | 7 | ||||
-rw-r--r-- | src/logger.c | 7 | ||||
-rw-r--r-- | src/manager.c | 161 | ||||
-rw-r--r-- | src/manager.h | 3 | ||||
-rw-r--r-- | src/mount.c | 8 | ||||
-rw-r--r-- | src/sd-daemon.c | 113 | ||||
-rw-r--r-- | src/sd-daemon.h | 59 | ||||
-rw-r--r-- | src/service.c | 81 | ||||
-rw-r--r-- | src/service.h | 3 | ||||
-rw-r--r-- | src/socket.c | 8 | ||||
-rw-r--r-- | src/test-daemon.c | 37 | ||||
-rw-r--r-- | src/unit.h | 3 |
18 files changed, 516 insertions, 25 deletions
diff --git a/.gitignore b/.gitignore index ecc9f7074f..0ad1446777 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +test-daemon systemd-install org.freedesktop.systemd1.*.xml test-ns diff --git a/Makefile.am b/Makefile.am index d8757868e8..e50ae82040 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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) \ @@ -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 diff --git a/src/cgroup.c b/src/cgroup.c index 330014dc9f..108c4fcf5e 100644 --- a/src/cgroup.c +++ b/src/cgroup.c @@ -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; diff --git a/src/cgroup.h b/src/cgroup.h index 26fac0a660..67c7cc3501 100644 --- a/src/cgroup.h +++ b/src/cgroup.h @@ -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 diff --git a/src/dbus-service.c b/src/dbus-service.c index 6286172a1b..f70a77212c 100644 --- a/src/dbus-service.c +++ b/src/dbus-service.c @@ -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 } }; diff --git a/src/initctl.c b/src/initctl.c index 34c38839df..56ed5cdf30 100644 --- a/src/initctl.c +++ b/src/initctl.c @@ -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()); diff --git a/src/logger.c b/src/logger.c index 5c7e4ee42b..48eee6cd12 100644 --- a/src/logger.c +++ b/src/logger.c @@ -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()); diff --git a/src/manager.c b/src/manager.c index 4dcdf2e507..97d05b52c3 100644 --- a/src/manager.c +++ b/src/manager.c @@ -60,6 +60,67 @@ /* 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 */ diff --git a/src/manager.h b/src/manager.h index d78bcf5d76..070a27f70b 100644 --- a/src/manager.h +++ b/src/manager.h @@ -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; diff --git a/src/mount.c b/src/mount.c index 5577f16dfd..a8f3d7b9aa 100644 --- a/src/mount.c +++ b/src/mount.c @@ -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; diff --git a/src/sd-daemon.c b/src/sd-daemon.c index 29bd204680..0dad73f94d 100644 --- a/src/sd-daemon.c +++ b/src/sd-daemon.c @@ -24,6 +24,10 @@ SOFTWARE. ***/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> @@ -34,12 +38,14 @@ #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 +} diff --git a/src/sd-daemon.h b/src/sd-daemon.h index 0d8de45b72..0277b0fb5c 100644 --- a/src/sd-daemon.h +++ b/src/sd-daemon.h @@ -27,8 +27,13 @@ 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 diff --git a/src/service.c b/src/service.c index 547a55567a..6e35a4da58 100644 --- a/src/service.c +++ b/src/service.c @@ -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, diff --git a/src/service.h b/src/service.h index 5242de58fa..d644e7207a 100644 --- a/src/service.h +++ b/src/service.h @@ -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; diff --git a/src/socket.c b/src/socket.c index 19f1d22097..66131f867d 100644 --- a/src/socket.c +++ b/src/socket.c @@ -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 index 0000000000..8911b68620 --- /dev/null +++ b/src/test-daemon.c @@ -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; +} diff --git a/src/unit.h b/src/unit.h index 5e61f7c027..1f8874f4cc 100644 --- a/src/unit.h +++ b/src/unit.h @@ -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); |