transmission/libtransmission/makemeta.c

499 lines
13 KiB
C

/*
* This file Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libgen.h>
#include <dirent.h>
#include <stdio.h> /* FILE, snprintf, stderr */
#include <stdlib.h> /* malloc, calloc */
#include "transmission.h"
#include "internal.h" /* for tr_torrent_t */
#include "bencode.h"
#include "makemeta.h"
#include "platform.h" /* threads, locks */
#include "shared.h" /* shared lock */
#include "version.h"
/****
*****
****/
struct FileList
{
struct FileList * next;
char filename[MAX_PATH_LENGTH];
};
static struct FileList*
getFiles( const char * dir,
const char * base,
struct FileList * list )
{
int i;
char buf[MAX_PATH_LENGTH];
struct stat sb;
DIR * odir = NULL;
sb.st_size = 0;
snprintf( buf, sizeof(buf), "%s"TR_PATH_DELIMITER_STR"%s", dir, base );
i = stat( buf, &sb );
if( i ) {
tr_err("makemeta couldn't stat \"%s\"; skipping. (%s)", buf, strerror(errno));
return list;
}
if ( S_ISDIR( sb.st_mode ) && (( odir = opendir ( buf ) )) )
{
struct dirent *d;
for (d = readdir( odir ); d!=NULL; d=readdir( odir ) )
if( strcmp( d->d_name,"." ) && strcmp( d->d_name,".." ) )
list = getFiles( buf, d->d_name, list );
closedir( odir );
}
else if( S_ISREG( sb.st_mode ) )
{
struct FileList * node = malloc( sizeof( struct FileList ) );
snprintf( node->filename, sizeof( node->filename ), "%s", buf );
node->next = list;
list = node;
}
return list;
}
static void
freeFileList( struct FileList * list )
{
while( list ) {
struct FileList * tmp = list->next;
free( list );
list = tmp;
}
}
static off_t
getFileSize ( const char * filename )
{
struct stat sb;
sb.st_size = 0;
stat( filename, &sb );
return sb.st_size;
}
static int
bestPieceSize( uint64_t totalSize )
{
const int MiB = 1048576;
const int GiB = totalSize / (uint64_t)1073741824;
/* almost always best to have a piee size of 512 or 256 kb.
common practice seems to be to bump up to 1MB pieces at
at total size of around 8GiB or so */
if( GiB >= 8 )
return MiB;
if( GiB >= 1 )
return MiB / 2;
return MiB / 4;
}
/****
*****
****/
static int pstrcmp( const void * va, const void * vb)
{
const char * a = *(const char**) va;
const char * b = *(const char**) vb;
return strcmp( a, b );
}
tr_metainfo_builder_t*
tr_metaInfoBuilderCreate( tr_handle_t * handle, const char * topFile )
{
int i;
struct FileList * files;
const struct FileList * walk;
tr_metainfo_builder_t * ret = calloc( 1, sizeof(tr_metainfo_builder_t) );
ret->top = tr_strdup( topFile );
ret->handle = handle;
if (1) {
struct stat sb;
stat( topFile, &sb );
ret->isSingleFile = !S_ISDIR( sb.st_mode );
}
/* build a list of files containing topFile and,
if it's a directory, all of its children */
if (1) {
char *dir, *base;
char dirbuf[MAX_PATH_LENGTH];
char basebuf[MAX_PATH_LENGTH];
strlcpy( dirbuf, topFile, sizeof( dirbuf ) );
strlcpy( basebuf, topFile, sizeof( basebuf ) );
dir = dirname( dirbuf );
base = basename( basebuf );
files = getFiles( dir, base, NULL );
}
for( walk=files; walk!=NULL; walk=walk->next )
++ret->fileCount;
ret->files = calloc( ret->fileCount, sizeof(char*) );
ret->fileLengths = calloc( ret->fileCount, sizeof(uint64_t) );
for( i=0, walk=files; walk!=NULL; walk=walk->next, ++i )
ret->files[i] = tr_strdup( walk->filename );
qsort( ret->files, ret->fileCount, sizeof(char*), pstrcmp );
for( i=0; i<ret->fileCount; ++i ) {
ret->fileLengths[i] = getFileSize( ret->files[i] );
ret->totalSize += ret->fileLengths[i];
}
freeFileList( files );
ret->pieceSize = bestPieceSize( ret->totalSize );
ret->pieceCount = (int)( ret->totalSize / ret->pieceSize);
if( ret->totalSize % ret->pieceSize )
++ret->pieceCount;
return ret;
}
void
tr_metaInfoBuilderFree( tr_metainfo_builder_t * builder )
{
if( builder != NULL )
{
int i;
for( i=0; i<builder->fileCount; ++i )
tr_free( builder->files[i] );
tr_free( builder->files );
tr_free( builder->fileLengths );
tr_free( builder->top );
tr_free( builder->comment );
tr_free( builder->announce );
tr_free( builder->outputFile );
tr_free( builder );
}
}
/****
*****
****/
static uint8_t*
getHashInfo ( tr_metainfo_builder_t * b )
{
int fileIndex = 0;
uint8_t *ret = (uint8_t*) malloc ( SHA_DIGEST_LENGTH * b->pieceCount );
uint8_t *walk = ret;
uint8_t *buf = malloc( b->pieceSize );
uint64_t totalRemain;
uint64_t off = 0;
FILE * fp;
b->pieceIndex = 0;
totalRemain = b->totalSize;
fp = fopen( b->files[fileIndex], "rb" );
while ( totalRemain )
{
uint8_t *bufptr = buf;
const uint64_t thisPieceSize = MIN( (uint32_t)b->pieceSize, totalRemain );
uint64_t pieceRemain = thisPieceSize;
assert( b->pieceIndex < b->pieceCount );
while( pieceRemain )
{
const uint64_t n_this_pass = MIN( (b->fileLengths[fileIndex] - off), pieceRemain );
fread( bufptr, 1, n_this_pass, fp );
bufptr += n_this_pass;
off += n_this_pass;
pieceRemain -= n_this_pass;
if( off == b->fileLengths[fileIndex] ) {
off = 0;
fclose( fp );
fp = NULL;
if( ++fileIndex < b->fileCount ) {
fp = fopen( b->files[fileIndex], "rb" );
}
}
}
assert( bufptr-buf == (int)thisPieceSize );
assert( pieceRemain == 0 );
SHA1( buf, thisPieceSize, walk );
walk += SHA_DIGEST_LENGTH;
if( b->abortFlag ) {
b->failed = 1;
break;
}
totalRemain -= thisPieceSize;
++b->pieceIndex;
}
assert( b->abortFlag || (walk-ret == (int)(SHA_DIGEST_LENGTH*b->pieceCount)) );
assert( b->abortFlag || !totalRemain );
assert( b->abortFlag || fp == NULL );
if( fp != NULL )
fclose( fp );
free( buf );
return ret;
}
static void
getFileInfo( const char * topFile,
const char * filename,
benc_val_t * uninitialized_length,
benc_val_t * uninitialized_path )
{
benc_val_t *sub;
const char *pch, *prev;
const size_t topLen = strlen(topFile) + 1; /* +1 for '/' */
int n;
/* get the file size */
tr_bencInitInt( uninitialized_length, getFileSize(filename) );
/* the path list */
n = 1;
for( pch=filename+topLen; *pch; ++pch )
if (*pch == TR_PATH_DELIMITER)
++n;
tr_bencInit( uninitialized_path, TYPE_LIST );
tr_bencListReserve( uninitialized_path, n );
for( prev=pch=filename+topLen; ; ++pch )
{
char buf[MAX_PATH_LENGTH];
if (*pch && *pch!=TR_PATH_DELIMITER )
continue;
memcpy( buf, prev, pch-prev );
buf[pch-prev] = '\0';
sub = tr_bencListAdd( uninitialized_path );
tr_bencInitStrDup( sub, buf );
prev = pch + 1;
if (!*pch)
break;
}
}
static void
makeFilesList( benc_val_t * list,
const tr_metainfo_builder_t * builder )
{
int i = 0;
tr_bencListReserve( list, builder->fileCount );
for( i=0; i<builder->fileCount; ++i )
{
benc_val_t * dict = tr_bencListAdd( list );
benc_val_t *length, *pathVal;
tr_bencInit( dict, TYPE_DICT );
tr_bencDictReserve( dict, 2 );
length = tr_bencDictAdd( dict, "length" );
pathVal = tr_bencDictAdd( dict, "path" );
getFileInfo( builder->top, builder->files[i], length, pathVal );
}
}
static void
makeInfoDict ( benc_val_t * dict,
tr_metainfo_builder_t * builder )
{
uint8_t * pch;
benc_val_t * val;
char base[MAX_PATH_LENGTH];
tr_bencDictReserve( dict, 5 );
val = tr_bencDictAdd( dict, "name" );
strlcpy( base, builder->top, sizeof( base ) );
tr_bencInitStrDup ( val, basename( base ) );
val = tr_bencDictAdd( dict, "piece length" );
tr_bencInitInt( val, builder->pieceSize );
pch = getHashInfo( builder );
val = tr_bencDictAdd( dict, "pieces" );
tr_bencInitStr( val, pch, SHA_DIGEST_LENGTH * builder->pieceCount, 0 );
if ( builder->isSingleFile )
{
val = tr_bencDictAdd( dict, "length" );
tr_bencInitInt( val, builder->fileLengths[0] );
}
else
{
val = tr_bencDictAdd( dict, "files" );
tr_bencInit( val, TYPE_LIST );
makeFilesList( val, builder );
}
val = tr_bencDictAdd( dict, "private" );
tr_bencInitInt( val, builder->isPrivate ? 1 : 0 );
}
static void tr_realMakeMetaInfo ( tr_metainfo_builder_t * builder )
{
int n = 5;
benc_val_t top, *val;
tr_bencInit ( &top, TYPE_DICT );
if ( builder->comment && *builder->comment ) ++n;
tr_bencDictReserve( &top, n );
val = tr_bencDictAdd( &top, "announce" );
tr_bencInitStrDup( val, builder->announce );
val = tr_bencDictAdd( &top, "created by" );
tr_bencInitStrDup( val, TR_NAME " " VERSION_STRING );
val = tr_bencDictAdd( &top, "creation date" );
tr_bencInitInt( val, time(0) );
val = tr_bencDictAdd( &top, "encoding" );
tr_bencInitStrDup( val, "UTF-8" );
if( builder->comment && *builder->comment ) {
val = tr_bencDictAdd( &top, "comment" );
tr_bencInitStrDup( val, builder->comment );
}
val = tr_bencDictAdd( &top, "info" );
tr_bencInit( val, TYPE_DICT );
tr_bencDictReserve( val, 666 );
makeInfoDict( val, builder );
/* save the file */
if ( !builder->abortFlag ) {
size_t nmemb;
char * pch = tr_bencSaveMalloc( &top, &n );
FILE * fp = fopen( builder->outputFile, "wb+" );
nmemb = n;
if( fp == NULL )
builder->failed = 1;
else if( fwrite( pch, 1, nmemb, fp ) != nmemb )
builder->failed = 1;
free( pch );
fclose( fp );
}
/* cleanup */
tr_bencFree( & top );
builder->failed |= builder->abortFlag;
builder->isDone = 1;
}
/***
****
**** A threaded builder queue
****
***/
static tr_metainfo_builder_t * queue = NULL;
static int workerIsRunning = 0;
static tr_thread_t workerThread;
static tr_lock_t* getQueueLock( tr_handle_t * h )
{
static tr_lock_t * lock = NULL;
tr_sharedLock( h->shared );
if( lock == NULL )
{
lock = calloc( 1, sizeof( tr_lock_t ) );
tr_lockInit( lock );
}
tr_sharedUnlock( h->shared );
return lock;
}
static void workerFunc( void * user_data )
{
tr_handle_t * handle = (tr_handle_t *) user_data;
for (;;)
{
tr_metainfo_builder_t * builder = NULL;
/* find the next builder to process */
tr_lock_t * lock = getQueueLock ( handle );
tr_lockLock( lock );
if( queue != NULL ) {
builder = queue;
queue = queue->nextBuilder;
}
tr_lockUnlock( lock );
/* if no builders, this worker thread is done */
if( builder == NULL )
break;
tr_realMakeMetaInfo ( builder );
}
workerIsRunning = 0;
}
void
tr_makeMetaInfo( tr_metainfo_builder_t * builder,
const char * outputFile,
const char * announce,
const char * comment,
int isPrivate )
{
tr_lock_t * lock;
builder->abortFlag = 0;
builder->isDone = 0;
builder->announce = tr_strdup( announce );
builder->comment = tr_strdup( comment );
builder->isPrivate = isPrivate;
if( outputFile && *outputFile )
builder->outputFile = tr_strdup( outputFile );
else {
char out[MAX_PATH_LENGTH];
snprintf( out, sizeof(out), "%s.torrent", builder->top);
builder->outputFile = tr_strdup( out );
}
/* enqueue the builder */
lock = getQueueLock ( builder->handle );
tr_lockLock( lock );
builder->nextBuilder = queue;
queue = builder;
if( !workerIsRunning ) {
workerIsRunning = 1;
tr_threadCreate( &workerThread, workerFunc, builder->handle, "makeMeta" );
}
tr_lockUnlock( lock );
}