source: trunk/libtransmission/fastresume.c @ 7567

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

(trunk libT) Fix sparse warnings: symbol 'XXX' shadows an earlier one

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