source: trunk/libtransmission/verify.c @ 10191

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

(trunk libT) optimize out some unnecessary cycles when there are missing files in a torrent being verified

  • Property svn:keywords set to Date Rev Author Id
File size: 10.2 KB
Line 
1/*
2 * This file Copyright (C) 2007-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: verify.c 10191 2010-02-13 05:46:31Z charles $
11 */
12
13#include <unistd.h> /* S_ISREG */
14#include <sys/stat.h>
15
16#ifdef HAVE_POSIX_FADVISE
17 #define _XOPEN_SOURCE 600
18#endif
19#if defined(HAVE_POSIX_FADVISE) || defined(SYS_DARWIN)
20 #include <fcntl.h> /* posix_fadvise() / fcntl() */
21#endif
22#if defined(SYS_DARWIN)
23 #define HAVE_VALLOC
24#endif
25
26#include <openssl/sha.h>
27
28#include "transmission.h"
29#include "completion.h"
30#include "fdlimit.h"
31#include "inout.h"
32#include "list.h"
33#include "platform.h"
34#include "torrent.h"
35#include "utils.h" /* tr_buildPath */
36#include "verify.h"
37
38
39/***
40****
41***/
42
43enum
44{
45    MSEC_TO_SLEEP_PER_SECOND_DURING_VERIFY = 200
46};
47
48/* #define STOPWATCH */
49
50#ifndef HAVE_VALLOC
51 #define valloc malloc
52#endif
53
54static tr_bool
55verifyTorrent( tr_torrent * tor, tr_bool * stopFlag )
56{
57    SHA_CTX sha;
58    int fd = -1;
59    int64_t filePos = 0;
60    tr_bool changed = 0;
61    tr_bool hadPiece = 0;
62    time_t lastSleptAt = 0;
63    uint32_t piecePos = 0;
64    uint32_t pieceBytesRead = 0;
65    tr_file_index_t fileIndex = 0;
66    tr_file_index_t prevFileIndex = !fileIndex;
67    tr_piece_index_t pieceIndex = 0;
68    const int64_t buflen = 4096;
69    uint8_t * buffer = valloc( buflen );
70    const time_t begin = tr_time( );
71    time_t end;
72
73    SHA1_Init( &sha );
74
75    while( !*stopFlag && ( pieceIndex < tor->info.pieceCount ) )
76    {
77        int64_t leftInPiece;
78        int64_t leftInFile;
79        int64_t bytesThisPass;
80        const tr_file * file = &tor->info.files[fileIndex];
81
82        /* if we're starting a new piece... */
83        if( piecePos == 0 )
84        {
85            hadPiece = tr_cpPieceIsComplete( &tor->completion, pieceIndex );
86            /* fprintf( stderr, "starting piece %d of %d\n", (int)pieceIndex, (int)tor->info.pieceCount ); */
87        }
88
89        /* if we're starting a new file... */
90        if( !filePos && (fd<0) && (fileIndex!=prevFileIndex) )
91        {
92            char * filename = tr_torrentFindFile( tor, fileIndex );
93            fd = filename == NULL ? -1 : tr_open_file_for_scanning( filename );
94            /* fprintf( stderr, "opening file #%d (%s) -- %d\n", fileIndex, filename, fd ); */
95            tr_free( filename );
96            prevFileIndex = fileIndex;
97        }
98
99        /* figure out how much we can read this pass */
100        leftInPiece = tr_torPieceCountBytes( tor, pieceIndex ) - piecePos;
101        leftInFile = file->length - filePos;
102        bytesThisPass = MIN( leftInFile, leftInPiece );
103        bytesThisPass = MIN( bytesThisPass, buflen );
104        /* fprintf( stderr, "reading this pass: %d\n", (int)bytesThisPass ); */
105
106        /* read a bit */
107        if( fd >= 0 ) {
108            const ssize_t numRead = tr_pread( fd, buffer, bytesThisPass, filePos );
109            if( numRead == bytesThisPass )
110                SHA1_Update( &sha, buffer, numRead );
111            if( numRead > 0 ) {
112                pieceBytesRead += numRead;
113#if defined HAVE_POSIX_FADVISE && defined POSIX_FADV_DONTNEED
114                posix_fadvise( fd, filePos, bytesThisPass, POSIX_FADV_DONTNEED );
115#endif
116            }
117        }
118
119        /* move our offsets */
120        leftInPiece -= bytesThisPass;
121        leftInFile -= bytesThisPass;
122        piecePos += bytesThisPass;
123        filePos += bytesThisPass;
124
125        /* if we're finishing a piece... */
126        if( leftInPiece == 0 )
127        {
128            time_t now;
129            tr_bool hasPiece;
130            uint8_t hash[SHA_DIGEST_LENGTH];
131
132            SHA1_Final( hash, &sha );
133            hasPiece = !memcmp( hash, tor->info.pieces[pieceIndex].hash, SHA_DIGEST_LENGTH );
134            /* fprintf( stderr, "do the hashes match? %s\n", (hasPiece?"yes":"no") ); */
135
136            if( hasPiece ) {
137                tr_torrentSetHasPiece( tor, pieceIndex, TRUE );
138                if( !hadPiece )
139                    changed = TRUE;
140            } else if( hadPiece ) {
141                tr_torrentSetHasPiece( tor, pieceIndex, FALSE );
142                changed = TRUE;
143            }
144            tr_torrentSetPieceChecked( tor, pieceIndex, TRUE );
145            now = tr_time( );
146            tor->anyDate = now;
147
148            /* sleeping even just a few msec per second goes a long
149             * way towards reducing IO load... */
150            if( lastSleptAt != now ) {
151                lastSleptAt = now;
152                tr_wait_msec( MSEC_TO_SLEEP_PER_SECOND_DURING_VERIFY );
153            }
154
155            SHA1_Init( &sha );
156            ++pieceIndex;
157            piecePos = 0;
158            pieceBytesRead = 0;
159        }
160
161        /* if we're finishing a file... */
162        if( leftInFile == 0 )
163        {
164            /* fprintf( stderr, "closing file\n" ); */
165            if( fd >= 0 ) { tr_close_file( fd ); fd = -1; }
166            ++fileIndex;
167            filePos = 0;
168        }
169    }
170
171    /* cleanup */
172    if( fd >= 0 )
173        tr_close_file( fd );
174    free( buffer );
175
176    /* stopwatch */
177    end = tr_time( );
178    tr_tordbg( tor, "it took %d seconds to verify %"PRIu64" bytes (%"PRIu64" bytes per second)",
179               (int)(end-begin), tor->info.totalSize, (uint64_t)(tor->info.totalSize/(1+(end-begin))) );
180
181    return changed;
182}
183
184/***
185****
186***/
187
188struct verify_node
189{
190    tr_torrent *         torrent;
191    tr_verify_done_cb    verify_done_cb;
192};
193
194static void
195fireCheckDone( tr_torrent * tor, tr_verify_done_cb verify_done_cb )
196{
197    assert( tr_isTorrent( tor ) );
198
199    if( verify_done_cb )
200        verify_done_cb( tor );
201}
202
203static struct verify_node currentNode;
204static tr_list * verifyList = NULL;
205static tr_thread * verifyThread = NULL;
206static tr_bool stopCurrent = FALSE;
207
208static tr_lock*
209getVerifyLock( void )
210{
211    static tr_lock * lock = NULL;
212
213    if( lock == NULL )
214        lock = tr_lockNew( );
215    return lock;
216}
217
218static void
219verifyThreadFunc( void * unused UNUSED )
220{
221    for( ;; )
222    {
223        int                  changed = 0;
224        tr_torrent         * tor;
225        struct verify_node * node;
226
227        tr_lockLock( getVerifyLock( ) );
228        stopCurrent = FALSE;
229        node = (struct verify_node*) verifyList ? verifyList->data : NULL;
230        if( node == NULL )
231        {
232            currentNode.torrent = NULL;
233            break;
234        }
235
236        currentNode = *node;
237        tor = currentNode.torrent;
238        tr_list_remove_data( &verifyList, node );
239        tr_free( node );
240        tr_lockUnlock( getVerifyLock( ) );
241
242        tr_torinf( tor, "%s", _( "Verifying torrent" ) );
243        tr_torrentSetVerifyState( tor, TR_VERIFY_NOW );
244        changed = verifyTorrent( tor, &stopCurrent );
245        tr_torrentSetVerifyState( tor, TR_VERIFY_NONE );
246        assert( tr_isTorrent( tor ) );
247
248        if( !stopCurrent )
249        {
250            if( changed )
251                tr_torrentSetDirty( tor );
252            fireCheckDone( tor, currentNode.verify_done_cb );
253        }
254    }
255
256    verifyThread = NULL;
257    tr_lockUnlock( getVerifyLock( ) );
258}
259
260static tr_bool
261torrentHasAnyLocalData( const tr_torrent * tor )
262{
263    tr_file_index_t i;
264    tr_bool hasAny = FALSE;
265    const tr_file_index_t n = tor->info.fileCount;
266
267    assert( tr_isTorrent( tor ) );
268
269    for( i=0; i<n && !hasAny; ++i )
270    {
271        struct stat sb;
272        char * path = tr_torrentFindFile( tor, i );
273        if( ( path != NULL ) && !stat( path, &sb ) && ( sb.st_size > 0 ) )
274            hasAny = TRUE;
275        tr_free( path );
276    }
277
278    return hasAny;
279}
280
281void
282tr_verifyAdd( tr_torrent *      tor,
283              tr_verify_done_cb verify_done_cb )
284{
285    assert( tr_isTorrent( tor ) );
286
287    if( tr_torrentCountUncheckedPieces( tor ) == 0 )
288    {
289        /* doesn't need to be checked... */
290        fireCheckDone( tor, verify_done_cb );
291    }
292    else if( !torrentHasAnyLocalData( tor ) )
293    {
294        /* we haven't downloaded anything for this torrent yet...
295         * no need to leave it waiting in the back of the queue.
296         * we can mark it as all-missing from here and fire
297         * the "done" callback */
298        const tr_bool hadAny = tr_cpHaveTotal( &tor->completion ) != 0;
299        tr_piece_index_t i;
300        for( i=0; i<tor->info.pieceCount; ++i ) {
301            tr_torrentSetHasPiece( tor, i, FALSE );
302            tr_torrentSetPieceChecked( tor, i, TRUE );
303        }
304        if( hadAny ) /* if we thought we had some, flag as dirty */
305            tr_torrentSetDirty( tor );
306        fireCheckDone( tor, verify_done_cb );
307    }
308    else
309    {
310        struct verify_node * node;
311
312        tr_torinf( tor, "%s", _( "Queued for verification" ) );
313
314        node = tr_new( struct verify_node, 1 );
315        node->torrent = tor;
316        node->verify_done_cb = verify_done_cb;
317
318        tr_lockLock( getVerifyLock( ) );
319        tr_torrentSetVerifyState( tor, TR_VERIFY_WAIT );
320        tr_list_append( &verifyList, node );
321        if( verifyThread == NULL )
322            verifyThread = tr_threadNew( verifyThreadFunc, NULL );
323        tr_lockUnlock( getVerifyLock( ) );
324    }
325}
326
327static int
328compareVerifyByTorrent( const void * va,
329                        const void * vb )
330{
331    const struct verify_node * a = va;
332    const tr_torrent *         b = vb;
333
334    return a->torrent - b;
335}
336
337tr_bool
338tr_verifyInProgress( const tr_torrent * tor )
339{
340    tr_bool found = FALSE;
341    tr_lock * lock = getVerifyLock( );
342    tr_lockLock( lock );
343
344    assert( tr_isTorrent( tor ) );
345
346    found = ( tor == currentNode.torrent )
347         || ( tr_list_find( verifyList, tor, compareVerifyByTorrent ) != NULL );
348
349    tr_lockUnlock( lock );
350    return found;
351}
352
353void
354tr_verifyRemove( tr_torrent * tor )
355{
356    tr_lock * lock = getVerifyLock( );
357    tr_lockLock( lock );
358
359    assert( tr_isTorrent( tor ) );
360
361    if( tor == currentNode.torrent )
362    {
363        stopCurrent = TRUE;
364        while( stopCurrent )
365        {
366            tr_lockUnlock( lock );
367            tr_wait_msec( 100 );
368            tr_lockLock( lock );
369        }
370    }
371    else
372    {
373        tr_free( tr_list_remove( &verifyList, tor, compareVerifyByTorrent ) );
374        tr_torrentSetVerifyState( tor, TR_VERIFY_NONE );
375    }
376
377    tr_lockUnlock( lock );
378}
379
380void
381tr_verifyClose( tr_session * session UNUSED )
382{
383    tr_lockLock( getVerifyLock( ) );
384
385    stopCurrent = TRUE;
386    tr_list_free( &verifyList, tr_free );
387
388    tr_lockUnlock( getVerifyLock( ) );
389}
Note: See TracBrowser for help on using the repository browser.