Simplify daemon and always close file descriptors

Use daemon(3) to daemonise rather than writing our own code. Since we
use the mode of daemon(3) which closes the standard 3 file descriptors,
if the user requests logging to one of those we dup(2) the original
first. We must also take care to always close any newly-opened file
descriptors before execve(2) of the child.
This commit is contained in:
Laurence Withers 2012-10-02 19:55:54 +00:00
parent c55693ce1c
commit 639944b905
4 changed files with 64 additions and 178 deletions

View File

@ -1,8 +1,8 @@
/* daemonitor/src/daemonitor/200_log.c /* daemonitor/src/daemonitor/200_log.c
* *
* (c)2007, Laurence Withers, <l@lwithers.me.uk>. * Copyright: ©20072012, Laurence Withers.
* Released under the GNU GPLv3. See file COPYING or * Author: Laurence Withers <l@lwithers.me.uk>
* http://www.gnu.org/copyleft/gpl.html for details. * License: GPLv3
*/ */
@ -170,16 +170,25 @@ void log_func_syslog(int level, const char* fmt, ...)
/* log_destination_set() /* log_destination_set()
* Set `log_func' function pointer. * Set `log_func' function pointer.
*/ */
void log_destination_set(enum log_destination_t dest) void
log_destination_set(enum log_destination_t dest)
{ {
switch(dest) { switch(dest) {
case log_destination_stdout: case log_destination_stdout:
log_func_fd = STDOUT_FILENO; log_func_fd = dup(STDOUT_FILENO);
if(log_func_fd == -1) {
printf("dup() failed: %m.");
exit(1);
}
log_func = log_func_file; log_func = log_func_file;
break; break;
case log_destination_stderr: case log_destination_stderr:
log_func_fd = STDERR_FILENO; log_func_fd = dup(STDERR_FILENO);
if(log_func_fd == -1) {
fprintf(stderr, "dup() failed: %m.");
exit(1);
}
log_func = log_func_file; log_func = log_func_file;
break; break;
@ -210,6 +219,5 @@ int log_destination_set_file(const char* filename)
/* options for text editors /* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4 vim: expandtab:ts=4:sw=4
*/ */

View File

@ -1,160 +0,0 @@
/* daemonitor/src/daemonitor/400_daemon.c
*
* (c)2007, Laurence Withers, <l@lwithers.me.uk>.
* Released under the GNU GPLv3. See file COPYING or
* http://www.gnu.org/copyleft/gpl.html for details.
*/
/* DAEMON ROUTINES ********************************************************************************
*
* This file provides the daemonise() routine, which makes the process a daemon (fork() + setsid()
* so that the process lives in the background, also opens /dev/console). It also provides the
* close_file_descriptors() routine, which clears all file descriptors but 0, 1 and 2.
*/
/* PUBLIC API ************************************************************************************/
/* daemonise()
* Performs a fork() and setsid() so that the process lives in the background. It closes stdin,
* stdout and stderr and opens /dev/null and/or /dev/console instead, as appropriate. On error,
* this function exits the program.
*/
void daemonise(void);
/* close_file_descriptors()
* Closes all file descriptors but stdin, stdout and stderr. Should be called whether we are
* daemonising or not.
*/
void close_file_descriptors(void);
/* IMPLEMENTATION ********************************************************************************/
void daemonise(void)
{
int fd;
pid_t pid;
/* close stdin and reopen it as /dev/null */
TEMP_FAILURE_RETRY( close(STDIN_FILENO) );
fd = open("/dev/null", O_RDONLY);
switch(fd) {
case -1:
LOG(LOG_CRIT, "Could not open `/dev/null' (%m).");
exit(1);
case STDIN_FILENO:
break;
default:
LOG(LOG_CRIT, "Opened `/dev/null' for stdin, but got file descriptor %d instead.", fd);
exit(1);
}
/* close stdout and reopen it as /dev/console if possible, or /dev/null if not */
TEMP_FAILURE_RETRY( close(STDOUT_FILENO) );
fd = open("/dev/console", O_WRONLY | O_NOCTTY);
if(fd == -1) {
LOG(LOG_WARNING, "Could not open `/dev/console' for stdout (%m).");
fd = open("/dev/null", O_WRONLY);
}
switch(fd) {
case -1:
LOG(LOG_CRIT, "Could not open `/dev/null' (%m).");
exit(1);
case STDOUT_FILENO:
break;
default:
LOG(LOG_CRIT, "Opened `/dev/null' for stdout, but got file descriptor %d instead.", fd);
exit(1);
}
/* close stderr and reopen it as /dev/console if possible, or /dev/null if not */
TEMP_FAILURE_RETRY( close(STDERR_FILENO) );
fd = open("/dev/console", O_WRONLY | O_NOCTTY);
if(fd == -1) {
LOG(LOG_WARNING, "Could not open `/dev/console' for stderr (%m).");
/* force fd closed again, in case glibc kindly connected it to syslog for us */
TEMP_FAILURE_RETRY( close(STDERR_FILENO) );
fd = open("/dev/null", O_WRONLY);
}
switch(fd) {
case -1:
LOG(LOG_CRIT, "Could not open `/dev/null' (%m).");
exit(1);
case STDERR_FILENO:
break;
default:
LOG(LOG_CRIT, "Opened `/dev/null' for stderr, but got file descriptor %d instead.", fd);
exit(1);
}
/* daemonise */
pid = fork();
if(pid == (pid_t)-1) {
LOG(LOG_CRIT, "Could not fork daemonised process (%m).");
exit(1);
}
if(pid) {
/* parent */
_exit(0); /* don't call atexit() et al. in parent */
}
setsid();
LOG(LOG_NOTICE, "Daemonised, PID %lu.", (unsigned long)getpid());
}
/* close_file_descriptors()
* Adapted from libgslutil (GPL-3).
*/
void close_file_descriptors(void)
{
DIR* d;
struct dirent* de;
int i, end, dfd;
d = opendir("/proc/self/fd");
if(!d) {
/* backup method */
for(i = STDERR_FILENO + 1, end = getdtablesize(); i < end; ++i) {
close(i);
}
return;
}
dfd = dirfd(d);
while( (de = readdir(d)) ) {
if(de->d_name[0] == '.') continue;
i = strtol(de->d_name, 0, 10);
if(i == dfd) continue; /* skip file descriptor associated with `DIR* d' */
if(i <= STDERR_FILENO) continue; /* leave stdin, stdout, stderr alone */
TEMP_FAILURE_RETRY( close(i) );
}
closedir(d);
}
/* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4
*/

View File

@ -1,8 +1,8 @@
/* daemonitor/src/daemonitor/600_run_program.c /* daemonitor/src/daemonitor/600_run_program.c
* *
* (c)2007, Laurence Withers, <l@lwithers.me.uk>. * Copyright: ©20072012, Laurence Withers.
* Released under the GNU GPLv3. See file COPYING or * Author: Laurence Withers <l@lwithers.me.uk>
* http://www.gnu.org/copyleft/gpl.html for details. * License: GPLv3
*/ */
@ -45,8 +45,8 @@ volatile int sigterm_received = 0;
/* close_file_descriptors() /* close_file_descriptors()
* Closes all file descriptors but stdin, stdout and stderr. Should be called whether we are * Closes all file descriptors but stdin, stdout and stderr (which it leaves
* daemonising or not. * unchanged).
*/ */
void close_file_descriptors(void); void close_file_descriptors(void);
@ -56,6 +56,38 @@ void close_file_descriptors(void);
/* close_file_descriptors()
* Adapted from libgslutil (GPL-3).
*/
void
close_file_descriptors(void)
{
DIR* d;
struct dirent* de;
int i, end, dfd;
d = opendir("/proc/self/fd");
if(!d) {
/* backup method */
for(i = STDERR_FILENO + 1, end = getdtablesize(); i < end; ++i) {
close(i);
}
return;
}
dfd = dirfd(d);
while( (de = readdir(d)) ) {
if(de->d_name[0] == '.') continue;
i = strtol(de->d_name, 0, 10);
if(i == dfd) continue; /* skip file descriptor associated with `DIR* d' */
if(i <= STDERR_FILENO) continue; /* leave stdin, stdout, stderr alone */
TEMP_FAILURE_RETRY( close(i) );
}
closedir(d);
}
/* crash() /* crash()
* Crash the program. This is better than just calling exit() because it doesn't clean up. We * Crash the program. This is better than just calling exit() because it doesn't clean up. We
* should leave our PID file lying around to look like we crashed. This function is paranoid. * should leave our PID file lying around to look like we crashed. This function is paranoid.
@ -113,6 +145,8 @@ void run_program(const char* exe_path, char** argv)
/* child -- execute new process */ /* child -- execute new process */
if(pid == 0) { if(pid == 0) {
close_file_descriptors();
/* clear the child's signal mask */ /* clear the child's signal mask */
sigemptyset(&ss); sigemptyset(&ss);
sigprocmask(SIG_SETMASK, &ss, 0); sigprocmask(SIG_SETMASK, &ss, 0);
@ -203,6 +237,5 @@ void run_program(const char* exe_path, char** argv)
/* options for text editors /* options for text editors
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
vim: expandtab:ts=4:sw=4 vim: expandtab:ts=4:sw=4
*/ */

View File

@ -79,7 +79,7 @@ int main(int argc, char* argv[])
{ {
char* endp; char* endp;
const char* log_argument = 0, * pidfile_argument = 0, * restart_argument = 0; const char* log_argument = 0, * pidfile_argument = 0, * restart_argument = 0;
int daemon = 0, restart_interval = DEFAULT_RESTART_INTERVAL; int do_daemon = 0, restart_interval = DEFAULT_RESTART_INTERVAL;
/* safety */ /* safety */
close_file_descriptors(); /* leaves stdout etc. open */ close_file_descriptors(); /* leaves stdout etc. open */
@ -91,7 +91,7 @@ int main(int argc, char* argv[])
case '?': LOG_ANYWHERE("Invalid commandline options."); return 1; case '?': LOG_ANYWHERE("Invalid commandline options."); return 1;
case 'h': usage(); return 1; case 'h': usage(); return 1;
case 'V': fputs("daemonitor " VERSION "\n", stderr); return 1; case 'V': fputs("daemonitor " VERSION "\n", stderr); return 1;
case 'd': ++daemon; break; case 'd': ++do_daemon; break;
case 'l': log_argument = optarg; break; case 'l': log_argument = optarg; break;
case 'p': pidfile_argument = optarg; break; case 'p': pidfile_argument = optarg; break;
case 'R': restart_argument = optarg; break; case 'R': restart_argument = optarg; break;
@ -136,7 +136,12 @@ int main(int argc, char* argv[])
if(load_child_environ()) return 1; if(load_child_environ()) return 1;
/* daemonise? */ /* daemonise? */
if(daemon) daemonise(); if(do_daemon) {
if(daemon(0, 0)) {
LOG(LOG_CRIT, "daemon() failed: %m.");
return 1;
}
}
/* write a PID file */ /* write a PID file */
if(pidfile_argument) { if(pidfile_argument) {