/******************************************************************************
 * $Id: fdlimit.c 261 2006-05-29 21:27:31Z titer $
 *
 * Copyright (c) 2005-2006 Transmission authors and contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/

#include "transmission.h"

#define TR_MAX_OPEN_FILES 16 /* That is, real files, not sockets */
#define TR_RESERVED_FDS   16 /* Number of sockets reserved for
                                connections to trackers */

typedef struct tr_openFile_s
{
    char       path[MAX_PATH_LENGTH];
    int        file;

#define STATUS_INVALID 1
#define STATUS_UNUSED  2
#define STATUS_USED    4
#define STATUS_CLOSING 8
    int        status;

    uint64_t   date;

} tr_openFile_t;

struct tr_fd_s
{
    tr_lock_t       lock;
    
    int             reserved;

    int             normal;
    int             normalMax;

    tr_openFile_t   open[TR_MAX_OPEN_FILES];
};

/***********************************************************************
 * tr_fdInit
 **********************************************************************/
tr_fd_t * tr_fdInit()
{
    tr_fd_t * f;
    int i, j, s[4096];

    f = calloc( sizeof( tr_fd_t ), 1 );

    /* Init lock */
    tr_lockInit( &f->lock );

    /* Detect the maximum number of open files or sockets */
    for( i = 0; i < 4096; i++ )
    {
        if( ( s[i] = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
        {
            break;
        }
    }
    for( j = 0; j < i; j++ )
    {
        tr_netClose( s[j] );
    }

    tr_dbg( "%d usable file descriptors", i );

    f->reserved  = 0;
    f->normal    = 0;

    f->normalMax = i - TR_RESERVED_FDS - 10;
        /* To be safe, in case the UI needs to write a preferences file
           or something */

    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
    {
        f->open[i].status = STATUS_INVALID;
    }

    return f;
}

/***********************************************************************
 * tr_fdFileOpen
 **********************************************************************/
int tr_fdFileOpen( tr_fd_t * f, char * path )
{
    int i, winner;
    uint64_t date;

    tr_lockLock( &f->lock );

    /* Is it already open? */
    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
    {
        if( f->open[i].status > STATUS_INVALID &&
            !strcmp( path, f->open[i].path ) )
        {
            if( f->open[i].status & STATUS_CLOSING )
            {
                /* Wait until the file is closed */
                tr_lockUnlock( &f->lock );
                tr_wait( 10 );
                tr_lockLock( &f->lock );
                i = -1;
                continue;
            }
            winner = i;
            goto done;
        }
    }

    /* Can we open one more file? */
    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
    {
        if( f->open[i].status & STATUS_INVALID )
        {
            winner = i;
            goto open;
        }
    }

    for( ;; )
    {
        /* Close the oldest currently unused file */
        date   = tr_date() + 1;
        winner = -1;

        for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
        {
            if( !( f->open[i].status & STATUS_UNUSED ) )
            {
                continue;
            }
            if( f->open[i].date < date )
            {
                winner = i;
                date   = f->open[i].date;
            }
        }

        if( winner >= 0 )
        {
            /* Close the file: we mark it as closing then release the
               lock while doing so, because close may take same time
               and we don't want to block other threads */
            tr_dbg( "Closing %s", f->open[winner].path );
            f->open[winner].status = STATUS_CLOSING;
            tr_lockUnlock( &f->lock );
            close( f->open[winner].file );
            tr_lockLock( &f->lock );
            goto open;
        }

        /* All used! Wait a bit and try again */
        tr_lockUnlock( &f->lock );
        tr_wait( 10 );
        tr_lockLock( &f->lock );
    }

open:
    tr_dbg( "Opening %s", path );
    snprintf( f->open[winner].path, MAX_PATH_LENGTH, "%s", path );
    f->open[winner].file = open( path, O_RDWR, 0 );

done:
    f->open[winner].status = STATUS_USED;
    f->open[winner].date   = tr_date();
    tr_lockUnlock( &f->lock );
    
    return f->open[winner].file;
}

/***********************************************************************
 * tr_fdFileRelease
 **********************************************************************/
void tr_fdFileRelease( tr_fd_t * f, int file )
{
    int i;
    tr_lockLock( &f->lock );

    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
    {
        if( f->open[i].file == file )
        {
            f->open[i].status = STATUS_UNUSED;
            break;
        }
    }
    
    tr_lockUnlock( &f->lock );
}

/***********************************************************************
 * tr_fdFileClose
 **********************************************************************/
void tr_fdFileClose( tr_fd_t * f, char * path )
{
    int i;

    tr_lockLock( &f->lock );

    /* Is it already open? */
    for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
    {
        if( f->open[i].status & STATUS_INVALID )
        {
            continue;
        }
        if( !strcmp( path, f->open[i].path ) )
        {
            tr_dbg( "Closing %s", path );
            close( f->open[i].file );
            f->open[i].status = STATUS_INVALID;
            break;
        }
    }

    tr_lockUnlock( &f->lock );
}

int tr_fdSocketWillCreate( tr_fd_t * f, int reserved )
{
    int ret;

    tr_lockLock( &f->lock );

    if( reserved )
    {
        if( f->reserved < TR_RESERVED_FDS )
        {
            ret = 0;
            (f->reserved)++;
        }
        else
        {
            ret = 1;
        }
    }
    else
    {
        if( f->normal < f->normalMax )
        {
            ret = 0;
            (f->normal)++;
        }
        else
        {
            ret = 1;
        }
    }

    tr_lockUnlock( &f->lock );

    return ret;
}

void tr_fdSocketClosed( tr_fd_t * f, int reserved )
{
    tr_lockLock( &f->lock );

    if( reserved )
    {
        (f->reserved)--;
    }
    else
    {
        (f->normal)--;
    }

    tr_lockUnlock( &f->lock );
}

void tr_fdClose( tr_fd_t * f )
{
    tr_lockClose( &f->lock );
    free( f );
}

