source: trunk/libtransmission/fdlimit.c @ 2154

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