source: trunk/libtransmission/fdlimit.c @ 6896

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

have tr_buildPath() allocate memory from the heap rather than using an input buffer

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