source: trunk/libtransmission/fdlimit.c @ 2555

Last change on this file since 2555 was 2555, checked in by charles, 15 years ago

add portability wrapper for in_port_t...

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