(trunk libT) #2955 "Lazy Verification (aka Just-in-Time Verification)" -- implemented.

This commit is contained in:
Charles Kerr 2010-12-09 20:43:23 +00:00
parent 88bb3efa4e
commit 43ed57b278
7 changed files with 255 additions and 340 deletions

View File

@ -28,7 +28,6 @@
#include "completion.h"
#include "crypto.h"
#include "handshake.h"
#include "inout.h" /* tr_ioTestPiece */
#include "net.h"
#include "peer-io.h"
#include "peer-mgr.h"
@ -1453,7 +1452,9 @@ peerCallbackFunc( tr_peer * peer, const tr_peer_event * e, void * vt )
if( tr_cpPieceIsComplete( &tor->completion, e->pieceIndex ) )
{
const tr_piece_index_t p = e->pieceIndex;
const tr_bool ok = tr_ioTestPiece( tor, p );
const tr_bool ok = tr_torrentCheckPiece( tor, p );
tr_tordbg( tor, "[LAZY] checked just-completed piece %zu", (size_t)p );
if( !ok )
{
@ -1461,8 +1462,6 @@ peerCallbackFunc( tr_peer * peer, const tr_peer_event * e, void * vt )
(unsigned long)p );
}
tr_torrentSetHasPiece( tor, p, ok );
tr_torrentSetPieceChecked( tor, p, TRUE );
tr_peerMgrSetBlame( tor, p, ok );
if( !ok )

View File

