source: trunk/libtransmission/fdlimit.c @ 7115

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

(libT) get file preallocation working on Windows by copying how fsutil does it. (source: http://social.msdn.microsoft.com/forums/en-US/vclanguage/thread/4dabec8e-2909-40b3-b398-66bd4c587b9b/)

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