source: trunk/libtransmission/fdlimit.c @ 1441

Last change on this file since 1441 was 1441, checked in by joshe, 15 years ago

Store the peers's listening port instead of the peer connections's remote port.

  • Property svn:keywords set to Date Rev Author Id
File size: 13.8 KB
Line 
1/******************************************************************************
2 * $Id: fdlimit.c 1441 2007-01-28 00:24:41Z joshe $
3 *
4 * Copyright (c) 2005-2006 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#include "transmission.h"
26
27#define TR_MAX_OPEN_FILES 16 /* That is, real files, not sockets */
28#define TR_RESERVED_FDS   16 /* Number of sockets reserved for
29                                connections to trackers */
30
31/***********************************************************************
32 * Structures
33 **********************************************************************/
34typedef struct tr_openFile_s
35{
36    char       folder[MAX_PATH_LENGTH];
37    char       name[MAX_PATH_LENGTH];
38    int        file;
39    int        write;
40
41#define STATUS_INVALID 1
42#define STATUS_UNUSED  2
43#define STATUS_USED    4
44#define STATUS_CLOSING 8
45    int        status;
46
47    uint64_t   date;
48}
49tr_openFile_t;
50
51typedef struct tr_fd_s
52{
53    tr_lock_t       lock;
54    tr_cond_t       cond;
55   
56    int             reserved;
57
58    int             normal;
59    int             normalMax;
60
61    tr_openFile_t   open[TR_MAX_OPEN_FILES];
62}
63tr_fd_t;
64
65static tr_fd_t * gFd = NULL;
66
67/***********************************************************************
68 * Local prototypes
69 **********************************************************************/
70static int  ErrorFromErrno();
71static int  OpenFile( int i, char * folder, char * name, int write );
72static void CloseFile( int i );
73
74
75/***********************************************************************
76 * tr_fdInit
77 **********************************************************************/
78void tr_fdInit()
79{
80    int i, j, s[4096];
81
82    if( gFd )
83    {
84        tr_err( "tr_fdInit was called before!" );
85        return;
86    }
87
88    gFd = calloc( sizeof( tr_fd_t ), 1 );
89
90    /* Init lock and cond */
91    tr_lockInit( &gFd->lock );
92    tr_condInit( &gFd->cond );
93
94    /* Detect the maximum number of open files or sockets */
95    for( i = 0; i < 4096; i++ )
96    {
97        if( ( s[i] = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
98        {
99            break;
100        }
101    }
102    for( j = 0; j < i; j++ )
103    {
104#ifdef BEOS_NETSERVER
105            closesocket( s[j] );
106#else
107            close( s[j] );
108#endif
109    }
110
111    tr_dbg( "%d usable file descriptors", i );
112
113    gFd->reserved  = 0;
114    gFd->normal    = 0;
115
116    gFd->normalMax = i - TR_RESERVED_FDS - 10;
117        /* To be safe, in case the UI needs to write a preferences file
118           or something */
119
120    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
121    {
122        gFd->open[i].status = STATUS_INVALID;
123    }
124}
125
126/***********************************************************************
127 * tr_fdFileOpen
128 **********************************************************************/
129int tr_fdFileOpen( char * folder, char * name, int write )
130{
131    int i, winner, ret;
132    uint64_t date;
133
134    tr_lockLock( &gFd->lock );
135
136    /* Is it already open? */
137    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
138    {
139        if( gFd->open[i].status & STATUS_INVALID ||
140            strcmp( folder, gFd->open[i].folder ) ||
141            strcmp( name, gFd->open[i].name ) )
142        {
143            continue;
144        }
145        if( gFd->open[i].status & STATUS_CLOSING )
146        {
147            /* File is being closed by another thread, wait until
148             * it's done before we reopen it */
149            tr_condWait( &gFd->cond, &gFd->lock );
150            i = -1;
151            continue;
152        }
153        if( gFd->open[i].write < write )
154        {
155            /* File is open read-only and needs to be closed then
156             * re-opened read-write */
157            CloseFile( i );
158            continue;
159        }
160        winner = i;
161        goto done;
162    }
163
164    /* Can we open one more file? */
165    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
166    {
167        if( gFd->open[i].status & STATUS_INVALID )
168        {
169            winner = i;
170            goto open;
171        }
172    }
173
174    /* All slots taken - close the oldest currently unused file */
175    for( ;; )
176    {
177        date   = tr_date() + 1;
178        winner = -1;
179
180        for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
181        {
182            if( !( gFd->open[i].status & STATUS_UNUSED ) )
183            {
184                continue;
185            }
186            if( gFd->open[i].date < date )
187            {
188                winner = i;
189                date   = gFd->open[i].date;
190            }
191        }
192
193        if( winner >= 0 )
194        {
195            CloseFile( winner );
196            goto open;
197        }
198
199        /* All used! Wait a bit and try again */
200        tr_condWait( &gFd->cond, &gFd->lock );
201    }
202
203open:
204    if( ( ret = OpenFile( winner, folder, name, write ) ) )
205    {
206        tr_lockUnlock( &gFd->lock );
207        return ret;
208    }
209    snprintf( gFd->open[winner].folder, MAX_PATH_LENGTH, "%s", folder );
210    snprintf( gFd->open[winner].name, MAX_PATH_LENGTH, "%s", name );
211    gFd->open[winner].write = write;
212
213done:
214    gFd->open[winner].status = STATUS_USED;
215    gFd->open[winner].date   = tr_date();
216    tr_lockUnlock( &gFd->lock );
217   
218    return gFd->open[winner].file;
219}
220
221/***********************************************************************
222 * tr_fdFileRelease
223 **********************************************************************/
224void tr_fdFileRelease( int file )
225{
226    int i;
227    tr_lockLock( &gFd->lock );
228
229    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
230    {
231        if( gFd->open[i].file == file )
232        {
233            gFd->open[i].status = STATUS_UNUSED;
234            break;
235        }
236    }
237   
238    tr_condSignal( &gFd->cond );
239    tr_lockUnlock( &gFd->lock );
240}
241
242/***********************************************************************
243 * tr_fdFileClose
244 **********************************************************************/
245void tr_fdFileClose( char * folder, char * name )
246{
247    int i;
248
249    tr_lockLock( &gFd->lock );
250
251    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
252    {
253        if( gFd->open[i].status & STATUS_INVALID )
254        {
255            continue;
256        }
257        if( !strcmp( folder, gFd->open[i].folder ) &&
258            !strcmp( name, gFd->open[i].name ) )
259        {
260            CloseFile( i );
261        }
262    }
263
264    tr_lockUnlock( &gFd->lock );
265}
266
267
268/***********************************************************************
269 * Sockets
270 **********************************************************************/
271typedef struct
272{
273    int socket;
274    int priority;
275}
276tr_socket_t;
277
278/* Remember the priority of every socket we open, so that we can keep
279 * track of how many reserved file descriptors we are using */
280static tr_socket_t * gSockets = NULL;
281static int gSocketsSize = 0;
282static int gSocketsCount = 0;
283static void SocketSetPriority( int s, int priority )
284{
285    if( gSocketsSize < 1 )
286    {
287        gSocketsSize = 256;
288        gSockets = malloc( gSocketsSize * sizeof( tr_socket_t ) );
289    }
290    if( gSocketsSize <= gSocketsCount )
291    {
292        gSocketsSize *= 2;
293        gSockets = realloc( gSockets, gSocketsSize * sizeof( tr_socket_t ) );
294    }
295    gSockets[gSocketsCount].socket = s;
296    gSockets[gSocketsCount].priority = priority;
297    gSocketsCount++;
298}
299static int SocketGetPriority( int s )
300{
301    int i, ret;
302    for( i = 0; i < gSocketsCount; i++ )
303        if( gSockets[i].socket == s )
304            break;
305    if( i >= gSocketsCount )
306    {
307        tr_err( "could not find that socket (%d)!", s );
308        return -1;
309    }
310    ret = gSockets[i].priority;
311    gSocketsCount--;
312    memmove( &gSockets[i], &gSockets[i+1],
313            ( gSocketsCount - i ) * sizeof( tr_socket_t ) );
314    return ret;
315}
316
317/***********************************************************************
318 * tr_fdSocketCreate
319 **********************************************************************/
320int tr_fdSocketCreate( int type, int priority )
321{
322    int s = -1;
323
324    tr_lockLock( &gFd->lock );
325    if( ( priority && gFd->reserved < TR_RESERVED_FDS ) ||
326        ( !priority && gFd->normal < gFd->normalMax ) )
327    {
328       if( ( s = socket( AF_INET, type, 0 ) ) < 0 )
329       {
330           tr_err( "Could not create socket (%s)", strerror( errno ) );
331       }
332    }
333    if( s > -1 )
334    {
335        SocketSetPriority( s, priority );
336        if( priority )
337            gFd->reserved++;
338        else
339            gFd->normal++;
340    }
341    tr_lockUnlock( &gFd->lock );
342
343    return s;
344}
345
346int tr_fdSocketAccept( int b, struct in_addr * addr, in_port_t * port )
347{
348    int s = -1;
349    unsigned len;
350    struct sockaddr_in sock;
351
352    tr_lockLock( &gFd->lock );
353    if( gFd->normal < gFd->normalMax )
354    {
355        len = sizeof( sock );
356        s = accept( b, (struct sockaddr *) &sock, &len );
357    }
358    if( s > -1 )
359    {
360        SocketSetPriority( s, 0 );
361        if( NULL != addr )
362        {
363            *addr = sock.sin_addr;
364        }
365        if( NULL != port )
366        {
367            *port = sock.sin_port;
368        }
369        gFd->normal++;
370    }
371    tr_lockUnlock( &gFd->lock );
372
373    return s;
374}
375
376/***********************************************************************
377 * tr_fdSocketClose
378 **********************************************************************/
379void tr_fdSocketClose( int s )
380{
381    tr_lockLock( &gFd->lock );
382#ifdef BEOS_NETSERVER
383    closesocket( s );
384#else
385    close( s );
386#endif
387    if( SocketGetPriority( s ) )
388        gFd->reserved--;
389    else
390        gFd->normal--;
391    tr_lockUnlock( &gFd->lock );
392}
393
394/***********************************************************************
395 * tr_fdClose
396 **********************************************************************/
397void tr_fdClose()
398{
399    tr_lockClose( &gFd->lock );
400    tr_condClose( &gFd->cond );
401    free( gFd );
402}
403
404
405/***********************************************************************
406 * Local functions
407 **********************************************************************/
408
409/***********************************************************************
410 * ErrorFromErrno
411 **********************************************************************/
412static int ErrorFromErrno()
413{
414    if( errno == EACCES || errno == EROFS )
415        return TR_ERROR_IO_PERMISSIONS;
416    return TR_ERROR_IO_OTHER;
417}
418
419/***********************************************************************
420 * CheckFolder
421 ***********************************************************************
422 *
423 **********************************************************************/
424static int OpenFile( int i, char * folder, char * name, int write )
425{
426    tr_openFile_t * file = &gFd->open[i];
427    struct stat sb;
428    char * path;
429
430    tr_dbg( "Opening %s in %s (%d)", name, folder, write );
431
432    /* Make sure the parent folder exists */
433    if( stat( folder, &sb ) || !S_ISDIR( sb.st_mode ) )
434    {
435        return TR_ERROR_IO_PARENT;
436    }
437
438    asprintf( &path, "%s/%s", folder, name );
439
440    /* Create subfolders, if any */
441    if( write )
442    {
443        char * p = path + strlen( folder ) + 1;
444        char * s;
445
446        while( ( s = strchr( p, '/' ) ) )
447        {
448            *s = '\0';
449            if( stat( path, &sb ) )
450            {
451                if( mkdir( path, 0777 ) )
452                {
453                    tr_err( "Could not create folder '%s'", path );
454                    free( path );
455                    return ErrorFromErrno();
456                }
457            }
458            else
459            {
460                if( !S_ISDIR( sb.st_mode ) )
461                {
462                    tr_err( "Is not a folder: '%s'", path );
463                    free( path );
464                    return TR_ERROR_IO_OTHER;
465                }
466            }
467            *s = '/';
468            p = s + 1;
469        }
470    }
471
472    /* Now try to really open the file */
473    file->file = open( path, write ? ( O_RDWR | O_CREAT ) : O_RDONLY, 0666 );
474    free( path );
475
476    if( file->file < 0 )
477    {
478        int ret = ErrorFromErrno();
479        tr_err( "Could not open %s in %s (%d, %d)", name, folder, write, ret );
480        return ret;
481    }
482
483    return TR_OK;
484}
485
486/***********************************************************************
487 * CloseFile
488 ***********************************************************************
489 * We first mark it as closing then release the lock while doing so,
490 * because close() may take same time and we don't want to block other
491 * threads.
492 **********************************************************************/
493static void CloseFile( int i )
494{
495    tr_openFile_t * file = &gFd->open[i];
496
497    /* If it's already being closed by another thread, just wait till
498     * it is done */
499    while( file->status & STATUS_CLOSING )
500    {
501        tr_condWait( &gFd->cond, &gFd->lock );
502    }
503    if( file->status & STATUS_INVALID )
504    {
505        return;
506    }
507
508    /* Nobody is closing it already, so let's do it */
509    if( file->status & STATUS_USED )
510    {
511        tr_err( "CloseFile: closing a file that's being used!" );
512    }
513    tr_dbg( "Closing %s in %s (%d)", file->name, file->folder, file->write );
514    file->status = STATUS_CLOSING;
515    tr_lockUnlock( &gFd->lock );
516    close( file->file );
517    tr_lockLock( &gFd->lock );
518    file->status = STATUS_INVALID;
519    tr_condSignal( &gFd->cond );
520}
521
Note: See TracBrowser for help on using the repository browser.