source: trunk/libtransmission/fastresume.c @ 2154

Last change on this file since 2154 was 2154, checked in by charles, 15 years ago
  • fix error checking large files reported by Gimp_
  • portability changes to pathname/filename building
  • small gratuitous changes
File size: 12.5 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
64/* macros for the length of various pieces of the progress data */
65#define FR_MTIME_LEN( t ) \
66  ( sizeof(tr_time_t) * (t)->info.fileCount )
67#define FR_BLOCK_BITFIELD_LEN( t ) \
68  ( ( (t)->blockCount + 7 ) / 8 )
69#define FR_PROGRESS_LEN( t ) \
70  ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) )
71
72static void
73fastResumeFileName( char * buf, size_t buflen, const tr_torrent_t * tor, int tag )
74{
75    const char * cacheDir = tr_getCacheDirectory ();
76    const char * hash = tor->info.hashString;
77
78    if( !tag )
79    {
80        tr_buildPath( buf, buflen, cacheDir, hash, NULL );
81    }
82    else
83    {
84        char base[1024];
85        snprintf( base, sizeof(base), "%s-%s", hash, tor->handle->tag );
86        tr_buildPath( buf, buflen, cacheDir, base, NULL );
87    }
88}
89
90static tr_time_t*
91getMTimes( const tr_torrent_t * tor, int * setme_n )
92{
93    int i;
94    const int n = tor->info.fileCount;
95    tr_time_t * m = calloc( n, sizeof(tr_time_t) );
96
97    for( i=0; i<n; ++i ) {
98        char fname[MAX_PATH_LENGTH];
99        struct stat sb;
100        tr_buildPath( fname, sizeof(fname),
101                      tor->destination, tor->info.files[i].name, NULL );
102        if ( !stat( fname, &sb ) && S_ISREG( sb.st_mode ) ) {
103#ifdef SYS_DARWIN
104            m[i] = sb.st_mtimespec.tv_sec;
105#else
106            m[i] = sb.st_mtime;
107#endif
108        }
109    }
110
111    *setme_n = n;
112    return m;
113}
114
115static inline void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
116                                        uint32_t count, FILE * file )
117{
118    uint32_t  datalen = size * count;
119
120    fwrite( &id, 1, 1, file );
121    fwrite( &datalen, 4, 1, file );
122    fwrite( data, size, count, file );
123}
124
125void fastResumeSave( const tr_torrent_t * tor )
126{
127    char      path[MAX_PATH_LENGTH];
128    FILE    * file;
129    const int version = 1;
130    uint64_t  total;
131
132    fastResumeFileName( path, sizeof path, tor, 1 );
133    file = fopen( path, "w" );
134    if( NULL == file ) {
135        tr_err( "Couldn't open '%s' for writing", path );
136        return;
137    }
138   
139    /* Write format version */
140    fwrite( &version, 4, 1, file );
141
142    /* Write progress data */
143    if (1) {
144        int n;
145        tr_time_t * mtimes;
146        uint8_t * buf = malloc( FR_PROGRESS_LEN( tor ) );
147        uint8_t * walk = buf;
148        const tr_bitfield_t * bitfield;
149
150        /* mtimes */
151        mtimes = getMTimes( tor, &n );
152        memcpy( walk, mtimes, n*sizeof(tr_time_t) );
153        walk += n * sizeof(tr_time_t);
154
155        /* completion bitfield */
156        bitfield = tr_cpBlockBitfield( tor->completion );
157        assert( (unsigned)FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
158        memcpy( walk, bitfield->bits, bitfield->len );
159        walk += bitfield->len;
160
161        /* write it */
162        assert( walk-buf == (int)FR_PROGRESS_LEN( tor ) );
163        fastResumeWriteData( FR_ID_PROGRESS, buf, 1, walk-buf, file );
164
165        /* cleanup */
166        free( mtimes );
167        free( buf );
168    }
169
170    /* Write download and upload totals */
171    total = tor->downloadedCur + tor->downloadedPrev;
172    fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
173    total = tor->uploadedCur + tor->uploadedPrev;
174    fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
175
176    if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
177    {
178        /* Write IPs and ports of connectable peers, if any */
179        int size;
180        uint8_t * buf = NULL;
181        if( ( size = tr_peerGetConnectable( tor, &buf ) ) > 0 )
182        {
183            fastResumeWriteData( FR_ID_PEERS, buf, size, 1, file );
184            free( buf );
185        }
186    }
187
188    fclose( file );
189
190    tr_dbg( "Resume file '%s' written", path );
191}
192
193static int
194fastResumeLoadProgress( const tr_torrent_t  * tor,
195                        tr_bitfield_t       * uncheckedPieces,
196                        FILE                * file )
197{
198    const size_t len = FR_PROGRESS_LEN( tor );
199    uint8_t * buf = calloc( len, 1 );
200    uint8_t * walk = buf;
201
202    if( len != fread( buf, 1, len, file ) ) {
203        tr_inf( "Couldn't read from resume file" );
204        free( buf );
205        return TR_ERROR_IO_OTHER;
206    }
207
208    /* compare file mtimes */
209    if (1) {
210        int i, n;
211        tr_time_t * curMTimes = getMTimes( tor, &n );
212        const tr_time_t * oldMTimes = (const tr_time_t *) walk;
213        for( i=0; i<n; ++i ) {
214            if ( !curMTimes[i] || ( curMTimes[i]!=oldMTimes[i] ) ) {
215                const tr_file_t * file = &tor->info.files[i];
216                tr_inf( "File '%s' mtimes differ-- flaggin pieces [%d..%d]",
217                        file->name, file->firstPiece, file->lastPiece);
218                tr_bitfieldAddRange( uncheckedPieces, 
219                                     file->firstPiece, file->lastPiece );
220            }
221        }
222        free( curMTimes );
223        walk += n * sizeof(tr_time_t);
224    }
225
226    /* get the completion bitfield */
227    if (1) {
228        tr_bitfield_t bitfield;
229        memset( &bitfield, 0, sizeof bitfield );
230        bitfield.len = FR_BLOCK_BITFIELD_LEN( tor );
231        bitfield.bits = walk;
232        tr_cpBlockBitfieldSet( tor->completion, &bitfield );
233    }
234
235    free( buf );
236    return TR_OK;
237}
238
239static int
240fastResumeLoadOld( tr_torrent_t   * tor,
241                   tr_bitfield_t  * uncheckedPieces, 
242                   FILE           * file )
243{
244    /* Check the size */
245    const int size = 4 + FR_PROGRESS_LEN( tor );
246    fseek( file, 0, SEEK_END );
247    if( ftell( file ) != size )
248    {
249        tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
250                (int)ftell( file ), size );
251        fclose( file );
252        return 1;
253    }
254
255    /* load progress information */
256    fseek( file, 4, SEEK_SET );
257    if( fastResumeLoadProgress( tor, uncheckedPieces, file ) )
258    {
259        fclose( file );
260        return 1;
261    }
262
263    fclose( file );
264
265    tr_inf( "Fast resuming successful (version 0)" );
266   
267    return 0;
268}
269
270int
271fastResumeLoad( tr_torrent_t   * tor,
272                tr_bitfield_t  * uncheckedPieces )
273{
274    char      path[MAX_PATH_LENGTH];
275    FILE    * file;
276    int       version = 0;
277    uint8_t   id;
278    uint32_t  len;
279    int       ret;
280
281    assert( tor != NULL );
282    assert( uncheckedPieces != NULL );
283
284    /* Open resume file */
285    fastResumeFileName( path, sizeof path, tor, 1 );
286    file = fopen( path, "r" );
287    if( NULL == file )
288    {
289        if( ENOENT == errno )
290        {
291            fastResumeFileName( path, sizeof path, tor, 0 );
292            file = fopen( path, "r" );
293            if( NULL != file )
294            {
295                goto good;
296            }
297            fastResumeFileName( path, sizeof path, tor, 1 );
298        }
299        tr_inf( "Could not open '%s' for reading", path );
300        return 1;
301    }
302  good:
303    tr_dbg( "Resume file '%s' loaded", path );
304
305    /* Check format version */
306    fread( &version, 4, 1, file );
307    if( 0 == version )
308    {
309        return fastResumeLoadOld( tor, uncheckedPieces, file );
310    }
311    if( 1 != version )
312    {
313        tr_inf( "Resume file has version %d, not supported", version );
314        fclose( file );
315        return 1;
316    }
317
318    ret = 1;
319    /* read each block of data */
320    while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
321    {
322        switch( id )
323        {
324            case FR_ID_PROGRESS:
325                /* read progress data */
326                if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
327                {
328                    if( fastResumeLoadProgress( tor, uncheckedPieces, file ) )
329                    {
330                        if( feof( file ) || ferror( file ) )
331                        {
332                            fclose( file );
333                            return 1;
334                        }
335                    }
336                    else
337                    {
338                        ret = 0;
339                    }
340                    continue;
341                }
342                break;
343
344            case FR_ID_DOWNLOADED:
345                /* read download total */
346                if( 8 == len)
347                {
348                    if( 1 != fread( &tor->downloadedPrev, 8, 1, file ) )
349                    {
350                        fclose( file );
351                        return 1;
352                    }
353                    continue;
354                }
355                break;
356
357            case FR_ID_UPLOADED:
358                /* read upload total */
359                if( 8 == len)
360                {
361                    if( 1 != fread( &tor->uploadedPrev, 8, 1, file ) )
362                    {
363                        fclose( file );
364                        return 1;
365                    }
366                    continue;
367                }
368                break;
369
370            case FR_ID_PEERS:
371                if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
372                {
373                    int used;
374                    uint8_t * buf = malloc( len );
375                    if( 1 != fread( buf, len, 1, file ) )
376                    {
377                        free( buf );
378                        fclose( file );
379                        return 1;
380                    }
381                    used = tr_torrentAddCompact( tor, TR_PEER_FROM_CACHE,
382                                                 buf, len / 6 );
383                    tr_dbg( "found %i peers in resume file, used %i",
384                            len / 6, used );
385                    free( buf );
386                }
387                continue;
388
389            default:
390                break;
391        }
392
393        /* if we didn't read the data, seek past it */
394        tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
395        fseek( file, len, SEEK_CUR );
396    }
397
398    fclose( file );
399
400    if( !ret )
401    {
402        tr_inf( "Fast resuming successful" );
403    }
404   
405    return ret;
406}
407
408void
409fastResumeRemove( tr_torrent_t * tor )
410{
411    char file[MAX_PATH_LENGTH];
412    fastResumeFileName( file, sizeof file, tor, NULL != tor->handle->tag );
413   
414    if ( unlink( file ) )
415    {
416        tr_inf( "Removing fast resume file failed" );
417    }
418}
Note: See TracBrowser for help on using the repository browser.