source: trunk/libtransmission/fdlimit.c @ 6893

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

more changes inspired by spry's `winport' code: for portability, use the standard VA_ARGS macro for variadic macros instead of the CPP extensions.

  • Property svn:keywords set to Date Rev Author Id
File size: 12.3 KB
Line 
1/******************************************************************************
2 * $Id: fdlimit.c 6893 2008-10-13 22:45:05Z 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( ... ) tr_deepLog( __FILE__, __LINE__, NULL, __VA_ARGS__ )
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
103/**
104 * returns 0 on success, or an errno value on failure.
105 * errno values include ENOENT if the parent folder doesn't exist,
106 * plus the errno values set by tr_mkdirp() and open().
107 */
108static int
109TrOpenFile( int          i,
110            const char * folder,
111            const char * torrentFile,
112            int          write )
113{
114    struct tr_openfile * file = &gFd->open[i];
115    int                  flags;
116    char                 filename[MAX_PATH_LENGTH];
117    struct stat          sb;
118
119    /* confirm the parent folder exists */
120    if( stat( folder, &sb ) || !S_ISDIR( sb.st_mode ) )
121        return ENOENT;
122
123    /* create subfolders, if any */
124    tr_buildPath ( filename, sizeof( filename ), folder, torrentFile, NULL );
125    if( write )
126    {
127        char *    tmp = tr_strdup( filename );
128        const int err = tr_mkdirp( dirname( tmp ), 0777 ) ? errno : 0;
129        tr_free( tmp );
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        return err;
149    }
150
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[MAX_PATH_LENGTH];
189
190    assert( folder && *folder );
191    assert( torrentFile && *torrentFile );
192    assert( write == 0 || write == 1 );
193
194    tr_buildPath ( filename, sizeof( filename ), 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            errno = err;
287            return -1;
288        }
289
290        dbgmsg( "opened '%s' in slot %d, write %c", filename, winner,
291                write ? 'y' : 'n' );
292        tr_strlcpy( o->filename, filename, sizeof( o->filename ) );
293        o->isWritable = write;
294    }
295
296    dbgmsg( "checking out '%s' in slot %d", filename, winner );
297    o->isCheckedOut = 1;
298    o->closeWhenDone = 0;
299    o->date = tr_date( );
300    tr_lockUnlock( gFd->lock );
301    return o->fd;
302}
303
304void
305tr_fdFileReturn( int fd )
306{
307    int i;
308
309    tr_lockLock( gFd->lock );
310
311    for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
312    {
313        struct tr_openfile * o = &gFd->open[i];
314        if( o->fd != fd )
315            continue;
316
317        dbgmsg( "releasing file '%s' in slot #%d", o->filename, i );
318        o->isCheckedOut = 0;
319        if( o->closeWhenDone )
320            TrCloseFile( i );
321
322        break;
323    }
324
325    tr_lockUnlock( gFd->lock );
326}
327
328void
329tr_fdFileClose( const char * filename )
330{
331    int i;
332
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        {
345            dbgmsg( "not checked out, so closing it now... '%s'", filename );
346            TrCloseFile( i );
347        }
348        else
349        {
350            dbgmsg(
351                "flagging file '%s', slot #%d to be closed when checked in",
352                gFd->open[i].filename, i );
353            o->closeWhenDone = 1;
354        }
355    }
356
357    tr_lockUnlock( gFd->lock );
358}
359
360/***
361****
362****  Sockets
363****
364***/
365
366static tr_list * reservedSockets = NULL;
367
368static void
369setSocketPriority( int fd,
370                   int isReserved )
371{
372    if( isReserved )
373        tr_list_append( &reservedSockets, TR_UINT_TO_PTR( fd ) );
374}
375
376static int
377socketWasReserved( int fd )
378{
379    return tr_list_remove_data( &reservedSockets,
380                               TR_UINT_TO_PTR( fd ) ) != NULL;
381}
382
383static int
384getSocketMax( struct tr_fd_s * gFd )
385{
386    return gFd->normalMax;
387}
388
389int
390tr_fdSocketCreate( int type,
391                   int isReserved )
392{
393    int s = -1;
394
395    tr_lockLock( gFd->lock );
396
397    if( isReserved || ( gFd->normal < getSocketMax( gFd ) ) )
398        if( ( s = socket( AF_INET, type, 0 ) ) < 0 )
399            tr_err( _( "Couldn't create socket: %s" ),
400                   tr_strerror( sockerrno ) );
401
402    if( s > -1 )
403    {
404        setSocketPriority( s, isReserved );
405
406        if( isReserved )
407            ++gFd->reserved;
408        else
409            ++gFd->normal;
410    }
411
412    assert( gFd->reserved >= 0 );
413    assert( gFd->normal >= 0 );
414
415    tr_lockUnlock( gFd->lock );
416    return s;
417}
418
419int
420tr_fdSocketAccept( int              b,
421                   struct in_addr * addr,
422                   tr_port_t *      port )
423{
424    int                s = -1;
425    unsigned int       len;
426    struct sockaddr_in sock;
427
428    assert( addr );
429    assert( port );
430
431    tr_lockLock( gFd->lock );
432    if( gFd->normal < getSocketMax( gFd ) )
433    {
434        len = sizeof( sock );
435        s = accept( b, (struct sockaddr *) &sock, &len );
436    }
437    if( s > -1 )
438    {
439        setSocketPriority( s, FALSE );
440        *addr = sock.sin_addr;
441        *port = sock.sin_port;
442        gFd->normal++;
443    }
444    tr_lockUnlock( gFd->lock );
445
446    return s;
447}
448
449static void
450socketClose( int fd )
451{
452#ifdef BEOS_NETSERVER
453    closesocket( fd );
454#else
455    EVUTIL_CLOSESOCKET( fd );
456#endif
457}
458
459void
460tr_fdSocketClose( int s )
461{
462    tr_lockLock( gFd->lock );
463
464    if( s >= 0 )
465    {
466        socketClose( s );
467        if( socketWasReserved( s ) )
468            --gFd->reserved;
469        else
470            --gFd->normal;
471    }
472
473    assert( gFd->reserved >= 0 );
474    assert( gFd->normal >= 0 );
475
476    tr_lockUnlock( gFd->lock );
477}
478
479/***
480****
481****  Startup / Shutdown
482****
483***/
484
485void
486tr_fdInit( int globalPeerLimit )
487{
488    int i;
489
490    assert( gFd == NULL );
491    gFd = tr_new0( struct tr_fd_s, 1 );
492    gFd->lock = tr_lockNew( );
493
494#ifdef HAVE_GETRLIMIT
495    {
496        struct rlimit rlim;
497        getrlimit( RLIMIT_NOFILE, &rlim );
498        rlim.rlim_cur = MIN( rlim.rlim_max,
499                            (rlim_t)( globalPeerLimit + NOFILE_BUFFER ) );
500        setrlimit( RLIMIT_NOFILE, &rlim );
501        gFd->normalMax = rlim.rlim_cur - NOFILE_BUFFER;
502        tr_dbg( "setrlimit( RLIMIT_NOFILE, %d )", (int)rlim.rlim_cur );
503    }
504#else
505    gFd->normalMax = globalPeerLimit;
506#endif
507    tr_dbg( "%d usable file descriptors", globalPeerLimit );
508
509    for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
510        gFd->open[i].fd = -1;
511}
512
513void
514tr_fdClose( void )
515{
516    int i = 0;
517
518    for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
519        if( fileIsOpen( &gFd->open[i] ) )
520            TrCloseFile( i );
521
522    tr_lockFree( gFd->lock );
523
524    tr_list_free( &reservedSockets, NULL );
525    tr_free( gFd );
526}
527
528void
529tr_fdSetPeerLimit( uint16_t n )
530{
531    assert( gFd != NULL && "tr_fdInit() must be called first!" );
532    gFd->normalMax = n;
533}
534
535uint16_t
536tr_fdGetPeerLimit( void )
537{
538    return gFd ? gFd->normalMax : -1;
539}
540
Note: See TracBrowser for help on using the repository browser.