source: trunk/libtransmission/transmission.c @ 202

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

Bind the default port when starting the first torrent

if it hasn't been done already.

File size: 19.7 KB
Line 
1/******************************************************************************
2 * Copyright (c) 2005-2006 Transmission authors and contributors
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_torrent_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 sock;
89    tr_torrent_t * tor;
90
91    if( h->bindPort == port )
92      return;
93
94#ifndef BEOS_NETSERVER
95    /* BeOS net_server seems to be unable to set incoming connections to
96       non-blocking. Too bad. */
97    if( !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
98    {
99        /* XXX should handle failure here in a better way */
100        sock = tr_netBind( port );
101    }
102#else
103    return;
104#endif
105
106    tr_lockLock( &h->acceptLock );
107
108    h->bindPort = port;
109
110    for( tor = h->torrentList; tor; tor = tor->next )
111    {
112        tr_lockLock( &tor->lock );
113        if( NULL != tor->tracker )
114        {
115            tr_trackerChangePort( tor->tracker, port );
116        }
117        tr_lockUnlock( &tor->lock );
118    }
119
120    if( h->bindSocket > -1 )
121    {
122        tr_netClose( h->bindSocket );
123        tr_fdSocketClosed( h->fdlimit, 0 );
124    }
125
126    h->bindSocket = sock;
127
128    tr_lockUnlock( &h->acceptLock );
129}
130
131/***********************************************************************
132 * tr_setUploadLimit
133 ***********************************************************************
134 *
135 **********************************************************************/
136void tr_setUploadLimit( tr_handle_t * h, int limit )
137{
138    tr_rcSetLimit( h->upload, limit );
139    tr_chokingSetLimit( h->choking, limit );
140}
141
142/***********************************************************************
143 * tr_torrentRates
144 ***********************************************************************
145 *
146 **********************************************************************/
147void tr_torrentRates( tr_handle_t * h, float * dl, float * ul )
148{
149    tr_torrent_t * tor;
150
151    *dl = 0.0;
152    *ul = 0.0;
153    for( tor = h->torrentList; tor; tor = tor->next )
154    {
155        tr_lockLock( &tor->lock );
156        if( tor->status & TR_STATUS_DOWNLOAD )
157            *dl += tr_rcRate( tor->download );
158        *ul += tr_rcRate( tor->upload );
159        tr_lockUnlock( &tor->lock );
160    }
161}
162
163/***********************************************************************
164 * tr_torrentInit
165 ***********************************************************************
166 * Allocates a tr_torrent_t structure, then relies on tr_metainfoParse
167 * to fill it.
168 **********************************************************************/
169tr_torrent_t * tr_torrentInit( tr_handle_t * h, const char * path,
170                               int * error )
171{
172    tr_torrent_t  * tor, * tor_tmp;
173    tr_info_t     * inf;
174    int             i;
175    char          * s1, * s2;
176
177    tor = calloc( sizeof( tr_torrent_t ), 1 );
178    inf = &tor->info;
179
180    /* Parse torrent file */
181    if( tr_metainfoParse( inf, path ) )
182    {
183        *error = TR_EINVALID;
184        free( tor );
185        return NULL;
186    }
187
188    /* Make sure this torrent is not already open */
189    for( tor_tmp = h->torrentList; tor_tmp; tor_tmp = tor_tmp->next )
190    {
191        if( !memcmp( tor->info.hash, tor_tmp->info.hash,
192                     SHA_DIGEST_LENGTH ) )
193        {
194            *error = TR_EDUPLICATE;
195            free( tor );
196            return NULL;
197        }
198    }
199
200    tor->status = TR_STATUS_PAUSE;
201    tor->id     = h->id;
202    tor->key    = h->key;
203    tor->bindPort = &h->bindPort;
204        tor->finished = 0;
205
206
207    /* Guess scrape URL */
208    s1 = strchr( inf->trackerAnnounce, '/' );
209    while( ( s2 = strchr( s1 + 1, '/' ) ) )
210    {
211        s1 = s2;
212    }
213    s1++;
214    if( !strncmp( s1, "announce", 8 ) )
215    {
216        int pre  = (long) s1 - (long) inf->trackerAnnounce;
217        int post = strlen( inf->trackerAnnounce ) - pre - 8;
218        memcpy( tor->scrape, inf->trackerAnnounce, pre );
219        sprintf( &tor->scrape[pre], "scrape" );
220        memcpy( &tor->scrape[pre+6], &inf->trackerAnnounce[pre+8], post );
221    }
222
223    /* Escaped info hash for HTTP queries */
224    for( i = 0; i < SHA_DIGEST_LENGTH; i++ )
225    {
226        sprintf( &tor->hashString[3*i], "%%%02x", inf->hash[i] );
227    }
228
229    /* Block size: usually 16 ko, or less if we have to */
230    tor->blockSize  = MIN( inf->pieceSize, 1 << 14 );
231    tor->blockCount = ( inf->totalSize + tor->blockSize - 1 ) /
232                        tor->blockSize;
233    tor->completion = tr_cpInit( tor );
234
235    tr_lockInit( &tor->lock );
236
237    tor->globalUpload   = h->upload;
238    tor->globalDownload = h->download;
239    tor->fdlimit        = h->fdlimit;
240    tor->upload         = tr_rcInit();
241    tor->download       = tr_rcInit();
242 
243    /* We have a new torrent */
244    tr_lockLock( &h->acceptLock );
245    tor->prev = NULL;
246    tor->next = h->torrentList;
247    if( tor->next )
248    {
249        tor->next->prev = tor;
250    }
251    h->torrentList = tor;
252    (h->torrentCount)++;
253    tr_lockUnlock( &h->acceptLock );
254
255    return tor;
256}
257
258tr_info_t * tr_torrentInfo( tr_torrent_t * tor )
259{
260    return &tor->info;
261}
262
263/***********************************************************************
264 * tr_torrentScrape
265 **********************************************************************/
266int tr_torrentScrape( tr_torrent_t * tor, int * s, int * l )
267{
268    return tr_trackerScrape( tor, s, l );
269}
270
271void tr_torrentSetFolder( tr_torrent_t * tor, const char * path )
272{
273    tor->destination = strdup( path );
274}
275
276char * tr_torrentGetFolder( tr_torrent_t * tor )
277{
278    return tor->destination;
279}
280
281void tr_torrentStart( tr_torrent_t * tor )
282{
283    if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
284    {
285        /* Join the thread first */
286        torrentReallyStop( tor );
287    }
288
289    tor->status  = TR_STATUS_CHECK;
290    tor->tracker = tr_trackerInit( tor );
291
292    if( 0 > h->bindPort )
293    {
294        tr_setBindPort( h, TR_DEFAULT_PORT );
295    }
296
297    tor->date = tr_date();
298    tor->die = 0;
299    tr_threadCreate( &tor->thread, downloadLoop, tor );
300}
301
302void tr_torrentStop( tr_torrent_t * tor )
303{
304    tr_lockLock( &tor->lock );
305    tr_trackerStopped( tor->tracker );
306    tr_rcReset( tor->download );
307    tr_rcReset( tor->upload );
308    tor->status = TR_STATUS_STOPPING;
309    tor->stopDate = tr_date();
310    tr_lockUnlock( &tor->lock );
311}
312
313/***********************************************************************
314 * torrentReallyStop
315 ***********************************************************************
316 * Joins the download thread and frees/closes everything related to it.
317 **********************************************************************/
318static void torrentReallyStop( tr_torrent_t * tor )
319{
320    tor->die = 1;
321    tr_threadJoin( &tor->thread );
322    tr_dbg( "Thread joined" );
323
324    tr_trackerClose( tor->tracker );
325
326    while( tor->peerCount > 0 )
327    {
328        tr_peerRem( tor, 0 );
329    }
330}
331
332/***********************************************************************
333 * tr_torrentCount
334 ***********************************************************************
335 *
336 **********************************************************************/
337int tr_torrentCount( tr_handle_t * h )
338{
339    return h->torrentCount;
340}
341
342void tr_torrentIterate( tr_handle_t * h, tr_callback_t func, void * d )
343{
344    tr_torrent_t * tor;
345
346    for( tor = h->torrentList; tor; tor = tor->next )
347    {
348        func( tor, d );
349    }
350}
351
352int tr_getFinished( tr_torrent_t * tor )
353{
354    if( tor->finished )
355    {
356        tor->finished = 0;
357        return 1;
358    }
359    return 0;
360}
361
362tr_stat_t * tr_torrentStat( tr_torrent_t * tor )
363{
364    tr_stat_t * s;
365    tr_info_t * inf = &tor->info;
366    int i;
367
368    tor->statCur = ( tor->statCur + 1 ) % 2;
369    s = &tor->stats[tor->statCur];
370
371    if( ( tor->status & TR_STATUS_STOPPED ) ||
372        ( ( tor->status & TR_STATUS_STOPPING ) &&
373          tr_date() > tor->stopDate + 60000 ) )
374    {
375        torrentReallyStop( tor );
376        tor->status = TR_STATUS_PAUSE;
377    }
378
379    tr_lockLock( &tor->lock );
380
381    s->status = tor->status;
382    memcpy( s->trackerError, tor->trackerError,
383            sizeof( s->trackerError ) );
384
385    s->peersTotal       = 0;
386    s->peersUploading   = 0;
387    s->peersDownloading = 0;
388
389    for( i = 0; i < tor->peerCount; i++ )
390    {
391        if( tr_peerIsConnected( tor->peers[i] ) )
392        {
393            (s->peersTotal)++;
394            if( tr_peerIsUploading( tor->peers[i] ) )
395            {
396                (s->peersUploading)++;
397            }
398            if( tr_peerIsDownloading( tor->peers[i] ) )
399            {
400                (s->peersDownloading)++;
401            }
402        }
403    }
404
405    s->progress = tr_cpCompletionAsFloat( tor->completion );
406    if( tor->status & TR_STATUS_DOWNLOAD )
407        s->rateDownload = tr_rcRate( tor->download );
408    else
409        /* tr_rcRate() doesn't make the difference between 'piece'
410           messages and other messages, which causes a non-zero
411           download rate even tough we are not downloading. So we
412           force it to zero not to confuse the user. */
413        s->rateDownload = 0.0;
414    s->rateUpload = tr_rcRate( tor->upload );
415   
416    s->seeders    = tr_trackerSeeders(tor);
417        s->leechers       = tr_trackerLeechers(tor);
418
419    if( s->rateDownload < 0.1 )
420    {
421        s->eta = -1;
422    }
423    else
424    {
425        s->eta = (float) ( 1.0 - s->progress ) *
426            (float) inf->totalSize / s->rateDownload / 1024.0;
427        if( s->eta > 99 * 3600 + 59 * 60 + 59 )
428        {
429            s->eta = -1;
430        }
431    }
432
433    s->downloaded = tor->downloaded;
434    s->uploaded   = tor->uploaded;
435
436    tr_lockUnlock( &tor->lock );
437
438    return s;
439}
440
441void tr_torrentAvailability( tr_torrent_t * tor, int8_t * tab, int size )
442{
443    int i, j, piece;
444
445    tr_lockLock( &tor->lock );
446    for( i = 0; i < size; i++ )
447    {
448        piece = i * tor->info.pieceCount / size;
449
450        if( tr_cpPieceIsComplete( tor->completion, piece ) )
451        {
452            tab[i] = -1;
453            continue;
454        }
455
456        tab[i] = 0;
457        for( j = 0; j < tor->peerCount; j++ )
458        {
459            if( tr_peerBitfield( tor->peers[j] ) &&
460                tr_bitfieldHas( tr_peerBitfield( tor->peers[j] ), piece ) )
461            {
462                (tab[i])++;
463            }
464        }
465    }
466    tr_lockUnlock( &tor->lock );
467}
468
469/***********************************************************************
470 * tr_torrentClose
471 ***********************************************************************
472 * Frees memory allocated by tr_torrentInit.
473 **********************************************************************/
474void tr_torrentClose( tr_handle_t * h, tr_torrent_t * tor )
475{
476    tr_info_t * inf = &tor->info;
477
478    if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
479    {
480        /* Join the thread first */
481        torrentReallyStop( tor );
482    }
483
484    tr_lockLock( &h->acceptLock );
485
486    h->torrentCount--;
487
488    tr_lockClose( &tor->lock );
489    tr_cpClose( tor->completion );
490
491    tr_rcClose( tor->upload );
492    tr_rcClose( tor->download );
493
494    if( tor->destination )
495    {
496        free( tor->destination );
497    }
498    free( inf->pieces );
499    free( inf->files );
500
501    if( tor->prev )
502    {
503        tor->prev->next = tor->next;
504    }
505    else
506    {
507        h->torrentList = tor->next;
508    }
509    if( tor->next )
510    {
511        tor->next->prev = tor->prev;
512    }
513    free( tor );
514
515    tr_lockUnlock( &h->acceptLock );
516}
517
518void tr_close( tr_handle_t * h )
519{
520    acceptStop( h );
521    tr_chokingClose( h->choking );
522    tr_fdClose( h->fdlimit );
523    tr_rcClose( h->upload );
524    tr_rcClose( h->download );
525    free( h );
526}
527
528/***********************************************************************
529 * downloadLoop
530 **********************************************************************/
531static void downloadLoop( void * _tor )
532{
533    tr_torrent_t * tor = _tor;
534    uint64_t       date1, date2;
535
536    tr_dbg( "Thread started" );
537
538#ifdef SYS_BEOS
539    /* This is required because on BeOS, SIGINT is sent to each thread,
540       which kills them not nicely */
541    signal( SIGINT, SIG_IGN );
542#endif
543
544    tr_lockLock( &tor->lock );
545
546    tr_cpReset( tor->completion );
547    tor->io     = tr_ioInit( tor );
548    tor->status = tr_cpIsSeeding( tor->completion ) ?
549                      TR_STATUS_SEED : TR_STATUS_DOWNLOAD;
550
551    while( !tor->die )
552    {
553        date1 = tr_date();
554
555        /* Are we finished ? */
556        if( ( tor->status & TR_STATUS_DOWNLOAD ) &&
557            tr_cpIsSeeding( tor->completion ) )
558        {
559            /* Done */
560            tor->status = TR_STATUS_SEED;
561                        tor->finished = 1;
562            tr_trackerCompleted( tor->tracker );
563            tr_ioSaveResume( tor->io );
564        }
565
566        /* Receive/send messages */
567        tr_peerPulse( tor );
568
569        /* Try to get new peers or to send a message to the tracker */
570        tr_trackerPulse( tor->tracker );
571
572        if( tor->status & TR_STATUS_STOPPED )
573        {
574            break;
575        }
576
577        /* Wait up to 20 ms */
578        date2 = tr_date();
579        if( date2 < date1 + 20 )
580        {
581            tr_lockUnlock( &tor->lock );
582            tr_wait( date1 + 20 - date2 );
583            tr_lockLock( &tor->lock );
584        }
585    }
586
587    tr_lockUnlock( &tor->lock );
588
589    tr_ioClose( tor->io );
590
591    tor->status = TR_STATUS_STOPPED;
592
593    tr_dbg( "Thread exited" );
594}
595
596/***********************************************************************
597 * acceptLoop
598 **********************************************************************/
599static void acceptLoop( void * _h )
600{
601    tr_handle_t * h = _h;
602    uint64_t      date1, date2, lastchoke = 0;
603    int           ii;
604    uint8_t     * hash;
605    tr_torrent_t * tor;
606
607    tr_dbg( "Accept thread started" );
608
609#ifdef SYS_BEOS
610    /* This is required because on BeOS, SIGINT is sent to each thread,
611       which kills them not nicely */
612    signal( SIGINT, SIG_IGN );
613#endif
614
615    tr_lockLock( &h->acceptLock );
616
617    while( !h->acceptDie )
618    {
619        date1 = tr_date();
620
621        /* Check for incoming connections */
622        if( h->bindSocket > -1 &&
623            h->acceptPeerCount < TR_MAX_PEER_COUNT &&
624            !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
625        {
626            int            s;
627            struct in_addr addr;
628            in_port_t      port;
629            s = tr_netAccept( h->bindSocket, &addr, &port );
630            if( s > -1 )
631            {
632                h->acceptPeers[h->acceptPeerCount++] = tr_peerInit( addr, port, s );
633            }
634            else
635            {
636                tr_fdSocketClosed( h->fdlimit, 0 );
637            }
638        }
639
640        for( ii = 0; ii < h->acceptPeerCount; )
641        {
642            if( tr_peerRead( NULL, h->acceptPeers[ii] ) )
643            {
644                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
645                goto removePeer;
646            }
647            if( NULL != ( hash = tr_peerHash( h->acceptPeers[ii] ) ) )
648            {
649                for( tor = h->torrentList; tor; tor = tor->next )
650                {
651                    tr_lockLock( &tor->lock );
652                    if( 0 == memcmp( tor->info.hash, hash,
653                                     SHA_DIGEST_LENGTH ) )
654                    {
655                      tr_peerAttach( tor, h->acceptPeers[ii] );
656                      tr_lockUnlock( &tor->lock );
657                      goto removePeer;
658                    }
659                    tr_lockUnlock( &tor->lock );
660                }
661                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
662                goto removePeer;
663            }
664            if( date1 > tr_peerDate( h->acceptPeers[ii] ) + 10000 )
665            {
666                /* Give them 10 seconds to send the handshake */
667                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
668                goto removePeer;
669            }
670            ii++;
671            continue;
672           removePeer:
673            h->acceptPeerCount--;
674            memmove( &h->acceptPeers[ii], &h->acceptPeers[ii+1],
675                     ( h->acceptPeerCount - ii ) * sizeof( tr_peer_t * ) );
676        }
677
678        if( date1 > lastchoke + 2000 )
679        {
680            tr_chokingPulse( h->choking );
681            lastchoke = date1;
682        }
683
684        /* Wait up to 20 ms */
685        date2 = tr_date();
686        if( date2 < date1 + 20 )
687        {
688            tr_lockUnlock( &h->acceptLock );
689            tr_wait( date1 + 20 - date2 );
690            tr_lockLock( &h->acceptLock );
691        }
692    }
693
694    tr_lockUnlock( &h->acceptLock );
695
696    tr_dbg( "Accept thread exited" );
697}
698
699/***********************************************************************
700 * acceptStop
701 ***********************************************************************
702 * Joins the accept thread and frees/closes everything related to it.
703 **********************************************************************/
704static void acceptStop( tr_handle_t * h )
705{
706    int ii;
707
708    h->acceptDie = 1;
709    tr_threadJoin( &h->acceptThread );
710    tr_lockClose( &h->acceptLock );
711    tr_dbg( "Accept thread joined" );
712
713    for( ii = 0; ii < h->acceptPeerCount; ii++ )
714    {
715        tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
716    }
717
718    if( h->bindSocket > -1 )
719    {
720        tr_netClose( h->bindSocket );
721        tr_fdSocketClosed( h->fdlimit, 0 );
722    }
723}
Note: See TracBrowser for help on using the repository browser.