source: trunk/libtransmission/fdlimit.c @ 7051

Last change on this file since 7051 was 7051, checked in by charles, 14 years ago

(libT) #849: preallocate files when possible to prevent disk fragmentation

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