source: trunk/libtransmission/fastresume.c @ 3985

Last change on this file since 3985 was 3985, checked in by charles, 15 years ago

remove dead code

  • Property svn:keywords set to Date Rev Author Id
File size: 19.7 KB
Line 
1/******************************************************************************
2 * $Id: fastresume.c 3985 2007-11-26 20:22:08Z charles $
3 *
4 * Copyright (c) 2005-2007 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/***********************************************************************
26 * Fast resume
27 ***********************************************************************
28 * The format of the resume file is a 4 byte format version (currently 1),
29 * followed by several variable-sized blocks of data.  Each block is
30 * preceded by a 1 byte ID and a 4 byte length.  The currently recognized
31 * IDs are defined below by the FR_ID_* macros.  The length does not include
32 * the 5 bytes for the ID and length.
33 *
34 * The name of the resume file is "resume.<hash>-<tag>", although
35 * older files with a name of "resume.<hash>" will be recognized if
36 * the former doesn't exist.
37 *
38 * All values are stored in the native endianness. Moving a
39 * libtransmission resume file from an architecture to another will not
40 * work, although it will not hurt either (the version will be wrong,
41 * so the resume file will not be read).
42 **********************************************************************/
43
44#include <assert.h>
45#include <errno.h>
46#include <stdio.h>
47#include <stdlib.h>
48#include <string.h>
49#include <time.h>
50
51#include <sys/types.h>
52#include <sys/stat.h>
53#include <unistd.h>
54
55#include <event.h>
56
57#include "transmission.h"
58#include "completion.h"
59#include "fastresume.h"
60#include "peer-mgr.h"
61#include "platform.h"
62#include "utils.h"
63
64/* time_t can be 32 or 64 bits... for consistency we'll hardwire 64 */ 
65typedef uint64_t tr_time_t; 
66
67enum
68{
69    /* number of bytes downloaded */
70    FR_ID_DOWNLOADED = 2,
71
72    /* number of bytes uploaded */
73    FR_ID_UPLOADED = 3,
74
75    /* progress data:
76     *  - 4 bytes * number of files: mtimes of files
77     *  - 1 bit * number of blocks: whether we have the block or not */
78    FR_ID_PROGRESS = 5,
79
80    /* dnd and priority
81     * char * number of files: l,n,h for low, normal, high priority
82     * char * number of files: t,f for DND flags */
83    FR_ID_PRIORITY = 6,
84
85    /* transfer speeds
86     * uint32_t: dl speed rate to use when the mode is single
87     * uint32_t: dl's tr_speedlimit
88     * uint32_t: ul speed rate to use when the mode is single
89     * uint32_t: ul's tr_speedlimit
90     */
91    FR_ID_SPEED = 8,
92
93    /* active
94     * char: 't' if running, 'f' if paused
95     */
96    FR_ID_RUN = 9,
97
98    /* number of corrupt bytes downloaded */
99    FR_ID_CORRUPT = 10,
100
101    /* IPs and ports of connectable peers */
102    FR_ID_PEERS = 11,
103
104    /* destination of the torrent: zero-terminated string */
105    FR_ID_DESTINATION = 12,
106
107    /* pex flag
108     * 't' if pex is enabled, 'f' if disabled */
109    FR_ID_PEX = 13
110};
111
112
113/* macros for the length of various pieces of the progress data */
114#define FR_MTIME_LEN( t ) \
115  ( sizeof(tr_time_t) * (t)->info.fileCount )
116#define FR_BLOCK_BITFIELD_LEN( t ) \
117  ( ( (t)->blockCount + 7 ) / 8 )
118#define FR_PROGRESS_LEN( t ) \
119  ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) )
120#define FR_SPEED_LEN (2 * (sizeof(uint16_t) + sizeof(uint8_t) ) )
121
122static void
123fastResumeFileName( char * buf, size_t buflen, const tr_torrent * tor, int tag )
124{
125    const char * cacheDir = tr_getCacheDirectory ();
126    const char * hash = tor->info.hashString;
127
128    if( !tag )
129    {
130        tr_buildPath( buf, buflen, cacheDir, hash, NULL );
131    }
132    else
133    {
134        char base[1024];
135        snprintf( base, sizeof(base), "%s-%s", hash, tor->handle->tag );
136        tr_buildPath( buf, buflen, cacheDir, base, NULL );
137    }
138}
139
140static tr_time_t*
141getMTimes( const tr_torrent * tor, int * setme_n )
142{
143    int i;
144    const int n = tor->info.fileCount;
145    tr_time_t * m = calloc( n, sizeof(tr_time_t) );
146
147    for( i=0; i<n; ++i ) {
148        char fname[MAX_PATH_LENGTH];
149        struct stat sb;
150        tr_buildPath( fname, sizeof(fname),
151                      tor->destination, tor->info.files[i].name, NULL );
152        if ( !stat( fname, &sb ) && S_ISREG( sb.st_mode ) ) {
153#ifdef SYS_DARWIN
154            m[i] = sb.st_mtimespec.tv_sec;
155#else
156            m[i] = sb.st_mtime;
157#endif
158        }
159    }
160
161    *setme_n = n;
162    return m;
163}
164
165static void
166fastResumeWriteData( uint8_t       id,
167                     const void  * data,
168                     uint32_t      size,
169                     uint32_t      count,
170                     FILE        * file )
171{
172    uint32_t  datalen = size * count;
173
174    fwrite( &id, 1, 1, file );
175    fwrite( &datalen, 4, 1, file );
176    fwrite( data, size, count, file );
177}
178
179void
180tr_fastResumeSave( const tr_torrent * tor )
181{
182    char      path[MAX_PATH_LENGTH];
183    FILE    * file;
184    const int version = 1;
185    uint64_t  total;
186
187    fastResumeFileName( path, sizeof path, tor, 1 );
188    file = fopen( path, "wb+" );
189    if( NULL == file ) {
190        tr_err( "Couldn't open '%s' for writing", path );
191        return;
192    }
193   
194    /* Write format version */
195    fwrite( &version, 4, 1, file );
196
197    if( TRUE ) /* FR_ID_DESTINATION */
198    {
199        const char * d = tor->destination ? tor->destination : "";
200        const int byteCount = strlen( d ) + 1;
201        fastResumeWriteData( FR_ID_DESTINATION, d, 1, byteCount, file );
202    }
203
204    /* Write progress data */
205    if (1) {
206        int n;
207        tr_time_t * mtimes;
208        uint8_t * buf = malloc( FR_PROGRESS_LEN( tor ) );
209        uint8_t * walk = buf;
210        const tr_bitfield * bitfield;
211
212        /* mtimes */
213        mtimes = getMTimes( tor, &n );
214        memcpy( walk, mtimes, n*sizeof(tr_time_t) );
215        walk += n * sizeof(tr_time_t);
216
217        /* completion bitfield */
218        bitfield = tr_cpBlockBitfield( tor->completion );
219        assert( (unsigned)FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
220        memcpy( walk, bitfield->bits, bitfield->len );
221        walk += bitfield->len;
222
223        /* write it */
224        assert( walk-buf == (int)FR_PROGRESS_LEN( tor ) );
225        fastResumeWriteData( FR_ID_PROGRESS, buf, 1, walk-buf, file );
226
227        /* cleanup */
228        free( mtimes );
229        free( buf );
230    }
231
232
233    /* Write the priorities and DND flags */
234    if( TRUE )
235    {
236        int i;
237        const int n = tor->info.fileCount;
238        char * buf = tr_new0( char, n*2 );
239        char * walk = buf;
240
241        /* priorities */
242        for( i=0; i<n; ++i ) {
243            char ch;
244            const int priority = tor->info.files[i].priority;
245            switch( priority ) {
246               case TR_PRI_LOW:   ch = 'l'; break; /* low */
247               case TR_PRI_HIGH:  ch = 'h'; break; /* high */
248               default:           ch = 'n'; break; /* normal */
249            };
250            *walk++ = ch;
251        }
252
253        /* dnd flags */
254        for( i=0; i<n; ++i )
255            *walk++ = tor->info.files[i].dnd ? 't' : 'f';
256
257        /* write it */
258        assert( walk - buf == 2*n );
259        fastResumeWriteData( FR_ID_PRIORITY, buf, 1, walk-buf, file );
260
261        /* cleanup */
262        tr_free( buf );
263    }
264
265
266    /* Write the torrent ul/dl speed caps */
267    if( TRUE )
268    {
269        const int len = FR_SPEED_LEN;
270        char * buf = tr_new0( char, len );
271        char * walk = buf;
272        uint16_t i16;
273        uint8_t i8;
274
275        i16 = (uint16_t) tr_torrentGetSpeedLimit( tor, TR_DOWN );
276        memcpy( walk, &i16, 2 ); walk += 2;
277        i8 = (uint8_t) tr_torrentGetSpeedMode( tor, TR_DOWN );
278        memcpy( walk, &i8, 1 ); walk += 1;
279        i16 = (uint16_t) tr_torrentGetSpeedLimit( tor, TR_UP );
280        memcpy( walk, &i16, 2 ); walk += 2;
281        i8 = (uint8_t) tr_torrentGetSpeedMode( tor, TR_UP );
282        memcpy( walk, &i8, 1 ); walk += 1;
283
284        assert( walk - buf == len );
285        fastResumeWriteData( FR_ID_SPEED, buf, 1, walk-buf, file );
286        tr_free( buf );
287    }
288
289    if( TRUE ) /* FR_ID_PEX */
290    {
291        const char flag = tor->pexDisabled ? 'f' : 't';
292        fastResumeWriteData( FR_ID_PEX, &flag, 1, 1, file );
293    }
294
295    if( TRUE ) /* FR_ID_RUN */
296    {
297        const char is_running = tor->isRunning ? 't' : 'f';
298        fastResumeWriteData( FR_ID_RUN, &is_running, 1, 1, file );
299    }
300
301    /* Write download and upload totals */
302
303    total = tor->downloadedCur + tor->downloadedPrev;
304    fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
305
306    total = tor->uploadedCur + tor->uploadedPrev;
307    fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
308
309    total = tor->corruptCur + tor->corruptPrev;
310    fastResumeWriteData( FR_ID_CORRUPT, &total, 8, 1, file );
311
312    if( !tor->info.isPrivate )
313    {
314        tr_pex * pex;
315        const int count = tr_peerMgrGetPeers( tor->handle->peerMgr,
316                                              tor->info.hash,
317                                              &pex );
318        if( count > 0 )
319            fastResumeWriteData( FR_ID_PEERS, pex, sizeof(tr_pex), count, file );
320        tr_free( pex );
321    }
322
323    fclose( file );
324
325    tr_dbg( "Resume file '%s' written", path );
326}
327
328/***
329****
330***/
331
332static uint64_t
333internalIdToPublicBitfield( uint8_t id )
334{
335    uint64_t ret = 0;
336
337    switch( id )
338    {
339        case FR_ID_DOWNLOADED:     ret = TR_FR_DOWNLOADED;  break;
340        case FR_ID_UPLOADED:       ret = TR_FR_UPLOADED;    break;
341        case FR_ID_PROGRESS:       ret = TR_FR_PROGRESS;    break;
342        case FR_ID_PRIORITY:       ret = TR_FR_PRIORITY;    break;
343        case FR_ID_SPEED:          ret = TR_FR_SPEEDLIMIT;  break;
344        case FR_ID_RUN:            ret = TR_FR_RUN;         break;
345        case FR_ID_CORRUPT:        ret = TR_FR_CORRUPT;     break;
346        case FR_ID_PEERS:          ret = TR_FR_PEERS;       break;
347        case FR_ID_DESTINATION:    ret = TR_FR_DESTINATION; break;
348        case FR_ID_PEX:            ret = TR_FR_PEX;         break;
349    }
350
351    return ret;
352}
353
354static void
355readBytes( void * target, const uint8_t ** source, size_t byteCount )
356{
357    memcpy( target, *source, byteCount );
358    *source += byteCount;
359}
360
361static uint64_t
362parseDownloaded( tr_torrent * tor, const uint8_t * buf, uint32_t len )
363{
364    if( len != sizeof(uint64_t) )
365        return 0;
366    readBytes( &tor->downloadedPrev, &buf, sizeof(uint64_t) );
367    return TR_FR_DOWNLOADED;
368}
369
370static uint64_t
371parseUploaded( tr_torrent * tor, const uint8_t * buf, uint32_t len )
372{
373    if( len != sizeof(uint64_t) )
374        return 0;
375    readBytes( &tor->uploadedPrev, &buf, sizeof(uint64_t) );
376    return TR_FR_UPLOADED;
377}
378
379static uint64_t
380parseCorrupt( tr_torrent * tor, const uint8_t * buf, uint32_t len )
381{
382    if( len != sizeof(uint64_t) )
383        return 0;
384    readBytes( &tor->corruptPrev, &buf, sizeof(uint64_t) );
385    return TR_FR_CORRUPT;
386}
387
388static uint64_t
389parseProgress( const tr_torrent  * tor,
390               const uint8_t     * buf,
391               uint32_t            len,
392               tr_bitfield       * uncheckedPieces )
393{
394    int i;
395    uint64_t ret = 0;
396   
397    if( len == FR_PROGRESS_LEN( tor ) )
398    {
399        int n;
400        tr_bitfield bitfield;
401
402        /* compare file mtimes */
403        tr_time_t * curMTimes = getMTimes( tor, &n );
404        const uint8_t * walk = buf;
405        const tr_time_t * oldMTimes = (const tr_time_t *) walk;
406        for( i=0; i<n; ++i ) {
407            if ( !curMTimes[i] || ( curMTimes[i] != oldMTimes[i] ) ) {
408                const tr_file * file = &tor->info.files[i];
409                tr_dbg( "File '%s' mtimes differ-- flagging pieces [%d..%d] for recheck",
410                        file->name, file->firstPiece, file->lastPiece);
411                tr_bitfieldAddRange( uncheckedPieces, 
412                                     file->firstPiece, file->lastPiece+1 );
413            }
414        }
415        free( curMTimes );
416        walk += n * sizeof(tr_time_t);
417
418        /* get the completion bitfield */
419        memset( &bitfield, 0, sizeof bitfield );
420        bitfield.len = FR_BLOCK_BITFIELD_LEN( tor );
421        bitfield.bits = (uint8_t*) walk;
422        tr_cpBlockBitfieldSet( tor->completion, &bitfield );
423
424        ret = TR_FR_PROGRESS;
425    }
426
427    /* the files whose mtimes are wrong,
428       remove from completion pending a recheck... */
429    for( i=0; i<tor->info.pieceCount; ++i )
430        if( tr_bitfieldHas( uncheckedPieces, i ) )
431            tr_cpPieceRem( tor->completion, i );
432
433    return ret;
434}
435
436static uint64_t
437parsePriorities( tr_torrent * tor, const uint8_t * buf, uint32_t len )
438{
439    uint64_t ret = 0;
440
441    if( len == (uint32_t)(2 * tor->info.fileCount) )
442    {
443        const size_t n = tor->info.fileCount;
444        const size_t len = 2 * n;
445        int *dnd = NULL, dndCount = 0;
446        int *dl = NULL, dlCount = 0;
447        size_t i;
448        const uint8_t * walk = buf;
449
450        /* set file priorities */
451        for( i=0; i<n; ++i ) {
452           tr_priority_t priority;
453           const char ch = *walk++;
454           switch( ch ) {
455               case 'l': priority = TR_PRI_LOW; break;
456               case 'h': priority = TR_PRI_HIGH; break;
457               default:  priority = TR_PRI_NORMAL; break;
458           }
459           tor->info.files[i].priority = priority;
460        }
461
462        /* set the dnd flags */
463        dl = tr_new( int, len );
464        dnd = tr_new( int, len );
465        for( i=0; i<n; ++i )
466            if( *walk++ == 't' ) /* 't' means the DND flag is true */
467                dnd[dndCount++] = i;
468            else
469                dl[dlCount++] = i;
470
471        if( dndCount )
472            tr_torrentSetFileDLs ( tor, dnd, dndCount, FALSE );
473        if( dlCount )
474            tr_torrentSetFileDLs ( tor, dl, dlCount, TRUE );
475
476        tr_free( dnd );
477        tr_free( dl );
478
479        ret = TR_FR_PRIORITY;
480    }
481
482    return ret;
483}
484
485static uint64_t
486parseSpeedLimit( tr_torrent * tor, const uint8_t * buf, uint32_t len )
487{
488    uint64_t ret = 0;
489
490    if( len == FR_SPEED_LEN )
491    {
492        uint8_t i8;
493        uint16_t i16;
494
495        readBytes( &i16, &buf, sizeof(i16) );
496        tr_torrentSetSpeedLimit( tor, TR_DOWN, i16 );
497        readBytes( &i8, &buf, sizeof(i8) );
498        tr_torrentSetSpeedMode( tor, TR_DOWN, (tr_speedlimit)i8 );
499        readBytes( &i16, &buf, sizeof(i16) );
500        tr_torrentSetSpeedLimit( tor, TR_UP, i16 );
501        readBytes( &i8, &buf, sizeof(i8) );
502        tr_torrentSetSpeedMode( tor, TR_UP, (tr_speedlimit)i8 );
503
504        ret = TR_FR_SPEEDLIMIT;
505    }
506
507    return ret;
508}
509
510static uint64_t
511parseRun( tr_torrent * tor, const uint8_t * buf, uint32_t len )
512{
513    if( len != 1 )
514        return 0;
515    tor->isRunning = *buf=='t';
516    return TR_FR_RUN;
517}
518
519static uint64_t
520parsePex( tr_torrent * tor, const uint8_t * buf, uint32_t len )
521{
522    if( len != 1 )
523        return 0;
524    tor->pexDisabled = *buf!='t';
525    return TR_FR_PEX;
526}
527
528static uint64_t
529parsePeers( tr_torrent * tor, const uint8_t * buf, uint32_t len )
530{
531    uint64_t ret = 0;
532
533    if( !tor->info.isPrivate )
534    {
535        const int count = len / sizeof(tr_pex);
536        tr_peerMgrAddPex( tor->handle->peerMgr,
537                          tor->info.hash,
538                          TR_PEER_FROM_CACHE,
539                          (tr_pex*)buf, count );
540        tr_dbg( "found %i peers in resume file", count );
541        ret = TR_FR_PEERS;
542    }
543
544    return ret;
545}
546
547static uint64_t
548parseDestination( tr_torrent * tor, const uint8_t * buf, uint32_t len,
549                  const char * destination, int argIsFallback )
550{
551    if( argIsFallback )
552        tor->destination = tr_strdup( len ? (const char*)buf : destination );
553    else
554        tor->destination = tr_strdup( len ? destination : (const char*)buf );
555
556    return TR_FR_DESTINATION;
557}
558
559static uint64_t
560parseVersion1( tr_torrent * tor, const uint8_t * buf, const uint8_t * end,
561               uint64_t fieldsToLoad,
562               tr_bitfield  * uncheckedPieces,
563               const char * destination, int argIsFallback )
564{
565    uint64_t ret = 0;
566
567    while( end-buf >= 5 )
568    {
569        uint8_t id;
570        uint32_t len;
571        readBytes( &id, &buf, sizeof(id) );
572        readBytes( &len, &buf, sizeof(len) );
573
574        if( fieldsToLoad & internalIdToPublicBitfield( id ) ) switch( id )
575        {
576            case FR_ID_DOWNLOADED:  ret |= parseDownloaded( tor, buf, len ); break;
577            case FR_ID_UPLOADED:    ret |= parseUploaded( tor, buf, len ); break;
578            case FR_ID_PROGRESS:    ret |= parseProgress( tor, buf, len, uncheckedPieces ); break;
579            case FR_ID_PRIORITY:    ret |= parsePriorities( tor, buf, len ); break;
580            case FR_ID_SPEED:       ret |= parseSpeedLimit( tor, buf, len ); break;
581            case FR_ID_RUN:         ret |= parseRun( tor, buf, len ); break;
582            case FR_ID_CORRUPT:     ret |= parseCorrupt( tor, buf, len ); break;
583            case FR_ID_PEERS:       ret |= parsePeers( tor, buf, len ); break;
584            case FR_ID_PEX:         ret |= parsePex( tor, buf, len ); break;
585            case FR_ID_DESTINATION: ret |= parseDestination( tor, buf, len, destination, argIsFallback ); break;
586            default:                tr_dbg( "Skipping unknown resume code %d", (int)id ); break;
587        }
588
589        buf += len;
590    }
591
592    return ret;
593}
594
595static uint8_t* 
596loadResumeFile( const tr_torrent * tor, size_t * len )
597{
598    uint8_t * ret = NULL;
599    char path[MAX_PATH_LENGTH];
600    const char * cacheDir = tr_getCacheDirectory ();
601    const char * hash = tor->info.hashString;
602
603    if( !ret && tor->handle->tag )
604    {
605        char base[1024];
606        snprintf( base, sizeof(base), "%s-%s", hash, tor->handle->tag );
607        tr_buildPath( path, sizeof(path), cacheDir, base, NULL );
608        ret = tr_loadFile( path, len );
609    }
610
611    if( !ret )
612    {
613        tr_buildPath( path, sizeof(path), cacheDir, hash, NULL );
614        ret = tr_loadFile( path, len );
615    }
616
617    return ret;
618}
619
620static uint64_t
621fastResumeLoadImpl ( tr_torrent   * tor,
622                     uint64_t       fieldsToLoad,
623                     tr_bitfield  * uncheckedPieces,
624                     const char   * destination,
625                     int            argIsFallback )
626{
627    uint64_t ret = 0;
628    size_t size = 0;
629    uint8_t * buf = loadResumeFile( tor, &size );
630
631    if( !buf )
632        tr_inf( "Couldn't read resume file for '%s'", tor->info.name );
633    else {
634        const uint8_t * walk = buf;
635        const uint8_t * end = walk + size;
636        if( end - walk >= 4 ) {
637            uint32_t version;
638            readBytes( &version, &walk, sizeof(version) );
639            if( version == 1 )
640                ret |= parseVersion1 ( tor, walk, end, fieldsToLoad, uncheckedPieces, destination, argIsFallback );
641            else
642                tr_inf( "Unsupported resume file %d for '%s'", version, tor->info.name );
643        }
644
645        tr_free( buf );
646    }
647
648    return ret;
649}
650
651uint64_t
652tr_fastResumeLoad( tr_torrent   * tor,
653                   uint64_t       fieldsToLoad,
654                   tr_bitfield  * uncheckedPieces,
655                   const char   * destination,
656                   int            argIsFallback )
657{
658    const uint64_t ret = fastResumeLoadImpl( tor, fieldsToLoad, uncheckedPieces, destination, argIsFallback );
659
660    if( ! ( ret & TR_FR_PROGRESS ) )
661        tr_bitfieldAddRange( uncheckedPieces, 0, tor->info.pieceCount );
662
663    if( !tor->destination )
664        tor->destination = tr_strdup( destination );
665
666    return ret;
667}
Note: See TracBrowser for help on using the repository browser.