source: trunk/libtransmission/makemeta.c @ 5085

Last change on this file since 5085 was 5085, checked in by charles, 14 years ago

#714: libT creates invalid torrent files when given makemeta is given a non-absolute pathname

  • Property svn:keywords set to Date Rev Author Id
File size: 12.7 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 5085 2008-02-20 11:36:42Z charles $
11 */
12
13#include <assert.h>
14#include <errno.h>
15#include <stdio.h> /* FILE, snprintf, stderr */
16#include <stdlib.h> /* qsort */
17
18#include <sys/types.h>
19#include <sys/stat.h>
20#include <unistd.h>
21#include <libgen.h> /* dirname, basename */
22#include <dirent.h>
23
24#include "crypto.h" /* tr_sha1 */
25#include "trcompat.h" /* strlcpy */
26#include "transmission.h"
27#include "bencode.h"
28#include "makemeta.h"
29#include "platform.h" /* threads, locks */
30#include "shared.h" /* shared lock */
31#include "utils.h" /* buildpath */
32#include "version.h"
33
34/****
35*****
36****/
37
38struct FileList
39{
40    struct FileList * next;
41    uint64_t size;
42    char * filename;
43};
44
45static struct FileList*
46getFiles( const char        * dir,
47          const char        * base,
48          struct FileList   * list )
49{
50    int i;
51    char buf[MAX_PATH_LENGTH];
52    struct stat sb;
53    DIR * odir = NULL;
54    sb.st_size = 0;
55
56    tr_buildPath( buf, sizeof(buf), dir, base, NULL );
57    i = stat( buf, &sb );
58    if( i ) {
59        tr_err("makemeta couldn't stat \"%s\"; skipping. (%s)", buf, tr_strerror(errno));
60        return list;
61    }
62
63    if ( S_ISDIR( sb.st_mode ) && (( odir = opendir ( buf ) )) )
64    {
65        struct dirent *d;
66        for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
67            if( d->d_name && d->d_name[0]!='.' ) /* skip dotfiles, ., and .. */
68                list = getFiles( buf, d->d_name, list );
69        closedir( odir );
70    }
71    else if( S_ISREG( sb.st_mode ) )
72    {
73        struct FileList * node = tr_new( struct FileList, 1 );
74        node->size = sb.st_size;
75        if( ( buf[0]=='.' ) && ( buf[1]=='/' ) )
76            node->filename = tr_strdup( buf + 2 );
77        else
78            node->filename = tr_strdup( buf );
79        node->next = list;
80        list = node;
81    }
82
83    return list;
84}
85
86static int
87bestPieceSize( uint64_t totalSize )
88{
89    static const uint64_t GiB = 1073741824;
90    static const uint64_t MiB = 1048576;
91    static const uint64_t KiB = 1024;
92
93    if( totalSize >=   (1*GiB) ) return 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    if (1) {
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    if (1) {
127        char *dir, *base;
128        char dirbuf[MAX_PATH_LENGTH];
129        char basebuf[MAX_PATH_LENGTH];
130        strlcpy( dirbuf, topFile, sizeof( dirbuf ) );
131        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 != NULL )
172    {
173        uint32_t i;
174        for( i=0; i<builder->fileCount; ++i )
175            tr_free( builder->files[i].filename );
176        tr_free( builder->files );
177        tr_free( builder->top );
178        tr_free( builder->comment );
179        tr_free( builder->announce );
180        tr_free( builder->outputFile );
181        tr_free( builder );
182    }
183}
184
185/****
186*****
187****/
188
189static uint8_t*
190getHashInfo ( tr_metainfo_builder * b )
191{
192    uint32_t fileIndex = 0;
193    uint8_t *ret = tr_new0( uint8_t, SHA_DIGEST_LENGTH * b->pieceCount );
194    uint8_t *walk = ret;
195    uint8_t *buf;
196    uint64_t totalRemain;
197    uint64_t off = 0;
198    FILE * fp;
199
200    if( !b->totalSize )
201        return ret;
202
203    buf = tr_new( uint8_t, b->pieceSize );
204    b->pieceIndex = 0;
205    totalRemain = b->totalSize;
206    fp = fopen( b->files[fileIndex].filename, "rb" );
207    while ( totalRemain )
208    {
209        uint8_t *bufptr = buf;
210        const uint64_t thisPieceSize =
211            MIN( (uint32_t)b->pieceSize, totalRemain );
212        uint64_t pieceRemain = thisPieceSize;
213
214        assert( b->pieceIndex < b->pieceCount );
215
216        while( pieceRemain )
217        {
218            const uint64_t n_this_pass =
219                MIN( (b->files[fileIndex].size - off), pieceRemain );
220            fread( bufptr, 1, n_this_pass, fp );
221            bufptr += n_this_pass;
222            off += n_this_pass;
223            pieceRemain -= n_this_pass;
224            if( off == b->files[fileIndex].size ) {
225                off = 0;
226                fclose( fp );
227                fp = NULL;
228                if( ++fileIndex < b->fileCount ) {
229                    fp = fopen( b->files[fileIndex].filename, "rb" );
230                }
231            }
232        }
233
234        assert( bufptr-buf == (int)thisPieceSize );
235        assert( pieceRemain == 0 );
236        tr_sha1( walk, buf, thisPieceSize, NULL );
237        walk += SHA_DIGEST_LENGTH;
238
239        if( b->abortFlag ) {
240            b->failed = 1;
241            break;
242        }
243
244        totalRemain -= thisPieceSize;
245        ++b->pieceIndex;
246    }
247    assert( b->abortFlag || (walk-ret == (int)(SHA_DIGEST_LENGTH*b->pieceCount)) );
248    assert( b->abortFlag || !totalRemain );
249    assert( b->abortFlag || fp == NULL );
250
251    if( fp != NULL )
252        fclose( fp );
253
254    tr_free( buf );
255    return ret;
256}
257
258static void
259getFileInfo( const char                      * topFile,
260             const tr_metainfo_builder_file  * file,
261             benc_val_t                      * uninitialized_length,
262             benc_val_t                      * uninitialized_path )
263{
264    benc_val_t *sub;
265    const char *pch, *prev;
266    const size_t topLen = strlen(topFile) + 1; /* +1 for '/' */
267    int n;
268
269    /* get the file size */
270    tr_bencInitInt( uninitialized_length, file->size );
271
272    /* the path list */
273    n = 1;
274    for( pch=file->filename+topLen; *pch; ++pch )
275        if (*pch == TR_PATH_DELIMITER)
276            ++n;
277    tr_bencInit( uninitialized_path, TYPE_LIST );
278    tr_bencListReserve( uninitialized_path, n );
279    for( prev=pch=file->filename+topLen; ; ++pch )
280    {
281        char buf[MAX_PATH_LENGTH];
282
283        if (*pch && *pch!=TR_PATH_DELIMITER )
284            continue;
285
286        memcpy( buf, prev, pch-prev );
287        buf[pch-prev] = '\0';
288
289        sub = tr_bencListAdd( uninitialized_path );
290        tr_bencInitStrDup( sub, buf );
291
292        prev = pch + 1;
293        if (!*pch)
294           break;
295    }
296}
297
298static void
299makeFilesList( benc_val_t                 * list,
300               const tr_metainfo_builder  * builder )
301{
302    uint32_t i = 0;
303
304    tr_bencListReserve( list, builder->fileCount );
305
306    for( i=0; i<builder->fileCount; ++i )
307    {
308        benc_val_t * dict = tr_bencListAdd( list );
309        benc_val_t *length, *pathVal;
310
311        tr_bencInit( dict, TYPE_DICT );
312        tr_bencDictReserve( dict, 2 );
313        length = tr_bencDictAdd( dict, "length" );
314        pathVal = tr_bencDictAdd( dict, "path" );
315        getFileInfo( builder->top, &builder->files[i], length, pathVal );
316    }
317}
318
319static void
320makeInfoDict ( benc_val_t           * dict,
321               tr_metainfo_builder  * builder )
322{
323    uint8_t * pch;
324    benc_val_t * val;
325    char base[MAX_PATH_LENGTH];
326
327    tr_bencDictReserve( dict, 5 );
328   
329    if ( builder->isSingleFile )
330    {
331        val = tr_bencDictAdd( dict, "length" );
332        tr_bencInitInt( val, builder->files[0].size );
333    }
334    else
335    {
336        val = tr_bencDictAdd( dict, "files" );
337        tr_bencInit( val, TYPE_LIST );
338        makeFilesList( val, builder );
339    }
340
341    val = tr_bencDictAdd( dict, "name" );
342    strlcpy( base, builder->top, sizeof( base ) );
343    tr_bencInitStrDup ( val, basename( base ) );
344
345    val = tr_bencDictAdd( dict, "piece length" );
346    tr_bencInitInt( val, builder->pieceSize );
347
348    pch = getHashInfo( builder );
349    val = tr_bencDictAdd( dict, "pieces" );
350    tr_bencInitStr( val, pch, SHA_DIGEST_LENGTH * builder->pieceCount, 0 );
351
352    val = tr_bencDictAdd( dict, "private" );
353    tr_bencInitInt( val, builder->isPrivate ? 1 : 0 );
354}
355
356static void tr_realMakeMetaInfo ( tr_metainfo_builder * builder )
357{
358    int n = 5;
359    benc_val_t top, *val;
360
361    tr_bencInit ( &top, TYPE_DICT );
362    if ( builder->comment && *builder->comment ) ++n;
363    tr_bencDictReserve( &top, n );
364
365    val = tr_bencDictAdd( &top, "announce" );
366    tr_bencInitStrDup( val, builder->announce );
367   
368    if( builder->comment && *builder->comment ) {
369        val = tr_bencDictAdd( &top, "comment" );
370        tr_bencInitStrDup( val, builder->comment );
371    }
372
373    val = tr_bencDictAdd( &top, "created by" );
374    tr_bencInitStrDup( val, TR_NAME "/" LONG_VERSION_STRING );
375
376    val = tr_bencDictAdd( &top, "creation date" );
377    tr_bencInitInt( val, time(0) );
378
379    val = tr_bencDictAdd( &top, "encoding" );
380    tr_bencInitStrDup( val, "UTF-8" );
381
382    val = tr_bencDictAdd( &top, "info" );
383    tr_bencInit( val, TYPE_DICT );
384    tr_bencDictReserve( val, 666 );
385    makeInfoDict( val, builder );
386
387    /* save the file */
388    if ( !builder->abortFlag ) {
389        size_t nmemb;
390        char * pch = tr_bencSave( &top, &n );
391        FILE * fp = fopen( builder->outputFile, "wb+" );
392        nmemb = n;
393        if( fp == NULL )
394            builder->failed = 1;
395        else if( fwrite( pch, 1, nmemb, fp ) != nmemb )
396            builder->failed = 1;
397        tr_free( pch );
398        fclose( fp );
399    }
400
401    /* cleanup */
402    tr_bencFree( & top );
403    builder->failed |= builder->abortFlag;
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 workerFunc( 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 != 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    workerThread = NULL;
454}
455
456void
457tr_makeMetaInfo( tr_metainfo_builder  * builder,
458                 const char           * outputFile,
459                 const char           * announce,
460                 const char           * comment,
461                 int                    isPrivate )
462{
463    tr_lock * 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( !workerThread )
484         workerThread = tr_threadNew( workerFunc, builder->handle, "makeMeta" );
485    tr_lockUnlock( lock );
486}
487
Note: See TracBrowser for help on using the repository browser.