source: trunk/libtransmission/fastresume.h @ 931

Last change on this file since 931 was 931, checked in by joshe, 16 years ago

Fix possible uint64_t underflow which could cause insanely huge (16,000,000 TB)

upload and/or download totals to be reported to the tracker.

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