source: trunk/libtransmission/fdlimit.c @ 2544

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

this looks bug but it's not: just janitorial cleanup, moving #includes from headers into source file

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