source: trunk/libtransmission/fdlimit.c @ 7129

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

(libT) #1482: fix another win32-related preallocate bug. Thanks to Spry and Lubomir for fixing my bugs. ;)

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