source: trunk/libtransmission/fdlimit.c @ 7098

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

(libT) better testing for fallocate() on linux

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