source: trunk/libtransmission/fdlimit.c @ 6897

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

make MAX_PATH_LENGTH private to libtransmission. add tr_dirname() and tr_basename() utility / portability wrappers

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