source: trunk/libtransmission/makemeta.c @ 11272

Last change on this file since 11272 was 11272, checked in by Longinus00, 12 years ago

(libt) r11244 introduced a regression when making single file torrents

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