source: branches/0.9x/libtransmission/makemeta.c @ 3657

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

fix compile warning

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