source: trunk/libtransmission/fdlimit.c @ 4856

Last change on this file since 4856 was 4856, checked in by charles, 14 years ago

#663: connection limits don't work correctly.

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