source: trunk/libtransmission/makemeta.c @ 2557

Last change on this file since 2557 was 2557, checked in by charles, 15 years ago

(libT) not all libT source files need to #include sha1/openssl, just three of them. Also, no need to compile a local sha1 implementation if we're using openssl's.

File size: 12.6 KB
Line 
1/*
2 * This file Copyright (C) 2007 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
11#include <assert.h>
12#include <errno.h>
13#include <math.h>
14#include <stdio.h> /* FILE, snprintf, stderr */
15#include <stdlib.h>
16
17#include <sys/types.h>
18#include <sys/stat.h>
19#include <unistd.h>
20#include <dirent.h>
21
22#include "trcompat.h" /* for strlcpy */
23#include "transmission.h"
24#include "internal.h" /* for tr_torrent_t */
25#include "bencode.h"
26#include "makemeta.h"
27#include "platform.h" /* threads, locks */
28#include "shared.h" /* shared lock */
29#include "sha1.h"
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[MAX_PATH_LENGTH];
51    struct stat sb;
52    DIR * odir = NULL;
53    sb.st_size = 0;
54
55    tr_buildPath( buf, sizeof(buf), dir, base, NULL );
56    i = stat( buf, &sb );
57    if( i ) {
58        tr_err("makemeta couldn't stat \"%s\"; skipping. (%s)", buf, strerror(errno));
59        return list;
60    }
61
62    if ( S_ISDIR( sb.st_mode ) && (( odir = opendir ( buf ) )) )
63    {
64        struct dirent *d;
65        for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
66            if( d->d_name && d->d_name[0]!='.' ) /* skip dotfiles, ., and .. */
67                list = getFiles( buf, d->d_name, list );
68        closedir( odir );
69    }
70    else if( S_ISREG( sb.st_mode ) )
71    {
72        struct FileList * node = tr_new( struct FileList, 1 );
73        node->size = sb.st_size;
74        node->filename = tr_strdup( buf );
75        node->next = list;
76        list = node;
77    }
78
79    return list;
80}
81
82static int
83bestPieceSize( uint64_t totalSize )
84{
85    static const uint64_t GiB = 1073741824;
86    static const uint64_t MiB = 1048576;
87    static const uint64_t KiB = 1024;
88
89    if( totalSize >= (8*GiB) )
90        return MiB;
91
92    if( totalSize <= (8*MiB) )
93        return 256 * KiB;
94
95    return 512 * KiB;
96}
97
98static int
99builderFileCompare ( const void * va, const void * vb)
100{
101    const tr_metainfo_builder_file_t * a = (const tr_metainfo_builder_file_t*) va;
102    const tr_metainfo_builder_file_t * b = (const tr_metainfo_builder_file_t*) vb;
103    return strcmp( a->filename, b->filename );
104}
105
106tr_metainfo_builder_t*
107tr_metaInfoBuilderCreate( tr_handle_t * handle, const char * topFile )
108{
109    int i;
110    struct FileList * files;
111    struct FileList * walk;
112    tr_metainfo_builder_t * ret = tr_new0( tr_metainfo_builder_t, 1 );
113    ret->top = tr_strdup( topFile );
114    ret->handle = handle; 
115    if (1) {
116        struct stat sb;
117        stat( topFile, &sb );
118        ret->isSingleFile = !S_ISDIR( sb.st_mode );
119    }
120
121    /* build a list of files containing topFile and,
122       if it's a directory, all of its children */
123    if (1) {
124        char *dir, *base;
125        char dirbuf[MAX_PATH_LENGTH];
126        char basebuf[MAX_PATH_LENGTH];
127        strlcpy( dirbuf, topFile, sizeof( dirbuf ) );
128        strlcpy( basebuf, topFile, sizeof( basebuf ) );
129        dir = dirname( dirbuf );
130        base = basename( basebuf );
131        files = getFiles( dir, base, NULL );
132    }
133
134    for( walk=files; walk!=NULL; walk=walk->next )
135        ++ret->fileCount;
136
137    ret->files = tr_new0( tr_metainfo_builder_file_t, ret->fileCount );
138
139    for( i=0, walk=files; walk!=NULL; ++i )
140    {
141        struct FileList * tmp = walk;
142        tr_metainfo_builder_file_t * file = &ret->files[i];
143        walk = walk->next;
144        file->filename = tmp->filename;
145        file->size = tmp->size;
146        ret->totalSize += tmp->size;
147        tr_free( tmp );
148    }
149
150    qsort( ret->files,
151           ret->fileCount,
152           sizeof(tr_metainfo_builder_file_t),
153           builderFileCompare );
154
155    ret->pieceSize = bestPieceSize( ret->totalSize );
156    ret->pieceCount = ret->pieceSize
157        ? (int)( ret->totalSize / ret->pieceSize)
158        : 0;
159    if( ret->totalSize % ret->pieceSize )
160        ++ret->pieceCount;
161
162    return ret;
163}
164
165void
166tr_metaInfoBuilderFree( tr_metainfo_builder_t * builder )
167{
168    if( builder != NULL )
169    {
170        int i;
171        for( i=0; i<builder->fileCount; ++i )
172            tr_free( builder->files[i].filename );
173        tr_free( builder->files );
174        tr_free( builder->top );
175        tr_free( builder->comment );
176        tr_free( builder->announce );
177        tr_free( builder->outputFile );
178        tr_free( builder );
179    }
180}
181
182/****
183*****
184****/
185
186static uint8_t*
187getHashInfo ( tr_metainfo_builder_t * b )
188{
189    int fileIndex = 0;
190    uint8_t *ret = tr_new0( uint8_t, SHA_DIGEST_LENGTH * b->pieceCount );
191    uint8_t *walk = ret;
192    uint8_t *buf;
193    uint64_t totalRemain;
194    uint64_t off = 0;
195    FILE * fp;
196
197    if( !b->totalSize )
198        return ret;
199
200    buf = tr_new( uint8_t, b->pieceSize );
201    b->pieceIndex = 0;
202    totalRemain = b->totalSize;
203    fp = fopen( b->files[fileIndex].filename, "rb" );
204    while ( totalRemain )
205    {
206        uint8_t *bufptr = buf;
207        const uint64_t thisPieceSize =
208            MIN( (uint32_t)b->pieceSize, totalRemain );
209        uint64_t pieceRemain = thisPieceSize;
210
211        assert( b->pieceIndex < b->pieceCount );
212
213        while( pieceRemain )
214        {
215            const uint64_t n_this_pass =
216                MIN( (b->files[fileIndex].size - off), pieceRemain );
217            fread( bufptr, 1, n_this_pass, fp );
218            bufptr += n_this_pass;
219            off += n_this_pass;
220            pieceRemain -= n_this_pass;
221            if( off == b->files[fileIndex].size ) {
222                off = 0;
223                fclose( fp );
224                fp = NULL;
225                if( ++fileIndex < b->fileCount ) {
226                    fp = fopen( b->files[fileIndex].filename, "rb" );
227                }
228            }
229        }
230
231        assert( bufptr-buf == (int)thisPieceSize );
232        assert( pieceRemain == 0 );
233        SHA1( buf, thisPieceSize, walk );
234        walk += SHA_DIGEST_LENGTH;
235
236        if( b->abortFlag ) {
237            b->failed = 1;
238            break;
239        }
240
241        totalRemain -= thisPieceSize;
242        ++b->pieceIndex;
243    }
244    assert( b->abortFlag || (walk-ret == (int)(SHA_DIGEST_LENGTH*b->pieceCount)) );
245    assert( b->abortFlag || !totalRemain );
246    assert( b->abortFlag || fp == NULL );
247
248    if( fp != NULL )
249        fclose( fp );
250
251    tr_free( buf );
252    return ret;
253}
254
255static void
256getFileInfo( const char                        * topFile,
257             const tr_metainfo_builder_file_t  * file,
258             benc_val_t                        * uninitialized_length,
259             benc_val_t                        * uninitialized_path )
260{
261    benc_val_t *sub;
262    const char *pch, *prev;
263    const size_t topLen = strlen(topFile) + 1; /* +1 for '/' */
264    int n;
265
266    /* get the file size */
267    tr_bencInitInt( uninitialized_length, file->size );
268
269    /* the path list */
270    n = 1;
271    for( pch=file->filename+topLen; *pch; ++pch )
272        if (*pch == TR_PATH_DELIMITER)
273            ++n;
274    tr_bencInit( uninitialized_path, TYPE_LIST );
275    tr_bencListReserve( uninitialized_path, n );
276    for( prev=pch=file->filename+topLen; ; ++pch )
277    {
278        char buf[MAX_PATH_LENGTH];
279
280        if (*pch && *pch!=TR_PATH_DELIMITER )
281            continue;
282
283        memcpy( buf, prev, pch-prev );
284        buf[pch-prev] = '\0';
285
286        sub = tr_bencListAdd( uninitialized_path );
287        tr_bencInitStrDup( sub, buf );
288
289        prev = pch + 1;
290        if (!*pch)
291           break;
292    }
293}
294
295static void
296makeFilesList( benc_val_t                 * list,
297               const tr_metainfo_builder_t  * builder )
298{
299    int i = 0;
300
301    tr_bencListReserve( list, builder->fileCount );
302
303    for( i=0; i<builder->fileCount; ++i )
304    {
305        benc_val_t * dict = tr_bencListAdd( list );
306        benc_val_t *length, *pathVal;
307
308        tr_bencInit( dict, TYPE_DICT );
309        tr_bencDictReserve( dict, 2 );
310        length = tr_bencDictAdd( dict, "length" );
311        pathVal = tr_bencDictAdd( dict, "path" );
312        getFileInfo( builder->top, &builder->files[i], length, pathVal );
313    }
314}
315
316static void
317makeInfoDict ( benc_val_t             * dict,
318               tr_metainfo_builder_t  * builder )
319{
320    uint8_t * pch;
321    benc_val_t * val;
322    char base[MAX_PATH_LENGTH];
323
324    tr_bencDictReserve( dict, 5 );
325   
326    if ( builder->isSingleFile )
327    {
328        val = tr_bencDictAdd( dict, "length" );
329        tr_bencInitInt( val, builder->files[0].size );
330    }
331    else
332    {
333        val = tr_bencDictAdd( dict, "files" );
334        tr_bencInit( val, TYPE_LIST );
335        makeFilesList( val, builder );
336    }
337
338    val = tr_bencDictAdd( dict, "name" );
339    strlcpy( base, builder->top, sizeof( base ) );
340    tr_bencInitStrDup ( val, basename( base ) );
341
342    val = tr_bencDictAdd( dict, "piece length" );
343    tr_bencInitInt( val, builder->pieceSize );
344
345    pch = getHashInfo( builder );
346    val = tr_bencDictAdd( dict, "pieces" );
347    tr_bencInitStr( val, pch, SHA_DIGEST_LENGTH * builder->pieceCount, 0 );
348
349    val = tr_bencDictAdd( dict, "private" );
350    tr_bencInitInt( val, builder->isPrivate ? 1 : 0 );
351}
352
353static void tr_realMakeMetaInfo ( tr_metainfo_builder_t * builder )
354{
355    int n = 5;
356    benc_val_t top, *val;
357
358    tr_bencInit ( &top, TYPE_DICT );
359    if ( builder->comment && *builder->comment ) ++n;
360    tr_bencDictReserve( &top, n );
361
362    val = tr_bencDictAdd( &top, "announce" );
363    tr_bencInitStrDup( val, builder->announce );
364   
365    if( builder->comment && *builder->comment ) {
366        val = tr_bencDictAdd( &top, "comment" );
367        tr_bencInitStrDup( val, builder->comment );
368    }
369
370    val = tr_bencDictAdd( &top, "created by" );
371    tr_bencInitStrDup( val, TR_NAME "/" LONG_VERSION_STRING );
372
373    val = tr_bencDictAdd( &top, "creation date" );
374    tr_bencInitInt( val, time(0) );
375
376    val = tr_bencDictAdd( &top, "encoding" );
377    tr_bencInitStrDup( val, "UTF-8" );
378
379    val = tr_bencDictAdd( &top, "info" );
380    tr_bencInit( val, TYPE_DICT );
381    tr_bencDictReserve( val, 666 );
382    makeInfoDict( val, builder );
383
384    /* save the file */
385    if ( !builder->abortFlag ) {
386        size_t nmemb;
387        char * pch = tr_bencSaveMalloc( &top, &n );
388        FILE * fp = fopen( builder->outputFile, "wb+" );
389        nmemb = n;
390        if( fp == NULL )
391            builder->failed = 1;
392        else if( fwrite( pch, 1, nmemb, fp ) != nmemb )
393            builder->failed = 1;
394        tr_free( pch );
395        fclose( fp );
396    }
397
398    /* cleanup */
399    tr_bencFree( & top );
400    builder->failed |= builder->abortFlag;
401    builder->isDone = 1;
402}
403
404/***
405****
406****  A threaded builder queue
407****
408***/
409
410static tr_metainfo_builder_t * queue = NULL;
411
412static tr_thread_t * workerThread = NULL;
413
414static tr_lock_t* getQueueLock( tr_handle_t * h )
415{
416    static tr_lock_t * lock = NULL;
417
418    tr_sharedLock( h->shared );
419    if( !lock )
420         lock = tr_lockNew( );
421    tr_sharedUnlock( h->shared );
422
423    return lock;
424}
425
426static void workerFunc( void * user_data )
427{
428    tr_handle_t * handle = (tr_handle_t *) user_data;
429
430    for (;;)
431    {
432        tr_metainfo_builder_t * builder = NULL;
433
434        /* find the next builder to process */
435        tr_lock_t * lock = getQueueLock ( handle );
436        tr_lockLock( lock );
437        if( queue != NULL ) {
438            builder = queue;
439            queue = queue->nextBuilder;
440        }
441        tr_lockUnlock( lock );
442
443        /* if no builders, this worker thread is done */
444        if( builder == NULL )
445          break;
446
447        tr_realMakeMetaInfo ( builder );
448    }
449
450    workerThread = NULL;
451}
452
453void
454tr_makeMetaInfo( tr_metainfo_builder_t  * builder,
455                 const char             * outputFile,
456                 const char             * announce,
457                 const char             * comment,
458                 int                      isPrivate )
459{
460    tr_lock_t * lock;
461
462    builder->abortFlag = 0;
463    builder->isDone = 0;
464    builder->announce = tr_strdup( announce );
465    builder->comment = tr_strdup( comment );
466    builder->isPrivate = isPrivate;
467    if( outputFile && *outputFile )
468        builder->outputFile = tr_strdup( outputFile );
469    else {
470        char out[MAX_PATH_LENGTH];
471        snprintf( out, sizeof(out), "%s.torrent", builder->top);
472        builder->outputFile = tr_strdup( out );
473    }
474
475    /* enqueue the builder */
476    lock = getQueueLock ( builder->handle );
477    tr_lockLock( lock );
478    builder->nextBuilder = queue;
479    queue = builder;
480    if( !workerThread )
481         workerThread = tr_threadNew( workerFunc, builder->handle, "makeMeta" );
482    tr_lockUnlock( lock );
483}
484
Note: See TracBrowser for help on using the repository browser.