source: trunk/libtransmission/fdlimit.c @ 6961

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

(libT) low-hanging fruit discovered from softwareelves' shark profile.

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