source: trunk/daemon/daemon.c @ 12000

Last change on this file since 12000 was 12000, checked in by jordan, 11 years ago

(trunk libT) ensure that the last few log messages actually get logged before shutdown finishes.

  • Property svn:keywords set to Date Rev Author Id
File size: 19.7 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation.
7 *
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9 *
10 * $Id: daemon.c 12000 2011-02-20 19:12:45Z jordan $
11 */
12
13#include <errno.h>
14#include <stdio.h> /* printf */
15#include <stdlib.h> /* exit, atoi */
16#include <string.h> /* strcmp */
17
18#include <fcntl.h> /* open */
19#include <signal.h>
20#ifdef HAVE_SYSLOG
21#include <syslog.h>
22#endif
23#include <unistd.h> /* daemon */
24
25#include <event2/buffer.h>
26
27#include <libtransmission/transmission.h>
28#include <libtransmission/bencode.h>
29#include <libtransmission/tr-getopt.h>
30#include <libtransmission/utils.h>
31#include <libtransmission/version.h>
32
33#include "watch.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#define PREF_KEY_PIDFILE            "pidfile"
40
41#define MEM_K 1024
42#define MEM_K_STR "KiB"
43#define MEM_M_STR "MiB"
44#define MEM_G_STR "GiB"
45#define MEM_T_STR "TiB"
46
47#define DISK_K 1024
48#define DISK_B_STR   "B"
49#define DISK_K_STR "KiB"
50#define DISK_M_STR "MiB"
51#define DISK_G_STR "GiB"
52#define DISK_T_STR "TiB"
53
54#define SPEED_K 1024
55#define SPEED_B_STR   "B/s"
56#define SPEED_K_STR "KiB/s"
57#define SPEED_M_STR "MiB/s"
58#define SPEED_G_STR "GiB/s"
59#define SPEED_T_STR "TiB/s"
60
61static tr_bool paused = FALSE;
62static tr_bool closing = FALSE;
63static tr_session * mySession = NULL;
64
65/***
66****  Config File
67***/
68
69static const char *
70getUsage( void )
71{
72    return "Transmission " LONG_VERSION_STRING
73           "  http://www.transmissionbt.com/\n"
74           "A fast and easy BitTorrent client\n"
75           "\n"
76           MY_NAME " is a headless Transmission session\n"
77           "that can be controlled via transmission-remote\n"
78           "or the web interface.\n"
79           "\n"
80           "Usage: " MY_NAME " [options]";
81}
82
83static const struct tr_option options[] =
84{
85
86    { 'a', "allowed", "Allowed IP addresses. (Default: " TR_DEFAULT_RPC_WHITELIST ")", "a", 1, "<list>" },
87    { 'b', "blocklist", "Enable peer blocklists", "b", 0, NULL },
88    { 'B', "no-blocklist", "Disable peer blocklists", "B", 0, NULL },
89    { 'c', "watch-dir", "Where to watch for new .torrent files", "c", 1, "<directory>" },
90    { 'C', "no-watch-dir", "Disable the watch-dir", "C", 0, NULL },
91    { 941, "incomplete-dir", "Where to store new torrents until they're complete", NULL, 1, "<directory>" },
92    { 942, "no-incomplete-dir", "Don't store incomplete torrents in a different location", NULL, 0, NULL },
93    { 'd', "dump-settings", "Dump the settings and exit", "d", 0, NULL },
94    { 'e', "logfile", "Dump the log messages to this filename", "e", 1, "<filename>" },
95    { 'f', "foreground", "Run in the foreground instead of daemonizing", "f", 0, NULL },
96    { 'g', "config-dir", "Where to look for configuration files", "g", 1, "<path>" },
97    { 'p', "port", "RPC port (Default: " TR_DEFAULT_RPC_PORT_STR ")", "p", 1, "<port>" },
98    { 't', "auth", "Require authentication", "t", 0, NULL },
99    { 'T', "no-auth", "Don't require authentication", "T", 0, NULL },
100    { 'u', "username", "Set username for authentication", "u", 1, "<username>" },
101    { 'v', "password", "Set password for authentication", "v", 1, "<password>" },
102    { 'V', "version", "Show version number and exit", "V", 0, NULL },
103    { 810, "log-error", "Show error messages", NULL, 0, NULL },
104    { 811, "log-info", "Show error and info messages", NULL, 0, NULL },
105    { 812, "log-debug", "Show error, info, and debug messages", NULL, 0, NULL },
106    { 'w', "download-dir", "Where to save downloaded data", "w", 1, "<path>" },
107    { 800, "paused", "Pause all torrents on startup", NULL, 0, NULL },
108    { 'o', "dht", "Enable distributed hash tables (DHT)", "o", 0, NULL },
109    { 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", 0, NULL },
110    { 'y', "lpd", "Enable local peer discovery (LPD)", "y", 0, NULL },
111    { 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", 0, NULL },
112    { 'P', "peerport", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "P", 1, "<port>" },
113    { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
114    { 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
115    { 'L', "peerlimit-global", "Maximum overall number of peers (Default: " TR_DEFAULT_PEER_LIMIT_GLOBAL_STR ")", "L", 1, "<limit>" },
116    { 'l', "peerlimit-torrent", "Maximum number of peers per torrent (Default: " TR_DEFAULT_PEER_LIMIT_TORRENT_STR ")", "l", 1, "<limit>" },
117    { 910, "encryption-required",  "Encrypt all peer connections", "er", 0, NULL },
118    { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
119    { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
120    { 'i', "bind-address-ipv4", "Where to listen for peer connections", "i", 1, "<ipv4 addr>" },
121    { 'I', "bind-address-ipv6", "Where to listen for peer connections", "I", 1, "<ipv6 addr>" },
122    { 'r', "rpc-bind-address", "Where to listen for RPC connections", "r", 1, "<ipv4 addr>" },
123    { 953, "global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio", "gsr", 1, "ratio" },
124    { 954, "no-global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio", "GSR", 0, NULL },
125    { 'x', "pid-file", "Enable PID file", "x", 1, "<pid-file>" },
126    { 0, NULL, NULL, NULL, 0, NULL }
127};
128
129static void
130showUsage( void )
131{
132    tr_getopt_usage( MY_NAME, getUsage( ), options );
133    exit( 0 );
134}
135
136static void
137gotsig( int sig )
138{
139    switch( sig )
140    {
141        case SIGHUP:
142        {
143            tr_benc settings;
144            const char * configDir = tr_sessionGetConfigDir( mySession );
145            tr_inf( "Reloading settings from \"%s\"", configDir );
146            tr_bencInitDict( &settings, 0 );
147            tr_bencDictAddBool( &settings, TR_PREFS_KEY_RPC_ENABLED, TRUE );
148            tr_sessionLoadSettings( &settings, configDir, MY_NAME );
149            tr_sessionSet( mySession, &settings );
150            tr_bencFree( &settings );
151            tr_sessionReloadBlocklists( mySession );
152            break;
153        }
154
155        default:
156            closing = TRUE;
157            break;
158    }
159}
160
161#if defined(WIN32)
162 #define USE_NO_DAEMON
163#elif !defined(HAVE_DAEMON) || defined(__UCLIBC__)
164 #define USE_TR_DAEMON
165#else
166 #define USE_OS_DAEMON
167#endif
168
169static int
170tr_daemon( int nochdir, int noclose )
171{
172#if defined(USE_OS_DAEMON)
173
174    return daemon( nochdir, noclose );
175
176#elif defined(USE_TR_DAEMON)
177
178    /* this is loosely based off of glibc's daemon() implementation
179     * http://sourceware.org/git/?p=glibc.git;a=blob_plain;f=misc/daemon.c */
180
181    switch( fork( ) ) {
182        case -1: return -1;
183        case 0: break;
184        default: _exit(0);
185    }
186
187    if( setsid( ) == -1 )
188        return -1;
189
190    if( !nochdir )
191        chdir( "/" );
192
193    if( !noclose ) {
194        int fd = open( "/dev/null", O_RDWR, 0 );
195        dup2( fd, STDIN_FILENO );
196        dup2( fd, STDOUT_FILENO );
197        dup2( fd, STDERR_FILENO );
198        close( fd );
199    }
200
201    return 0;
202
203#else /* USE_NO_DAEMON */
204    return 0;
205#endif
206}
207
208static const char*
209getConfigDir( int argc, const char ** argv )
210{
211    int c;
212    const char * configDir = NULL;
213    const char * optarg;
214    const int ind = tr_optind;
215
216    while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg ))) {
217        if( c == 'g' ) {
218            configDir = optarg;
219            break;
220        }
221    }
222
223    tr_optind = ind;
224
225    if( configDir == NULL )
226        configDir = tr_getDefaultConfigDir( MY_NAME );
227
228    return configDir;
229}
230
231static void
232onFileAdded( tr_session * session, const char * dir, const char * file )
233{
234    char * filename = tr_buildPath( dir, file, NULL );
235    tr_ctor * ctor = tr_ctorNew( session );
236    int err = tr_ctorSetMetainfoFromFile( ctor, filename );
237
238    if( !err )
239    {
240        tr_torrentNew( ctor, &err );
241
242        if( err == TR_PARSE_ERR )
243            tr_err( "Error parsing .torrent file \"%s\"", file );
244        else
245        {
246            tr_bool trash = FALSE;
247            int test = tr_ctorGetDeleteSource( ctor, &trash );
248
249            tr_inf( "Parsing .torrent file successful \"%s\"", file );
250
251            if( !test && trash )
252            {
253                tr_inf( "Deleting input .torrent file \"%s\"", file );
254                if( remove( filename ) )
255                    tr_err( "Error deleting .torrent file: %s", tr_strerror( errno ) );
256            }
257            else
258            {
259                char * new_filename = tr_strdup_printf( "%s.added", filename );
260                rename( filename, new_filename );
261                tr_free( new_filename );
262            }
263        }
264    }
265
266    tr_ctorFree( ctor );
267    tr_free( filename );
268}
269
270static void
271printMessage( FILE * logfile, int level, const char * name, const char * message, const char * file, int line )
272{
273    if( logfile != NULL )
274    {
275        char timestr[64];
276        tr_getLogTimeStr( timestr, sizeof( timestr ) );
277        if( name )
278            fprintf( logfile, "[%s] %s %s (%s:%d)\n", timestr, name, message, file, line );
279        else
280            fprintf( logfile, "[%s] %s (%s:%d)\n", timestr, message, file, line );
281    }
282#ifdef HAVE_SYSLOG
283    else /* daemon... write to syslog */
284    {
285        int priority;
286
287        /* figure out the syslog priority */
288        switch( level ) {
289            case TR_MSG_ERR: priority = LOG_ERR; break;
290            case TR_MSG_DBG: priority = LOG_DEBUG; break;
291            default: priority = LOG_INFO; break;
292        }
293
294        if( name )
295            syslog( priority, "%s %s (%s:%d)", name, message, file, line );
296        else
297            syslog( priority, "%s (%s:%d)", message, file, line );
298    }
299#endif
300}
301
302static void
303pumpLogMessages( FILE * logfile )
304{
305    const tr_msg_list * l;
306    tr_msg_list * list = tr_getQueuedMessages( );
307
308    for( l=list; l!=NULL; l=l->next )
309        printMessage( logfile, l->level, l->name, l->message, l->file, l->line );
310
311    if( logfile != NULL )
312        fflush( logfile );
313
314    tr_freeMessageList( list );
315}
316
317static tr_rpc_callback_status
318on_rpc_callback( tr_session            * session UNUSED,
319                 tr_rpc_callback_type    type,
320                 struct tr_torrent     * tor UNUSED,
321                 void                  * user_data UNUSED )
322{
323    if( type == TR_RPC_SESSION_CLOSE )
324        closing = TRUE;
325    return TR_RPC_OK;
326}
327
328int
329main( int argc, char ** argv )
330{
331    int c;
332    const char * optarg;
333    tr_benc settings;
334    tr_bool boolVal;
335    tr_bool loaded;
336    tr_bool foreground = FALSE;
337    tr_bool dumpSettings = FALSE;
338    const char * configDir = NULL;
339    const char * pid_filename;
340    dtr_watchdir * watchdir = NULL;
341    FILE * logfile = NULL;
342    tr_bool pidfile_created = FALSE;
343
344    signal( SIGINT, gotsig );
345    signal( SIGTERM, gotsig );
346#ifndef WIN32
347    signal( SIGHUP, gotsig );
348#endif
349
350    /* load settings from defaults + config file */
351    tr_bencInitDict( &settings, 0 );
352    tr_bencDictAddBool( &settings, TR_PREFS_KEY_RPC_ENABLED, TRUE );
353    configDir = getConfigDir( argc, (const char**)argv );
354    loaded = tr_sessionLoadSettings( &settings, configDir, MY_NAME );
355
356    /* overwrite settings from the comamndline */
357    tr_optind = 1;
358    while(( c = tr_getopt( getUsage(), argc, (const char**)argv, options, &optarg ))) {
359        switch( c ) {
360            case 'a': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_WHITELIST, optarg );
361                      tr_bencDictAddBool( &settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, TRUE );
362                      break;
363            case 'b': tr_bencDictAddBool( &settings, TR_PREFS_KEY_BLOCKLIST_ENABLED, TRUE );
364                      break;
365            case 'B': tr_bencDictAddBool( &settings, TR_PREFS_KEY_BLOCKLIST_ENABLED, FALSE );
366                      break;
367            case 'c': tr_bencDictAddStr( &settings, PREF_KEY_DIR_WATCH, optarg );
368                      tr_bencDictAddBool( &settings, PREF_KEY_DIR_WATCH_ENABLED, TRUE );
369                      break;
370            case 'C': tr_bencDictAddBool( &settings, PREF_KEY_DIR_WATCH_ENABLED, FALSE );
371                      break;
372            case 941: tr_bencDictAddStr( &settings, TR_PREFS_KEY_INCOMPLETE_DIR, optarg );
373                      tr_bencDictAddBool( &settings, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, TRUE );
374                      break;
375            case 942: tr_bencDictAddBool( &settings, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, FALSE );
376                      break;
377            case 'd': dumpSettings = TRUE;
378                      break;
379            case 'e': logfile = fopen( optarg, "a+" );
380                      if( logfile == NULL )
381                          fprintf( stderr, "Couldn't open \"%s\": %s\n", optarg, tr_strerror( errno ) );
382                      break;
383            case 'f': foreground = TRUE;
384                      break;
385            case 'g': /* handled above */
386                      break;
387            case 'V': /* version */
388                      fprintf(stderr, "%s %s\n", MY_NAME, LONG_VERSION_STRING);
389                      exit( 0 );
390            case 'o': tr_bencDictAddBool( &settings, TR_PREFS_KEY_DHT_ENABLED, TRUE );
391                      break;
392            case 'O': tr_bencDictAddBool( &settings, TR_PREFS_KEY_DHT_ENABLED, FALSE );
393                      break;
394            case 'p': tr_bencDictAddInt( &settings, TR_PREFS_KEY_RPC_PORT, atoi( optarg ) );
395                      break;
396            case 't': tr_bencDictAddBool( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, TRUE );
397                      break;
398            case 'T': tr_bencDictAddBool( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, FALSE );
399                      break;
400            case 'u': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_USERNAME, optarg );
401                      break;
402            case 'v': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_PASSWORD, optarg );
403                      break;
404            case 'w': tr_bencDictAddStr( &settings, TR_PREFS_KEY_DOWNLOAD_DIR, optarg );
405                      break;
406            case 'P': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_PORT, atoi( optarg ) );
407                      break;
408            case 'm': tr_bencDictAddBool( &settings, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
409                      break;
410            case 'M': tr_bencDictAddBool( &settings, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
411                      break;
412            case 'L': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi( optarg ) );
413                      break;
414            case 'l': tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_LIMIT_TORRENT, atoi( optarg ) );
415                      break;
416            case 800: paused = TRUE;
417                      break;
418            case 910: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_REQUIRED );
419                      break;
420            case 911: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_PREFERRED );
421                      break;
422            case 912: tr_bencDictAddInt( &settings, TR_PREFS_KEY_ENCRYPTION, TR_CLEAR_PREFERRED );
423                      break;
424            case 'i': tr_bencDictAddStr( &settings, TR_PREFS_KEY_BIND_ADDRESS_IPV4, optarg );
425                      break;
426            case 'I': tr_bencDictAddStr( &settings, TR_PREFS_KEY_BIND_ADDRESS_IPV6, optarg );
427                      break;
428            case 'r': tr_bencDictAddStr( &settings, TR_PREFS_KEY_RPC_BIND_ADDRESS, optarg );
429                      break;
430            case 953: tr_bencDictAddReal( &settings, TR_PREFS_KEY_RATIO, atof(optarg) );
431                      tr_bencDictAddBool( &settings, TR_PREFS_KEY_RATIO_ENABLED, TRUE );
432                      break;
433            case 954: tr_bencDictAddBool( &settings, TR_PREFS_KEY_RATIO_ENABLED, FALSE );
434                      break;
435            case 'x': tr_bencDictAddStr( &settings, PREF_KEY_PIDFILE, optarg );
436                      break;
437            case 'y': tr_bencDictAddBool( &settings, TR_PREFS_KEY_LPD_ENABLED, TRUE );
438                      break;
439            case 'Y': tr_bencDictAddBool( &settings, TR_PREFS_KEY_LPD_ENABLED, FALSE );
440                      break;
441            case 810: tr_bencDictAddInt( &settings,  TR_PREFS_KEY_MSGLEVEL, TR_MSG_ERR );
442                      break;
443            case 811: tr_bencDictAddInt( &settings,  TR_PREFS_KEY_MSGLEVEL, TR_MSG_INF );
444                      break;
445            case 812: tr_bencDictAddInt( &settings,  TR_PREFS_KEY_MSGLEVEL, TR_MSG_DBG );
446                      break;
447            default:  showUsage( );
448                      break;
449        }
450    }
451
452    if( foreground && !logfile )
453        logfile = stderr;
454
455    if( !loaded )
456    {
457        printMessage( logfile, TR_MSG_ERR, MY_NAME, "Error loading config file -- exiting.", __FILE__, __LINE__ );
458        return -1;
459    }
460
461    if( dumpSettings )
462    {
463        char * str = tr_bencToStr( &settings, TR_FMT_JSON, NULL );
464        fprintf( stderr, "%s", str );
465        tr_free( str );
466        return 0;
467    }
468
469    if( !foreground && tr_daemon( TRUE, FALSE ) < 0 )
470    {
471        char buf[256];
472        tr_snprintf( buf, sizeof( buf ), "Failed to daemonize: %s", tr_strerror( errno ) );
473        printMessage( logfile, TR_MSG_ERR, MY_NAME, buf, __FILE__, __LINE__ );
474        exit( 1 );
475    }
476
477    /* start the session */
478    tr_formatter_mem_init( MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR );
479    tr_formatter_size_init( DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR );
480    tr_formatter_speed_init( SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR );
481    mySession = tr_sessionInit( "daemon", configDir, TRUE, &settings );
482    tr_sessionSetRPCCallback( mySession, on_rpc_callback, NULL );
483    tr_ninf( NULL, "Using settings from \"%s\"", configDir );
484    tr_sessionSaveSettings( mySession, configDir, &settings );
485
486    pid_filename = NULL;
487    tr_bencDictFindStr( &settings, PREF_KEY_PIDFILE, &pid_filename );
488    if( pid_filename && *pid_filename )
489    {
490        FILE * fp = fopen( pid_filename, "w+" );
491        if( fp != NULL )
492        {
493            fprintf( fp, "%d", (int)getpid() );
494            fclose( fp );
495            tr_inf( "Saved pidfile \"%s\"", pid_filename );
496            pidfile_created = TRUE;
497        }
498        else
499            tr_err( "Unable to save pidfile \"%s\": %s", pid_filename, strerror( errno ) );
500    }
501
502    if( tr_bencDictFindBool( &settings, TR_PREFS_KEY_RPC_AUTH_REQUIRED, &boolVal ) && boolVal )
503        tr_ninf( MY_NAME, "requiring authentication" );
504
505    /* maybe add a watchdir */
506    {
507        const char * dir;
508
509        if( tr_bencDictFindBool( &settings, PREF_KEY_DIR_WATCH_ENABLED, &boolVal )
510            && boolVal
511            && tr_bencDictFindStr( &settings, PREF_KEY_DIR_WATCH, &dir )
512            && dir
513            && *dir )
514        {
515            tr_inf( "Watching \"%s\" for new .torrent files", dir );
516            watchdir = dtr_watchdir_new( mySession, dir, onFileAdded );
517        }
518    }
519
520    /* load the torrents */
521    {
522        tr_torrent ** torrents;
523        tr_ctor * ctor = tr_ctorNew( mySession );
524        if( paused )
525            tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
526        torrents = tr_sessionLoadTorrents( mySession, ctor, NULL );
527        tr_free( torrents );
528        tr_ctorFree( ctor );
529    }
530
531#ifdef HAVE_SYSLOG
532    if( !foreground )
533        openlog( MY_NAME, LOG_CONS|LOG_PID, LOG_DAEMON );
534#endif
535
536    while( !closing ) {
537        tr_wait_msec( 1000 ); /* sleep one second */
538        dtr_watchdir_update( watchdir );
539        pumpLogMessages( logfile );
540    }
541
542    printf( "Closing transmission session..." );
543    tr_sessionSaveSettings( mySession, configDir, &settings );
544    dtr_watchdir_free( watchdir );
545    tr_sessionClose( mySession );
546    pumpLogMessages( logfile );
547    printf( " done.\n" );
548
549    /* shutdown */
550#if HAVE_SYSLOG
551    if( !foreground )
552    {
553        syslog( LOG_INFO, "%s", "Closing session" );
554        closelog( );
555    }
556#endif
557
558    /* cleanup */
559    if( pidfile_created )
560        remove( pid_filename );
561    tr_bencFree( &settings );
562    return 0;
563}
Note: See TracBrowser for help on using the repository browser.