source: trunk/libtransmission/fdlimit.c @ 11633

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

(trunk) #3875 "Typo: missing gettext macro _ in recent commit" -- fix r11602 regression discovered by ijuxda

  • 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 11633 2011-01-06 01:05:13Z 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
74static tr_bool
75preallocate_file_sparse( 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
97preallocate_file_full( 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( preallocate_file_full( 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        preallocate_file_sparse( 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    const 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->end = set->begin = 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
506static int
507fileset_get_size( const struct tr_fileset * set )
508{
509    return set ? set->end - set->begin : 0;
510}
511
512/***
513****
514***/
515
516struct tr_fdInfo
517{
518    int socket_count;
519    int socket_limit;
520    int public_socket_limit;
521    struct tr_fileset fileset;
522};
523
524static struct tr_fileset*
525get_fileset( tr_session * session )
526{
527    return session && session->fdInfo ? &session->fdInfo->fileset : NULL;
528}
529
530void
531tr_fdFileClose( tr_session * s, const tr_torrent * tor, tr_file_index_t i )
532{
533    struct tr_cached_file * o;
534
535    if(( o = fileset_lookup( get_fileset( s ), tr_torrentId( tor ), i )))
536        cached_file_close( o );
537}
538
539int
540tr_fdFileGetCached( tr_session * s, int torrent_id, tr_file_index_t i, tr_bool writable )
541{
542    struct tr_cached_file * o = fileset_lookup( get_fileset( s ), torrent_id, i );
543
544    if( !o || ( writable && !o->is_writable ) )
545        return -1;
546
547    o->used_at = tr_time( );
548    return o->fd;
549}
550
551void
552tr_fdTorrentClose( tr_session * session, int torrent_id )
553{
554    fileset_close_torrent( get_fileset( session ), torrent_id );
555}
556
557/* returns an fd on success, or a -1 on failure and sets errno */
558int
559tr_fdFileCheckout( tr_session             * session,
560                   int                      torrent_id,
561                   tr_file_index_t          i,
562                   const char             * filename,
563                   tr_bool                  writable,
564                   tr_preallocation_mode    allocation,
565                   uint64_t                 file_size )
566{
567    struct tr_fileset * set = get_fileset( session );
568    struct tr_cached_file * o = fileset_lookup( set, torrent_id, i );
569
570    if( o && writable && !o->is_writable )
571        cached_file_close( o ); /* close it so we can reopen in rw mode */
572    else if( !o )
573        o = fileset_get_empty_slot( set );
574
575    if( !cached_file_is_open( o ) )
576    {
577        const int err = cached_file_open( o, filename, writable, allocation, file_size );
578        if( err ) {
579            errno = err;
580            return -1;
581        }
582
583        dbgmsg( "opened '%s' writable %c", filename, writable?'y':'n' );
584        o->is_writable = writable;
585    }
586
587    dbgmsg( "checking out '%s'", filename );
588    o->torrent_id = torrent_id;
589    o->file_index = i;
590    o->used_at = tr_time( );
591    return o->fd;
592}
593
594/***
595****
596****  Sockets
597****
598***/
599
600int
601tr_fdSocketCreate( tr_session * session, int domain, int type )
602{
603    int s = -1;
604    struct tr_fdInfo * gFd;
605
606    assert( tr_isSession( session ) );
607    assert( session->fdInfo != NULL );
608
609    gFd = session->fdInfo;
610
611    if( gFd->socket_count < gFd->socket_limit )
612        if(( s = socket( domain, type, 0 )) < 0 )
613            if( sockerrno != EAFNOSUPPORT )
614                tr_err( _( "Couldn't create socket: %s" ), tr_strerror( sockerrno ) );
615
616    if( s > -1 )
617        ++gFd->socket_count;
618
619    assert( gFd->socket_count >= 0 );
620
621    if( s >= 0 )
622    {
623        static tr_bool buf_logged = FALSE;
624        if( !buf_logged )
625        {
626            int i;
627            socklen_t size = sizeof( int );
628            buf_logged = TRUE;
629            getsockopt( s, SOL_SOCKET, SO_SNDBUF, &i, &size );
630            tr_dbg( "SO_SNDBUF size is %d", i );
631            getsockopt( s, SOL_SOCKET, SO_RCVBUF, &i, &size );
632            tr_dbg( "SO_RCVBUF size is %d", i );
633        }
634    }
635
636    return s;
637}
638
639int
640tr_fdSocketAccept( tr_session * s, int sockfd, tr_address * addr, tr_port * port )
641{
642    int fd;
643    unsigned int len;
644    struct tr_fdInfo * gFd;
645    struct sockaddr_storage sock;
646
647    assert( tr_isSession( s ) );
648    assert( s->fdInfo != NULL );
649    assert( addr );
650    assert( port );
651
652    gFd = s->fdInfo;
653
654    len = sizeof( struct sockaddr_storage );
655    fd = accept( sockfd, (struct sockaddr *) &sock, &len );
656
657    if( ( fd >= 0 ) && gFd->socket_count > gFd->socket_limit )
658    {
659        tr_netCloseSocket( fd );
660        fd = -1;
661    }
662
663    if( fd >= 0 )
664    {
665        /* "The ss_family field of the sockaddr_storage structure will always
666         * align with the family field of any protocol-specific structure." */
667        if( sock.ss_family == AF_INET )
668        {
669            struct sockaddr_in *si;
670            union { struct sockaddr_storage dummy; struct sockaddr_in si; } s;
671            s.dummy = sock;
672            si = &s.si;
673            addr->type = TR_AF_INET;
674            addr->addr.addr4.s_addr = si->sin_addr.s_addr;
675            *port = si->sin_port;
676        }
677        else
678        {
679            struct sockaddr_in6 *si;
680            union { struct sockaddr_storage dummy; struct sockaddr_in6 si; } s;
681            s.dummy = sock;
682            si = &s.si;
683            addr->type = TR_AF_INET6;
684            addr->addr.addr6 = si->sin6_addr;
685            *port = si->sin6_port;
686        }
687        ++gFd->socket_count;
688    }
689
690    return fd;
691}
692
693void
694tr_fdSocketClose( tr_session * session, int fd )
695{
696    assert( tr_isSession( session ) );
697
698    if( session->fdInfo != NULL )
699    {
700        struct tr_fdInfo * gFd = session->fdInfo;
701
702        if( fd >= 0 )
703        {
704            tr_netCloseSocket( fd );
705            --gFd->socket_count;
706        }
707
708        assert( gFd->socket_count >= 0 );
709    }
710}
711
712/***
713****
714****  Startup / Shutdown
715****
716***/
717
718static void
719ensureSessionFdInfoExists( tr_session * session )
720{
721    assert( tr_isSession( session ) );
722
723    if( session->fdInfo == NULL )
724        session->fdInfo = tr_new0( struct tr_fdInfo, 1 );
725}
726
727void
728tr_fdClose( tr_session * session )
729{
730    struct tr_fdInfo * gFd = session->fdInfo;
731
732    if( gFd != NULL )
733    {
734        fileset_destruct( &gFd->fileset );
735        tr_free( gFd );
736    }
737
738    session->fdInfo = NULL;
739}
740
741/***
742****
743***/
744
745int
746tr_fdGetFileLimit( tr_session * session )
747{
748    return fileset_get_size( get_fileset( session ) );
749}
750
751void
752tr_fdSetFileLimit( tr_session * session, int limit )
753{
754    ensureSessionFdInfoExists( session );
755
756    if( limit != tr_fdGetFileLimit( session ) )
757    {
758        struct tr_fileset * set = get_fileset( session );
759        fileset_destruct( set );
760        fileset_construct( set, limit );
761    }
762}
763
764void
765tr_fdSetPeerLimit( tr_session * session, int socket_limit )
766{
767    struct tr_fdInfo * gFd;
768
769    ensureSessionFdInfoExists( session );
770
771    gFd = session->fdInfo;
772
773#ifdef HAVE_GETRLIMIT
774    {
775        struct rlimit rlim;
776        const int NOFILE_BUFFER = 512;
777        const int open_max = sysconf( _SC_OPEN_MAX );
778        getrlimit( RLIMIT_NOFILE, &rlim );
779        rlim.rlim_cur = MAX( 1024, open_max );
780        rlim.rlim_cur = MIN( rlim.rlim_cur, rlim.rlim_max );
781        setrlimit( RLIMIT_NOFILE, &rlim );
782        tr_dbg( "setrlimit( RLIMIT_NOFILE, %d )", (int)rlim.rlim_cur );
783        gFd->socket_limit = MIN( socket_limit, (int)rlim.rlim_cur - NOFILE_BUFFER );
784    }
785#else
786    gFd->socket_limit = socket_limit;
787#endif
788    gFd->public_socket_limit = socket_limit;
789
790    tr_dbg( "socket limit is %d", (int)gFd->socket_limit );
791}
792
793int
794tr_fdGetPeerLimit( const tr_session * session )
795{
796    return session && session->fdInfo ? session->fdInfo->public_socket_limit : -1;
797}
Note: See TracBrowser for help on using the repository browser.