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