/****************************************************************************** * $Id$ * * Copyright (c) 2005-2008 Transmission authors and contributors * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ /*********************************************************************** * Fast resume *********************************************************************** * The format of the resume file is a 4 byte format version (currently 1), * followed by several variable-sized blocks of data. Each block is * preceded by a 1 byte ID and a 4 byte length. The currently recognized * IDs are defined below by the FR_ID_* macros. The length does not include * the 5 bytes for the ID and length. * * The name of the resume file is "resume.-", although * older files with a name of "resume." will be recognized if * the former doesn't exist. * * All values are stored in the native endianness. Moving a * libtransmission resume file from an architecture to another will not * work, although it will not hurt either (the version will be wrong, * so the resume file will not be read). **********************************************************************/ #include #include #include #include /* calloc */ #include /* strcpy, memset, memcmp */ #include #include /* stat */ #include /* unlink */ #include "transmission.h" #include "session.h" #include "completion.h" #include "fastresume.h" #include "peer-mgr.h" #include "platform.h" #include "resume.h" /* TR_FR_ bitwise enum */ #include "torrent.h" #include "utils.h" /* time_t can be 32 or 64 bits... for consistency we'll hardwire 64 */ typedef uint64_t tr_time_t; enum { /* number of bytes downloaded */ FR_ID_DOWNLOADED = 2, /* number of bytes uploaded */ FR_ID_UPLOADED = 3, /* progress data: * - 4 bytes * number of files: mtimes of files * - 1 bit * number of blocks: whether we have the block or not */ FR_ID_PROGRESS = 5, /* dnd and priority * char * number of files: l,n,h for low, normal, high priority * char * number of files: t,f for DND flags */ FR_ID_PRIORITY = 6, /* transfer speeds * uint32_t: dl speed rate to use when the mode is single * uint32_t: dl's tr_speedlimit * uint32_t: ul speed rate to use when the mode is single * uint32_t: ul's tr_speedlimit */ FR_ID_SPEED = 8, /* active * char: 't' if running, 'f' if paused */ FR_ID_RUN = 9, /* number of corrupt bytes downloaded */ FR_ID_CORRUPT = 10, /* IPs and ports of connectable peers */ FR_ID_PEERS = 11, /* download dir of the torrent: zero-terminated string */ FR_ID_DOWNLOAD_DIR = 12, /* pex flag * 't' if pex is enabled, 'f' if disabled */ FR_ID_PEX = 13, /* max connected peers -- uint16_t */ FR_ID_MAX_PEERS = 14 }; /* macros for the length of various pieces of the progress data */ #define FR_MTIME_LEN( t ) \ ( sizeof( tr_time_t ) * ( t )->info.fileCount ) #define FR_BLOCK_BITFIELD_LEN( t ) \ ( ( ( t )->blockCount + 7u ) / 8u ) #define FR_PROGRESS_LEN( t ) \ ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) ) #define FR_SPEED_LEN ( 2 * ( sizeof( uint16_t ) + sizeof( uint8_t ) ) ) static tr_time_t* getMTimes( const tr_torrent * tor, int * setme_n ) { int i; const int n = tor->info.fileCount; tr_time_t * m = calloc( n, sizeof( tr_time_t ) ); for( i = 0; i < n; ++i ) { struct stat sb; char * fname = tr_buildPath( tor->downloadDir, tor->info.files[i].name, NULL ); if( !stat( fname, &sb ) && S_ISREG( sb.st_mode ) ) { #ifdef SYS_DARWIN m[i] = sb.st_mtimespec.tv_sec; #else m[i] = sb.st_mtime; #endif } tr_free( fname ); } *setme_n = n; return m; } /*** **** ***/ static uint64_t internalIdToPublicBitfield( uint8_t id ) { uint64_t ret = 0; switch( id ) { case FR_ID_DOWNLOADED: ret = TR_FR_DOWNLOADED; break; case FR_ID_UPLOADED: ret = TR_FR_UPLOADED; break; case FR_ID_PROGRESS: ret = TR_FR_PROGRESS; break; case FR_ID_PRIORITY: ret = TR_FR_PRIORITY; break; case FR_ID_SPEED: ret = TR_FR_SPEEDLIMIT; break; case FR_ID_RUN: ret = TR_FR_RUN; break; case FR_ID_CORRUPT: ret = TR_FR_CORRUPT; break; case FR_ID_PEERS: ret = TR_FR_PEERS; break; case FR_ID_DOWNLOAD_DIR: ret = TR_FR_DOWNLOAD_DIR; break; case FR_ID_MAX_PEERS: ret = TR_FR_MAX_PEERS; break; } return ret; } static void readBytes( void * target, const uint8_t ** source, size_t byteCount ) { memcpy( target, *source, byteCount ); *source += byteCount; } static uint64_t parseDownloaded( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { if( len != sizeof( uint64_t ) ) return 0; readBytes( &tor->downloadedPrev, &buf, sizeof( uint64_t ) ); return TR_FR_DOWNLOADED; } static uint64_t parseUploaded( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { if( len != sizeof( uint64_t ) ) return 0; readBytes( &tor->uploadedPrev, &buf, sizeof( uint64_t ) ); return TR_FR_UPLOADED; } static uint64_t parseCorrupt( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { if( len != sizeof( uint64_t ) ) return 0; readBytes( &tor->corruptPrev, &buf, sizeof( uint64_t ) ); return TR_FR_CORRUPT; } static uint64_t parseConnections( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { if( len != sizeof( uint16_t ) ) return 0; readBytes( &tor->maxConnectedPeers, &buf, sizeof( uint16_t ) ); return TR_FR_MAX_PEERS; } static uint64_t parseProgress( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { uint64_t ret = 0; if( len == FR_PROGRESS_LEN( tor ) ) { int i; int n; tr_bitfield bitfield; /* compare file mtimes */ tr_time_t * curMTimes = getMTimes( tor, &n ); const uint8_t * walk = buf; tr_time_t mtime; for( i = 0; i < n; ++i ) { readBytes( &mtime, &walk, sizeof( tr_time_t ) ); if( curMTimes[i] == mtime ) tr_torrentSetFileChecked( tor, i, TRUE ); else { tr_torrentSetFileChecked( tor, i, FALSE ); tr_tordbg( tor, "Torrent needs to be verified" ); } } free( curMTimes ); /* get the completion bitfield */ memset( &bitfield, 0, sizeof bitfield ); bitfield.byteCount = FR_BLOCK_BITFIELD_LEN( tor ); bitfield.bitCount = bitfield.byteCount * 8; bitfield.bits = (uint8_t*) walk; if( tr_cpBlockBitfieldSet( tor->completion, &bitfield ) ) ret = TR_FR_PROGRESS; else { tr_torrentUncheck( tor ); tr_tordbg( tor, "Torrent needs to be verified" ); } } /* the files whose mtimes are wrong, remove from completion pending a recheck... */ { tr_piece_index_t i; for( i = 0; i < tor->info.pieceCount; ++i ) if( !tr_torrentIsPieceChecked( tor, i ) ) tr_cpPieceRem( tor->completion, i ); } return ret; } static uint64_t parsePriorities( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { uint64_t ret = 0; if( len == (uint32_t)( 2 * tor->info.fileCount ) ) { const size_t n = tor->info.fileCount; const uint8_t * walk = buf; tr_file_index_t *dnd = NULL, dndCount = 0; tr_file_index_t *dl = NULL, dlCount = 0; size_t i; len = 2 * n; /* set file priorities */ for( i = 0; i < n; ++i ) { tr_priority_t priority; const char ch = *walk++; switch( ch ) { case 'l': priority = TR_PRI_LOW; break; case 'h': priority = TR_PRI_HIGH; break; default: priority = TR_PRI_NORMAL; break; } tr_torrentInitFilePriority( tor, i, priority ); } /* set the dnd flags */ dl = tr_new( tr_file_index_t, len ); dnd = tr_new( tr_file_index_t, len ); for( i = 0; i < n; ++i ) if( *walk++ == 't' ) /* 't' means the DND flag is true */ dnd[dndCount++] = i; else dl[dlCount++] = i; if( dndCount ) tr_torrentInitFileDLs ( tor, dnd, dndCount, FALSE ); if( dlCount ) tr_torrentInitFileDLs ( tor, dl, dlCount, TRUE ); tr_free( dnd ); tr_free( dl ); ret = TR_FR_PRIORITY; } return ret; } static uint64_t parseSpeedLimit( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { uint64_t ret = 0; if( len == FR_SPEED_LEN ) { uint8_t i8; uint16_t i16; readBytes( &i16, &buf, sizeof( i16 ) ); tr_torrentSetSpeedLimit( tor, TR_DOWN, i16 ); readBytes( &i8, &buf, sizeof( i8 ) ); tr_torrentSetSpeedMode( tor, TR_DOWN, (tr_speedlimit)i8 ); readBytes( &i16, &buf, sizeof( i16 ) ); tr_torrentSetSpeedLimit( tor, TR_UP, i16 ); readBytes( &i8, &buf, sizeof( i8 ) ); tr_torrentSetSpeedMode( tor, TR_UP, (tr_speedlimit)i8 ); ret = TR_FR_SPEEDLIMIT; } return ret; } static uint64_t parseRun( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { if( len != 1 ) return 0; tor->isRunning = *buf == 't'; return TR_FR_RUN; } static uint64_t parsePeers( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { uint64_t ret = 0; if( !tor->info.isPrivate ) { int i; const int count = len / sizeof( tr_pex ); for( i = 0; i < count; ++i ) { tr_pex pex; readBytes( &pex, &buf, sizeof( tr_pex ) ); tr_peerMgrAddPex( tor->session->peerMgr, tor->info.hash, TR_PEER_FROM_CACHE, &pex ); } tr_tordbg( tor, "Loaded %d peers from resume file", count ); ret = TR_FR_PEERS; } return ret; } static uint64_t parseDownloadDir( tr_torrent * tor, const uint8_t * buf, uint32_t len ) { uint64_t ret = 0; if( buf && *buf && len ) { tr_free( tor->downloadDir ); tor->downloadDir = tr_strndup( (char*)buf, len ); ret = TR_FR_DOWNLOAD_DIR; } return ret; } static uint64_t parseVersion1( tr_torrent * tor, const uint8_t * buf, const uint8_t * end, uint64_t fieldsToLoad ) { uint64_t ret = 0; while( end - buf >= 5 ) { uint8_t id; uint32_t len; readBytes( &id, &buf, sizeof( id ) ); readBytes( &len, &buf, sizeof( len ) ); if( buf + len > end ) { tr_torerr( tor, "Resume file seems to be corrupt. Skipping." ); } else if( fieldsToLoad & internalIdToPublicBitfield( id ) ) switch( id ) { case FR_ID_DOWNLOADED: ret |= parseDownloaded( tor, buf, len ); break; case FR_ID_UPLOADED: ret |= parseUploaded( tor, buf, len ); break; case FR_ID_PROGRESS: ret |= parseProgress( tor, buf, len ); break; case FR_ID_PRIORITY: ret |= parsePriorities( tor, buf, len ); break; case FR_ID_SPEED: ret |= parseSpeedLimit( tor, buf, len ); break; case FR_ID_RUN: ret |= parseRun( tor, buf, len ); break; case FR_ID_CORRUPT: ret |= parseCorrupt( tor, buf, len ); break; case FR_ID_PEERS: ret |= parsePeers( tor, buf, len ); break; case FR_ID_MAX_PEERS: ret |= parseConnections( tor, buf, len ); break; case FR_ID_DOWNLOAD_DIR: ret |= parseDownloadDir( tor, buf, len ); break; default: tr_tordbg( tor, "Skipping unknown resume code %d", (int)id ); break; } buf += len; } return ret; } static uint8_t* loadResumeFile( const tr_torrent * tor, size_t * len ) { uint8_t * ret = NULL; const char * cacheDir = tr_getResumeDir( tor->session ); const char * hash = tor->info.hashString; if( !ret && tor->session->tag ) { char * path = tr_strdup_printf( "%s" TR_PATH_DELIMITER_STR "%s-%s", cacheDir, hash, tor->session->tag ); ret = tr_loadFile( path, len ); tr_free( path ); } if( !ret ) { char * path = tr_buildPath( cacheDir, hash, NULL ); ret = tr_loadFile( path, len ); tr_free( path ); } return ret; } static uint64_t fastResumeLoadImpl( tr_torrent * tor, uint64_t fieldsToLoad ) { uint64_t ret = 0; size_t size = 0; uint8_t * buf = loadResumeFile( tor, &size ); if( !buf ) /* %s is the torrent name */ tr_torinf( tor, _( "Couldn't read resume file" ) ); else { const uint8_t * walk = buf; const uint8_t * end = walk + size; if( end - walk >= 4 ) { uint32_t version; readBytes( &version, &walk, sizeof( version ) ); if( version == 1 ) ret |= parseVersion1 ( tor, walk, end, fieldsToLoad ); else /* %s is the torrent name */ tr_torinf( tor, _( "Couldn't read resume file" ) ); } tr_free( buf ); } return ret; } uint64_t tr_fastResumeLoad( tr_torrent * tor, uint64_t fieldsToLoad ) { return fastResumeLoadImpl( tor, fieldsToLoad ); } void tr_fastResumeRemove( const tr_torrent * tor ) { const char * cacheDir = tr_getResumeDir( tor->session ); const char * hash = tor->info.hashString; if( tor->session->tag ) { char * path = tr_strdup_printf( "%s" TR_PATH_DELIMITER_STR "%s-%s", cacheDir, hash, tor->session->tag ); unlink( path ); tr_free( path ); } else { char * path = tr_buildPath( cacheDir, hash, NULL ); unlink( path ); tr_free( path ); } }