source: trunk/libtransmission/makemeta.c @ 12513

Last change on this file since 12513 was 12513, checked in by jordan, 10 years ago

(trunk libT) minor code formatting

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