source: trunk/cli/cli.c @ 7230

Last change on this file since 7230 was 7230, checked in by charles, 12 years ago

(cli) #1547: free memory read

  • Property svn:keywords set to Date Rev Author Id
File size: 19.3 KB
Line 
1/******************************************************************************
2 * $Id: cli.c 7230 2008-12-02 02:04:32Z charles $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <unistd.h>
29#include <signal.h>
30
31#include <libtransmission/transmission.h>
32#include <libtransmission/bencode.h>
33#include <libtransmission/makemeta.h>
34#include <libtransmission/tr-getopt.h>
35#include <libtransmission/utils.h> /* tr_wait */
36#include <libtransmission/web.h> /* tr_webRun */
37
38#define LINEWIDTH 80
39#define MY_NAME "transmission-cli"
40
41static int          showInfo         = 0;
42static int          showScrape       = 0;
43static int          isPrivate        = 0;
44static int          verboseLevel     = 0;
45static int          encryptionMode   = TR_DEFAULT_ENCRYPTION;
46static int          peerPort         = TR_DEFAULT_PORT;
47static int          peerSocketTOS    = TR_DEFAULT_PEER_SOCKET_TOS;
48static int          blocklistEnabled = TR_DEFAULT_BLOCKLIST_ENABLED;
49static int          uploadLimit      = 20;
50static int          downloadLimit    = -1;
51static int          natTraversal     = TR_DEFAULT_PORT_FORWARDING_ENABLED;
52static int          verify           = 0;
53static sig_atomic_t gotsig           = 0;
54static sig_atomic_t manualUpdate     = 0;
55
56static const char * torrentPath  = NULL;
57static const char * downloadDir  = NULL;
58static const char * finishCall   = NULL;
59static const char * announce     = NULL;
60static const char * configdir    = NULL;
61static const char * sourceFile   = NULL;
62static const char * comment      = NULL;
63
64static const struct tr_option options[] =
65{
66    { 'a', "announce",             "Set the new torrent's announce URL",
67      "a",  1, "<url>"     },
68    { 'b', "blocklist",            "Enable peer blocklists",
69      "b",  0, NULL        },
70    { 'B', "no-blocklist",         "Disable peer blocklists",
71      "B",  0, NULL        },
72    { 'c', "comment",              "Set the new torrent's comment",
73      "c",  1, "<comment>" },
74    { 'd', "downlimit",            "Set max download speed in KB/s",
75      "d",  1, "<speed>"   },
76    { 'D', "no-downlimit",         "Don't limit the download speed",
77      "D",  0, NULL        },
78    { 910, "encryption-required",  "Encrypt all peer connections",
79      "er", 0, NULL        },
80    { 911, "encryption-preferred", "Prefer encrypted peer connections",
81      "ep", 0, NULL        },
82    { 912, "encryption-tolerated", "Prefer unencrypted peer connections",
83      "et", 0, NULL        },
84    { 'f', "finish",               "Run a script when the torrent finishes",
85      "f", 1, "<script>" },
86    { 'g', "config-dir",           "Where to find configuration files",
87      "g", 1, "<path>" },
88    { 'i', "info",                 "Show torrent details and exit",
89      "i",  0, NULL        },
90    { 'm', "portmap",              "Enable portmapping via NAT-PMP or UPnP",
91      "m",  0, NULL        },
92    { 'M', "no-portmap",           "Disable portmapping",
93      "M",  0, NULL        },
94    { 'n', "new",                  "Create a new torrent",
95      "n", 1, "<source>" },
96    { 'p', "port",
97      "Port for incoming peers (Default: " TR_DEFAULT_PORT_STR ")",
98      "p", 1, "<port>" },
99    { 'r', "private",              "Set the new torrent's 'private' flag",
100      "r",  0, NULL        },
101    { 's', "scrape",               "Scrape the torrent and exit",
102      "s",  0, NULL        },
103    { 't', "tos",
104      "Peer socket TOS (0 to 255, default=" TR_DEFAULT_PEER_SOCKET_TOS_STR
105      ")",
106      "t", 1, "<tos>" },
107    { 'u', "uplimit",              "Set max upload speed in KB/s",
108      "u",  1, "<speed>"   },
109    { 'U', "no-uplimit",           "Don't limit the upload speed",
110      "U",  0, NULL        },
111    { 'v', "verify",               "Verify the specified torrent",
112      "v",  0, NULL        },
113    { 'w', "download-dir",         "Where to save downloaded data",
114      "w",  1, "<path>"    },
115    {   0, NULL,                   NULL,
116        NULL, 0, NULL        }
117};
118
119static const char *
120getUsage( void )
121{
122    return "A fast and easy BitTorrent client\n"
123           "\n"
124           "Usage: " MY_NAME " [options] <torrent-filename>";
125}
126
127static int          parseCommandLine( int           argc,
128                                      const char ** argv );
129
130static void         sigHandler( int signal );
131
132static char*
133tr_strlratio( char * buf,
134              double ratio,
135              size_t buflen )
136{
137    if( (int)ratio == TR_RATIO_NA )
138        tr_strlcpy( buf, _( "None" ), buflen );
139    else if( (int)ratio == TR_RATIO_INF )
140        tr_strlcpy( buf, "Inf", buflen );
141    else if( ratio < 10.0 )
142        tr_snprintf( buf, buflen, "%.2f", ratio );
143    else if( ratio < 100.0 )
144        tr_snprintf( buf, buflen, "%.1f", ratio );
145    else
146        tr_snprintf( buf, buflen, "%.0f", ratio );
147    return buf;
148}
149
150static int
151is_rfc2396_alnum( char ch )
152{
153    return ( '0' <= ch && ch <= '9' )
154           || ( 'A' <= ch && ch <= 'Z' )
155           || ( 'a' <= ch && ch <= 'z' );
156}
157
158static void
159escape( char *          out,
160        const uint8_t * in,
161        int             in_len )                     /* rfc2396 */
162{
163    const uint8_t *end = in + in_len;
164
165    while( in != end )
166        if( is_rfc2396_alnum( *in ) )
167            *out++ = (char) *in++;
168        else
169            out += tr_snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
170
171    *out = '\0';
172}
173
174static void
175torrentCompletenessChanged( tr_torrent       * torrent       UNUSED,
176                            tr_completeness    completeness  UNUSED,
177                            void             * user_data     UNUSED )
178{
179    system( finishCall );
180}
181
182static int leftToScrape = 0;
183
184static void
185scrapeDoneFunc( tr_session   * session UNUSED,
186                long           response_code,
187                const void   * response,
188                size_t         response_byte_count,
189                void         * host )
190{
191    tr_benc top, *files;
192
193    if( !tr_bencLoad( response, response_byte_count, &top, NULL )
194      && tr_bencDictFindDict( &top, "files", &files )
195      && files->val.l.count >= 2 )
196    {
197        int64_t   complete = -1, incomplete = -1, downloaded = -1;
198        tr_benc * hash = &files->val.l.vals[1];
199        tr_bencDictFindInt( hash, "complete", &complete );
200        tr_bencDictFindInt( hash, "incomplete", &incomplete );
201        tr_bencDictFindInt( hash, "downloaded", &downloaded );
202        printf( "%4d seeders, %4d leechers, %5d downloads at %s\n",
203                (int)complete, (int)incomplete, (int)downloaded,
204                (char*)host );
205        tr_bencFree( &top );
206    }
207    else
208        fprintf( stderr, "Unable to parse response (http code %lu) at %s",
209                 response_code,
210                 (char*)host );
211
212    --leftToScrape;
213
214    tr_free( host );
215}
216
217static void
218dumpInfo( FILE *          out,
219          const tr_info * inf )
220{
221    int             i;
222    int             prevTier = -1;
223    tr_file_index_t ff;
224
225    fprintf( out, "hash:\t" );
226    for( i = 0; i < SHA_DIGEST_LENGTH; ++i )
227        fprintf( out, "%02x", inf->hash[i] );
228    fprintf( out, "\n" );
229
230    fprintf( out, "name:\t%s\n", inf->name );
231
232    for( i = 0; i < inf->trackerCount; ++i )
233    {
234        if( prevTier != inf->trackers[i].tier )
235        {
236            prevTier = inf->trackers[i].tier;
237            fprintf( out, "\ntracker tier #%d:\n", ( prevTier + 1 ) );
238        }
239        fprintf( out, "\tannounce:\t%s\n", inf->trackers[i].announce );
240    }
241
242    fprintf( out, "size:\t%" PRIu64 " (%" PRIu64 " * %d + %" PRIu64 ")\n",
243             inf->totalSize, inf->totalSize / inf->pieceSize,
244             inf->pieceSize, inf->totalSize % inf->pieceSize );
245
246    if( inf->comment && *inf->comment )
247        fprintf( out, "comment:\t%s\n", inf->comment );
248    if( inf->creator && *inf->creator )
249        fprintf( out, "creator:\t%s\n", inf->creator );
250    if( inf->isPrivate )
251        fprintf( out, "private flag set\n" );
252
253    fprintf( out, "file(s):\n" );
254    for( ff = 0; ff < inf->fileCount; ++ff )
255        fprintf( out, "\t%s (%" PRIu64 ")\n", inf->files[ff].name,
256                 inf->files[ff].length );
257}
258
259static void
260getStatusStr( const tr_stat * st,
261              char *          buf,
262              size_t          buflen )
263{
264    if( st->activity & TR_STATUS_CHECK_WAIT )
265    {
266        tr_snprintf( buf, buflen, "Waiting to verify local files" );
267    }
268    else if( st->activity & TR_STATUS_CHECK )
269    {
270        tr_snprintf( buf, buflen,
271                     "Verifying local files (%.2f%%, %.2f%% valid)",
272                     100 * st->recheckProgress,
273                     100.0 * st->percentDone );
274    }
275    else if( st->activity & TR_STATUS_DOWNLOAD )
276    {
277        char ratioStr[80];
278        tr_strlratio( ratioStr, st->ratio, sizeof( ratioStr ) );
279        tr_snprintf(
280            buf, buflen,
281            "Progress: %.1f%%, dl from %d of %d peers (%.0f KB/s), "
282            "ul to %d (%.0f KB/s) [%s]",
283            st->percentDone * 100.0,
284            st->peersSendingToUs,
285            st->peersConnected,
286            st->pieceDownloadSpeed,
287            st->peersGettingFromUs,
288            st->pieceUploadSpeed,
289            ratioStr );
290    }
291    else if( st->activity & TR_STATUS_SEED )
292    {
293        char ratioStr[80];
294        tr_strlratio( ratioStr, st->ratio, sizeof( ratioStr ) );
295        tr_snprintf(
296            buf, buflen,
297            "Seeding, uploading to %d of %d peer(s), %.0f KB/s [%s]",
298            st->peersGettingFromUs, st->peersConnected,
299            st->pieceUploadSpeed, ratioStr );
300    }
301    else *buf = '\0';
302}
303
304int
305main( int     argc,
306      char ** argv )
307{
308    int           error;
309    tr_session  * h;
310    tr_ctor     * ctor;
311    tr_torrent  * tor = NULL;
312
313    printf( "Transmission %s - http://www.transmissionbt.com/\n",
314            LONG_VERSION_STRING );
315
316    /* user needs to pass in at least one argument */
317    if( argc < 2 ) {
318        tr_getopt_usage( MY_NAME, getUsage( ), options );
319        return EXIT_FAILURE;
320    }
321
322    /* Get options */
323    if( parseCommandLine( argc, (const char**)argv ) )
324        return EXIT_FAILURE;
325
326    /* Check the options for validity */
327    if( !torrentPath )
328    {
329        fprintf( stderr, "No torrent specified!\n" );
330        return EXIT_FAILURE;
331    }
332    if( peerPort < 1 || peerPort > 65535 )
333    {
334        fprintf( stderr, "Error: Port must between 1 and 65535; got %d\n",
335                 peerPort );
336        return EXIT_FAILURE;
337    }
338    if( peerSocketTOS < 0 || peerSocketTOS > 255 )
339    {
340        fprintf( stderr, "Error: value must between 0 and 255; got %d\n",
341                 peerSocketTOS );
342        return EXIT_FAILURE;
343    }
344
345    /* don't bind the port if we're just running the CLI
346     * to get metainfo or to create a torrent */
347    if( showInfo || showScrape || ( sourceFile != NULL ) )
348        peerPort = -1;
349
350    if( configdir == NULL )
351        configdir = tr_getDefaultConfigDir( );
352
353    if( downloadDir == NULL )
354        downloadDir = tr_getDefaultDownloadDir( );
355
356    /* Initialize libtransmission */
357    h = tr_sessionInitFull(
358        configdir,
359        "cli",                            /* tag */
360        downloadDir,                       /* where to download torrents */
361        TR_DEFAULT_PEX_ENABLED,
362        natTraversal,                      /* nat enabled */
363        peerPort,
364        encryptionMode,
365        TR_DEFAULT_LAZY_BITFIELD_ENABLED,
366        uploadLimit >= 0,
367        uploadLimit,
368        downloadLimit >= 0,
369        downloadLimit,
370        TR_DEFAULT_GLOBAL_PEER_LIMIT,
371        verboseLevel + 1,                  /* messageLevel */
372        0,                                 /* is message queueing enabled? */
373        blocklistEnabled,
374        peerSocketTOS,
375        TR_DEFAULT_RPC_ENABLED,
376        TR_DEFAULT_RPC_PORT,
377        TR_DEFAULT_RPC_WHITELIST_ENABLED,
378        TR_DEFAULT_RPC_WHITELIST,
379        FALSE, "fnord", "potzrebie",
380        TR_DEFAULT_PROXY_ENABLED,
381        TR_DEFAULT_PROXY,
382        TR_DEFAULT_PROXY_PORT,
383        TR_DEFAULT_PROXY_TYPE,
384        TR_DEFAULT_PROXY_AUTH_ENABLED,
385        TR_DEFAULT_PROXY_USERNAME,
386        TR_DEFAULT_PROXY_PASSWORD );
387
388    if( sourceFile && *sourceFile ) /* creating a torrent */
389    {
390        int                   err;
391        tr_metainfo_builder * b = tr_metaInfoBuilderCreate( h, sourceFile );
392        tr_tracker_info       ti;
393        ti.tier = 0;
394        ti.announce = (char*) announce;
395        tr_makeMetaInfo( b, torrentPath, &ti, 1, comment, isPrivate );
396        while( !b->isDone )
397        {
398            tr_wait( 1000 );
399            printf( "." );
400        }
401
402        err = b->result;
403        tr_metaInfoBuilderFree( b );
404        return err;
405    }
406
407    ctor = tr_ctorNew( h );
408    tr_ctorSetMetainfoFromFile( ctor, torrentPath );
409    tr_ctorSetPaused( ctor, TR_FORCE, showScrape );
410    tr_ctorSetDownloadDir( ctor, TR_FORCE, downloadDir );
411
412    if( showScrape )
413    {
414        tr_info info;
415
416        if( !tr_torrentParse( h, ctor, &info ) )
417        {
418            int          i;
419            const time_t start = time( NULL );
420            for( i = 0; i < info.trackerCount; ++i )
421            {
422                if( info.trackers[i].scrape )
423                {
424                    const char * scrape = info.trackers[i].scrape;
425                    char         escaped[SHA_DIGEST_LENGTH * 3 + 1];
426                    char *       url, *host;
427                    escape( escaped, info.hash, SHA_DIGEST_LENGTH );
428                    url = tr_strdup_printf( "%s%cinfo_hash=%s",
429                                            scrape,
430                                            strchr( scrape,
431                                                    '?' ) ? '&' : '?',
432                                            escaped );
433                    tr_httpParseURL( scrape, -1, &host, NULL, NULL );
434                    ++leftToScrape;
435                    tr_webRun( h, url, NULL, scrapeDoneFunc, host );
436                    tr_free( url );
437                }
438            }
439
440            fprintf( stderr, "scraping %d trackers:\n", leftToScrape );
441
442            while( leftToScrape > 0 && ( ( time( NULL ) - start ) < 20 ) )
443                tr_wait( 250 );
444        }
445        goto cleanup;
446    }
447
448    if( showInfo )
449    {
450        tr_info info;
451
452        if( !tr_torrentParse( h, ctor, &info ) )
453        {
454            dumpInfo( stdout, &info );
455            tr_metainfoFree( &info );
456        }
457
458        tr_ctorFree( ctor );
459        goto cleanup;
460    }
461
462    tor = tr_torrentNew( h, ctor, &error );
463    tr_ctorFree( ctor );
464    if( !tor )
465    {
466        fprintf( stderr, "Failed opening torrent file `%s'\n", torrentPath );
467        tr_sessionClose( h );
468        return EXIT_FAILURE;
469    }
470
471    signal( SIGINT, sigHandler );
472#ifndef WIN32
473    signal( SIGHUP, sigHandler );
474#endif
475    tr_torrentSetCompletenessCallback( tor, torrentCompletenessChanged, NULL );
476    tr_torrentStart( tor );
477
478    if( verify )
479    {
480        verify = 0;
481        tr_torrentVerify( tor );
482    }
483
484    for( ; ; )
485    {
486        char            line[LINEWIDTH];
487        const tr_stat * st;
488
489        tr_wait( 200 );
490
491        if( gotsig )
492        {
493            gotsig = 0;
494            printf( "\nStopping torrent...\n" );
495            tr_torrentStop( tor );
496        }
497
498        if( manualUpdate )
499        {
500            manualUpdate = 0;
501            if( !tr_torrentCanManualUpdate( tor ) )
502                fprintf(
503                    stderr,
504                    "\nReceived SIGHUP, but can't send a manual update now\n" );
505            else
506            {
507                fprintf( stderr,
508                         "\nReceived SIGHUP: manual update scheduled\n" );
509                tr_torrentManualUpdate( tor );
510            }
511        }
512
513        st = tr_torrentStat( tor );
514        if( st->activity & TR_STATUS_STOPPED )
515            break;
516
517        getStatusStr( st, line, sizeof( line ) );
518        printf( "\r%-*s", LINEWIDTH, line );
519        if( st->error )
520            fprintf( stderr, "\n%s\n", st->errorString );
521    }
522
523cleanup:
524    printf( "\n" );
525    tr_sessionClose( h );
526    return EXIT_SUCCESS;
527}
528
529/***
530****
531****
532****
533***/
534
535static void
536showUsage( void )
537{
538    tr_getopt_usage( MY_NAME, getUsage( ), options );
539    exit( 0 );
540}
541
542static int
543numarg( const char * arg )
544{
545    char *     end = NULL;
546    const long num = strtol( arg, &end, 10 );
547
548    if( *end )
549    {
550        fprintf( stderr, "Not a number: \"%s\"\n", arg );
551        showUsage( );
552    }
553    return num;
554}
555
556static int
557parseCommandLine( int           argc,
558                  const char ** argv )
559{
560    int          c;
561    const char * optarg;
562
563    while( ( c = tr_getopt( getUsage( ), argc, argv, options, &optarg ) ) )
564    {
565        switch( c )
566        {
567            case 'a':
568                announce = optarg; break;
569
570            case 'b':
571                blocklistEnabled = 1; break;
572
573            case 'B':
574                blocklistEnabled = 0; break;
575
576            case 'c':
577                comment = optarg; break;
578
579            case 'd':
580                downloadLimit = numarg( optarg ); break;
581
582            case 'D':
583                downloadLimit = -1; break;
584
585            case 'f':
586                finishCall = optarg; break;
587
588            case 'g':
589                configdir = optarg; break;
590
591            case 'i':
592                showInfo = 1; break;
593
594            case 'm':
595                natTraversal = 1; break;
596
597            case 'M':
598                natTraversal = 0; break;
599
600            case 'n':
601                sourceFile = optarg; break;
602
603            case 'p':
604                peerPort = numarg( optarg ); break;
605
606            case 'r':
607                isPrivate = 1; break;
608
609            case 's':
610                showScrape = 1; break;
611
612            case 't':
613                peerSocketTOS = numarg( optarg ); break;
614
615            case 'u':
616                uploadLimit = numarg( optarg ); break;
617
618            case 'U':
619                uploadLimit = -1; break;
620
621            case 'v':
622                verify = 1; break;
623
624            case 'w':
625                downloadDir = optarg; break;
626
627            case 910:
628                encryptionMode = TR_ENCRYPTION_REQUIRED; break;
629
630            case 911:
631                encryptionMode = TR_CLEAR_PREFERRED; break;
632
633            case 912:
634                encryptionMode = TR_ENCRYPTION_PREFERRED; break;
635
636            case TR_OPT_UNK:
637                torrentPath = optarg; break;
638
639            default:
640                return 1;
641        }
642    }
643
644    return 0;
645}
646
647static void
648sigHandler( int signal )
649{
650    switch( signal )
651    {
652        case SIGINT:
653            gotsig = 1; break;
654
655#ifndef WIN32
656        case SIGHUP:
657            manualUpdate = 1; break;
658
659#endif
660        default:
661            break;
662    }
663}
664
Note: See TracBrowser for help on using the repository browser.