1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-25 09:13:06 +00:00
transmission/libtransmission/verify.c

319 lines
8.4 KiB
C

/*
* This file Copyright (C) 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 <string.h> /* memcmp() */
#include <stdlib.h> /* free() */
#ifdef HAVE_POSIX_FADVISE
#define _XOPEN_SOURCE 600
#include <fcntl.h> /* posix_fadvise() */
#endif
#include <openssl/sha.h>
#include "transmission.h"
#include "completion.h"
#include "fdlimit.h"
#include "list.h"
#include "platform.h" /* tr_lock() */
#include "torrent.h"
#include "utils.h" /* tr_valloc(), tr_free() */
#include "verify.h"
/***
****
***/
enum
{
MSEC_TO_SLEEP_PER_SECOND_DURING_VERIFY = 100
};
static bool
verifyTorrent( tr_torrent * tor, bool * stopFlag )
{
time_t end;
SHA_CTX sha;
int fd = -1;
int64_t filePos = 0;
bool changed = 0;
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( );
const size_t buflen = 1024 * 128; /* 128 KiB buffer */
uint8_t * buffer = tr_valloc( buflen );
SHA1_Init( &sha );
tr_tordbg( tor, "%s", "verifying torrent..." );
tr_torrentSetChecked( tor, 0 );
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 );
/* 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 );
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 );
/* 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;
bool hasPiece;
uint8_t hash[SHA_DIGEST_LENGTH];
SHA1_Final( hash, &sha );
hasPiece = !memcmp( hash, tor->info.pieces[pieceIndex].hash, SHA_DIGEST_LENGTH );
if( hasPiece || hadPiece ) {
tr_torrentSetHasPiece( tor, pieceIndex, hasPiece );
changed |= hasPiece != hadPiece;
}
tr_torrentSetPieceChecked( tor, pieceIndex );
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 )
{
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, "Verification is done. 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 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 int
compareVerifyByPriorityAndSize( const void * va, const void * vb )
{
const struct verify_node * a = va;
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 );
if( pa != pb )
return pa > pb ? -1 : 1;
/* 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 )
{
struct verify_node * node;
assert( tr_isTorrent( tor ) );
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 = tr_torrentGetCurrentSizeOnDisk( tor );
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( ) );
}