@ -1920,6 +1920,12 @@ fillOutputBuffer( tr_peermsgs * msgs, time_t now )
tr_peerIoWriteUint32( io, out, req.offset );
err = tr_cacheReadBlock( getSession(msgs)->cache, msgs->torrent, req.index, req.offset, req.length, EVBUFFER_DATA(out)+EVBUFFER_LENGTH(out) );
/* check the piece if it needs checking... */
if( !err && tr_torrentPieceNeedsCheck( msgs->torrent, req.index ) )
if(( err = !tr_torrentCheckPiece( msgs->torrent, req.index )))
tr_torrentSetLocalError( msgs->torrent, _( "Piece #%zu is corrupt! Please Verify Local Data." ), (size_t)piece );
if( err )
{
if( fext )

View File

@ -60,7 +60,7 @@
#define KEY_IDLELIMIT_MINS "idle-limit"
#define KEY_IDLELIMIT_MODE "idle-mode"
#define KEY_PROGRESS_MTIMES "mtimes"
#define KEY_PROGRESS_CHECKTIME "time-checked"
#define KEY_PROGRESS_BITFIELD "bitfield"
#define KEY_PROGRESS_HAVE "have"
@ -415,7 +415,6 @@ saveProgress( tr_benc * dict,
const tr_torrent * tor )
{
size_t i, n;
time_t * mtimes;
tr_benc * p;
tr_benc * m;
const tr_bitfield * bitfield;
@ -423,15 +422,11 @@ saveProgress( tr_benc * dict,
p = tr_bencDictAdd( dict, KEY_PROGRESS );
tr_bencInitDict( p, 2 );
/* add the mtimes */
mtimes = tr_torrentGetMTimes( tor, &n );
m = tr_bencDictAddList( p, KEY_PROGRESS_MTIMES, n );
for( i = 0; i < n; ++i )
{
if( !tr_torrentIsFileChecked( tor, i ) )
mtimes[i] = ~(time_t)0; /* force a recheck */
tr_bencListAddInt( m, mtimes[i] );
}
/* add each piece's timeChecked */
n = tor->info.pieceCount;
m = tr_bencDictAddList( p, KEY_PROGRESS_CHECKTIME, n );
for( i=0; i<n; ++i )
tr_bencListAddInt( m, tor->info.pieces[i].timeChecked );
/* add the progress */
if( tor->completeness == TR_SEED )
@ -439,18 +434,19 @@ saveProgress( tr_benc * dict,
bitfield = tr_cpBlockBitfield( &tor->completion );
tr_bencDictAddRaw( p, KEY_PROGRESS_BITFIELD,
bitfield->bits, bitfield->byteCount );
/* cleanup */
tr_free( mtimes );
}
static uint64_t
loadProgress( tr_benc * dict,
tr_torrent * tor )
{
size_t i, n;
uint64_t ret = 0;
tr_benc * p;
for( i=0, n=tor->info.pieceCount; i<n; ++i )
tor->info.pieces[i].timeChecked = 0;
if( tr_bencDictFindDict( dict, KEY_PROGRESS, &p ) )
{
const char * err;
@ -458,47 +454,13 @@ loadProgress( tr_benc * dict,
const uint8_t * raw;
size_t rawlen;
tr_benc * m;
size_t n;
time_t * curMTimes = tr_torrentGetMTimes( tor, &n );
int64_t timeChecked;
if( tr_bencDictFindList( p, KEY_PROGRESS_MTIMES, &m )
&& ( n == tor->info.fileCount )
&& ( n == tr_bencListSize( m ) ) )
{
size_t i;
for( i = 0; i < n; ++i )
{
int64_t tmp;
if( !tr_bencGetInt( tr_bencListChild( m, i ), &tmp ) )
{
tr_tordbg(
tor,
"File #%zu needs to be verified - couldn't find benc entry",
i );
tr_torrentSetFileChecked( tor, i, FALSE );
}
else
{
const time_t t = (time_t) tmp;
if( t == curMTimes[i] )
tr_torrentSetFileChecked( tor, i, TRUE );
else
{
tr_tordbg(
tor,
"File #%zu needs to be verified - times %lu and %lu don't match",
i, t, curMTimes[i] );
tr_torrentSetFileChecked( tor, i, FALSE );
}
}
}
}
else
{
tr_torrentUncheck( tor );
tr_tordbg(
tor, "Torrent needs to be verified - unable to find mtimes" );
}
/* load in the timestamp of when we last checked each piece */
if( tr_bencDictFindList( p, KEY_PROGRESS_CHECKTIME, &m ) )
for( i=0, n=tor->info.pieceCount; i<n; ++i )
if( tr_bencGetInt( tr_bencListChild( m, i ), &timeChecked ) )
tor->info.pieces[i].timeChecked = (time_t)timeChecked;
err = NULL;
if( tr_bencDictFindStr( p, KEY_PROGRESS_HAVE, &str ) )
@ -518,13 +480,10 @@ loadProgress( tr_benc * dict,
err = "Error loading bitfield";
}
else err = "Couldn't find 'have' or 'bitfield'";
if( err != NULL )
{
tr_torrentUncheck( tor );
tr_tordbg( tor, "Torrent needs to be verified - %s", err );
}
tr_free( curMTimes );
if( err != NULL )
tr_tordbg( tor, "Torrent needs to be verified - %s", err );
ret = TR_FR_PROGRESS;
}

View File

@ -34,6 +34,7 @@
#include "crypto.h" /* for tr_sha1 */
#include "resume.h"
#include "fdlimit.h" /* tr_fdTorrentClose */
#include "inout.h" /* tr_ioTestPiece() */
#include "magnet.h"
#include "metainfo.h"
#include "peer-common.h" /* MAX_BLOCK_SIZE */
@ -459,6 +460,8 @@ tr_torrentSetLocalError( tr_torrent * tor, const char * fmt, ... )
evutil_vsnprintf( tor->errorString, sizeof( tor->errorString ), fmt, ap );
va_end( ap );
tr_torerr( tor, "%s", tor->errorString );
if( tor->isRunning )
tor->isStopping = TRUE;
}
@ -604,11 +607,11 @@ calculatePiecePriority( const tr_torrent * tor,
static void
tr_torrentInitFilePieces( tr_torrent * tor )
{
tr_file_index_t f;
int * firstFiles;
tr_file_index_t f;
tr_piece_index_t p;
uint64_t offset = 0;
tr_info * inf = &tor->info;
int * firstFiles;
/* assign the file offsets */
for( f=0; f<inf->fileCount; ++f ) {
@ -720,8 +723,6 @@ torrentInitFromInfo( tr_torrent * tor )
tr_torrentInitFilePieces( tor );
tr_bitfieldConstruct( &tor->checkedPieces, tor->info.pieceCount );
tor->completeness = tr_cpGetStatus( &tor->completion );
}
@ -735,12 +736,28 @@ tr_torrentGotNewInfoDict( tr_torrent * tor )
tr_torrentFireMetadataCompleted( tor );
}
static tr_bool
setLocalErrorIfFilesDisappeared( tr_torrent * tor )
{
const tr_bool disappeared = ( tr_cpHaveTotal( &tor->completion ) > 0 ) && ( tr_torrentGetCurrentSizeOnDisk( tor ) == 0 );
if( disappeared )
{
tr_tordbg( tor, "%s", "[LAZY] uh oh, the files disappeared" );
tr_torrentSetLocalError( tor, "%s", _( "No data found! Ensure your drives are connected or use \"Set Location\". To re-download, remove the torrent and re-add it." ) );
}
return disappeared;
}
static void
torrentInit( tr_torrent * tor, const tr_ctor * ctor )
{
int doStart;
uint64_t loaded;
const char * dir;
tr_bool isNewTorrent;
struct stat st;
static int nextUniqueId = 1;
tr_session * session = tr_ctorGetSession( ctor );
@ -778,14 +795,13 @@ torrentInit( tr_torrent * tor, const tr_ctor * ctor )
assert( !tor->downloadedCur );
assert( !tor->uploadedCur );
tr_torrentUncheck( tor );
tr_torrentSetAddedDate( tor, tr_time( ) ); /* this is a default value to be
overwritten by the resume file */
torrentInitFromInfo( tor );
loaded = tr_torrentLoadResume( tor, ~0, ctor );
tor->completeness = tr_cpGetStatus( &tor->completion );
setLocalErrorIfFilesDisappeared( tor );
tr_ctorInitTorrentPriorities( ctor, tor );
tr_ctorInitTorrentWanted( ctor, tor );
@ -829,6 +845,9 @@ torrentInit( tr_torrent * tor, const tr_ctor * ctor )
++session->torrentCount;
}
/* if we don't have a local .torrent file already, assume the torrent is new */
isNewTorrent = stat( tor->info.torrent, &st );
/* maybe save our own copy of the metainfo */
if( tr_ctorGetSave( ctor ) )
{
@ -845,8 +864,15 @@ torrentInit( tr_torrent * tor, const tr_ctor * ctor )
tor->tiers = tr_announcerAddTorrent( tor->session->announcer, tor, onTrackerResponse, NULL );
if( doStart )
if( isNewTorrent )
{
tor->startAfterVerify = doStart;
tr_torrentVerify( tor );
}
else if( doStart )
{
torrentStart( tor );
}
tr_sessionUnlock( session );
}
@ -1048,6 +1074,21 @@ tr_torrentGetActivity( tr_torrent * tor )
return TR_STATUS_SEED;
}
static double
getVerifyProgress( const tr_torrent * tor )
{
tr_piece_index_t i, n;
tr_piece_index_t checked = 0;
assert( tr_isTorrent( tor ) );
for( i=0, n=tor->info.pieceCount; i!=n; ++i )
if( tor->info.pieces[i].timeChecked )
++checked;
return checked / (double)tor->info.pieceCount;
}
const tr_stat *
tr_torrentStat( tr_torrent * tor )
{
@ -1097,18 +1138,14 @@ tr_torrentStat( tr_torrent * tor )
s->percentComplete = tr_cpPercentComplete ( &tor->completion );
s->metadataPercentComplete = tr_torrentGetMetadataPercent( tor );
s->percentDone = tr_cpPercentDone ( &tor->completion );
s->leftUntilDone = tr_cpLeftUntilDone( &tor->completion );
s->sizeWhenDone = tr_cpSizeWhenDone ( &tor->completion );
s->recheckProgress = s->activity == TR_STATUS_CHECK
? 1.0 - ( tr_torrentCountUncheckedPieces( tor ) / (float) tor->info.pieceCount )
: 0.0;
s->activityDate = tor->activityDate;
s->addedDate = tor->addedDate;
s->doneDate = tor->doneDate;
s->startDate = tor->startDate;
s->percentDone = tr_cpPercentDone ( &tor->completion );
s->leftUntilDone = tr_cpLeftUntilDone( &tor->completion );
s->sizeWhenDone = tr_cpSizeWhenDone ( &tor->completion );
s->recheckProgress = s->activity == TR_STATUS_CHECK ? getVerifyProgress( tor ) : 0;
s->activityDate = tor->activityDate;
s->addedDate = tor->addedDate;
s->doneDate = tor->doneDate;
s->startDate = tor->startDate;
if ((s->activity == TR_STATUS_DOWNLOAD || s->activity == TR_STATUS_SEED) && s->startDate != 0)
s->idleSecs = difftime(tr_time(), MAX(s->startDate, s->activityDate));
@ -1441,8 +1478,6 @@ freeTorrent( tr_torrent * tor )
tr_announcerRemoveTorrent( session->announcer, tor );
tr_bitfieldDestruct( &tor->checkedPieces );
tr_free( tor->downloadDir );
tr_free( tor->incompleteDir );
tr_free( tor->peer_id );
@ -1472,47 +1507,54 @@ freeTorrent( tr_torrent * tor )
**/
static void
checkAndStartImpl( void * vtor )
torrentStartImpl( void * vtor )
{
time_t now;
tr_torrent * tor = vtor;
assert( tr_isTorrent( tor ) );
tr_sessionLock( tor->session );
/** If we had local data before, but it's disappeared,
stop the torrent and log an error. */
if( tor->preVerifyTotal && !tr_cpHaveTotal( &tor->completion ) )
{
tr_torrentSetLocalError( tor, "%s", _( "No data found! Reconnect any disconnected drives, use \"Set Location\", or restart the torrent to re-download." ) );
}
else
{
const time_t now = tr_time( );
tor->isRunning = TRUE;
tor->completeness = tr_cpGetStatus( &tor->completion );
tor->startDate = tor->anyDate = now;
tr_torrentClearError( tor );
tor->finishedSeedingByIdle = FALSE;
tr_torrentRecheckCompleteness( tor );
tr_torrentResetTransferStats( tor );
tr_announcerTorrentStarted( tor );
tor->dhtAnnounceAt = now + tr_cryptoWeakRandInt( 20 );
tor->dhtAnnounce6At = now + tr_cryptoWeakRandInt( 20 );
tor->lpdAnnounceAt = now;
tr_peerMgrStartTorrent( tor );
}
now = tr_time( );
tor->isRunning = TRUE;
tor->completeness = tr_cpGetStatus( &tor->completion );
tor->startDate = tor->anyDate = now;
tr_torrentClearError( tor );
tor->finishedSeedingByIdle = FALSE;
tr_torrentResetTransferStats( tor );
tr_announcerTorrentStarted( tor );
tor->dhtAnnounceAt = now + tr_cryptoWeakRandInt( 20 );
tor->dhtAnnounce6At = now + tr_cryptoWeakRandInt( 20 );
tor->lpdAnnounceAt = now;
tr_peerMgrStartTorrent( tor );
tr_sessionUnlock( tor->session );
}
static void
checkAndStartCB( tr_torrent * tor )
uint64_t
tr_torrentGetCurrentSizeOnDisk( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
assert( tr_isSession( tor->session ) );
tr_file_index_t i;
uint64_t byte_count = 0;
const tr_file_index_t n = tor->info.fileCount;
tr_runInEventThread( tor->session, checkAndStartImpl, tor );
for( i=0; i<n; ++i )
{
struct stat sb;
char * filename = tr_torrentFindFile( tor, i );
sb.st_size = 0;
if( filename && !stat( filename, &sb ) )
byte_count += sb.st_size;
tr_free( filename );
}
return byte_count;
}
static void
@ -1520,33 +1562,36 @@ torrentStart( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
/* already running... */
if( tor->isRunning )
return;
/* don't allow the torrent to be started if the files disappeared */
if( setLocalErrorIfFilesDisappeared( tor ) )
return;
/* otherwise, start it now... */
tr_sessionLock( tor->session );
if( !tor->isRunning )
{
/* allow finished torrents to be resumed */
if( tr_torrentIsSeedRatioDone( tor ) )
{
tr_torinf( tor, "Restarted manually -- disabling its seed ratio" );
tr_torrentSetRatioMode( tor, TR_RATIOLIMIT_UNLIMITED );
}
tr_verifyRemove( tor );
/* corresponds to the peer_id sent as a tracker request parameter.
* one tracker admin says: "When the same torrent is opened and
* closed and opened again without quitting Transmission ...
* change the peerid. It would help sometimes if a stopped event
* was missed to ensure that we didn't think someone was cheating. */
tr_free( tor->peer_id );
tor->peer_id = tr_peerIdNew( );
tor->isRunning = 1;
tr_torrentSetDirty( tor );
tor->preVerifyTotal = tr_cpHaveTotal( &tor->completion );
tr_verifyAdd( tor, checkAndStartCB );
/* allow finished torrents to be resumed */
if( tr_torrentIsSeedRatioDone( tor ) ) {
tr_torinf( tor, _( "Restarted manually -- disabling its seed ratio" ) );
tr_torrentSetRatioMode( tor, TR_RATIOLIMIT_UNLIMITED );
}
tr_verifyRemove( tor );
/* corresponds to the peer_id sent as a tracker request parameter.
* one tracker admin says: "When the same torrent is opened and
* closed and opened again without quitting Transmission ...
* change the peerid. It would help sometimes if a stopped event
* was missed to ensure that we didn't think someone was cheating. */
tr_free( tor->peer_id );
tor->peer_id = tr_peerIdNew( );
tor->isRunning = 1;
tr_torrentSetDirty( tor );
tr_runInEventThread( tor->session, torrentStartImpl, tor );
tr_sessionUnlock( tor->session );
}
@ -1561,19 +1606,13 @@ static void
torrentRecheckDoneImpl( void * vtor )
{
tr_torrent * tor = vtor;
assert( tr_isTorrent( tor ) );
tr_torrentRecheckCompleteness( tor );
if( tor->preVerifyTotal && !tr_cpHaveTotal( &tor->completion ) )
{
tr_torrentSetLocalError( tor, "%s", _( "Can't find local data. Try \"Set Location\" to find it, or restart the torrent to re-download." ) );
}
else if( tor->startAfterVerify )
{
if( tor->startAfterVerify ) {
tor->startAfterVerify = FALSE;
tr_torrentStart( tor );
torrentStart( tor );
}
}
@ -1604,10 +1643,10 @@ verifyTorrent( void * vtor )
tor->startAfterVerify = startAfter;
}
/* add the torrent to the recheck queue */
tor->preVerifyTotal = tr_cpHaveTotal( &tor->completion );
tr_torrentUncheck( tor );
tr_verifyAdd( tor, torrentRecheckDoneCB );
if( setLocalErrorIfFilesDisappeared( tor ) )
tor->startAfterVerify = FALSE;
else
tr_verifyAdd( tor, torrentRecheckDoneCB );
tr_sessionUnlock( tor->session );
}
@ -2208,96 +2247,88 @@ tr_pieceOffset( const tr_torrent * tor,
***/
void
tr_torrentSetPieceChecked( tr_torrent * tor,
tr_piece_index_t piece,
tr_bool isChecked )
tr_torrentSetPieceChecked( tr_torrent * tor, tr_piece_index_t pieceIndex )
{
assert( tr_isTorrent( tor ) );
assert( pieceIndex < tor->info.pieceCount );
if( isChecked )
tr_bitfieldAdd( &tor->checkedPieces, piece );
else
tr_bitfieldRem( &tor->checkedPieces, piece );
tr_tordbg( tor, "[LAZY] setting piece %zu timeChecked to now", (size_t)pieceIndex );
tor->info.pieces[pieceIndex].timeChecked = tr_time( );
}
void
tr_torrentSetFileChecked( tr_torrent * tor,
tr_file_index_t fileIndex,
tr_bool isChecked )
tr_torrentSetChecked( tr_torrent * tor, time_t when )
{
const tr_file * file = &tor->info.files[fileIndex];
const tr_piece_index_t begin = file->firstPiece;
const tr_piece_index_t end = file->lastPiece + 1;
tr_piece_index_t i, n;
assert( tr_isTorrent( tor ) );
if( isChecked )
tr_bitfieldAddRange( &tor->checkedPieces, begin, end );
else
tr_bitfieldRemRange( &tor->checkedPieces, begin, end );
for( i=0, n=tor->info.pieceCount; i!=n; ++i )
tor->info.pieces[i].timeChecked = when;
}
tr_bool
tr_torrentIsFileChecked( const tr_torrent * tor,
tr_file_index_t fileIndex )
tr_torrentCheckPiece( tr_torrent * tor, tr_piece_index_t pieceIndex )
{
const tr_file * file = &tor->info.files[fileIndex];
const tr_piece_index_t begin = file->firstPiece;
const tr_piece_index_t end = file->lastPiece + 1;
tr_piece_index_t i;
tr_bool isChecked = TRUE;
const tr_bool pass = tr_ioTestPiece( tor, pieceIndex );
assert( tr_isTorrent( tor ) );
tr_tordbg( tor, "[LAZY] tr_torrentCheckPiece tested piece %zu, pass==%d", (size_t)pieceIndex, (int)pass );
tr_torrentSetHasPiece( tor, pieceIndex, pass );
tr_torrentSetPieceChecked( tor, pieceIndex );
tor->anyDate = tr_time( );
tr_torrentSetDirty( tor );
for( i = begin; isChecked && i < end; ++i )
if( !tr_torrentIsPieceChecked( tor, i ) )
isChecked = FALSE;
return isChecked;
return pass;
}
void
tr_torrentUncheck( tr_torrent * tor )
static time_t
getFileMTime( const tr_torrent * tor, tr_file_index_t i )
{
assert( tr_isTorrent( tor ) );
struct stat sb;
time_t mtime = 0;
char * path = tr_torrentFindFile( tor, i );
tr_bitfieldRemRange( &tor->checkedPieces, 0, tor->info.pieceCount );
}
int
tr_torrentCountUncheckedPieces( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->info.pieceCount - tr_bitfieldCountTrueBits( &tor->checkedPieces );
}
time_t*
tr_torrentGetMTimes( const tr_torrent * tor, size_t * setme_n )
{
size_t i;
const size_t n = tor->info.fileCount;
time_t * m = tr_new0( time_t, n );
assert( tr_isTorrent( tor ) );
for( i = 0; i < n; ++i )
if( ( path != NULL ) && !stat( path, &sb ) && S_ISREG( sb.st_mode ) )
{
struct stat sb;
char * path = tr_torrentFindFile( tor, i );
if( ( path != NULL ) && !stat( path, &sb ) && S_ISREG( sb.st_mode ) )
{
#ifdef SYS_DARWIN
m[i] = sb.st_mtimespec.tv_sec;
mtime = sb.st_mtimespec.tv_sec;
#else
m[i] = sb.st_mtime;
mtime = sb.st_mtime;
#endif
}
tr_free( path );
}
*setme_n = n;
return m;
tr_free( path );
return mtime;
}
tr_bool
tr_torrentPieceNeedsCheck( const tr_torrent * tor, tr_piece_index_t p )
{
uint64_t unused;
tr_file_index_t f;
const tr_info * inf = tr_torrentInfo( tor );
/* if we've never checked this piece, then it needs to be checked */
if( !inf->pieces[p].timeChecked ) {
tr_tordbg( tor, "[LAZY] piece %zu needs to be tested because it's never been tested", (size_t)p );
return TRUE;
}
/* If we think we've completed one of the files in this piece,
* but it's been modified since we last checked it,
* then it needs to be rechecked */
tr_ioFindFileLocation( tor, p, 0, &f, &unused );
for( ; f < inf->fileCount && pieceHasFile( p, &inf->files[f] ); ++f ) {
if( tr_cpFileIsComplete( &tor->completion, f ) ) {
if( getFileMTime( tor, f ) > inf->pieces[p].timeChecked ) {
tr_tordbg( tor, "[LAZY] piece %zu needs to be tested because file %zu mtime is newer than check time %zu", (size_t)p, (size_t)f, (size_t)inf->pieces[p].timeChecked );
return TRUE;
}
}
}
tr_tordbg( tor, "[LAZY] piece %zu does not need to be tested", (size_t)p );
return FALSE;
}
/***

View File

@ -86,23 +86,10 @@ void tr_torrentInitFilePriority( tr_torrent * tor,
tr_file_index_t fileIndex,
tr_priority_t priority );
int tr_torrentCountUncheckedPieces( const tr_torrent * );
tr_bool tr_torrentIsFileChecked( const tr_torrent * tor,
tr_file_index_t file );
void tr_torrentSetPieceChecked( tr_torrent * tor,
tr_piece_index_t piece,
tr_bool isChecked );
tr_piece_index_t piece );
void tr_torrentSetFileChecked( tr_torrent * tor,
tr_file_index_t file,
tr_bool isChecked );
void tr_torrentUncheck( tr_torrent * tor );
time_t* tr_torrentGetMTimes( const tr_torrent * tor,
size_t * setmeCount );
void tr_torrentSetChecked( tr_torrent * tor, time_t when );
tr_torrent* tr_torrentNext( tr_session * session,
tr_torrent * current );
@ -190,7 +177,6 @@ struct tr_torrent
struct tr_completion completion;
struct tr_bitfield checkedPieces;
tr_completeness completeness;
struct tr_torrent_tiers * tiers;
@ -261,8 +247,6 @@ struct tr_torrent
uint16_t idleLimitMinutes;
tr_idlelimit idleLimitMode;
tr_bool finishedSeedingByIdle;
uint64_t preVerifyTotal;
};
/* get the index of this piece's first block */
@ -353,12 +337,6 @@ static inline tr_bool tr_torrentAllowsLPD( const tr_torrent * tor )
&& ( !tr_torrentIsPrivate( tor ) );
}
static inline tr_bool tr_torrentIsPieceChecked( const tr_torrent * tor,
tr_piece_index_t i )
{
return tr_bitfieldHasFast( &tor->checkedPieces, i );
}
/***
****
***/
@ -431,5 +409,18 @@ void tr_torrentGotNewInfoDict( tr_torrent * tor );
void tr_torrentSetSpeedLimit_Bps ( tr_torrent *, tr_direction, int Bps );
int tr_torrentGetSpeedLimit_Bps ( const tr_torrent *, tr_direction );
/**
* @return true if this piece needs to be tested
*/
tr_bool tr_torrentPieceNeedsCheck( const tr_torrent * tor, tr_piece_index_t pieceIndex );
/**
* @brief Test a piece against its info dict checksum
* @return true if the piece's passes the checksum test
*/
tr_bool tr_torrentCheckPiece( tr_torrent * tor, tr_piece_index_t pieceIndex );
uint64_t tr_torrentGetCurrentSizeOnDisk( const tr_torrent * tor );
#endif

View File

@ -1609,6 +1609,7 @@ tr_file;
/** @brief a part of tr_info that represents a single piece of the torrent's content */
typedef struct tr_piece
{
time_t timeChecked; /* the last time we tested this piece */
uint8_t hash[SHA_DIGEST_LENGTH]; /* pieces hash */
int8_t priority; /* TR_PRI_HIGH, _NORMAL, or _LOW */
int8_t dnd; /* nonzero if the piece shouldn't be

View File

@ -10,14 +10,9 @@
* $Id$
*/
#include <unistd.h> /* S_ISREG */
#include <sys/stat.h>
#ifdef HAVE_POSIX_FADVISE
#define _XOPEN_SOURCE 600
#endif
#if defined(HAVE_POSIX_FADVISE) || defined(SYS_DARWIN)
#include <fcntl.h> /* posix_fadvise() / fcntl() */
#include <fcntl.h> /* posix_fadvise() */
#endif
#include <openssl/sha.h>
@ -25,14 +20,12 @@
#include "transmission.h"
#include "completion.h"
#include "fdlimit.h"
#include "inout.h"
#include "list.h"
#include "platform.h"
#include "platform.h" /* tr_lock() */
#include "torrent.h"
#include "utils.h" /* tr_buildPath */
#include "utils.h" /* tr_valloc(), tr_free() */
#include "verify.h"
/***
****
***/
@ -42,11 +35,10 @@ enum
MSEC_TO_SLEEP_PER_SECOND_DURING_VERIFY = 100
};
/* #define STOPWATCH */
static tr_bool
verifyTorrent( tr_torrent * tor, tr_bool * stopFlag )
{
time_t end;
SHA_CTX sha;
int fd = -1;
int64_t filePos = 0;
@ -58,14 +50,13 @@ verifyTorrent( tr_torrent * tor, tr_bool * stopFlag )
tr_file_index_t prevFileIndex = !fileIndex;
tr_piece_index_t pieceIndex = 0;
const time_t begin = tr_time( );
time_t end;
const size_t buflen = 1024 * 128; /* 128 KiB buffer */
uint8_t * buffer = tr_valloc( buflen );
tr_torrentUncheck( tor );
SHA1_Init( &sha );
tr_tordbg( tor, "%s", "[LAZY] verifying torrent..." );
tr_torrentSetChecked( tor, 0 );
while( !*stopFlag && ( pieceIndex < tor->info.pieceCount ) )
{
uint32_t leftInPiece;
@ -75,17 +66,13 @@ verifyTorrent( tr_torrent * tor, tr_bool * stopFlag )
/* if we're starting a new piece... */
if( piecePos == 0 )
{
hadPiece = tr_cpPieceIsComplete( &tor->completion, pieceIndex );
/* fprintf( stderr, "starting piece %d of %d\n", (int)pieceIndex, (int)tor->info.pieceCount ); */
}
/* if we're starting a new file... */
if( !filePos && (fd<0) && (fileIndex!=prevFileIndex) )
{
char * filename = tr_torrentFindFile( tor, fileIndex );
fd = filename == NULL ? -1 : tr_open_file_for_scanning( filename );
/* fprintf( stderr, "opening file #%d (%s) -- %d\n", fileIndex, filename, fd ); */
tr_free( filename );
prevFileIndex = fileIndex;
}
@ -95,7 +82,6 @@ verifyTorrent( tr_torrent * tor, tr_bool * stopFlag )
leftInFile = file->length - filePos;
bytesThisPass = MIN( leftInFile, leftInPiece );
bytesThisPass = MIN( bytesThisPass, buflen );
/* fprintf( stderr, "reading this pass: %d\n", (int)bytesThisPass ); */
/* read a bit */
if( fd >= 0 ) {
@ -124,17 +110,12 @@ verifyTorrent( tr_torrent * tor, tr_bool * stopFlag )
SHA1_Final( hash, &sha );
hasPiece = !memcmp( hash, tor->info.pieces[pieceIndex].hash, SHA_DIGEST_LENGTH );
/* fprintf( stderr, "do the hashes match? %s\n", (hasPiece?"yes":"no") ); */
if( hasPiece ) {
tr_torrentSetHasPiece( tor, pieceIndex, TRUE );
if( !hadPiece )
changed = TRUE;
} else if( hadPiece ) {
tr_torrentSetHasPiece( tor, pieceIndex, FALSE );
changed = TRUE;
if( hasPiece || hadPiece ) {
tr_torrentSetHasPiece( tor, pieceIndex, hasPiece );
changed |= hasPiece != hadPiece;
}
tr_torrentSetPieceChecked( tor, pieceIndex, TRUE );
tr_torrentSetPieceChecked( tor, pieceIndex );
now = tr_time( );
tor->anyDate = now;
@ -153,12 +134,12 @@ verifyTorrent( tr_torrent * tor, tr_bool * stopFlag )
/* if we're finishing a file... */
if( leftInFile == 0 )
{
/* fprintf( stderr, "closing file\n" ); */
if( fd >= 0 ) { tr_close_file( fd ); fd = -1; }
++fileIndex;
filePos = 0;
}
}
tr_tordbg( tor, "%s", "[LAZY] DONE verifying torrent..." );
/* cleanup */
if( fd >= 0 )
@ -168,7 +149,8 @@ verifyTorrent( tr_torrent * tor, tr_bool * stopFlag )
/* stopwatch */
end = tr_time( );
tr_tordbg( tor, "it took %d seconds to verify %"PRIu64" bytes (%"PRIu64" bytes per second)",
(int)(end-begin), tor->info.totalSize, (uint64_t)(tor->info.totalSize/(1+(end-begin))) );
(int)(end-begin), tor->info.totalSize,
(uint64_t)(tor->info.totalSize/(1+(end-begin))) );
return changed;
}
@ -202,7 +184,6 @@ static tr_lock*
getVerifyLock( void )
{
static tr_lock * lock = NULL;
if( lock == NULL )
lock = tr_lockNew( );
return lock;
@ -213,8 +194,8 @@ verifyThreadFunc( void * unused UNUSED )
{
for( ;; )
{
int changed = 0;
tr_torrent * tor;
int changed = 0;
tr_torrent * tor;
struct verify_node * node;
tr_lockLock( getVerifyLock( ) );
@ -250,28 +231,6 @@ verifyThreadFunc( void * unused UNUSED )
tr_lockUnlock( getVerifyLock( ) );
}
static uint64_t
getCurrentSize( tr_torrent * tor )
{
tr_file_index_t i;
uint64_t byte_count = 0;
const tr_file_index_t n = tor->info.fileCount;
for( i=0; i<n; ++i )
{
struct stat sb;
char * filename = tr_torrentFindFile( tor, i );
sb.st_size = 0;
if( filename && !stat( filename, &sb ) )
byte_count += sb.st_size;
tr_free( filename );
}
return byte_count;
}
static int
compareVerifyByPriorityAndSize( const void * va, const void * vb )
{
@ -279,67 +238,36 @@ compareVerifyByPriorityAndSize( const void * va, const void * vb )
const struct verify_node * b = vb;
/* higher priority comes before lower priority */
const tr_priority_t pa = tr_torrentGetPriority( a->torrent );
const tr_priority_t pb = tr_torrentGetPriority( b->torrent );
const tr_priority_t pa = tr_torrentGetPriority( a->torrent );
const tr_priority_t pb = tr_torrentGetPriority( b->torrent );
if( pa != pb )
return pa > pb ? -1 : 1;
/* smaller size comes before larger size... smaller sizes are faster to verify */
/* smaller torrents come before larger ones because they verify faster */
if( a->current_size < b->current_size ) return -1;
if( a->current_size > b->current_size ) return 1;
return 0;
}
void
tr_verifyAdd( tr_torrent * tor,
tr_verify_done_cb verify_done_cb )
tr_verifyAdd( tr_torrent * tor, tr_verify_done_cb verify_done_cb )
{
struct verify_node * node;
assert( tr_isTorrent( tor ) );
tr_torinf( tor, "%s", _( "Queued for verification" ) );
if( tr_torrentCountUncheckedPieces( tor ) == 0 )
{
/* doesn't need to be checked... */
fireCheckDone( tor, verify_done_cb );
}
else
{
const uint64_t current_size = getCurrentSize( tor );
node = tr_new( struct verify_node, 1 );
node->torrent = tor;
node->verify_done_cb = verify_done_cb;
node->current_size = tr_torrentGetCurrentSizeOnDisk( tor );
if( !current_size )
{
/* we haven't downloaded anything for this torrent yet...
* no need to leave it waiting in the back of the queue.
* we can mark it as all-missing from here and fire
* the "done" callback */
const tr_bool hadAny = tr_cpHaveTotal( &tor->completion ) != 0;
tr_piece_index_t i;
for( i=0; i<tor->info.pieceCount; ++i ) {
tr_torrentSetHasPiece( tor, i, FALSE );
tr_torrentSetPieceChecked( tor, i, TRUE );
}
if( hadAny ) /* if we thought we had some, flag as dirty */
tr_torrentSetDirty( tor );
fireCheckDone( tor, verify_done_cb );
}
else
{
struct verify_node * node;
tr_torinf( tor, "%s", _( "Queued for verification" ) );
node = tr_new( struct verify_node, 1 );
node->torrent = tor;
node->verify_done_cb = verify_done_cb;
node->current_size = current_size;
tr_lockLock( getVerifyLock( ) );
tr_torrentSetVerifyState( tor, TR_VERIFY_WAIT );
tr_list_insert_sorted( &verifyList, node, compareVerifyByPriorityAndSize );
if( verifyThread == NULL )
verifyThread = tr_threadNew( verifyThreadFunc, NULL );
tr_lockUnlock( getVerifyLock( ) );
}
}
tr_lockLock( getVerifyLock( ) );
tr_torrentSetVerifyState( tor, TR_VERIFY_WAIT );
tr_list_insert_sorted( &verifyList, node, compareVerifyByPriorityAndSize );
if( verifyThread == NULL )
verifyThread = tr_threadNew( verifyThreadFunc, NULL );
tr_lockUnlock( getVerifyLock( ) );
}
static int