source: trunk/daemon/daemon.c @ 7974

Last change on this file since 7974 was 7974, checked in by charles, 13 years ago

(trunk daemon) another experiment: implement the watchdir with opendir() and readdir(), since those are available on every system we're on, even embedded ones...

  • Property svn:keywords set to Date Rev Author Id
File size: 12.3 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 Charles Kerr <charles@transmissionbt.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: daemon.c 7974 2009-02-28 15:27:39Z charles $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <stdio.h> /* printf */
16#include <stdlib.h> /* exit, atoi */
17#include <string.h> /* strcmp */
18
19#include <sys/types.h> /* umask*/
20#include <sys/stat.h> /* umask*/
21#include <dirent.h> /* readdir */
22
23#include <fcntl.h> /* open */
24#include <signal.h>
25#include <unistd.h> /* daemon */
26
27#include <event.h>
28
29#include <libtransmission/transmission.h>
30#include <libtransmission/bencode.h>
31#include <libtransmission/tr-getopt.h>
32#include <libtransmission/utils.h>
33#include <libtransmission/version.h>
34
35#define MY_NAME "transmission-daemon"
36
37#define PREF_KEY_DIR_WATCH          "watch-dir"
38#define PREF_KEY_DIR_WATCH_ENABLED  "watch-dir-enabled"
39
40#define WATCHDIR_POLL_INTERVAL_SECS  15
41
42
43static int           closing = FALSE;
44static tr_session  * mySession = NULL;
45
46/***
47****  Config File
48***/
49
50static const char *
51getUsage( void )
52{
53    return "Transmission " LONG_VERSION_STRING
54           "  http://www.transmissionbt.com/\n"
55           "A fast and easy BitTorrent client\n"
56           "\n"
57           MY_NAME " is a headless Transmission session\n"
58           "that can be controlled via transmission-remote or Clutch.\n"
59           "\n"
60           "Usage: " MY_NAME " [options]";
61}
62
63static const struct tr_option options[] =
64{
65    { 'a', "allowed", "Allowed IP addresses.  (Default: " TR_DEFAULT_RPC_WHITELIST ")", "a", 1, "<list>" },
66    { 'b', "blocklist", "Enable peer blocklists", "b", 0, NULL },
67    { 'B', "no-blocklist", "Disable peer blocklists", "B", 0, NULL },
68    { 'c', "watch-dir", "Directory to watch for new .torrent files", "c", 1, "<directory>" },
69    { 'C', "no-watch-dir", "Disable the watch-dir", "C", 0, NULL },
70    { 'd', "dump-settings", "Dump the settings and exit", "d", 0, NULL },
71    { 'f', "foreground", "Run in the foreground instead of daemonizing", "f", 0, NULL },
72    { 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
73    { 'p', "port", "RPC port (Default: " TR_DEFAULT_RPC_PORT_STR ")", "p", 1, "<port>" },
74    { 't', "auth", "Require authentication", "t", 0, NULL },
75    { 'T', "no-auth", "Don't require authentication", "T", 0, NULL },
76    { 'u', "username", "Set username for authentication", "u", 1, "<username>" },
77    { 'v', "password", "Set password for authentication", "v", 1, "<password>" },
78    { 'V', "version", "Show version number and exit", "V", 0, NULL },
79    { 'w', "download-dir", "Where to save downloaded data", "w", 1, "<path>" },
80    { 'P', "peerport", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "P", 1, "<port>" },
81    { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
82    { 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
83    { 'L', "peerlimit-global", "Maximum overall number of peers (Default: " TR_DEFAULT_PEER_LIMIT_GLOBAL_STR ")", "L", 1, "<limit>" },
84    { 'l', "peerlimit-torrent", "Maximum number of peers per torrent (Default: " TR_DEFAULT_PEER_LIMIT_TORRENT_STR ")", "l", 1, "<limit>" },
85    { 910, "encryption-required",  "Encrypt all peer connections", "er", 0, NULL },
86    { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
87    { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
88    { 0, NULL, NULL, NULL, 0, NULL }
89};
90
91static void
92showUsage( void )
93{
94    tr_getopt_usage( MY_NAME, getUsage( ), options );
95    exit( 0 );
96}
97
98static void
99gotsig( int sig UNUSED )
100{
101    closing = TRUE;
102}
103
104#if defined(WIN32)
105 #define USE_NO_DAEMON
106#elif !defined(HAVE_DAEMON) || defined(__UCLIBC__)
107 #define USE_TR_DAEMON
108#else
109 #define USE_OS_DAEMON
110#endif
111
112static int
113tr_daemon( int nochdir, int noclose )
114{
115#if defined(USE_OS_DAEMON)
116    return daemon( nochdir, noclose );
117#elif defined(USE_TR_DAEMON)
118    pid_t pid = fork( );
119    if( pid < 0 )
120        return -1;
121    else if( pid > 0 )
122        _exit( 0 );
123    else {
124        pid = setsid( );
125        if( pid < 0 )
126            return -1;
127
128        pid = fork( );
129        if( pid < 0 )
130            return -1;
131        else if( pid > 0 )
132            _exit( 0 );
133        else {
134
135            if( !nochdir )
136                if( chdir( "/" ) < 0 )
137                    return -1;
138
139            umask( (mode_t)0 );
140
141            if( !noclose ) {
142                /* send stdin, stdout, and stderr to /dev/null */
143                int i;
144                int fd = open( "/dev/null", O_RDWR, 0 );
145                for( i=0; i<3; ++i ) {
146                    if( close( i ) )
147                        return -1;
148                    dup2( fd, i );
149                }
150                close( fd );
151            }
152
153            return 0;
154        }
155    }
156#else /* USE_NO_DAEMON */
157    return 0;
158#endif
159}
160
161static const char*
162getConfigDir( int argc, const char ** argv )
163{
164    int c;
165    const char * configDir = NULL;
166    const char * optarg;
167    const int ind = tr_optind;
168
169    while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg ))) {
170        if( c == 'g' ) {
171            configDir = optarg;
172            break;
173        }
174    }
175
176    tr_optind = ind;
177
178    if( configDir == NULL )
179        configDir = tr_getDefaultConfigDir( MY_NAME );
180
181    return configDir;
182}
183
184/**
185 * This is crude compared to using kqueue/inotify/etc, but has the advantage
186 * of working portably on whatever random embedded platform we throw at it.
187 * Since we're only walking a single directory, nonrecursively, a few times
188 * per minute, let's go with this unless users complain about the load
189 */
190static void
191checkForNewFiles( tr_session * session, const char * dirname, time_t oldTime )
192{
193    struct stat sb;
194    DIR * odir;
195
196    if( !stat( dirname, &sb ) && S_ISDIR( sb.st_mode ) && (( odir = opendir( dirname ))) )
197    {
198        struct dirent * d;
199
200        for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
201        {
202            char * filename;
203
204            if( !d->d_name || *d->d_name=='.' ) /* skip dotfiles */
205                continue;
206            if( !strstr( d->d_name, ".torrent" ) ) /* skip non-torrents */
207                continue;
208
209            /* if the file's changed since our last pass, try adding it */
210            filename = tr_buildPath( dirname, d->d_name, NULL );
211            if( !stat( filename, &sb ) && sb.st_mtime >= oldTime )
212            {
213                tr_ctor * ctor = tr_ctorNew( session );
214                int err = tr_ctorSetMetainfoFromFile( ctor, filename );
215                if( !err )
216                    tr_torrentNew( session, ctor, &err );
217                tr_ctorFree( ctor );
218            }
219
220            tr_free( filename );
221        }
222
223        closedir( odir );
224    }
225}
226
227int
228main( int argc, char ** argv )
229{
230    int c;
231    int64_t i;
232    const char * optarg;
233    tr_benc settings;
234    tr_bool foreground = FALSE;
235    tr_bool dumpSettings = FALSE;
236    const char * configDir = NULL;
237    const char * watchDir = NULL;
238    time_t lastCheckTime = 0;
239
240    signal( SIGINT, gotsig );
241    signal( SIGTERM, gotsig );
242#ifndef WIN32
243    signal( SIGQUIT, gotsig );
244    signal( SIGPIPE, SIG_IGN );
245    signal( SIGHUP, SIG_IGN );
246#endif
247
248    /* load settings from defaults + config file */
249    tr_bencInitDict( &settings, 0 );
250    configDir = getConfigDir( argc, (const char**)argv );
251    tr_sessionLoadSettings( &settings, configDir, MY_NAME );
252    tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_ENABLED, 1 );
253
254    /* overwrite settings from the comamndline */
255    tr_optind = 1;
256    while(( c = tr_getopt( getUsage(), argc, (const char**)argv, options, &optarg ))) {
257        switch( c ) {
258            case 'a': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_WHITELIST, optarg );
259                      tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, 1 );
260                      break;
261            case 'b': tr_bencDictAddInt( &settings, TR_PREFS_KEY_BLOCKLIST_ENABLED, 1 );
262                      break;
263            case 'B': tr_bencDictAddInt( &settings, TR_PREFS_KEY_BLOCKLIST_ENABLED, 0 );
264                      break;
265            case 'c': tr_bencDictAddStr( &settings, PREF_KEY_DIR_WATCH, optarg );
266                      tr_bencDictAddInt( &settings, PREF_KEY_DIR_WATCH_ENABLED, 1 );
267                      break;
268            case 'C': tr_bencDictAddInt( &settings, PREF_KEY_DIR_WATCH_ENABLED, 0 );
269                      break;
270            case 'd': dumpSettings = TRUE;
271                      break;
272            case 'f': foreground = TRUE;
273                      break;
274            case 'g': /* handled above */
275                      break;
276            case 'V': /* version */
277                      fprintf(stderr, "Transmission %s\n", LONG_VERSION_STRING);
278                      exit( 0 );
279            case 'p': tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_PORT, atoi( optarg ) );
280                      break;
281            case 't': tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, 1 );
282                      break;
283            case 'T': tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, 0 );
284                      break;
285            case 'u': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_USERNAME, optarg );
286                      break;
287            case 'v': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_PASSWORD, optarg );
288                      break;
289            case 'w': tr_bencDictAddStr( &settings, TR_PREFS_KEY_DOWNLOAD_DIR, optarg );
290                      break;
291            case 'P': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_PORT, atoi( optarg ) );
292                      break;
293            case 'm': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PORT_FORWARDING, 1 );
294                      break;
295            case 'M': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PORT_FORWARDING, 0 );
296                      break;
297            case 'L': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi( optarg ) );
298                      break;
299            case 'l': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_LIMIT_TORRENT, atoi( optarg ) );
300                      break;
301            case 910: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_REQUIRED );
302                      break;
303            case 911: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_PREFERRED );
304                      break;
305            case 912: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_CLEAR_PREFERRED );
306                      break;
307            default:  showUsage( );
308                      break;
309        }
310    }
311
312    if( dumpSettings )
313    {
314        struct evbuffer * buf = tr_getBuffer( );
315
316        tr_bencSaveAsJSON( &settings, buf );
317        fprintf( stderr, "%s", (char*)EVBUFFER_DATA(buf) );
318
319        tr_releaseBuffer( buf );
320        return 0;
321    }
322
323    if( !foreground && tr_daemon( TRUE, FALSE ) < 0 )
324    {
325        fprintf( stderr, "failed to daemonize: %s\n", strerror( errno ) );
326        exit( 1 );
327    }
328
329    /* start the session */
330    mySession = tr_sessionInit( "daemon", configDir, FALSE, &settings );
331
332    if( tr_bencDictFindInt( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, &i ) && i!=0 )
333        tr_ninf( MY_NAME, "requiring authentication" );
334
335    /* maybe add a watchdir */
336    {
337        int64_t doWatch;
338        if( tr_bencDictFindInt( &settings, PREF_KEY_DIR_WATCH_ENABLED, &doWatch )
339            && doWatch
340            && tr_bencDictFindStr( &settings, PREF_KEY_DIR_WATCH, &watchDir )
341            && watchDir
342            && *watchDir )
343        {
344            tr_ninf( MY_NAME, "watching \"%s\" for added .torrent files", watchDir );
345        }
346    }
347
348    /* load the torrents */
349    {
350        tr_ctor * ctor = tr_ctorNew( mySession );
351        tr_torrent ** torrents = tr_sessionLoadTorrents( mySession, ctor, NULL );
352        tr_free( torrents );
353        tr_ctorFree( ctor );
354    }
355
356    while( !closing )
357    {
358        tr_wait( 1000 ); /* sleep one second */
359
360        if( watchDir && ( lastCheckTime + WATCHDIR_POLL_INTERVAL_SECS < time( NULL ) ) )
361        {
362            checkForNewFiles( mySession, watchDir, lastCheckTime );
363            lastCheckTime = time( NULL );
364        }
365    }
366
367    /* shutdown */
368    printf( "Closing transmission session..." );
369    tr_sessionSaveSettings( mySession, configDir, &settings );
370    tr_sessionClose( mySession );
371    printf( " done.\n" );
372
373    /* cleanup */
374    tr_bencFree( &settings );
375    return 0;
376}
Note: See TracBrowser for help on using the repository browser.