/*********************************************************************
 *
 * This is based on code created by Peter Harvey,
 * (pharvey@codebydesign.com).
 *
 * Modified and extended by Nick Gorham
 * (nick@easysoft.com).
 *
 * Any bugs or problems should be considered the fault of Nick and not
 * Peter.
 *
 * copyright (c) 1999 Nick Gorham
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 **********************************************************************
 *
 * $Id: __handles.c,v 1.2 1999/12/10 01:50:38 harvey Exp $
 *
 * $Log: __handles.c,v $
 * Revision 1.2  1999/12/10 01:50:38  harvey
 * Updated with current sources from unixODBC cvs.
 *
 * Revision 1.9  1999/12/01 09:20:07  ngorham
 *
 * Fix some threading problems
 *
 * Revision 1.8  1999/11/13 23:41:01  ngorham
 *
 * Alter the way DM logging works
 * Upgrade the Postgres driver to 6.4.6
 *
 * Revision 1.7  1999/11/10 03:51:34  ngorham
 *
 * Update the error reporting in the DM to enable ODBC 3 and 2 calls to
 * work at the same time
 *
 * Revision 1.6  1999/08/05 18:59:49  ngorham
 *
 * Typo error found by Greg Bentz
 *
 * Revision 1.5  1999/08/03 21:47:39  shandyb
 * Moving to automake: changed files in DriverManager
 *
 * Revision 1.4  1999/07/10 21:10:17  ngorham
 *
 * Adjust error sqlstate from driver manager, depending on requested
 * version (ODBC2/3)
 *
 * Revision 1.3  1999/07/04 21:05:08  ngorham
 *
 * Add LGPL Headers to code
 *
 * Revision 1.2  1999/06/30 23:56:56  ngorham
 *
 * Add initial thread safety code
 *
 * Revision 1.1.1.1  1999/05/29 13:41:09  sShandyb
 * first go at it
 *
 * Revision 1.1.1.1  1999/05/27 18:23:18  pharvey
 * Imported sources
 *
 * Revision 1.3  1999/05/09 23:27:11  nick
 * All the API done now
 *
 * Revision 1.2  1999/05/03 19:50:43  nick
 * Another check point
 *
 * Revision 1.1  1999/04/25 23:06:11  nick
 * Initial revision
 *
 *
 **********************************************************************/

#include <ctype.h>
#include "drivermanager.h"

static char const rcsid[]= "$RCSfile: __handles.c,v $ $Revision: 1.2 $";

/*
 * these are used to enable us to check if a handle is
 * valid without the danger of a seg-vio.
 */

static DMHENV enviroment_root;
static DMHDBC connection_root;
static DMHSTMT statement_root;
static DMHDESC descriptor_root;

#ifdef HAVE_LIBPTHREAD

/*
 * use just one mutex for all the lists, this avoids any issues
 * with deadlocks, the performance issue should be minimal, if it
 * turns out to be a problem, we can readdress this
 *
 * If compiled with thread support the DM allows four different
 * thread strategies
 *
 * Level 0 - Only the DM internal structures are protected
 * the driver is assumed to take care of it's self
 *
 * Level 1 - The driver is protected down to the statement level
 * each statement will be protected, and the same for the connect 
 * level for connect functions, note that descriptors are considered
 * equal to statements when it comes to thread protection.
 *
 * Level 2 - The driver is protected at the connection level. only
 * one thread can be in a particular driver at one time
 *
 * Level 3 - The driver is protected at the env level, only one thing
 * at a time.
 *
 * By default the driver open connections with a lock level of 3, 
 * this can be changed by adding the line
 *
 * Threading = N
 *
 * to the driver entry in odbcinst.ini, where N is the locking level 
 * (0-3)
 * 
 */

#include <pthread.h>

static pthread_mutex_t mutex_lists = PTHREAD_MUTEX_INITIALIZER;

int mutex_entry( pthread_mutex_t *mutex )
{
    return pthread_mutex_lock( mutex );
}

int mutex_exit( pthread_mutex_t *mutex )
{
    return pthread_mutex_unlock( mutex );
}

#else

#define mutex_entry(x)
#define mutex_exit(x)

#endif

/*
 * allocate and register a environment handle
 */

