From a079af9ed5e0891edae9d789b618c31f1e61789f Mon Sep 17 00:00:00 2001 From: Mike Gelfand Date: Sat, 4 Apr 2015 17:43:56 +0000 Subject: [PATCH] #5910: Run daemon as a service on Windows Factor out demonization implementation to platform-specific files. Implement daemonization on Windows using service API. Improve *NIX implementation by handling signals asynchronously to prevent potential issues of running complex code inside the handler. --- daemon/CMakeLists.txt | 14 +- daemon/daemon-posix.c | 236 +++++++++++++++++++ daemon/daemon-win32.c | 273 ++++++++++++++++++++++ daemon/daemon.c | 523 ++++++++++++++++++++---------------------- daemon/daemon.h | 29 +++ 5 files changed, 800 insertions(+), 275 deletions(-) create mode 100644 daemon/daemon-posix.c create mode 100644 daemon/daemon-win32.c create mode 100644 daemon/daemon.h diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 3ce7f0238..2325ef435 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -18,12 +18,24 @@ include_directories( ${EVENT2_INCLUDE_DIRS} ) -add_executable(${TR_NAME}-daemon +set(${PROJECT_NAME}_SOURCES daemon.c watch.c +) + +if(WIN32) + list(APPEND ${PROJECT_NAME}_SOURCES daemon-win32.c) +else() + list(APPEND ${PROJECT_NAME}_SOURCES daemon-posix.c) +endif() + +set(${PROJECT_NAME}_HEADERS + daemon.h watch.h ) +add_executable(${TR_NAME}-daemon ${${PROJECT_NAME}_SOURCES} ${${PROJECT_NAME}_HEADERS}) + set_target_properties(${TR_NAME}-daemon PROPERTIES COMPILE_FLAGS "${SYSTEMD_DAEMON_CFLAGS}" LINK_FLAGS "${SYSTEMD_DAEMON_LDFLAGS}" diff --git a/daemon/daemon-posix.c b/daemon/daemon-posix.c new file mode 100644 index 000000000..46b183ac7 --- /dev/null +++ b/daemon/daemon-posix.c @@ -0,0 +1,236 @@ +/* + * 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 /* 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; + + write (signal_pipe[1], &sig, sizeof (sig)); + + 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 () +{ + 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 (__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; +} diff --git a/daemon/daemon-win32.c b/daemon/daemon-win32.c new file mode 100644 index 000000000..4b66fce0c --- /dev/null +++ b/daemon/daemon-win32.c @@ -0,0 +1,273 @@ +/* + * 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 /* _beginthreadex () */ + +#include + +#include +#include +#include +#include + +#include "daemon.h" + +/*** +**** +***/ + +#ifndef SERVICE_ACCEPT_PRESHUTDOWN + #define SERVICE_ACCEPT_PRESHUTDOWN 0x00000100 +#endif +#ifndef SERVICE_CONTROL_PRESHUTDOWN + #define SERVICE_CONTROL_PRESHUTDOWN 0x0000000F +#endif + +static const dtr_callbacks * callbacks = NULL; +static void * callback_arg = NULL; + +static const LPCWSTR service_name = L"TransmissionDaemon"; + +static SERVICE_STATUS_HANDLE status_handle = NULL; +static DWORD current_state = SERVICE_STOPPED; +static HANDLE service_thread = NULL; +static HANDLE service_stop_thread = NULL; + +/*** +**** +***/ + +static void +set_system_error (tr_error ** error, + DWORD code, + const char * message) +{ + char * const system_message = tr_win32_format_message (code); + tr_error_set (error, code, "%s (0x%08lx): %s", message, code, system_message); + tr_free (system_message); +} + +static void +do_log_system_error (const char * file, + int line, + tr_log_level level, + DWORD code, + const char * message) +{ + char * const system_message = tr_win32_format_message (code); + tr_logAddMessage (file, line, level, "[dtr_daemon] %s (0x%08lx): %s", message, code, system_message); + tr_free (system_message); +} + +#define log_system_error(level, code, message) \ + do \ + { \ + const DWORD local_code = (code); \ + if (tr_logLevelIsActive ((level))) \ + do_log_system_error (__FILE__, __LINE__, (level), local_code, (message)); \ + } \ + while (0) + +/*** +**** +***/ + +static BOOL WINAPI +handle_console_ctrl (DWORD control_type) +{ + (void) control_type; + + callbacks->on_stop (callback_arg); + return TRUE; +} + +static void +update_service_status (DWORD new_state, + DWORD win32_exit_code, + DWORD service_specific_exit_code, + DWORD check_point, + DWORD wait_hint) +{ + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + status.dwCurrentState = new_state; + status.dwControlsAccepted = new_state != SERVICE_RUNNING ? 0 : + SERVICE_ACCEPT_PRESHUTDOWN | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PARAMCHANGE; + status.dwWin32ExitCode = service_specific_exit_code == 0 ? win32_exit_code : ERROR_SERVICE_SPECIFIC_ERROR; + status.dwServiceSpecificExitCode = service_specific_exit_code; + status.dwCheckPoint = check_point; + status.dwWaitHint = wait_hint; + + if (SetServiceStatus (status_handle, &status)) + current_state = new_state; + else + log_system_error (TR_LOG_DEBUG, GetLastError (), "SetServiceStatus() failed"); +} + +static unsigned int __stdcall +service_stop_thread_main (void * param) +{ + callbacks->on_stop (callback_arg); + + const DWORD sleep_time = 500; + DWORD wait_time = (DWORD) (UINT_PTR) param; + for (DWORD checkpoint = 2; WaitForSingleObject (service_thread, sleep_time) == WAIT_TIMEOUT; ++checkpoint) + { + wait_time = wait_time >= sleep_time ? wait_time - sleep_time : 0; + update_service_status (SERVICE_STOP_PENDING, NO_ERROR, 0, checkpoint, MAX (wait_time, sleep_time * 2)); + } + + return 0; +} + +static void +stop_service (void) +{ + if (service_stop_thread != NULL) + return; + + const DWORD wait_time = 30 * 1000; + + update_service_status (SERVICE_STOP_PENDING, NO_ERROR, 0, 1, wait_time); + + service_stop_thread = (HANDLE) _beginthreadex (NULL, 0, &service_stop_thread_main, + (LPVOID) (UINT_PTR) wait_time, 0, NULL); + if (service_stop_thread == NULL) + { + log_system_error (TR_LOG_DEBUG, GetLastError (), "_beginthreadex() failed, trying to stop synchronously"); + service_stop_thread_main ((LPVOID) (UINT_PTR) wait_time); + } +} + +static DWORD +handle_service_ctrl (DWORD control_code, + DWORD event_type, + LPVOID event_data, + LPVOID context) +{ + (void) event_type; + (void) event_data; + (void) context; + + switch (control_code) + { + case SERVICE_CONTROL_PRESHUTDOWN: + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + stop_service (); + return NO_ERROR; + + case SERVICE_CONTROL_PARAMCHANGE: + callbacks->on_reconfigure (callback_arg); + return NO_ERROR; + + case SERVICE_CONTROL_INTERROGATE: + update_service_status (current_state, NO_ERROR, 0, 0, 0); + return NO_ERROR; + } + + return ERROR_CALL_NOT_IMPLEMENTED; +} + +static unsigned int __stdcall +service_thread_main (void * context) +{ + (void) context; + + return callbacks->on_start (callback_arg, false); +} + +static VOID WINAPI +service_main (DWORD argc, + LPWSTR * argv) +{ + (void) argc; + (void) argv; + + status_handle = RegisterServiceCtrlHandlerExW (service_name, &handle_service_ctrl, NULL); + if (status_handle == NULL) + { + log_system_error (TR_LOG_ERROR, GetLastError (), "RegisterServiceCtrlHandlerEx() failed"); + return; + } + + update_service_status (SERVICE_START_PENDING, NO_ERROR, 0, 1, 1000); + + service_thread = (HANDLE) _beginthreadex (NULL, 0, &service_thread_main, NULL, 0, NULL); + if (service_thread == NULL) + { + log_system_error (TR_LOG_ERROR, GetLastError (), "_beginthreadex() failed"); + return; + } + + update_service_status (SERVICE_RUNNING, NO_ERROR, 0, 0, 0); + + if (WaitForSingleObject (service_thread, INFINITE) != WAIT_OBJECT_0) + log_system_error (TR_LOG_ERROR, GetLastError (), "WaitForSingleObject() failed"); + + if (service_stop_thread != NULL) + { + WaitForSingleObject (service_stop_thread, INFINITE); + CloseHandle (service_stop_thread); + } + + DWORD exit_code; + if (!GetExitCodeThread (service_thread, &exit_code)) + exit_code = 1; + + CloseHandle (service_thread); + + update_service_status (SERVICE_STOPPED, NO_ERROR, exit_code, 0, 0); +} + +/*** +**** +***/ + +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 (!SetConsoleCtrlHandler (&handle_console_ctrl, TRUE)) + { + set_system_error (error, GetLastError (), "SetConsoleCtrlHandler() failed"); + return false; + } + + *exit_code = cb->on_start (cb_arg, true); + } + else + { + const SERVICE_TABLE_ENTRY service_table[] = + { + { (LPWSTR) service_name, &service_main }, + { NULL, NULL } + }; + + if (!StartServiceCtrlDispatcherW (service_table)) + { + set_system_error (error, GetLastError (), "StartServiceCtrlDispatcher() failed"); + return false; + } + + *exit_code = 0; + } + + return true; +} diff --git a/daemon/daemon.c b/daemon/daemon.c index 1457f7a41..230aab84c 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -9,9 +9,8 @@ #include #include /* printf */ -#include /* exit, atoi */ +#include /* atoi */ -#include #ifdef HAVE_SYSLOG #include #endif @@ -19,8 +18,7 @@ #ifdef _WIN32 #include /* getpid */ #else - #include /* open */ - #include /* daemon */ + #include /* getpid */ #endif #include @@ -42,6 +40,7 @@ static void sd_notifyf (int status UNUSED, const char * fmt UNUSED, ...) { } #endif +#include "daemon.h" #include "watch.h" #define MY_NAME "transmission-daemon" @@ -66,10 +65,7 @@ #define SPEED_G_STR "GB/s" #define SPEED_T_STR "TB/s" -static bool paused = false; -#ifdef SIGHUP static bool seenHUP = false; -#endif static const char *logfileName = NULL; static tr_sys_file_t logfile = TR_BAD_SYS_FILE; static tr_session * mySession = NULL; @@ -142,13 +138,6 @@ static const struct tr_option options[] = { 0, NULL, NULL, NULL, 0, NULL } }; -static void -showUsage (void) -{ - tr_getopt_usage (MY_NAME, getUsage (), options); - exit (0); -} - static bool reopen_log_file (const char *filename) { @@ -173,106 +162,6 @@ reopen_log_file (const char *filename) return true; } -static void -gotsig (int sig) -{ - switch (sig) - { -#ifdef SIGHUP - case SIGHUP: - { - if (!mySession) - { - tr_logAddInfo ("Deferring reload until session is fully started."); - seenHUP = true; - } - else - { - tr_variant settings; - const char * configDir; - - /* reopen the logfile to allow for log rotation */ - if (logfileName != NULL) - { - reopen_log_file (logfileName); - } - - configDir = tr_sessionGetConfigDir (mySession); - tr_logAddInfo ("Reloading settings from \"%s\"", configDir); - tr_variantInitDict (&settings, 0); - tr_variantDictAddBool (&settings, TR_KEY_rpc_enabled, true); - tr_sessionLoadSettings (&settings, configDir, MY_NAME); - tr_sessionSet (mySession, &settings); - tr_variantFree (&settings); - tr_sessionReloadBlocklists (mySession); - } - break; - } -#endif - - default: - tr_logAddError ("Unexpected signal (%d) in daemon, closing.", sig); - /* no break */ - - case SIGINT: - case SIGTERM: - event_base_loopexit(ev_base, NULL); - break; - } -} - -#if defined (_WIN32) - #define USE_NO_DAEMON -#elif !defined (HAVE_DAEMON) || defined (__UCLIBC__) - #define USE_TR_DAEMON -#else - #define USE_OS_DAEMON -#endif - -static int -tr_daemon (int nochdir, int noclose) -{ -#if defined (USE_OS_DAEMON) - - return daemon (nochdir, noclose); - -#elif defined (USE_TR_DAEMON) - - /* 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: return -1; - case 0: break; - default: _exit (0); - } - - if (setsid () == -1) - return -1; - - if (!nochdir) - chdir ("/"); - - if (!noclose) { - int fd = open ("/dev/null", O_RDWR, 0); - dup2 (fd, STDIN_FILENO); - dup2 (fd, STDOUT_FILENO); - dup2 (fd, STDERR_FILENO); - close (fd); - } - - return 0; - -#else /* USE_NO_DAEMON */ - - (void) nochdir; - (void) noclose; - - return 0; - -#endif -} - static const char* getConfigDir (int argc, const char ** argv) { @@ -424,165 +313,185 @@ on_rpc_callback (tr_session * session UNUSED, return TR_RPC_OK; } -int -main (int argc, char ** argv) +static bool +parse_args (int argc, + const char ** argv, + tr_variant * settings, + bool * paused, + bool * dump_settings, + bool * foreground, + int * exit_code) { int c; const char * optarg; - tr_variant settings; + + *paused = false; + *dump_settings = false; + *foreground = false; + + tr_optind = 1; + while ((c = tr_getopt (getUsage (), argc, argv, options, &optarg))) { + switch (c) { + case 'a': tr_variantDictAddStr (settings, TR_KEY_rpc_whitelist, optarg); + tr_variantDictAddBool (settings, TR_KEY_rpc_whitelist_enabled, true); + break; + case 'b': tr_variantDictAddBool (settings, TR_KEY_blocklist_enabled, true); + break; + case 'B': tr_variantDictAddBool (settings, TR_KEY_blocklist_enabled, false); + break; + case 'c': tr_variantDictAddStr (settings, TR_KEY_watch_dir, optarg); + tr_variantDictAddBool (settings, TR_KEY_watch_dir_enabled, true); + break; + case 'C': tr_variantDictAddBool (settings, TR_KEY_watch_dir_enabled, false); + break; + case 941: tr_variantDictAddStr (settings, TR_KEY_incomplete_dir, optarg); + tr_variantDictAddBool (settings, TR_KEY_incomplete_dir_enabled, true); + break; + case 942: tr_variantDictAddBool (settings, TR_KEY_incomplete_dir_enabled, false); + break; + case 'd': *dump_settings = true; + break; + case 'e': if (reopen_log_file (optarg)) + logfileName = optarg; + break; + case 'f': *foreground = true; + break; + case 'g': /* handled above */ + break; + case 'V': /* version */ + fprintf (stderr, "%s %s\n", MY_NAME, LONG_VERSION_STRING); + *exit_code = 0; + return false; + case 'o': tr_variantDictAddBool (settings, TR_KEY_dht_enabled, true); + break; + case 'O': tr_variantDictAddBool (settings, TR_KEY_dht_enabled, false); + break; + case 'p': tr_variantDictAddInt (settings, TR_KEY_rpc_port, atoi (optarg)); + break; + case 't': tr_variantDictAddBool (settings, TR_KEY_rpc_authentication_required, true); + break; + case 'T': tr_variantDictAddBool (settings, TR_KEY_rpc_authentication_required, false); + break; + case 'u': tr_variantDictAddStr (settings, TR_KEY_rpc_username, optarg); + break; + case 'v': tr_variantDictAddStr (settings, TR_KEY_rpc_password, optarg); + break; + case 'w': tr_variantDictAddStr (settings, TR_KEY_download_dir, optarg); + break; + case 'P': tr_variantDictAddInt (settings, TR_KEY_peer_port, atoi (optarg)); + break; + case 'm': tr_variantDictAddBool (settings, TR_KEY_port_forwarding_enabled, true); + break; + case 'M': tr_variantDictAddBool (settings, TR_KEY_port_forwarding_enabled, false); + break; + case 'L': tr_variantDictAddInt (settings, TR_KEY_peer_limit_global, atoi (optarg)); + break; + case 'l': tr_variantDictAddInt (settings, TR_KEY_peer_limit_per_torrent, atoi (optarg)); + break; + case 800: *paused = true; + break; + case 910: tr_variantDictAddInt (settings, TR_KEY_encryption, TR_ENCRYPTION_REQUIRED); + break; + case 911: tr_variantDictAddInt (settings, TR_KEY_encryption, TR_ENCRYPTION_PREFERRED); + break; + case 912: tr_variantDictAddInt (settings, TR_KEY_encryption, TR_CLEAR_PREFERRED); + break; + case 'i': tr_variantDictAddStr (settings, TR_KEY_bind_address_ipv4, optarg); + break; + case 'I': tr_variantDictAddStr (settings, TR_KEY_bind_address_ipv6, optarg); + break; + case 'r': tr_variantDictAddStr (settings, TR_KEY_rpc_bind_address, optarg); + break; + case 953: tr_variantDictAddReal (settings, TR_KEY_ratio_limit, atof (optarg)); + tr_variantDictAddBool (settings, TR_KEY_ratio_limit_enabled, true); + break; + case 954: tr_variantDictAddBool (settings, TR_KEY_ratio_limit_enabled, false); + break; + case 'x': tr_variantDictAddStr (settings, key_pidfile, optarg); + break; + case 'y': tr_variantDictAddBool (settings, TR_KEY_lpd_enabled, true); + break; + case 'Y': tr_variantDictAddBool (settings, TR_KEY_lpd_enabled, false); + break; + case 810: tr_variantDictAddInt (settings, TR_KEY_message_level, TR_LOG_ERROR); + break; + case 811: tr_variantDictAddInt (settings, TR_KEY_message_level, TR_LOG_INFO); + break; + case 812: tr_variantDictAddInt (settings, TR_KEY_message_level, TR_LOG_DEBUG); + break; + case 830: tr_variantDictAddBool (settings, TR_KEY_utp_enabled, true); + break; + case 831: tr_variantDictAddBool (settings, TR_KEY_utp_enabled, false); + break; + default: tr_getopt_usage (MY_NAME, getUsage (), options); + *exit_code = 0; + return false; + } + } + + return true; +} + +struct daemon_data +{ + tr_variant settings; + const char * configDir; + bool paused; +}; + +static void +daemon_reconfigure (void * arg UNUSED) +{ + if (!mySession) + { + tr_logAddInfo ("Deferring reload until session is fully started."); + seenHUP = true; + } + else + { + tr_variant settings; + const char * configDir; + + /* reopen the logfile to allow for log rotation */ + if (logfileName != NULL) + reopen_log_file (logfileName); + + configDir = tr_sessionGetConfigDir (mySession); + tr_logAddInfo ("Reloading settings from \"%s\"", configDir); + tr_variantInitDict (&settings, 0); + tr_variantDictAddBool (&settings, TR_KEY_rpc_enabled, true); + tr_sessionLoadSettings (&settings, configDir, MY_NAME); + tr_sessionSet (mySession, &settings); + tr_variantFree (&settings); + tr_sessionReloadBlocklists (mySession); + } +} + +static void +daemon_stop (void * arg UNUSED) +{ + event_base_loopexit (ev_base, NULL); +} + +static int +daemon_start (void * raw_arg, + bool foreground) +{ bool boolVal; - bool loaded; - bool foreground = false; - bool dumpSettings = false; - const char * configDir = NULL; const char * pid_filename; dtr_watchdir * watchdir = NULL; bool pidfile_created = false; tr_session * session = NULL; struct event *status_ev; -#ifdef _WIN32 - tr_win32_make_args_utf8 (&argc, &argv); + struct daemon_data * const arg = raw_arg; + tr_variant * const settings = &arg->settings; + const char * const configDir = arg->configDir; + +#ifndef HAVE_SYSLOG + (void) foreground; #endif - key_pidfile = tr_quark_new ("pidfile", 7); - - signal (SIGINT, gotsig); - signal (SIGTERM, gotsig); -#ifdef SIGHUP - signal (SIGHUP, gotsig); -#endif - - /* load settings from defaults + config file */ - tr_variantInitDict (&settings, 0); - tr_variantDictAddBool (&settings, TR_KEY_rpc_enabled, true); - configDir = getConfigDir (argc, (const char**)argv); - loaded = tr_sessionLoadSettings (&settings, configDir, MY_NAME); - - /* overwrite settings from the comamndline */ - tr_optind = 1; - while ((c = tr_getopt (getUsage (), argc, (const char**)argv, options, &optarg))) { - switch (c) { - case 'a': tr_variantDictAddStr (&settings, TR_KEY_rpc_whitelist, optarg); - tr_variantDictAddBool (&settings, TR_KEY_rpc_whitelist_enabled, true); - break; - case 'b': tr_variantDictAddBool (&settings, TR_KEY_blocklist_enabled, true); - break; - case 'B': tr_variantDictAddBool (&settings, TR_KEY_blocklist_enabled, false); - break; - case 'c': tr_variantDictAddStr (&settings, TR_KEY_watch_dir, optarg); - tr_variantDictAddBool (&settings, TR_KEY_watch_dir_enabled, true); - break; - case 'C': tr_variantDictAddBool (&settings, TR_KEY_watch_dir_enabled, false); - break; - case 941: tr_variantDictAddStr (&settings, TR_KEY_incomplete_dir, optarg); - tr_variantDictAddBool (&settings, TR_KEY_incomplete_dir_enabled, true); - break; - case 942: tr_variantDictAddBool (&settings, TR_KEY_incomplete_dir_enabled, false); - break; - case 'd': dumpSettings = true; - break; - case 'e': if (reopen_log_file (optarg)) - logfileName = optarg; - break; - case 'f': foreground = true; - break; - case 'g': /* handled above */ - break; - case 'V': /* version */ - fprintf (stderr, "%s %s\n", MY_NAME, LONG_VERSION_STRING); - exit (0); - case 'o': tr_variantDictAddBool (&settings, TR_KEY_dht_enabled, true); - break; - case 'O': tr_variantDictAddBool (&settings, TR_KEY_dht_enabled, false); - break; - case 'p': tr_variantDictAddInt (&settings, TR_KEY_rpc_port, atoi (optarg)); - break; - case 't': tr_variantDictAddBool (&settings, TR_KEY_rpc_authentication_required, true); - break; - case 'T': tr_variantDictAddBool (&settings, TR_KEY_rpc_authentication_required, false); - break; - case 'u': tr_variantDictAddStr (&settings, TR_KEY_rpc_username, optarg); - break; - case 'v': tr_variantDictAddStr (&settings, TR_KEY_rpc_password, optarg); - break; - case 'w': tr_variantDictAddStr (&settings, TR_KEY_download_dir, optarg); - break; - case 'P': tr_variantDictAddInt (&settings, TR_KEY_peer_port, atoi (optarg)); - break; - case 'm': tr_variantDictAddBool (&settings, TR_KEY_port_forwarding_enabled, true); - break; - case 'M': tr_variantDictAddBool (&settings, TR_KEY_port_forwarding_enabled, false); - break; - case 'L': tr_variantDictAddInt (&settings, TR_KEY_peer_limit_global, atoi (optarg)); - break; - case 'l': tr_variantDictAddInt (&settings, TR_KEY_peer_limit_per_torrent, atoi (optarg)); - break; - case 800: paused = true; - break; - case 910: tr_variantDictAddInt (&settings, TR_KEY_encryption, TR_ENCRYPTION_REQUIRED); - break; - case 911: tr_variantDictAddInt (&settings, TR_KEY_encryption, TR_ENCRYPTION_PREFERRED); - break; - case 912: tr_variantDictAddInt (&settings, TR_KEY_encryption, TR_CLEAR_PREFERRED); - break; - case 'i': tr_variantDictAddStr (&settings, TR_KEY_bind_address_ipv4, optarg); - break; - case 'I': tr_variantDictAddStr (&settings, TR_KEY_bind_address_ipv6, optarg); - break; - case 'r': tr_variantDictAddStr (&settings, TR_KEY_rpc_bind_address, optarg); - break; - case 953: tr_variantDictAddReal (&settings, TR_KEY_ratio_limit, atof (optarg)); - tr_variantDictAddBool (&settings, TR_KEY_ratio_limit_enabled, true); - break; - case 954: tr_variantDictAddBool (&settings, TR_KEY_ratio_limit_enabled, false); - break; - case 'x': tr_variantDictAddStr (&settings, key_pidfile, optarg); - break; - case 'y': tr_variantDictAddBool (&settings, TR_KEY_lpd_enabled, true); - break; - case 'Y': tr_variantDictAddBool (&settings, TR_KEY_lpd_enabled, false); - break; - case 810: tr_variantDictAddInt (&settings, TR_KEY_message_level, TR_LOG_ERROR); - break; - case 811: tr_variantDictAddInt (&settings, TR_KEY_message_level, TR_LOG_INFO); - break; - case 812: tr_variantDictAddInt (&settings, TR_KEY_message_level, TR_LOG_DEBUG); - break; - case 830: tr_variantDictAddBool (&settings, TR_KEY_utp_enabled, true); - break; - case 831: tr_variantDictAddBool (&settings, TR_KEY_utp_enabled, false); - break; - default: showUsage (); - break; - } - } - - if (foreground && logfile == TR_BAD_SYS_FILE) - logfile = tr_sys_file_get_std (TR_STD_SYS_FILE_ERR, NULL); - - if (!loaded) - { - printMessage (logfile, TR_LOG_ERROR, MY_NAME, "Error loading config file -- exiting.", __FILE__, __LINE__); - return -1; - } - - if (dumpSettings) - { - char * str = tr_variantToStr (&settings, TR_VARIANT_FMT_JSON, NULL); - fprintf (stderr, "%s", str); - tr_free (str); - return 0; - } - - if (!foreground && tr_daemon (true, false) < 0) - { - char buf[256]; - tr_snprintf (buf, sizeof (buf), "Failed to daemonize: %s", tr_strerror (errno)); - printMessage (logfile, TR_LOG_ERROR, MY_NAME, buf, __FILE__, __LINE__); - exit (1); - } - sd_notifyf (0, "MAINPID=%d\n", (int)getpid()); /* setup event state */ @@ -592,20 +501,20 @@ main (int argc, char ** argv) char buf[256]; tr_snprintf(buf, sizeof(buf), "Failed to init daemon event state: %s", tr_strerror(errno)); printMessage (logfile, TR_LOG_ERROR, MY_NAME, buf, __FILE__, __LINE__); - exit (1); + return 1; } /* start the session */ tr_formatter_mem_init (MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR); tr_formatter_size_init (DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR); tr_formatter_speed_init (SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR); - session = tr_sessionInit ("daemon", configDir, true, &settings); + session = tr_sessionInit ("daemon", configDir, true, settings); tr_sessionSetRPCCallback (session, on_rpc_callback, NULL); tr_logAddNamedInfo (NULL, "Using settings from \"%s\"", configDir); - tr_sessionSaveSettings (session, configDir, &settings); + tr_sessionSaveSettings (session, configDir, settings); pid_filename = NULL; - tr_variantDictFindStr (&settings, key_pidfile, &pid_filename, NULL); + tr_variantDictFindStr (settings, key_pidfile, &pid_filename, NULL); if (pid_filename && *pid_filename) { tr_error * error = NULL; @@ -626,24 +535,22 @@ main (int argc, char ** argv) } } - if (tr_variantDictFindBool (&settings, TR_KEY_rpc_authentication_required, &boolVal) && boolVal) + if (tr_variantDictFindBool (settings, TR_KEY_rpc_authentication_required, &boolVal) && boolVal) tr_logAddNamedInfo (MY_NAME, "requiring authentication"); mySession = session; -#ifdef SIGHUP /* If we got a SIGHUP during startup, process that now. */ if (seenHUP) - gotsig (SIGHUP); -#endif + daemon_reconfigure (arg); /* maybe add a watchdir */ { const char * dir; - if (tr_variantDictFindBool (&settings, TR_KEY_watch_dir_enabled, &boolVal) + if (tr_variantDictFindBool (settings, TR_KEY_watch_dir_enabled, &boolVal) && boolVal - && tr_variantDictFindStr (&settings, TR_KEY_watch_dir, &dir, NULL) + && tr_variantDictFindStr (settings, TR_KEY_watch_dir, &dir, NULL) && dir && *dir) { @@ -656,7 +563,7 @@ main (int argc, char ** argv) { tr_torrent ** torrents; tr_ctor * ctor = tr_ctorNew (mySession); - if (paused) + if (arg->paused) tr_ctorSetPaused (ctor, TR_FORCE, true); torrents = tr_sessionLoadTorrents (mySession, ctor, NULL); tr_free (torrents); @@ -704,7 +611,7 @@ cleanup: } event_base_free(ev_base); - tr_sessionSaveSettings (mySession, configDir, &settings); + tr_sessionSaveSettings (mySession, configDir, settings); dtr_watchdir_free (watchdir); tr_sessionClose (mySession); pumpLogMessages (logfile); @@ -722,7 +629,75 @@ cleanup: /* cleanup */ if (pidfile_created) tr_sys_path_remove (pid_filename, NULL); - tr_variantFree (&settings); + sd_notify (0, "STATUS=\n"); + return 0; } + +int +main (int argc, + char ** argv) +{ + const dtr_callbacks cb = + { + .on_start = &daemon_start, + .on_stop = &daemon_stop, + .on_reconfigure = &daemon_reconfigure, + }; + + int ret; + bool loaded, dumpSettings, foreground; + tr_error * error = NULL; + + struct daemon_data arg; + tr_variant * const settings = &arg.settings; + const char ** const configDir = &arg.configDir; + +#ifdef _WIN32 + tr_win32_make_args_utf8 (&argc, &argv); +#endif + + key_pidfile = tr_quark_new ("pidfile", 7); + + /* load settings from defaults + config file */ + tr_variantInitDict (settings, 0); + tr_variantDictAddBool (settings, TR_KEY_rpc_enabled, true); + *configDir = getConfigDir (argc, (const char**)argv); + loaded = tr_sessionLoadSettings (settings, *configDir, MY_NAME); + + /* overwrite settings from the comamndline */ + if (!parse_args (argc, (const char**) argv, settings, &arg.paused, &dumpSettings, &foreground, &ret)) + goto cleanup; + + if (foreground && logfile == TR_BAD_SYS_FILE) + logfile = tr_sys_file_get_std (TR_STD_SYS_FILE_ERR, NULL); + + if (!loaded) + { + printMessage (logfile, TR_LOG_ERROR, MY_NAME, "Error loading config file -- exiting.", __FILE__, __LINE__); + ret = 1; + goto cleanup; + } + + if (dumpSettings) + { + char * str = tr_variantToStr (settings, TR_VARIANT_FMT_JSON, NULL); + fprintf (stderr, "%s", str); + tr_free (str); + goto cleanup; + } + + if (!dtr_daemon (&cb, &arg, foreground, &ret, &error)) + { + char buf[256]; + tr_snprintf (buf, sizeof (buf), "Failed to daemonize: %s", error->message); + printMessage (logfile, TR_LOG_ERROR, MY_NAME, buf, __FILE__, __LINE__); + tr_error_free (error); + } + +cleanup: + tr_variantFree (settings); + + return ret; +} diff --git a/daemon/daemon.h b/daemon/daemon.h new file mode 100644 index 000000000..771ae3f3b --- /dev/null +++ b/daemon/daemon.h @@ -0,0 +1,29 @@ +/* + * 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$ + */ + +#ifndef DTR_DAEMON_H +#define DTR_DAEMON_H + +struct tr_error; + +typedef struct dtr_callbacks +{ + int (*on_start) (void * arg, bool foreground); + void (*on_stop) (void * arg); + void (*on_reconfigure) (void * arg); +} +dtr_callbacks; + +bool dtr_daemon (const dtr_callbacks * cb, + void * cb_arg, + bool foreground, + int * exit_code, + struct tr_error ** error); + +#endif /* DTR_DAEMON_H */