source: trunk/libtransmission/fdlimit.c @ 8791

Last change on this file since 8791 was 8791, checked in by titer, 12 years ago

Don't assume that errnos are positive (they are not on Haiku), and
pass them on through an additional parameter if needed

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