mirror of
https://github.com/transmission/transmission
synced 2024-12-25 09:13:06 +00:00
236 lines
4.5 KiB
C
236 lines
4.5 KiB
C
/*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h> /* abort (), 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;
|
|
|
|
if (write (signal_pipe[1], &sig, sizeof (sig)) == -1)
|
|
abort ();
|
|
|
|
errno = old_errno;
|
|
}
|
|
|
|
static void *
|
|
signal_handler_thread_main (void * arg UNUSED)
|
|
{
|
|
int sig;
|
|
|
|
while (read (signal_pipe[0], &sig, sizeof (sig)) == sizeof (sig) && sig != 0)
|
|
handle_signal (sig);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static bool
|
|
create_signal_pipe (tr_error ** error)
|
|
{
|
|
if (pipe (signal_pipe) == -1)
|
|
{
|
|
set_system_error (error, errno, "pipe() failed");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
destroy_signal_pipe (void)
|
|
{
|
|
close (signal_pipe[0]);
|
|
close (signal_pipe[1]);
|
|
}
|
|
|
|
static bool
|
|
create_signal_handler_thread (pthread_t * thread,
|
|
tr_error ** error)
|
|
{
|
|
if (!create_signal_pipe (error))
|
|
return false;
|
|
|
|
if ((errno = pthread_create (thread, NULL, &signal_handler_thread_main, NULL)) != 0)
|
|
{
|
|
set_system_error (error, errno, "pthread_create() failed");
|
|
destroy_signal_pipe ();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
destroy_signal_handler_thread (pthread_t thread)
|
|
{
|
|
send_signal_to_pipe (0);
|
|
pthread_join (thread, NULL);
|
|
|
|
destroy_signal_pipe ();
|
|
}
|
|
|
|
static bool
|
|
setup_signal_handler (int sig,
|
|
tr_error ** error)
|
|
{
|
|
assert (sig != 0);
|
|
|
|
if (signal (sig, &send_signal_to_pipe) == SIG_ERR)
|
|
{
|
|
set_system_error (error, errno, "signal() failed");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
bool
|
|
dtr_daemon (const dtr_callbacks * cb,
|
|
void * cb_arg,
|
|
bool foreground,
|
|
int * exit_code,
|
|
tr_error ** error)
|
|
{
|
|
callbacks = cb;
|
|
callback_arg = cb_arg;
|
|
|
|
*exit_code = 1;
|
|
|
|
if (!foreground)
|
|
{
|
|
|
|
#if defined (HAVE_DAEMON) && !defined (__APPLE__) && !defined (__UCLIBC__)
|
|
|
|
if (daemon (true, false) == -1)
|
|
{
|
|
set_system_error (error, errno, "daemon() failed");
|
|
return false;
|
|
}
|
|
|
|
#else
|
|
|
|
/* this is loosely based off of glibc's daemon () implementation
|
|
* http://sourceware.org/git/?p=glibc.git;a=blob_plain;f=misc/daemon.c */
|
|
|
|
switch (fork ())
|
|
{
|
|
case -1:
|
|
set_system_error (error, errno, "fork() failed");
|
|
return false;
|
|
case 0:
|
|
break;
|
|
default:
|
|
*exit_code = 0;
|
|
return true;
|
|
}
|
|
|
|
if (setsid () == -1)
|
|
{
|
|
set_system_error (error, errno, "setsid() failed");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
if (chdir ("/") == -1)
|
|
{
|
|
set_system_error (error, errno, "chdir() failed");
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
{
|
|
const int fd = open ("/dev/null", O_RDWR, 0);
|
|
dup2 (fd, STDIN_FILENO);
|
|
dup2 (fd, STDOUT_FILENO);
|
|
dup2 (fd, STDERR_FILENO);
|
|
close (fd);
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
pthread_t signal_thread;
|
|
if (!create_signal_handler_thread (&signal_thread, error))
|
|
return false;
|
|
|
|
if (!setup_signal_handler (SIGINT, error) ||
|
|
!setup_signal_handler (SIGTERM, error) ||
|
|
!setup_signal_handler (SIGHUP, error))
|
|
{
|
|
destroy_signal_handler_thread (signal_thread);
|
|
return false;
|
|
}
|
|
|
|
*exit_code = cb->on_start (cb_arg, foreground);
|
|
|
|
destroy_signal_handler_thread (signal_thread);
|
|
|
|
return true;
|
|
}
|