source: trunk/libtransmission/makemeta.c @ 6612

Last change on this file since 6612 was 6612, checked in by charles, 13 years ago

bencode cleanup: remove unused functions and unnecessary #includes

  • Property svn:keywords set to Date Rev Author Id
File size: 14.1 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
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: makemeta.c 6612 2008-08-21 14:57:59Z charles $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <stdio.h> /* FILE, stderr */
16#include <stdlib.h> /* qsort */
17#include <string.h> /* strcmp, strlen */
18
19#include <sys/types.h>
20#include <sys/stat.h>
21#include <unistd.h>
22#include <libgen.h> /* dirname, basename */
23#include <dirent.h>
24
25#include "crypto.h" /* tr_sha1 */
26#include "transmission.h"
27#include "bencode.h"
28#include "makemeta.h"
29#include "platform.h" /* threads, locks */
30#include "utils.h" /* buildpath */
31#include "version.h"
32
33/****
34*****
35****/
36
37struct FileList
38{
39    struct FileList * next;
40    uint64_t size;
41    char * filename;
42};
43
44static struct FileList*
45getFiles( const char        * dir,
46          const char        * base,
47          struct FileList   * list )
48{
49    int i;
50    char buf[MAX_PATH_LENGTH];
51    struct stat sb;
52    DIR * odir = NULL;
53    sb.st_size = 0;
54
55    tr_buildPath( buf, sizeof(buf), dir, base, NULL );
56    i = stat( buf, &sb );
57    if( i ) {
58        tr_err( _( "Torrent Creator is skipping file \"%s\": %s" ), buf, tr_strerror(errno));
59        return list;
60    }
61
62    if ( S_ISDIR( sb.st_mode ) && (( odir = opendir ( buf ) )) )
63    {
64        struct dirent *d;
65        for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
66            if( d->d_name && d->d_name[0]!='.' ) /* skip dotfiles, ., and .. */
67                list = getFiles( buf, d->d_name, list );
68        closedir( odir );
69    }
70    else if( S_ISREG( sb.st_mode ) )
71    {
72        struct FileList * node = tr_new( struct FileList, 1 );
73        node->size = sb.st_size;
74        if( ( buf[0]=='.' ) && ( buf[1]=='/' ) )
75            node->filename = tr_strdup( buf + 2 );
76        else
77            node->filename = tr_strdup( buf );
78        node->next = list;
79        list = node;
80    }
81
82    return list;
83}
84
85static int
86bestPieceSize( uint64_t totalSize )
87{
88    static const uint64_t GiB = 1073741824;
89    static const uint64_t MiB = 1048576;
90    static const uint64_t KiB = 1024;
91
92    if( totalSize >=   (2*GiB) ) return (2*MiB);
93    if( totalSize >=   (1*GiB) ) return (1*MiB);
94    if( totalSize >= (512*MiB) ) return (512*KiB);
95    if( totalSize >= (350*MiB) ) return (256*KiB);
96    if( totalSize >= (150*MiB) ) return (128*KiB);
97    if( totalSize >=  (50*MiB) ) return (64*KiB);
98    return (32*KiB); /* less than 50 meg */
99}
100
101static int
102builderFileCompare ( const void * va, const void * vb)
103{
104    const tr_metainfo_builder_file * a = va;
105    const tr_metainfo_builder_file * b = vb;
106    return strcmp( a->filename, b->filename );
107}
108
109tr_metainfo_builder*
110tr_metaInfoBuilderCreate( tr_handle * handle, const char * topFile )
111{
112    int i;
113    struct FileList * files;
114    struct FileList * walk;
115    tr_metainfo_builder * ret = tr_new0( tr_metainfo_builder, 1 );
116    ret->top = tr_strdup( topFile );
117    ret->handle = handle; 
118    {
119        struct stat sb;
120        stat( topFile, &sb );
121        ret->isSingleFile = !S_ISDIR( sb.st_mode );
122    }
123
124    /* build a list of files containing topFile and,
125       if it's a directory, all of its children */
126    {
127        char *dir, *base;
128        char dirbuf[MAX_PATH_LENGTH];
129        char basebuf[MAX_PATH_LENGTH];
130        tr_strlcpy( dirbuf, topFile, sizeof( dirbuf ) );
131        tr_strlcpy( basebuf, topFile, sizeof( basebuf ) );
132        dir = dirname( dirbuf );
133        base = basename( basebuf );
134        files = getFiles( dir, base, NULL );
135    }
136
137    for( walk=files; walk!=NULL; walk=walk->next )
138        ++ret->fileCount;
139
140    ret->files = tr_new0( tr_metainfo_builder_file, ret->fileCount );
141
142    for( i=0, walk=files; walk!=NULL; ++i )
143    {
144        struct FileList * tmp = walk;
145        tr_metainfo_builder_file * file = &ret->files[i];
146        walk = walk->next;
147        file->filename = tmp->filename;
148        file->size = tmp->size;
149        ret->totalSize += tmp->size;
150        tr_free( tmp );
151    }
152
153    qsort( ret->files,
154           ret->fileCount,
155           sizeof(tr_metainfo_builder_file),
156           builderFileCompare );
157
158    ret->pieceSize = bestPieceSize( ret->totalSize );
159    ret->pieceCount = ret->pieceSize
160        ? (int)( ret->totalSize / ret->pieceSize)
161        : 0;
162    if( ret->totalSize % ret->pieceSize )
163        ++ret->pieceCount;
164
165    return ret;
166}
167
168void
169tr_metaInfoBuilderFree( tr_metainfo_builder * builder )
170{
171    if( builder )
172    {
173        tr_file_index_t t;
174        int i;
175        for( t=0; t<builder->fileCount; ++t )
176            tr_free( builder->files[t].filename );
177        tr_free( builder->files );
178        tr_free( builder->top );
179        tr_free( builder->comment );
180        for( i=0; i<builder->trackerCount; ++i )
181            tr_free( builder->trackers[i].announce );
182        tr_free( builder->trackers );
183        tr_free( builder->outputFile );
184        tr_free( builder );
185    }
186}
187
188/****
189*****
190****/
191
192static uint8_t*
193getHashInfo ( tr_metainfo_builder * b )
194{
195    uint32_t fileIndex = 0;
196    uint8_t *ret = tr_new0( uint8_t, SHA_DIGEST_LENGTH * b->pieceCount );
197    uint8_t *walk = ret;
198    uint8_t *buf;
199    uint64_t totalRemain;
200    uint64_t off = 0;
201    FILE * fp;
202
203    if( !b->totalSize )
204        return ret;
205
206    buf = tr_new( uint8_t, b->pieceSize );
207    b->pieceIndex = 0;
208    totalRemain = b->totalSize;
209    fp = fopen( b->files[fileIndex].filename, "rb" );
210    if( !fp ) {
211        b->my_errno = errno;
212        tr_snprintf( b->errfile, sizeof( b->errfile ), b->files[fileIndex].filename );
213        b->result = TR_MAKEMETA_IO_READ;
214        tr_free( buf );
215        tr_free( ret );
216        return NULL;
217    }
218    while ( totalRemain )
219    {
220        uint8_t *bufptr = buf;
221        const uint64_t thisPieceSize =
222            MIN( (uint32_t)b->pieceSize, totalRemain );
223        uint64_t pieceRemain = thisPieceSize;
224
225        assert( b->pieceIndex < b->pieceCount );
226
227        while( pieceRemain )
228        {
229            const uint64_t n_this_pass =
230                MIN( (b->files[fileIndex].size - off), pieceRemain );
231            fread( bufptr, 1, n_this_pass, fp );
232            bufptr += n_this_pass;
233            off += n_this_pass;
234            pieceRemain -= n_this_pass;
235            if( off == b->files[fileIndex].size ) {
236                off = 0;
237                fclose( fp );
238                fp = NULL;
239                if( ++fileIndex < b->fileCount ) {
240                    fp = fopen( b->files[fileIndex].filename, "rb" );
241                    if( !fp ) {
242                        b->my_errno = errno;
243                        tr_snprintf( b->errfile, sizeof( b->errfile ), b->files[fileIndex].filename );
244                        b->result = TR_MAKEMETA_IO_READ;
245                        tr_free( buf );
246                        tr_free( ret );
247                        return NULL;
248                    }
249                }
250            }
251        }
252
253        assert( bufptr-buf == (int)thisPieceSize );
254        assert( pieceRemain == 0 );
255        tr_sha1( walk, buf, thisPieceSize, NULL );
256        walk += SHA_DIGEST_LENGTH;
257
258        if( b->abortFlag ) {
259            b->result = TR_MAKEMETA_CANCELLED;
260            break;
261        }
262
263        totalRemain -= thisPieceSize;
264        ++b->pieceIndex;
265    }
266    assert( b->abortFlag || (walk-ret == (int)(SHA_DIGEST_LENGTH*b->pieceCount)) );
267    assert( b->abortFlag || !totalRemain );
268
269    if( fp )
270        fclose( fp );
271
272    tr_free( buf );
273    return ret;
274}
275
276static void
277getFileInfo( const char                      * topFile,
278             const tr_metainfo_builder_file  * file,
279             tr_benc                         * uninitialized_length,
280             tr_benc                         * uninitialized_path )
281{
282    const char *pch, *prev;
283    const size_t topLen = strlen(topFile) + 1; /* +1 for '/' */
284    int n;
285
286    /* get the file size */
287    tr_bencInitInt( uninitialized_length, file->size );
288
289    /* the path list */
290    n = 1;
291    for( pch=file->filename+topLen; *pch; ++pch )
292        if (*pch == TR_PATH_DELIMITER)
293            ++n;
294    tr_bencInitList( uninitialized_path, n );
295    for( prev=pch=file->filename+topLen; ; ++pch )
296    {
297        char buf[MAX_PATH_LENGTH];
298
299        if (*pch && *pch!=TR_PATH_DELIMITER )
300            continue;
301
302        memcpy( buf, prev, pch-prev );
303        buf[pch-prev] = '\0';
304
305        tr_bencListAddStr( uninitialized_path, buf );
306
307        prev = pch + 1;
308        if (!*pch)
309           break;
310    }
311}
312
313static void
314makeInfoDict ( tr_benc              * dict,
315               tr_metainfo_builder  * builder )
316{
317    uint8_t * pch;
318    char base[MAX_PATH_LENGTH];
319
320    tr_bencDictReserve( dict, 5 );
321   
322    if ( builder->isSingleFile )
323        tr_bencDictAddInt( dict, "length", builder->files[0].size );
324    else {
325        uint32_t i;
326        tr_benc * list = tr_bencDictAddList( dict, "files", builder->fileCount );
327        for( i=0; i<builder->fileCount; ++i ) {
328            tr_benc * dict = tr_bencListAddDict( list, 2 );
329            tr_benc * length = tr_bencDictAdd( dict, "length" );
330            tr_benc * pathVal = tr_bencDictAdd( dict, "path" );
331            getFileInfo( builder->top, &builder->files[i], length, pathVal );
332        }
333    }
334
335    tr_strlcpy( base, builder->top, sizeof( base ) );
336    tr_bencDictAddStr( dict, "name", basename( base ) );
337
338    tr_bencDictAddInt( dict, "piece length", builder->pieceSize );
339
340    if(( pch = getHashInfo( builder ))) {
341        tr_bencDictAddRaw( dict, "pieces", pch, SHA_DIGEST_LENGTH * builder->pieceCount );
342        tr_free( pch );
343    }
344
345    tr_bencDictAddInt( dict, "private", builder->isPrivate ? 1 : 0 );
346}
347
348static void
349tr_realMakeMetaInfo ( tr_metainfo_builder * builder )
350{
351    int i;
352    tr_benc top;
353
354    /* allow an empty set, but if URLs *are* listed, verify them. #814, #971 */
355    for( i=0; i<builder->trackerCount && !builder->result; ++i )
356        if( !tr_httpIsValidURL( builder->trackers[i].announce ) )
357            builder->result = TR_MAKEMETA_URL;
358
359    tr_bencInitDict( &top, 6 );
360
361    if( !builder->result && builder->trackerCount )
362    {
363        int prevTier = -1;
364        tr_benc * tier = NULL;
365
366        if( builder->trackerCount > 1 )
367        {
368            tr_benc * annList = tr_bencDictAddList( &top, "announce-list", 0 );
369            for( i=0; i<builder->trackerCount; ++i ) {
370                if( prevTier != builder->trackers[i].tier ) {
371                    prevTier = builder->trackers[i].tier;
372                    tier = tr_bencListAddList( annList, 0 );
373                }
374                tr_bencListAddStr( tier, builder->trackers[i].announce );
375            }
376        }
377
378        tr_bencDictAddStr( &top, "announce", builder->trackers[0].announce );
379    }
380
381    if( !builder->result && !builder->abortFlag )
382    {
383        if( builder->comment && *builder->comment )
384            tr_bencDictAddStr( &top, "comment", builder->comment );
385        tr_bencDictAddStr( &top, "created by", TR_NAME "/" LONG_VERSION_STRING );
386        tr_bencDictAddInt( &top, "creation date", time(NULL) );
387        tr_bencDictAddStr( &top, "encoding", "UTF-8" );
388        makeInfoDict( tr_bencDictAddDict( &top, "info", 666 ), builder );
389    }
390
391    /* save the file */
392    if ( !builder->result && !builder->abortFlag ) {
393        if( tr_bencSaveFile( builder->outputFile, &top ) ) {
394            builder->my_errno = errno;
395            tr_strlcpy( builder->errfile, builder->outputFile, sizeof( builder->errfile ) );
396            builder->result = TR_MAKEMETA_IO_WRITE;
397        }
398    }
399
400    /* cleanup */
401    tr_bencFree( &top );
402    if( builder->abortFlag )
403        builder->result = TR_MAKEMETA_CANCELLED;
404    builder->isDone = 1;
405}
406
407/***
408****
409****  A threaded builder queue
410****
411***/
412
413static tr_metainfo_builder * queue = NULL;
414
415static tr_thread * workerThread = NULL;
416
417static tr_lock* getQueueLock( tr_handle * h )
418{
419    static tr_lock * lock = NULL;
420
421    tr_globalLock( h );
422    if( !lock )
423         lock = tr_lockNew( );
424    tr_globalUnlock( h );
425
426    return lock;
427}
428
429static void makeMetaWorkerFunc( void * user_data )
430{
431    tr_handle * handle = (tr_handle *) user_data;
432
433    for (;;)
434    {
435        tr_metainfo_builder * builder = NULL;
436
437        /* find the next builder to process */
438        tr_lock * lock = getQueueLock ( handle );
439        tr_lockLock( lock );
440        if( queue ) {
441            builder = queue;
442            queue = queue->nextBuilder;
443        }
444        tr_lockUnlock( lock );
445
446        /* if no builders, this worker thread is done */
447        if( builder == NULL )
448          break;
449
450        tr_realMakeMetaInfo ( builder );
451    }
452
453    workerThread = NULL;
454}
455
456void
457tr_makeMetaInfo( tr_metainfo_builder    * builder,
458                 const char             * outputFile,
459                 const tr_tracker_info  * trackers,
460                 int                      trackerCount,
461                 const char             * comment,
462                 int                      isPrivate )
463{
464    int i;
465    tr_lock * lock;
466
467    /* free any variables from a previous run */
468    for( i=0; i<builder->trackerCount; ++i )
469        tr_free( builder->trackers[i].announce );
470    tr_free( builder->trackers );
471    tr_free( builder->comment );
472    tr_free( builder->outputFile );
473
474    /* initialize the builder variables */
475    builder->abortFlag = 0;
476    builder->isDone = 0;
477    builder->trackerCount = trackerCount;
478    builder->trackers = tr_new0( tr_tracker_info, builder->trackerCount );
479    for( i=0; i<builder->trackerCount; ++i ) {
480        builder->trackers[i].tier = trackers[i].tier;
481        builder->trackers[i].announce = tr_strdup( trackers[i].announce );
482    }
483    builder->comment = tr_strdup( comment );
484    builder->isPrivate = isPrivate;
485    if( outputFile && *outputFile )
486        builder->outputFile = tr_strdup( outputFile );
487    else {
488        char out[MAX_PATH_LENGTH];
489        tr_snprintf( out, sizeof(out), "%s.torrent", builder->top);
490        builder->outputFile = tr_strdup( out );
491    }
492
493    /* enqueue the builder */
494    lock = getQueueLock ( builder->handle );
495    tr_lockLock( lock );
496    builder->nextBuilder = queue;
497    queue = builder;
498    if( !workerThread )
499         workerThread = tr_threadNew( makeMetaWorkerFunc, builder->handle );
500    tr_lockUnlock( lock );
501}
502
Note: See TracBrowser for help on using the repository browser.