source: trunk/libtransmission/fdlimit.c @ 7735

Last change on this file since 7735 was 7735, checked in by charles, 13 years ago

(trunk libT) check in some of the extra error logging messages developed last night while chasing down the problem denis found

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