/* * This file Copyright (C) 2007-2010 Mnemosyne LLC * * This file is licensed by the GPL version 2. Works owned by the * Transmission project are granted a special exemption to clause 2(b) * so that the bulk of its code can remain under the MIT license. * This exemption does not extend to derived works not owned by * the Transmission project. * * $Id$ */ #include /* S_ISREG */ #include #ifdef HAVE_POSIX_FADVISE #define _XOPEN_SOURCE 600 #endif #if defined(HAVE_POSIX_FADVISE) || defined(SYS_DARWIN) #include /* posix_fadvise() / fcntl() */ #endif #include #include "transmission.h" #include "completion.h" #include "fdlimit.h" #include "inout.h" #include "list.h" #include "platform.h" #include "torrent.h" #include "utils.h" /* tr_buildPath */ #include "verify.h" /*** **** ***/ enum { MSEC_TO_SLEEP_PER_SECOND_DURING_VERIFY = 100 }; /* #define STOPWATCH */ static tr_bool verifyTorrent( tr_torrent * tor, tr_bool * stopFlag ) { SHA_CTX sha; int fd = -1; int64_t filePos = 0; tr_bool changed = 0; tr_bool hadPiece = 0; time_t lastSleptAt = 0; uint32_t piecePos = 0; tr_file_index_t fileIndex = 0; 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 ); while( !*stopFlag && ( pieceIndex < tor->info.pieceCount ) ) { uint32_t leftInPiece; uint32_t bytesThisPass; uint64_t leftInFile; const tr_file * file = &tor->info.files[fileIndex]; /* 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; } /* figure out how much we can read this pass */ leftInPiece = tr_torPieceCountBytes( tor, pieceIndex ) - piecePos; 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 ) { const ssize_t numRead = tr_pread( fd, buffer, bytesThisPass, filePos ); if( numRead > 0 ) { bytesThisPass = (uint32_t)numRead; SHA1_Update( &sha, buffer, bytesThisPass ); #if defined HAVE_POSIX_FADVISE && defined POSIX_FADV_DONTNEED posix_fadvise( fd, filePos, bytesThisPass, POSIX_FADV_DONTNEED ); #endif } } /* move our offsets */ leftInPiece -= bytesThisPass; leftInFile -= bytesThisPass; piecePos += bytesThisPass; filePos += bytesThisPass; /* if we're finishing a piece... */ if( leftInPiece == 0 ) { time_t now; tr_bool hasPiece; uint8_t hash[SHA_DIGEST_LENGTH]; 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; } tr_torrentSetPieceChecked( tor, pieceIndex, TRUE ); now = tr_time( ); tor->anyDate = now; /* sleeping even just a few msec per second goes a long * way towards reducing IO load... */ if( lastSleptAt != now ) { lastSleptAt = now; tr_wait_msec( MSEC_TO_SLEEP_PER_SECOND_DURING_VERIFY ); } SHA1_Init( &sha ); ++pieceIndex; piecePos = 0; } /* 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; } } /* cleanup */ if( fd >= 0 ) tr_close_file( fd ); free( buffer ); /* 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))) ); return changed; } /*** **** ***/ struct verify_node { tr_torrent * torrent; tr_verify_done_cb verify_done_cb; uint64_t current_size; }; static void fireCheckDone( tr_torrent * tor, tr_verify_done_cb verify_done_cb ) { assert( tr_isTorrent( tor ) ); if( verify_done_cb ) verify_done_cb( tor ); } static struct verify_node currentNode; static tr_list * verifyList = NULL; static tr_thread * verifyThread = NULL; static tr_bool stopCurrent = FALSE; static tr_lock* getVerifyLock( void ) { static tr_lock * lock = NULL; if( lock == NULL ) lock = tr_lockNew( ); return lock; } static void verifyThreadFunc( void * unused UNUSED ) { for( ;; ) { int changed = 0; tr_torrent * tor; struct verify_node * node; tr_lockLock( getVerifyLock( ) ); stopCurrent = FALSE; node = (struct verify_node*) verifyList ? verifyList->data : NULL; if( node == NULL ) { currentNode.torrent = NULL; break; } currentNode = *node; tor = currentNode.torrent; tr_list_remove_data( &verifyList, node ); tr_free( node ); tr_lockUnlock( getVerifyLock( ) ); tr_torinf( tor, "%s", _( "Verifying torrent" ) ); tr_torrentSetVerifyState( tor, TR_VERIFY_NOW ); changed = verifyTorrent( tor, &stopCurrent ); tr_torrentSetVerifyState( tor, TR_VERIFY_NONE ); assert( tr_isTorrent( tor ) ); if( !stopCurrent ) { if( changed ) tr_torrentSetDirty( tor ); fireCheckDone( tor, currentNode.verify_done_cb ); } } verifyThread = NULL; 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; itorrent ); 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 */ 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 ) { assert( tr_isTorrent( tor ) ); if( tr_torrentCountUncheckedPieces( tor ) == 0 ) { /* doesn't need to be checked... */ fireCheckDone( tor, verify_done_cb ); } else { const uint64_t current_size = getCurrentSize( 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; iinfo.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( ) ); } } } static int compareVerifyByTorrent( const void * va, const void * vb ) { const struct verify_node * a = va; const tr_torrent * b = vb; return a->torrent - b; } void tr_verifyRemove( tr_torrent * tor ) { tr_lock * lock = getVerifyLock( ); tr_lockLock( lock ); assert( tr_isTorrent( tor ) ); if( tor == currentNode.torrent ) { stopCurrent = TRUE; while( stopCurrent ) { tr_lockUnlock( lock ); tr_wait_msec( 100 ); tr_lockLock( lock ); } } else { tr_free( tr_list_remove( &verifyList, tor, compareVerifyByTorrent ) ); tr_torrentSetVerifyState( tor, TR_VERIFY_NONE ); } tr_lockUnlock( lock ); } void tr_verifyClose( tr_session * session UNUSED ) { tr_lockLock( getVerifyLock( ) ); stopCurrent = TRUE; tr_list_free( &verifyList, tr_free ); tr_lockUnlock( getVerifyLock( ) ); }