summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillem Jover <guillem@debian.org>2018-03-25 19:43:18 +0200
committerGuillem Jover <guillem@debian.org>2019-01-15 04:49:02 +0100
commit711f929ec0eea9b2248b1723e64c2021cf99c16d (patch)
treee8b7fda2662bbe4cf22383121f082d089bedf706
parentf47ab454d1eb51cb2302172899847064541158da (diff)
downloaddpkg-711f929ec0eea9b2248b1723e64c2021cf99c16d.tar.gz
s-s-d: Implement --notify-await and --notify-timeout options
These implement the systemd readiness protocol for services. So that a service can tell s-s-d when it's ready and then we can return and callers can assume safely that the service is ready to work. [biebl@debian.org: - Fix some typos. ] [trek00@inbox.ru: - Fix notification directory and socket permissions. ] Closes: #910707
-rw-r--r--debian/changelog3
-rw-r--r--man/start-stop-daemon.man29
-rw-r--r--utils/start-stop-daemon.c217
3 files changed, 248 insertions, 1 deletions
diff --git a/debian/changelog b/debian/changelog
index da2897367..651b97f73 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -15,6 +15,9 @@ dpkg (1.19.3) UNRELEASED; urgency=medium
Prompted by Michael Orlitzky <michael@orlitzky.com>.
* start-stop-daemon: Print complete verbose lines, instead of partial lines
with no newlines and a final print with a newline.
+ * start-stop-daemon: Add new --notify-await and --notify-timeout options,
+ which implement the systemd readiness protocol for services.
+ Closes: #910707
* Perl modules:
- Dpkg::Changelog::Debian: Preserve modelines at EOF. Closes: #916056
Thanks to Chris Lamb <lamby@debian.org> for initial test cases.
diff --git a/man/start-stop-daemon.man b/man/start-stop-daemon.man
index 2391d5e9f..4723596d3 100644
--- a/man/start-stop-daemon.man
+++ b/man/start-stop-daemon.man
@@ -5,7 +5,7 @@
.\" Copyright © 2000-2001 Wichert Akkerman <wakkerma@debian.org>
.\" Copyright © 2002-2003 Adam Heath <doogie@debian.org>
.\" Copyright © 2004 Scott James Remnant <keybuk@debian.org>
-.\" Copyright © 2008-2015 Guillem Jover <guillem@debian.org>
+.\" Copyright © 2008-2016, 2018 Guillem Jover <guillem@debian.org>
.\"
.\" This is free software; you can redistribute it and/or modify
.\" it under the terms of the GNU General Public License as published by
@@ -266,6 +266,33 @@ reason. This is a last resort, and is only meant for programs that either
make no sense forking on their own, or where it's not feasible to add the
code for them to do this themselves.
.TP
+.BR \-\-notify\-await
+Wait for the background process to send a readiness notification before
+considering the service started (since version 1.19.3).
+This implements parts of the systemd readiness procotol, as specified
+in the \fBsd_notify\fP(3) man page.
+The following variables are supported:
+.RS
+.TP
+.B READY=1
+The program is ready to give service, so we can exit safely.
+.TP
+.BI EXTEND_TIMEOUT_USEC= number
+The program requests to extend the timeout by \fInumber\fP microseconds.
+This will reset the current timeout to the specified value.
+.TP
+.BI ERRNO= number
+The program is exiting with an error.
+Do the same and print the user-friendly string for the \fBerrno\fP value.
+.RE
+.
+.TP
+.BI \-\-notify\-timeout timeout
+Set a timeout for the \fB\-\-notify\-await\fP option (since version 1.19.3).
+When the timeout is reached, \fBstart\-stop\-daemon\fP will exit with an
+error code, and no readiness notification will be awaited.
+The default is \fB60\fP seconds.
+.TP
.BR \-C ", " \-\-no\-close
Do not close any file descriptor when forcing the daemon into the background
(since version 1.16.5).
diff --git a/utils/start-stop-daemon.c b/utils/start-stop-daemon.c
index 63fb36a7d..e7e1cdc3d 100644
--- a/utils/start-stop-daemon.c
+++ b/utils/start-stop-daemon.c
@@ -79,6 +79,8 @@
#include <sys/wait.h>
#include <sys/select.h>
#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
#include <errno.h>
#include <limits.h>
@@ -210,6 +212,10 @@ static int quietmode = 0;
static int exitnodo = 1;
static bool background = false;
static bool close_io = true;
+static bool notify_await = false;
+static int notify_timeout = 60;
+static char *notify_sockdir;
+static char *notify_socket;
static bool mpidfile = false;
static bool rpidfile = false;
static int signal_nr = SIGTERM;
@@ -526,6 +532,187 @@ wait_for_child(pid_t pid)
}
static void
+cleanup_socket_dir(void)
+{
+ unlink(notify_socket);
+ rmdir(notify_sockdir);
+}
+
+static char *
+setup_socket_name(const char *suffix)
+{
+ const char *basedir;
+
+ if (getuid() == 0 && access("/run", F_OK) == 0) {
+ basedir = "/run";
+ } else {
+ basedir = getenv("TMPDIR");
+ if (basedir == NULL)
+ basedir = P_tmpdir;
+ }
+
+ if (asprintf(&notify_sockdir, "%s/%s.XXXXXX", basedir, suffix) < 0)
+ fatal("cannot allocate socket directory name");
+
+ if (mkdtemp(notify_sockdir) == NULL)
+ fatal("cannot create socket directory %s", notify_sockdir);
+
+ atexit(cleanup_socket_dir);
+
+ if (chown(notify_sockdir, runas_uid, runas_gid))
+ fatal("cannot change socket directory ownership");
+
+ if (asprintf(&notify_socket, "%s/notify", notify_sockdir) < 0)
+ fatal("cannot allocate socket name");
+
+ setenv("NOTIFY_SOCKET", notify_socket, 1);
+
+ return notify_socket;
+}
+
+static int
+create_notify_socket(void)
+{
+ const char *sockname;
+ struct sockaddr_un su;
+ int fd, rc, flags;
+ static const int enable = 1;
+
+ /* Create notification socket. */
+ fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ fatal("cannot create notification socket");
+
+ /* We could set SOCK_CLOEXEC instead, but then we would need to
+ * check whether the socket call failed, try and then do this anyway,
+ * when we have no threading problems to worry about. */
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0)
+ fatal("cannot read fd flags for notification socket");
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
+ fatal("cannot set close-on-exec flag for notification socket");
+
+ sockname = setup_socket_name(".s-s-d-notify");
+
+ /* Bind to a socket in a temporary directory, selected based on
+ * the platform. */
+ memset(&su, 0, sizeof(su));
+ su.sun_family = AF_UNIX;
+ strncpy(su.sun_path, sockname, sizeof(su.sun_path) - 1);
+
+ rc = bind(fd, &su, sizeof(su));
+ if (rc < 0)
+ fatal("cannot bind to notification socket");
+
+ rc = chmod(su.sun_path, 0660);
+ if (rc < 0)
+ fatal("cannot change notification socket permissions");
+
+ rc = chown(su.sun_path, runas_uid, runas_gid);
+ if (rc < 0)
+ fatal("cannot change notification socket ownership");
+
+ /* XXX: Verify we are talking to an expected child? Although it is not
+ * clear whether this is feasible given the knowledge we have got. */
+ setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable));
+
+ return fd;
+}
+
+static void
+wait_for_notify(int fd)
+{
+ struct timespec startat, now, elapsed, timeout, timeout_orig;
+ fd_set fdrs;
+ int rc;
+
+ timeout.tv_sec = notify_timeout;
+ timeout.tv_nsec = 0;
+ timeout_orig = timeout;
+
+ timespec_gettime(&startat);
+
+ while (timeout.tv_sec >= 0 && timeout.tv_nsec >= 0) {
+ FD_ZERO(&fdrs);
+ FD_SET(fd, &fdrs);
+
+ /* Wait for input. */
+ debug("Waiting for notifications... (timeout %lusec %lunsec)\n",
+ timeout.tv_sec, timeout.tv_nsec);
+ rc = pselect(fd + 1, &fdrs, NULL, NULL, &timeout, NULL);
+
+ /* Catch non-restartable errors, that is, not signals nor
+ * kernel out of resources. */
+ if (rc < 0 && (errno != EINTR && errno != EAGAIN))
+ fatal("cannot monitor notification socket for activity");
+
+ /* Timed-out. */
+ if (rc == 0)
+ fatal("timed out waiting for a notification");
+
+ /* Update the timeout, as should not rely on pselect() having
+ * done that for us, which is an unportable assumption. */
+ timespec_gettime(&now);
+ timespec_sub(&now, &startat, &elapsed);
+ timespec_sub(&timeout_orig, &elapsed, &timeout);
+
+ /* Restartable error, a signal or kernel out of resources. */
+ if (rc < 0)
+ continue;
+
+ /* Parse it and check for a supported notification message,
+ * once we get a READY=1, we exit. */
+ for (;;) {
+ ssize_t nrecv;
+ char buf[4096];
+ char *line, *line_next;
+
+ nrecv = recv(fd, buf, sizeof(buf), 0);
+ if (nrecv < 0 && (errno != EINTR && errno != EAGAIN))
+ fatal("cannot receive notification packet");
+ if (nrecv < 0)
+ break;
+
+ buf[nrecv] = '\0';
+
+ for (line = buf; *line; line = line_next) {
+ line_next = strchrnul(line, '\n');
+ if (*line_next == '\n')
+ *line_next++ = '\0';
+
+ debug("Child sent some notification...\n");
+ if (strncmp(line, "EXTEND_TIMEOUT_USEC=", 20) == 0) {
+ int extend_usec = 0;
+
+ if (parse_unsigned(line + 20, 10, &extend_usec) != 0)
+ fatal("cannot parse extended timeout notification %s", line);
+
+ /* Reset the current timeout. */
+ timeout.tv_sec = extend_usec / 1000L;
+ timeout.tv_nsec = (extend_usec % 1000L) *
+ NANOSEC_IN_MILLISEC;
+ timeout_orig = timeout;
+
+ timespec_gettime(&startat);
+ } else if (strncmp(line, "ERRNO=", 6) == 0) {
+ int suberrno = 0;
+
+ if (parse_unsigned(line + 6, 10, &suberrno) != 0)
+ fatal("cannot parse errno notification %s", line);
+ errno = suberrno;
+ fatal("program failed to initialize");
+ } else if (strcmp(line, "READY=1") == 0) {
+ debug("-> Notification => ready for service.\n");
+ return;
+ } else {
+ debug("-> Notification line '%s' received\n", line);
+ }
+ }
+ }
+ }
+}
+
+static void
write_pidfile(const char *filename, pid_t pid)
{
FILE *fp;
@@ -556,6 +743,7 @@ remove_pidfile(const char *filename)
static void
daemonize(void)
{
+ int notify_fd = -1;
pid_t pid;
sigset_t mask;
sigset_t oldmask;
@@ -569,6 +757,9 @@ daemonize(void)
if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1)
fatal("cannot block SIGCHLD");
+ if (notify_await)
+ notify_fd = create_notify_socket();
+
pid = fork();
if (pid < 0)
fatal("unable to do first fork");
@@ -578,6 +769,15 @@ daemonize(void)
* not suffer from race conditions on return. */
wait_for_child(pid);
+ if (notify_await) {
+ /* Wait for a readiness notification from the second
+ * child, so that we can safely exit when the service
+ * is up. */
+ wait_for_notify(notify_fd);
+ close(notify_fd);
+ cleanup_socket_dir();
+ }
+
_exit(0);
}
@@ -675,6 +875,8 @@ usage(void)
" scheduler (default prio is 4)\n"
" -k, --umask <mask> change the umask to <mask> before starting\n"
" -b, --background force the process to detach\n"
+" --notify-await wait for a readiness notification\n"
+" --notify-timeout <int> timeout after <int> seconds of notify wait\n"
" -C, --no-close do not close any file descriptor\n"
" -m, --make-pidfile create the pidfile before starting\n"
" --remove-pidfile delete the pidfile after stopping\n"
@@ -1014,6 +1216,8 @@ set_action(enum action_code new_action)
#define OPT_PID 500
#define OPT_PPID 501
#define OPT_RM_PIDFILE 502
+#define OPT_NOTIFY_AWAIT 503
+#define OPT_NOTIFY_TIMEOUT 504
static void
parse_options(int argc, char * const *argv)
@@ -1044,6 +1248,8 @@ parse_options(int argc, char * const *argv)
{ "iosched", 1, NULL, 'I'},
{ "umask", 1, NULL, 'k'},
{ "background", 0, NULL, 'b'},
+ { "notify-await", 0, NULL, OPT_NOTIFY_AWAIT},
+ { "notify-timeout", 1, NULL, OPT_NOTIFY_TIMEOUT},
{ "no-close", 0, NULL, 'C'},
{ "make-pidfile", 0, NULL, 'm'},
{ "remove-pidfile", 0, NULL, OPT_RM_PIDFILE},
@@ -1058,6 +1264,7 @@ parse_options(int argc, char * const *argv)
const char *schedule_str = NULL;
const char *proc_schedule_str = NULL;
const char *io_schedule_str = NULL;
+ const char *notify_timeout_str = NULL;
size_t changeuser_len;
int c;
@@ -1157,6 +1364,12 @@ parse_options(int argc, char * const *argv)
case 'b': /* --background */
background = true;
break;
+ case OPT_NOTIFY_AWAIT:
+ notify_await = true;
+ break;
+ case OPT_NOTIFY_TIMEOUT:
+ notify_timeout_str = optarg;
+ break;
case 'C': /* --no-close */
close_io = false;
break;
@@ -1209,6 +1422,10 @@ parse_options(int argc, char * const *argv)
badusage("umask value must be a positive number");
}
+ if (notify_timeout_str != NULL)
+ if (parse_unsigned(notify_timeout_str, 10, &notify_timeout) != 0)
+ badusage("invalid notify timeout value");
+
if (action == ACTION_NONE)
badusage("need one of --start or --stop or --status");