transmission/daemon/daemon.c

377 lines
12 KiB
C
Raw Normal View History

2008-05-18 16:44:30 +00:00
/*
* This file Copyright (C) 2008-2009 Charles Kerr <charles@transmissionbt.com>
2007-04-18 16:39:10 +00:00
*
2008-05-18 16:44:30 +00:00
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
2008-05-18 16:44:30 +00:00
* This exemption does not extend to derived works not owned by
* the Transmission project.
2007-04-18 16:39:10 +00:00
*
2008-05-19 16:16:38 +00:00
* $Id$
2008-05-18 16:44:30 +00:00
*/
2007-04-18 16:39:10 +00:00
#include <assert.h>
#include <errno.h>
#include <stdio.h> /* printf */
2008-05-18 16:44:30 +00:00
#include <stdlib.h> /* exit, atoi */
#include <string.h> /* strcmp */
#include <sys/types.h> /* umask*/
#include <sys/stat.h> /* umask*/
#include <dirent.h> /* readdir */
2008-05-18 16:44:30 +00:00
#include <fcntl.h> /* open */
2007-04-18 16:39:10 +00:00
#include <signal.h>
#include <unistd.h> /* daemon */
2008-05-18 16:44:30 +00:00
#include <event.h>
2008-05-18 16:44:30 +00:00
#include <libtransmission/transmission.h>
#include <libtransmission/bencode.h>
2008-07-08 16:50:34 +00:00
#include <libtransmission/tr-getopt.h>
#include <libtransmission/utils.h>
#include <libtransmission/version.h>
2008-05-18 16:44:30 +00:00
#define MY_NAME "transmission-daemon"
2007-04-18 16:39:10 +00:00
#define PREF_KEY_DIR_WATCH "watch-dir"
#define PREF_KEY_DIR_WATCH_ENABLED "watch-dir-enabled"
#define WATCHDIR_POLL_INTERVAL_SECS 15
2008-10-28 19:49:33 +00:00
static int closing = FALSE;
static tr_session * mySession = NULL;
/***
**** Config File
***/
2008-07-08 16:50:34 +00:00
static const char *
getUsage( void )
{
return "Transmission " LONG_VERSION_STRING
" http://www.transmissionbt.com/\n"
2008-07-08 16:50:34 +00:00
"A fast and easy BitTorrent client\n"
"\n"
MY_NAME " is a headless Transmission session\n"
"that can be controlled via transmission-remote or Clutch.\n"
"\n"
"Usage: " MY_NAME " [options]";
2008-07-08 16:50:34 +00:00
}
static const struct tr_option options[] =
{
{ 'a', "allowed", "Allowed IP addresses. (Default: " TR_DEFAULT_RPC_WHITELIST ")", "a", 1, "<list>" },
{ 'b', "blocklist", "Enable peer blocklists", "b", 0, NULL },
{ 'B', "no-blocklist", "Disable peer blocklists", "B", 0, NULL },
{ 'c', "watch-dir", "Directory to watch for new .torrent files", "c", 1, "<directory>" },
{ 'C', "no-watch-dir", "Disable the watch-dir", "C", 0, NULL },
{ 'd', "dump-settings", "Dump the settings and exit", "d", 0, NULL },
{ 'f', "foreground", "Run in the foreground instead of daemonizing", "f", 0, NULL },
{ 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
{ 'p', "port", "RPC port (Default: " TR_DEFAULT_RPC_PORT_STR ")", "p", 1, "<port>" },
{ 't', "auth", "Require authentication", "t", 0, NULL },
{ 'T', "no-auth", "Don't require authentication", "T", 0, NULL },
{ 'u', "username", "Set username for authentication", "u", 1, "<username>" },
{ 'v', "password", "Set password for authentication", "v", 1, "<password>" },
{ 'V', "version", "Show version number and exit", "V", 0, NULL },
{ 'w', "download-dir", "Where to save downloaded data", "w", 1, "<path>" },
{ 'P', "peerport", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "P", 1, "<port>" },
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
{ 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
{ 'L', "peerlimit-global", "Maximum overall number of peers (Default: " TR_DEFAULT_PEER_LIMIT_GLOBAL_STR ")", "L", 1, "<limit>" },
{ 'l', "peerlimit-torrent", "Maximum number of peers per torrent (Default: " TR_DEFAULT_PEER_LIMIT_TORRENT_STR ")", "l", 1, "<limit>" },
{ 910, "encryption-required", "Encrypt all peer connections", "er", 0, NULL },
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
{ 0, NULL, NULL, NULL, 0, NULL }
2008-07-08 16:50:34 +00:00
};
2008-05-18 16:44:30 +00:00
static void
2008-07-08 16:50:34 +00:00
showUsage( void )
2008-05-18 16:44:30 +00:00
{
tr_getopt_usage( MY_NAME, getUsage( ), options );
2007-04-18 16:39:10 +00:00
exit( 0 );
}
2008-05-18 16:44:30 +00:00
static void
gotsig( int sig UNUSED )
{
2008-05-18 16:44:30 +00:00
closing = TRUE;
}
#if defined(WIN32)
#define USE_NO_DAEMON
#elif !defined(HAVE_DAEMON) || defined(__UCLIBC__)
#define USE_TR_DAEMON
#else
#define USE_OS_DAEMON
#endif
2008-05-18 16:44:30 +00:00
static int
tr_daemon( int nochdir, int noclose )
2008-05-18 16:44:30 +00:00
{
#if defined(USE_OS_DAEMON)
return daemon( nochdir, noclose );
#elif defined(USE_TR_DAEMON)
pid_t pid = fork( );
if( pid < 0 )
2007-04-18 16:39:10 +00:00
return -1;
else if( pid > 0 )
_exit( 0 );
else {
pid = setsid( );
if( pid < 0 )
2008-05-18 16:44:30 +00:00
return -1;
pid = fork( );
if( pid < 0 )
return -1;
else if( pid > 0 )
_exit( 0 );
else {
if( !nochdir )
if( chdir( "/" ) < 0 )
return -1;
umask( (mode_t)0 );
if( !noclose ) {
/* send stdin, stdout, and stderr to /dev/null */
int i;
int fd = open( "/dev/null", O_RDWR, 0 );
for( i=0; i<3; ++i ) {
if( close( i ) )
return -1;
dup2( fd, i );
}
close( fd );
}
return 0;
2007-04-18 16:39:10 +00:00
}
}
#else /* USE_NO_DAEMON */
return 0;
2008-05-18 16:44:30 +00:00
#endif
}
2007-04-18 16:39:10 +00:00
static const char*
getConfigDir( int argc, const char ** argv )
{
int c;
const char * configDir = NULL;
const char * optarg;
const int ind = tr_optind;
while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg ))) {
if( c == 'g' ) {
configDir = optarg;
break;
}
}
tr_optind = ind;
if( configDir == NULL )
configDir = tr_getDefaultConfigDir( MY_NAME );
return configDir;
}
/**
* This is crude compared to using kqueue/inotify/etc, but has the advantage
* of working portably on whatever random embedded platform we throw at it.
* Since we're only walking a single directory, nonrecursively, a few times
* per minute, let's go with this unless users complain about the load
*/
static void
checkForNewFiles( tr_session * session, const char * dirname, time_t oldTime )
{
struct stat sb;
DIR * odir;
if( !stat( dirname, &sb ) && S_ISDIR( sb.st_mode ) && (( odir = opendir( dirname ))) )
{
struct dirent * d;
for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
{
char * filename;
if( !d->d_name || *d->d_name=='.' ) /* skip dotfiles */
continue;
if( !strstr( d->d_name, ".torrent" ) ) /* skip non-torrents */
continue;
/* if the file's changed since our last pass, try adding it */
filename = tr_buildPath( dirname, d->d_name, NULL );
if( !stat( filename, &sb ) && sb.st_mtime >= oldTime )
{
tr_ctor * ctor = tr_ctorNew( session );
int err = tr_ctorSetMetainfoFromFile( ctor, filename );
if( !err )
tr_torrentNew( session, ctor, &err );
tr_ctorFree( ctor );
}
tr_free( filename );
}
closedir( odir );
}
}
2008-05-18 16:44:30 +00:00
int
main( int argc, char ** argv )
2007-04-18 16:39:10 +00:00
{
int c;
int64_t i;
const char * optarg;
tr_benc settings;
tr_bool foreground = FALSE;
tr_bool dumpSettings = FALSE;
2008-07-08 16:50:34 +00:00
const char * configDir = NULL;
const char * watchDir = NULL;
time_t lastCheckTime = 0;
2008-05-18 16:44:30 +00:00
signal( SIGINT, gotsig );
signal( SIGTERM, gotsig );
#ifndef WIN32
signal( SIGQUIT, gotsig );
2007-04-18 16:39:10 +00:00
signal( SIGPIPE, SIG_IGN );
signal( SIGHUP, SIG_IGN );
#endif
2007-04-18 16:39:10 +00:00
/* load settings from defaults + config file */
tr_bencInitDict( &settings, 0 );
configDir = getConfigDir( argc, (const char**)argv );
tr_sessionLoadSettings( &settings, configDir, MY_NAME );
tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_ENABLED, 1 );
/* overwrite settings from the comamndline */
tr_optind = 1;
while(( c = tr_getopt( getUsage(), argc, (const char**)argv, options, &optarg ))) {
switch( c ) {
case 'a': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_WHITELIST, optarg );
tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, 1 );
break;
case 'b': tr_bencDictAddInt( &settings, TR_PREFS_KEY_BLOCKLIST_ENABLED, 1 );
break;
case 'B': tr_bencDictAddInt( &settings, TR_PREFS_KEY_BLOCKLIST_ENABLED, 0 );
break;
case 'c': tr_bencDictAddStr( &settings, PREF_KEY_DIR_WATCH, optarg );
tr_bencDictAddInt( &settings, PREF_KEY_DIR_WATCH_ENABLED, 1 );
break;
case 'C': tr_bencDictAddInt( &settings, PREF_KEY_DIR_WATCH_ENABLED, 0 );
break;
case 'd': dumpSettings = TRUE;
break;
case 'f': foreground = TRUE;
break;
case 'g': /* handled above */
break;
case 'V': /* version */
fprintf(stderr, "Transmission %s\n", LONG_VERSION_STRING);
exit( 0 );
case 'p': tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_PORT, atoi( optarg ) );
break;
case 't': tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, 1 );
break;
case 'T': tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, 0 );
break;
case 'u': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_USERNAME, optarg );
break;
case 'v': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_PASSWORD, optarg );
break;
case 'w': tr_bencDictAddStr( &settings, TR_PREFS_KEY_DOWNLOAD_DIR, optarg );
break;
case 'P': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_PORT, atoi( optarg ) );
break;
case 'm': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PORT_FORWARDING, 1 );
break;
case 'M': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PORT_FORWARDING, 0 );
break;
case 'L': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi( optarg ) );
break;
case 'l': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_LIMIT_TORRENT, atoi( optarg ) );
break;
case 910: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_REQUIRED );
break;
case 911: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_PREFERRED );
break;
case 912: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_CLEAR_PREFERRED );
break;
default: showUsage( );
break;
}
}
2007-04-18 16:39:10 +00:00
if( dumpSettings )
{
struct evbuffer * buf = tr_getBuffer( );
tr_bencSaveAsJSON( &settings, buf );
fprintf( stderr, "%s", (char*)EVBUFFER_DATA(buf) );
tr_releaseBuffer( buf );
return 0;
}
if( !foreground && tr_daemon( TRUE, FALSE ) < 0 )
{
fprintf( stderr, "failed to daemonize: %s\n", strerror( errno ) );
exit( 1 );
2007-04-18 16:39:10 +00:00
}
2007-08-14 04:02:50 +00:00
/* start the session */
mySession = tr_sessionInit( "daemon", configDir, FALSE, &settings );
if( tr_bencDictFindInt( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, &i ) && i!=0 )
tr_ninf( MY_NAME, "requiring authentication" );
/* maybe add a watchdir */
{
int64_t doWatch;
if( tr_bencDictFindInt( &settings, PREF_KEY_DIR_WATCH_ENABLED, &doWatch )
&& doWatch
&& tr_bencDictFindStr( &settings, PREF_KEY_DIR_WATCH, &watchDir )
&& watchDir
&& *watchDir )
{
tr_ninf( MY_NAME, "watching \"%s\" for added .torrent files", watchDir );
}
}
/* load the torrents */
{
tr_ctor * ctor = tr_ctorNew( mySession );
tr_torrent ** torrents = tr_sessionLoadTorrents( mySession, ctor, NULL );
tr_free( torrents );
tr_ctorFree( ctor );
}
2007-08-14 04:02:50 +00:00
while( !closing )
{
tr_wait( 1000 ); /* sleep one second */
if( watchDir && ( lastCheckTime + WATCHDIR_POLL_INTERVAL_SECS < time( NULL ) ) )
{
checkForNewFiles( mySession, watchDir, lastCheckTime );
lastCheckTime = time( NULL );
}
}
2007-08-14 04:02:50 +00:00
/* shutdown */
2008-05-18 16:44:30 +00:00
printf( "Closing transmission session..." );
tr_sessionSaveSettings( mySession, configDir, &settings );
tr_sessionClose( mySession );
2008-05-18 16:44:30 +00:00
printf( " done.\n" );
2007-08-14 04:02:50 +00:00
/* cleanup */
tr_bencFree( &settings );
2007-08-14 04:02:50 +00:00
return 0;
}