2007-06-18 03:40:41 +00:00
|
|
|
/*
|
2010-01-04 21:00:47 +00:00
|
|
|
* This file Copyright (C) 2007-2010 Mnemosyne LLC
|
2006-07-16 19:39:23 +00:00
|
|
|
*
|
2007-06-18 03:40:41 +00:00
|
|
|
* This file is licensed by the GPL version 2. Works owned by the
|
|
|
|
* Transmission project are granted a special exemption to clause 2(b)
|
2008-09-23 19:11:04 +00:00
|
|
|
* so that the bulk of its code can remain under the MIT license.
|
2007-06-18 03:40:41 +00:00
|
|
|
* This exemption does not extend to derived works not owned by
|
|
|
|
* the Transmission project.
|
2007-08-18 17:19:49 +00:00
|
|
|
*
|
|
|
|
* $Id$
|
2007-06-18 03:40:41 +00:00
|
|
|
*/
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2009-09-09 03:23:50 +00:00
|
|
|
#ifdef HAVE_LSEEK64
|
|
|
|
#define _LARGEFILE64_SOURCE
|
|
|
|
#endif
|
|
|
|
|
2007-11-09 20:07:52 +00:00
|
|
|
#include <assert.h>
|
2008-02-19 04:16:04 +00:00
|
|
|
#include <errno.h>
|
2008-03-13 00:38:16 +00:00
|
|
|
#include <stdlib.h> /* realloc */
|
2008-02-19 18:39:49 +00:00
|
|
|
#include <string.h> /* memcmp */
|
2007-07-29 18:11:21 +00:00
|
|
|
|
2007-07-12 17:51:45 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
2007-07-29 18:11:21 +00:00
|
|
|
|
2008-09-06 13:25:21 +00:00
|
|
|
#include <openssl/sha.h>
|
|
|
|
|
2006-07-16 19:39:23 +00:00
|
|
|
#include "transmission.h"
|
2010-06-19 14:25:11 +00:00
|
|
|
#include "cache.h"
|
2008-03-13 00:38:16 +00:00
|
|
|
#include "crypto.h"
|
2007-07-09 20:10:42 +00:00
|
|
|
#include "fdlimit.h"
|
2007-07-30 18:04:10 +00:00
|
|
|
#include "inout.h"
|
2010-07-08 23:04:12 +00:00
|
|
|
#include "peer-common.h" /* MAX_BLOCK_SIZE */
|
2008-03-13 00:38:16 +00:00
|
|
|
#include "platform.h"
|
2007-11-26 20:37:07 +00:00
|
|
|
#include "stats.h"
|
2007-12-25 05:37:32 +00:00
|
|
|
#include "torrent.h"
|
2007-07-30 18:04:10 +00:00
|
|
|
#include "utils.h"
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2007-06-18 03:40:41 +00:00
|
|
|
/****
|
|
|
|
***** Low-level IO functions
|
|
|
|
****/
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2007-09-09 01:32:59 +00:00
|
|
|
#ifdef WIN32
|
2008-10-15 19:52:15 +00:00
|
|
|
#if defined(read)
|
2008-11-20 17:19:59 +00:00
|
|
|
#undef read
|
2008-10-14 20:31:16 +00:00
|
|
|
#endif
|
2008-10-14 01:00:15 +00:00
|
|
|
#define read _read
|
2009-08-12 14:40:32 +00:00
|
|
|
|
2008-10-15 19:52:15 +00:00
|
|
|
#if defined(write)
|
2008-11-20 17:19:59 +00:00
|
|
|
#undef write
|
2008-10-14 20:31:16 +00:00
|
|
|
#endif
|
2008-10-14 01:00:15 +00:00
|
|
|
#define write _write
|
2007-09-09 01:32:59 +00:00
|
|
|
#endif
|
|
|
|
|
2009-11-08 23:43:38 +00:00
|
|
|
enum { TR_IO_READ, TR_IO_PREFETCH,
|
|
|
|
/* Any operations that require write access must follow TR_IO_WRITE. */
|
|
|
|
TR_IO_WRITE
|
|
|
|
};
|
2007-09-20 16:32:01 +00:00
|
|
|
|
2008-10-03 04:49:06 +00:00
|
|
|
/* returns 0 on success, or an errno on failure */
|
|
|
|
static int
|
2009-10-23 03:41:36 +00:00
|
|
|
readOrWriteBytes( tr_session * session,
|
|
|
|
const tr_torrent * tor,
|
2008-09-23 19:11:04 +00:00
|
|
|
int ioMode,
|
|
|
|
tr_file_index_t fileIndex,
|
|
|
|
uint64_t fileOffset,
|
|
|
|
void * buf,
|
|
|
|
size_t buflen )
|
2007-06-18 03:40:41 +00:00
|
|
|
{
|
2007-09-20 16:32:01 +00:00
|
|
|
const tr_info * info = &tor->info;
|
|
|
|
const tr_file * file = &info->files[fileIndex];
|
2008-09-23 19:11:04 +00:00
|
|
|
|
|
|
|
int fd = -1;
|
2009-10-19 05:05:00 +00:00
|
|
|
int err = 0;
|
2009-11-08 23:43:38 +00:00
|
|
|
const tr_bool doWrite = ioMode >= TR_IO_WRITE;
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2010-06-20 13:19:24 +00:00
|
|
|
//if( doWrite )
|
|
|
|
// fprintf( stderr, "in file %s at offset %zu, writing %zu bytes; file length is %zu\n", file->name, (size_t)fileOffset, buflen, (size_t)file->length );
|
2010-06-19 14:25:11 +00:00
|
|
|
|
2008-03-22 18:10:59 +00:00
|
|
|
assert( fileIndex < info->fileCount );
|
2008-09-23 19:11:04 +00:00
|
|
|
assert( !file->length || ( fileOffset < file->length ) );
|
2007-11-01 13:47:32 +00:00
|
|
|
assert( fileOffset + buflen <= file->length );
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2007-06-18 03:40:41 +00:00
|
|
|
if( !file->length )
|
2008-10-03 04:49:06 +00:00
|
|
|
return 0;
|
2008-01-18 19:13:32 +00:00
|
|
|
|
2009-10-23 03:41:36 +00:00
|
|
|
fd = tr_fdFileGetCached( session, tr_torrentId( tor ), fileIndex, doWrite );
|
2009-01-16 16:38:16 +00:00
|
|
|
|
2009-10-19 05:05:00 +00:00
|
|
|
if( fd < 0 )
|
|
|
|
{
|
|
|
|
/* the fd cache doesn't have this file...
|
|
|
|
* we'll need to open it and maybe create it */
|
2009-10-20 03:14:44 +00:00
|
|
|
char * subpath;
|
|
|
|
const char * base;
|
2009-10-19 05:05:00 +00:00
|
|
|
tr_bool fileExists;
|
2009-10-20 03:14:44 +00:00
|
|
|
tr_preallocation_mode preallocationMode;
|
2009-11-22 03:57:36 +00:00
|
|
|
|
2009-10-20 03:14:44 +00:00
|
|
|
fileExists = tr_torrentFindFile2( tor, fileIndex, &base, &subpath );
|
2009-10-19 05:05:00 +00:00
|
|
|
|
2009-10-20 03:14:44 +00:00
|
|
|
if( !fileExists )
|
|
|
|
{
|
2009-10-21 05:03:10 +00:00
|
|
|
base = tr_torrentGetCurrentDir( tor );
|
2009-10-19 05:05:00 +00:00
|
|
|
|
2009-10-20 04:43:51 +00:00
|
|
|
if( tr_sessionIsIncompleteFileNamingEnabled( tor->session ) )
|
2009-10-20 03:14:44 +00:00
|
|
|
subpath = tr_torrentBuildPartial( tor, fileIndex );
|
2009-10-19 05:05:00 +00:00
|
|
|
else
|
2009-10-20 03:14:44 +00:00
|
|
|
subpath = tr_strdup( file->name );
|
2009-10-19 05:05:00 +00:00
|
|
|
}
|
|
|
|
|
2009-11-08 23:43:38 +00:00
|
|
|
if( ( file->dnd ) || ( ioMode < TR_IO_WRITE ) )
|
2009-10-19 05:05:00 +00:00
|
|
|
preallocationMode = TR_PREALLOCATE_NONE;
|
|
|
|
else
|
|
|
|
preallocationMode = tor->session->preallocationMode;
|
|
|
|
|
2009-11-08 23:43:38 +00:00
|
|
|
if( ( ioMode < TR_IO_WRITE ) && !fileExists ) /* does file exist? */
|
2009-10-19 05:05:00 +00:00
|
|
|
{
|
|
|
|
err = ENOENT;
|
|
|
|
}
|
2009-10-21 19:33:37 +00:00
|
|
|
else
|
2009-10-19 05:05:00 +00:00
|
|
|
{
|
2009-10-21 19:33:37 +00:00
|
|
|
char * filename = tr_buildPath( base, subpath, NULL );
|
|
|
|
|
2009-10-23 03:41:36 +00:00
|
|
|
if( ( fd = tr_fdFileCheckout( session, tor->uniqueId, fileIndex, filename,
|
2009-10-21 19:33:37 +00:00
|
|
|
doWrite, preallocationMode, file->length ) ) < 0 )
|
|
|
|
{
|
|
|
|
err = errno;
|
|
|
|
tr_torerr( tor, "tr_fdFileCheckout failed for \"%s\": %s", filename, tr_strerror( err ) );
|
|
|
|
}
|
|
|
|
|
2009-10-20 03:14:44 +00:00
|
|
|
tr_free( filename );
|
2009-10-19 05:05:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if( doWrite && !err )
|
|
|
|
tr_statsFileCreated( tor->session );
|
2009-10-20 03:14:44 +00:00
|
|
|
|
|
|
|
tr_free( subpath );
|
2009-09-08 06:25:40 +00:00
|
|
|
}
|
2007-11-26 20:37:07 +00:00
|
|
|
|
2009-10-19 05:05:00 +00:00
|
|
|
if( !err )
|
|
|
|
{
|
2009-11-08 23:43:38 +00:00
|
|
|
if( ioMode == TR_IO_READ ) {
|
2009-11-25 19:26:30 +00:00
|
|
|
const int rc = tr_pread( fd, buf, buflen, fileOffset );
|
|
|
|
if( rc < 0 ) {
|
2009-11-08 23:43:38 +00:00
|
|
|
err = errno;
|
|
|
|
tr_torerr( tor, "read failed for \"%s\": %s",
|
|
|
|
file->name, tr_strerror( err ) );
|
|
|
|
}
|
|
|
|
} else if( ioMode == TR_IO_PREFETCH ) {
|
2009-11-25 19:26:30 +00:00
|
|
|
const int rc = tr_prefetch( fd, fileOffset, buflen );
|
|
|
|
if( rc < 0 ) {
|
|
|
|
tr_tordbg( tor, "prefetch failed for \"%s\": %s",
|
2009-11-08 23:43:38 +00:00
|
|
|
file->name, tr_strerror( err ) );
|
|
|
|
}
|
|
|
|
} else if( ioMode == TR_IO_WRITE ) {
|
2009-11-25 19:26:30 +00:00
|
|
|
const int rc = tr_pwrite( fd, buf, buflen, fileOffset );
|
|
|
|
if( rc < 0 ) {
|
2009-11-08 23:43:38 +00:00
|
|
|
err = errno;
|
|
|
|
tr_torerr( tor, "write failed for \"%s\": %s",
|
|
|
|
file->name, tr_strerror( err ) );
|
|
|
|
}
|
2009-11-25 19:26:30 +00:00
|
|
|
} else {
|
2009-11-08 23:43:38 +00:00
|
|
|
abort();
|
2009-11-25 19:26:30 +00:00
|
|
|
}
|
2009-10-19 05:05:00 +00:00
|
|
|
}
|
2008-09-23 19:11:04 +00:00
|
|
|
|
2008-01-18 19:13:32 +00:00
|
|
|
return err;
|
2007-05-02 19:35:34 +00:00
|
|
|
}
|
|
|
|
|
2008-04-20 21:54:44 +00:00
|
|
|
static int
|
2008-09-23 19:11:04 +00:00
|
|
|
compareOffsetToFile( const void * a,
|
|
|
|
const void * b )
|
2008-04-20 21:54:44 +00:00
|
|
|
{
|
2008-09-23 19:11:04 +00:00
|
|
|
const uint64_t offset = *(const uint64_t*)a;
|
2008-04-20 21:54:44 +00:00
|
|
|
const tr_file * file = b;
|
|
|
|
|
|
|
|
if( offset < file->offset ) return -1;
|
2008-04-20 22:06:05 +00:00
|
|
|
if( offset >= file->offset + file->length ) return 1;
|
2008-04-20 21:54:44 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-06-07 21:26:41 +00:00
|
|
|
void
|
|
|
|
tr_ioFindFileLocation( const tr_torrent * tor,
|
|
|
|
tr_piece_index_t pieceIndex,
|
|
|
|
uint32_t pieceOffset,
|
2008-11-20 17:19:59 +00:00
|
|
|
tr_file_index_t * fileIndex,
|
|
|
|
uint64_t * fileOffset )
|
2006-07-16 19:39:23 +00:00
|
|
|
{
|
2008-11-20 17:19:59 +00:00
|
|
|
const uint64_t offset = tr_pieceOffset( tor, pieceIndex, pieceOffset, 0 );
|
2008-04-20 21:54:44 +00:00
|
|
|
const tr_file * file;
|
|
|
|
|
|
|
|
file = bsearch( &offset,
|
2008-09-23 19:11:04 +00:00
|
|
|
tor->info.files, tor->info.fileCount, sizeof( tr_file ),
|
2008-04-20 21:54:44 +00:00
|
|
|
compareOffsetToFile );
|
2008-09-23 19:11:04 +00:00
|
|
|
|
2008-04-20 21:54:44 +00:00
|
|
|
*fileIndex = file - tor->info.files;
|
|
|
|
*fileOffset = offset - file->offset;
|
|
|
|
|
|
|
|
assert( *fileIndex < tor->info.fileCount );
|
|
|
|
assert( *fileOffset < file->length );
|
|
|
|
assert( tor->info.files[*fileIndex].offset + *fileOffset == offset );
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2008-10-03 04:49:06 +00:00
|
|
|
/* returns 0 on success, or an errno on failure */
|
|
|
|
static int
|
2009-08-13 17:25:26 +00:00
|
|
|
readOrWritePiece( tr_torrent * tor,
|
2008-09-23 19:11:04 +00:00
|
|
|
int ioMode,
|
|
|
|
tr_piece_index_t pieceIndex,
|
|
|
|
uint32_t pieceOffset,
|
2009-08-13 17:25:26 +00:00
|
|
|
uint8_t * buf,
|
2008-09-23 19:11:04 +00:00
|
|
|
size_t buflen )
|
2007-06-18 03:40:41 +00:00
|
|
|
{
|
2008-10-03 04:49:06 +00:00
|
|
|
int err = 0;
|
2008-03-22 18:10:59 +00:00
|
|
|
tr_file_index_t fileIndex;
|
2008-09-23 19:11:04 +00:00
|
|
|
uint64_t fileOffset;
|
2007-09-20 16:32:01 +00:00
|
|
|
const tr_info * info = &tor->info;
|
2007-05-27 23:32:26 +00:00
|
|
|
|
2008-04-18 23:17:40 +00:00
|
|
|
if( pieceIndex >= tor->info.pieceCount )
|
2008-10-03 04:49:06 +00:00
|
|
|
return EINVAL;
|
2010-06-19 14:25:11 +00:00
|
|
|
//if( pieceOffset + buflen > tr_torPieceCountBytes( tor, pieceIndex ) )
|
|
|
|
// return EINVAL;
|
2007-05-27 23:32:26 +00:00
|
|
|
|
2008-06-07 21:26:41 +00:00
|
|
|
tr_ioFindFileLocation( tor, pieceIndex, pieceOffset,
|
|
|
|
&fileIndex, &fileOffset );
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2008-01-18 19:13:32 +00:00
|
|
|
while( buflen && !err )
|
2006-07-16 19:39:23 +00:00
|
|
|
{
|
2007-09-20 16:32:01 +00:00
|
|
|
const tr_file * file = &info->files[fileIndex];
|
2009-09-08 06:25:40 +00:00
|
|
|
const uint64_t bytesThisPass = MIN( buflen, file->length - fileOffset );
|
2007-06-18 03:40:41 +00:00
|
|
|
|
2009-10-23 03:41:36 +00:00
|
|
|
err = readOrWriteBytes( tor->session, tor, ioMode, fileIndex, fileOffset, buf, bytesThisPass );
|
2007-06-18 03:40:41 +00:00
|
|
|
buf += bytesThisPass;
|
|
|
|
buflen -= bytesThisPass;
|
2010-06-19 14:25:11 +00:00
|
|
|
//fprintf( stderr, "++fileIndex to %d\n", (int)fileIndex );
|
2008-04-18 23:17:40 +00:00
|
|
|
++fileIndex;
|
2007-06-18 03:40:41 +00:00
|
|
|
fileOffset = 0;
|
2009-08-13 17:25:26 +00:00
|
|
|
|
2010-01-26 21:41:40 +00:00
|
|
|
if( ( err != 0 ) && (ioMode == TR_IO_WRITE ) )
|
|
|
|
{
|
2009-08-13 17:25:26 +00:00
|
|
|
char * path = tr_buildPath( tor->downloadDir, file->name, NULL );
|
|
|
|
tr_torrentSetLocalError( tor, "%s (%s)", tr_strerror( err ), path );
|
|
|
|
tr_free( path );
|
|
|
|
}
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2008-01-18 19:13:32 +00:00
|
|
|
return err;
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2008-10-03 04:49:06 +00:00
|
|
|
int
|
2009-08-13 17:25:26 +00:00
|
|
|
tr_ioRead( tr_torrent * tor,
|
2008-09-23 19:11:04 +00:00
|
|
|
tr_piece_index_t pieceIndex,
|
|
|
|
uint32_t begin,
|
|
|
|
uint32_t len,
|
2009-08-13 17:25:26 +00:00
|
|
|
uint8_t * buf )
|
2006-07-16 19:39:23 +00:00
|
|
|
{
|
2008-04-18 23:17:40 +00:00
|
|
|
return readOrWritePiece( tor, TR_IO_READ, pieceIndex, begin, buf, len );
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2009-11-08 23:43:38 +00:00
|
|
|
int
|
|
|
|
tr_ioPrefetch( tr_torrent * tor,
|
|
|
|
tr_piece_index_t pieceIndex,
|
|
|
|
uint32_t begin,
|
|
|
|
uint32_t len)
|
|
|
|
{
|
|
|
|
return readOrWritePiece( tor, TR_IO_PREFETCH, pieceIndex, begin,
|
|
|
|
NULL, len );
|
|
|
|
}
|
|
|
|
|
2008-10-03 04:49:06 +00:00
|
|
|
int
|
2009-08-13 17:25:26 +00:00
|
|
|
tr_ioWrite( tr_torrent * tor,
|
2008-09-23 19:11:04 +00:00
|
|
|
tr_piece_index_t pieceIndex,
|
|
|
|
uint32_t begin,
|
|
|
|
uint32_t len,
|
2009-08-13 17:25:26 +00:00
|
|
|
const uint8_t * buf )
|
2006-07-16 19:39:23 +00:00
|
|
|
{
|
2008-09-23 19:11:04 +00:00
|
|
|
return readOrWritePiece( tor, TR_IO_WRITE, pieceIndex, begin,
|
|
|
|
(uint8_t*)buf,
|
|
|
|
len );
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2007-06-18 03:40:41 +00:00
|
|
|
/****
|
|
|
|
*****
|
|
|
|
****/
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2008-12-31 18:08:13 +00:00
|
|
|
static tr_bool
|
2009-08-13 17:25:26 +00:00
|
|
|
recalculateHash( tr_torrent * tor,
|
2008-09-23 19:11:04 +00:00
|
|
|
tr_piece_index_t pieceIndex,
|
2009-08-13 17:25:26 +00:00
|
|
|
uint8_t * setme )
|
2007-06-18 03:40:41 +00:00
|
|
|
{
|
2008-12-11 00:39:47 +00:00
|
|
|
size_t bytesLeft;
|
2008-09-06 13:25:21 +00:00
|
|
|
uint32_t offset = 0;
|
2008-12-31 18:08:13 +00:00
|
|
|
tr_bool success = TRUE;
|
2010-07-08 23:04:12 +00:00
|
|
|
const size_t buflen = MAX_BLOCK_SIZE;
|
2010-07-07 16:31:07 +00:00
|
|
|
void * buffer = tr_valloc( buflen );
|
2008-09-23 19:11:04 +00:00
|
|
|
SHA_CTX sha;
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2008-12-31 18:08:13 +00:00
|
|
|
assert( tor != NULL );
|
2008-03-22 18:10:59 +00:00
|
|
|
assert( pieceIndex < tor->info.pieceCount );
|
2008-12-31 18:08:13 +00:00
|
|
|
assert( buffer != NULL );
|
|
|
|
assert( buflen > 0 );
|
|
|
|
assert( setme != NULL );
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2008-09-06 13:25:21 +00:00
|
|
|
SHA1_Init( &sha );
|
2008-11-05 00:21:30 +00:00
|
|
|
bytesLeft = tr_torPieceCountBytes( tor, pieceIndex );
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2010-04-30 00:04:15 +00:00
|
|
|
tr_ioPrefetch( tor, pieceIndex, offset, bytesLeft );
|
|
|
|
|
2008-09-06 13:25:21 +00:00
|
|
|
while( bytesLeft )
|
|
|
|
{
|
2008-12-31 18:08:13 +00:00
|
|
|
const int len = MIN( bytesLeft, buflen );
|
2010-06-19 14:25:11 +00:00
|
|
|
success = !tr_cacheReadBlock( tor->session->cache, tor, pieceIndex, offset, len, buffer );
|
2008-10-03 00:51:40 +00:00
|
|
|
if( !success )
|
2008-09-06 13:25:21 +00:00
|
|
|
break;
|
2008-12-31 18:08:13 +00:00
|
|
|
SHA1_Update( &sha, buffer, len );
|
2008-09-06 13:25:21 +00:00
|
|
|
offset += len;
|
|
|
|
bytesLeft -= len;
|
2008-01-27 17:03:58 +00:00
|
|
|
}
|
2008-09-06 13:25:21 +00:00
|
|
|
|
2008-10-03 00:51:40 +00:00
|
|
|
if( success )
|
2008-09-06 13:25:21 +00:00
|
|
|
SHA1_Final( setme, &sha );
|
2006-07-16 19:39:23 +00:00
|
|
|
|
2010-06-19 14:25:11 +00:00
|
|
|
tr_free( buffer );
|
2008-10-03 00:51:40 +00:00
|
|
|
return success;
|
2006-07-16 19:39:23 +00:00
|
|
|
}
|
|
|
|
|
2008-12-31 18:08:13 +00:00
|
|
|
tr_bool
|
2010-02-23 06:05:18 +00:00
|
|
|
tr_ioTestPiece( tr_torrent * tor, tr_piece_index_t piece )
|
2006-07-16 19:39:23 +00:00
|
|
|
{
|
2007-06-18 03:40:41 +00:00
|
|
|
uint8_t hash[SHA_DIGEST_LENGTH];
|
2008-09-23 19:11:04 +00:00
|
|
|
|
2010-02-23 06:05:18 +00:00
|
|
|
return recalculateHash( tor, piece, hash )
|
|
|
|
&& !memcmp( hash, tor->info.pieces[piece].hash, SHA_DIGEST_LENGTH );
|
2008-12-31 18:08:13 +00:00
|
|
|
}
|