source: trunk/libtransmission/fastresume.c @ 2443

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

more work on speed control

File size: 17.3 KB
Line 
1/******************************************************************************
2 * $Id:$
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 <sys/types.h>
45#include <sys/stat.h>
46#include <unistd.h>
47#include "transmission.h"
48#include "fastresume.h"
49
50/* time_t can be 32 or 64 bits... for consistency we'll hardwire 64 */ 
51typedef uint64_t tr_time_t; 
52
53enum
54{
55    /* deprecated */
56    FR_ID_PROGRESS_SLOTS = 1,
57
58    /* number of bytes downloaded */
59    FR_ID_DOWNLOADED = 2,
60
61    /* number of bytes uploaded */
62    FR_ID_UPLOADED = 3,
63
64    /* IPs and ports of connectable peers */
65    FR_ID_PEERS = 4,
66
67    /* progress data:
68     *  - 4 bytes * number of files: mtimes of files
69     *  - 1 bit * number of blocks: whether we have the block or not */
70    FR_ID_PROGRESS = 5,
71
72    /* dnd and priority
73     * char * number of files: l,n,h for low, normal, high priority
74     * char * number of files: t,f for DND flags */
75    FR_ID_PRIORITY = 6,
76
77    /* transfer speeds
78     * uint32_t: dl speed rate to use when the mode is single
79     * uint32_t: dl's tr_speedlimit_t
80     * uint32_t: ul speed rate to use when the mode is single
81     * uint32_t: ul's tr_speedlimit_t
82     */
83    FR_ID_SPEED = 8
84};
85
86
87/* macros for the length of various pieces of the progress data */
88#define FR_MTIME_LEN( t ) \
89  ( sizeof(tr_time_t) * (t)->info.fileCount )
90#define FR_BLOCK_BITFIELD_LEN( t ) \
91  ( ( (t)->blockCount + 7 ) / 8 )
92#define FR_PROGRESS_LEN( t ) \
93  ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) )
94#define FR_SPEED_LEN (2 * (sizeof(uint16_t) + sizeof(uint8_t) ) )
95
96static void
97fastResumeFileName( char * buf, size_t buflen, const tr_torrent_t * tor, int tag )
98{
99    const char * cacheDir = tr_getCacheDirectory ();
100    const char * hash = tor->info.hashString;
101
102    if( !tag )
103    {
104        tr_buildPath( buf, buflen, cacheDir, hash, NULL );
105    }
106    else
107    {
108        char base[1024];
109        snprintf( base, sizeof(base), "%s-%s", hash, tor->handle->tag );
110        tr_buildPath( buf, buflen, cacheDir, base, NULL );
111    }
112}
113
114static tr_time_t*
115getMTimes( const tr_torrent_t * tor, int * setme_n )
116{
117    int i;
118    const int n = tor->info.fileCount;
119    tr_time_t * m = calloc( n, sizeof(tr_time_t) );
120
121    for( i=0; i<n; ++i ) {
122        char fname[MAX_PATH_LENGTH];
123        struct stat sb;
124        tr_buildPath( fname, sizeof(fname),
125                      tor->destination, tor->info.files[i].name, NULL );
126        if ( !stat( fname, &sb ) && S_ISREG( sb.st_mode ) ) {
127#ifdef SYS_DARWIN
128            m[i] = sb.st_mtimespec.tv_sec;
129#else
130            m[i] = sb.st_mtime;
131#endif
132        }
133    }
134
135    *setme_n = n;
136    return m;
137}
138
139static void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
140                                 uint32_t count, FILE * file )
141{
142    uint32_t  datalen = size * count;
143
144    fwrite( &id, 1, 1, file );
145    fwrite( &datalen, 4, 1, file );
146    fwrite( data, size, count, file );
147}
148
149void fastResumeSave( const tr_torrent_t * tor )
150{
151    char      path[MAX_PATH_LENGTH];
152    FILE    * file;
153    const int version = 1;
154    uint64_t  total;
155
156    fastResumeFileName( path, sizeof path, tor, 1 );
157    file = fopen( path, "w" );
158    if( NULL == file ) {
159        tr_err( "Couldn't open '%s' for writing", path );
160        return;
161    }
162   
163    /* Write format version */
164    fwrite( &version, 4, 1, file );
165
166    /* Write progress data */
167    if (1) {
168        int n;
169        tr_time_t * mtimes;
170        uint8_t * buf = malloc( FR_PROGRESS_LEN( tor ) );
171        uint8_t * walk = buf;
172        const tr_bitfield_t * bitfield;
173
174        /* mtimes */
175        mtimes = getMTimes( tor, &n );
176        memcpy( walk, mtimes, n*sizeof(tr_time_t) );
177        walk += n * sizeof(tr_time_t);
178
179        /* completion bitfield */
180        bitfield = tr_cpBlockBitfield( tor->completion );
181        assert( (unsigned)FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
182        memcpy( walk, bitfield->bits, bitfield->len );
183        walk += bitfield->len;
184
185        /* write it */
186        assert( walk-buf == (int)FR_PROGRESS_LEN( tor ) );
187        fastResumeWriteData( FR_ID_PROGRESS, buf, 1, walk-buf, file );
188
189        /* cleanup */
190        free( mtimes );
191        free( buf );
192    }
193
194
195    /* Write the priorities and DND flags */
196    if( TRUE )
197    {
198        int i;
199        const int n = tor->info.fileCount;
200        char * buf = tr_new0( char, n*2 );
201        char * walk = buf;
202
203        /* priorities */
204        for( i=0; i<n; ++i ) {
205            char ch;
206            const int priority = tor->info.files[i].priority;
207            switch( priority ) {
208               case TR_PRI_LOW:   ch = 'l'; break; /* low */
209               case TR_PRI_HIGH:  ch = 'h'; break; /* high */
210               default:           ch = 'n'; break; /* normal */
211            };
212            *walk++ = ch;
213        }
214
215        /* dnd flags */
216        for( i=0; i<n; ++i )
217            *walk++ = tor->info.files[i].dnd ? 't' : 'f';
218
219        /* write it */
220        assert( walk - buf == 2*n );
221        fastResumeWriteData( FR_ID_PRIORITY, buf, 1, walk-buf, file );
222
223        /* cleanup */
224        tr_free( buf );
225    }
226
227
228    /* Write the torrent ul/dl speed caps */
229    if( TRUE )
230    {
231        const int len = FR_SPEED_LEN;
232        char * buf = tr_new0( char, len );
233        char * walk = buf;
234        uint16_t i16;
235        uint8_t i8;
236
237        i16 = (uint16_t) tr_torrentGetSpeedLimit( tor, TR_DOWN );
238        memcpy( walk, &i16, 2 ); walk += 2;
239        i8 = (uint8_t) tr_torrentGetSpeedMode( tor, TR_DOWN );
240        memcpy( walk, &i8, 1 ); walk += 1;
241        i16 = (uint16_t) tr_torrentGetSpeedLimit( tor, TR_UP );
242        memcpy( walk, &i16, 2 ); walk += 2;
243        i8 = (uint8_t) tr_torrentGetSpeedMode( tor, TR_UP );
244        memcpy( walk, &i8, 1 ); walk += 1;
245
246        assert( walk - buf == len );
247        fastResumeWriteData( FR_ID_SPEED, buf, 1, walk-buf, file );
248    }
249
250
251    /* Write download and upload totals */
252    total = tor->downloadedCur + tor->downloadedPrev;
253    fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
254    total = tor->uploadedCur + tor->uploadedPrev;
255    fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
256
257    if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
258    {
259        /* Write IPs and ports of connectable peers, if any */
260        int size;
261        uint8_t * buf = NULL;
262        if( ( size = tr_peerGetConnectable( tor, &buf ) ) > 0 )
263        {
264            fastResumeWriteData( FR_ID_PEERS, buf, size, 1, file );
265            free( buf );
266        }
267    }
268
269    fclose( file );
270
271    tr_dbg( "Resume file '%s' written", path );
272}
273
274static int
275loadSpeeds( tr_torrent_t * tor, FILE * file )
276{
277    const size_t len = FR_SPEED_LEN;
278    char * buf = tr_new0( char, len );
279    char * walk = buf;
280    uint16_t i16;
281    uint8_t i8;
282
283    if( len != fread( buf, 1, len, file ) ) {
284        tr_inf( "Couldn't read from resume file" );
285        free( buf );
286        return TR_ERROR_IO_OTHER;
287    }
288
289    memcpy( &i16, walk, 2 ); walk += 2;
290    tr_torrentSetSpeedLimit( tor, TR_DOWN, i16 );
291    memcpy( &i8, walk, 1 ); walk += 1;
292    tr_torrentSetSpeedMode( tor, TR_DOWN, (tr_speedlimit_t)i8 );
293    memcpy( &i16, walk, 2 ); walk += 2;
294    tr_torrentSetSpeedLimit( tor, TR_UP, i16 );
295    memcpy( &i8, walk, 1 ); walk += 1;
296    tr_torrentSetSpeedMode( tor, TR_UP, (tr_speedlimit_t)i8 );
297
298    tr_free( buf );
299    return TR_OK;
300}
301
302
303static int
304loadPriorities( tr_torrent_t * tor,
305                FILE         * file )
306{
307    const size_t n = tor->info.fileCount;
308    const size_t len = 2 * n;
309    int *dnd = NULL, dndCount = 0;
310    int *dl = NULL, dlCount = 0;
311    char * buf = tr_new0( char, len );
312    char * walk = buf;
313    size_t i;
314
315    if( len != fread( buf, 1, len, file ) ) {
316        tr_inf( "Couldn't read from resume file" );
317        free( buf );
318        return TR_ERROR_IO_OTHER;
319    }
320
321    /* set file priorities */
322    for( i=0; i<n; ++i ) {
323       tr_priority_t priority;
324       const char ch = *walk++;
325       switch( ch ) {
326           case 'l': priority = TR_PRI_LOW; break;
327           case 'h': priority = TR_PRI_HIGH; break;
328           default:  priority = TR_PRI_NORMAL; break;
329       }
330       tor->info.files[i].priority = priority;
331    }
332
333    /* set the dnd flags */
334    dl = tr_new( int, len );
335    dnd = tr_new( int, len );
336    for( i=0; i<n; ++i )
337        if( *walk++ == 't' ) /* 't' means the DND flag is true */
338            dnd[dndCount++] = i;
339        else
340            dl[dlCount++] = i;
341
342    if( dndCount )
343        tr_torrentSetFileDLs ( tor, dnd, dndCount, FALSE );
344    if( dlCount )
345        tr_torrentSetFileDLs ( tor, dl, dlCount, TRUE );
346
347    tr_free( dnd );
348    tr_free( dl );
349    tr_free( buf );
350    return TR_OK;
351}
352
353static int
354fastResumeLoadProgress( const tr_torrent_t  * tor,
355                        tr_bitfield_t       * uncheckedPieces,
356                        FILE                * file )
357{
358    const size_t len = FR_PROGRESS_LEN( tor );
359    uint8_t * buf = calloc( len, 1 );
360    uint8_t * walk = buf;
361
362    if( len != fread( buf, 1, len, file ) ) {
363        tr_inf( "Couldn't read from resume file" );
364        free( buf );
365        return TR_ERROR_IO_OTHER;
366    }
367
368    /* compare file mtimes */
369    if (1) {
370        int i, n;
371        tr_time_t * curMTimes = getMTimes( tor, &n );
372        const tr_time_t * oldMTimes = (const tr_time_t *) walk;
373        for( i=0; i<n; ++i ) {
374            if ( curMTimes[i]!=oldMTimes[i] ) {
375                const tr_file_t * file = &tor->info.files[i];
376                tr_dbg( "File '%s' mtimes differ-- flagging pieces [%d..%d] for recheck",
377                        file->name, file->firstPiece, file->lastPiece);
378                tr_bitfieldAddRange( uncheckedPieces, 
379                                     file->firstPiece, file->lastPiece+1 );
380            }
381        }
382        free( curMTimes );
383        walk += n * sizeof(tr_time_t);
384    }
385
386    /* get the completion bitfield */
387    if (1) {
388        tr_bitfield_t bitfield;
389        memset( &bitfield, 0, sizeof bitfield );
390        bitfield.len = FR_BLOCK_BITFIELD_LEN( tor );
391        bitfield.bits = walk;
392        tr_cpBlockBitfieldSet( tor->completion, &bitfield );
393    }
394
395    free( buf );
396    return TR_OK;
397}
398
399static int
400fastResumeLoadOld( tr_torrent_t   * tor,
401                   tr_bitfield_t  * uncheckedPieces, 
402                   FILE           * file )
403{
404    /* Check the size */
405    const int size = 4 + FR_PROGRESS_LEN( tor );
406    fseek( file, 0, SEEK_END );
407    if( ftell( file ) != size )
408    {
409        tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
410                (int)ftell( file ), size );
411        fclose( file );
412        return 1;
413    }
414
415    /* load progress information */
416    fseek( file, 4, SEEK_SET );
417    if( fastResumeLoadProgress( tor, uncheckedPieces, file ) )
418    {
419        fclose( file );
420        return 1;
421    }
422
423    fclose( file );
424
425    tr_inf( "Fast resuming successful (version 0)" );
426   
427    return 0;
428}
429
430int
431fastResumeLoad( tr_torrent_t   * tor,
432                tr_bitfield_t  * uncheckedPieces )
433{
434    char      path[MAX_PATH_LENGTH];
435    FILE    * file;
436    int       version = 0;
437    uint8_t   id;
438    uint32_t  len;
439    int       ret;
440
441    assert( tor != NULL );
442    assert( uncheckedPieces != NULL );
443
444    /* Open resume file */
445    fastResumeFileName( path, sizeof path, tor, 1 );
446    file = fopen( path, "r" );
447    if( NULL == file )
448    {
449        if( ENOENT == errno )
450        {
451            fastResumeFileName( path, sizeof path, tor, 0 );
452            file = fopen( path, "r" );
453            if( NULL != file )
454            {
455                goto good;
456            }
457            fastResumeFileName( path, sizeof path, tor, 1 );
458        }
459        tr_inf( "Could not open '%s' for reading", path );
460        return 1;
461    }
462  good:
463    tr_dbg( "Resume file '%s' loaded", path );
464
465    /* Check format version */
466    fread( &version, 4, 1, file );
467    if( 0 == version )
468    {
469        return fastResumeLoadOld( tor, uncheckedPieces, file );
470    }
471    if( 1 != version )
472    {
473        tr_inf( "Resume file has version %d, not supported", version );
474        fclose( file );
475        return 1;
476    }
477
478    ret = 1;
479    /* read each block of data */
480    while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
481    {
482        switch( id )
483        {
484            case FR_ID_PROGRESS:
485                /* read progress data */
486                if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
487                {
488                    ret = fastResumeLoadProgress( tor, uncheckedPieces, file );
489
490                    if( ret && ( feof(file) || ferror(file) ) )
491                    {
492                        fclose( file );
493                        return 1;
494                    }
495
496                    continue;
497                }
498                break;
499
500            case FR_ID_PRIORITY:
501
502                /* read priority data */
503                if( len == (uint32_t)(2 * tor->info.fileCount) )
504                {
505                    ret = loadPriorities( tor, file );
506
507                    if( ret && ( feof(file) || ferror(file) ) )
508                    {
509                        fclose( file );
510                        return 1;
511                    }
512
513                    continue;
514                }
515                break;
516
517            case FR_ID_SPEED:
518                /*  read speed data */
519                if( len == FR_SPEED_LEN )
520                {
521                    ret = loadSpeeds( tor, file );
522
523                    if( ret && ( feof(file) || ferror(file) ) )
524                    {
525                        fclose( file );
526                        return 1;
527                    }
528
529                    continue;
530                }
531                break;
532
533            case FR_ID_DOWNLOADED:
534                /* read download total */
535                if( 8 == len)
536                {
537                    if( 1 != fread( &tor->downloadedPrev, 8, 1, file ) )
538                    {
539                        fclose( file );
540                        return 1;
541                    }
542                    tor->downloadedCur = 0;
543                    continue;
544                }
545                break;
546
547            case FR_ID_UPLOADED:
548                /* read upload total */
549                if( 8 == len)
550                {
551                    if( 1 != fread( &tor->uploadedPrev, 8, 1, file ) )
552                    {
553                        fclose( file );
554                        return 1;
555                    }
556                    continue;
557                }
558                break;
559
560            case FR_ID_PEERS:
561                if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
562                {
563                    int used;
564                    uint8_t * buf = malloc( len );
565                    if( 1 != fread( buf, len, 1, file ) )
566                    {
567                        free( buf );
568                        fclose( file );
569                        return 1;
570                    }
571                    used = tr_torrentAddCompact( tor, TR_PEER_FROM_CACHE,
572                                                 buf, len / 6 );
573                    tr_dbg( "found %i peers in resume file, used %i",
574                            len / 6, used );
575                    free( buf );
576                }
577                continue;
578
579            default:
580                break;
581        }
582
583        /* if we didn't read the data, seek past it */
584        tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
585        fseek( file, len, SEEK_CUR );
586    }
587
588    fclose( file );
589
590    if( !ret )
591    {
592        tr_inf( "Fast resuming successful" );
593    }
594   
595    return ret;
596}
Note: See TracBrowser for help on using the repository browser.