source: trunk/libtransmission/fastresume.c @ 2377

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

fix overlap bug in file-selection where files adjacent to a "do not download" file could end up missing a few bytes.

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