source: trunk/libtransmission/fastresume.c @ 2462

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

set default torrent ul/dl speed limits from global ul/dl speed. as a side effect, totally decouples fastresume from inout.

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