/**
* slibc — Yet another C library
* Copyright © 2015, 2016 Mattias Andrée (m@maandree.se)
*
* This program 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 3 of the License, or
* (at your option) any later version. As a special exception for this
* file, you may also redistribute it and/or modify it under the terms
* of the Expat License.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/resource.h>
/**
* The process's environment variables.
*/
extern char** environ;
/**
* The pidfile created by `daemonise`.
*/
static char* __pidfile = NULL;
/**
* Wrapper for `dup` create a new file descriptor
* with a number not less than 3.
*
* @param old The file descriptor to duplicate.
* @return The new file descriptor, -1 on error.
*
* @throws Any error specified for dup(3).
*/
static int dup_at_least_3(int old)
{
int intermediary[3];
int i = 0, saved_errno;
do
{
if (old = dup(old), old == -1)
goto fail;
assert(i < 3);
if (i >= 3)
abort();
intermediary[i++] = old;
}
while (old < 3);
i--;
fail:
saved_errno = errno;
while (i--)
close(intermediary[i]);
errno = saved_errno;
return old;
}
/**
* Daemonise the process. This means to:
*
* - close all file descritors except for those to
* stdin, stdout, and stderr,
*
* - remove all custom signal handlers, and apply
* the default handlers.
*
* - unblock all signals,
*
* - remove all malformatted entries in the
* environment (this not containing an '=',)
*
* - set the umask to zero, to be ensure that all
* file permissions are set as specified,
*
* - change directory to '/', to ensure that the
* process does not block any mountpoint from being
* unmounted,
*
* - fork to become a background process,
*
* - temporarily become a session leader to ensure
* that the process does not have a controlling
* terminal.
*
* - fork again to become a child of the daemon
* supervisor, (subreaper could however be in the
* say, so one should not merely rely on this when
* writing a daemon supervisor,) (the first child
* shall exit after this,)
*
* - create, exclusively, a PID file to stop the daemon
* to be being run twice concurrently, and to let
* the daemon supervicer know the process ID of the
* daemon,
*
* - redirect stdin and stdout to /dev/null,
* as well as stderr if it is currently directed
* to a terminal, and
*
* - exit in the original process to let the daemon
* supervisor know that the daemon has been
* initialised.
*
* Before calling this function, you should remove any
* environment variable that could negatively impact
* the runtime of the process.
*
* After calling this function, you should remove
* unnecessary privileges.
*
* Do not try do continue the process in failure unless
* you make sure to only do this in the original process.
* But not that things will not necessarily be as when
* you make the function call. The process can have become
* partially deamonised.
*
* If $XDG_RUNTIME_DIR is set and is not empty, its value
* should be used instead of /run for the runtime data-files
* directory, in which the PID file is stored.
*
* This is a slibc extension.
*
* @etymology (Daemonise) the process!
*
* @param name The name of the daemon. Use a hardcoded value,
* not the process name. Must not be `NULL`.
* @param flags Flags to modify the behaviour of the function.
* A bitwise OR combination of the constants:
* - `DAEMONISE_NO_CLOSE`
* - `DAEMONISE_NO_SIG_DFL`
* - `DAEMONISE_KEEP_SIGMASK`
* - `DAEMONISE_KEEP_ENVIRON`
* - `DAEMONISE_KEEP_UMASK`
* - `DAEMONISE_NO_PID_FILE`
* - `DAEMONISE_KEEP_STDERR`
* - `DAEMONISE_CLOSE_STDERR`
* - `DAEMONISE_KEEP_STDIN`
* - `DAEMONISE_KEEP_STDOUT`
* - `DAEMONISE_KEEP_FDS`
* - `DAEMONISE_NEW_PID`
* @param ... Enabled if `DAEMONISE_KEEP_FDS` is used,
* do not add anything if `DAEMONISE_KEEP_FDS`
* is unused. This is a `-1`-terminated list
* of file descritors to keep open. 0, 1, and 2
* are implied by `DAEMONISE_KEEP_STDIN`,
* `DAEMONISE_KEEP_STDOUT`, and `DAEMONISE_KEEP_STDERR`,
* respectively. All arguments are of type `int`.
* @return Zero on success, -1 on error.
*
* @throws EEXIST The PID file already exists on the system.
* Unless your daemon supervisor removs old
* PID files, this could mean that the daemon
* has exited without removing the PID file.
* @throws EINVAL `flags` contains an unsupported bit, both
* `DAEMONISE_KEEP_STDERR` and `DAEMONISE_CLOSE_STDERR`
* are set, both `DAEMONISE_NO_PID_FILE` and
* `DAEMONISE_NEW_PID`, or both `DAEMONISE_CLOSE_STDERR`
* and `DAEMONISE_KEEP_FDS` are set whilst `2` is
* in the list of file descriptor not to close.
* @throws Any error specified for signal(3).
* @throws Any error specified for sigemptyset(3).
* @throws Any error specified for sigprocmask(3).
* @throws Any error specified for chdir(3).
* @throws Any error specified for pipe(3).
* @throws Any error specified for dup(3).
* @throws Any error specified for dup2(3).
* @throws Any error specified for fork(3).
* @throws Any error specified for setsid(3).
* @throws Any error specified for open(3).
* @throws Any error specified for malloc(3).
*
* @since Always.
*/
int daemonise(const char* name, int flags, ...)
{
#define t(...) do { if (__VA_ARGS__) goto fail; } while (0)
struct rlimit rlimit;
int pipe_rw[2] = { -1, -1 };
sigset_t set;
char** r;
char** w;
char* run;
int i, closeerr, fd = -1;
char* keep = NULL;
int keepmax = 0;
pid_t pid;
va_list args;
int saved_errno;
/* Validate flags. */
if (flags & (int)~(2048L * 2 - 1))
return errno = EINVAL, -1;
if ((flags & DAEMONISE_KEEP_STDERR) && (flags & DAEMONISE_CLOSE_STDERR))
return errno = EINVAL, -1;
if ((flags & DAEMONISE_NO_PID_FILE) && (flags & DAEMONISE_NEW_PID))
return errno = EINVAL, -1;
/* Find out which file descriptors not too close. */
if (flags & DAEMONISE_KEEP_FDS)
{
va_start(args, flags);
while ((fd = va_arg(args, int)) >= 0)
if ((fd > 2) && (keepmax < fd))
keepmax = fd;
fd = -1;
va_end(args);
keep = calloc((size_t)keepmax + 1, sizeof(char));
t (keep == NULL);
va_start(args, flags);
while ((fd = va_arg(args, int)) >= 0)
switch (fd)
{
case 0: flags |= DAEMONISE_KEEP_STDIN; break;
case 1: flags |= DAEMONISE_KEEP_STDOUT; break;
case 2: flags |= DAEMONISE_KEEP_STDERR;
if (flags & DAEMONISE_CLOSE_STDERR)
return free(keep), errno = EINVAL, -1;
break;
default:
keep[fd] = 1;
break;
}
fd = -1;
va_end(args);
}
/* We assume that the maximum file descriptor is not extremely large.
* We also assume the number of file descriptors too keep is very small,
* but this does not affect us. */
/* Close all files except stdin, stdout, and stderr. */
if ((flags & DAEMONISE_NO_CLOSE) == 0)
{
if (getrlimit(RLIMIT_NOFILE, &rlimit))
rlimit.rlim_cur = 4 << 10;
for (i = 3; (rlim_t)i < rlimit.rlim_cur; i++)
/* File descriptors with numbers above and including
* `rlimit.rlim_cur` cannot be created. They cause EBADF. */
if ((i > keepmax) || (keep[i] == 0))
close(i);
}
free(keep), keep = NULL;
/* Reset all signal handlers. */
if ((flags & DAEMONISE_NO_SIG_DFL) == 0)
for (i = 1; i < _NSIG; i++)
if (signal(i, SIG_DFL) == SIG_ERR)
t (errno != EINVAL);
/* Set signal mask. */
if ((flags & DAEMONISE_KEEP_SIGMASK) == 0)
{
t (sigemptyset(&set));
t (sigprocmask(SIG_SETMASK, &set, NULL));
}
/* Remove malformatted environment entires. */
if (((flags & DAEMONISE_KEEP_ENVIRON) == 0) && (environ != NULL))
{
for (r = w = environ; *r; r++)
if (strchr(*r, '=')) /* It happens that this is not the case! (Thank you PAM!) */
*w++ = *r;
*w = NULL;
}
/* Zero umask. */
if ((flags & DAEMONISE_KEEP_UMASK) == 0)
umask(0);
/* Change current working directory to '/'. */
t (chdir("/"));
/* Create a channel for letting the original process know when to exit. */
if (pipe(pipe_rw))
t ((pipe_rw[0] = pipe_rw[1] = -1));
t (fd = dup_at_least_3(pipe_rw[0]), fd == -1);
close(pipe_rw[0]);
pipe_rw[0] = fd;
t (fd = dup_at_least_3(pipe_rw[1]), fd == -1);
close(pipe_rw[1]);
pipe_rw[1] = fd;
/* Become a background process. */
t (pid = fork(), pid == -1);
close(pipe_rw[!!pid]), pipe_rw[!!pid] = 1;
if (pid)
exit(read(pipe_rw[0], &fd, (size_t)1) <= 0);
/* Temporarily become session leader. */
t (setsid() == -1);
/* Fork again. */
t (pid = fork(), pid == -1);
if (pid > 0)
exit(0);
/* Create PID file. */
if (flags & DAEMONISE_NO_PID_FILE)
goto no_pid_file;
run = getenv("XDG_RUNTIME_DIR");
if (run && *run)
{
__pidfile = malloc(sizeof("/.pid") + (strlen(run) + strlen(name)) * sizeof(char));
t (__pidfile == NULL);
stpcpy(stpcpy(stpcpy(stpcpy(__pidfile, run), "/"), name), ".pid");
}
else
{
__pidfile = malloc(sizeof("/run/.pid") + strlen(name) * sizeof(char));
t (__pidfile == NULL);
stpcpy(stpcpy(stpcpy(__pidfile, "/run/"), name), ".pid");
}
fd = open(__pidfile, O_WRONLY | O_CREAT | ((flags & DAEMONISE_NEW_PID) ? 0 : O_EXCL), 0644);
if (fd == -1)
{
saved_errno = errno;
free(__pidfile), __pidfile = NULL;
errno = saved_errno;
goto fail;
}
pid = getpid();
t (dprintf(fd, "%lli\n", (long long int)pid) < 0);
t (close(fd) && (errno != EINTR));
no_pid_file:
/* Redirect to '/dev/null'. */
if (flags & DAEMONISE_KEEP_STDERR)
closeerr = 0;
else if (flags & DAEMONISE_CLOSE_STDERR)
closeerr = 1;
else
closeerr = (isatty(2) || (errno == EBADF));
t (fd = open("/dev/null", O_RDWR), fd == -1);
if ((flags & DAEMONISE_KEEP_STDIN) == 0) if (fd != 0) close(0);
if ((flags & DAEMONISE_KEEP_STDOUT) == 0) if (fd != 1) close(1);
if (closeerr) if (fd != 2) close(2);
if ((flags & DAEMONISE_KEEP_STDIN) == 0) t (dup2(fd, 0) == -1);
if ((flags & DAEMONISE_KEEP_STDOUT) == 0) t (dup2(fd, 1) == -1);
if (closeerr) t (dup2(fd, 2) == -1);
if (fd > 2)
close(fd);
fd = -1;
/* We are done! Let the original process exit. */
if ((write(pipe_rw[1], &fd, (size_t)1) <= 0) ||
(close(pipe_rw[1]) && (errno != EINTR)))
{
if (flags & DAEMONISE_KEEP_STDERR)
return -1;
undaemonise();
abort(); /* Do not overcomplicate things, just abort in this unlikely event. */
}
return 0;
fail:
saved_errno = errno;
if (pipe_rw[0] >= 0) close(pipe_rw[0]);
if (pipe_rw[1] >= 0) close(pipe_rw[1]);
if (fd >= 0) close(fd);
free(keep);
errno = saved_errno;
return -1;
}
/**
* Remove the PID file created by `daemonise`. This shall
* always be called before exiting after calling `daemonise`,
* even if it failed.
*
* This is a slibc extension.
*
* @etymology (Un)link PID file created by `(daemonise)`!
*
* @return Zero on success, -1 on error.
*
* @throws Any error specified for unlink(3).
*
* @since Always.
*/
int undaemonise(void)
{
int r, saved_errno;
if (__pidfile == NULL)
return 0;
r = unlink(__pidfile);
saved_errno = errno;
free(__pidfile), __pidfile = NULL;
errno = saved_errno;
return r;
}