/* * This file Copyright (C) 2015 Mnemosyne LLC * * It may be used under the GNU GPL versions 2 or 3 * or any future license endorsed by Mnemosyne LLC. * * $Id$ */ #include #include #include #include #include /* abort (), daemon (), exit () */ #include /* open () */ #include /* fork (), setsid (), chdir (), dup2 (), close (), pipe () */ #include #include #include #include "daemon.h" /*** **** ***/ static const dtr_callbacks * callbacks = NULL; static void * callback_arg = NULL; static int signal_pipe[2]; /*** **** ***/ static void set_system_error (tr_error ** error, int code, const char * message) { tr_error_set (error, code, "%s (%d): %s", message, code, tr_strerror (code)); } /*** **** ***/ static void handle_signal (int sig) { switch (sig) { case SIGHUP: callbacks->on_reconfigure (callback_arg); break; case SIGINT: case SIGTERM: callbacks->on_stop (callback_arg); break; default: assert (!"Unexpected signal"); } } static void send_signal_to_pipe (int sig) { const int old_errno = errno; if (write (signal_pipe[1], &sig, sizeof (sig)) == -1) abort (); errno = old_errno; } static void * signal_handler_thread_main (void * arg UNUSED) { int sig; while (read (signal_pipe[0], &sig, sizeof (sig)) == sizeof (sig) && sig != 0) handle_signal (sig); return NULL; } static bool create_signal_pipe (tr_error ** error) { if (pipe (signal_pipe) == -1) { set_system_error (error, errno, "pipe() failed"); return false; } return true; } static void destroy_signal_pipe (void) { close (signal_pipe[0]); close (signal_pipe[1]); } static bool create_signal_handler_thread (pthread_t * thread, tr_error ** error) { if (!create_signal_pipe (error)) return false; if ((errno = pthread_create (thread, NULL, &signal_handler_thread_main, NULL)) != 0) { set_system_error (error, errno, "pthread_create() failed"); destroy_signal_pipe (); return false; } return true; } static void destroy_signal_handler_thread (pthread_t thread) { send_signal_to_pipe (0); pthread_join (thread, NULL); destroy_signal_pipe (); } static bool setup_signal_handler (int sig, tr_error ** error) { assert (sig != 0); if (signal (sig, &send_signal_to_pipe) == SIG_ERR) { set_system_error (error, errno, "signal() failed"); return false; } return true; } /*** **** ***/ bool dtr_daemon (const dtr_callbacks * cb, void * cb_arg, bool foreground, int * exit_code, tr_error ** error) { callbacks = cb; callback_arg = cb_arg; *exit_code = 1; if (!foreground) { #if defined (HAVE_DAEMON) && !defined (__APPLE__) && !defined (__UCLIBC__) if (daemon (true, false) == -1) { set_system_error (error, errno, "daemon() failed"); return false; } #else /* this is loosely based off of glibc's daemon () implementation * http://sourceware.org/git/?p=glibc.git;a=blob_plain;f=misc/daemon.c */ switch (fork ()) { case -1: set_system_error (error, errno, "fork() failed"); return false; case 0: break; default: *exit_code = 0; return true; } if (setsid () == -1) { set_system_error (error, errno, "setsid() failed"); return false; } /* if (chdir ("/") == -1) { set_system_error (error, errno, "chdir() failed"); return false; } */ { const int fd = open ("/dev/null", O_RDWR, 0); dup2 (fd, STDIN_FILENO); dup2 (fd, STDOUT_FILENO); dup2 (fd, STDERR_FILENO); close (fd); } #endif } pthread_t signal_thread; if (!create_signal_handler_thread (&signal_thread, error)) return false; if (!setup_signal_handler (SIGINT, error) || !setup_signal_handler (SIGTERM, error) || !setup_signal_handler (SIGHUP, error)) { destroy_signal_handler_thread (signal_thread); return false; } *exit_code = cb->on_start (cb_arg, foreground); destroy_signal_handler_thread (signal_thread); return true; }