source: trunk/libtransmission/fastresume.c @ 6896

Last change on this file since 6896 was 6896, checked in by charles, 12 years ago

have tr_buildPath() allocate memory from the heap rather than using an input buffer

  • Property svn:keywords set to Date Rev Author Id
File size: 16.0 KB
Line 
1/******************************************************************************
2 * $Id: fastresume.c 6896 2008-10-14 03:03:29Z charles $
3 *
4 * Copyright (c) 2005-2008 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> /* calloc */
48#include <string.h> /* strcpy, memset, memcmp */
49
50#include <sys/types.h>
51#include <sys/stat.h> /* stat */
52#include <unistd.h> /* unlink */
53
54#include "transmission.h"
55#include "completion.h"
56#include "fastresume.h"
57#include "peer-mgr.h"
58#include "platform.h"
59#include "resume.h" /* TR_FR_ bitwise enum */
60#include "torrent.h"
61#include "utils.h"
62
63/* time_t can be 32 or 64 bits... for consistency we'll hardwire 64 */
64typedef uint64_t tr_time_t;
65
66enum
67{
68    /* number of bytes downloaded */
69    FR_ID_DOWNLOADED = 2,
70
71    /* number of bytes uploaded */
72    FR_ID_UPLOADED = 3,
73
74    /* progress data:
75     *  - 4 bytes * number of files: mtimes of files
76     *  - 1 bit * number of blocks: whether we have the block or not */
77    FR_ID_PROGRESS = 5,
78
79    /* dnd and priority
80     * char * number of files: l,n,h for low, normal, high priority
81     * char * number of files: t,f for DND flags */
82    FR_ID_PRIORITY = 6,
83
84    /* transfer speeds
85     * uint32_t: dl speed rate to use when the mode is single
86     * uint32_t: dl's tr_speedlimit
87     * uint32_t: ul speed rate to use when the mode is single
88     * uint32_t: ul's tr_speedlimit
89     */
90    FR_ID_SPEED = 8,
91
92    /* active
93     * char: 't' if running, 'f' if paused
94     */
95    FR_ID_RUN = 9,
96
97    /* number of corrupt bytes downloaded */
98    FR_ID_CORRUPT = 10,
99
100    /* IPs and ports of connectable peers */
101    FR_ID_PEERS = 11,
102
103    /* download dir of the torrent: zero-terminated string */
104    FR_ID_DOWNLOAD_DIR = 12,
105
106    /* pex flag
107     * 't' if pex is enabled, 'f' if disabled */
108    FR_ID_PEX = 13,
109
110    /* max connected peers -- uint16_t */
111    FR_ID_MAX_PEERS = 14
112};
113
114
115/* macros for the length of various pieces of the progress data */
116#define FR_MTIME_LEN( t ) \
117    ( sizeof( tr_time_t ) * ( t )->info.fileCount )
118#define FR_BLOCK_BITFIELD_LEN( t ) \
119    ( ( ( t )->blockCount + 7u ) / 8u )
120#define FR_PROGRESS_LEN( t ) \
121    ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) )
122#define FR_SPEED_LEN ( 2 * ( sizeof( uint16_t ) + sizeof( uint8_t ) ) )
123
124static tr_time_t*
125getMTimes( const tr_torrent * tor,
126           int *              setme_n )
127{
128    int         i;
129    const int   n = tor->info.fileCount;
130    tr_time_t * m = calloc( n, sizeof( tr_time_t ) );
131
132    for( i = 0; i < n; ++i )
133    {
134        struct stat sb;
135        char * fname = tr_buildPath( tor->downloadDir, tor->info.files[i].name, NULL );
136        if( !stat( fname, &sb ) && S_ISREG( sb.st_mode ) )
137        {
138#ifdef SYS_DARWIN
139            m[i] = sb.st_mtimespec.tv_sec;
140#else
141            m[i] = sb.st_mtime;
142#endif
143        }
144        tr_free( fname );
145    }
146
147    *setme_n = n;
148    return m;
149}
150
151/***
152****
153***/
154
155static uint64_t
156internalIdToPublicBitfield( uint8_t id )
157{
158    uint64_t ret = 0;
159
160    switch( id )
161    {
162        case FR_ID_DOWNLOADED:
163            ret = TR_FR_DOWNLOADED;    break;
164
165        case FR_ID_UPLOADED:
166            ret = TR_FR_UPLOADED;      break;
167
168        case FR_ID_PROGRESS:
169            ret = TR_FR_PROGRESS;      break;
170
171        case FR_ID_PRIORITY:
172            ret = TR_FR_PRIORITY;      break;
173
174        case FR_ID_SPEED:
175            ret = TR_FR_SPEEDLIMIT;    break;
176
177        case FR_ID_RUN:
178            ret = TR_FR_RUN;           break;
179
180        case FR_ID_CORRUPT:
181            ret = TR_FR_CORRUPT;       break;
182
183        case FR_ID_PEERS:
184            ret = TR_FR_PEERS;         break;
185
186        case FR_ID_DOWNLOAD_DIR:
187            ret = TR_FR_DOWNLOAD_DIR;  break;
188
189        case FR_ID_MAX_PEERS:
190            ret = TR_FR_MAX_PEERS;     break;
191    }
192
193    return ret;
194}
195
196static void
197readBytes( void *           target,
198           const uint8_t ** source,
199           size_t           byteCount )
200{
201    memcpy( target, *source, byteCount );
202    *source += byteCount;
203}
204
205static uint64_t
206parseDownloaded( tr_torrent *    tor,
207                 const uint8_t * buf,
208                 uint32_t        len )
209{
210    if( len != sizeof( uint64_t ) )
211        return 0;
212    readBytes( &tor->downloadedPrev, &buf, sizeof( uint64_t ) );
213    return TR_FR_DOWNLOADED;
214}
215
216static uint64_t
217parseUploaded( tr_torrent *    tor,
218               const uint8_t * buf,
219               uint32_t        len )
220{
221    if( len != sizeof( uint64_t ) )
222        return 0;
223    readBytes( &tor->uploadedPrev, &buf, sizeof( uint64_t ) );
224    return TR_FR_UPLOADED;
225}
226
227static uint64_t
228parseCorrupt( tr_torrent *    tor,
229              const uint8_t * buf,
230              uint32_t        len )
231{
232    if( len != sizeof( uint64_t ) )
233        return 0;
234    readBytes( &tor->corruptPrev, &buf, sizeof( uint64_t ) );
235    return TR_FR_CORRUPT;
236}
237
238static uint64_t
239parseConnections( tr_torrent *    tor,
240                  const uint8_t * buf,
241                  uint32_t        len )
242{
243    if( len != sizeof( uint16_t ) )
244        return 0;
245    readBytes( &tor->maxConnectedPeers, &buf, sizeof( uint16_t ) );
246    return TR_FR_MAX_PEERS;
247}
248
249static uint64_t
250parseProgress( tr_torrent *    tor,
251               const uint8_t * buf,
252               uint32_t        len )
253{
254    uint64_t ret = 0;
255
256    if( len == FR_PROGRESS_LEN( tor ) )
257    {
258        int             i;
259        int             n;
260        tr_bitfield     bitfield;
261
262        /* compare file mtimes */
263        tr_time_t *     curMTimes = getMTimes( tor, &n );
264        const uint8_t * walk = buf;
265        tr_time_t       mtime;
266        for( i = 0; i < n; ++i )
267        {
268            readBytes( &mtime, &walk, sizeof( tr_time_t ) );
269            if( curMTimes[i] == mtime )
270                tr_torrentSetFileChecked( tor, i, TRUE );
271            else
272            {
273                tr_torrentSetFileChecked( tor, i, FALSE );
274                tr_tordbg( tor, "Torrent needs to be verified" );
275            }
276        }
277        free( curMTimes );
278
279        /* get the completion bitfield */
280        memset( &bitfield, 0, sizeof bitfield );
281        bitfield.byteCount = FR_BLOCK_BITFIELD_LEN( tor );
282        bitfield.bitCount = bitfield.byteCount * 8;
283        bitfield.bits = (uint8_t*) walk;
284        if( tr_cpBlockBitfieldSet( tor->completion, &bitfield ) )
285            ret = TR_FR_PROGRESS;
286        else {
287            tr_torrentUncheck( tor );
288            tr_tordbg( tor, "Torrent needs to be verified" );
289        }
290    }
291
292    /* the files whose mtimes are wrong,
293       remove from completion pending a recheck... */
294    {
295        tr_piece_index_t i;
296        for( i = 0; i < tor->info.pieceCount; ++i )
297            if( !tr_torrentIsPieceChecked( tor, i ) )
298                tr_cpPieceRem( tor->completion, i );
299    }
300
301    return ret;
302}
303
304static uint64_t
305parsePriorities( tr_torrent *    tor,
306                 const uint8_t * buf,
307                 uint32_t        len )
308{
309    uint64_t ret = 0;
310
311    if( len == (uint32_t)( 2 * tor->info.fileCount ) )
312    {
313        const size_t     n = tor->info.fileCount;
314        const size_t     len = 2 * n;
315        tr_file_index_t *dnd = NULL, dndCount = 0;
316        tr_file_index_t *dl = NULL, dlCount = 0;
317        size_t           i;
318        const uint8_t *  walk = buf;
319
320        /* set file priorities */
321        for( i = 0; i < n; ++i )
322        {
323            tr_priority_t priority;
324            const char    ch = *walk++;
325            switch( ch )
326            {
327                case 'l':
328                    priority = TR_PRI_LOW; break;
329
330                case 'h':
331                    priority = TR_PRI_HIGH; break;
332
333                default:
334                    priority = TR_PRI_NORMAL; break;
335            }
336            tr_torrentInitFilePriority( tor, i, priority );
337        }
338
339        /* set the dnd flags */
340        dl = tr_new( tr_file_index_t, len );
341        dnd = tr_new( tr_file_index_t, len );
342        for( i = 0; i < n; ++i )
343            if( *walk++ == 't' ) /* 't' means the DND flag is true */
344                dnd[dndCount++] = i;
345            else
346                dl[dlCount++] = i;
347
348        if( dndCount )
349            tr_torrentInitFileDLs ( tor, dnd, dndCount, FALSE );
350        if( dlCount )
351            tr_torrentInitFileDLs ( tor, dl, dlCount, TRUE );
352
353        tr_free( dnd );
354        tr_free( dl );
355
356        ret = TR_FR_PRIORITY;
357    }
358
359    return ret;
360}
361
362static uint64_t
363parseSpeedLimit( tr_torrent *    tor,
364                 const uint8_t * buf,
365                 uint32_t        len )
366{
367    uint64_t ret = 0;
368
369    if( len == FR_SPEED_LEN )
370    {
371        uint8_t  i8;
372        uint16_t i16;
373
374        readBytes( &i16, &buf, sizeof( i16 ) );
375        tr_torrentSetSpeedLimit( tor, TR_DOWN, i16 );
376        readBytes( &i8, &buf, sizeof( i8 ) );
377        tr_torrentSetSpeedMode( tor, TR_DOWN, (tr_speedlimit)i8 );
378        readBytes( &i16, &buf, sizeof( i16 ) );
379        tr_torrentSetSpeedLimit( tor, TR_UP, i16 );
380        readBytes( &i8, &buf, sizeof( i8 ) );
381        tr_torrentSetSpeedMode( tor, TR_UP, (tr_speedlimit)i8 );
382
383        ret = TR_FR_SPEEDLIMIT;
384    }
385
386    return ret;
387}
388
389static uint64_t
390parseRun( tr_torrent *    tor,
391          const uint8_t * buf,
392          uint32_t        len )
393{
394    if( len != 1 )
395        return 0;
396    tor->isRunning = *buf == 't';
397    return TR_FR_RUN;
398}
399
400static uint64_t
401parsePeers( tr_torrent *    tor,
402            const uint8_t * buf,
403            uint32_t        len )
404{
405    uint64_t ret = 0;
406
407    if( !tor->info.isPrivate )
408    {
409        int       i;
410        const int count = len / sizeof( tr_pex );
411
412        for( i = 0; i < count; ++i )
413        {
414            tr_pex pex;
415            readBytes( &pex, &buf, sizeof( tr_pex ) );
416            tr_peerMgrAddPex( tor->session->peerMgr, tor->info.hash,
417                              TR_PEER_FROM_CACHE,
418                              &pex );
419        }
420
421        tr_tordbg( tor, "Loaded %d peers from resume file", count );
422        ret = TR_FR_PEERS;
423    }
424
425    return ret;
426}
427
428static uint64_t
429parseDownloadDir( tr_torrent *    tor,
430                  const uint8_t * buf,
431                  uint32_t        len )
432{
433    uint64_t ret = 0;
434
435    if( buf && *buf && len )
436    {
437        tr_free( tor->downloadDir );
438        tor->downloadDir = tr_strndup( (char*)buf, len );
439        ret = TR_FR_DOWNLOAD_DIR;
440    }
441
442    return ret;
443}
444
445static uint64_t
446parseVersion1( tr_torrent *    tor,
447               const uint8_t * buf,
448               const uint8_t * end,
449               uint64_t        fieldsToLoad )
450{
451    uint64_t ret = 0;
452
453    while( end - buf >= 5 )
454    {
455        uint8_t  id;
456        uint32_t len;
457        readBytes( &id, &buf, sizeof( id ) );
458        readBytes( &len, &buf, sizeof( len ) );
459
460        if( buf + len > end )
461        {
462            tr_torerr( tor, "Resume file seems to be corrupt.  Skipping." );
463        }
464        else if( fieldsToLoad &
465                internalIdToPublicBitfield( id ) ) switch( id )
466            {
467                case FR_ID_DOWNLOADED:
468                    ret |= parseDownloaded( tor, buf, len ); break;
469
470                case FR_ID_UPLOADED:
471                    ret |= parseUploaded( tor, buf, len ); break;
472
473                case FR_ID_PROGRESS:
474                    ret |= parseProgress( tor, buf, len ); break;
475
476                case FR_ID_PRIORITY:
477                    ret |= parsePriorities( tor, buf, len ); break;
478
479                case FR_ID_SPEED:
480                    ret |= parseSpeedLimit( tor, buf, len ); break;
481
482                case FR_ID_RUN:
483                    ret |= parseRun( tor, buf, len ); break;
484
485                case FR_ID_CORRUPT:
486                    ret |= parseCorrupt( tor, buf, len ); break;
487
488                case FR_ID_PEERS:
489                    ret |= parsePeers( tor, buf, len ); break;
490
491                case FR_ID_MAX_PEERS:
492                    ret |= parseConnections( tor, buf, len ); break;
493
494                case FR_ID_DOWNLOAD_DIR:
495                    ret |= parseDownloadDir( tor, buf, len ); break;
496
497                default:
498                    tr_tordbg( tor, "Skipping unknown resume code %d",
499                               (int)id ); break;
500            }
501
502        buf += len;
503    }
504
505    return ret;
506}
507
508static uint8_t*
509loadResumeFile( const tr_torrent * tor,
510                size_t *           len )
511{
512    uint8_t *    ret = NULL;
513    const char * cacheDir = tr_getResumeDir( tor->session );
514    const char * hash = tor->info.hashString;
515
516    if( !ret && tor->session->tag )
517    {
518        char * path = tr_strdup_printf( "%s" TR_PATH_DELIMITER_STR "%s-%s", cacheDir, hash, tor->session->tag );
519        ret = tr_loadFile( path, len );
520        tr_free( path );
521    }
522    if( !ret )
523    {
524        char * path = tr_buildPath( cacheDir, hash, NULL );
525        ret = tr_loadFile( path, len );
526        tr_free( path );
527    }
528
529    return ret;
530}
531
532static uint64_t
533fastResumeLoadImpl( tr_torrent * tor,
534                    uint64_t     fieldsToLoad )
535{
536    uint64_t  ret = 0;
537    size_t    size = 0;
538    uint8_t * buf = loadResumeFile( tor, &size );
539
540    if( !buf )
541        /* %s is the torrent name */
542        tr_torinf( tor, _( "Couldn't read resume file" ) );
543    else
544    {
545        const uint8_t * walk = buf;
546        const uint8_t * end = walk + size;
547        if( end - walk >= 4 )
548        {
549            uint32_t version;
550            readBytes( &version, &walk, sizeof( version ) );
551            if( version == 1 )
552                ret |= parseVersion1 ( tor, walk, end, fieldsToLoad );
553            else
554                /* %s is the torrent name */
555                tr_torinf( tor, _( "Couldn't read resume file" ) );
556        }
557
558        tr_free( buf );
559    }
560
561    return ret;
562}
563
564uint64_t
565tr_fastResumeLoad( tr_torrent * tor,
566                   uint64_t     fieldsToLoad )
567{
568    return fastResumeLoadImpl( tor, fieldsToLoad );
569}
570
571void
572tr_fastResumeRemove( const tr_torrent * tor )
573{
574    const char * cacheDir = tr_getResumeDir( tor->session );
575    const char * hash = tor->info.hashString;
576
577    if( tor->session->tag )
578    {
579        char * path = tr_strdup_printf( "%s" TR_PATH_DELIMITER_STR "%s-%s", cacheDir, hash, tor->session->tag );
580        unlink( path );
581        tr_free( path );
582    }
583    else
584    {
585        char * path = tr_buildPath( cacheDir, hash, NULL );
586        unlink( path );
587        tr_free( path );
588    }
589}
590
Note: See TracBrowser for help on using the repository browser.