source: trunk/libtransmission/fdlimit.c @ 7643

Last change on this file since 7643 was 7643, checked in by charles, 13 years ago

(trunk libT) make the default number of open files 32 rather than 16, and make it configurable via settings.json. (Reported by Lucius Windschuh via denis_)

  • Property svn:keywords set to Date Rev Author Id
File size: 14.1 KB
Line 
1/******************************************************************************
2 * $Id: fdlimit.c 7643 2009-01-08 19:20:24Z 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#include <assert.h>
30#include <errno.h>
31#include <inttypes.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#ifdef SYS_DARWIN
36 #include <fcntl.h>
37#endif
38
39#ifdef HAVE_FALLOCATE
40 #include <linux/falloc.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 <unistd.h>
50#include <fcntl.h> /* O_LARGEFILE */
51
52#include <event.h>
53#include <evutil.h>
54
55#include "transmission.h"
56#include "fdlimit.h"
57#include "list.h"
58#include "net.h"
59#include "platform.h" /* tr_lock */
60#include "utils.h"
61
62#define dbgmsg( ... ) \
63    do { \
64        if( tr_deepLoggingIsActive( ) ) \
65            tr_deepLog( __FILE__, __LINE__, NULL, __VA_ARGS__ ); \
66    } while( 0 )
67
68/**
69***
70**/
71
72enum
73{
74    NOFILE_BUFFER = 512, /* the process' number of open files is
75                            globalMaxPeers + NOFILE_BUFFER */
76};
77
78struct tr_openfile
79{
80    tr_bool    isCheckedOut;
81    tr_bool    isWritable;
82    tr_bool    closeWhenDone;
83    char       filename[MAX_PATH_LENGTH];
84    int        fd;
85    uint64_t   date;
86};
87
88struct tr_fd_s
89{
90    int                   socketCount;
91    int                   socketLimit;
92
93    struct tr_openfile  * openFiles;
94    int                   openFileLimit;
95
96    tr_lock             * lock;
97};
98
99static struct tr_fd_s * gFd = NULL;
100
101/***
102****
103****  Local Files
104****
105***/
106
107#ifndef O_LARGEFILE
108 #define O_LARGEFILE 0
109#endif
110
111static int
112preallocateFile( const char * filename, uint64_t length )
113{
114    int success = 0;
115
116#ifdef WIN32
117
118    HANDLE hFile = CreateFile( filename, GENERIC_WRITE, 0, 0, CREATE_NEW, 0, 0 );
119    if( hFile != INVALID_HANDLE_VALUE )
120    {
121        LARGE_INTEGER li;
122        li.QuadPart = length;
123        success = SetFilePointerEx( hFile, li, NULL, FILE_BEGIN ) && SetEndOfFile( hFile );
124        CloseHandle( hFile );
125    }
126
127#else
128
129    int flags = O_RDWR | O_CREAT | O_LARGEFILE;
130    int fd = open( filename, flags, 0666 );
131    if( fd >= 0 )
132    {
133       
134# ifdef HAVE_FALLOCATE
135
136        success = !fallocate( fd, FALLOC_FL_KEEP_SIZE, 0, length );
137
138# elif defined(HAVE_POSIX_FALLOCATE)
139
140        success = !posix_fallocate( fd, 0, length );
141
142# elif defined(SYS_DARWIN)
143
144        fstore_t fst;
145        fst.fst_flags = F_ALLOCATECONTIG;
146        fst.fst_posmode = F_PEOFPOSMODE;
147        fst.fst_offset = 0;
148        fst.fst_length = length;
149        fst.fst_bytesalloc = 0;
150        success = !fcntl( fd, F_PREALLOCATE, &fst );
151
152# else
153
154        #warning no known method to preallocate files on this platform
155        success = 0;
156
157# endif
158
159        close( fd );
160    }
161
162#endif
163
164    return success;
165}
166
167/**
168 * returns 0 on success, or an errno value on failure.
169 * errno values include ENOENT if the parent folder doesn't exist,
170 * plus the errno values set by tr_mkdirp() and open().
171 */
172static int
173TrOpenFile( int          i,
174            const char * folder,
175            const char * torrentFile,
176            int          doWrite,
177            int          doPreallocate,
178            uint64_t     desiredFileSize )
179{
180    struct tr_openfile * file = &gFd->openFiles[i];
181    int                  flags;
182    char               * filename;
183    struct stat          sb;
184    int                  alreadyExisted;
185
186    /* confirm the parent folder exists */
187    if( stat( folder, &sb ) || !S_ISDIR( sb.st_mode ) )
188        return ENOENT;
189
190    /* create subfolders, if any */
191    filename = tr_buildPath( folder, torrentFile, NULL );
192    if( doWrite )
193    {
194        char * tmp = tr_dirname( filename );
195        const int err = tr_mkdirp( tmp, 0777 ) ? errno : 0;
196        tr_free( tmp );
197        if( err ) {
198            tr_free( filename );
199            return err;
200        }
201    }
202
203    alreadyExisted = !stat( filename, &sb ) && S_ISREG( sb.st_mode );
204
205    if( doWrite && !alreadyExisted && doPreallocate )
206        if( preallocateFile( filename, desiredFileSize ) )
207            tr_inf( _( "Preallocated file \"%s\"" ), filename );
208   
209    /* open the file */
210    flags = doWrite ? ( O_RDWR | O_CREAT ) : O_RDONLY;
211#ifdef O_LARGEFILE
212    flags |= O_LARGEFILE;
213#endif
214#ifdef WIN32
215    flags |= O_BINARY;
216#endif
217    file->fd = open( filename, flags, 0666 );
218    if( file->fd == -1 )
219    {
220        const int err = errno;
221        tr_err( _( "Couldn't open \"%1$s\": %2$s" ), filename,
222               tr_strerror( err ) );
223        tr_free( filename );
224        return err;
225    }
226
227    tr_free( filename );
228    return 0;
229}
230
231static int
232fileIsOpen( const struct tr_openfile * o )
233{
234    return o->fd >= 0;
235}
236
237static void
238TrCloseFile( int i )
239{
240    struct tr_openfile * o = &gFd->openFiles[i];
241
242    assert( i >= 0 );
243    assert( i < gFd->openFileLimit );
244    assert( fileIsOpen( o ) );
245
246    close( o->fd );
247    o->fd = -1;
248    o->isCheckedOut = 0;
249}
250
251static int
252fileIsCheckedOut( const struct tr_openfile * o )
253{
254    return fileIsOpen( o ) && o->isCheckedOut;
255}
256
257/* returns an fd on success, or a -1 on failure and sets errno */
258int
259tr_fdFileCheckout( const char * folder,
260                   const char * torrentFile,
261                   int          doWrite,
262                   int          doPreallocate,
263                   uint64_t     desiredFileSize )
264{
265    int i, winner = -1;
266    struct tr_openfile * o;
267    char filename[MAX_PATH_LENGTH];
268
269    assert( folder && *folder );
270    assert( torrentFile && *torrentFile );
271    assert( doWrite == 0 || doWrite == 1 );
272
273    tr_snprintf( filename, sizeof( filename ), "%s%c%s", folder, TR_PATH_DELIMITER, torrentFile );
274    dbgmsg( "looking for file '%s', writable %c", filename, doWrite ? 'y' : 'n' );
275
276    tr_lockLock( gFd->lock );
277
278    /* Is it already open? */
279    for( i = 0; i < gFd->openFileLimit; ++i )
280    {
281        o = &gFd->openFiles[i];
282
283        if( !fileIsOpen( o ) )
284            continue;
285
286        if( strcmp( filename, o->filename ) )
287            continue;
288
289        if( fileIsCheckedOut( o ) )
290        {
291            dbgmsg( "found it!  it's open, but checked out.  waiting..." );
292            tr_lockUnlock( gFd->lock );
293            tr_wait( 200 );
294            tr_lockLock( gFd->lock );
295            i = -1; /* reloop */
296            continue;
297        }
298
299        if( doWrite && !o->isWritable )
300        {
301            dbgmsg(
302                "found it!  it's open and available, but isn't writable. closing..." );
303            TrCloseFile( i );
304            break;
305        }
306
307        dbgmsg( "found it!  it's ready for use!" );
308        winner = i;
309        break;
310    }
311
312    dbgmsg(
313        "it's not already open.  looking for an open slot or an old file." );
314    while( winner < 0 )
315    {
316        uint64_t date = tr_date( ) + 1;
317
318        /* look for the file that's been open longest */
319        for( i = 0; i < gFd->openFileLimit; ++i )
320        {
321            o = &gFd->openFiles[i];
322
323            if( !fileIsOpen( o ) )
324            {
325                winner = i;
326                dbgmsg( "found an empty slot in %d", winner );
327                break;
328            }
329
330            if( date > o->date )
331            {
332                date = o->date;
333                winner = i;
334            }
335        }
336
337        if( winner >= 0 )
338        {
339            if( fileIsOpen( &gFd->openFiles[winner] ) )
340            {
341                dbgmsg( "closing file '%s', slot #%d",
342                        gFd->openFiles[winner].filename,
343                        winner );
344                TrCloseFile( winner );
345            }
346        }
347        else
348        {
349            dbgmsg(
350                "everything's full!  waiting for someone else to finish something" );
351            tr_lockUnlock( gFd->lock );
352            tr_wait( 200 );
353            tr_lockLock( gFd->lock );
354        }
355    }
356
357    assert( winner >= 0 );
358    o = &gFd->openFiles[winner];
359    if( !fileIsOpen( o ) )
360    {
361        const int err = TrOpenFile( winner, folder, torrentFile, doWrite, doPreallocate, desiredFileSize );
362        if( err ) {
363            tr_lockUnlock( gFd->lock );
364            errno = err;
365            return -1;
366        }
367
368        dbgmsg( "opened '%s' in slot %d, doWrite %c", filename, winner,
369                doWrite ? 'y' : 'n' );
370        tr_strlcpy( o->filename, filename, sizeof( o->filename ) );
371        o->isWritable = doWrite;
372    }
373
374    dbgmsg( "checking out '%s' in slot %d", filename, winner );
375    o->isCheckedOut = 1;
376    o->closeWhenDone = 0;
377    o->date = tr_date( );
378    tr_lockUnlock( gFd->lock );
379    return o->fd;
380}
381
382void
383tr_fdFileReturn( int fd )
384{
385    int i;
386
387    tr_lockLock( gFd->lock );
388
389    for( i = 0; i < gFd->openFileLimit; ++i )
390    {
391        struct tr_openfile * o = &gFd->openFiles[i];
392        if( o->fd != fd )
393            continue;
394
395        dbgmsg( "releasing file '%s' in slot #%d", o->filename, i );
396        o->isCheckedOut = 0;
397        if( o->closeWhenDone )
398            TrCloseFile( i );
399
400        break;
401    }
402
403    tr_lockUnlock( gFd->lock );
404}
405
406void
407tr_fdFileClose( const char * filename )
408{
409    int i;
410
411    tr_lockLock( gFd->lock );
412
413    for( i = 0; i < gFd->openFileLimit; ++i )
414    {
415        struct tr_openfile * o = &gFd->openFiles[i];
416        if( !fileIsOpen( o ) || strcmp( filename, o->filename ) )
417            continue;
418
419        dbgmsg( "tr_fdFileClose closing '%s'", filename );
420
421        if( !o->isCheckedOut )
422        {
423            dbgmsg( "not checked out, so closing it now... '%s'", filename );
424            TrCloseFile( i );
425        }
426        else
427        {
428            dbgmsg(
429                "flagging file '%s', slot #%d to be closed when checked in",
430                gFd->openFiles[i].filename, i );
431            o->closeWhenDone = 1;
432        }
433    }
434
435    tr_lockUnlock( gFd->lock );
436}
437
438/***
439****
440****  Sockets
441****
442***/
443
444static int
445getSocketMax( struct tr_fd_s * gFd )
446{
447    return gFd->socketLimit;
448}
449
450int
451tr_fdSocketCreate( int domain, int type )
452{
453    int s = -1;
454
455    tr_lockLock( gFd->lock );
456
457    if( gFd->socketCount < getSocketMax( gFd ) )
458        if( ( s = socket( domain, type, 0 ) ) < 0 )
459        {
460#ifdef SYS_DARWIN
461            if( sockerrno != EAFNOSUPPORT )
462#endif
463            tr_err( _( "Couldn't create socket: %s" ),
464                   tr_strerror( sockerrno ) );
465            s = -sockerrno;
466        }
467
468    if( s > -1 )
469        ++gFd->socketCount;
470
471    assert( gFd->socketCount >= 0 );
472
473    tr_lockUnlock( gFd->lock );
474    return s;
475}
476
477int
478tr_fdSocketAccept( int           b,
479                   tr_address  * addr,
480                   tr_port     * port )
481{
482    int                s = -1;
483    unsigned int       len;
484    struct sockaddr_storage sock;
485
486    assert( addr );
487    assert( port );
488
489    tr_lockLock( gFd->lock );
490    if( gFd->socketCount < getSocketMax( gFd ) )
491    {
492        len = sizeof( struct sockaddr_storage );
493        s = accept( b, (struct sockaddr *) &sock, &len );
494    }
495    if( s > -1 )
496    {
497        /* "The ss_family field of the sockaddr_storage structure will always
498         * align with the family field of any protocol-specific structure." */ 
499        if( sock.ss_family == AF_INET ) 
500        { 
501            struct sockaddr_in * sock4 = (struct sockaddr_in *)&sock; 
502            addr->type = TR_AF_INET; 
503            addr->addr.addr4.s_addr = sock4->sin_addr.s_addr; 
504            *port = sock4->sin_port; 
505        } 
506        else 
507        { 
508            struct sockaddr_in6 * sock6 = (struct sockaddr_in6 *)&sock; 
509            addr->type = TR_AF_INET6; 
510            addr->addr.addr6 = sock6->sin6_addr;
511            *port = sock6->sin6_port; 
512        } 
513        ++gFd->socketCount;
514    }
515    tr_lockUnlock( gFd->lock );
516
517    return s;
518}
519
520static void
521socketClose( int fd )
522{
523    EVUTIL_CLOSESOCKET( fd );
524}
525
526void
527tr_fdSocketClose( int s )
528{
529    tr_lockLock( gFd->lock );
530
531    if( s >= 0 )
532    {
533        socketClose( s );
534        --gFd->socketCount;
535    }
536
537    assert( gFd->socketCount >= 0 );
538
539    tr_lockUnlock( gFd->lock );
540}
541
542/***
543****
544****  Startup / Shutdown
545****
546***/
547
548void
549tr_fdInit( size_t openFileLimit, size_t socketLimit )
550{
551    int i;
552
553    assert( gFd == NULL );
554    gFd = tr_new0( struct tr_fd_s, 1 );
555    gFd->openFiles = tr_new0( struct tr_openfile, openFileLimit );
556    gFd->openFileLimit = openFileLimit;
557    gFd->lock = tr_lockNew( );
558
559#ifdef HAVE_GETRLIMIT
560    {
561        struct rlimit rlim;
562        getrlimit( RLIMIT_NOFILE, &rlim );
563        rlim.rlim_cur = MIN( rlim.rlim_max,
564                            (rlim_t)( socketLimit + NOFILE_BUFFER ) );
565        setrlimit( RLIMIT_NOFILE, &rlim );
566        gFd->socketLimit = rlim.rlim_cur - NOFILE_BUFFER;
567        tr_dbg( "setrlimit( RLIMIT_NOFILE, %d )", (int)rlim.rlim_cur );
568    }
569#else
570    gFd->socketLimit = socketLimit;
571#endif
572    tr_dbg( "%zu usable file descriptors", socketLimit );
573
574    for( i = 0; i < gFd->openFileLimit; ++i )
575        gFd->openFiles[i].fd = -1;
576}
577
578void
579tr_fdClose( void )
580{
581    int i = 0;
582
583    for( i = 0; i < gFd->openFileLimit; ++i )
584        if( fileIsOpen( &gFd->openFiles[i] ) )
585            TrCloseFile( i );
586
587    tr_lockFree( gFd->lock );
588
589    tr_free( gFd );
590    gFd = NULL;
591}
592
593void
594tr_fdSetPeerLimit( uint16_t n )
595{
596    assert( gFd != NULL && "tr_fdInit() must be called first!" );
597    gFd->socketLimit = n;
598}
599
600uint16_t
601tr_fdGetPeerLimit( void )
602{
603    return gFd ? gFd->socketLimit : -1;
604}
605
Note: See TracBrowser for help on using the repository browser.