source: trunk/libtransmission/fdlimit.c @ 12514

Last change on this file since 12514 was 12514, checked in by jordan, 10 years ago

(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.

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