source: trunk/libtransmission/transmission.c @ 132

Last change on this file since 132 was 132, checked in by joshe, 16 years ago

libtransmission:

Lazily bind the listening port to avoid spurious
'Could not bind port 9090' error on startup.

File size: 20.0 KB
Line 
1/******************************************************************************
2 * Copyright (c) 2005 Eric Petit
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 *****************************************************************************/
22
23#include "transmission.h"
24
25/***********************************************************************
26 * Local prototypes
27 **********************************************************************/
28static void torrentReallyStop( tr_handle_t * h, int t );
29static void  downloadLoop( void * );
30static void  acceptLoop( void * );
31static void acceptStop( tr_handle_t * h );
32
33/***********************************************************************
34 * tr_init
35 ***********************************************************************
36 * Allocates a tr_handle_t structure and initializes a few things
37 **********************************************************************/
38tr_handle_t * tr_init()
39{
40    tr_handle_t * h;
41    int           i, r;
42
43    h = calloc( sizeof( tr_handle_t ), 1 );
44
45    /* Generate a peer id : "-TRxxyy-" + 12 random alphanumeric
46       characters, where xx is the major version number and yy the
47       minor version number (Azureus-style) */
48    sprintf( h->id, "-TR%02d%02d-", VERSION_MAJOR, VERSION_MINOR );
49    for( i = 8; i < 20; i++ )
50    {
51        r        = tr_rand( 36 );
52        h->id[i] = ( r < 26 ) ? ( 'a' + r ) : ( '0' + r - 26 ) ;
53    }
54
55    /* Random key */
56    for( i = 0; i < 20; i++ )
57    {
58        r         = tr_rand( 36 );
59        h->key[i] = ( r < 26 ) ? ( 'a' + r ) : ( '0' + r - 26 ) ;
60    }
61
62    /* Don't exit when writing on a broken socket */
63    signal( SIGPIPE, SIG_IGN );
64
65    /* Initialize rate and file descripts controls */
66    h->upload   = tr_rcInit();
67    h->download = tr_rcInit();
68    h->fdlimit  = tr_fdInit();
69    h->choking  = tr_chokingInit( h );
70
71    h->bindPort = -1;
72    h->bindSocket = -1;
73
74    h->acceptDie = 0;
75    tr_lockInit( &h->acceptLock );
76    tr_threadCreate( &h->acceptThread, acceptLoop, h );
77
78    return h;
79}
80
81/***********************************************************************
82 * tr_setBindPort
83 ***********************************************************************
84 *
85 **********************************************************************/
86void tr_setBindPort( tr_handle_t * h, int port )
87{
88    int ii, sock;
89
90    if( h->bindPort == port )
91      return;
92
93#ifndef BEOS_NETSERVER
94    /* BeOS net_server seems to be unable to set incoming connections to
95       non-blocking. Too bad. */
96    if( !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
97    {
98        /* XXX should handle failure here in a better way */
99        sock = tr_netBind( port );
100    }
101#else
102    return;
103#endif
104
105    tr_lockLock( &h->acceptLock );
106
107    h->bindPort = port;
108
109    for( ii = 0; ii < h->torrentCount; ii++ )
110    {
111        tr_lockLock( &h->torrents[ii]->lock );
112        if( NULL != h->torrents[ii]->tracker )
113        {
114            tr_trackerChangePort( h->torrents[ii]->tracker, port );
115        }
116        tr_lockUnlock( &h->torrents[ii]->lock );
117    }
118
119    if( h->bindSocket > -1 )
120    {
121        tr_netClose( h->bindSocket );
122        tr_fdSocketClosed( h->fdlimit, 0 );
123    }
124
125    h->bindSocket = sock;
126
127    tr_lockUnlock( &h->acceptLock );
128}
129
130/***********************************************************************
131 * tr_setUploadLimit
132 ***********************************************************************
133 *
134 **********************************************************************/
135void tr_setUploadLimit( tr_handle_t * h, int limit )
136{
137    tr_rcSetLimit( h->upload, limit );
138    tr_chokingSetLimit( h->choking, limit );
139}
140
141/***********************************************************************
142 * tr_torrentRates
143 ***********************************************************************
144 *
145 **********************************************************************/
146void tr_torrentRates( tr_handle_t * h, float * dl, float * ul )
147{
148    tr_torrent_t * tor;
149    int i;
150
151    *dl = 0.0;
152    *ul = 0.0;
153    for( i = 0; i < h->torrentCount; i++ )
154    {
155        tor = h->torrents[i];
156        tr_lockLock( &tor->lock );
157        if( tor->status & TR_STATUS_DOWNLOAD )
158            *dl += tr_rcRate( tor->download );
159        *ul += tr_rcRate( tor->upload );
160        tr_lockUnlock( &tor->lock );
161    }
162}
163
164/***********************************************************************
165 * tr_torrentInit
166 ***********************************************************************
167 * Allocates a tr_torrent_t structure, then relies on tr_metainfoParse
168 * to fill it.
169 **********************************************************************/
170int tr_torrentInit( tr_handle_t * h, const char * path )
171{
172    tr_torrent_t  * tor;
173    tr_info_t     * inf;
174    int             i;
175    char          * s1, * s2;
176
177    if( h->torrentCount >= TR_MAX_TORRENT_COUNT )
178    {
179        tr_err( "Maximum number of torrents reached" );
180        return 1;
181    }
182
183    tor = calloc( sizeof( tr_torrent_t ), 1 );
184    inf = &tor->info;
185
186    /* Parse torrent file */
187    if( tr_metainfoParse( inf, path ) )
188    {
189        free( tor );
190        return 1;
191    }
192
193    /* Make sure this torrent is not already open */
194    for( i = 0; i < h->torrentCount; i++ )
195    {
196        if( !memcmp( tor->info.hash, h->torrents[i]->info.hash,
197                     SHA_DIGEST_LENGTH ) )
198        {
199            tr_err( "Torrent already open" );
200            free( tor );
201            return 1;
202        }
203    }
204
205    tor->status = TR_STATUS_PAUSE;
206    tor->id     = h->id;
207    tor->key    = h->key;
208        tor->finished = 0;
209
210
211    /* Guess scrape URL */
212    s1 = strchr( inf->trackerAnnounce, '/' );
213    while( ( s2 = strchr( s1 + 1, '/' ) ) )
214    {
215        s1 = s2;
216    }
217    s1++;
218    if( !strncmp( s1, "announce", 8 ) )
219    {
220        int pre  = (long) s1 - (long) inf->trackerAnnounce;
221        int post = strlen( inf->trackerAnnounce ) - pre - 8;
222        memcpy( tor->scrape, inf->trackerAnnounce, pre );
223        sprintf( &tor->scrape[pre], "scrape" );
224        memcpy( &tor->scrape[pre+6], &inf->trackerAnnounce[pre+8], post );
225    }
226
227    /* Escaped info hash for HTTP queries */
228    for( i = 0; i < SHA_DIGEST_LENGTH; i++ )
229    {
230        sprintf( &tor->hashString[3*i], "%%%02x", inf->hash[i] );
231    }
232
233    /* Block size: usually 16 ko, or less if we have to */
234    tor->blockSize  = MIN( inf->pieceSize, 1 << 14 );
235    tor->blockCount = ( inf->totalSize + tor->blockSize - 1 ) /
236                        tor->blockSize;
237    tor->completion = tr_cpInit( tor );
238
239    tr_lockInit( &tor->lock );
240
241    tor->globalUpload   = h->upload;
242    tor->globalDownload = h->download;
243    tor->fdlimit        = h->fdlimit;
244    tor->upload         = tr_rcInit();
245    tor->download       = tr_rcInit();
246 
247    /* We have a new torrent */
248    tr_lockLock( &h->acceptLock );
249    h->torrents[h->torrentCount] = tor;
250    (h->torrentCount)++;
251    tr_lockUnlock( &h->acceptLock );
252
253    return 0;
254}
255
256/***********************************************************************
257 * tr_torrentScrape
258 ***********************************************************************
259 * Allocates a tr_torrent_t structure, then relies on tr_metainfoParse
260 * to fill it.
261 **********************************************************************/
262int tr_torrentScrape( tr_handle_t * h, int t, int * s, int * l )
263{
264    return tr_trackerScrape( h->torrents[t], s, l );
265}
266
267void tr_torrentSetFolder( tr_handle_t * h, int t, const char * path )
268{
269    tr_torrent_t * tor = h->torrents[t];
270
271    tor->destination = strdup( path );
272}
273
274char * tr_torrentGetFolder( tr_handle_t * h, int t )
275{
276    tr_torrent_t * tor = h->torrents[t];
277
278    return tor->destination;
279}
280
281void tr_torrentStart( tr_handle_t * h, int t )
282{
283    tr_torrent_t * tor = h->torrents[t];
284
285    if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
286    {
287        /* Join the thread first */
288        torrentReallyStop( h, t );
289    }
290
291    tor->status   = TR_STATUS_CHECK;
292    tor->tracker  = tr_trackerInit( h, tor );
293
294    if( 0 > h->bindPort )
295    {
296        tr_setBindPort( h, TR_DEFAULT_PORT );
297    }
298
299    tor->date = tr_date();
300    tor->die = 0;
301    tr_threadCreate( &tor->thread, downloadLoop, tor );
302}
303
304void tr_torrentStop( tr_handle_t * h, int t )
305{
306    tr_torrent_t * tor = h->torrents[t];
307
308    tr_lockLock( &tor->lock );
309    tr_trackerStopped( tor->tracker );
310    tr_rcReset( tor->download );
311    tr_rcReset( tor->upload );
312    tor->status = TR_STATUS_STOPPING;
313    tor->stopDate = tr_date();
314    tr_lockUnlock( &tor->lock );
315}
316
317/***********************************************************************
318 * torrentReallyStop
319 ***********************************************************************
320 * Joins the download thread and frees/closes everything related to it.
321 **********************************************************************/
322static void torrentReallyStop( tr_handle_t * h, int t )
323{
324    tr_torrent_t * tor = h->torrents[t];
325
326    tor->die = 1;
327    tr_threadJoin( &tor->thread );
328    tr_dbg( "Thread joined" );
329
330    tr_trackerClose( tor->tracker );
331
332    while( tor->peerCount > 0 )
333    {
334        tr_peerRem( tor, 0 );
335    }
336}
337
338/***********************************************************************
339 * tr_torrentCount
340 ***********************************************************************
341 *
342 **********************************************************************/
343int tr_torrentCount( tr_handle_t * h )
344{
345    return h->torrentCount;
346}
347
348int tr_getFinished( tr_handle_t * h, int i)
349{
350        return h->torrents[i]->finished;
351}
352void tr_setFinished( tr_handle_t * h, int i, int val)
353{
354        h->torrents[i]->finished = val;
355}
356
357int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
358{
359    tr_stat_t * s;
360    tr_torrent_t * tor;
361    tr_info_t * inf;
362    int i, j, k, piece;
363
364    if( h->torrentCount < 1 )
365    {
366        *stat = NULL;
367        return 0;
368    }
369
370    s = malloc( h->torrentCount * sizeof( tr_stat_t ) );
371
372    for( i = 0; i < h->torrentCount; i++ )
373    {
374        tor = h->torrents[i];
375        inf = &tor->info;
376
377        if( ( tor->status & TR_STATUS_STOPPED ) ||
378            ( ( tor->status & TR_STATUS_STOPPING ) &&
379              tr_date() > tor->stopDate + 60000 ) )
380        {
381            torrentReallyStop( h, i );
382            tor->status = TR_STATUS_PAUSE;
383        }
384
385        tr_lockLock( &tor->lock );
386
387        memcpy( &s[i].info, &tor->info, sizeof( tr_info_t ) );
388        s[i].status = tor->status;
389        memcpy( s[i].error, tor->error, sizeof( s[i].error ) );
390
391        s[i].peersTotal       = 0;
392        s[i].peersUploading   = 0;
393        s[i].peersDownloading = 0;
394
395        for( j = 0; j < tor->peerCount; j++ )
396        {
397            if( tr_peerIsConnected( tor->peers[j] ) )
398            {
399                (s[i].peersTotal)++;
400                if( tr_peerIsUploading( tor->peers[j] ) )
401                {
402                    (s[i].peersUploading)++;
403                }
404                if( tr_peerIsDownloading( tor->peers[j] ) )
405                {
406                    (s[i].peersDownloading)++;
407                }
408            }
409        }
410
411        s[i].progress     = tr_cpCompletionAsFloat( tor->completion );
412        if( tor->status & TR_STATUS_DOWNLOAD )
413            s[i].rateDownload = tr_rcRate( tor->download );
414        else
415            /* tr_rcRate() doesn't make the difference between 'piece'
416               messages and other messages, which causes a non-zero
417               download rate even tough we are not downloading. So we
418               force it to zero not to confuse the user. */
419            s[i].rateDownload = 0.0;
420        s[i].rateUpload = tr_rcRate( tor->upload );
421       
422        s[i].seeders      = tr_trackerSeeders(tor);
423                s[i].leechers     = tr_trackerLeechers(tor);
424
425        if( s[i].rateDownload < 0.1 )
426        {
427            s[i].eta = -1;
428        }
429        else
430        {
431            s[i].eta = (float) ( 1.0 - s[i].progress ) *
432                (float) inf->totalSize / s[i].rateDownload / 1024.0;
433            if( s[i].eta > 99 * 3600 + 59 * 60 + 59 )
434            {
435                s[i].eta = -1;
436            }
437        }
438
439        for( j = 0; j < 120; j++ )
440        {
441            piece = j * inf->pieceCount / 120;
442
443            if( tr_cpPieceIsComplete( tor->completion, piece ) )
444            {
445                s[i].pieces[j] = -1;
446                continue;
447            }
448
449            s[i].pieces[j] = 0;
450           
451            for( k = 0; k < tor->peerCount; k++ )
452            {
453                if( tr_peerBitfield( tor->peers[k] ) &&
454                    tr_bitfieldHas( tr_peerBitfield( tor->peers[k] ), piece ) )
455                {
456                    (s[i].pieces[j])++;
457                }
458            }
459        }
460
461        s[i].downloaded = tor->downloaded;
462        s[i].uploaded   = tor->uploaded;
463
464        s[i].folder = tor->destination;
465
466        tr_lockUnlock( &tor->lock );
467    }
468
469    *stat = s;
470    return h->torrentCount;
471}
472
473/***********************************************************************
474 * tr_torrentClose
475 ***********************************************************************
476 * Frees memory allocated by tr_torrentInit.
477 **********************************************************************/
478void tr_torrentClose( tr_handle_t * h, int t )
479{
480    tr_torrent_t * tor = h->torrents[t];
481    tr_info_t    * inf = &tor->info;
482
483    if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
484    {
485        /* Join the thread first */
486        torrentReallyStop( h, t );
487    }
488
489    tr_lockLock( &h->acceptLock );
490
491    h->torrentCount--;
492
493    tr_lockClose( &tor->lock );
494    tr_cpClose( tor->completion );
495
496    tr_rcClose( tor->upload );
497    tr_rcClose( tor->download );
498
499    if( tor->destination )
500    {
501        free( tor->destination );
502    }
503    free( inf->pieces );
504    free( inf->files );
505    free( tor );
506
507    memmove( &h->torrents[t], &h->torrents[t+1],
508             ( h->torrentCount - t ) * sizeof( void * ) );
509
510    tr_lockUnlock( &h->acceptLock );
511}
512
513void tr_close( tr_handle_t * h )
514{
515    acceptStop( h );
516    tr_chokingClose( h->choking );
517    tr_fdClose( h->fdlimit );
518    tr_rcClose( h->upload );
519    tr_rcClose( h->download );
520    free( h );
521}
522
523/***********************************************************************
524 * downloadLoop
525 **********************************************************************/
526static void downloadLoop( void * _tor )
527{
528    tr_torrent_t * tor = _tor;
529    uint64_t       date1, date2;
530
531    tr_dbg( "Thread started" );
532
533#ifdef SYS_BEOS
534    /* This is required because on BeOS, SIGINT is sent to each thread,
535       which kills them not nicely */
536    signal( SIGINT, SIG_IGN );
537#endif
538
539    tr_lockLock( &tor->lock );
540
541    tr_cpReset( tor->completion );
542    tor->io     = tr_ioInit( tor );
543    tor->status = tr_cpIsSeeding( tor->completion ) ?
544                      TR_STATUS_SEED : TR_STATUS_DOWNLOAD;
545
546    while( !tor->die )
547    {
548        date1 = tr_date();
549
550        /* Are we finished ? */
551        if( ( tor->status & TR_STATUS_DOWNLOAD ) &&
552            tr_cpIsSeeding( tor->completion ) )
553        {
554            /* Done */
555            tor->status = TR_STATUS_SEED;
556                        tor->finished = 1;
557            tr_trackerCompleted( tor->tracker );
558            tr_ioSaveResume( tor->io );
559        }
560
561        /* Receive/send messages */
562        tr_peerPulse( tor );
563
564        /* Try to get new peers or to send a message to the tracker */
565        tr_trackerPulse( tor->tracker );
566
567        if( tor->status & TR_STATUS_STOPPED )
568        {
569            break;
570        }
571
572        /* Wait up to 20 ms */
573        date2 = tr_date();
574        if( date2 < date1 + 20 )
575        {
576            tr_lockUnlock( &tor->lock );
577            tr_wait( date1 + 20 - date2 );
578            tr_lockLock( &tor->lock );
579        }
580    }
581
582    tr_lockUnlock( &tor->lock );
583
584    tr_ioClose( tor->io );
585
586    tor->status = TR_STATUS_STOPPED;
587
588    tr_dbg( "Thread exited" );
589}
590
591/***********************************************************************
592 * acceptLoop
593 **********************************************************************/
594static void acceptLoop( void * _h )
595{
596    tr_handle_t * h = _h;
597    uint64_t      date1, date2, lastchoke = 0;
598    int           ii, jj;
599    uint8_t     * hash;
600
601    tr_dbg( "Accept thread started" );
602
603#ifdef SYS_BEOS
604    /* This is required because on BeOS, SIGINT is sent to each thread,
605       which kills them not nicely */
606    signal( SIGINT, SIG_IGN );
607#endif
608
609    tr_lockLock( &h->acceptLock );
610
611    while( !h->acceptDie )
612    {
613        date1 = tr_date();
614
615        /* Check for incoming connections */
616        if( h->bindSocket > -1 &&
617            h->acceptPeerCount < TR_MAX_PEER_COUNT &&
618            !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
619        {
620            int            s;
621            struct in_addr addr;
622            in_port_t      port;
623            s = tr_netAccept( h->bindSocket, &addr, &port );
624            if( s > -1 )
625            {
626                h->acceptPeers[h->acceptPeerCount++] = tr_peerInit( addr, port, s );
627            }
628            else
629            {
630                tr_fdSocketClosed( h->fdlimit, 0 );
631            }
632        }
633
634        for( ii = 0; ii < h->acceptPeerCount; )
635        {
636            if( tr_peerRead( NULL, h->acceptPeers[ii] ) )
637            {
638                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
639                goto removePeer;
640            }
641            if( NULL != ( hash = tr_peerHash( h->acceptPeers[ii] ) ) )
642            {
643                for( jj = 0; jj < h->torrentCount; jj++ )
644                {
645                    tr_lockLock( &h->torrents[jj]->lock );
646                    if( 0 == memcmp( h->torrents[jj]->info.hash, hash,
647                                     SHA_DIGEST_LENGTH ) )
648                    {
649                      tr_peerAttach( h->torrents[jj], h->acceptPeers[ii] );
650                      tr_lockUnlock( &h->torrents[jj]->lock );
651                      goto removePeer;
652                    }
653                    tr_lockUnlock( &h->torrents[jj]->lock );
654                }
655                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
656                goto removePeer;
657            }
658            ii++;
659            continue;
660           removePeer:
661            h->acceptPeerCount--;
662            memmove( &h->acceptPeers[ii], &h->acceptPeers[ii+1],
663                     ( h->acceptPeerCount - ii ) * sizeof( tr_peer_t * ) );
664        }
665
666        if( date1 > lastchoke + 2000 )
667        {
668            tr_chokingPulse( h->choking );
669            lastchoke = date1;
670        }
671
672        /* Wait up to 20 ms */
673        date2 = tr_date();
674        if( date2 < date1 + 20 )
675        {
676            tr_lockUnlock( &h->acceptLock );
677            tr_wait( date1 + 20 - date2 );
678            tr_lockLock( &h->acceptLock );
679        }
680    }
681
682    tr_lockUnlock( &h->acceptLock );
683
684    tr_dbg( "Accept thread exited" );
685}
686
687/***********************************************************************
688 * acceptStop
689 ***********************************************************************
690 * Joins the accept thread and frees/closes everything related to it.
691 **********************************************************************/
692static void acceptStop( tr_handle_t * h )
693{
694    int ii;
695
696    h->acceptDie = 1;
697    tr_threadJoin( &h->acceptThread );
698    tr_lockClose( &h->acceptLock );
699    tr_dbg( "Accept thread joined" );
700
701    for( ii = 0; ii < h->acceptPeerCount; ii++ )
702    {
703        tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
704    }
705
706    if( h->bindSocket > -1 )
707    {
708        tr_netClose( h->bindSocket );
709        tr_fdSocketClosed( h->fdlimit, 0 );
710    }
711}
Note: See TracBrowser for help on using the repository browser.