source: trunk/libtransmission/fastresume.c @ 2149

Last change on this file since 2149 was 2149, checked in by livings124, 15 years ago

Merge file selection and torrent creation into the main branch.

The new code for these features is under a new license.

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