443 lines
13 KiB
C
443 lines
13 KiB
C
/******************************************************************************
|
|
* $Id$
|
|
*
|
|
* Copyright (c) 2005-2007 Transmission authors and contributors
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*****************************************************************************/
|
|
|
|
/***********************************************************************
|
|
* Fast resume
|
|
***********************************************************************
|
|
* The format of the resume file is a 4 byte format version (currently 1),
|
|
* followed by several variable-sized blocks of data. Each block is
|
|
* preceded by a 1 byte ID and a 4 byte length. The currently recognized
|
|
* IDs are defined below by the FR_ID_* macros. The length does not include
|
|
* the 5 bytes for the ID and length.
|
|
*
|
|
* The name of the resume file is "resume.<hash>-<tag>", although
|
|
* older files with a name of "resume.<hash>" will be recognized if
|
|
* the former doesn't exist.
|
|
*
|
|
* All values are stored in the native endianness. Moving a
|
|
* libtransmission resume file from an architecture to another will not
|
|
* work, although it will not hurt either (the version will be wrong,
|
|
* so the resume file will not be read).
|
|
**********************************************************************/
|
|
|
|
/* progress data:
|
|
* - 4 bytes * number of files: mtimes of files
|
|
* - 1 bit * number of blocks: whether we have the block or not
|
|
* - 4 bytes * number of pieces (byte aligned): the pieces that have
|
|
* been completed or started in each slot
|
|
*/
|
|
#define FR_ID_PROGRESS 0x01
|
|
/* number of bytes downloaded */
|
|
#define FR_ID_DOWNLOADED 0x02
|
|
/* number of bytes uploaded */
|
|
#define FR_ID_UPLOADED 0x03
|
|
/* IPs and ports of connectable peers */
|
|
#define FR_ID_PEERS 0x04
|
|
|
|
/* macros for the length of various pieces of the progress data */
|
|
#define FR_MTIME_LEN( t ) \
|
|
( 4 * (t)->info.fileCount )
|
|
#define FR_BLOCK_BITFIELD_LEN( t ) \
|
|
( ( (t)->blockCount + 7 ) / 8 )
|
|
#define FR_SLOTPIECE_LEN( t ) \
|
|
( 4 * (t)->info.pieceCount )
|
|
#define FR_PROGRESS_LEN( t ) \
|
|
( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) + FR_SLOTPIECE_LEN( t ) )
|
|
|
|
static void
|
|
fastResumeFileName( char * path, size_t size, tr_torrent_t * tor, int tag )
|
|
{
|
|
if( tag )
|
|
{
|
|
snprintf( path, size, "%s/resume.%s-%s", tr_getCacheDirectory(),
|
|
tor->info.hashString, tor->handle->tag );
|
|
}
|
|
else
|
|
{
|
|
snprintf( path, size, "%s/resume.%s", tr_getCacheDirectory(),
|
|
tor->info.hashString );
|
|
}
|
|
}
|
|
|
|
static int fastResumeMTimes( tr_io_t * io, int * tab )
|
|
{
|
|
tr_torrent_t * tor = io->tor;
|
|
tr_info_t * inf = &tor->info;
|
|
|
|
int i;
|
|
char * path;
|
|
struct stat sb;
|
|
|
|
for( i = 0; i < inf->fileCount; i++ )
|
|
{
|
|
asprintf( &path, "%s/%s", tor->destination, inf->files[i].name );
|
|
if( stat( path, &sb ) )
|
|
{
|
|
tab[i] = 0xFFFFFFFF;
|
|
}
|
|
else if( S_ISREG( sb.st_mode ) )
|
|
{
|
|
#ifdef SYS_DARWIN
|
|
tab[i] = ( sb.st_mtimespec.tv_sec & 0x7FFFFFFF );
|
|
#else
|
|
tab[i] = ( sb.st_mtime & 0x7FFFFFFF );
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/* Empty folder */
|
|
tab[i] = 0;
|
|
}
|
|
free( path );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
|
|
uint32_t count, FILE * file )
|
|
{
|
|
uint32_t datalen = size * count;
|
|
|
|
fwrite( &id, 1, 1, file );
|
|
fwrite( &datalen, 4, 1, file );
|
|
fwrite( data, size, count, file );
|
|
}
|
|
|
|
static void fastResumeSave( tr_io_t * io )
|
|
{
|
|
tr_torrent_t * tor = io->tor;
|
|
|
|
char path[MAX_PATH_LENGTH];
|
|
FILE * file;
|
|
int version = 1;
|
|
uint8_t * buf;
|
|
uint64_t total;
|
|
int size;
|
|
tr_bitfield_t * bitfield;
|
|
|
|
buf = malloc( FR_PROGRESS_LEN( tor ) );
|
|
|
|
/* Get file sizes */
|
|
if( fastResumeMTimes( io, (int*)buf ) )
|
|
{
|
|
free( buf );
|
|
return;
|
|
}
|
|
|
|
/* Create/overwrite the resume file */
|
|
fastResumeFileName( path, sizeof path, tor, 1 );
|
|
file = fopen( path, "w" );
|
|
if( NULL == file )
|
|
{
|
|
tr_err( "Could not open '%s' for writing", path );
|
|
free( buf );
|
|
return;
|
|
}
|
|
|
|
/* Write format version */
|
|
fwrite( &version, 4, 1, file );
|
|
|
|
/* Build and copy the bitfield for blocks */
|
|
bitfield = tr_cpBlockBitfield( tor->completion );
|
|
assert( FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
|
|
memcpy(buf + FR_MTIME_LEN( tor ), bitfield->bits, bitfield->len );
|
|
|
|
/* Copy the 'slotPiece' table */
|
|
memcpy(buf + FR_MTIME_LEN( tor ) + FR_BLOCK_BITFIELD_LEN( tor ),
|
|
io->slotPiece, FR_SLOTPIECE_LEN( tor ) );
|
|
|
|
/* Write progress data */
|
|
fastResumeWriteData( FR_ID_PROGRESS, buf, 1, FR_PROGRESS_LEN( tor ), file );
|
|
free( buf );
|
|
|
|
/* Write download and upload totals */
|
|
total = tor->downloadedCur + tor->downloadedPrev;
|
|
fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
|
|
total = tor->uploadedCur + tor->uploadedPrev;
|
|
fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
|
|
|
|
if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
|
|
{
|
|
/* Write IPs and ports of connectable peers, if any */
|
|
if( ( size = tr_peerGetConnectable( tor, &buf ) ) > 0 )
|
|
{
|
|
fastResumeWriteData( FR_ID_PEERS, buf, size, 1, file );
|
|
free( buf );
|
|
}
|
|
}
|
|
|
|
fclose( file );
|
|
|
|
tr_dbg( "Resume file '%s' written", path );
|
|
}
|
|
|
|
static int fastResumeLoadProgress( tr_io_t * io, FILE * file )
|
|
{
|
|
tr_torrent_t * tor = io->tor;
|
|
tr_info_t * inf = &tor->info;
|
|
|
|
int * fileMTimes;
|
|
int i, j;
|
|
uint8_t * buf;
|
|
size_t len;
|
|
tr_bitfield_t bitfield;
|
|
|
|
len = FR_PROGRESS_LEN( tor );
|
|
buf = calloc( len, 1 );
|
|
if( len != fread( buf, 1, len, file ) )
|
|
{
|
|
tr_inf( "Could not read from resume file" );
|
|
free( buf );
|
|
return 1;
|
|
}
|
|
|
|
/* Compare file mtimes */
|
|
fileMTimes = malloc( FR_MTIME_LEN( tor ) );
|
|
if( fastResumeMTimes( io, fileMTimes ) )
|
|
{
|
|
free( buf );
|
|
free( fileMTimes );
|
|
return 1;
|
|
}
|
|
if( memcmp( fileMTimes, buf, FR_MTIME_LEN( tor ) ) )
|
|
{
|
|
tr_inf( "File mtimes don't match" );
|
|
free( buf );
|
|
free( fileMTimes );
|
|
return 1;
|
|
}
|
|
free( fileMTimes );
|
|
|
|
/* Copy the bitfield for blocks and fill blockHave */
|
|
memset( &bitfield, 0, sizeof bitfield );
|
|
bitfield.len = FR_BLOCK_BITFIELD_LEN( tor );
|
|
bitfield.bits = buf + FR_MTIME_LEN( tor );
|
|
tr_cpBlockBitfieldSet( tor->completion, &bitfield );
|
|
|
|
/* Copy the 'slotPiece' table */
|
|
memcpy( io->slotPiece, buf + FR_MTIME_LEN( tor ) +
|
|
FR_BLOCK_BITFIELD_LEN( tor ), FR_SLOTPIECE_LEN( tor ) );
|
|
|
|
free( buf );
|
|
|
|
/* Update io->pieceSlot, io->slotsUsed, and tor->bitfield */
|
|
io->slotsUsed = 0;
|
|
for( i = 0; i < inf->pieceCount; i++ )
|
|
{
|
|
io->pieceSlot[i] = -1;
|
|
for( j = 0; j < inf->pieceCount; j++ )
|
|
{
|
|
if( io->slotPiece[j] == i )
|
|
{
|
|
// tr_dbg( "Has piece %d in slot %d", i, j );
|
|
io->pieceSlot[i] = j;
|
|
io->slotsUsed = MAX( io->slotsUsed, j + 1 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// tr_dbg( "Slot used: %d", io->slotsUsed );
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fastResumeLoadOld( tr_io_t * io, FILE * file )
|
|
{
|
|
tr_torrent_t * tor = io->tor;
|
|
|
|
int size;
|
|
|
|
/* Check the size */
|
|
size = 4 + FR_PROGRESS_LEN( tor );
|
|
fseek( file, 0, SEEK_END );
|
|
if( ftell( file ) != size )
|
|
{
|
|
tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
|
|
(int)ftell( file ), size );
|
|
fclose( file );
|
|
return 1;
|
|
}
|
|
|
|
/* load progress information */
|
|
fseek( file, 4, SEEK_SET );
|
|
if( fastResumeLoadProgress( io, file ) )
|
|
{
|
|
fclose( file );
|
|
return 1;
|
|
}
|
|
|
|
fclose( file );
|
|
|
|
tr_inf( "Fast resuming successful (version 0)" );
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fastResumeLoad( tr_io_t * io )
|
|
{
|
|
tr_torrent_t * tor = io->tor;
|
|
|
|
char path[MAX_PATH_LENGTH];
|
|
FILE * file;
|
|
int version = 0;
|
|
uint8_t id;
|
|
uint32_t len;
|
|
int ret;
|
|
|
|
/* Open resume file */
|
|
fastResumeFileName( path, sizeof path, tor, 1 );
|
|
file = fopen( path, "r" );
|
|
if( NULL == file )
|
|
{
|
|
if( ENOENT == errno )
|
|
{
|
|
fastResumeFileName( path, sizeof path, tor, 0 );
|
|
file = fopen( path, "r" );
|
|
if( NULL != file )
|
|
{
|
|
goto good;
|
|
}
|
|
fastResumeFileName( path, sizeof path, tor, 1 );
|
|
}
|
|
tr_inf( "Could not open '%s' for reading", path );
|
|
return 1;
|
|
}
|
|
good:
|
|
tr_dbg( "Resume file '%s' loaded", path );
|
|
|
|
/* Check format version */
|
|
fread( &version, 4, 1, file );
|
|
if( 0 == version )
|
|
{
|
|
return fastResumeLoadOld( io, file );
|
|
}
|
|
if( 1 != version )
|
|
{
|
|
tr_inf( "Resume file has version %d, not supported", version );
|
|
fclose( file );
|
|
return 1;
|
|
}
|
|
|
|
ret = 1;
|
|
/* read each block of data */
|
|
while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
|
|
{
|
|
switch( id )
|
|
{
|
|
case FR_ID_PROGRESS:
|
|
/* read progress data */
|
|
if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
|
|
{
|
|
if( fastResumeLoadProgress( io, file ) )
|
|
{
|
|
if( feof( file ) || ferror( file ) )
|
|
{
|
|
fclose( file );
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = 0;
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case FR_ID_DOWNLOADED:
|
|
/* read download total */
|
|
if( 8 == len)
|
|
{
|
|
if( 1 != fread( &tor->downloadedPrev, 8, 1, file ) )
|
|
{
|
|
fclose( file );
|
|
return 1;
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case FR_ID_UPLOADED:
|
|
/* read upload total */
|
|
if( 8 == len)
|
|
{
|
|
if( 1 != fread( &tor->uploadedPrev, 8, 1, file ) )
|
|
{
|
|
fclose( file );
|
|
return 1;
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case FR_ID_PEERS:
|
|
if( !( TR_FLAG_PRIVATE & tor->info.flags ) )
|
|
{
|
|
int used;
|
|
uint8_t * buf = malloc( len );
|
|
if( 1 != fread( buf, len, 1, file ) )
|
|
{
|
|
free( buf );
|
|
fclose( file );
|
|
return 1;
|
|
}
|
|
used = tr_torrentAddCompact( tor, TR_PEER_FROM_CACHE,
|
|
buf, len / 6 );
|
|
tr_dbg( "found %i peers in resume file, used %i",
|
|
len / 6, used );
|
|
free( buf );
|
|
}
|
|
continue;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* if we didn't read the data, seek past it */
|
|
tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
|
|
fseek( file, len, SEEK_CUR );
|
|
}
|
|
|
|
fclose( file );
|
|
|
|
if( !ret )
|
|
{
|
|
tr_inf( "Fast resuming successful" );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void fastResumeRemove( tr_torrent_t * tor )
|
|
{
|
|
char file[MAX_PATH_LENGTH];
|
|
fastResumeFileName( file, sizeof file, tor, NULL != tor->handle->tag );
|
|
|
|
if ( unlink( file ) )
|
|
{
|
|
tr_inf( "Removing fast resume file failed" );
|
|
}
|
|
}
|