source: trunk/libtransmission/torrent.c @ 5913

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

sine we now have two public ports (peer and rpc), rename "publicPort" as "peerPort"

  • Property svn:keywords set to Date Rev Author Id
File size: 38.7 KB
Line 
1/******************************************************************************
2 * $Id: torrent.c 5913 2008-05-23 16:18:58Z charles $
3 *
4 * Copyright (c) 2005-2008 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 <sys/types.h> /* stat */
26#include <sys/stat.h> /* stat */
27#include <unistd.h> /* stat */
28
29#include <assert.h>
30#include <limits.h> /* INT_MAX */
31#include <string.h> /* memcmp */
32#include <stdlib.h> /* qsort */
33
34#include "transmission.h"
35#include "bencode.h"
36#include "completion.h"
37#include "crypto.h" /* for tr_sha1 */
38#include "resume.h"
39#include "fdlimit.h" /* tr_fdFileClose */
40#include "metainfo.h"
41#include "peer-mgr.h"
42#include "ratecontrol.h"
43#include "torrent.h"
44#include "tracker.h"
45#include "trevent.h"
46#include "utils.h"
47#include "verify.h"
48
49#define MAX_BLOCK_SIZE (1024*16)
50
51/***
52****
53***/
54
55int
56tr_torrentId( const tr_torrent * tor )
57{
58    return tor->uniqueId;
59}
60
61tr_torrent*
62tr_torrentFindFromId( tr_handle * handle, int id )
63{
64    tr_torrent * tor = NULL;
65
66    while(( tor = tr_torrentNext( handle, tor )))
67        if( tor->uniqueId == id )
68            return tor;
69
70    return NULL;
71}
72
73tr_torrent*
74tr_torrentFindFromHashString( tr_handle * handle, const char * str )
75{
76    tr_torrent * tor = NULL;
77
78    while(( tor = tr_torrentNext( handle, tor )))
79        if( !strcmp( str, tor->info.hashString ) )
80            return tor;
81
82    return NULL;
83}
84
85int
86tr_torrentExists( const tr_handle * handle,
87                  const uint8_t   * torrentHash )
88{
89    return tr_torrentFindFromHash( (tr_handle*)handle, torrentHash ) != NULL;
90}
91
92tr_torrent*
93tr_torrentFindFromHash( tr_handle      * handle,
94                        const uint8_t  * torrentHash )
95{
96    tr_torrent * tor = NULL;
97
98    while(( tor = tr_torrentNext( handle, tor )))
99        if( !memcmp( tor->info.hash, torrentHash, SHA_DIGEST_LENGTH ) )
100            return tor;
101
102    return NULL;
103}
104
105tr_torrent*
106tr_torrentFindFromObfuscatedHash( tr_handle      * handle,
107                                  const uint8_t  * obfuscatedTorrentHash )
108{
109    tr_torrent * tor = NULL;
110
111    while(( tor = tr_torrentNext( handle, tor )))
112        if( !memcmp( tor->obfuscatedHash, obfuscatedTorrentHash, SHA_DIGEST_LENGTH ) )
113            return tor;
114
115    return NULL;
116}
117
118/***
119****  LOCKS
120***/
121
122void
123tr_torrentLock( const tr_torrent * tor )
124{
125    tr_globalLock( tor->handle );
126}
127
128void
129tr_torrentUnlock( const tr_torrent * tor )
130{
131    tr_globalUnlock( tor->handle );
132}
133
134/***
135****  PER-TORRENT UL / DL SPEEDS
136***/
137
138void
139tr_torrentSetSpeedMode( tr_torrent   * tor,
140                        int            up_or_down,
141                        tr_speedlimit  mode )
142{
143    tr_speedlimit * limit = up_or_down==TR_UP
144        ? &tor->uploadLimitMode
145        : &tor->downloadLimitMode;
146    *limit = mode;
147}
148
149tr_speedlimit
150tr_torrentGetSpeedMode( const tr_torrent * tor,
151                        int                up_or_down)
152{
153    return up_or_down==TR_UP ? tor->uploadLimitMode
154                             : tor->downloadLimitMode;
155}
156
157void
158tr_torrentSetSpeedLimit( tr_torrent   * tor,
159                         int            up_or_down,
160                         int            single_KiB_sec )
161{
162    tr_ratecontrol * rc = up_or_down==TR_UP ? tor->upload : tor->download;
163    tr_rcSetLimit( rc, single_KiB_sec );
164}
165
166int
167tr_torrentGetSpeedLimit( const tr_torrent  * tor,
168                         int                 up_or_down )
169{
170    tr_ratecontrol * rc = up_or_down==TR_UP ? tor->upload : tor->download;
171    return tr_rcGetLimit( rc );
172}
173
174/***
175****
176***/
177
178static void
179onTrackerResponse( void * tracker UNUSED, void * vevent, void * user_data )
180{
181    tr_torrent * tor = user_data;
182    tr_tracker_event * event = vevent;
183
184    switch( event->messageType )
185    {
186        case TR_TRACKER_PEERS: {
187            size_t i, n;
188            tr_pex * pex = tr_peerMgrCompactToPex( event->compact,
189                                                   event->compactLen,
190                                                   NULL, &n );
191            if( event->allAreSeeds )
192                tr_tordbg( tor, "Got %d seeds from tracker", (int)n );
193            else
194                tr_torinf( tor, _( "Got %d peers from tracker" ), (int)n );
195
196            for( i=0; i<n; ++i ) {
197                if( event->allAreSeeds )
198                    pex[i].flags |= ADDED_F_SEED_FLAG;
199                tr_peerMgrAddPex( tor->handle->peerMgr, tor->info.hash,
200                                  TR_PEER_FROM_TRACKER, pex+i );
201            }
202
203            tr_free( pex );
204            break;
205        }
206
207        case TR_TRACKER_WARNING:
208            tr_torerr( tor, _( "Tracker warning: \"%s\"" ), event->text );
209            tor->error = TR_ERROR_TC_WARNING;
210            tr_strlcpy( tor->errorString, event->text, sizeof(tor->errorString) );
211            break;
212
213        case TR_TRACKER_ERROR:
214            tr_torerr( tor, _( "Tracker error: \"%s\"" ), event->text );
215            tor->error = TR_ERROR_TC_ERROR;
216            tr_strlcpy( tor->errorString, event->text, sizeof(tor->errorString) );
217            break;
218
219        case TR_TRACKER_ERROR_CLEAR:
220            tor->error = 0;
221            tor->errorString[0] = '\0';
222            break;
223    }
224}
225
226/***
227****
228****  TORRENT INSTANTIATION
229****
230***/
231
232static int
233getBytePiece( const tr_info * info, uint64_t byteOffset )
234{
235    assert( info != NULL );
236    assert( info->pieceSize != 0 );
237
238    return byteOffset / info->pieceSize;
239}
240
241static void
242initFilePieces ( tr_info * info, tr_file_index_t fileIndex )
243{
244    tr_file * file = &info->files[fileIndex];
245    uint64_t firstByte, lastByte;
246
247    assert( info != NULL );
248    assert( fileIndex < info->fileCount );
249
250    file = &info->files[fileIndex];
251    firstByte = file->offset;
252    lastByte = firstByte + (file->length ? file->length-1 : 0);
253    file->firstPiece = getBytePiece( info, firstByte );
254    file->lastPiece = getBytePiece( info, lastByte );
255}
256
257static int
258pieceHasFile( tr_piece_index_t piece, const tr_file * file )
259{
260    return ( file->firstPiece <= piece ) && ( piece <= file->lastPiece );
261}
262
263static tr_priority_t
264calculatePiecePriority ( const tr_torrent * tor,
265                         tr_piece_index_t   piece,
266                         int                fileHint )
267{
268    tr_file_index_t i;
269    int priority = TR_PRI_LOW;
270
271    /* find the first file that has data in this piece */
272    if( fileHint >= 0 ) {
273        i = fileHint;
274        while( i>0 && pieceHasFile( piece, &tor->info.files[i-1] ) )
275            --i;
276    } else {
277        for( i=0; i<tor->info.fileCount; ++i )
278            if( pieceHasFile( piece, &tor->info.files[i] ) )
279                break;
280    }
281
282    /* the piece's priority is the max of the priorities
283     * of all the files in that piece */
284    for( ; i<tor->info.fileCount; ++i )
285    {
286        const tr_file * file = &tor->info.files[i];
287
288        if( !pieceHasFile( piece, file ) )
289            break;
290
291        priority = MAX( priority, file->priority );
292
293        /* when dealing with multimedia files, getting the first and
294           last pieces can sometimes allow you to preview it a bit
295           before it's fully downloaded... */
296        if ( file->priority >= TR_PRI_NORMAL )
297            if ( file->firstPiece == piece || file->lastPiece == piece )
298                priority = TR_PRI_HIGH;
299    }
300
301    return priority;
302}
303
304static void
305tr_torrentInitFilePieces( tr_torrent * tor )
306{
307    tr_file_index_t ff;
308    tr_piece_index_t pp;
309    uint64_t offset = 0;
310
311    assert( tor != NULL );
312
313    for( ff=0; ff<tor->info.fileCount; ++ff ) {
314      tor->info.files[ff].offset = offset;
315      offset += tor->info.files[ff].length;
316      initFilePieces( &tor->info, ff );
317    }
318
319    for( pp=0; pp<tor->info.pieceCount; ++pp )
320        tor->info.pieces[pp].priority = calculatePiecePriority( tor, pp, -1 );
321}
322
323int
324tr_torrentPromoteTracker( tr_torrent * tor, int pos )
325{
326    int i;
327    int tier;
328
329    assert( tor != NULL );
330    assert( ( 0 <= pos ) && ( pos < tor->info.trackerCount ) );
331
332    /* the tier of the tracker we're promoting */
333    tier = tor->info.trackers[pos].tier;
334
335    /* find the index of that tier's first tracker */
336    for( i=0; i<tor->info.trackerCount; ++i )
337        if( tor->info.trackers[i].tier == tier )
338            break;
339
340    assert( i < tor->info.trackerCount );
341
342    /* promote the tracker at `pos' to the front of the tier */
343    if( i != pos ) {
344        const tr_tracker_info tmp = tor->info.trackers[i];
345        tor->info.trackers[i] = tor->info.trackers[pos];
346        tor->info.trackers[pos] = tmp;
347    }
348
349    /* return the new position of the tracker that started out at [pos] */
350    return i;
351}
352
353struct RandomTracker
354{
355    tr_tracker_info tracker;
356    int random_value;
357};
358
359/* the tiers will be sorted from lowest to highest,
360 * and trackers are randomized within the tiers */
361static int
362compareRandomTracker( const void * va, const void * vb )
363{
364    const struct RandomTracker * a = va;
365    const struct RandomTracker * b = vb;
366
367    if( a->tracker.tier != b->tracker.tier )
368        return a->tracker.tier - b->tracker.tier;
369
370   return a->random_value - b->random_value;
371}
372   
373static void
374randomizeTiers( tr_info * info )
375{
376    int i;
377    const int n = info->trackerCount;
378    struct RandomTracker * r = tr_new0( struct RandomTracker, n );
379    for( i=0; i<n; ++i ) {
380        r[i].tracker = info->trackers[i];
381        r[i].random_value = tr_rand( INT_MAX );
382    }
383    qsort( r, n, sizeof( struct RandomTracker ), compareRandomTracker );
384    for( i=0; i<n; ++i )
385        info->trackers[i] = r[i].tracker;
386    tr_free( r );
387}
388
389static void torrentStart( tr_torrent * tor, int reloadProgress );
390
391static void
392torrentRealInit( tr_handle     * h,
393                 tr_torrent    * tor,
394                 const tr_ctor * ctor )
395{
396    int doStart;
397    uint64_t loaded;
398    uint64_t t;
399    tr_info * info = &tor->info;
400    static int nextUniqueId = 1;
401   
402    tr_globalLock( h );
403
404    tor->handle   = h;
405    tor->uniqueId = nextUniqueId++;
406
407    randomizeTiers( info );
408
409    /**
410     * Decide on a block size.  constraints:
411     * (1) most clients decline requests over 16 KiB
412     * (2) pieceSize must be a multiple of block size
413     */
414    tor->blockSize = info->pieceSize;
415    while( tor->blockSize > MAX_BLOCK_SIZE )
416        tor->blockSize /= 2;
417
418    tor->lastPieceSize = info->totalSize % info->pieceSize;
419
420    if( !tor->lastPieceSize )
421         tor->lastPieceSize = info->pieceSize;
422
423    tor->lastBlockSize = info->totalSize % tor->blockSize;
424
425    if( !tor->lastBlockSize )
426         tor->lastBlockSize = tor->blockSize;
427
428    tor->blockCount =
429        ( info->totalSize + tor->blockSize - 1 ) / tor->blockSize;
430
431    tor->blockCountInPiece =
432        info->pieceSize / tor->blockSize;
433
434    tor->blockCountInLastPiece =
435        ( tor->lastPieceSize + tor->blockSize - 1 ) / tor->blockSize;
436
437    /* check our work */
438    assert( ( info->pieceSize % tor->blockSize ) == 0 );
439    t = info->pieceCount - 1;
440    t *= info->pieceSize;
441    t += tor->lastPieceSize;
442    assert( t == info->totalSize );
443    t = tor->blockCount - 1;
444    t *= tor->blockSize;
445    t += tor->lastBlockSize;
446    assert( t == info->totalSize );
447    t = info->pieceCount - 1;
448    t *= tor->blockCountInPiece;
449    t += tor->blockCountInLastPiece;
450    assert( t == (uint64_t)tor->blockCount );
451
452    tor->completion = tr_cpInit( tor );
453
454    tr_torrentInitFilePieces( tor );
455
456    tor->upload         = tr_rcInit();
457    tor->download       = tr_rcInit();
458    tor->swarmSpeed     = tr_rcInit();
459
460    tr_sha1( tor->obfuscatedHash, "req2", 4,
461                                  info->hash, SHA_DIGEST_LENGTH,
462                                  NULL );
463
464    tr_peerMgrAddTorrent( h->peerMgr, tor );
465
466    if( !h->isPortSet )
467        tr_sessionSetPeerPort( h, TR_DEFAULT_PORT );
468
469    assert( !tor->downloadedCur );
470    assert( !tor->uploadedCur );
471
472    tor->error   = TR_OK;
473
474    tor->checkedPieces = tr_bitfieldNew( tor->info.pieceCount );
475    tr_torrentUncheck( tor );
476    loaded = tr_torrentLoadResume( tor, ~0, ctor );
477   
478    doStart = tor->isRunning;
479    tor->isRunning = 0;
480
481    if( !(loaded & TR_FR_SPEEDLIMIT ) ) {
482        tr_torrentSetSpeedLimit( tor, TR_UP,
483                tr_sessionGetSpeedLimit( tor->handle, TR_UP ) );
484        tr_torrentSetSpeedLimit( tor, TR_DOWN,
485                tr_sessionGetSpeedLimit( tor->handle, TR_DOWN ) );
486    }
487
488    tor->cpStatus = tr_cpGetStatus( tor->completion );
489
490    tor->tracker = tr_trackerNew( tor );
491    tor->trackerSubscription = tr_trackerSubscribe( tor->tracker, onTrackerResponse, tor );
492
493    {
494        tr_torrent * it = NULL;
495        tr_torrent * last = NULL;
496        while(( it = tr_torrentNext( h, it )))
497            last = it;
498        if( !last )
499            h->torrentList = tor;
500        else
501            last->next = tor;
502        ++h->torrentCount;
503    }
504
505    tr_globalUnlock( h );
506
507    /* maybe save our own copy of the metainfo */
508    if( tr_ctorGetSave( ctor ) ) {
509        const tr_benc * val;
510        if( !tr_ctorGetMetainfo( ctor, &val ) ) {
511            const char * filename = tor->info.torrent;
512            tr_bencSaveFile( filename, val );
513            tr_sessionSetTorrentFile( tor->handle, tor->info.hashString, filename );
514        }
515    }
516
517    tr_metainfoMigrate( h, &tor->info );
518
519    if( doStart )
520        torrentStart( tor, FALSE );
521}
522
523int
524tr_torrentParse( const tr_handle  * handle,
525                 const tr_ctor    * ctor,
526                 tr_info          * setmeInfo )
527{
528    int err = 0;
529    int doFree;
530    tr_info tmp;
531    const tr_benc * metainfo;
532
533    if( setmeInfo == NULL )
534        setmeInfo = &tmp;
535    memset( setmeInfo, 0, sizeof( tr_info ) );
536
537    if( !err && tr_ctorGetMetainfo( ctor, &metainfo ) )
538        return TR_EINVALID;
539
540    err = tr_metainfoParse( handle, setmeInfo, metainfo );
541    doFree = !err && ( setmeInfo == &tmp );
542
543    if( !err && tr_torrentExists( handle, setmeInfo->hash ) )
544        err = TR_EDUPLICATE;
545
546    if( doFree )
547        tr_metainfoFree( setmeInfo );
548
549    return err;
550}
551
552tr_torrent *
553tr_torrentNew( tr_handle      * handle,
554               const tr_ctor  * ctor,
555               int            * setmeError )
556{
557    int err;
558    tr_info tmpInfo;
559    tr_torrent * tor = NULL;
560
561    err = tr_torrentParse( handle, ctor, &tmpInfo );
562    if( !err ) {
563        tor = tr_new0( tr_torrent, 1 );
564        tor->info = tmpInfo;
565        torrentRealInit( handle, tor, ctor );
566    } else if( setmeError ) {
567        *setmeError = err;
568    }
569
570    return tor;
571}
572
573/**
574***
575**/
576
577void
578tr_torrentSetDownloadDir( tr_torrent * tor, const char * path )
579{
580    if( !path || !tor->downloadDir || strcmp( path, tor->downloadDir ) )
581    {
582        tr_free( tor->downloadDir );
583        tor->downloadDir = tr_strdup( path );
584        tr_torrentSaveResume( tor );
585    }
586}
587
588const char*
589tr_torrentGetDownloadDir( const tr_torrent * tor )
590{
591    return tor->downloadDir;
592}
593
594void
595tr_torrentChangeMyPort( tr_torrent * tor )
596{
597    if( tor->tracker )
598        tr_trackerChangeMyPort( tor->tracker );
599}
600
601int
602tr_torrentIsPrivate( const tr_torrent * tor )
603{
604    return tor
605        && tor->info.isPrivate;
606}
607
608int
609tr_torrentAllowsPex( const tr_torrent * tor )
610{
611    return tor
612        && tor->handle->isPexEnabled
613        && !tr_torrentIsPrivate( tor );
614}
615
616static void
617tr_torrentManualUpdateImpl( void * vtor )
618{
619    tr_torrent * tor = vtor;
620    if( tor->isRunning )
621        tr_trackerReannounce( tor->tracker );
622}
623void
624tr_torrentManualUpdate( tr_torrent * tor )
625{
626    tr_runInEventThread( tor->handle, tr_torrentManualUpdateImpl, tor );
627}
628int
629tr_torrentCanManualUpdate( const tr_torrent * tor )
630{
631    return ( tor != NULL )
632        && ( tor->isRunning )
633        && ( tr_trackerCanManualAnnounce( tor->tracker ) );
634}
635
636/* rcRate's averaging code can make it appear that we're
637 * still sending bytes after a torrent stops or all the
638 * peers disconnect, so short-circuit that appearance here */
639void
640tr_torrentGetRates( const tr_torrent * tor,
641                    float            * toClient,
642                    float            * toPeer)
643{
644    const int showSpeed = tor->isRunning
645        && tr_peerMgrHasConnections( tor->handle->peerMgr, tor->info.hash );
646
647    if( toClient )
648        *toClient = showSpeed ? tr_rcRate( tor->download ) : 0.0;
649    if( toPeer )
650        *toPeer = showSpeed ? tr_rcRate( tor->upload ) : 0.0;
651}
652const tr_info *
653tr_torrentInfo( const tr_torrent * tor )
654{
655    return tor ? &tor->info : NULL;
656}
657
658const tr_stat *
659tr_torrentStatCached( tr_torrent * tor )
660{
661    const time_t now = time( NULL );
662
663    return tor && ( now == tor->lastStatTime ) ? &tor->stats
664                                               : tr_torrentStat( tor );
665}
666
667tr_torrent_status
668tr_torrentGetStatus( tr_torrent * tor )
669{
670    tr_torrentRecheckCompleteness( tor );
671
672    if( tor->verifyState == TR_VERIFY_NOW )
673        return TR_STATUS_CHECK;
674    if( tor->verifyState == TR_VERIFY_WAIT )
675        return TR_STATUS_CHECK_WAIT;
676    if( !tor->isRunning )
677        return TR_STATUS_STOPPED;
678    if( tor->cpStatus == TR_CP_INCOMPLETE )
679        return TR_STATUS_DOWNLOAD;
680
681    return TR_STATUS_SEED;
682}
683
684const tr_stat *
685tr_torrentStat( tr_torrent * tor )
686{
687    tr_stat * s;
688    struct tr_tracker * tc;
689    const tr_tracker_info * ti;
690
691    if( !tor )
692        return NULL;
693
694    tr_torrentLock( tor );
695
696    tor->lastStatTime = time( NULL );
697
698    s = &tor->stats;
699    s->id = tor->uniqueId;
700    s->status = tr_torrentGetStatus( tor );
701    s->error  = tor->error;
702    memcpy( s->errorString, tor->errorString,
703            sizeof( s->errorString ) );
704
705    tc = tor->tracker;
706    ti = tr_trackerGetAddress( tor->tracker );
707    s->announceURL = ti ? ti->announce : NULL;
708    s->scrapeURL   = ti ? ti->scrape   : NULL;
709    tr_trackerStat( tc, s );
710    tr_trackerGetCounts( tc, &s->completedFromTracker,
711                             &s->leechers, 
712                             &s->seeders );
713    tr_peerMgrTorrentStats( tor->handle->peerMgr,
714                            tor->info.hash,
715                            &s->peersKnown,
716                            &s->peersConnected,
717                            &s->peersSendingToUs,
718                            &s->peersGettingFromUs,
719                             s->peersFrom );
720
721    s->percentComplete = tr_cpPercentComplete ( tor->completion );
722
723    s->percentDone = tr_cpPercentDone( tor->completion );
724    s->leftUntilDone = tr_cpLeftUntilDone( tor->completion );
725    s->sizeWhenDone = tr_cpSizeWhenDone( tor->completion );
726
727    s->recheckProgress =
728        1.0 - (tr_torrentCountUncheckedPieces( tor ) / (double) tor->info.pieceCount);
729
730    tr_torrentGetRates( tor, &s->rateDownload, &s->rateUpload );
731
732    s->swarmSpeed = tr_rcRate( tor->swarmSpeed );
733   
734    s->startDate = tor->startDate;
735    s->activityDate = tor->activityDate;
736
737    s->corruptEver     = tor->corruptCur    + tor->corruptPrev;
738    s->downloadedEver  = tor->downloadedCur + tor->downloadedPrev;
739    s->uploadedEver    = tor->uploadedCur   + tor->uploadedPrev;
740    s->haveValid       = tr_cpHaveValid( tor->completion );
741    s->haveUnchecked   = tr_cpHaveTotal( tor->completion ) - s->haveValid;
742
743
744    {
745        tr_piece_index_t i;
746        tr_bitfield * peerPieces = tr_peerMgrGetAvailable( tor->handle->peerMgr,
747                                                           tor->info.hash );
748        s->desiredAvailable = 0;
749        for( i=0; i<tor->info.pieceCount; ++i )
750            if( !tor->info.pieces[i].dnd && tr_bitfieldHas( peerPieces, i ) )
751                s->desiredAvailable += tr_cpMissingBlocksInPiece( tor->completion, i );
752        s->desiredAvailable *= tor->blockSize;
753        tr_bitfieldFree( peerPieces );
754    }
755
756    if( s->leftUntilDone > s->desiredAvailable )
757        s->eta = TR_ETA_NOT_AVAIL;
758    else if( s->rateDownload < 0.1 )
759        s->eta = TR_ETA_UNKNOWN;
760    else
761        s->eta = s->leftUntilDone / s->rateDownload / 1024.0;
762
763    s->ratio = tr_getRatio( s->uploadedEver,
764                            s->downloadedEver ? s->downloadedEver : s->haveValid );
765   
766    tr_torrentUnlock( tor );
767
768    return s;
769}
770
771/***
772****
773***/
774
775static uint64_t
776fileBytesCompleted ( const tr_torrent * tor, tr_file_index_t fileIndex )
777{
778    const tr_file * file     =  &tor->info.files[fileIndex];
779    const tr_block_index_t firstBlock       =  file->offset / tor->blockSize;
780    const uint64_t firstBlockOffset =  file->offset % tor->blockSize;
781    const uint64_t lastOffset       =  file->length ? (file->length-1) : 0;
782    const tr_block_index_t lastBlock        = (file->offset + lastOffset) / tor->blockSize;
783    const uint64_t lastBlockOffset  = (file->offset + lastOffset) % tor->blockSize;
784    uint64_t haveBytes = 0;
785
786    assert( tor != NULL );
787    assert( fileIndex < tor->info.fileCount );
788    assert( file->offset + file->length <= tor->info.totalSize );
789    assert( ( firstBlock < tor->blockCount ) || (!file->length && file->offset==tor->info.totalSize) );
790    assert( ( lastBlock < tor->blockCount ) || (!file->length && file->offset==tor->info.totalSize) );
791    assert( firstBlock <= lastBlock );
792    assert( tr_torBlockPiece( tor, firstBlock ) == file->firstPiece );
793    assert( tr_torBlockPiece( tor, lastBlock ) == file->lastPiece );
794
795    if( firstBlock == lastBlock )
796    {
797        if( tr_cpBlockIsComplete( tor->completion, firstBlock ) )
798            haveBytes += lastBlockOffset + 1 - firstBlockOffset;
799    }
800    else
801    {
802        tr_block_index_t i;
803
804        if( tr_cpBlockIsComplete( tor->completion, firstBlock ) )
805            haveBytes += tor->blockSize - firstBlockOffset;
806
807        for( i=firstBlock+1; i<lastBlock; ++i )
808            if( tr_cpBlockIsComplete( tor->completion, i ) )
809               haveBytes += tor->blockSize;
810
811        if( tr_cpBlockIsComplete( tor->completion, lastBlock ) )
812            haveBytes += lastBlockOffset + 1;
813    }
814
815    return haveBytes;
816}
817
818tr_file_stat *
819tr_torrentFiles( const tr_torrent * tor, tr_file_index_t * fileCount )
820{
821    tr_file_index_t i;
822    const tr_file_index_t n = tor->info.fileCount;
823    tr_file_stat * files = tr_new0( tr_file_stat, n );
824    tr_file_stat * walk = files;
825
826    for( i=0; i<n; ++i, ++walk ) {
827        const uint64_t b = fileBytesCompleted( tor, i );
828        walk->bytesCompleted = b;
829        walk->progress = tr_getRatio( b, tor->info.files[i].length );
830    }
831
832    if( fileCount )
833        *fileCount = n;
834
835    return files;
836}
837
838void
839tr_torrentFilesFree( tr_file_stat * files, tr_file_index_t fileCount UNUSED )
840{
841    tr_free( files );
842}
843
844/***
845****
846***/
847
848tr_peer_stat *
849tr_torrentPeers( const tr_torrent * tor, int * peerCount )
850{
851    tr_peer_stat * ret = NULL;
852
853    if( tor != NULL )
854        ret = tr_peerMgrPeerStats( tor->handle->peerMgr,
855                                   tor->info.hash, peerCount );
856
857    return ret;
858}
859
860void
861tr_torrentPeersFree( tr_peer_stat * peers, int peerCount UNUSED )
862{
863    tr_free( peers );
864}
865
866void tr_torrentAvailability( const tr_torrent * tor, int8_t * tab, int size )
867{
868    return tr_peerMgrTorrentAvailability( tor->handle->peerMgr,
869                                          tor->info.hash,
870                                          tab, size );
871}
872
873void
874tr_torrentAmountFinished( const tr_torrent * tor, float * tab, int size )
875{
876    tr_torrentLock( tor );
877    tr_cpGetAmountDone( tor->completion, tab, size );
878    tr_torrentUnlock( tor );
879}
880
881void
882tr_torrentResetTransferStats( tr_torrent * tor )
883{
884    tr_torrentLock( tor );
885
886    tor->downloadedPrev += tor->downloadedCur;
887    tor->downloadedCur   = 0;
888    tor->uploadedPrev   += tor->uploadedCur;
889    tor->uploadedCur     = 0;
890    tor->corruptPrev    += tor->corruptCur;
891    tor->corruptCur      = 0;
892
893    tr_torrentUnlock( tor );
894}
895
896
897void
898tr_torrentSetHasPiece( tr_torrent * tor, tr_piece_index_t pieceIndex, int has )
899{
900    tr_torrentLock( tor );
901
902    assert( tor != NULL );
903    assert( pieceIndex < tor->info.pieceCount );
904
905    if( has )
906        tr_cpPieceAdd( tor->completion, pieceIndex );
907    else
908        tr_cpPieceRem( tor->completion, pieceIndex );
909
910    tr_torrentUnlock( tor );
911}
912
913/***
914****
915***/
916
917static void
918freeTorrent( tr_torrent * tor )
919{
920    tr_torrent * t;
921    tr_handle * h = tor->handle;
922    tr_info * inf = &tor->info;
923
924    assert( tor != NULL );
925    assert( !tor->isRunning );
926
927    tr_globalLock( h );
928
929    tr_peerMgrRemoveTorrent( h->peerMgr, tor->info.hash );
930
931    tr_cpClose( tor->completion );
932
933    tr_rcClose( tor->upload );
934    tr_rcClose( tor->download );
935    tr_rcClose( tor->swarmSpeed );
936
937    tr_trackerUnsubscribe( tor->tracker, tor->trackerSubscription );
938    tr_trackerFree( tor->tracker );
939    tor->tracker = NULL;
940
941    tr_bitfieldFree( tor->checkedPieces );
942
943    tr_free( tor->downloadDir );
944
945    if( tor == h->torrentList )
946        h->torrentList = tor->next;
947    else for( t=h->torrentList; t!=NULL; t=t->next ) {
948        if( t->next == tor ) {
949            t->next = tor->next;
950            break;
951        }
952    }
953
954    assert( h->torrentCount >= 1 );
955    h->torrentCount--;
956
957    tr_metainfoFree( inf );
958    tr_free( tor );
959
960    tr_globalUnlock( h );
961}
962
963/**
964***  Start/Stop Callback
965**/
966
967static void
968fireActiveChange( tr_torrent * tor, int isRunning )
969{
970    assert( tor != NULL );
971
972    if( tor->active_func != NULL )
973        (tor->active_func)( tor, isRunning, tor->active_func_user_data );
974}
975
976void
977tr_torrentSetActiveCallback( tr_torrent             * tor,
978                             tr_torrent_active_func   func,
979                             void                   * user_data )
980{
981    assert( tor != NULL );
982    tor->active_func = func;
983    tor->active_func_user_data = user_data;
984}
985
986void
987tr_torrentClearActiveCallback( tr_torrent * torrent )
988{
989    tr_torrentSetActiveCallback( torrent, NULL, NULL );
990}
991
992
993static void
994checkAndStartImpl( void * vtor )
995{
996    tr_torrent * tor = vtor;
997
998    tr_globalLock( tor->handle );
999
1000    tor->isRunning  = 1;
1001    fireActiveChange( tor, tor->isRunning );
1002    *tor->errorString = '\0';
1003    tr_torrentResetTransferStats( tor );
1004    tor->cpStatus = tr_cpGetStatus( tor->completion );
1005    tr_torrentSaveResume( tor );
1006    tor->startDate = tr_date( );
1007    tr_trackerStart( tor->tracker );
1008    tr_peerMgrStartTorrent( tor->handle->peerMgr, tor->info.hash );
1009
1010    tr_globalUnlock( tor->handle );
1011}
1012
1013static void
1014checkAndStartCB( tr_torrent * tor )
1015{
1016    tr_runInEventThread( tor->handle, checkAndStartImpl, tor );
1017}
1018
1019static void
1020torrentStart( tr_torrent * tor, int reloadProgress )
1021{
1022    tr_globalLock( tor->handle );
1023
1024    if( !tor->isRunning )
1025    {
1026        if( reloadProgress )
1027            tr_torrentLoadResume( tor, TR_FR_PROGRESS, NULL );
1028        tor->isRunning = 1;
1029        tr_verifyAdd( tor, checkAndStartCB );
1030    }
1031
1032    tr_globalUnlock( tor->handle );
1033}
1034
1035void
1036tr_torrentStart( tr_torrent * tor )
1037{
1038    torrentStart( tor, TRUE );
1039}
1040
1041static void
1042torrentRecheckDoneImpl( void * vtor )
1043{
1044    tr_torrentRecheckCompleteness( vtor );
1045}
1046static void
1047torrentRecheckDoneCB( tr_torrent * tor )
1048{
1049    tr_runInEventThread( tor->handle, torrentRecheckDoneImpl, tor );
1050}
1051void
1052tr_torrentVerify( tr_torrent * tor )
1053{
1054    tr_verifyRemove( tor );
1055
1056    tr_globalLock( tor->handle );
1057
1058    tr_torrentUncheck( tor );
1059    tr_verifyAdd( tor, torrentRecheckDoneCB );
1060
1061    tr_globalUnlock( tor->handle );
1062}
1063
1064
1065static void
1066stopTorrent( void * vtor )
1067{
1068    tr_file_index_t i;
1069
1070    tr_torrent * tor = vtor;
1071    tr_verifyRemove( tor );
1072    tr_peerMgrStopTorrent( tor->handle->peerMgr, tor->info.hash );
1073    tr_trackerStop( tor->tracker );
1074    fireActiveChange( tor, 0 );
1075
1076    for( i=0; i<tor->info.fileCount; ++i )
1077    {
1078        char path[MAX_PATH_LENGTH];
1079        const tr_file * file = &tor->info.files[i];
1080        tr_buildPath( path, sizeof(path), tor->downloadDir, file->name, NULL );
1081        tr_fdFileClose( path );
1082    }
1083}
1084
1085void
1086tr_torrentStop( tr_torrent * tor )
1087{
1088    tr_globalLock( tor->handle );
1089
1090    tor->isRunning = 0;
1091    if( !tor->isDeleting )
1092        tr_torrentSaveResume( tor );
1093    tr_runInEventThread( tor->handle, stopTorrent, tor );
1094
1095    tr_globalUnlock( tor->handle );
1096}
1097
1098static void
1099closeTorrent( void * vtor )
1100{
1101    tr_torrent * tor = vtor;
1102    tr_torrentSaveResume( tor );
1103    tor->isRunning = 0;
1104    stopTorrent( tor );
1105    if( tor->isDeleting ) {
1106        tr_metainfoRemoveSaved( tor->handle, &tor->info );
1107        tr_torrentRemoveResume( tor );
1108    }
1109    freeTorrent( tor );
1110}
1111
1112void
1113tr_torrentFree( tr_torrent * tor )
1114{
1115    if( tor != NULL )
1116    {
1117        tr_handle * handle = tor->handle;
1118        tr_globalLock( handle );
1119
1120        tr_torrentClearStatusCallback( tor );
1121        tr_runInEventThread( handle, closeTorrent, tor );
1122
1123        tr_globalUnlock( handle );
1124    }
1125}
1126
1127void
1128tr_torrentRemove( tr_torrent * tor )
1129{
1130    tor->isDeleting = 1;
1131    tr_torrentFree( tor );
1132}
1133
1134
1135/**
1136***  Completeness
1137**/
1138
1139static const char *
1140getCompletionString( int type )
1141{
1142    switch( type )
1143    {
1144        /* Translators: this is a minor point that's safe to skip over, but FYI:
1145           "Complete" and "Done" are specific, different terms in Transmission:
1146           "Complete" means we've downloaded every file in the torrent.
1147           "Done" means we're done downloading the files we wanted, but NOT all that exist */
1148        case TR_CP_DONE:     return _( "Done" );
1149        case TR_CP_COMPLETE: return _( "Complete" );
1150        default:             return _( "Incomplete" );
1151    }
1152}
1153
1154static void
1155fireStatusChange( tr_torrent * tor, cp_status_t status )
1156{
1157    assert( tor != NULL );
1158    assert( status==TR_CP_INCOMPLETE || status==TR_CP_DONE || status==TR_CP_COMPLETE );
1159
1160    if( tor->status_func != NULL )
1161        (tor->status_func)( tor, status, tor->status_func_user_data );
1162}
1163
1164void
1165tr_torrentSetStatusCallback( tr_torrent             * tor,
1166                             tr_torrent_status_func   func,
1167                             void                   * user_data )
1168{
1169    assert( tor != NULL );
1170    tor->status_func = func;
1171    tor->status_func_user_data = user_data;
1172}
1173
1174void
1175tr_torrentClearStatusCallback( tr_torrent * torrent )
1176{
1177    tr_torrentSetStatusCallback( torrent, NULL, NULL );
1178}
1179
1180void
1181tr_torrentRecheckCompleteness( tr_torrent * tor )
1182{
1183    cp_status_t cpStatus;
1184
1185    tr_torrentLock( tor );
1186
1187    cpStatus = tr_cpGetStatus( tor->completion );
1188
1189    if( cpStatus != tor->cpStatus )
1190    {
1191        const int recentChange = tor->downloadedCur != 0;
1192
1193        if( recentChange )
1194        {
1195            tr_torinf( tor, _( "State changed from \"%1$s\" to \"%2$s\"" ),
1196                            getCompletionString( tor->cpStatus ),
1197                            getCompletionString( cpStatus ) );
1198        }
1199
1200        tor->cpStatus = cpStatus;
1201        fireStatusChange( tor, cpStatus );
1202
1203        if( recentChange && ( cpStatus == TR_CP_COMPLETE ) )
1204            tr_trackerCompleted( tor->tracker );
1205
1206        tr_torrentSaveResume( tor );
1207    }
1208
1209    tr_torrentUnlock( tor );
1210}
1211
1212int
1213tr_torrentIsSeed( const tr_torrent * tor )
1214{
1215    return tor->cpStatus==TR_CP_COMPLETE || tor->cpStatus==TR_CP_DONE;
1216}
1217
1218/**
1219***  File priorities
1220**/
1221
1222void
1223tr_torrentInitFilePriority( tr_torrent      * tor,
1224                            tr_file_index_t   fileIndex,
1225                            tr_priority_t     priority )
1226{
1227    tr_piece_index_t i;
1228    tr_file * file;
1229
1230    assert( tor != NULL );
1231    assert( fileIndex < tor->info.fileCount );
1232    assert( priority==TR_PRI_LOW || priority==TR_PRI_NORMAL || priority==TR_PRI_HIGH );
1233
1234    file = &tor->info.files[fileIndex];
1235    file->priority = priority;
1236    for( i=file->firstPiece; i<=file->lastPiece; ++i )
1237      tor->info.pieces[i].priority = calculatePiecePriority( tor, i, fileIndex );
1238}
1239
1240void
1241tr_torrentSetFilePriorities( tr_torrent       * tor,
1242                             tr_file_index_t  * files,
1243                             tr_file_index_t    fileCount,
1244                             tr_priority_t      priority )
1245{
1246    tr_file_index_t i;
1247    tr_torrentLock( tor );
1248
1249    for( i=0; i<fileCount; ++i )
1250        tr_torrentInitFilePriority( tor, files[i], priority );
1251
1252    tr_torrentSaveResume( tor );
1253    tr_torrentUnlock( tor );
1254}
1255
1256tr_priority_t
1257tr_torrentGetFilePriority( const tr_torrent *  tor, tr_file_index_t file )
1258{
1259    tr_priority_t ret;
1260
1261    tr_torrentLock( tor );
1262    assert( tor != NULL );
1263    assert( file < tor->info.fileCount );
1264    ret = tor->info.files[file].priority;
1265    tr_torrentUnlock( tor );
1266
1267    return ret;
1268}
1269
1270tr_priority_t*
1271tr_torrentGetFilePriorities( const tr_torrent * tor )
1272{
1273    tr_file_index_t i;
1274    tr_priority_t * p;
1275
1276    tr_torrentLock( tor );
1277    p = tr_new0( tr_priority_t, tor->info.fileCount );
1278    for( i=0; i<tor->info.fileCount; ++i )
1279        p[i] = tor->info.files[i].priority;
1280    tr_torrentUnlock( tor );
1281
1282    return p;
1283}
1284
1285/**
1286***  File DND
1287**/
1288
1289int
1290tr_torrentGetFileDL( const tr_torrent * tor,
1291                     tr_file_index_t    file )
1292{
1293    int doDownload;
1294    tr_torrentLock( tor );
1295
1296    assert( file < tor->info.fileCount );
1297    doDownload = !tor->info.files[file].dnd;
1298
1299    tr_torrentUnlock( tor );
1300    return doDownload != 0;
1301}
1302
1303static void
1304setFileDND( tr_torrent      * tor,
1305            tr_file_index_t   fileIndex,
1306            int               doDownload )
1307{
1308    tr_file * file;
1309    const int dnd = !doDownload;
1310    tr_piece_index_t firstPiece, firstPieceDND;
1311    tr_piece_index_t lastPiece, lastPieceDND;
1312    tr_file_index_t i;
1313
1314    file = &tor->info.files[fileIndex];
1315    file->dnd = dnd;
1316    firstPiece = file->firstPiece;
1317    lastPiece = file->lastPiece;
1318
1319    /* can't set the first piece to DND unless
1320       every file using that piece is DND */
1321    firstPieceDND = dnd;
1322    if( fileIndex > 0 ) {
1323        for( i=fileIndex-1; firstPieceDND; --i ) {
1324            if( tor->info.files[i].lastPiece != firstPiece )
1325                break;
1326            firstPieceDND = tor->info.files[i].dnd;
1327            if( !i )
1328                break;
1329        }
1330    }
1331
1332    /* can't set the last piece to DND unless
1333       every file using that piece is DND */
1334    lastPieceDND = dnd;
1335    for( i=fileIndex+1; lastPieceDND && i<tor->info.fileCount; ++i ) {
1336        if( tor->info.files[i].firstPiece != lastPiece )
1337            break;
1338        lastPieceDND = tor->info.files[i].dnd;
1339    }
1340
1341    if( firstPiece == lastPiece )
1342    {
1343        tor->info.pieces[firstPiece].dnd = firstPieceDND && lastPieceDND;
1344    }
1345    else
1346    {
1347        tr_piece_index_t pp;
1348        tor->info.pieces[firstPiece].dnd = firstPieceDND;
1349        tor->info.pieces[lastPiece].dnd = lastPieceDND;
1350        for( pp=firstPiece+1; pp<lastPiece; ++pp )
1351            tor->info.pieces[pp].dnd = dnd;
1352    }
1353}
1354
1355void
1356tr_torrentInitFileDLs ( tr_torrent       * tor,
1357                        tr_file_index_t  * files,
1358                        tr_file_index_t    fileCount,
1359                        int                doDownload )
1360{
1361    tr_file_index_t i;
1362    tr_torrentLock( tor );
1363
1364    for( i=0; i<fileCount; ++i )
1365        setFileDND( tor, files[i], doDownload );
1366    tr_cpInvalidateDND ( tor->completion );
1367
1368    tr_torrentUnlock( tor );
1369}
1370
1371void
1372tr_torrentSetFileDLs ( tr_torrent      * tor,
1373                       tr_file_index_t * files,
1374                       tr_file_index_t   fileCount,
1375                       int               doDownload )
1376{
1377    tr_torrentLock( tor );
1378    tr_torrentInitFileDLs( tor, files, fileCount, doDownload );
1379    tr_torrentSaveResume( tor );
1380    tr_torrentUnlock( tor );
1381}
1382
1383/***
1384****
1385***/
1386
1387void
1388tr_torrentSetPeerLimit( tr_torrent  * tor,
1389                        uint16_t      maxConnectedPeers )
1390{
1391    tor->maxConnectedPeers = maxConnectedPeers;
1392}
1393
1394uint16_t
1395tr_torrentGetPeerLimit( const tr_torrent  * tor )
1396{
1397    return tor->maxConnectedPeers;
1398}
1399
1400/***
1401****
1402***/
1403
1404tr_block_index_t
1405_tr_block( const tr_torrent  * tor,
1406           tr_piece_index_t    index,
1407           uint32_t            offset )
1408{
1409    const tr_info * inf = &tor->info;
1410    tr_block_index_t ret;
1411    ret = index;
1412    ret *= ( inf->pieceSize / tor->blockSize );
1413    ret += offset / tor->blockSize;
1414    return ret;
1415}
1416
1417int
1418tr_torrentReqIsValid( const tr_torrent * tor,
1419                      tr_piece_index_t   index,
1420                      uint32_t           offset,
1421                      uint32_t           length )
1422{
1423    int err = 0;
1424
1425    if( index >= tor->info.pieceCount )
1426        err = 1;
1427    else if ( offset >= tr_torPieceCountBytes( tor, index ) )
1428        err = 2;
1429    else if( length > MAX_BLOCK_SIZE )
1430        err = 3;
1431    else if( tr_pieceOffset( tor, index, offset, length ) > tor->info.totalSize )
1432        err = 4;
1433
1434    return !err;
1435}
1436
1437
1438uint64_t
1439tr_pieceOffset( const tr_torrent * tor,
1440                tr_piece_index_t   index,
1441                uint32_t           offset,
1442                uint32_t           length )
1443{
1444    uint64_t ret;
1445    ret = tor->info.pieceSize;
1446    ret *= index;
1447    ret += offset;
1448    ret += length;
1449    return ret;
1450}
1451
1452/***
1453****
1454***/
1455
1456int
1457tr_torrentIsPieceChecked( const tr_torrent * tor, tr_piece_index_t piece )
1458{
1459    return tr_bitfieldHas( tor->checkedPieces, piece );
1460}
1461
1462void
1463tr_torrentSetPieceChecked( tr_torrent * tor, tr_piece_index_t piece, int isChecked )
1464{
1465    if( isChecked )
1466        tr_bitfieldAdd( tor->checkedPieces, piece );
1467    else
1468        tr_bitfieldRem( tor->checkedPieces, piece );
1469}
1470
1471void
1472tr_torrentSetFileChecked( tr_torrent * tor, tr_file_index_t fileIndex, int isChecked )
1473{
1474    const tr_file * file = &tor->info.files[fileIndex];
1475    const tr_piece_index_t begin = file->firstPiece;
1476    const tr_piece_index_t end = file->lastPiece + 1;
1477
1478    if( isChecked )
1479        tr_bitfieldAddRange ( tor->checkedPieces, begin, end );
1480    else
1481        tr_bitfieldRemRange ( tor->checkedPieces, begin, end );
1482}
1483
1484int
1485tr_torrentIsFileChecked( const tr_torrent * tor, tr_file_index_t fileIndex )
1486{
1487    const tr_file * file = &tor->info.files[fileIndex];
1488    const tr_piece_index_t begin = file->firstPiece;
1489    const tr_piece_index_t end = file->lastPiece + 1;
1490    tr_piece_index_t i;
1491    int isChecked = TRUE;
1492
1493    for( i=begin; isChecked && i<end; ++i )
1494        if( !tr_torrentIsPieceChecked( tor, i ) )
1495            isChecked = FALSE;
1496
1497    return isChecked;
1498}
1499
1500void
1501tr_torrentUncheck( tr_torrent * tor )
1502{
1503    tr_bitfieldRemRange ( tor->checkedPieces, 0, tor->info.pieceCount );
1504}
1505
1506int
1507tr_torrentCountUncheckedPieces( const tr_torrent * tor )
1508{
1509    return tor->info.pieceCount - tr_bitfieldCountTrueBits( tor->checkedPieces );
1510}
1511
1512time_t*
1513tr_torrentGetMTimes( const tr_torrent * tor, int * setme_n )
1514{
1515    int i;
1516    const int n = tor->info.fileCount;
1517    time_t * m = tr_new0( time_t, n );
1518
1519    for( i=0; i<n; ++i ) {
1520        char fname[MAX_PATH_LENGTH];
1521        struct stat sb;
1522        tr_buildPath( fname, sizeof(fname),
1523                      tor->downloadDir, tor->info.files[i].name, NULL );
1524        if ( !stat( fname, &sb ) ) {
1525#ifdef SYS_DARWIN
1526            m[i] = sb.st_mtimespec.tv_sec;
1527#else
1528            m[i] = sb.st_mtime;
1529#endif
1530        }
1531    }
1532
1533    *setme_n = n;
1534    return m;
1535}
Note: See TracBrowser for help on using the repository browser.