source: trunk/libtransmission/makemeta.c @ 2544

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

this looks bug but it's not: just janitorial cleanup, moving #includes from headers into source file

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