source: trunk/libtransmission/torrent-magnet.c @ 12918

Last change on this file since 12918 was 12589, checked in by jordan, 10 years ago

undo r12585, where I checked in the wrong file.

  • Property svn:keywords set to Date Rev Author Id
File size: 11.0 KB
Line 
1/*
2 * This file Copyright (C) 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: torrent-magnet.c 12589 2011-07-25 22:30:56Z jordan $
11 */
12
13#include <assert.h>
14#include <stdio.h> /* remove() */
15#include <string.h> /* memcpy(), memset(), memcmp() */
16
17#include <event2/buffer.h>
18
19#include "transmission.h"
20#include "bencode.h"
21#include "crypto.h" /* tr_sha1() */
22#include "magnet.h"
23#include "metainfo.h"
24#include "resume.h"
25#include "torrent.h"
26#include "torrent-magnet.h"
27#include "utils.h"
28#include "web.h"
29
30#define dbgmsg( tor, ... ) \
31    do { \
32        if( tr_deepLoggingIsActive( ) ) \
33            tr_deepLog( __FILE__, __LINE__, tor->info.name, __VA_ARGS__ ); \
34    } while( 0 )
35
36/***
37****
38***/
39
40enum
41{
42    /* don't ask for the same metadata piece more than this often */
43    MIN_REPEAT_INTERVAL_SECS = 3
44};
45
46struct metadata_node
47{
48    time_t requestedAt;
49    int piece;
50};
51
52struct tr_incomplete_metadata
53{
54    uint8_t * metadata;
55    int metadata_size;
56    int pieceCount;
57
58    /** sorted from least to most recently requested */
59    struct metadata_node * piecesNeeded;
60    int piecesNeededCount;
61};
62
63static void
64incompleteMetadataFree( struct tr_incomplete_metadata * m )
65{
66    tr_free( m->metadata );
67    tr_free( m->piecesNeeded );
68    tr_free( m );
69}
70
71void
72tr_torrentSetMetadataSizeHint( tr_torrent * tor, int size )
73{
74    if( !tr_torrentHasMetadata( tor ) )
75    {
76        if( tor->incompleteMetadata == NULL )
77        {
78            int i;
79            struct tr_incomplete_metadata * m;
80            const int n = ( size + ( METADATA_PIECE_SIZE - 1 ) ) / METADATA_PIECE_SIZE;
81            dbgmsg( tor, "metadata is %d bytes in %d pieces", size, n );
82
83            m = tr_new( struct tr_incomplete_metadata, 1 );
84            m->pieceCount = n;
85            m->metadata = tr_new( uint8_t, size );
86            m->metadata_size = size;
87            m->piecesNeededCount = n;
88            m->piecesNeeded = tr_new( struct metadata_node, n );
89
90            for( i=0; i<n; ++i ) {
91                m->piecesNeeded[i].piece = i;
92                m->piecesNeeded[i].requestedAt = 0;
93            }
94
95            tor->incompleteMetadata = m;
96        }
97    }
98}
99
100static int
101findInfoDictOffset( const tr_torrent * tor )
102{
103    size_t fileLen;
104    uint8_t * fileContents;
105    int offset = 0;
106
107    /* load the file, and find the info dict's offset inside the file */
108    if(( fileContents = tr_loadFile( tor->info.torrent, &fileLen )))
109    {
110        tr_benc top;
111
112        if( !tr_bencParse( fileContents, fileContents + fileLen, &top, NULL ) )
113        {
114            tr_benc * infoDict;
115
116            if( tr_bencDictFindDict( &top, "info", &infoDict ) )
117            {
118                int infoLen;
119                char * infoContents = tr_bencToStr( infoDict, TR_FMT_BENC, &infoLen );
120                const uint8_t * i = (const uint8_t*) tr_memmem( (char*)fileContents, fileLen, infoContents, infoLen );
121                offset = i != NULL ? i - fileContents : 0;
122                tr_free( infoContents );
123            }
124
125            tr_bencFree( &top );
126        }
127
128        tr_free( fileContents );
129    }
130
131    return offset;
132}
133
134static void
135ensureInfoDictOffsetIsCached( tr_torrent * tor )
136{
137    assert( tr_torrentHasMetadata( tor ) );
138
139    if( !tor->infoDictOffsetIsCached )
140    {
141        tor->infoDictOffset = findInfoDictOffset( tor );
142        tor->infoDictOffsetIsCached = true;
143    }
144}
145
146void*
147tr_torrentGetMetadataPiece( tr_torrent * tor, int piece, int * len )
148{
149    char * ret = NULL;
150
151    assert( tr_isTorrent( tor ) );
152    assert( piece >= 0 );
153    assert( len != NULL );
154
155    if( tr_torrentHasMetadata( tor ) )
156    {
157        FILE * fp;
158
159        ensureInfoDictOffsetIsCached( tor );
160
161        assert( tor->infoDictLength > 0 );
162        assert( tor->infoDictOffset >= 0 );
163
164        fp = fopen( tor->info.torrent, "rb" );
165        if( fp != NULL )
166        {
167            const int o = piece  * METADATA_PIECE_SIZE;
168
169            if( !fseek( fp, tor->infoDictOffset + o, SEEK_SET ) )
170            {
171                const int l = o + METADATA_PIECE_SIZE <= tor->infoDictLength
172                            ? METADATA_PIECE_SIZE
173                            : tor->infoDictLength - o;
174
175                if( 0<l && l<=METADATA_PIECE_SIZE )
176                {
177                    char * buf = tr_new( char, l );
178                    const int n = fread( buf, 1, l, fp );
179                    if( n == l )
180                    {
181                        *len = l;
182                        ret = buf;
183                        buf = NULL;
184                    }
185
186                    tr_free( buf );
187                }
188            }
189
190            fclose( fp );
191        }
192    }
193
194    return ret;
195}
196
197void
198tr_torrentSetMetadataPiece( tr_torrent  * tor, int piece, const void  * data, int len )
199{
200    int i;
201    struct tr_incomplete_metadata * m;
202    const int offset = piece * METADATA_PIECE_SIZE;
203
204    assert( tr_isTorrent( tor ) );
205
206    dbgmsg( tor, "got metadata piece %d", piece );
207
208    /* are we set up to download metadata? */
209    m = tor->incompleteMetadata;
210    if( m == NULL )
211        return;
212
213    /* does this data pass the smell test? */
214    if( offset + len > m->metadata_size )
215        return;
216
217    /* do we need this piece? */
218    for( i=0; i<m->piecesNeededCount; ++i )
219        if( m->piecesNeeded[i].piece == piece )
220            break;
221    if( i==m->piecesNeededCount )
222        return;
223
224    memcpy( m->metadata + offset, data, len );
225
226    tr_removeElementFromArray( m->piecesNeeded, i,
227                               sizeof( struct metadata_node ),
228                               m->piecesNeededCount-- );
229
230    dbgmsg( tor, "saving metainfo piece %d... %d remain", piece, m->piecesNeededCount );
231
232    /* are we done? */
233    if( m->piecesNeededCount == 0 )
234    {
235        bool success = false;
236        bool checksumPassed = false;
237        bool metainfoParsed = false;
238        uint8_t sha1[SHA_DIGEST_LENGTH];
239
240        /* we've got a complete set of metainfo... see if it passes the checksum test */
241        dbgmsg( tor, "metainfo piece %d was the last one", piece );
242        tr_sha1( sha1, m->metadata, m->metadata_size, NULL );
243        if(( checksumPassed = !memcmp( sha1, tor->info.hash, SHA_DIGEST_LENGTH )))
244        {
245            /* checksum passed; now try to parse it as benc */
246            tr_benc infoDict;
247            const int err = tr_bencLoad( m->metadata, m->metadata_size, &infoDict, NULL );
248            dbgmsg( tor, "err is %d", err );
249            if(( metainfoParsed = !err ))
250            {
251                /* yay we have bencoded metainfo... merge it into our .torrent file */
252                tr_benc newMetainfo;
253                char * path = tr_strdup( tor->info.torrent );
254
255                if( !tr_bencLoadFile( &newMetainfo, TR_FMT_BENC, path ) )
256                {
257                    bool hasInfo;
258                    tr_info info;
259                    int infoDictLength;
260
261                    /* remove any old .torrent and .resume files */
262                    remove( path );
263                    tr_torrentRemoveResume( tor );
264
265                    dbgmsg( tor, "Saving completed metadata to \"%s\"", path );
266                    tr_bencMergeDicts( tr_bencDictAddDict( &newMetainfo, "info", 0 ), &infoDict );
267
268                    memset( &info, 0, sizeof( tr_info ) );
269                    success = tr_metainfoParse( tor->session, &newMetainfo, &info, &hasInfo, &infoDictLength );
270
271                    if( success && !tr_getBlockSize( info.pieceSize ) )
272                    {
273                        tr_torrentSetLocalError( tor, "%s", _( "Magnet torrent's metadata is not usable" ) );
274                        success = false;
275                    }
276
277                    if( success )
278                    {
279                        /* keep the new info */
280                        tor->info = info;
281                        tor->infoDictLength = infoDictLength;
282
283                        /* save the new .torrent file */
284                        tr_bencToFile( &newMetainfo, TR_FMT_BENC, tor->info.torrent );
285                        tr_sessionSetTorrentFile( tor->session, tor->info.hashString, tor->info.torrent );
286                        tr_torrentGotNewInfoDict( tor );
287                        tr_torrentSetDirty( tor );
288                    }
289
290                    tr_bencFree( &newMetainfo );
291                }
292
293                tr_bencFree( &infoDict );
294                tr_free( path );
295            }
296        }
297
298        if( success )
299        {
300            incompleteMetadataFree( tor->incompleteMetadata );
301            tor->incompleteMetadata = NULL;
302        }
303        else /* drat. */
304        {
305            const int n = m->pieceCount;
306            for( i=0; i<n; ++i )
307            {
308                m->piecesNeeded[i].piece = i;
309                m->piecesNeeded[i].requestedAt = 0;
310            }
311            m->piecesNeededCount = n;
312            dbgmsg( tor, "metadata error; trying again. %d pieces left", n );
313
314            tr_err( "magnet status: checksum passed %d, metainfo parsed %d",
315                    (int)checksumPassed, (int)metainfoParsed );
316        }
317    }
318}
319
320bool
321tr_torrentGetNextMetadataRequest( tr_torrent * tor, time_t now, int * setme_piece )
322{
323    bool have_request = false;
324    struct tr_incomplete_metadata * m;
325
326    assert( tr_isTorrent( tor ) );
327
328    m = tor->incompleteMetadata;
329
330    if( ( m != NULL )
331        && ( m->piecesNeededCount > 0 )
332        && ( m->piecesNeeded[0].requestedAt + MIN_REPEAT_INTERVAL_SECS < now ) )
333    {
334        int i;
335        const int piece = m->piecesNeeded[0].piece;
336
337        tr_removeElementFromArray( m->piecesNeeded, 0,
338                                   sizeof( struct metadata_node ),
339                                   m->piecesNeededCount-- );
340
341        i = m->piecesNeededCount++;
342        m->piecesNeeded[i].piece = piece;
343        m->piecesNeeded[i].requestedAt = now;
344
345        dbgmsg( tor, "next piece to request: %d", piece );
346        *setme_piece = piece;
347        have_request = true;
348    }
349
350    return have_request;
351}
352
353double
354tr_torrentGetMetadataPercent( const tr_torrent * tor )
355{
356    double ret;
357
358    if( tr_torrentHasMetadata( tor ) )
359        ret = 1.0;
360    else {
361        const struct tr_incomplete_metadata * m = tor->incompleteMetadata;
362        if( !m || !m->pieceCount )
363            ret = 0.0;
364        else
365            ret = (m->pieceCount - m->piecesNeededCount) / (double)m->pieceCount;
366    }
367
368    return ret;
369}
370
371char*
372tr_torrentGetMagnetLink( const tr_torrent * tor )
373{
374    int i;
375    const char * name;
376    struct evbuffer * s;
377
378    assert( tr_isTorrent( tor ) );
379
380    s = evbuffer_new( );
381    evbuffer_add_printf( s, "magnet:?xt=urn:btih:%s", tor->info.hashString );
382    name = tr_torrentName( tor );
383    if( name && *name )
384    {
385        evbuffer_add_printf( s, "%s", "&dn=" );
386        tr_http_escape( s, tr_torrentName( tor ), -1, true );
387    }
388    for( i=0; i<tor->info.trackerCount; ++i )
389    {
390        evbuffer_add_printf( s, "%s", "&tr=" );
391        tr_http_escape( s, tor->info.trackers[i].announce, -1, true );
392    }
393
394    return evbuffer_free_to_str( s );
395}
Note: See TracBrowser for help on using the repository browser.