mirror of
https://github.com/transmission/transmission
synced 2024-12-31 20:16:57 +00:00
#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:
parent
5578d616a8
commit
a079af9ed5
5 changed files with 800 additions and 275 deletions
|
@ -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
236
daemon/daemon-posix.c
Normal 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
273
daemon/daemon-win32.c
Normal 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;
|
||||
}
|
523
daemon/daemon.c
523
daemon/daemon.c
|
@ -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
29
daemon/daemon.h
Normal 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 */
|
Loading…
Reference in a new issue