DMHENV __alloc_env( void )
{
    DMHENV environment = NULL;
    char s0[ 20 ];

    mutex_entry( &mutex_lists );

    environment = calloc( sizeof( *environment ), 1 );

    if ( environment )
    {
        char tracing_string[ 64 ];
        char tracing_file[ 64 ];

        /*
         * add to list of env handles
         */

        environment -> next_class_list = enviroment_root;
        enviroment_root = environment;
        environment -> type = HENV_MAGIC;

        SQLGetPrivateProfileString( "ODBC", "Trace", "No",
				tracing_string, sizeof( tracing_string ), 
                "odbcinst.ini" );

        if ( tracing_string[ 0 ] == '1' ||
                toupper( tracing_string[ 0 ] ) == 'Y' ||
                toupper( tracing_string[ 0 ] ) == 'O' )
        {
            SQLGetPrivateProfileString( "ODBC", "Trace File", "/tmp/sql.log",
                    tracing_file, sizeof( tracing_file ), 
                    "odbcinst.ini" );

            /*
             * start logging
             */

            if ( !dm_log_open( &environment -> log_handle,
                        "ODBC", tracing_file  ))
            {
                environment -> log_handle = NULL;
            }

            sprintf( environment -> msg,
                    "\n\t\tExit:[SQL_SUCCESS]\n\t\t\tEnvironment = %p",	environment );

            dm_log_write( environment -> log_handle,
                    __get_pid( s0 ),
                    __FILE__,
                    __LINE__,
                    LOG_INFO,
                    LOG_INFO, environment -> msg );
        }
    }

    setup_error_head( &environment -> error, environment,
            SQL_HANDLE_ENV );

#ifdef HAVE_LIBPTHREAD
    pthread_mutex_init( &environment -> mutex, NULL );
#endif

    mutex_exit( &mutex_lists );

    return environment;
}

/*
 * check that a env is real
 */

int __validate_env( DMHENV env )
{
    DMHENV ptr = enviroment_root;
    int ret = 0;

    mutex_entry( &mutex_lists );

    while( ptr )
    {
        if ( ptr == env )
        {
            ret = 1;
            break;
        }

        ptr = ptr -> next_class_list;
    }

    mutex_exit( &mutex_lists );

    return ret;
}

/*
 * remove from list
 */

void __release_env( DMHENV environment )
{
    DMHENV last = NULL;
    DMHENV ptr = enviroment_root;

    mutex_entry( &mutex_lists );

    while( ptr )
    {
        if ( environment == ptr )
        {
            break;
        }
        last = ptr;
        ptr = ptr -> next_class_list;
    }

    if ( ptr )
    {
        if ( last )
        {
            last -> next_class_list = ptr -> next_class_list;
        }
        else
        {
            enviroment_root = ptr -> next_class_list;
        }
    }

    clear_error_head( &environment -> error );

	/*
	 * free log
	 */

	if ( environment->log_handle )
		dm_log_close( environment->log_handle );

#ifdef HAVE_LIBPTHREAD
    pthread_mutex_destroy( &environment -> mutex );
#endif

    free( environment );

    mutex_exit( &mutex_lists );
}

/*
 * get the root, for use in SQLEndTran and SQLTransact
 */

DMHDBC __get_dbc_root( void )
{
    return connection_root;
}

/*
 * allocate and register a connection handle
 */

DMHDBC __alloc_dbc( void )
{
    DMHDBC connection = NULL;

    mutex_entry( &mutex_lists );

    connection = calloc( sizeof( *connection ), 1 );

    if ( connection )
    {
        /*
         * add to list of connection handles
         */

        connection -> next_class_list = connection_root;
        connection_root = connection;
        connection -> type = HDBC_MAGIC;
    }

    setup_error_head( &connection -> error, connection,
            SQL_HANDLE_DBC );

#ifdef HAVE_LIBPTHREAD
    pthread_mutex_init( &connection -> mutex, NULL );
    /*
     * for the moment protect on a environment level
     */
    connection -> protection_level = TS_LEVEL3;
#endif

    mutex_exit( &mutex_lists );

    return connection;
}

/*
 * adjust the threading level
 */

void dbc_change_thread_support( DMHDBC connection, int level )
{
#ifdef HAVE_LIBPTHREAD

    if ( connection -> protection_level == level )
        return;

    mutex_entry( &mutex_lists );

    /*
     * switch level
     * If the previous level was at less than connection level,
     * we need to create a lock at the environment level then release
     * the connection lock.
     * 
     * If we are moving from a greater than env lock, the current lock
     * on the connection will be ok
     */ 

    if ( level == TS_LEVEL3 )
    {
        mutex_entry( &connection -> environment -> mutex );
        mutex_exit( &connection -> mutex );
    }
    else if ( connection -> protection_level == TS_LEVEL3 )
    {
        /*
         * if we are moving from level 3 we have to create the new
         * connection lock, and remove the env lock
         */
        mutex_entry( &connection -> mutex );
        mutex_exit( &connection -> environment -> mutex );
    }
    connection -> protection_level = level;

    mutex_exit( &mutex_lists );
                        
#endif
}

