source: trunk/libtransmission/fastresume.c @ 2388

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

fold per-torrent ul/dl speed cap settings into libtransmission. synchronize gtk+ client with this change. breaks os x client.

File size: 17.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 <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: the dl speed rate to use when the flag is true
79     * char: t,f for whether or not dl speed is capped
80     * uint32_t: the ul speed rate to use when the flag is true
81     * char: t,f for whether or not ul speed is capped
82     */
83    FR_ID_SPEED = 7
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
95static void
96fastResumeFileName( char * buf, size_t buflen, const tr_torrent_t * tor, int tag )
97{
98    const char * cacheDir = tr_getCacheDirectory ();
99    const char * hash = tor->info.hashString;
100
101    if( !tag )
102    {
103        tr_buildPath( buf, buflen, cacheDir, hash, NULL );
104    }
105    else
106    {
107        char base[1024];
108        snprintf( base, sizeof(base), "%s-%s", hash, tor->handle->tag );
109        tr_buildPath( buf, buflen, cacheDir, base, NULL );
110    }
111}
112
113static tr_time_t*
114getMTimes( const tr_torrent_t * tor, int * setme_n )
115{
116    int i;
117    const int n = tor->info.fileCount;
118    tr_time_t * m = calloc( n, sizeof(tr_time_t) );
119
120    for( i=0; i<n; ++i ) {
121        char fname[MAX_PATH_LENGTH];
122        struct stat sb;
123        tr_buildPath( fname, sizeof(fname),
124                      tor->destination, tor->info.files[i].name, NULL );
125        if ( !stat( fname, &sb ) && S_ISREG( sb.st_mode ) ) {
126#ifdef SYS_DARWIN
127            m[i] = sb.st_mtimespec.tv_sec;
128#else
129            m[i] = sb.st_mtime;
130#endif
131        }
132    }
133
134    *setme_n = n;
135    return m;
136}
137
138static inline void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
139                                        uint32_t count, FILE * file )
140{
141    uint32_t  datalen = size * count;
142
143    fwrite( &id, 1, 1, file );
144    fwrite( &datalen, 4, 1, file );
145    fwrite( data, size, count, file );
146}
147
148void fastResumeSave( const tr_torrent_t * tor )
149{
150    char      path[MAX_PATH_LENGTH];
151    FILE    * file;
152    const int version = 1;
153    uint64_t  total;
154
155    fastResumeFileName( path, sizeof path, tor, 1 );
156    file = fopen( path, "w" );
157    if( NULL == file ) {
158        tr_err( "Couldn't open '%s' for writing", path );
159        return;
160    }
161   
162    /* Write format version */
163    fwrite( &version, 4, 1, file );
164
165    /* Write progress data */
166    if (1) {
167        int n;
168        tr_time_t * mtimes;
169        uint8_t * buf = malloc( FR_PROGRESS_LEN( tor ) );
170        uint8_t * walk = buf;
171        const tr_bitfield_t * bitfield;
172
173        /* mtimes */
174        mtimes = getMTimes( tor, &n );
175        memcpy( walk, mtimes, n*sizeof(tr_time_t) );
176        walk += n * sizeof(tr_time_t);
177
178        /* completion bitfield */
179        bitfield = tr_cpBlockBitfield( tor->completion );
180        assert( (unsigned)FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
181        memcpy( walk, bitfield->bits, bitfield->len );
182        walk += bitfield->len;
183
184        /* write it */
185        assert( walk-buf == (int)FR_PROGRESS_LEN( tor ) );
186        fastResumeWriteData( FR_ID_PROGRESS, buf, 1, walk-buf, file );
187
188        /* cleanup */
189        free( mtimes );
190        free( buf );
191    }
192
193
194    /* Write the priorities and DND flags */
195    if( TRUE )
196    {
197        int i;
198        const int n = tor->info.fileCount;
199        char * buf = tr_new0( char, n*2 );
200        char * walk = buf;
201
202        /* priorities */
203        for( i=0; i<n; ++i ) {
204            char ch;
205            const int priority = tor->info.files[i].priority;
206            switch( priority ) {
207               case TR_PRI_LOW:   ch = 'l'; break; /* low */
208               case TR_PRI_HIGH:  ch = 'h'; break; /* high */
209               default:           ch = 'n'; break; /* normal */
210            };
211            *walk++ = ch;
212        }
213
214        /* dnd flags */
215        for( i=0; i<n; ++i )
216            *walk++ = tor->info.files[i].dnd ? 't' : 'f';
217
218        /* write it */
219        assert( walk - buf == 2*n );
220        fastResumeWriteData( FR_ID_PRIORITY, buf, 1, walk-buf, file );
221
222        /* cleanup */
223        tr_free( buf );
224    }
225
226
227    /* Write the torrent ul/dl speed caps */
228    if( TRUE )
229    {
230        const int len = ( sizeof(uint32_t) + sizeof(char) ) * 2;
231        char * buf = tr_new0( char, len );
232        char * walk = buf;
233        char enabled;
234        uint32_t i;
235
236        i = (uint32_t) tr_torrentGetMaxSpeedDL( tor );
237        memcpy( walk, &i, 4 ); walk += 4;
238        enabled = tr_torrentIsMaxSpeedEnabledDL( tor ) ? 't' : 'f';
239        *walk++ = enabled;
240
241        i = (uint32_t) tr_torrentGetMaxSpeedUL( tor );
242        memcpy( walk, &i, 4 ); walk += 4;
243        enabled = tr_torrentIsMaxSpeedEnabledUL( tor ) ? 't' : 'f';
244        *walk++ = enabled;
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 = 2 * (sizeof(uint32_t) + sizeof(char));
278    char * buf = tr_new0( char, len );
279    char * walk = buf;
280    uint32_t rate;
281    char enabled;
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( &rate, walk, 4 ); walk += 4;
290    memcpy( &enabled, walk, 1 ); walk += 1;
291    tr_torrentSetMaxSpeedDL( tor, rate );
292    tr_torrentEnableMaxSpeedDL( tor, enabled=='t' );
293
294    memcpy( &rate, walk, 4 ); walk += 4;
295    memcpy( &enabled, walk, 1 ); walk += 1;
296    tr_torrentSetMaxSpeedUL( tor, rate );
297    tr_torrentEnableMaxSpeedUL( tor, enabled=='t' );
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    }
395
396    free( buf );
397    return TR_OK;
398}
399
400static int
401fastResumeLoadOld( tr_torrent_t   * tor,
402                   tr_bitfield_t  * uncheckedPieces, 
403                   FILE           * file )
404{
405    /* Check the size */
406    const int size = 4 + FR_PROGRESS_LEN( tor );
407    fseek( file, 0, SEEK_END );
408    if( ftell( file ) != size )
409    {
410        tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
411                (int)ftell( file ), size );
412        fclose( file );
413        return 1;
414    }
415
416    /* load progress information */
417    fseek( file, 4, SEEK_SET );
418    if( fastResumeLoadProgress( tor, uncheckedPieces, file ) )
419    {
420        fclose( file );
421        return 1;
422    }
423
424    fclose( file );
425
426    tr_inf( "Fast resuming successful (version 0)" );
427   
428    return 0;
429}
430
431int
432fastResumeLoad( tr_torrent_t   * tor,
433                tr_bitfield_t  * uncheckedPieces )
434{
435    char      path[MAX_PATH_LENGTH];
436    FILE    * file;
437    int       version = 0;
438    uint8_t   id;
439    uint32_t  len;
440    int       ret;
441
442    assert( tor != NULL );
443    assert( uncheckedPieces != NULL );
444
445    /* Open resume file */
446    fastResumeFileName( path, sizeof path, tor, 1 );
447    file = fopen( path, "r" );
448    if( NULL == file )
449    {
450        if( ENOENT == errno )
451        {
452            fastResumeFileName( path, sizeof path, tor, 0 );
453            file = fopen( path, "r" );
454            if( NULL != file )
455            {
456                goto good;
457            }
458            fastResumeFileName( path, sizeof path, tor, 1 );
459        }
460        tr_inf( "Could not open '%s' for reading", path );
461        return 1;
462    }
463  good:
464    tr_dbg( "Resume file '%s' loaded", path );
465
466    /* Check format version */
467    fread( &version, 4, 1, file );
468    if( 0 == version )
469    {
470        return fastResumeLoadOld( tor, uncheckedPieces, file );
471    }
472    if( 1 != version )
473    {
474        tr_inf( "Resume file has version %d, not supported", version );
475        fclose( file );
476        return 1;
477    }
478
479    ret = 1;
480    /* read each block of data */
481    while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
482    {
483        switch( id )
484        {
485            case FR_ID_PROGRESS:
486                /* read progress data */
487                if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
488                {
489                    ret = fastResumeLoadProgress( tor, uncheckedPieces, file );
490
491                    if( ret && ( feof(file) || ferror(file) ) )
492                    {
493                        fclose( file );
494                        return 1;
495                    }
496
497                    continue;
498                }
499                break;
500
501            case FR_ID_PRIORITY:
502
503                /* read priority data */
504                if( len == (uint32_t)(2 * tor->info.fileCount) )
505                {
506                    ret = loadPriorities( tor, file );
507
508                    if( ret && ( feof(file) || ferror(file) ) )
509                    {
510                        fclose( file );
511                        return 1;
512                    }
513
514                    continue;
515                }
516                break;
517
518            case FR_ID_SPEED:
519                /*  read speed data */
520                if( len == (uint32_t)(2*sizeof(uint32_t)+2) )
521                {
522                    ret = loadSpeeds( tor, file );
523
524                    if( ret && ( feof(file) || ferror(file) ) )
525                    {
526                        fclose( file );
527                        return 1;
528                    }
529
530                    continue;
531                }
532                break;
533
534            case FR_ID_DOWNLOADED:
535                /* read download total */
536                if( 8 == len)
537                {
538                    if( 1 != fread( &tor->downloadedPrev, 8, 1, file ) )
539                    {
540                        fclose( file );
541                        return 1;
542                    }
543                    tor->downloadedCur = 0;
544                    continue;
545                }
546                break;
547
548            case FR_ID_UPLOADED:
549                /* read upload total */
550                if( 8 == len)
551                {
552                    if( 1 != fread( &tor->uploadedPrev, 8, 1, file ) )
553                    {
554                        fclose( file );
555                        return 1;
556                    }
557                    continue;
558                }
559                break;
560
561            case FR_ID_PEERS:
562                if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
563                {
564                    int used;
565                    uint8_t * buf = malloc( len );
566                    if( 1 != fread( buf, len, 1, file ) )
567                    {
568                        free( buf );
569                        fclose( file );
570                        return 1;
571                    }
572                    used = tr_torrentAddCompact( tor, TR_PEER_FROM_CACHE,
573                                                 buf, len / 6 );
574                    tr_dbg( "found %i peers in resume file, used %i",
575                            len / 6, used );
576                    free( buf );
577                }
578                continue;
579
580            default:
581                break;
582        }
583
584        /* if we didn't read the data, seek past it */
585        tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
586        fseek( file, len, SEEK_CUR );
587    }
588
589    fclose( file );
590
591    if( !ret )
592    {
593        tr_inf( "Fast resuming successful" );
594    }
595   
596    return ret;
597}
Note: See TracBrowser for help on using the repository browser.