Commit 8c47c732 authored by Lennart Poettering's avatar Lennart Poettering
Browse files

notify: add minimal readiness/status protocol for spawned daemons

parent 17586c16
test-daemon
systemd-install
org.freedesktop.systemd1.*.xml
test-ns
......
......@@ -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) \
......
* 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
......
......@@ -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;
......
......@@ -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
......@@ -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 }
};
......
......@@ -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());
......
......@@ -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());
......
......@@ -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 */
......
......@@ -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;
......
......@@ -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;
......
......@@ -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
}
......@@ -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
......@@ -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",