source: branches/2.0x/libtransmission/makemeta.c @ 10971

Last change on this file since 10971 was 10971, checked in by charles, 12 years ago

(2.0x T) #3404 "crash when creating a .torrent containing empty content" -- fixed

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