source: trunk/libtransmission/fastresume.c @ 2544

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

this looks bug but it's not: just janitorial cleanup, moving #includes from headers into source file

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