source: branches/daemon/libtransmission/fastresume.h @ 1712

Last change on this file since 1712 was 1712, checked in by joshe, 15 years ago

Merge libT revs 1616:1711 from trunk to daemon branch.

  • Property svn:keywords set to Date Rev Author Id
File size: 12.5 KB
Line 
1/******************************************************************************
2 * $Id: fastresume.h 1712 2007-04-14 06:34:52Z joshe $
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/* progress data:
45 *  - 4 bytes * number of files: mtimes of files
46 *  - 1 bit * number of blocks: whether we have the block or not
47 *  - 4 bytes * number of pieces (byte aligned): the pieces that have
48 *    been completed or started in each slot
49 */
50#define FR_ID_PROGRESS          0x01
51/* number of bytes downloaded */
52#define FR_ID_DOWNLOADED        0x02
53/* number of bytes uploaded */
54#define FR_ID_UPLOADED          0x03
55/* IPs and ports of connectable peers */
56#define FR_ID_PEERS             0x04
57
58/* macros for the length of various pieces of the progress data */
59#define FR_MTIME_LEN( t ) \
60  ( 4 * (t)->info.fileCount )
61#define FR_BLOCK_BITFIELD_LEN( t ) \
62  ( ( (t)->blockCount + 7 ) / 8 )
63#define FR_SLOTPIECE_LEN( t ) \
64  ( 4 * (t)->info.pieceCount )
65#define FR_PROGRESS_LEN( t ) \
66  ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) + FR_SLOTPIECE_LEN( t ) )
67
68static void
69fastResumeFileName( char * path, size_t size, tr_torrent_t * tor, int tag )
70{
71    if( tag )
72    {
73        snprintf( path, size, "%s/resume.%s-%s", tr_getCacheDirectory(),
74                  tor->info.hashString, tor->handle->tag );
75    }
76    else
77    {
78        snprintf( path, size, "%s/resume.%s", tr_getCacheDirectory(),
79                  tor->info.hashString );
80    }
81}
82
83static int fastResumeMTimes( tr_io_t * io, int * tab )
84{
85    tr_torrent_t * tor = io->tor;
86    tr_info_t    * inf = &tor->info;
87
88    int           i;
89    char        * path;
90    struct stat   sb;
91
92    for( i = 0; i < inf->fileCount; i++ )
93    {
94        asprintf( &path, "%s/%s", tor->destination, inf->files[i].name );
95        if( stat( path, &sb ) )
96        {
97            tab[i] = 0xFFFFFFFF;
98        }
99        else if( S_ISREG( sb.st_mode ) )
100        {
101#ifdef SYS_DARWIN
102            tab[i] = ( sb.st_mtimespec.tv_sec & 0x7FFFFFFF );
103#else
104            tab[i] = ( sb.st_mtime & 0x7FFFFFFF );
105#endif
106        }
107        else
108        {
109            /* Empty folder */
110            tab[i] = 0;
111        }
112        free( path );
113    }
114
115    return 0;
116}
117
118static inline void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
119                                        uint32_t count, FILE * file )
120{
121    uint32_t  datalen = size * count;
122
123    fwrite( &id, 1, 1, file );
124    fwrite( &datalen, 4, 1, file );
125    fwrite( data, size, count, file );
126}
127
128static void fastResumeSave( tr_io_t * io )
129{
130    tr_torrent_t * tor = io->tor;
131
132    char      path[MAX_PATH_LENGTH];
133    FILE    * file;
134    int       version = 1;
135    uint8_t * buf;
136    uint64_t  total;
137    int       size;
138    tr_bitfield_t * bitfield;
139
140    buf = malloc( FR_PROGRESS_LEN( tor ) );
141
142    /* Get file sizes */
143    if( fastResumeMTimes( io, (int*)buf ) )
144    {
145        free( buf );
146        return;
147    }
148
149    /* Create/overwrite the resume file */
150    fastResumeFileName( path, sizeof path, tor, 1 );
151    file = fopen( path, "w" );
152    if( NULL == file )
153    {
154        tr_err( "Could not open '%s' for writing", path );
155        free( buf );
156        return;
157    }
158   
159    /* Write format version */
160    fwrite( &version, 4, 1, file );
161
162    /* Build and copy the bitfield for blocks */
163    bitfield = tr_cpBlockBitfield( tor->completion );
164    assert( FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
165    memcpy(buf + FR_MTIME_LEN( tor ), bitfield->bits, bitfield->len );
166
167    /* Copy the 'slotPiece' table */
168    memcpy(buf + FR_MTIME_LEN( tor ) + FR_BLOCK_BITFIELD_LEN( tor ),
169           io->slotPiece, FR_SLOTPIECE_LEN( tor ) );
170
171    /* Write progress data */
172    fastResumeWriteData( FR_ID_PROGRESS, buf, 1, FR_PROGRESS_LEN( tor ), file );
173    free( buf );
174
175    /* Write download and upload totals */
176    total = tor->downloadedCur + tor->downloadedPrev;
177    fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
178    total = tor->uploadedCur + tor->uploadedPrev;
179    fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
180
181    if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
182    {
183        /* Write IPs and ports of connectable peers, if any */
184        if( ( size = tr_peerGetConnectable( tor, &buf ) ) > 0 )
185        {
186            fastResumeWriteData( FR_ID_PEERS, buf, size, 1, file );
187            free( buf );
188        }
189    }
190
191    fclose( file );
192
193    tr_dbg( "Resume file '%s' written", path );
194}
195
196static int fastResumeLoadProgress( tr_io_t * io, FILE * file )
197{
198    tr_torrent_t * tor = io->tor;
199    tr_info_t    * inf = &tor->info;
200
201    int     * fileMTimes;
202    int       i, j;
203    uint8_t * buf;
204    size_t    len;
205    tr_bitfield_t bitfield;
206
207    len = FR_PROGRESS_LEN( tor );
208    buf = calloc( len, 1 );
209    if( len != fread( buf, 1, len, file ) )
210    {
211        tr_inf( "Could not read from resume file" );
212        free( buf );
213        return 1;
214    }
215
216    /* Compare file mtimes */
217    fileMTimes = malloc( FR_MTIME_LEN( tor ) );
218    if( fastResumeMTimes( io, fileMTimes ) )
219    {
220        free( buf );
221        free( fileMTimes );
222        return 1;
223    }
224    if( memcmp( fileMTimes, buf, FR_MTIME_LEN( tor ) ) )
225    {
226        tr_inf( "File mtimes don't match" );
227        free( buf );
228        free( fileMTimes );
229        return 1;
230    }
231    free( fileMTimes );
232
233    /* Copy the bitfield for blocks and fill blockHave */
234    memset( &bitfield, 0, sizeof bitfield );
235    bitfield.len = FR_BLOCK_BITFIELD_LEN( tor );
236    bitfield.bits = buf + FR_MTIME_LEN( tor );
237    tr_cpBlockBitfieldSet( tor->completion, &bitfield );
238
239    /* Copy the 'slotPiece' table */
240    memcpy( io->slotPiece, buf + FR_MTIME_LEN( tor ) +
241            FR_BLOCK_BITFIELD_LEN( tor ), FR_SLOTPIECE_LEN( tor ) );
242
243    free( buf );
244
245    /* Update io->pieceSlot, io->slotsUsed, and tor->bitfield */
246    io->slotsUsed = 0;
247    for( i = 0; i < inf->pieceCount; i++ )
248    {
249        io->pieceSlot[i] = -1;
250        for( j = 0; j < inf->pieceCount; j++ )
251        {
252            if( io->slotPiece[j] == i )
253            {
254                // tr_dbg( "Has piece %d in slot %d", i, j );
255                io->pieceSlot[i] = j;
256                io->slotsUsed = MAX( io->slotsUsed, j + 1 );
257                break;
258            }
259        }
260    }
261    // tr_dbg( "Slot used: %d", io->slotsUsed );
262
263    return 0;
264}
265
266static int fastResumeLoadOld( tr_io_t * io, FILE * file )
267{
268    tr_torrent_t * tor = io->tor;
269   
270    int size;
271
272    /* Check the size */
273    size = 4 + FR_PROGRESS_LEN( tor );
274    fseek( file, 0, SEEK_END );
275    if( ftell( file ) != size )
276    {
277        tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
278                (int)ftell( file ), size );
279        fclose( file );
280        return 1;
281    }
282
283    /* load progress information */
284    fseek( file, 4, SEEK_SET );
285    if( fastResumeLoadProgress( io, file ) )
286    {
287        fclose( file );
288        return 1;
289    }
290
291    fclose( file );
292
293    tr_inf( "Fast resuming successful (version 0)" );
294   
295    return 0;
296}
297
298static int fastResumeLoad( tr_io_t * io )
299{
300    tr_torrent_t * tor = io->tor;
301
302    char      path[MAX_PATH_LENGTH];
303    FILE    * file;
304    int       version = 0;
305    uint8_t   id;
306    uint32_t  len;
307    int       ret;
308
309    /* Open resume file */
310    fastResumeFileName( path, sizeof path, tor, 1 );
311    file = fopen( path, "r" );
312    if( NULL == file )
313    {
314        if( ENOENT == errno )
315        {
316            fastResumeFileName( path, sizeof path, tor, 0 );
317            file = fopen( path, "r" );
318            if( NULL != file )
319            {
320                goto good;
321            }
322            fastResumeFileName( path, sizeof path, tor, 1 );
323        }
324        tr_inf( "Could not open '%s' for reading", path );
325        return 1;
326    }
327  good:
328    tr_dbg( "Resume file '%s' loaded", path );
329
330    /* Check format version */
331    fread( &version, 4, 1, file );
332    if( 0 == version )
333    {
334        return fastResumeLoadOld( io, file );
335    }
336    if( 1 != version )
337    {
338        tr_inf( "Resume file has version %d, not supported", version );
339        fclose( file );
340        return 1;
341    }
342
343    ret = 1;
344    /* read each block of data */
345    while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
346    {
347        switch( id )
348        {
349            case FR_ID_PROGRESS:
350                /* read progress data */
351                if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
352                {
353                    if( fastResumeLoadProgress( io, file ) )
354                    {
355                        if( feof( file ) || ferror( file ) )
356                        {
357                            fclose( file );
358                            return 1;
359                        }
360                    }
361                    else
362                    {
363                        ret = 0;
364                    }
365                    continue;
366                }
367                break;
368
369            case FR_ID_DOWNLOADED:
370                /* read download total */
371                if( 8 == len)
372                {
373                    if( 1 != fread( &tor->downloadedPrev, 8, 1, file ) )
374                    {
375                        fclose( file );
376                        return 1;
377                    }
378                    continue;
379                }
380                break;
381
382            case FR_ID_UPLOADED:
383                /* read upload total */
384                if( 8 == len)
385                {
386                    if( 1 != fread( &tor->uploadedPrev, 8, 1, file ) )
387                    {
388                        fclose( file );
389                        return 1;
390                    }
391                    continue;
392                }
393                break;
394
395            case FR_ID_PEERS:
396                if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
397                {
398                    int used;
399                    uint8_t * buf = malloc( len );
400                    if( 1 != fread( buf, len, 1, file ) )
401                    {
402                        free( buf );
403                        fclose( file );
404                        return 1;
405                    }
406                    used = tr_torrentAddCompact( tor, TR_PEER_FROM_CACHE,
407                                                 buf, len / 6 );
408                    tr_dbg( "found %i peers in resume file, used %i",
409                            len / 6, used );
410                    free( buf );
411                }
412                continue;
413
414            default:
415                break;
416        }
417
418        /* if we didn't read the data, seek past it */
419        tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
420        fseek( file, len, SEEK_CUR );
421    }
422
423    fclose( file );
424
425    if( !ret )
426    {
427        tr_inf( "Fast resuming successful" );
428    }
429   
430    return ret;
431}
Note: See TracBrowser for help on using the repository browser.