source: trunk/libtransmission/fdlimit.c @ 6638

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

uncrash the build

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