source: trunk/libtransmission/fdlimit.c @ 7099

Last change on this file since 7099 was 7099, checked in by charles, 12 years ago

(libT) don't use getrlimit() / setrlimit() anymore. this doesn't do much good and could possibly do harm wrt fighting with libcurl for available sockets.

  • Property svn:keywords set to Date Rev Author Id
File size: 12.2 KB
Line 
1/******************************************************************************
2 * $Id: fdlimit.c 7099 2008-11-12 04:05:27Z charles $
3 *
4 * Copyright (c) 2005-2008 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 <inttypes.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#ifdef SYS_DARWIN
32#include <fcntl.h>
33#endif
34
35#ifdef HAVE_FALLOCATE
36 #include <linux/falloc.h>
37#endif
38
39#include <sys/types.h>
40#include <sys/stat.h>
41#include <unistd.h>
42#include <fcntl.h> /* O_LARGEFILE */
43
44#include <event.h>
45#include <evutil.h>
46
47#include "transmission.h"
48#include "fdlimit.h"
49#include "list.h"
50#include "net.h"
51#include "platform.h" /* tr_lock */
52#include "utils.h"
53
54#define dbgmsg( ... ) \
55    do { \
56        if( tr_deepLoggingIsActive( ) ) \
57            tr_deepLog( __FILE__, __LINE__, NULL, __VA_ARGS__ ); \
58    } while( 0 )
59
60/**
61***
62**/
63
64enum
65{
66    TR_MAX_OPEN_FILES = 16, /* real files, not sockets */
67
68    NOFILE_BUFFER = 512, /* the process' number of open files is
69                            globalMaxPeers + NOFILE_BUFFER */
70};
71
72struct tr_openfile
73{
74    unsigned int    isCheckedOut  : 1;
75    unsigned int    isWritable    : 1;
76    unsigned int    closeWhenDone : 1;
77    char            filename[MAX_PATH_LENGTH];
78    int             fd;
79    uint64_t        date;
80};
81
82struct tr_fd_s
83{
84    int                   socketCount;
85    int                   socketMax;
86    tr_lock *             lock;
87    struct tr_openfile    open[TR_MAX_OPEN_FILES];
88};
89
90static struct tr_fd_s * gFd = NULL;
91
92/***
93****
94****  Local Files
95****
96***/
97
98static int
99preallocateFile( int fd UNUSED, uint64_t length UNUSED )
100{
101#ifdef HAVE_FALLOCATE
102
103    return fallocate( fd, FALLOC_FL_KEEP_SIZE, 0, length );
104
105#elif defined(HAVE_POSIX_FALLOCATE)
106
107    return posix_fallocate( fd, 0, length );
108
109#elif defined(SYS_DARWIN)
110
111    fstore_t fst;
112    fst.fst_flags = F_ALLOCATECONTIG;
113    fst.fst_posmode = F_PEOFPOSMODE;
114    fst.fst_offset = 0;
115    fst.fst_length = length;
116    fst.fst_bytesalloc = 0;
117    return fcntl( fd, F_PREALLOCATE, &fst );
118
119#else
120
121    #warning no known method to preallocate files on this platform
122    return -1;
123
124#endif
125}
126
127/**
128 * returns 0 on success, or an errno value on failure.
129 * errno values include ENOENT if the parent folder doesn't exist,
130 * plus the errno values set by tr_mkdirp() and open().
131 */
132static int
133TrOpenFile( int          i,
134            const char * folder,
135            const char * torrentFile,
136            int          doWrite,
137            int          doPreallocate,
138            uint64_t     desiredFileSize )
139{
140    struct tr_openfile * file = &gFd->open[i];
141    int                  flags;
142    char               * filename;
143    struct stat          sb;
144    int                  alreadyExisted;
145
146    /* confirm the parent folder exists */
147    if( stat( folder, &sb ) || !S_ISDIR( sb.st_mode ) )
148        return ENOENT;
149
150    /* create subfolders, if any */
151    filename = tr_buildPath( folder, torrentFile, NULL );
152    if( doWrite )
153    {
154        char * tmp = tr_dirname( filename );
155        const int err = tr_mkdirp( tmp, 0777 ) ? errno : 0;
156        tr_free( tmp );
157        if( err ) {
158            tr_free( filename );
159            return err;
160        }
161    }
162
163    alreadyExisted = !stat( filename, &sb ) && S_ISREG( sb.st_mode );
164   
165    /* open the file */
166    flags = doWrite ? ( O_RDWR | O_CREAT ) : O_RDONLY;
167#ifdef O_LARGEFILE
168    flags |= O_LARGEFILE;
169#endif
170#ifdef WIN32
171    flags |= O_BINARY;
172#endif
173    file->fd = open( filename, flags, 0666 );
174    if( file->fd == -1 )
175    {
176        const int err = errno;
177        tr_err( _( "Couldn't open \"%1$s\": %2$s" ), filename,
178               tr_strerror( err ) );
179        tr_free( filename );
180        return err;
181    }
182
183    if( ( file->fd >= 0 ) && !alreadyExisted && doPreallocate )
184        if( !preallocateFile( file->fd, desiredFileSize ) )
185            tr_inf( _( "Preallocated file \"%s\"" ), filename );
186       
187    tr_free( filename );
188    return 0;
189}
190
191static int
192fileIsOpen( const struct tr_openfile * o )
193{
194    return o->fd >= 0;
195}
196
197static void
198TrCloseFile( int i )
199{
200    struct tr_openfile * o = &gFd->open[i];
201
202    assert( i >= 0 );
203    assert( i < TR_MAX_OPEN_FILES );
204    assert( fileIsOpen( o ) );
205
206    close( o->fd );
207    o->fd = -1;
208    o->isCheckedOut = 0;
209}
210
211static int
212fileIsCheckedOut( const struct tr_openfile * o )
213{
214    return fileIsOpen( o ) && o->isCheckedOut;
215}
216
217/* returns an fd on success, or a -1 on failure and sets errno */
218int
219tr_fdFileCheckout( const char * folder,
220                   const char * torrentFile,
221                   int          doWrite,
222                   int          doPreallocate,
223                   uint64_t     desiredFileSize )
224{
225    int                  i, winner = -1;
226    struct tr_openfile * o;
227    char               * filename;
228
229    assert( folder && *folder );
230    assert( torrentFile && *torrentFile );
231    assert( doWrite == 0 || doWrite == 1 );
232
233    filename = tr_buildPath( folder, torrentFile, NULL );
234    dbgmsg( "looking for file '%s', writable %c", filename,
235            doWrite ? 'y' : 'n' );
236
237    tr_lockLock( gFd->lock );
238
239    /* Is it already open? */
240    for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
241    {
242        o = &gFd->open[i];
243
244        if( !fileIsOpen( o ) )
245            continue;
246
247        if( strcmp( filename, o->filename ) )
248            continue;
249
250        if( fileIsCheckedOut( o ) )
251        {
252            dbgmsg( "found it!  it's open, but checked out.  waiting..." );
253            tr_lockUnlock( gFd->lock );
254            tr_wait( 200 );
255            tr_lockLock( gFd->lock );
256            i = -1; /* reloop */
257            continue;
258        }
259
260        if( doWrite && !o->isWritable )
261        {
262            dbgmsg(
263                "found it!  it's open and available, but isn't writable. closing..." );
264            TrCloseFile( i );
265            break;
266        }
267
268        dbgmsg( "found it!  it's ready for use!" );
269        winner = i;
270        break;
271    }
272
273    dbgmsg(
274        "it's not already open.  looking for an open slot or an old file." );
275    while( winner < 0 )
276    {
277        uint64_t date = tr_date( ) + 1;
278
279        /* look for the file that's been open longest */
280        for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
281        {
282            o = &gFd->open[i];
283
284            if( !fileIsOpen( o ) )
285            {
286                winner = i;
287                dbgmsg( "found an empty slot in %d", winner );
288                break;
289            }
290
291            if( date > o->date )
292            {
293                date = o->date;
294                winner = i;
295            }
296        }
297
298        if( winner >= 0 )
299        {
300            if( fileIsOpen( &gFd->open[winner] ) )
301            {
302                dbgmsg( "closing file '%s', slot #%d",
303                        gFd->open[winner].filename,
304                        winner );
305                TrCloseFile( winner );
306            }
307        }
308        else
309        {
310            dbgmsg(
311                "everything's full!  waiting for someone else to finish something" );
312            tr_lockUnlock( gFd->lock );
313            tr_wait( 200 );
314            tr_lockLock( gFd->lock );
315        }
316    }
317
318    assert( winner >= 0 );
319    o = &gFd->open[winner];
320    if( !fileIsOpen( o ) )
321    {
322        const int err = TrOpenFile( winner, folder, torrentFile, doWrite, doPreallocate, desiredFileSize );
323        if( err ) {
324            tr_lockUnlock( gFd->lock );
325            tr_free( filename );
326            errno = err;
327            return -1;
328        }
329
330        dbgmsg( "opened '%s' in slot %d, doWrite %c", filename, winner,
331                doWrite ? 'y' : 'n' );
332        tr_strlcpy( o->filename, filename, sizeof( o->filename ) );
333        o->isWritable = doWrite;
334    }
335
336    dbgmsg( "checking out '%s' in slot %d", filename, winner );
337    o->isCheckedOut = 1;
338    o->closeWhenDone = 0;
339    o->date = tr_date( );
340    tr_free( filename );
341    tr_lockUnlock( gFd->lock );
342    return o->fd;
343}
344
345void
346tr_fdFileReturn( int fd )
347{
348    int i;
349
350    tr_lockLock( gFd->lock );
351
352    for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
353    {
354        struct tr_openfile * o = &gFd->open[i];
355        if( o->fd != fd )
356            continue;
357
358        dbgmsg( "releasing file '%s' in slot #%d", o->filename, i );
359        o->isCheckedOut = 0;
360        if( o->closeWhenDone )
361            TrCloseFile( i );
362
363        break;
364    }
365
366    tr_lockUnlock( gFd->lock );
367}
368
369void
370tr_fdFileClose( const char * filename )
371{
372    int i;
373
374    tr_lockLock( gFd->lock );
375
376    for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
377    {
378        struct tr_openfile * o = &gFd->open[i];
379        if( !fileIsOpen( o ) || strcmp( filename, o->filename ) )
380            continue;
381
382        dbgmsg( "tr_fdFileClose closing '%s'", filename );
383
384        if( !o->isCheckedOut )
385        {
386            dbgmsg( "not checked out, so closing it now... '%s'", filename );
387            TrCloseFile( i );
388        }
389        else
390        {
391            dbgmsg(
392                "flagging file '%s', slot #%d to be closed when checked in",
393                gFd->open[i].filename, i );
394            o->closeWhenDone = 1;
395        }
396    }
397
398    tr_lockUnlock( gFd->lock );
399}
400
401/***
402****
403****  Sockets
404****
405***/
406
407static int
408getSocketMax( struct tr_fd_s * gFd )
409{
410    return gFd->socketMax;
411}
412
413int
414tr_fdSocketCreate( int type )
415{
416    int s = -1;
417
418    tr_lockLock( gFd->lock );
419
420    if( gFd->socketCount < getSocketMax( gFd ) )
421        if( ( s = socket( AF_INET, type, 0 ) ) < 0 )
422            tr_err( _( "Couldn't create socket: %s" ),
423                   tr_strerror( sockerrno ) );
424
425    if( s > -1 )
426        ++gFd->socketCount;
427
428    assert( gFd->socketCount >= 0 );
429
430    tr_lockUnlock( gFd->lock );
431    return s;
432}
433
434int
435tr_fdSocketAccept( int              b,
436                   struct in_addr * addr,
437                   tr_port_t *      port )
438{
439    int                s = -1;
440    unsigned int       len;
441    struct sockaddr_in sock;
442
443    assert( addr );
444    assert( port );
445
446    tr_lockLock( gFd->lock );
447    if( gFd->socketCount < getSocketMax( gFd ) )
448    {
449        len = sizeof( sock );
450        s = accept( b, (struct sockaddr *) &sock, &len );
451    }
452    if( s > -1 )
453    {
454        *addr = sock.sin_addr;
455        *port = sock.sin_port;
456        ++gFd->socketCount;
457    }
458    tr_lockUnlock( gFd->lock );
459
460    return s;
461}
462
463static void
464socketClose( int fd )
465{
466#ifdef BEOS_NETSERVER
467    closesocket( fd );
468#else
469    EVUTIL_CLOSESOCKET( fd );
470#endif
471}
472
473void
474tr_fdSocketClose( int s )
475{
476    tr_lockLock( gFd->lock );
477
478    if( s >= 0 )
479    {
480        socketClose( s );
481        --gFd->socketCount;
482    }
483
484    assert( gFd->socketCount >= 0 );
485
486    tr_lockUnlock( gFd->lock );
487}
488
489/***
490****
491****  Startup / Shutdown
492****
493***/
494
495void
496tr_fdInit( int globalPeerLimit )
497{
498    int i;
499
500    assert( gFd == NULL );
501    gFd = tr_new0( struct tr_fd_s, 1 );
502    gFd->lock = tr_lockNew( );
503    gFd->socketMax = globalPeerLimit;
504    tr_dbg( "%d usable file descriptors", globalPeerLimit );
505
506    for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
507        gFd->open[i].fd = -1;
508}
509
510void
511tr_fdClose( void )
512{
513    int i = 0;
514
515    for( i = 0; i < TR_MAX_OPEN_FILES; ++i )
516        if( fileIsOpen( &gFd->open[i] ) )
517            TrCloseFile( i );
518
519    tr_lockFree( gFd->lock );
520
521    tr_free( gFd );
522    gFd = NULL;
523}
524
525void
526tr_fdSetPeerLimit( uint16_t n )
527{
528    assert( gFd != NULL && "tr_fdInit() must be called first!" );
529    gFd->socketMax = n;
530}
531
532uint16_t
533tr_fdGetPeerLimit( void )
534{
535    return gFd ? gFd->socketMax : -1;
536}
537
Note: See TracBrowser for help on using the repository browser.