source: trunk/libtransmission/fdlimit.c @ 11602

Last change on this file since 11602 was 11602, checked in by charles, 11 years ago

(trunk libT) refactor libtransmission's file cache

  • Property svn:keywords set to Date Rev Author Id
File size: 18.8 KB
Line 
1/*
2 * This file Copyright (C) 2010 Mnemosyne LLC
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: fdlimit.c 11602 2010-12-28 07:24:10Z charles $
11 */
12
13#ifndef WIN32
14 #define HAVE_GETRLIMIT
15#endif
16
17#ifdef HAVE_POSIX_FADVISE
18 #ifdef _XOPEN_SOURCE
19  #undef _XOPEN_SOURCE
20 #endif
21 #define _XOPEN_SOURCE 600
22#endif
23
24#include <assert.h>
25#include <errno.h>
26#include <inttypes.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#ifdef SYS_DARWIN
31 #include <fcntl.h>
32#endif
33
34#ifdef HAVE_FALLOCATE64
35  /* FIXME can't find the right #include voodoo to pick up the declaration.. */
36  extern int fallocate64( int fd, int mode, uint64_t offset, uint64_t len );
37#endif
38
39#ifdef HAVE_XFS_XFS_H
40 #include <xfs/xfs.h>
41#endif
42
43#include <sys/types.h>
44#include <sys/stat.h>
45#ifdef HAVE_GETRLIMIT
46 #include <sys/time.h> /* getrlimit */
47 #include <sys/resource.h> /* getrlimit */
48#endif
49#include <fcntl.h> /* O_LARGEFILE posix_fadvise */
50#include <unistd.h>
51
52#include "transmission.h"
53#include "fdlimit.h"
54#include "net.h"
55#include "session.h"
56#include "torrent.h" /* tr_isTorrent() */
57
58#define dbgmsg( ... ) \
59    do { \
60        if( tr_deepLoggingIsActive( ) ) \
61            tr_deepLog( __FILE__, __LINE__, NULL, __VA_ARGS__ ); \
62    } while( 0 )
63
64/***
65****
66****  Local Files
67****
68***/
69
70#ifndef O_LARGEFILE
71 #define O_LARGEFILE 0
72#endif
73
74static tr_bool
75preallocateFileSparse( int fd, uint64_t length )
76{
77    const char zero = '\0';
78    tr_bool success = 0;
79
80    if( !length )
81        success = TRUE;
82
83#ifdef HAVE_FALLOCATE64
84    if( !success ) /* fallocate64 is always preferred, so try it first */
85        success = !fallocate64( fd, 0, 0, length );
86#endif
87
88    if( !success ) /* fallback: the old-style seek-and-write */
89        success = ( lseek( fd, length-1, SEEK_SET ) != -1 )
90               && ( write( fd, &zero, 1 ) != -1 )
91               && ( ftruncate( fd, length ) != -1 );
92
93    return success;
94}
95
96static tr_bool
97preallocateFileFull( const char * filename, uint64_t length )
98{
99    tr_bool success = 0;
100
101#ifdef WIN32
102
103    HANDLE hFile = CreateFile( filename, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_FLAG_RANDOM_ACCESS, 0 );
104    if( hFile != INVALID_HANDLE_VALUE )
105    {
106        LARGE_INTEGER li;
107        li.QuadPart = length;
108        success = SetFilePointerEx( hFile, li, NULL, FILE_BEGIN ) && SetEndOfFile( hFile );
109        CloseHandle( hFile );
110    }
111
112#else
113
114    int flags = O_RDWR | O_CREAT | O_LARGEFILE;
115    int fd = open( filename, flags, 0666 );
116    if( fd >= 0 )
117    {
118# ifdef HAVE_FALLOCATE64
119       if( !success )
120       {
121           success = !fallocate64( fd, 0, 0, length );
122       }
123# endif
124# ifdef HAVE_XFS_XFS_H
125        if( !success && platform_test_xfs_fd( fd ) )
126        {
127            xfs_flock64_t fl;
128            fl.l_whence = 0;
129            fl.l_start = 0;
130            fl.l_len = length;
131            success = !xfsctl( NULL, fd, XFS_IOC_RESVSP64, &fl );
132        }
133# endif
134# ifdef SYS_DARWIN
135        if( !success )
136        {
137            fstore_t fst;
138            fst.fst_flags = F_ALLOCATECONTIG;
139            fst.fst_posmode = F_PEOFPOSMODE;
140            fst.fst_offset = 0;
141            fst.fst_length = length;
142            fst.fst_bytesalloc = 0;
143            success = !fcntl( fd, F_PREALLOCATE, &fst );
144        }
145# endif
146# ifdef HAVE_POSIX_FALLOCATE
147        if( !success )
148        {
149            success = !posix_fallocate( fd, 0, length );
150        }
151# endif
152
153        if( !success ) /* if nothing else works, do it the old-fashioned way */
154        {
155            uint8_t buf[ 4096 ];
156            memset( buf, 0, sizeof( buf ) );
157            success = TRUE;
158            while ( success && ( length > 0 ) )
159            {
160                const int thisPass = MIN( length, sizeof( buf ) );
161                success = write( fd, buf, thisPass ) == thisPass;
162                length -= thisPass;
163            }
164        }
165
166        close( fd );
167    }
168
169#endif
170
171    return success;
172}
173
174/* Like pread and pwrite, except that the position is undefined afterwards.
175   And of course they are not thread-safe. */
176
177/* don't use pread/pwrite on old versions of uClibc because they're buggy.
178 * https://trac.transmissionbt.com/ticket/3826 */
179#ifdef __UCLIBC__
180#define TR_UCLIBC_CHECK_VERSION(major,minor,micro) \
181    (__UCLIBC_MAJOR__ > (major) || \
182     (__UCLIBC_MAJOR__ == (major) && __UCLIBC_MINOR__ > (minor)) || \
183     (__UCLIBC_MAJOR__ == (major) && __UCLIBC_MINOR__ == (minor) && \
184      __UCLIBC_SUBLEVEL__ >= (micro)))
185#if !TR_UCLIBC_CHECK_VERSION(0,9,28)
186 #undef HAVE_PREAD
187 #undef HAVE_PWRITE
188#endif
189#endif
190
191#ifdef SYS_DARWIN
192 #define HAVE_PREAD
193 #define HAVE_PWRITE
194#endif
195
196ssize_t
197tr_pread( int fd, void *buf, size_t count, off_t offset )
198{
199#ifdef HAVE_PREAD
200    return pread( fd, buf, count, offset );
201#else
202    const off_t lrc = lseek( fd, offset, SEEK_SET );
203    if( lrc < 0 )
204        return -1;
205    return read( fd, buf, count );
206#endif
207}
208
209ssize_t
210tr_pwrite( int fd, const void *buf, size_t count, off_t offset )
211{
212#ifdef HAVE_PWRITE
213    return pwrite( fd, buf, count, offset );
214#else
215    const off_t lrc = lseek( fd, offset, SEEK_SET );
216    if( lrc < 0 )
217        return -1;
218    return write( fd, buf, count );
219#endif
220}
221
222int
223tr_prefetch( int fd UNUSED, off_t offset UNUSED, size_t count UNUSED )
224{
225#ifdef HAVE_POSIX_FADVISE
226    return posix_fadvise( fd, offset, count, POSIX_FADV_WILLNEED );
227#elif defined(SYS_DARWIN)
228    struct radvisory radv;
229    radv.ra_offset = offset;
230    radv.ra_count = count;
231    return fcntl( fd, F_RDADVISE, &radv );
232#else
233    return 0;
234#endif
235}
236
237void
238tr_set_file_for_single_pass( int fd )
239{
240    if( fd >= 0 )
241    {
242        /* Set hints about the lookahead buffer and caching. It's okay
243           for these to fail silently, so don't let them affect errno */
244        const int err = errno;
245#ifdef HAVE_POSIX_FADVISE
246        posix_fadvise( fd, 0, 0, POSIX_FADV_SEQUENTIAL );
247#endif
248#ifdef SYS_DARWIN
249        fcntl( fd, F_RDAHEAD, 1 );
250        fcntl( fd, F_NOCACHE, 1 );
251#endif
252        errno = err;
253    }
254}
255
256int
257tr_open_file_for_writing( const char * filename )
258{
259    int fd;
260    int flags = O_WRONLY | O_CREAT;
261#ifdef O_BINARY
262    flags |= O_BINARY;
263#endif
264#ifdef O_LARGEFILE
265    flags |= O_LARGEFILE;
266#endif
267    fd = open( filename, flags, 0666 );
268    tr_set_file_for_single_pass( fd );
269    return fd;
270}
271
272int
273tr_open_file_for_scanning( const char * filename )
274{
275    int fd;
276    int flags;
277
278    /* build the flags */
279    flags = O_RDONLY;
280#ifdef O_SEQUENTIAL
281    flags |= O_SEQUENTIAL;
282#endif
283#ifdef O_BINARY
284    flags |= O_BINARY;
285#endif
286#ifdef O_LARGEFILE
287    flags |= O_LARGEFILE;
288#endif
289
290    /* open the file */
291    fd = open( filename, flags, 0666 );
292    tr_set_file_for_single_pass( fd );
293    return fd;
294}
295
296void
297tr_close_file( int fd )
298{
299#if defined(HAVE_POSIX_FADVISE)
300    /* Set hint about not caching this file.
301       It's okay for this to fail silently, so don't let it affect errno */
302    const int err = errno;
303    posix_fadvise( fd, 0, 0, POSIX_FADV_DONTNEED );
304    errno = err;
305#endif
306#ifdef SYS_DARWIN
307    /* it's unclear to me from the man pages if this actually flushes out the cache,
308     * but it couldn't hurt... */
309    fcntl( fd, F_NOCACHE, 1 );
310#endif
311    close( fd );
312}
313
314/*****
315******
316******
317******
318*****/
319
320struct tr_cached_file
321{
322    tr_bool          is_writable;
323    int              fd;
324    int              torrent_id;
325    tr_file_index_t  file_index;
326    time_t           used_at;
327};
328
329static inline tr_bool
330cached_file_is_open( const struct tr_cached_file * o )
331{
332    assert( o != NULL );
333
334    return o->fd >= 0;
335}
336
337static void
338cached_file_close( struct tr_cached_file * o )
339{
340    assert( cached_file_is_open( o ) );
341
342    tr_close_file( o->fd );
343    o->fd = -1;
344}
345
346/**
347 * returns 0 on success, or an errno value on failure.
348 * errno values include ENOENT if the parent folder doesn't exist,
349 * plus the errno values set by tr_mkdirp() and open().
350 */
351static int
352cached_file_open( struct tr_cached_file  * o,
353                  const char             * filename,
354                  tr_bool                  writable,
355                  tr_preallocation_mode    allocation,
356                  uint64_t                 file_size )
357{
358    int flags;
359    struct stat sb;
360    tr_bool alreadyExisted;
361
362    /* create subfolders, if any */
363    if( writable )
364    {
365        char * dir = tr_dirname( filename );
366        const int err = tr_mkdirp( dir, 0777 ) ? errno : 0;
367        if( err ) {
368            tr_err( _( "Couldn't create \"%1$s\": %2$s" ), dir, tr_strerror( err ) );
369            tr_free( dir );
370            return err;
371        }
372        tr_free( dir );
373    }
374
375    alreadyExisted = !stat( filename, &sb ) && S_ISREG( sb.st_mode );
376
377    if( writable && !alreadyExisted && ( allocation == TR_PREALLOCATE_FULL ) )
378        if( preallocateFileFull( filename, file_size ) )
379            tr_dbg( "Preallocated file \"%s\"", filename );
380
381    /* open the file */
382    flags = writable ? ( O_RDWR | O_CREAT ) : O_RDONLY;
383#ifdef O_SEQUENTIAL
384    flags |= O_SEQUENTIAL;
385#endif
386#ifdef O_LARGEFILE
387    flags |= O_LARGEFILE;
388#endif
389#ifdef WIN32
390    flags |= O_BINARY;
391#endif
392    o->fd = open( filename, flags, 0666 );
393
394    if( o->fd == -1 )
395    {
396        const int err = errno;
397        tr_err( ( "Couldn't open \"%1$s\": %2$s" ), filename, tr_strerror( err ) );
398        return err;
399    }
400
401    /* If the file already exists and it's too large, truncate it.
402     * This is a fringe case that happens if a torrent's been updated
403     * and one of the updated torrent's files is smaller.
404     * http://trac.transmissionbt.com/ticket/2228
405     * https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/318249
406     */
407    if( alreadyExisted && ( file_size < (uint64_t)sb.st_size ) )
408        ftruncate( o->fd, file_size );
409
410    if( writable && !alreadyExisted && ( allocation == TR_PREALLOCATE_SPARSE ) )
411        preallocateFileSparse( o->fd, file_size );
412
413    /* Many (most?) clients request blocks in ascending order,
414     * so increase the readahead buffer.
415     * Also, disable OS-level caching because "inactive memory" angers users. */
416    tr_set_file_for_single_pass( o->fd );
417
418    return 0;
419}
420
421/***
422****
423***/
424
425struct tr_fileset
426{
427    struct tr_cached_file * begin;
428    struct tr_cached_file * end;
429};
430
431static void
432fileset_construct( struct tr_fileset * set, int n )
433{
434    struct tr_cached_file * o;
435    const struct tr_cached_file TR_CACHED_FILE_INIT = { 0, -1, 0, 0, 0 };
436
437    set->begin = tr_new( struct tr_cached_file, n );
438    set->end = set->begin + n;
439
440    for( o=set->begin; o!=set->end; ++o )
441        *o = TR_CACHED_FILE_INIT;
442}
443
444static void
445fileset_close_all( struct tr_fileset * set )
446{
447    struct tr_cached_file * o;
448
449    if( set != NULL )
450        for( o=set->begin; o!=set->end; ++o )
451            if( cached_file_is_open( o ) )
452                cached_file_close( o );
453}
454
455static void
456fileset_destruct( struct tr_fileset * set )
457{
458    fileset_close_all( set );
459    tr_free( set->begin );
460    set->begin = set->end = NULL;
461}
462
463static void
464fileset_close_torrent( struct tr_fileset * set, int torrent_id )
465{
466    struct tr_cached_file * o;
467
468    if( set != NULL )
469        for( o=set->begin; o!=set->end; ++o )
470            if( ( o->torrent_id == torrent_id ) && cached_file_is_open( o ) )
471                cached_file_close( o );
472}
473
474static struct tr_cached_file *
475fileset_lookup( struct tr_fileset * set, int torrent_id, tr_file_index_t i )
476{
477    struct tr_cached_file * o;
478
479    if( set != NULL )
480        for( o=set->begin; o!=set->end; ++o )
481            if( ( torrent_id == o->torrent_id ) && ( i == o->file_index ) && cached_file_is_open( o ) )
482                return o;
483
484    return NULL;
485}
486
487static struct tr_cached_file *
488fileset_get_empty_slot( struct tr_fileset * set )
489{
490    struct tr_cached_file * o;
491    struct tr_cached_file * cull;
492
493    /* try to find an unused slot */
494    for( o=set->begin; o!=set->end; ++o )
495        if( !cached_file_is_open( o ) )
496            return o;
497
498    /* all slots are full... recycle the least recently used */
499    for( cull=NULL, o=set->begin; o!=set->end; ++o )
500        if( !cull || o->used_at < cull->used_at )
501            cull = o;
502    cached_file_close( cull );
503    return cull;
504}
505
506/***
507****
508***/
509
510struct tr_fdInfo
511{
512    int socket_count;
513    int socket_limit;
514    int public_socket_limit;
515    struct tr_fileset fileset;
516};
517
518static struct tr_fileset*
519get_fileset( tr_session * session )
520{
521    return session && session->fdInfo ? &session->fdInfo->fileset : NULL;
522}
523
524void
525tr_fdFileClose( tr_session * s, const tr_torrent * tor, tr_file_index_t i )
526{
527    struct tr_cached_file * o;
528
529    if(( o = fileset_lookup( get_fileset( s ), tr_torrentId( tor ), i )))
530        cached_file_close( o );
531}
532
533int
534tr_fdFileGetCached( tr_session * s, int torrent_id, tr_file_index_t i, tr_bool writable )
535{
536    struct tr_cached_file * o = fileset_lookup( get_fileset( s ), torrent_id, i );
537
538    if( !o || ( writable && !o->is_writable ) )
539        return -1;
540
541    o->used_at = tr_time( );
542    return o->fd;
543}
544
545void
546tr_fdTorrentClose( tr_session * session, int torrent_id )
547{
548    fileset_close_torrent( get_fileset( session ), torrent_id );
549}
550
551/* returns an fd on success, or a -1 on failure and sets errno */
552int
553tr_fdFileCheckout( tr_session             * session,
554                   int                      torrent_id,
555                   tr_file_index_t          i,
556                   const char             * filename,
557                   tr_bool                  writable,
558                   tr_preallocation_mode    preallocation_mode,
559                   uint64_t                 file_size )
560{
561    struct tr_fileset * set = get_fileset( session );
562    struct tr_cached_file * o = fileset_lookup( set, torrent_id, i );
563
564    if( o && writable && !o->is_writable )
565        cached_file_close( o ); /* close it so we can reopen in rw mode */
566    else if( !o )
567        o = fileset_get_empty_slot( set );
568
569    if( !cached_file_is_open( o ) )
570    {
571        const int err = cached_file_open( o, filename, writable, preallocation_mode, file_size );
572        if( err ) {
573            errno = err;
574            return -1;
575        }
576
577        dbgmsg( "opened '%s' writable %c", filename, writable?'y':'n' );
578        o->is_writable = writable;
579    }
580
581    dbgmsg( "checking out '%s'", filename );
582    o->torrent_id = torrent_id;
583    o->file_index = i;
584    o->used_at = tr_time( );
585    return o->fd;
586}
587
588/***
589****
590****  Sockets
591****
592***/
593
594int
595tr_fdSocketCreate( tr_session * session, int domain, int type )
596{
597    int s = -1;
598    struct tr_fdInfo * gFd;
599
600    assert( tr_isSession( session ) );
601    assert( session->fdInfo != NULL );
602
603    gFd = session->fdInfo;
604
605    if( gFd->socket_count < gFd->socket_limit )
606        if(( s = socket( domain, type, 0 )) < 0 )
607            if( sockerrno != EAFNOSUPPORT )
608                tr_err( _( "Couldn't create socket: %s" ), tr_strerror( sockerrno ) );
609
610    if( s > -1 )
611        ++gFd->socket_count;
612
613    assert( gFd->socket_count >= 0 );
614
615    if( s >= 0 )
616    {
617        static tr_bool buf_logged = FALSE;
618        if( !buf_logged )
619        {
620            int i;
621            socklen_t size = sizeof( int );
622            buf_logged = TRUE;
623            getsockopt( s, SOL_SOCKET, SO_SNDBUF, &i, &size );
624            tr_dbg( "SO_SNDBUF size is %d", i );
625            getsockopt( s, SOL_SOCKET, SO_RCVBUF, &i, &size );
626            tr_dbg( "SO_RCVBUF size is %d", i );
627        }
628    }
629
630    return s;
631}
632
633int
634tr_fdSocketAccept( tr_session * s, int sockfd, tr_address * addr, tr_port * port )
635{
636    int fd;
637    unsigned int len;
638    struct tr_fdInfo * gFd;
639    struct sockaddr_storage sock;
640
641    assert( tr_isSession( s ) );
642    assert( s->fdInfo != NULL );
643    assert( addr );
644    assert( port );
645
646    gFd = s->fdInfo;
647
648    len = sizeof( struct sockaddr_storage );
649    fd = accept( sockfd, (struct sockaddr *) &sock, &len );
650
651    if( ( fd >= 0 ) && gFd->socket_count > gFd->socket_limit )
652    {
653        tr_netCloseSocket( fd );
654        fd = -1;
655    }
656
657    if( fd >= 0 )
658    {
659        /* "The ss_family field of the sockaddr_storage structure will always
660         * align with the family field of any protocol-specific structure." */
661        if( sock.ss_family == AF_INET )
662        {
663            struct sockaddr_in *si;
664            union { struct sockaddr_storage dummy; struct sockaddr_in si; } s;
665            s.dummy = sock;
666            si = &s.si;
667            addr->type = TR_AF_INET;
668            addr->addr.addr4.s_addr = si->sin_addr.s_addr;
669            *port = si->sin_port;
670        }
671        else
672        {
673            struct sockaddr_in6 *si;
674            union { struct sockaddr_storage dummy; struct sockaddr_in6 si; } s;
675            s.dummy = sock;
676            si = &s.si;
677            addr->type = TR_AF_INET6;
678            addr->addr.addr6 = si->sin6_addr;
679            *port = si->sin6_port;
680        }
681        ++gFd->socket_count;
682    }
683
684    return fd;
685}
686
687void
688tr_fdSocketClose( tr_session * session, int fd )
689{
690    assert( tr_isSession( session ) );
691
692    if( session->fdInfo != NULL )
693    {
694        struct tr_fdInfo * gFd = session->fdInfo;
695
696        if( fd >= 0 )
697        {
698            tr_netCloseSocket( fd );
699            --gFd->socket_count;
700        }
701
702        assert( gFd->socket_count >= 0 );
703    }
704}
705
706/***
707****
708****  Startup / Shutdown
709****
710***/
711
712static void
713ensureSessionFdInfoExists( tr_session * session )
714{
715    assert( tr_isSession( session ) );
716
717    if( session->fdInfo == NULL )
718        session->fdInfo = tr_new0( struct tr_fdInfo, 1 );
719}
720
721void
722tr_fdClose( tr_session * session )
723{
724    struct tr_fdInfo * gFd = session->fdInfo;
725
726    if( gFd != NULL )
727    {
728        fileset_destruct( &gFd->fileset );
729        tr_free( gFd );
730    }
731
732    session->fdInfo = NULL;
733}
734
735/***
736****
737***/
738
739int
740tr_fdGetFileLimit( const tr_session * session )
741{
742    const struct tr_fileset * set = session && session->fdInfo ? &session->fdInfo->fileset : NULL;
743    return set ? set->end - set->begin : 0;
744}
745
746void
747tr_fdSetFileLimit( tr_session * session, int limit )
748{
749    ensureSessionFdInfoExists( session );
750
751    if( limit != tr_fdGetFileLimit( session ) )
752    {
753        struct tr_fileset * set = get_fileset( session );
754        fileset_destruct( set );
755        fileset_construct( set, limit );
756    }
757}
758
759void
760tr_fdSetPeerLimit( tr_session * session, int socket_limit )
761{
762    struct tr_fdInfo * gFd;
763
764    ensureSessionFdInfoExists( session );
765
766    gFd = session->fdInfo;
767
768#ifdef HAVE_GETRLIMIT
769    {
770        struct rlimit rlim;
771        const int NOFILE_BUFFER = 512;
772        const int open_max = sysconf( _SC_OPEN_MAX );
773        getrlimit( RLIMIT_NOFILE, &rlim );
774        rlim.rlim_cur = MAX( 1024, open_max );
775        rlim.rlim_cur = MIN( rlim.rlim_cur, rlim.rlim_max );
776        setrlimit( RLIMIT_NOFILE, &rlim );
777        tr_dbg( "setrlimit( RLIMIT_NOFILE, %d )", (int)rlim.rlim_cur );
778        gFd->socket_limit = MIN( socket_limit, (int)rlim.rlim_cur - NOFILE_BUFFER );
779    }
780#else
781    gFd->socket_limit = socket_limit;
782#endif
783    gFd->public_socket_limit = socket_limit;
784
785    tr_dbg( "socket limit is %d", (int)gFd->socket_limit );
786}
787
788int
789tr_fdGetPeerLimit( const tr_session * session )
790{
791    return session && session->fdInfo ? session->fdInfo->public_socket_limit : -1;
792}
Note: See TracBrowser for help on using the repository browser.