source: trunk/daemon/daemon.c @ 6409

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

(daemon) fix typo in daemon's help page.

  • Property svn:keywords set to Date Rev Author Id
File size: 13.3 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.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 6409 2008-07-27 19:04:29Z 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 <fcntl.h> /* open */
20#include <signal.h>
21#include <unistd.h> /* daemon, getcwd */
22
23#include <libtransmission/transmission.h>
24#include <libtransmission/bencode.h>
25#include <libtransmission/rpc.h>
26#include <libtransmission/tr-getopt.h>
27#include <libtransmission/utils.h>
28#include <libtransmission/version.h>
29
30#define MY_NAME "transmission-daemon"
31
32static int closing = FALSE;
33static tr_handle * mySession;
34static char myConfigFilename[MAX_PATH_LENGTH];
35
36#define KEY_BLOCKLIST        "blocklist-enabled"
37#define KEY_DOWNLOAD_DIR     "download-dir"
38#define KEY_ENCRYPTION       "encryption"
39#define KEY_PEER_LIMIT       "max-peers-global"
40#define KEY_PEER_PORT        "peer-port"
41#define KEY_PORT_FORWARDING  "port-forwarding-enabled"
42#define KEY_PEX_ENABLED      "pex-enabled"
43#define KEY_AUTH_REQUIRED    "rpc-authentication-required"
44#define KEY_USERNAME         "rpc-username"
45#define KEY_PASSWORD         "rpc-password"
46#define KEY_ACL              "rpc-access-control-list"
47#define KEY_RPC_PORT         "rpc-port"
48#define KEY_DSPEED           "download-limit"
49#define KEY_DSPEED_ENABLED   "download-limit-enabled"
50#define KEY_USPEED           "upload-limit"
51#define KEY_USPEED_ENABLED   "upload-limit-enabled"
52
53#define CONFIG_FILE          "settings.json"
54
55/***
56****  Config File
57***/
58
59static void
60replaceInt( tr_benc * dict, const char * key, int64_t value )
61{
62    tr_bencDictRemove( dict, key );
63    tr_bencDictAddInt( dict, key, value );
64}
65static void
66replaceStr( tr_benc * dict, const char * key, const char* value )
67{
68    tr_bencDictRemove( dict, key );
69    tr_bencDictAddStr( dict, key, value );
70}
71static void
72saveState( tr_session * s )
73{
74    int i, n = 0;
75    char * strs[4];
76
77    tr_benc d;
78    if( tr_bencLoadJSONFile( myConfigFilename, &d ) )
79        tr_bencInitDict( &d, 16 );
80   
81    replaceInt( &d, KEY_BLOCKLIST,       tr_blocklistIsEnabled( s ) );
82    replaceStr( &d, KEY_DOWNLOAD_DIR,    tr_sessionGetDownloadDir( s ) );
83    replaceInt( &d, KEY_PEER_LIMIT,      tr_sessionGetPeerLimit( s ) );
84    replaceInt( &d, KEY_PEER_PORT,       tr_sessionGetPeerPort( s ) );
85    replaceInt( &d, KEY_PORT_FORWARDING, tr_sessionIsPortForwardingEnabled( s ) );
86    replaceInt( &d, KEY_PEX_ENABLED,     tr_sessionIsPexEnabled( s ) );
87    replaceStr( &d, KEY_USERNAME,        strs[n++] = tr_sessionGetRPCUsername( s ) );
88    replaceStr( &d, KEY_PASSWORD,        strs[n++] = tr_sessionGetRPCPassword( s ) );
89    replaceStr( &d, KEY_ACL,             strs[n++] = tr_sessionGetRPCACL( s ) );
90    replaceInt( &d, KEY_RPC_PORT,        tr_sessionGetRPCPort( s ) );
91    replaceInt( &d, KEY_AUTH_REQUIRED,   tr_sessionIsRPCPasswordEnabled( s ) );
92    replaceInt( &d, KEY_DSPEED,          tr_sessionGetSpeedLimit( s, TR_DOWN ) );
93    replaceInt( &d, KEY_DSPEED_ENABLED,  tr_sessionIsSpeedLimitEnabled( s, TR_DOWN ) );
94    replaceInt( &d, KEY_USPEED,          tr_sessionGetSpeedLimit( s, TR_UP ) );
95    replaceInt( &d, KEY_USPEED_ENABLED,  tr_sessionIsSpeedLimitEnabled( s, TR_UP ) );
96    replaceInt( &d, KEY_ENCRYPTION,      tr_sessionGetEncryption( s ) );
97
98    tr_bencSaveJSONFile( myConfigFilename, &d );
99    tr_bencFree( &d );
100    tr_ninf( MY_NAME, "saved \"%s\"", myConfigFilename );
101
102    for( i=0; i<n; ++i )
103        tr_free( strs[i] );
104}
105
106static void
107getConfigInt( tr_benc     * dict,
108              const char  * key,
109              int         * setme,
110              int           defaultVal )
111{
112    if( *setme < 0 ) {
113        int64_t i;
114        if( tr_bencDictFindInt( dict, key, &i ) )
115            *setme = i;
116        else
117            *setme = defaultVal;
118    }
119}
120
121static void
122getConfigStr( tr_benc      * dict,
123              const char   * key,
124              const char  ** setme,
125              const char   * defaultVal )
126{
127    if( !*setme ) {
128        const char * s;
129        if( tr_bencDictFindStr( dict, key, &s ) )
130            *setme = s;
131        else
132            *setme = defaultVal;
133    }
134}
135
136/**
137 * @param port      port number, or -1 if not set in the command line
138 * @param auth      TRUE, FALSE, or -1 if not set on the command line
139 * @param blocklist TRUE, FALSE, or -1 if not set on the command line
140 */
141static void
142session_init( const char * configDir, const char * downloadDir,
143              int rpcPort, const char * acl,
144              int authRequired, const char * username, const char * password,
145              int blocklistEnabled )
146{
147    char mycwd[MAX_PATH_LENGTH];
148    tr_benc state, *dict = NULL;
149    int peerPort=-1, peers=-1;
150    int pexEnabled = -1;
151    int fwdEnabled = -1;
152    int upLimit=-1, upLimited=-1, downLimit=-1, downLimited=-1;
153    int encryption = -1;
154    tr_ctor * ctor;
155    tr_torrent ** torrents;
156
157    if( !tr_bencLoadJSONFile( myConfigFilename, &state ) )
158        dict = &state;
159
160    /***
161    ****  Decide on which values to pass into tr_sessionInitFull().
162    ****  The command-line arguments are given precedence and
163    ****  the state file from the previous session is used as a fallback.
164    ****  If neither of those can be found, the TR_DEFAULT fields are used .
165    ***/
166
167    getcwd( mycwd, sizeof( mycwd ) );
168    getConfigStr( dict, KEY_DOWNLOAD_DIR,    &downloadDir,       mycwd );
169    getConfigInt( dict, KEY_PEX_ENABLED,     &pexEnabled,        TR_DEFAULT_PEX_ENABLED );
170    getConfigInt( dict, KEY_PORT_FORWARDING, &fwdEnabled,        TR_DEFAULT_PORT_FORWARDING_ENABLED );
171    getConfigInt( dict, KEY_PEER_PORT,       &peerPort,          TR_DEFAULT_PORT );
172    getConfigInt( dict, KEY_DSPEED,          &downLimit,         100 );
173    getConfigInt( dict, KEY_DSPEED_ENABLED,  &downLimited,       FALSE );
174    getConfigInt( dict, KEY_USPEED,          &upLimit,           100 );
175    getConfigInt( dict, KEY_USPEED_ENABLED,  &upLimited,         FALSE );
176    getConfigInt( dict, KEY_PEER_LIMIT,      &peers,             TR_DEFAULT_GLOBAL_PEER_LIMIT );
177    getConfigInt( dict, KEY_BLOCKLIST,       &blocklistEnabled,  TR_DEFAULT_BLOCKLIST_ENABLED );
178    getConfigInt( dict, KEY_RPC_PORT,        &rpcPort,           TR_DEFAULT_RPC_PORT );
179    getConfigStr( dict, KEY_ACL,             &acl,               TR_DEFAULT_RPC_ACL );
180    getConfigInt( dict, KEY_AUTH_REQUIRED,   &authRequired,      FALSE );
181    getConfigStr( dict, KEY_USERNAME,        &username,          NULL );
182    getConfigStr( dict, KEY_PASSWORD,        &password,          NULL );
183    getConfigInt( dict, KEY_ENCRYPTION,      &encryption,        TR_ENCRYPTION_PREFERRED );
184
185    /***
186    ****
187    ***/
188
189    /* start the session */
190    mySession = tr_sessionInitFull( configDir, "daemon", downloadDir,
191                                    pexEnabled, fwdEnabled, peerPort,
192                                    encryption,
193                                    upLimited, upLimit,
194                                    downLimited, downLimit,
195                                    peers,
196                                    TR_MSG_INF, 0,
197                                    blocklistEnabled,
198                                    TR_DEFAULT_PEER_SOCKET_TOS,
199                                    TRUE, rpcPort, acl, authRequired, username, password,
200                                    TR_DEFAULT_PROXY_ENABLED,
201                                    TR_DEFAULT_PROXY,
202                                    TR_DEFAULT_PROXY_PORT,
203                                    TR_DEFAULT_PROXY_TYPE,
204                                    TR_DEFAULT_PROXY_AUTH_ENABLED,
205                                    TR_DEFAULT_PROXY_USERNAME,
206                                    TR_DEFAULT_PROXY_PASSWORD );
207
208
209    if( authRequired )
210        tr_ninf( MY_NAME, "requiring authentication" );
211
212    /* load the torrents */
213    ctor = tr_ctorNew( mySession );
214    torrents = tr_sessionLoadTorrents( mySession, ctor, NULL );
215    tr_free( torrents );
216    tr_ctorFree( ctor );
217
218    if( dict )
219        tr_bencFree( &state );
220}
221
222static const char *
223getUsage( void )
224{
225    return "Transmission "LONG_VERSION_STRING"  http://www.transmissionbt.com/\n"
226           "A fast and easy BitTorrent client\n"
227           "\n"
228           MY_NAME" is a headless Transmission session\n"
229           "that can be controlled via transmission-remote or Clutch.\n"
230           "\n"
231           "Usage: "MY_NAME" [options]";
232}
233
234const struct tr_option options[] = {
235    { 'a', "acl",       "Access Control List.  (Default: "TR_DEFAULT_RPC_ACL")", "a", 1, "<list>" },
236    { 'b', "blocklist", "Enable peer blocklists",             "b", 0, NULL },
237    { 'B', "no-blocklist", "Disable peer blocklists",         "B", 0, NULL },
238    { 'f', "foreground", "Run in the foreground instead of daemonizing", "f", 0, NULL },
239    { 'g', "config-dir",   "Where to look for configuration files", "g", 1, "<path>" },
240    { 'p', "port", "RPC port (Default: "TR_DEFAULT_RPC_PORT_STR")", "p", 1, "<port>" },
241    { 't', "auth",         "Require authentication",          "t", 0, NULL },
242    { 'T', "no-auth",      "Don't require authentication",    "T", 0, NULL },
243    { 'u', "username",     "Set username for authentication", "u", 1, "<username>" },
244    { 'v', "password",     "Set password for authentication", "v", 1, "<password>" },
245    { 'w', "download-dir", "Where to save downloaded data",   "w", 1, "<path>" },
246    { 0, NULL, NULL, NULL, 0, NULL }
247};
248
249static void
250showUsage( void )
251{
252    tr_getopt_usage( MY_NAME, getUsage(), options );
253    exit( 0 );
254}
255
256static void
257readargs( int argc, const char ** argv,
258          int * nofork, const char ** configDir, const char ** downloadDir,
259          int * rpcPort, const char ** acl,
260          int * authRequired, const char ** username, const char ** password,
261          int * blocklistEnabled )
262{
263    int c;
264    const char * optarg;
265    while(( c = tr_getopt( getUsage(), argc, argv, options, &optarg )))
266    {
267        switch( c )
268        {
269            case 'a': *acl = optarg; break;
270            case 'b': *blocklistEnabled = 1; break;
271            case 'B': *blocklistEnabled = 0; break;
272            case 'f': *nofork = 1; break;
273            case 'g': *configDir = optarg; break;
274            case 'p': *rpcPort = atoi( optarg ); break;
275            case 't': *authRequired = TRUE; break;
276            case 'T': *authRequired = FALSE; break;
277            case 'u': *username = optarg; break; 
278            case 'v': *password = optarg; break; 
279            case 'w': *downloadDir = optarg; break;
280            default: showUsage( ); break;
281        }
282    }
283}
284
285static void
286gotsig( int sig UNUSED )
287{
288    closing = TRUE;
289}
290
291#if !defined(HAVE_DAEMON)
292static int
293daemon( int nochdir, int noclose )
294{
295    switch( fork( ) ) {
296        case 0:
297            break;
298        case -1:
299            tr_nerr( MY_NAME, "Error daemonizing (fork)! %d - %s", errno, strerror(errno) );
300            return -1;
301        default:
302            _exit(0);
303    }
304
305    if( setsid() < 0 ) {
306        tr_nerr( MY_NAME, "Error daemonizing (setsid)! %d - %s", errno, strerror(errno) );
307        return -1;
308    }
309
310    switch( fork( ) ) {
311        case 0:
312            break;
313        case -1:
314            tr_nerr( MY_NAME, "Error daemonizing (fork2)! %d - %s", errno, strerror(errno) );
315            return -1;
316        default:
317            _exit(0);
318    }
319
320    if( !nochdir && 0 > chdir( "/" ) ) {
321        tr_nerr( MY_NAME, "Error daemonizing (chdir)! %d - %s", errno, strerror(errno) );
322        return -1;
323    }
324
325    if( !noclose ) {
326        int fd;
327        if((( fd = open("/dev/null", O_RDONLY))) != 0 ) {
328            dup2( fd,  0 );
329            close( fd );
330        }
331        if((( fd = open("/dev/null", O_WRONLY))) != 1 ) {
332            dup2( fd, 1 );
333            close( fd );
334        }
335        if((( fd = open("/dev/null", O_WRONLY))) != 2 ) {
336            dup2( fd, 2 );
337            close( fd );
338        }
339    }
340
341    return 0;
342}
343#endif
344
345int
346main( int argc, char ** argv )
347{
348    int nofork = 0;
349    int rpcPort = -1;
350    int authRequired = -1;
351    int blocklistEnabled = -1;
352    char * freeme = NULL;
353    const char * configDir = NULL;
354    const char * downloadDir = NULL;
355    const char * acl = NULL;
356    const char * username = NULL;
357    const char * password = NULL;
358
359    signal( SIGINT, gotsig );
360    signal( SIGQUIT, gotsig );
361    signal( SIGTERM, gotsig );
362    signal( SIGPIPE, SIG_IGN );
363    signal( SIGHUP, SIG_IGN );
364
365    readargs( argc, (const char**)argv, &nofork, &configDir, &downloadDir,
366              &rpcPort, &acl, &authRequired, &username, &password,
367              &blocklistEnabled );
368    if( configDir == NULL )
369        configDir = freeme = tr_strdup_printf( "%s-daemon", tr_getDefaultConfigDir() );
370    tr_buildPath( myConfigFilename, sizeof( myConfigFilename ),
371                  configDir, CONFIG_FILE, NULL );
372
373    if( !nofork ) {
374        if( 0 > daemon( 1, 0 ) ) {
375            fprintf( stderr, "failed to daemonize: %s\n", strerror( errno ) );
376            exit( 1 );
377        }
378    }
379
380    session_init( configDir, downloadDir,
381                  rpcPort, acl, authRequired, username, password,
382                  blocklistEnabled );
383
384    while( !closing )
385        sleep( 1 );
386
387    saveState( mySession );
388    printf( "Closing transmission session..." );
389    tr_sessionClose( mySession );
390    printf( " done.\n" );
391
392    tr_free( freeme );
393    return 0;
394}
Note: See TracBrowser for help on using the repository browser.