source: trunk/libtransmission/fastresume.c @ 2319

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

fix dnd-setting bug created while splitting DND and priorities into two separate entities

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