source: trunk/libtransmission/fdlimit.c @ 6425

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

minor text cleanup

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