/*
 * check that a connection is real
 */

int __validate_dbc( DMHDBC connection )
{
    DMHDBC ptr = connection_root;
    int ret = 0;

    mutex_entry( &mutex_lists );

    while( ptr )
    {
        if ( ptr == connection )
        {
            ret = 1;
            break;
        }

        ptr = ptr -> next_class_list;
    }

    mutex_exit( &mutex_lists );

    return ret;
}

/*
 * remove from list
 */

void __release_dbc( DMHDBC connection )
{
    DMHDBC last = NULL;
    DMHDBC ptr = connection_root;

    mutex_entry( &mutex_lists );

    while( ptr )
    {
        if ( connection == ptr )
        {
            break;
        }
        last = ptr;
        ptr = ptr -> next_class_list;
    }

    if ( ptr )
    {
        if ( last )
        {
            last -> next_class_list = ptr -> next_class_list;
        }
        else
        {
            connection_root = ptr -> next_class_list;
        }
    }

    clear_error_head( &connection -> error );

#ifdef HAVE_LIBPTHREAD
    pthread_mutex_destroy( &connection -> mutex );
#endif

    free( connection );

    mutex_exit( &mutex_lists );
}

/*
 * allocate and register a statement handle
 */

DMHSTMT __alloc_stmt( void )
{
    DMHSTMT statement = NULL;

    mutex_entry( &mutex_lists );

    statement = calloc( sizeof( *statement ), 1 );

    if ( statement )
    {
        /*
         * add to list of statement handles
         */

        statement -> next_class_list = statement_root;
        statement_root = statement;
        statement -> type = HSTMT_MAGIC;
    }

    setup_error_head( &statement -> error, statement,
            SQL_HANDLE_STMT );

#ifdef HAVE_LIBPTHREAD
    pthread_mutex_init( &statement -> mutex, NULL );
#endif

    mutex_exit( &mutex_lists );

    return statement;
}

/*
 * check that a statement is real
 */

int __validate_stmt( DMHSTMT statement )
{
    DMHSTMT ptr = statement_root;
    int ret = 0;

    mutex_entry( &mutex_lists );

    while( ptr )
    {
        if ( ptr == statement )
        {
            ret = 1;
            break;
        }

        ptr = ptr -> next_class_list;
    }

    mutex_exit( &mutex_lists );

    return ret;
}

/*
 * remove from list
 */

void __release_stmt( DMHSTMT statement )
{
    DMHSTMT last = NULL;
    DMHSTMT ptr = statement_root;

    mutex_entry( &mutex_lists );

    while( ptr )
    {
        if ( statement == ptr )
        {
            break;
        }
        last = ptr;
        ptr = ptr -> next_class_list;
    }

    if ( ptr )
    {
        if ( last )
        {
            last -> next_class_list = ptr -> next_class_list;
        }
        else
        {
            statement_root = ptr -> next_class_list;
        }
    }

    clear_error_head( &statement -> error );

#ifdef HAVE_LIBPTHREAD
    pthread_mutex_destroy( &statement -> mutex );
#endif

    free( statement );

    mutex_exit( &mutex_lists );
}

/*
 * allocate and register a descriptor handle
 */

DMHDESC __alloc_desc( void )
{
    DMHDESC descriptor = NULL;

    mutex_entry( &mutex_lists );

    descriptor = calloc( sizeof( *descriptor ), 1 );

    if ( descriptor )
    {
        /*
         * add to list of descriptor handles
         */

        descriptor -> next_class_list = descriptor_root;
        descriptor_root = descriptor;
        descriptor -> type = HDESC_MAGIC;
    }

    setup_error_head( &descriptor -> error, descriptor,
            SQL_HANDLE_DESC );

#ifdef HAVE_LIBPTHREAD
    pthread_mutex_init( &descriptor -> mutex, NULL );
#endif

    mutex_exit( &mutex_lists );

    return descriptor;
}

/*
 * check that a descriptor is real
 */

int __validate_desc( DMHDESC descriptor )
{
    DMHDESC ptr = descriptor_root;
    int ret = 0;

    mutex_entry( &mutex_lists );

    while( ptr )
    {
        if ( ptr == descriptor )
        {
            ret = 1;
            break;
        }

        ptr = ptr -> next_class_list;
    }

    mutex_exit( &mutex_lists );

    return ret;
}

