source: trunk/libtransmission/fdlimit.c @ 6795

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

run libT, cli, daemon, gtk through the source-code formatter "uncrustify" as promised/threatened

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