source: trunk/libtransmission/fdlimit.c @ 3171

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