source: trunk/libtransmission/fdlimit.c @ 11709

Last change on this file since 11709 was 11709, checked in by jordan, 11 years ago

Update the copyright year in the source code comments.

The Berne Convention says that the copyright year is moot, so instead of adding another year to each file as in previous years, I've removed the year altogether from the source code comments in libtransmission, gtk, qt, utils, daemon, and cli.

Juliusz's copyright notice in tr-dht and Johannes' copyright notice in tr-lpd have been left alone; it didn't seem appropriate to modify them.

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