source: trunk/daemon/daemon.c @ 6238

Last change on this file since 6238 was 6238, checked in by charles, 14 years ago

(daemon) #1047: man page inconsistencies

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