source: trunk/libtransmission/makemeta.c @ 2268

Last change on this file since 2268 was 2268, checked in by charles, 15 years ago

Fix makemeta infinite loop reported by BentMyWookie?

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