source: trunk/libtransmission/fastresume.c @ 2884

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

better saving of run/stopped state.

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