source: trunk/libtransmission/fdlimit.c @ 9387

Last change on this file since 9387 was 9387, checked in by charles, 12 years ago

(trunk) trunk's just been too stable lately. #2119: reload settings.json on SIGHUP

  • Property svn:keywords set to Date Rev Author Id
File size: 19.4 KB
Line 
1/******************************************************************************
2 * $Id: fdlimit.c 9387 2009-10-23 03:41:36Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#ifndef WIN32
26 #define HAVE_GETRLIMIT
27#endif
28
29#ifdef HAVE_POSIX_FADVISE
30 #ifdef _XOPEN_SOURCE
31  #undef _XOPEN_SOURCE
32 #endif
33 #define _XOPEN_SOURCE 600
34#endif
35
36#include <assert.h>
37#include <errno.h>
38#include <inttypes.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#ifdef SYS_DARWIN
43 #include <fcntl.h>
44#endif
45
46#ifdef HAVE_XFS_XFS_H
47 #include <xfs/xfs.h>
48#endif
49
50#include <sys/types.h>
51#include <sys/stat.h>
52#ifdef HAVE_GETRLIMIT
53 #include <sys/time.h> /* getrlimit */
54 #include <sys/resource.h> /* getrlimit */
55#endif
56#include <unistd.h>
57#include <fcntl.h> /* O_LARGEFILE posix_fadvise */
58
59#include <evutil.h>
60
61#include "transmission.h"
62#include "fdlimit.h"
63#include "list.h"
64#include "net.h"
65#include "platform.h" /* MAX_PATH_LENGTH, TR_PATH_DELIMITER */
66#include "session.h"
67#include "torrent.h" /* tr_isTorrent() */
68#include "utils.h"
69
70#define dbgmsg( ... ) \
71    do { \
72        if( tr_deepLoggingIsActive( ) ) \
73            tr_deepLog( __FILE__, __LINE__, NULL, __VA_ARGS__ ); \
74    } while( 0 )
75
76/**
77***
78**/
79
80enum
81{
82    NOFILE_BUFFER = 512, /* the process' number of open files is
83                            globalMaxPeers + NOFILE_BUFFER */
84};
85
86struct tr_openfile
87{
88    tr_bool          isWritable;
89    int              torrentId;
90    tr_file_index_t  fileNum;
91    char             filename[MAX_PATH_LENGTH];
92    int              fd;
93    uint64_t         date;
94};
95
96struct tr_fdInfo
97{
98    int                   socketCount;
99    int                   socketLimit;
100    int                   publicSocketLimit;
101    int                   openFileLimit;
102    struct tr_openfile  * openFiles;
103};
104
105/***
106****
107****  Local Files
108****
109***/
110
111#ifndef O_LARGEFILE
112 #define O_LARGEFILE 0
113#endif
114
115static tr_bool
116preallocateFileSparse( int fd, uint64_t length )
117{
118    const char zero = '\0';
119
120    if( length == 0 )
121        return TRUE;
122
123    if( lseek( fd, length-1, SEEK_SET ) == -1 )
124        return FALSE;
125    if( write( fd, &zero, 1 ) == -1 )
126        return FALSE;
127    if( ftruncate( fd, length ) == -1 )
128        return FALSE;
129
130    return TRUE;
131}
132
133static tr_bool
134preallocateFileFull( const char * filename, uint64_t length )
135{
136    tr_bool success = 0;
137
138#ifdef WIN32
139
140    HANDLE hFile = CreateFile( filename, GENERIC_WRITE, 0, 0, CREATE_NEW, 0, 0 );
141    if( hFile != INVALID_HANDLE_VALUE )
142    {
143        LARGE_INTEGER li;
144        li.QuadPart = length;
145        success = SetFilePointerEx( hFile, li, NULL, FILE_BEGIN ) && SetEndOfFile( hFile );
146        CloseHandle( hFile );
147    }
148
149#else
150
151    int flags = O_RDWR | O_CREAT | O_LARGEFILE;
152    int fd = open( filename, flags, 0666 );
153    if( fd >= 0 )
154    {
155# ifdef HAVE_XFS_XFS_H
156        if( !success && platform_test_xfs_fd( fd ) )
157        {
158            xfs_flock64_t fl;
159            fl.l_whence = 0;
160            fl.l_start = 0;
161            fl.l_len = length;
162            success = !xfsctl( NULL, fd, XFS_IOC_RESVSP64, &fl );
163        }
164# endif
165# ifdef SYS_DARWIN
166        if( !success )
167        {
168            fstore_t fst;
169            fst.fst_flags = F_ALLOCATECONTIG;
170            fst.fst_posmode = F_PEOFPOSMODE;
171            fst.fst_offset = 0;
172            fst.fst_length = length;
173            fst.fst_bytesalloc = 0;
174            success = !fcntl( fd, F_PREALLOCATE, &fst );
175        }
176# endif
177# ifdef HAVE_POSIX_FALLOCATE
178        if( !success )
179        {
180            success = !posix_fallocate( fd, 0, length );
181        }
182# endif
183
184        if( !success ) /* if nothing else works, do it the old-fashioned way */
185        {
186            uint8_t buf[ 4096 ];
187            memset( buf, 0, sizeof( buf ) );
188            success = TRUE;
189            while ( success && ( length > 0 ) )
190            {
191                const int thisPass = MIN( length, sizeof( buf ) );
192                success = write( fd, buf, thisPass ) == thisPass;
193                length -= thisPass;
194            }
195        }
196
197        close( fd );
198    }
199
200#endif
201
202    return success;
203}
204
205tr_bool
206tr_preallocate_file( const char * filename, uint64_t length )
207{
208    return preallocateFileFull( filename, length );
209}
210
211int
212tr_open_file_for_writing( const char * filename )
213{
214    int flags = O_WRONLY | O_CREAT;
215#ifdef O_BINARY
216    flags |= O_BINARY;
217#endif
218#ifdef O_LARGEFILE
219    flags |= O_LARGEFILE;
220#endif
221    return open( filename, flags, 0666 );
222}
223
224int
225tr_open_file_for_scanning( const char * filename )
226{
227    int fd;
228    int flags;
229
230    /* build the flags */
231    flags = O_RDONLY;
232#ifdef O_SEQUENTIAL
233    flags |= O_SEQUENTIAL;
234#endif
235#ifdef O_BINARY
236    flags |= O_BINARY;
237#endif
238#ifdef O_LARGEFILE
239    flags |= O_LARGEFILE;
240#endif
241
242    /* open the file */
243    fd = open( filename, flags, 0666 );
244    if( fd >= 0 )
245    {
246        /* Set hints about the lookahead buffer and caching. It's okay
247           for these to fail silently, so don't let them affect errno */
248        const int err = errno;
249#ifdef HAVE_POSIX_FADVISE
250        posix_fadvise( fd, 0, 0, POSIX_FADV_SEQUENTIAL );
251#endif
252#ifdef SYS_DARWIN
253        fcntl( fd, F_NOCACHE, 1 );
254        fcntl( fd, F_RDAHEAD, 1 );
255#endif
256        errno = err;
257    }
258
259    return fd;
260}
261
262void
263tr_close_file( int fd )
264{
265#if defined(HAVE_POSIX_FADVISE)
266    /* Set hint about not caching this file.
267       It's okay for this to fail silently, so don't let it affect errno */
268    const int err = errno;
269    posix_fadvise( fd, 0, 0, POSIX_FADV_DONTNEED );
270    errno = err;
271#endif
272    close( fd );
273}
274
275/**
276 * returns 0 on success, or an errno value on failure.
277 * errno values include ENOENT if the parent folder doesn't exist,
278 * plus the errno values set by tr_mkdirp() and open().
279 */
280static int
281TrOpenFile( tr_session             * session,
282            int                      i,
283            const char             * filename,
284            tr_bool                  doWrite,
285            tr_preallocation_mode    preallocationMode,
286            uint64_t                 desiredFileSize )
287{
288    int flags;
289    struct stat sb;
290    tr_bool alreadyExisted;
291    struct tr_openfile * file;
292
293    assert( tr_isSession( session ) );
294    assert( session->fdInfo != NULL );
295
296    file = &session->fdInfo->openFiles[i];
297
298    /* create subfolders, if any */
299    if( doWrite )
300    {
301        char * dir = tr_dirname( filename );
302        const int err = tr_mkdirp( dir, 0777 ) ? errno : 0;
303        if( err ) {
304            tr_err( _( "Couldn't create \"%1$s\": %2$s" ), dir, tr_strerror( err ) );
305            tr_free( dir );
306            return err;
307        }
308        tr_free( dir );
309    }
310
311    alreadyExisted = !stat( filename, &sb ) && S_ISREG( sb.st_mode );
312
313    if( doWrite && !alreadyExisted && ( preallocationMode == TR_PREALLOCATE_FULL ) )
314        if( preallocateFileFull( filename, desiredFileSize ) )
315            tr_inf( _( "Preallocated file \"%s\"" ), filename );
316
317    /* open the file */
318    flags = doWrite ? ( O_RDWR | O_CREAT ) : O_RDONLY;
319#ifdef O_SEQUENTIAL
320    flags |= O_SEQUENTIAL;
321#endif
322#ifdef O_LARGEFILE
323    flags |= O_LARGEFILE;
324#endif
325#ifdef WIN32
326    flags |= O_BINARY;
327#endif
328    file->fd = open( filename, flags, 0666 );
329    if( file->fd == -1 )
330    {
331        const int err = errno;
332        tr_err( _( "Couldn't open \"%1$s\": %2$s" ), filename, tr_strerror( err ) );
333        return err;
334    }
335
336    /* If the file already exists and it's too large, truncate it.
337     * This is a fringe case that happens if a torrent's been updated
338     * and one of the updated torrent's files is smaller.
339     * http://trac.transmissionbt.com/ticket/2228
340     * https://bugs.launchpad.net/ubuntu/+source/transmission/+bug/318249
341     */
342    if( alreadyExisted && ( desiredFileSize < (uint64_t)sb.st_size ) )
343        ftruncate( file->fd, desiredFileSize );
344
345    if( doWrite && !alreadyExisted && ( preallocationMode == TR_PREALLOCATE_SPARSE ) )
346        preallocateFileSparse( file->fd, desiredFileSize );
347
348#ifdef HAVE_POSIX_FADVISE
349    /* this doubles the OS level readahead buffer, which in practice
350     * turns out to be a good thing, because many (most?) clients request
351     * chunks of blocks in order.
352     * It's okay for this to fail silently, so don't let it affect errno */
353    {
354        const int err = errno;
355        posix_fadvise( file->fd, 0, 0, POSIX_FADV_SEQUENTIAL );
356        errno = err;
357    }
358#endif
359
360    return 0;
361}
362
363static TR_INLINE tr_bool
364fileIsOpen( const struct tr_openfile * o )
365{
366    return o->fd >= 0;
367}
368
369static void
370TrCloseFile( struct tr_openfile * o )
371{
372    assert( o != NULL );
373    assert( fileIsOpen( o ) );
374
375    tr_close_file( o->fd );
376    o->fd = -1;
377}
378
379int
380tr_fdFileGetCached( tr_session       * session,
381                    int                torrentId,
382                    tr_file_index_t    fileNum,
383                    tr_bool            doWrite )
384{
385    struct tr_openfile * match = NULL;
386    struct tr_fdInfo * gFd;
387
388    assert( tr_isSession( session ) );
389    assert( session->fdInfo != NULL );
390    assert( torrentId > 0 );
391    assert( tr_isBool( doWrite ) );
392
393    gFd = session->fdInfo;
394
395    /* is it already open? */
396    {
397        int i;
398        struct tr_openfile * o;
399        for( i=0; i<gFd->openFileLimit; ++i )
400        {
401            o = &gFd->openFiles[i];
402
403            if( torrentId != o->torrentId )
404                continue;
405            if( fileNum != o->fileNum )
406                continue;
407            if( !fileIsOpen( o ) )
408                continue;
409
410            match = o;
411            break;
412        }
413    }
414
415    if( ( match != NULL ) && ( !doWrite || match->isWritable ) )
416    {
417        match->date = tr_date( );
418        return match->fd;
419    }
420
421    return -1;
422}
423
424/* returns an fd on success, or a -1 on failure and sets errno */
425int
426tr_fdFileCheckout( tr_session             * session,
427                   int                      torrentId,
428                   tr_file_index_t          fileNum,
429                   const char             * filename,
430                   tr_bool                  doWrite,
431                   tr_preallocation_mode    preallocationMode,
432                   uint64_t                 desiredFileSize )
433{
434    int i, winner = -1;
435    struct tr_fdInfo * gFd;
436    struct tr_openfile * o;
437
438    assert( tr_isSession( session ) );
439    assert( session->fdInfo != NULL );
440    assert( torrentId > 0 );
441    assert( filename && *filename );
442    assert( tr_isBool( doWrite ) );
443
444    gFd = session->fdInfo;
445
446    dbgmsg( "looking for file '%s', writable %c", filename, doWrite ? 'y' : 'n' );
447
448    /* is it already open? */
449    for( i=0; i<gFd->openFileLimit; ++i )
450    {
451        o = &gFd->openFiles[i];
452
453        if( torrentId != o->torrentId )
454            continue;
455        if( fileNum != o->fileNum )
456            continue;
457        if( !fileIsOpen( o ) )
458            continue;
459
460        if( doWrite && !o->isWritable )
461        {
462            dbgmsg( "found it!  it's open and available, but isn't writable. closing..." );
463            TrCloseFile( o );
464            break;
465        }
466
467        dbgmsg( "found it!  it's ready for use!" );
468        winner = i;
469        break;
470    }
471
472    dbgmsg( "it's not already open.  looking for an open slot or an old file." );
473    while( winner < 0 )
474    {
475        uint64_t date = tr_date( ) + 1;
476
477        /* look for the file that's been open longest */
478        for( i=0; i<gFd->openFileLimit; ++i )
479        {
480            o = &gFd->openFiles[i];
481
482            if( !fileIsOpen( o ) )
483            {
484                winner = i;
485                dbgmsg( "found an empty slot in %d", winner );
486                break;
487            }
488
489            if( date > o->date )
490            {
491                date = o->date;
492                winner = i;
493            }
494        }
495
496        assert( winner >= 0 );
497
498        if( fileIsOpen( &gFd->openFiles[winner] ) )
499        {
500            dbgmsg( "closing file \"%s\"", gFd->openFiles[winner].filename );
501            TrCloseFile( &gFd->openFiles[winner] );
502        }
503    }
504
505    assert( winner >= 0 );
506    o = &gFd->openFiles[winner];
507    if( !fileIsOpen( o ) )
508    {
509        const int err = TrOpenFile( session, winner, filename, doWrite,
510                                    preallocationMode, desiredFileSize );
511        if( err ) {
512            errno = err;
513            return -1;
514        }
515
516        dbgmsg( "opened '%s' in slot %d, doWrite %c", filename, winner,
517                doWrite ? 'y' : 'n' );
518        tr_strlcpy( o->filename, filename, sizeof( o->filename ) );
519        o->isWritable = doWrite;
520    }
521
522    dbgmsg( "checking out '%s' in slot %d", filename, winner );
523    o->torrentId = torrentId;
524    o->fileNum = fileNum;
525    o->date = tr_date( );
526    return o->fd;
527}
528
529void
530tr_fdFileClose( tr_session        * session,
531                const tr_torrent  * tor,
532                tr_file_index_t     fileNum )
533{
534    struct tr_openfile * o;
535    struct tr_fdInfo * gFd;
536    const struct tr_openfile * end;
537    const int torrentId = tr_torrentId( tor );
538
539    assert( tr_isSession( session ) );
540    assert( session->fdInfo != NULL );
541    assert( tr_isTorrent( tor ) );
542    assert( fileNum < tor->info.fileCount );
543
544    gFd = session->fdInfo;
545
546    for( o=gFd->openFiles, end=o+gFd->openFileLimit; o!=end; ++o )
547    {
548        if( torrentId != o->torrentId )
549            continue;
550        if( fileNum != o->fileNum )
551            continue;
552        if( !fileIsOpen( o ) )
553            continue;
554
555        dbgmsg( "tr_fdFileClose closing \"%s\"", o->filename );
556        TrCloseFile( o );
557    }
558}
559
560void
561tr_fdTorrentClose( tr_session * session, int torrentId )
562{
563    struct tr_openfile * o;
564    struct tr_fdInfo * gFd;
565    const struct tr_openfile * end;
566
567    assert( tr_isSession( session ) );
568    assert( session->fdInfo != NULL );
569
570    gFd = session->fdInfo;
571
572    for( o=gFd->openFiles, end=o+gFd->openFileLimit; o!=end; ++o )
573        if( fileIsOpen( o ) && ( o->torrentId == torrentId ) )
574            TrCloseFile( o );
575}
576
577/***
578****
579****  Sockets
580****
581***/
582
583int
584tr_fdSocketCreate( tr_session * session, int domain, int type )
585{
586    int s = -1;
587    struct tr_fdInfo * gFd;
588
589    assert( tr_isSession( session ) );
590    assert( session->fdInfo != NULL );
591
592    gFd = session->fdInfo;
593
594    if( gFd->socketCount < gFd->socketLimit )
595        if( ( s = socket( domain, type, 0 ) ) < 0 )
596        {
597            if( sockerrno != EAFNOSUPPORT )
598                tr_err( _( "Couldn't create socket: %s" ),
599                        tr_strerror( sockerrno ) );
600        }
601
602    if( s > -1 )
603        ++gFd->socketCount;
604
605    assert( gFd->socketCount >= 0 );
606    return s;
607}
608
609int
610tr_fdSocketAccept( tr_session  * session,
611                   int           b,
612                   tr_address  * addr,
613                   tr_port     * port )
614{
615    int s;
616    unsigned int len;
617    struct tr_fdInfo * gFd;
618    struct sockaddr_storage sock;
619
620    assert( tr_isSession( session ) );
621    assert( session->fdInfo != NULL );
622    assert( addr );
623    assert( port );
624
625    gFd = session->fdInfo;
626
627    len = sizeof( struct sockaddr_storage );
628    s = accept( b, (struct sockaddr *) &sock, &len );
629
630    if( ( s >= 0 ) && gFd->socketCount > gFd->socketLimit )
631    {
632        EVUTIL_CLOSESOCKET( s );
633        s = -1;
634    }
635
636    if( s >= 0 )
637    {
638        /* "The ss_family field of the sockaddr_storage structure will always
639         * align with the family field of any protocol-specific structure." */
640        if( sock.ss_family == AF_INET )
641        {
642            struct sockaddr_in *si;
643            union { struct sockaddr_storage dummy; struct sockaddr_in si; } s;
644            s.dummy = sock;
645            si = &s.si;
646            addr->type = TR_AF_INET;
647            addr->addr.addr4.s_addr = si->sin_addr.s_addr;
648            *port = si->sin_port;
649        }
650        else
651        {
652            struct sockaddr_in6 *si;
653            union { struct sockaddr_storage dummy; struct sockaddr_in6 si; } s;
654            s.dummy = sock;
655            si = &s.si;
656            addr->type = TR_AF_INET6;
657            addr->addr.addr6 = si->sin6_addr;
658            *port = si->sin6_port;
659        }
660        ++gFd->socketCount;
661    }
662
663    return s;
664}
665
666void
667tr_fdSocketClose( tr_session * session, int fd )
668{
669    struct tr_fdInfo * gFd;
670
671    assert( tr_isSession( session ) );
672    assert( session->fdInfo != NULL );
673
674    gFd = session->fdInfo;
675
676    if( fd >= 0 )
677    {
678        EVUTIL_CLOSESOCKET( fd );
679        --gFd->socketCount;
680    }
681
682    assert( gFd->socketCount >= 0 );
683}
684
685/***
686****
687****  Startup / Shutdown
688****
689***/
690
691static void
692ensureSessionFdInfoExists( tr_session * session )
693{
694    assert( tr_isSession( session ) );
695
696    if( session->fdInfo == NULL )
697        session->fdInfo = tr_new0( struct tr_fdInfo, 1 );
698}
699
700void
701tr_fdClose( tr_session * session )
702{
703    struct tr_fdInfo * gFd;
704    struct tr_openfile * o;
705    const struct tr_openfile * end;
706
707    assert( tr_isSession( session ) );
708    assert( session->fdInfo != NULL );
709
710    gFd = session->fdInfo;
711
712    for( o=gFd->openFiles, end=o+gFd->openFileLimit; o!=end; ++o )
713        if( fileIsOpen( o ) )
714            TrCloseFile( o );
715
716    tr_free( gFd->openFiles );
717    tr_free( gFd );
718    session->fdInfo = NULL;
719}
720
721/***
722****
723***/
724
725void
726tr_fdSetFileLimit( tr_session * session, int limit )
727{
728    struct tr_fdInfo * gFd;
729
730    ensureSessionFdInfoExists( session );
731
732    gFd = session->fdInfo;
733
734    if( gFd->openFileLimit != limit )
735    {
736        int i;
737        struct tr_openfile * o;
738        const struct tr_openfile * end;
739
740        /* close any files we've got open  */
741        for( o=gFd->openFiles, end=o+gFd->openFileLimit; o!=end; ++o )
742            if( fileIsOpen( o ) )
743                TrCloseFile( o );
744
745        /* rebuild the openFiles array */
746        tr_free( gFd->openFiles );
747        gFd->openFiles = tr_new0( struct tr_openfile, limit );
748        gFd->openFileLimit = limit;
749        for( i=0; i<gFd->openFileLimit; ++i )
750            gFd->openFiles[i].fd = -1;
751    }
752}
753
754int
755tr_fdGetFileLimit( const tr_session * session )
756{
757    return session && session->fdInfo ? session->fdInfo->openFileLimit : -1;
758}
759
760void
761tr_fdSetPeerLimit( tr_session * session, int limit )
762{
763    struct tr_fdInfo * gFd;
764
765    ensureSessionFdInfoExists( session );
766
767    gFd = session->fdInfo;
768
769#ifdef HAVE_GETRLIMIT
770    {
771        struct rlimit rlim;
772        getrlimit( RLIMIT_NOFILE, &rlim );
773        rlim.rlim_cur = MIN( rlim.rlim_max, (rlim_t)( limit + NOFILE_BUFFER ) );
774        setrlimit( RLIMIT_NOFILE, &rlim );
775        gFd->socketLimit = rlim.rlim_cur - NOFILE_BUFFER;
776        tr_dbg( "setrlimit( RLIMIT_NOFILE, %d )", (int)rlim.rlim_cur );
777    }
778#else
779    gFd->socketLimit = limit;
780#endif
781    gFd->publicSocketLimit = limit;
782
783    tr_dbg( "%d usable file descriptors", limit );
784}
785
786int
787tr_fdGetPeerLimit( const tr_session * session )
788{
789    return session && session->fdInfo ? session->fdInfo->publicSocketLimit : -1;
790}
Note: See TracBrowser for help on using the repository browser.