/*
 * remove from list
 */

void __release_desc( DMHDESC descriptor )
{
    DMHDESC last = NULL;
    DMHDESC ptr = descriptor_root;

    mutex_entry( &mutex_lists );

    while( ptr )
    {
        if ( descriptor == ptr )
        {
            break;
        }
        last = ptr;
        ptr = ptr -> next_class_list;
    }

    if ( ptr )
    {
        if ( last )
        {
            last -> next_class_list = ptr -> next_class_list;
        }
        else
        {
            descriptor_root = ptr -> next_class_list;
        }
    }

    clear_error_head( &descriptor -> error );

#ifdef HAVE_LIBPTHREAD
    pthread_mutex_destroy( &descriptor -> mutex );
#endif

    free( descriptor );

    mutex_exit( &mutex_lists );
}

#ifdef HAVE_LIBPTHREAD

void thread_protect( int type, void *handle )
{
    DMHENV environment;
    DMHDBC connection;
    DMHSTMT statement;
    DMHDESC descriptor;

    switch( type )
    {
      case SQL_HANDLE_ENV:
        environment = handle;
        mutex_entry( &environment -> mutex );
        break;

      case SQL_HANDLE_DBC:
        connection = handle;
        if ( connection -> protection_level == TS_LEVEL3 )
        {
            mutex_entry( &connection -> environment -> mutex );
        }
        else if ( connection -> protection_level == TS_LEVEL2 ||
                connection -> protection_level == TS_LEVEL1 )
        {
            mutex_entry( &connection -> mutex );
        }
        break;

      case SQL_HANDLE_STMT:
        statement = handle;
        if ( statement -> connection -> protection_level == TS_LEVEL3 )
        {
            mutex_entry( &statement -> connection -> environment -> mutex );
        }
        else if ( statement -> connection -> protection_level == TS_LEVEL2 )
        {
            mutex_entry( &statement -> connection -> mutex );
        }
        else if ( statement -> connection -> protection_level == TS_LEVEL1 )
        {
            mutex_entry( &statement -> mutex );
        }
        break;

      case SQL_HANDLE_DESC:
        descriptor = handle;
        if ( descriptor -> connection -> protection_level == TS_LEVEL3 )
        {
            mutex_entry( &statement -> connection -> environment -> mutex );
        }
        if ( descriptor -> connection -> protection_level == TS_LEVEL2 )
        {
            mutex_entry( &statement -> connection -> mutex );
        }
        if ( descriptor -> connection -> protection_level == TS_LEVEL1 )
        {
            mutex_entry( &descriptor -> mutex );
        }
        break;
    }
}

void thread_release( int type, void *handle )
{
    DMHENV environment;
    DMHDBC connection;
    DMHSTMT statement;
    DMHDESC descriptor;

    switch( type )
    {
      case SQL_HANDLE_ENV:
        environment = handle;
        mutex_exit( &environment -> mutex );
        break;

      case SQL_HANDLE_DBC:
        connection = handle;
        if ( connection -> protection_level == TS_LEVEL3 )
        {
            mutex_exit( &connection -> environment -> mutex );
        }
        else if ( connection -> protection_level == TS_LEVEL2 ||
                connection -> protection_level == TS_LEVEL1 )
        {
            mutex_exit( &connection -> mutex );
        }
        break;

      case SQL_HANDLE_STMT:
        statement = handle;
        if ( statement -> connection -> protection_level == TS_LEVEL3 )
        {
            mutex_exit( &statement -> connection -> environment -> mutex );
        }
        else if ( statement -> connection -> protection_level == TS_LEVEL2 )
        {
            mutex_exit( &statement -> connection -> mutex );
        }
        else if ( statement -> connection -> protection_level == TS_LEVEL1 )
        {
            mutex_exit( &statement -> mutex );
        }
        break;

      case SQL_HANDLE_DESC:
        descriptor = handle;
        if ( descriptor -> connection -> protection_level == TS_LEVEL3 )
        {
            mutex_exit( &statement -> connection -> environment -> mutex );
        }
        if ( descriptor -> connection -> protection_level == TS_LEVEL2 )
        {
            mutex_exit( &statement -> connection -> mutex );
        }
        if ( descriptor -> connection -> protection_level == TS_LEVEL1 )
        {
            mutex_exit( &descriptor -> mutex );
        }
        break;
    }
}

#endif
