#include <cstdlib>
+#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include "service.h"
+// Move an fd, if necessary, to another fd. The destination fd must be available (not open).
+// if fd is specified as -1, returns -1 immediately. Returns 0 on success.
+static int move_fd(int fd, int dest)
+{
+ if (fd == -1) return -1;
+ if (fd == dest) return 0;
+
+ if (dup2(fd, dest) == -1) {
+ return -1;
+ }
+
+ close(fd);
+ return 0;
+}
+
void service_record::run_child_proc(const char * const *args, const char *working_dir,
const char *logfile, bool on_console, int wpipefd, int csfd, int socket_fd,
+ int notify_fd, int force_notify_fd, const char *notify_var,
uid_t uid, gid_t gid) noexcept
{
- // Child process. Must not allocate memory (or otherwise risk throwing any exception)
- // from here until exit().
+ // Child process. Must not risk throwing any uncaught exception from here until exit().
// If the console already has a session leader, presumably it is us. On the other hand
// if it has no session leader, and we don't create one, then control inputs such as
sigdelset(&sigwait_set, SIGTERM);
sigdelset(&sigwait_set, SIGQUIT);
- constexpr int bufsz = ((CHAR_BIT * sizeof(pid_t)) / 3 + 2) + 11;
+ constexpr int bufsz = 11 + ((CHAR_BIT * sizeof(pid_t) + 2) / 3) + 1;
// "LISTEN_PID=" - 11 characters; the expression above gives a conservative estimate
// on the maxiumum number of bytes required for LISTEN=nnn, including nul terminator,
// where nnn is a pid_t in decimal (i.e. one decimal digit is worth just over 3 bits).
char nbuf[bufsz];
// "DINIT_CS_FD=" - 12 bytes. (we -1 from sizeof(int) in account of sign bit).
- constexpr int csenvbufsz = ((CHAR_BIT * sizeof(int) - 1) / 3 + 2) + 12;
+ constexpr int csenvbufsz = 12 + ((CHAR_BIT * sizeof(int) - 1 + 2) / 3) + 1;
char csenvbuf[csenvbufsz];
int minfd = (socket_fd == -1) ? 3 : 4;
- // Move wpipefd/csfd to another fd if necessary
+ // Move wpipefd/csfd/notifyfd to another fd if necessary
+
+ // first allocate the forced notification fd, if specified:
+ if (force_notify_fd != -1) {
+ if (notify_fd != force_notify_fd) {
+ if (dup2(notify_fd, force_notify_fd) == -1) {
+ goto failure_out;
+ }
+ close(notify_fd);
+ notify_fd = force_notify_fd;
+ }
+ }
+
if (wpipefd < minfd) {
wpipefd = fcntl(wpipefd, F_DUPFD_CLOEXEC, minfd);
if (wpipefd == -1) goto failure_out;
if (csfd == -1) goto failure_out;
}
+ if (notify_fd < minfd && notify_fd != force_notify_fd) {
+ notify_fd = fcntl(notify_fd, F_DUPFD, minfd);
+ if (notify_fd == -1) goto failure_out;
+ }
+
+ // Set up notify-fd variable
+ if (notify_var != nullptr && *notify_var != 0) {
+ // We need to do an allocation: the variable name length, '=', and space for the value,
+ // and nul terminator:
+ int notify_var_len = strlen(notify_var);
+ int req_sz = notify_var_len + ((CHAR_BIT * sizeof(int) - 1 + 2) / 3) + 1;
+ char * var_str = (char *) malloc(req_sz);
+ if (var_str == nullptr) goto failure_out;
+ snprintf(var_str, req_sz, "%s=%d", notify_var, notify_fd);
+ if (putenv(var_str)) goto failure_out;
+ }
+
+ // Set up Systemd-style socket activation
if (socket_fd != -1) {
- // If we passing a pre-opened socket, it has to be fd number 3. (Thanks, systemd).
+ // If we passing a pre-opened socket, it has to be fd number 3. (Thanks, Systemd).
if (dup2(socket_fd, 3) == -1) goto failure_out;
- if (socket_fd != 3) {
- close(socket_fd);
- }
+ if (socket_fd != 3) close(socket_fd);
if (putenv(const_cast<char *>("LISTEN_FDS=1"))) goto failure_out;
snprintf(nbuf, bufsz, "LISTEN_PID=%jd", static_cast<intmax_t>(getpid()));
if (! on_console) {
// Re-set stdin, stdout, stderr
- close(0); close(1); close(2);
+ for (int i = 0; i < 3; i++) {
+ if (i != force_notify_fd) close(i);
+ }
- if (open("/dev/null", O_RDONLY) == 0) {
- // stdin = 0. That's what we should have; proceed with opening
- // stdout and stderr.
- if (open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR) != 1) {
- goto failure_out;
+ if (notify_fd == 0 || move_fd(open("/dev/null", O_RDONLY), 0) == 0) {
+ // stdin = 0. That's what we should have; proceed with opening stdout and stderr. We have to
+ // take care not to clobber the notify_fd.
+ if (notify_fd != 1) {
+ if (move_fd(open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR), 1) != 0) {
+ goto failure_out;
+ }
+ if (notify_fd != 2 && dup2(1, 2) != 2) {
+ goto failure_out;
+ }
}
- if (dup2(1, 2) != 2) {
+ else if (move_fd(open(logfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR), 2) != 0) {
goto failure_out;
}
}