1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 17:47:37 +00:00
transmission/libtransmission/fdlimit.c
Jordan Lee 35fb00fd9e (trunk libt) #4315 "Transmission 2.31 crashes (segfaults) immediately after launch" -- remove the "max-open-files" code.
max-open-files might have been a nice configuration option once, but (1) we've never advertised it in the gui apps, and (2) the crazy cases are causing more trouble than this feature is worth. It's more complicated now after #4164 -- see #4294, #4311, and this ticket.
2011-06-24 22:39:20 +00:00

747 lines
18 KiB
C

/*
* This file Copyright (C) Mnemosyne LLC
*
* 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.
*
* $Id$
*/
#ifdef HAVE_POSIX_FADVISE
#ifdef _XOPEN_SOURCE
#undef _XOPEN_SOURCE
#endif
#define _XOPEN_SOURCE 600
#endif
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <string.h>
#ifdef SYS_DARWIN
#include <fcntl.h>
#endif
#ifdef HAVE_FALLOCATE64
/* FIXME can't find the right #include voodoo to pick up the declaration.. */
extern int fallocate64( int fd, int mode, uint64_t offset, uint64_t len );
#endif
#ifdef HAVE_XFS_XFS_H
#include <xfs/xfs.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_GETRLIMIT
#include <sys/time.h> /* getrlimit */
#include <sys/resource.h> /* getrlimit */
#endif
#include <fcntl.h> /* O_LARGEFILE posix_fadvise */
#include <unistd.h> /* lseek(), write(), ftruncate(), pread(), pwrite(), etc */
#include "transmission.h"
#include "fdlimit.h"
#include "net.h"
#include "session.h"
#include "torrent.h" /* tr_isTorrent() */
#define dbgmsg( ... ) \
do { \
if( tr_deepLoggingIsActive( ) ) \
tr_deepLog( __FILE__, __LINE__, NULL, __VA_ARGS__ ); \
} while( 0 )
/***
****
**** Local Files
****
***/
#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif
#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifndef O_SEQUENTIAL
#define O_SEQUENTIAL 0
#endif
static bool
preallocate_file_sparse( int fd, uint64_t length )
{
const char zero = '\0';
bool success = 0;
if( !length )
success = true;
#ifdef HAVE_FALLOCATE64
if( !success ) /* fallocate64 is always preferred, so try it first */
success = !fallocate64( fd, 0, 0, length );
#endif
if( !success ) /* fallback: the old-style seek-and-write */
success = ( lseek( fd, length-1, SEEK_SET ) != -1 )
&& ( write( fd, &zero, 1 ) != -1 )
&& ( ftruncate( fd, length ) != -1 );
return success;
}
static bool
preallocate_file_full( const char * filename, uint64_t length )
{
bool success = 0;
#ifdef WIN32
HANDLE hFile = CreateFile( filename, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_FLAG_RANDOM_ACCESS, 0 );
if( hFile != INVALID_HANDLE_VALUE )
{
LARGE_INTEGER li;
li.QuadPart = length;
success = SetFilePointerEx( hFile, li, NULL, FILE_BEGIN ) && SetEndOfFile( hFile );
CloseHandle( hFile );
}
#else
int flags = O_RDWR | O_CREAT | O_LARGEFILE;
int fd = open( filename, flags, 0666 );
if( fd >= 0 )
{
# ifdef HAVE_FALLOCATE64
if( !success )
{
success = !fallocate64( fd, 0, 0, length );
}
# endif
# ifdef HAVE_XFS_XFS_H
if( !success && platform_test_xfs_fd( fd ) )
{
xfs_flock64_t fl;
fl.l_whence = 0;
fl.l_start = 0;
fl.l_len = length;
success = !xfsctl( NULL, fd, XFS_IOC_RESVSP64, &fl );
}
# endif
# ifdef SYS_DARWIN
if( !success )
{
fstore_t fst;
fst.fst_flags = F_ALLOCATECONTIG;
fst.fst_posmode = F_PEOFPOSMODE;
fst.fst_offset = 0;
fst.fst_length = length;
fst.fst_bytesalloc = 0;
success = !fcntl( fd, F_PREALLOCATE, &fst );
}
# endif
# ifdef HAVE_POSIX_FALLOCATE
if( !success )
{
success = !posix_fallocate( fd, 0, length );
}
# endif
if( !success ) /* if nothing else works, do it the old-fashioned way */
{
uint8_t buf[ 4096 ];
memset( buf, 0, sizeof( buf ) );
success = true;
while ( success && ( length > 0 ) )
{
const int thisPass = MIN( length, sizeof( buf ) );
success = write( fd, buf, thisPass ) == thisPass;
length -= thisPass;
}
}
close( fd );
}
#endif
return success;
}
/* portability wrapper for fsync(). */
int
tr_fsync( int fd )
{
#ifdef WIN32
return _commit( fd );
#else
return fsync( fd );
#endif
}
/* Like pread and pwrite, except that the position is undefined afterwards.
And of course they are not thread-safe. */
/* don't use pread/pwrite on old versions of uClibc because they're buggy.
* https://trac.transmissionbt.com/ticket/3826 */
#ifdef __UCLIBC__
#define TR_UCLIBC_CHECK_VERSION(major,minor,micro) \
(__UCLIBC_MAJOR__ > (major) || \
(__UCLIBC_MAJOR__ == (major) && __UCLIBC_MINOR__ > (minor)) || \
(__UCLIBC_MAJOR__ == (major) && __UCLIBC_MINOR__ == (minor) && \
__UCLIBC_SUBLEVEL__ >= (micro)))
#if !TR_UCLIBC_CHECK_VERSION(0,9,28)
#undef HAVE_PREAD
#undef HAVE_PWRITE
#endif
#endif
#ifdef SYS_DARWIN
#define HAVE_PREAD
#define HAVE_PWRITE
#endif
ssize_t
tr_pread( int fd, void *buf, size_t count, off_t offset )
{
#ifdef HAVE_PREAD
return pread( fd, buf, count, offset );
#else
const off_t lrc = lseek( fd, offset, SEEK_SET );
if( lrc < 0 )
return -1;
return read( fd, buf, count );
#endif
}
ssize_t
tr_pwrite( int fd, const void *buf, size_t count, off_t offset )
{
#ifdef HAVE_PWRITE
return pwrite( fd, buf, count, offset );
#else
const off_t lrc = lseek( fd, offset, SEEK_SET );
if( lrc < 0 )
return -1;
return write( fd, buf, count );
#endif
}
int
tr_prefetch( int fd UNUSED, off_t offset UNUSED, size_t count UNUSED )
{
#ifdef HAVE_POSIX_FADVISE
return posix_fadvise( fd, offset, count, POSIX_FADV_WILLNEED );
#elif defined(SYS_DARWIN)
struct radvisory radv;
radv.ra_offset = offset;
radv.ra_count = count;
return fcntl( fd, F_RDADVISE, &radv );
#else
return 0;
#endif
}
void
tr_set_file_for_single_pass( int fd )
{
if( fd >= 0 )
{
/* Set hints about the lookahead buffer and caching. It's okay
for these to fail silently, so don't let them affect errno */
const int err = errno;
#ifdef HAVE_POSIX_FADVISE
posix_fadvise( fd, 0, 0, POSIX_FADV_SEQUENTIAL );
#endif
#ifdef SYS_DARWIN
fcntl( fd, F_RDAHEAD, 1 );
fcntl( fd, F_NOCACHE, 1 );
#endif
errno = err;
}
}
static int
open_local_file( const char * filename, int flags )
{
const int fd = open( filename, flags, 0666 );
tr_set_file_for_single_pass( fd );
return fd;
}
int
tr_open_file_for_writing( const char * filename )
{
return open_local_file( filename, O_LARGEFILE|O_BINARY|O_CREAT|O_WRONLY );
}
int
tr_open_file_for_scanning( const char * filename )
{
return open_local_file( filename, O_LARGEFILE|O_BINARY|O_SEQUENTIAL|O_RDONLY );
}
void
tr_close_file( int fd )
{
#if defined(HAVE_POSIX_FADVISE)
/* Set hint about not caching this file.
It's okay for this to fail silently, so don't let it affect errno */
const int err = errno;
posix_fadvise( fd, 0, 0, POSIX_FADV_DONTNEED );
errno = err;
#endif
#ifdef SYS_DARWIN
/* it's unclear to me from the man pages if this actually flushes out the cache,
* but it couldn't hurt... */
fcntl( fd, F_NOCACHE, 1 );
#endif
close( fd );
}
/*****
******
******
******
*****/
struct tr_cached_file
{
bool is_writable;
int fd;
int torrent_id;
tr_file_index_t file_index;
time_t used_at;
};
static inline bool
cached_file_is_open( const struct tr_cached_file * o )
{
assert( o != NULL );
return o->fd >= 0;
}
static void
cached_file_close( struct tr_cached_file * o )
{
assert( cached_file_is_open( o ) );
tr_close_file( o->fd );
o->fd = -1;
}
/**
* returns 0 on success, or an errno value on failure.
* errno values include ENOENT if the parent folder doesn't exist,
* plus the errno values set by tr_mkdirp() and open().
*/
static int
cached_file_open( struct tr_cached_file * o,
const char * existing_dir,
const char * filename,
bool writable,
tr_preallocation_mode allocation,
uint64_t file_size )
{
int flags;
struct stat sb;
bool alreadyExisted;
/* confirm that existing_dir, if specified, exists on the disk */
if( existing_dir && *existing_dir && stat( existing_dir, &sb ) )
{
const int err = errno;
tr_err( _( "Couldn't open \"%1$s\": %2$s" ), existing_dir, tr_strerror( err ) );
return err;
}
/* create subfolders, if any */
if( writable )
{
char * dir = tr_dirname( filename );
const int err = tr_mkdirp( dir, 0777 ) ? errno : 0;
if( err ) {
tr_err( _( "Couldn't create \"%1$s\": %2$s" ), dir, tr_strerror( err ) );
tr_free( dir );
return err;
}
tr_free( dir );
}
alreadyExisted = !stat( filename, &sb ) && S_ISREG( sb.st_mode );
if( writable && !alreadyExisted && ( allocation == TR_PREALLOCATE_FULL ) )
if( preallocate_file_full( filename, file_size ) )
tr_dbg( "Preallocated file \"%s\"", filename );
/* open the file */
flags = writable ? ( O_RDWR | O_CREAT ) : O_RDONLY;
flags |= O_LARGEFILE | O_BINARY | O_SEQUENTIAL;
o->fd = open( filename, flags, 0666 );
if( o->fd == -1 )
{
const int err = errno;
tr_err( _( "Couldn't open \"%1$s\": %2$s" ), filename, tr_strerror( err ) );
return err;
}
/* If the file already exists and it's too large, truncate it.
* This is a fringe case that happens if a torrent's been updated
* and one of the updated torrent's files is smaller.
* http://trac.transmissionbt.com/ticket/2228
* https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/318249
*/
if( alreadyExisted && ( file_size < (uint64_t)sb.st_size ) )
{
if( ftruncate( o->fd, file_size ) == -1 )
{
const int err = errno;
tr_err( _( "Couldn't truncate \"%1$s\": %2$s" ), filename, tr_strerror( err ) );
return err;
}
}
if( writable && !alreadyExisted && ( allocation == TR_PREALLOCATE_SPARSE ) )
preallocate_file_sparse( o->fd, file_size );
/* Many (most?) clients request blocks in ascending order,
* so increase the readahead buffer.
* Also, disable OS-level caching because "inactive memory" angers users. */
tr_set_file_for_single_pass( o->fd );
return 0;
}
/***
****
***/
struct tr_fileset
{
struct tr_cached_file * begin;
const struct tr_cached_file * end;
};
static void
fileset_construct( struct tr_fileset * set, int n )
{
struct tr_cached_file * o;
const struct tr_cached_file TR_CACHED_FILE_INIT = { 0, -1, 0, 0, 0 };
set->begin = tr_new( struct tr_cached_file, n );
set->end = set->begin + n;
for( o=set->begin; o!=set->end; ++o )
*o = TR_CACHED_FILE_INIT;
}
static void
fileset_close_all( struct tr_fileset * set )
{
struct tr_cached_file * o;
if( set != NULL )
for( o=set->begin; o!=set->end; ++o )
if( cached_file_is_open( o ) )
cached_file_close( o );
}
static void
fileset_destruct( struct tr_fileset * set )
{
fileset_close_all( set );
tr_free( set->begin );
set->end = set->begin = NULL;
}
static void
fileset_close_torrent( struct tr_fileset * set, int torrent_id )
{
struct tr_cached_file * o;
if( set != NULL )
for( o=set->begin; o!=set->end; ++o )
if( ( o->torrent_id == torrent_id ) && cached_file_is_open( o ) )
cached_file_close( o );
}
static struct tr_cached_file *
fileset_lookup( struct tr_fileset * set, int torrent_id, tr_file_index_t i )
{
struct tr_cached_file * o;
if( set != NULL )
for( o=set->begin; o!=set->end; ++o )
if( ( torrent_id == o->torrent_id ) && ( i == o->file_index ) && cached_file_is_open( o ) )
return o;
return NULL;
}
static struct tr_cached_file *
fileset_get_empty_slot( struct tr_fileset * set )
{
struct tr_cached_file * o;
struct tr_cached_file * cull;
/* try to find an unused slot */
for( o=set->begin; o!=set->end; ++o )
if( !cached_file_is_open( o ) )
return o;
/* all slots are full... recycle the least recently used */
for( cull=NULL, o=set->begin; o!=set->end; ++o )
if( !cull || o->used_at < cull->used_at )
cull = o;
cached_file_close( cull );
return cull;
}
/***
****
**** Startup / Shutdown
****
***/
struct tr_fdInfo
{
int peerCount;
struct tr_fileset fileset;
};
static void
ensureSessionFdInfoExists( tr_session * session )
{
assert( tr_isSession( session ) );
if( session->fdInfo == NULL )
{
const int TR_MAX_OPEN_FILES = 32;
struct tr_fdInfo * i = tr_new0( struct tr_fdInfo, 1 );
fileset_construct( &i->fileset, TR_MAX_OPEN_FILES );
session->fdInfo = i;
}
}
void
tr_fdClose( tr_session * session )
{
if( session && session->fdInfo )
{
struct tr_fdInfo * i = session->fdInfo;
fileset_destruct( &i->fileset );
tr_free( i );
session->fdInfo = NULL;
}
}
/***
****
***/
static struct tr_fileset*
get_fileset( tr_session * session )
{
if( !session )
return NULL;
ensureSessionFdInfoExists( session );
return &session->fdInfo->fileset;
}
void
tr_fdFileClose( tr_session * s, const tr_torrent * tor, tr_file_index_t i )
{
struct tr_cached_file * o;
if(( o = fileset_lookup( get_fileset( s ), tr_torrentId( tor ), i )))
{
/* flush writable files so that their mtimes will be
* up-to-date when this function returns to the caller... */
if( o->is_writable )
tr_fsync( o->fd );
cached_file_close( o );
}
}
int
tr_fdFileGetCached( tr_session * s, int torrent_id, tr_file_index_t i, bool writable )
{
struct tr_cached_file * o = fileset_lookup( get_fileset( s ), torrent_id, i );
if( !o || ( writable && !o->is_writable ) )
return -1;
o->used_at = tr_time( );
return o->fd;
}
#ifdef SYS_DARWIN
#define TR_STAT_MTIME(sb) ((sb).st_mtimespec.tv_sec)
#else
#define TR_STAT_MTIME(sb) ((sb).st_mtime)
#endif
bool
tr_fdFileGetCachedMTime( tr_session * s, int torrent_id, tr_file_index_t i, time_t * mtime )
{
bool success;
struct stat sb;
struct tr_cached_file * o = fileset_lookup( get_fileset( s ), torrent_id, i );
if(( success = ( o != NULL ) && !fstat( o->fd, &sb )))
*mtime = TR_STAT_MTIME( sb );
return success;
}
void
tr_fdTorrentClose( tr_session * session, int torrent_id )
{
fileset_close_torrent( get_fileset( session ), torrent_id );
}
/* returns an fd on success, or a -1 on failure and sets errno */
int
tr_fdFileCheckout( tr_session * session,
int torrent_id,
tr_file_index_t i,
const char * existing_dir,
const char * filename,
bool writable,
tr_preallocation_mode allocation,
uint64_t file_size )
{
struct tr_fileset * set = get_fileset( session );
struct tr_cached_file * o = fileset_lookup( set, torrent_id, i );
if( o && writable && !o->is_writable )
cached_file_close( o ); /* close it so we can reopen in rw mode */
else if( !o )
o = fileset_get_empty_slot( set );
if( !cached_file_is_open( o ) )
{
const int err = cached_file_open( o, existing_dir, filename, writable, allocation, file_size );
if( err ) {
errno = err;
return -1;
}
dbgmsg( "opened '%s' writable %c", filename, writable?'y':'n' );
o->is_writable = writable;
}
dbgmsg( "checking out '%s'", filename );
o->torrent_id = torrent_id;
o->file_index = i;
o->used_at = tr_time( );
return o->fd;
}
/***
****
**** Sockets
****
***/
int
tr_fdSocketCreate( tr_session * session, int domain, int type )
{
int s = -1;
struct tr_fdInfo * gFd;
assert( tr_isSession( session ) );
ensureSessionFdInfoExists( session );
gFd = session->fdInfo;
if( gFd->peerCount < session->peerLimit )
if(( s = socket( domain, type, 0 )) < 0 )
if( sockerrno != EAFNOSUPPORT )
tr_err( _( "Couldn't create socket: %s" ), tr_strerror( sockerrno ) );
if( s > -1 )
++gFd->peerCount;
assert( gFd->peerCount >= 0 );
if( s >= 0 )
{
static bool buf_logged = false;
if( !buf_logged )
{
int i;
socklen_t size = sizeof( int );
buf_logged = true;
getsockopt( s, SOL_SOCKET, SO_SNDBUF, &i, &size );
tr_dbg( "SO_SNDBUF size is %d", i );
getsockopt( s, SOL_SOCKET, SO_RCVBUF, &i, &size );
tr_dbg( "SO_RCVBUF size is %d", i );
}
}
return s;
}
int
tr_fdSocketAccept( tr_session * s, int sockfd, tr_address * addr, tr_port * port )
{
int fd;
unsigned int len;
struct tr_fdInfo * gFd;
struct sockaddr_storage sock;
assert( tr_isSession( s ) );
assert( addr );
assert( port );
ensureSessionFdInfoExists( s );
gFd = s->fdInfo;
len = sizeof( struct sockaddr_storage );
fd = accept( sockfd, (struct sockaddr *) &sock, &len );
if( fd >= 0 )
{
if( ( gFd->peerCount < s->peerLimit )
&& tr_address_from_sockaddr_storage( addr, port, &sock ) )
{
++gFd->peerCount;
}
else
{
tr_netCloseSocket( fd );
fd = -1;
}
}
return fd;
}
void
tr_fdSocketClose( tr_session * session, int fd )
{
assert( tr_isSession( session ) );
if( session->fdInfo != NULL )
{
struct tr_fdInfo * gFd = session->fdInfo;
if( fd >= 0 )
{
tr_netCloseSocket( fd );
--gFd->peerCount;
}
assert( gFd->peerCount >= 0 );
}
}