source: branches/1.4x/libtransmission/fastresume.c @ 7455

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

(1.4x libT) backport handshake, peer, bandwidth, peer-io to 1.4x.

  • Property svn:keywords set to Date Rev Author Id
File size: 16.0 KB
Line 
1/******************************************************************************
2 * $Id: fastresume.c 7455 2008-12-22 00:51:14Z 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 "net.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 size_t     len = 2 * n;
316        tr_file_index_t *dnd = NULL, dndCount = 0;
317        tr_file_index_t *dl = NULL, dlCount = 0;
318        size_t           i;
319        const uint8_t *  walk = buf;
320
321        /* set file priorities */
322        for( i = 0; i < n; ++i )
323        {
324            tr_priority_t priority;
325            const char    ch = *walk++;
326            switch( ch )
327            {
328                case 'l':
329                    priority = TR_PRI_LOW; break;
330
331                case 'h':
332                    priority = TR_PRI_HIGH; break;
333
334                default:
335                    priority = TR_PRI_NORMAL; break;
336            }
337            tr_torrentInitFilePriority( tor, i, priority );
338        }
339
340        /* set the dnd flags */
341        dl = tr_new( tr_file_index_t, len );
342        dnd = tr_new( tr_file_index_t, len );
343        for( i = 0; i < n; ++i )
344            if( *walk++ == 't' ) /* 't' means the DND flag is true */
345                dnd[dndCount++] = i;
346            else
347                dl[dlCount++] = i;
348
349        if( dndCount )
350            tr_torrentInitFileDLs ( tor, dnd, dndCount, FALSE );
351        if( dlCount )
352            tr_torrentInitFileDLs ( tor, dl, dlCount, TRUE );
353
354        tr_free( dnd );
355        tr_free( dl );
356
357        ret = TR_FR_PRIORITY;
358    }
359
360    return ret;
361}
362
363static uint64_t
364parseSpeedLimit( tr_torrent *    tor,
365                 const uint8_t * buf,
366                 uint32_t        len )
367{
368    uint64_t ret = 0;
369
370    if( len == FR_SPEED_LEN )
371    {
372        uint8_t  i8;
373        uint16_t i16;
374
375        readBytes( &i16, &buf, sizeof( i16 ) );
376        tr_torrentSetSpeedLimit( tor, TR_DOWN, i16 );
377        readBytes( &i8, &buf, sizeof( i8 ) );
378        tr_torrentSetSpeedMode( tor, TR_DOWN, (tr_speedlimit)i8 );
379        readBytes( &i16, &buf, sizeof( i16 ) );
380        tr_torrentSetSpeedLimit( tor, TR_UP, i16 );
381        readBytes( &i8, &buf, sizeof( i8 ) );
382        tr_torrentSetSpeedMode( tor, TR_UP, (tr_speedlimit)i8 );
383
384        ret = TR_FR_SPEEDLIMIT;
385    }
386
387    return ret;
388}
389
390static uint64_t
391parseRun( tr_torrent *    tor,
392          const uint8_t * buf,
393          uint32_t        len )
394{
395    if( len != 1 )
396        return 0;
397    tor->isRunning = *buf == 't';
398    return TR_FR_RUN;
399}
400
401static uint64_t
402parsePeers( tr_torrent *    tor,
403            const uint8_t * buf,
404            uint32_t        len )
405{
406    uint64_t ret = 0;
407
408    if( !tor->info.isPrivate )
409    {
410        int       i;
411        const int count = len / sizeof( tr_pex );
412
413        for( i = 0; i < count; ++i )
414        {
415            tr_pex pex;
416            readBytes( &pex, &buf, sizeof( tr_pex ) );
417            tr_peerMgrAddPex( tor->session->peerMgr, tor->info.hash,
418                              TR_PEER_FROM_CACHE,
419                              &pex );
420        }
421
422        tr_tordbg( tor, "Loaded %d peers from resume file", count );
423        ret = TR_FR_PEERS;
424    }
425
426    return ret;
427}
428
429static uint64_t
430parseDownloadDir( tr_torrent *    tor,
431                  const uint8_t * buf,
432                  uint32_t        len )
433{
434    uint64_t ret = 0;
435
436    if( buf && *buf && len )
437    {
438        tr_free( tor->downloadDir );
439        tor->downloadDir = tr_strndup( (char*)buf, len );
440        ret = TR_FR_DOWNLOAD_DIR;
441    }
442
443    return ret;
444}
445
446static uint64_t
447parseVersion1( tr_torrent *    tor,
448               const uint8_t * buf,
449               const uint8_t * end,
450               uint64_t        fieldsToLoad )
451{
452    uint64_t ret = 0;
453
454    while( end - buf >= 5 )
455    {
456        uint8_t  id;
457        uint32_t len;
458        readBytes( &id, &buf, sizeof( id ) );
459        readBytes( &len, &buf, sizeof( len ) );
460
461        if( buf + len > end )
462        {
463            tr_torerr( tor, "Resume file seems to be corrupt.  Skipping." );
464        }
465        else if( fieldsToLoad &
466                internalIdToPublicBitfield( id ) ) switch( id )
467            {
468                case FR_ID_DOWNLOADED:
469                    ret |= parseDownloaded( tor, buf, len ); break;
470
471                case FR_ID_UPLOADED:
472                    ret |= parseUploaded( tor, buf, len ); break;
473
474                case FR_ID_PROGRESS:
475                    ret |= parseProgress( tor, buf, len ); break;
476
477                case FR_ID_PRIORITY:
478                    ret |= parsePriorities( tor, buf, len ); break;
479
480                case FR_ID_SPEED:
481                    ret |= parseSpeedLimit( tor, buf, len ); break;
482
483                case FR_ID_RUN:
484                    ret |= parseRun( tor, buf, len ); break;
485
486                case FR_ID_CORRUPT:
487                    ret |= parseCorrupt( tor, buf, len ); break;
488
489                case FR_ID_PEERS:
490                    ret |= parsePeers( tor, buf, len ); break;
491
492                case FR_ID_MAX_PEERS:
493                    ret |= parseConnections( tor, buf, len ); break;
494
495                case FR_ID_DOWNLOAD_DIR:
496                    ret |= parseDownloadDir( tor, buf, len ); break;
497
498                default:
499                    tr_tordbg( tor, "Skipping unknown resume code %d",
500                               (int)id ); break;
501            }
502
503        buf += len;
504    }
505
506    return ret;
507}
508
509static uint8_t*
510loadResumeFile( const tr_torrent * tor,
511                size_t *           len )
512{
513    uint8_t *    ret = NULL;
514    const char * cacheDir = tr_getResumeDir( tor->session );
515    const char * hash = tor->info.hashString;
516
517    if( !ret && tor->session->tag )
518    {
519        char * path = tr_strdup_printf( "%s" TR_PATH_DELIMITER_STR "%s-%s", cacheDir, hash, tor->session->tag );
520        ret = tr_loadFile( path, len );
521        tr_free( path );
522    }
523    if( !ret )
524    {
525        char * path = tr_buildPath( cacheDir, hash, NULL );
526        ret = tr_loadFile( path, len );
527        tr_free( path );
528    }
529
530    return ret;
531}
532
533static uint64_t
534fastResumeLoadImpl( tr_torrent * tor,
535                    uint64_t     fieldsToLoad )
536{
537    uint64_t  ret = 0;
538    size_t    size = 0;
539    uint8_t * buf = loadResumeFile( tor, &size );
540
541    if( !buf )
542        /* %s is the torrent name */
543        tr_torinf( tor, _( "Couldn't read resume file" ) );
544    else
545    {
546        const uint8_t * walk = buf;
547        const uint8_t * end = walk + size;
548        if( end - walk >= 4 )
549        {
550            uint32_t version;
551            readBytes( &version, &walk, sizeof( version ) );
552            if( version == 1 )
553                ret |= parseVersion1 ( tor, walk, end, fieldsToLoad );
554            else
555                /* %s is the torrent name */
556                tr_torinf( tor, _( "Couldn't read resume file" ) );
557        }
558
559        tr_free( buf );
560    }
561
562    return ret;
563}
564
565uint64_t
566tr_fastResumeLoad( tr_torrent * tor,
567                   uint64_t     fieldsToLoad )
568{
569    return fastResumeLoadImpl( tor, fieldsToLoad );
570}
571
572void
573tr_fastResumeRemove( const tr_torrent * tor )
574{
575    const char * cacheDir = tr_getResumeDir( tor->session );
576    const char * hash = tor->info.hashString;
577
578    if( tor->session->tag )
579    {
580        char * path = tr_strdup_printf( "%s" TR_PATH_DELIMITER_STR "%s-%s", cacheDir, hash, tor->session->tag );
581        unlink( path );
582        tr_free( path );
583    }
584    else
585    {
586        char * path = tr_buildPath( cacheDir, hash, NULL );
587        unlink( path );
588        tr_free( path );
589    }
590}
591
Note: See TracBrowser for help on using the repository browser.