#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.
This commit is contained in:
Mike Gelfand 2015-04-04 17:43:56 +00:00
parent 5578d616a8
commit a079af9ed5
5 changed files with 800 additions and 275 deletions

View File

@ -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}"

236
daemon/daemon-posix.c Normal file
View File

@ -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 <assert.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h> /* daemon (), exit () */
#include <fcntl.h> /* open () */
#include <unistd.h> /* fork (), setsid (), chdir (), dup2 (), close (), pipe () */
#include <libtransmission/transmission.h>
#include <libtransmission/error.h>
#include <libtransmission/utils.h>
#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;
}

273
daemon/daemon-win32.c Normal file
View File

@ -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 <process.h> /* _beginthreadex () */
#include <windows.h>
#include <libtransmission/transmission.h>
#include <libtransmission/error.h>
#include <libtransmission/log.h>
#include <libtransmission/utils.h>
#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;
}

View File

@ -9,9 +9,8 @@
#include <errno.h>
#include <stdio.h> /* printf */
#include <stdlib.h> /* exit, atoi */
#include <stdlib.h> /* atoi */
#include <signal.h>
#ifdef HAVE_SYSLOG
#include <syslog.h>
#endif
@ -19,8 +18,7 @@
#ifdef _WIN32
#include <process.h> /* getpid */
#else
#include <fcntl.h> /* open */
#include <unistd.h> /* daemon */
#include <unistd.h> /* getpid */
#endif
#include <event2/buffer.h>
@ -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;
}

29
daemon/daemon.h Normal file
View File

@ -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 */