Browse Source
Dumb programs which have a --daemon option call fork() early. This is terrible UX since startup errors get lost: the program exits with "success" immediately then you discover via the logs that it didn't start at all. However, forking late introduced a heap of problems with changing pids. Instead, fork early but keep stderr and the parent around: if we fail early on, the parent fails with us. We release our parent with an explicit action just before the main loop. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>pull/2938/head
committed by
Christian Decker
13 changed files with 88 additions and 270 deletions
@ -1 +0,0 @@ |
|||
../../licenses/BSD-MIT |
@ -1,54 +0,0 @@ |
|||
#include "config.h" |
|||
#include <stdio.h> |
|||
#include <string.h> |
|||
|
|||
/** |
|||
* daemonize - routine to turn a process into a well-behaved daemon. |
|||
* |
|||
* Daemons should detach themselves thoroughly from the process which launched |
|||
* them, and not prevent any filesystems from being unmounted. daemonize() |
|||
* helps with the process. |
|||
* |
|||
* Example: |
|||
* #include <ccan/daemonize/daemonize.h> |
|||
* #include <ccan/str/str.h> |
|||
* #include <err.h> |
|||
* #include <unistd.h> |
|||
* #include <stdlib.h> |
|||
* |
|||
* static void usage(const char *name) |
|||
* { |
|||
* errx(1, "Usage: %s [--daemonize]\n", name); |
|||
* } |
|||
* |
|||
* // Wait for a minute, possibly as a daemon. |
|||
* int main(int argc, char *argv[]) |
|||
* { |
|||
* if (argc != 1) { |
|||
* if (argc == 2 && streq(argv[1], "--daemonize")) { |
|||
* if (!daemonize()) |
|||
* err(1, "Failed to become daemon"); |
|||
* } else |
|||
* usage(argv[1]); |
|||
* } |
|||
* sleep(60); |
|||
* exit(0); |
|||
* } |
|||
* |
|||
* License: BSD-MIT |
|||
*/ |
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
if (argc != 2) |
|||
return 1; |
|||
|
|||
if (strcmp(argv[1], "depends") == 0) { |
|||
return 0; |
|||
} |
|||
|
|||
if (strcmp(argv[1], "libs") == 0) { |
|||
return 0; |
|||
} |
|||
|
|||
return 1; |
|||
} |
@ -1,45 +0,0 @@ |
|||
/* Licensed under BSD-MIT - see LICENSE file for details */ |
|||
#include <ccan/daemonize/daemonize.h> |
|||
#include <unistd.h> |
|||
#include <stdlib.h> |
|||
#include <sys/types.h> |
|||
#include <sys/stat.h> |
|||
#include <fcntl.h> |
|||
|
|||
/* This code is based on Stevens Advanced Programming in the UNIX
|
|||
* Environment. */ |
|||
bool daemonize(void) |
|||
{ |
|||
pid_t pid; |
|||
|
|||
/* Separate from our parent via fork, so init inherits us. */ |
|||
if ((pid = fork()) < 0) |
|||
return false; |
|||
/* use _exit() to avoid triggering atexit() processing */ |
|||
if (pid != 0) |
|||
_exit(0); |
|||
|
|||
/* Don't hold files open. */ |
|||
close(STDIN_FILENO); |
|||
close(STDOUT_FILENO); |
|||
close(STDERR_FILENO); |
|||
|
|||
/* Many routines write to stderr; that can cause chaos if used
|
|||
* for something else, so set it here. */ |
|||
if (open("/dev/null", O_WRONLY) != 0) |
|||
return false; |
|||
if (dup2(0, STDERR_FILENO) != STDERR_FILENO) |
|||
return false; |
|||
close(0); |
|||
|
|||
/* Session leader so ^C doesn't whack us. */ |
|||
if (setsid() == (pid_t)-1) |
|||
return false; |
|||
/* Move off any mount points we might be in. */ |
|||
if (chdir("/") != 0) |
|||
return false; |
|||
|
|||
/* Discard our parent's old-fashioned umask prejudices. */ |
|||
umask(0); |
|||
return true; |
|||
} |
@ -1,21 +0,0 @@ |
|||
/* Licensed under BSD-MIT - see LICENSE file for details */ |
|||
#ifndef CCAN_DAEMONIZE_H |
|||
#define CCAN_DAEMONIZE_H |
|||
#include <stdbool.h> |
|||
|
|||
/**
|
|||
* daemonize - turn this process into a daemon. |
|||
* |
|||
* This routine forks us off to become a daemon. It returns false on failure |
|||
* (eg. fork(), chdir or open failed) and sets errno. |
|||
* |
|||
* Side effects for programmers to be aware of: |
|||
* - PID changes (our parent exits, we become child of init) |
|||
* - stdin and stdout file descriptors are closed |
|||
* - stderr is reopened to /dev/null so you don't reuse it |
|||
* - Current working directory changes to / |
|||
* - Umask is set to 0. |
|||
*/ |
|||
bool daemonize(void); |
|||
|
|||
#endif /* CCAN_DAEMONIZE_H */ |
@ -1,76 +0,0 @@ |
|||
#include <ccan/daemonize/daemonize.h> |
|||
#include <ccan/daemonize/daemonize.c> |
|||
#include <ccan/tap/tap.h> |
|||
#include <stdlib.h> |
|||
#include <unistd.h> |
|||
#include <err.h> |
|||
#include <errno.h> |
|||
#include <string.h> |
|||
|
|||
struct child_data { |
|||
pid_t pid; |
|||
pid_t ppid; |
|||
bool in_root_dir; |
|||
int read_from_stdin, write_to_stdout, write_to_stderr; |
|||
}; |
|||
|
|||
int main(int argc, char *argv[]) |
|||
{ |
|||
int fds[2]; |
|||
struct child_data daemonized; |
|||
pid_t pid; |
|||
|
|||
plan_tests(5); |
|||
|
|||
if (pipe(fds) != 0) |
|||
err(1, "Failed pipe"); |
|||
|
|||
/* Since daemonize forks and parent exits, we need to fork
|
|||
* that parent. */ |
|||
pid = fork(); |
|||
if (pid == -1) |
|||
err(1, "Failed fork"); |
|||
|
|||
if (pid == 0) { |
|||
char buffer[2]; |
|||
pid = getpid(); |
|||
daemonize(); |
|||
/* Keep valgrind happy about uninitialized bytes. */ |
|||
memset(&daemonized, 0, sizeof(daemonized)); |
|||
daemonized.pid = getpid(); |
|||
daemonized.in_root_dir = (getcwd(buffer, 2) != NULL); |
|||
daemonized.read_from_stdin |
|||
= read(STDIN_FILENO, buffer, 1) == -1 ? errno : 0; |
|||
daemonized.write_to_stdout |
|||
= write(STDOUT_FILENO, buffer, 1) == -1 ? errno : 0; |
|||
if (write(STDERR_FILENO, buffer, 1) != 1) { |
|||
daemonized.write_to_stderr = errno; |
|||
if (daemonized.write_to_stderr == 0) |
|||
daemonized.write_to_stderr = -1; |
|||
} else |
|||
daemonized.write_to_stderr = 0; |
|||
|
|||
/* Make sure parent exits. */ |
|||
while (getppid() == pid) |
|||
sleep(1); |
|||
daemonized.ppid = getppid(); |
|||
if (write(fds[1], &daemonized, sizeof(daemonized)) |
|||
!= sizeof(daemonized)) |
|||
exit(1); |
|||
exit(0); |
|||
} |
|||
|
|||
if (read(fds[0], &daemonized, sizeof(daemonized)) != sizeof(daemonized)) |
|||
err(1, "Failed read"); |
|||
|
|||
ok1(daemonized.pid != pid); |
|||
#if 0 /* Believe it or not, this fails under Ubuntu 13.10 (Upstart) */
|
|||
ok1(daemonized.ppid == 1); |
|||
#endif |
|||
ok1(daemonized.in_root_dir); |
|||
ok1(daemonized.read_from_stdin == EBADF); |
|||
ok1(daemonized.write_to_stdout == EBADF); |
|||
ok1(daemonized.write_to_stderr == 0); |
|||
|
|||
return exit_status(); |
|||
} |
Loading…
Reference in new issue