source: trunk/libtransmission/makemeta.c @ 6490

Last change on this file since 6490 was 6490, checked in by charles, 13 years ago

lots of C correctness tweaks suggested by sparse/cgcc

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