
/****************************************************************************
**
**  Copyright (C) 2002  - the shmoo group -
**
**  This program is free software; you can redistribute it and/or
**  modify it, however, you cannot sell it.
**
**  This program 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.
**
**  You should have received a copy of the license attached to the
**  use of this software.  If not, visit www.shmoo.com/osiris for
**  details.
**
*****************************************************************************/

/*****************************************************************************
**
**    The Shmoo Group (TSG)
**
**    File:      osirisdb.c
**    Author:    Brian Wotring
**
**    Date:      May 14, 2002
**    Project:   osiris
**
******************************************************************************/


#include "libosiris.h"
#include "libosirism.h"
#include "libosirisdb.h"
#include "libfileapi.h"

int osi_db_create( OSI_DB *db )
{
    int result = OSI_ERROR_UNKNOWN;

    if( db != NULL )
    {
        int db_result = 0;
        u_int32_t flags = ( DB_CREATE );

        /* create the db handle. */

        db_result |= db_create( &( db->records ), NULL, 0 );

        if( db_result != 0 )
        {
            /* something bad happend when initializing one of these DBs. */
            /* we don't care which one it was, we bail here.             */

            return OSI_ERROR_DB_INIT_FAILED;
        }

        db_result |= ( db->records )->open( db->records, NULL, db->path,
                       NULL, OSI_DB_TYPE, flags, OSI_DB_PERMISSIONS );

        if( db_result != 0 )
        {
            /* something bad happened, delete it and bail. */

            ( db->records )->remove( db->records, db->path, NULL, 0 );

            return OSI_ERROR_DB_CREATE_FAILED;
        }

        /* now close the databases in this file. */

        db->error_id = 0;
        osi_db_close( db );

        result = OSI_DB_OK;
    }

    return result;
}

int osi_db_open( OSI_DB *db, int mode )
{
    int result = OSI_ERROR_UNKNOWN;

    if( db != NULL )
    {
        int db_result = 0;
        int flags = 0;

        /* get db handles for all of the databases in the file. */

        db_result |= db_create( &( db->records ), NULL, 0 );

        if( db_result != 0 )
        {
            /* something bad happend when initializing one of these DBs. */
            /* we don't care which one it was, we bail here.             */

            return OSI_ERROR_DB_INIT_FAILED;
        }

        /* now open the database based upon the mode. we never create them. */

        switch( mode )
        {
            case OSI_DB_READ:

                flags = ( flags | DB_RDONLY );

                db_result |= ( db->records )->open( db->records, NULL,
                               db->path, NULL, DB_UNKNOWN,
                               flags, OSI_DB_PERMISSIONS );
                break;

            case OSI_DB_WRITE:

                db_result |= ( db->records )->open( db->records, NULL,
                               db->path, NULL, DB_UNKNOWN,
                               flags, OSI_DB_PERMISSIONS );
                break;

            default:
                break;
        }

        /* now check our result, if we were unable to open any of them */
        /* we close them and bail.                                     */

        if( db_result == 0 )
        {
            db->open = TRUE;
            result   = OSI_DB_OK;
        }

        else
        {
            osi_db_close( db );
            result = OSI_ERROR_DB_OPEN_FAILED;
        }
    }

    return result;
}

int osi_db_close( OSI_DB *db )
{
    int result = OSI_ERROR_UNKNOWN;

    if( db != NULL )
    {
        result = OSI_DB_OK;

        if( db->records != NULL )
        {
            (db->records)->close( db->records, 0 );
            db->records = NULL;
        }

        db->open = FALSE;
    }

    return result;
}

int osi_db_truncate( OSI_DB *db )
{
    int result = OSI_ERROR_UNKNOWN;
    int truncate_count = 0;

    if( db != NULL )
    {
        (db->records)->truncate( db->records, NULL, &truncate_count, 0 );
    }

    return result;
}


int osi_db_delete( OSI_DB *db )
{
    int result = OSI_ERROR_UNKNOWN;

    if( db != NULL )
    {
        osi_db_close( db );
        result = osi_remove_file( db->path );
    }

    return result;
}

long osi_db_get_record_count( OSI_DB *db )
{       
    long count = -1;

    if( ( db != NULL ) && ( db->open ) )
    {
        DB_HASH_STAT *db_stats = NULL;

        if( db->records->stat( db->records, &db_stats, 0 ) == 0 )
        {
            if( db_stats != NULL )
            {
                count = (long)db_stats->hash_ndata;
                osi_free( db_stats );
            }
        }
    }

    return count;
}

int osi_db_get_record_with_name( OSI_DB *db, void *record,
                                 int record_size, char *name )
{
    int size;
    int result = OSI_ERROR_UNKNOWN;

    unsigned char buffer[MAX_SCAN_RECORD_LENGTH];

    if( ( db == NULL ) || ( record == NULL ) || ( name == NULL ) )
    {
        return result;
    }


    result = osi_db_get_data_with_key( ( db->records ), buffer,
                                       sizeof( buffer ), &size, name );

    /* unpack the data into our passed in record buffer. */

    if( result == OSI_DB_OK )
    {
        /* unpack and unwrap before we return the record. */

        memcpy( record, buffer, size );
    }

    return result;
}

int osi_db_store_record( OSI_DB *db, void *record, int record_size )
{
    int result = OSI_ERROR_UNKNOWN;
    int sequence;

    char key[20];

    if( ( db == NULL ) || ( db->records == NULL ) || ( record == NULL ) )
    {
        return result;
    }

    /* create a unique key. */

    sequence = osi_db_get_record_count( db );
    osi_snprintf( key, sizeof( key ), "%d", sequence );

    result = osi_db_store_data_with_key( ( db->records ),
                                         (void *)record, record_size, key );

    return result;
}


int osi_db_delete_record_with_name( OSI_DB *db, char *name )
{
    int result = OSI_ERROR_UNKNOWN;
    DBT key;

    if( ( db == NULL ) || ( db->records == NULL ) || ( name == NULL ) )
    {
        return result;
    }

    memset( &key, 0, sizeof( key ) );

    key.data = name;
    key.size = strlen( name );

    if( ( db->records )->del( (db->records), NULL, &key, 0 ) == 0 )
    {
        result = OSI_DB_OK;
    }

    return result;
}


int osi_db_get_first_record( OSI_DB *db, void *record, int record_size,
                             int *result_size )
{
    int result = OSI_ERROR_UNKNOWN;

    if( ( db != NULL ) && ( record != NULL ) && ( result_size != NULL ) )
    {
        DBT key;                 
        DBT data;

        *result_size = 0;

        result = db->records->cursor( db->records, NULL, &db->r_cursor, 0 );

        /* we can't do much without a cursor. */

        if( ( result != 0 ) || ( db->r_cursor == NULL ) )
        {
            return OSI_ERROR_DB_INVALID_CURSOR;
        }

        /* initialize the key/data pair. */

        memset( &key, 0, sizeof( key ) );
        memset( &data, 0, sizeof( data ) );

        result = db->r_cursor->c_get( db->r_cursor, &key, &data, DB_FIRST );

        /* if we just hit the end, inform caller. */

        if( result == DB_NOTFOUND )
        {
            return OSI_ERROR_DB_LAST_ITEM;
        }

        if( result != 0 )
        {
            /* we failed to retreive with the cursor,  so */
            /* set the error, close cursor, and bail.     */

            db->r_cursor->c_close( db->r_cursor );
            db->r_cursor = NULL;

            return OSI_ERROR_DB_RETRIEVING;
        }

        /* we have the data, set fields, make sure it will fit first. */

        if( data.size > (unsigned int)record_size )
        {
            return OSI_ERROR_DB_DATA_TOO_LARGE;
        }

        memcpy( record, data.data, data.size );
        *result_size = data.size;

        result = OSI_DB_OK;
    }

    return result;
}

int osi_db_get_next_record( OSI_DB *db, void *record, int record_size,
                            int *result_size )
{
    int result = OSI_ERROR_UNKNOWN;

    if( ( db != NULL ) && ( record != NULL ) && ( result_size != NULL ) )
    {
        DBT key;
        DBT data;

        *result_size = 0;

        /* verify our cursor first. */

        if( db->r_cursor == NULL )
        {
            return OSI_ERROR_DB_INVALID_CURSOR;
        }

        /* initialize the key/data pair. */

        memset( &key, 0, sizeof( key ) );
        memset( &data, 0, sizeof( data ) );

        result = db->r_cursor->c_get( db->r_cursor, &key, &data, DB_NEXT );

        /* if we hit the end, close cursor and inform caller. */

        if( result == DB_NOTFOUND )
        {
            db->r_cursor->c_close( db->r_cursor );
            db->r_cursor = NULL;

            return OSI_ERROR_DB_LAST_ITEM;
        }

        if( result != 0 )
        {
            /* we failed to retreive record, bail here. */

            return OSI_ERROR_DB_RETRIEVING;
        }

        /* we have the data, set fields, make sure it will fit first. */

        if( data.size > (unsigned int)record_size )
        {
            return OSI_ERROR_DB_DATA_TOO_LARGE;
        }

        memcpy( record, data.data, data.size );
        *result_size = data.size;

        result = OSI_DB_OK;
    }

    return result;
}


int osi_scan_db_create( OSI_DB *db )
{
    int result = OSI_ERROR_UNKNOWN;
    int truncate_count = 0;

    if( db != NULL )
    {
        int db_result = 0;
        u_int32_t flags = ( DB_CREATE );

        /* create the db handles for the three databases. */

        db_result |= db_create( &( db->header ), NULL, 0 );
        db_result |= db_create( &( db->errors ), NULL, 0 );
        db_result |= db_create( &( db->records ), NULL, 0 );
        db_result |= db_create( &( db->system ), NULL, 0 );

        if( db_result != 0 )
        {
            /* something bad happend when initializing one of these DBs. */
            /* we don't care which one it was, we bail here.             */

            return OSI_ERROR_DB_INIT_FAILED;
        }

        if( strlen( db->passphrase ) != 0 )
        {
            (db->header)->set_encrypt( (db->header), db->passphrase,
                                       DB_ENCRYPT_AES );

            (db->errors)->set_encrypt( (db->errors), db->passphrase,
                                       DB_ENCRYPT_AES );

            (db->records)->set_encrypt( (db->records), db->passphrase,
                                        DB_ENCRYPT_AES );

            (db->system)->set_encrypt( (db->system), db->passphrase,
                                       DB_ENCRYPT_AES );
        }

        /* now we actually create the file and the four databases that */
        /* file will hold.                                             */

        db_result |= ( db->header )->open( db->header, NULL, db->path,
                       OSI_DB_NAME_HEADER, OSI_DB_TYPE,
                       flags, OSI_DB_PERMISSIONS );

        db_result |= ( db->errors )->open( db->errors, NULL, db->path,
                       OSI_DB_NAME_ERRORS, OSI_DB_TYPE,
                       flags, OSI_DB_PERMISSIONS );

        db_result |= ( db->records )->open( db->records, NULL, db->path,
                       OSI_DB_NAME_RECORDS, OSI_DB_TYPE,
                       flags, OSI_DB_PERMISSIONS );

        db_result |= ( db->system )->open( db->system, NULL, db->path,
                       OSI_DB_NAME_SYSTEM, OSI_DB_TYPE,
                       flags, OSI_DB_PERMISSIONS );

        if( db_result != 0 )
        {
            /* something bad happend when creating one of these databases. */
            /* we don't care which one it was, delete them all and bail.   */

            ( db->header )->remove( db->header, db->path,
                                    OSI_DB_NAME_HEADER, 0 );

            ( db->errors )->remove( db->errors, db->path,
                                    OSI_DB_NAME_ERRORS, 0 );

            ( db->records )->remove( db->records, db->path,
                                     OSI_DB_NAME_RECORDS, 0 );

            ( db->system )->remove( db->system, db->path,
                                    OSI_DB_NAME_SYSTEM, 0 );

            return OSI_ERROR_DB_CREATE_FAILED;
        }

        /* truncate data in case we are overwriting the database. */

        (db->header)->truncate( db->header, NULL, &truncate_count, 0 );
        (db->errors)->truncate( db->errors, NULL, &truncate_count, 0 );
        (db->records)->truncate( db->records, NULL, &truncate_count, 0 );
        (db->system)->truncate( db->system, NULL, &truncate_count, 0 );

        /* we now have three databases, so we populate the header as much as */
        /* we can now, first, we create all of the fields, then overwrite    */
        /* with what we have.                                                */
        /* the int values must be completely zeroed out!                     */

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_COMPLETE,
                                       "\0\0\0\0\0\0\0\0", 8 );

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_RECORD_TYPE,
                                       "\0\0\0\0\0\0\0\0", 8 );

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_SCAN_RESULTS,
                                       "\0",1 );

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_CONFIG_DATA,
                                       "\0", 1 );

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_CONFIG_NAME,
                                       "\0", 1 );

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_CONFIG_ID,
                                       "\0", 1 );

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_HOST,
                                       "\0", 1 );

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_USERNAME,
                                       "\0", 1 );

        osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_NAME,
                                       "\0", 1 );

        if( db->cfg != NULL )
        {
            /* store config's name if there. */

            if( strlen( db->cfg->name ) > 0 )
            {
                char *name = db->cfg->name;
                osi_scan_db_store_header_item( db,
                                               OSI_DB_HEADER_ITEM_CONFIG_NAME,
                                               name, strlen( name ) );
            }

            /* store config's id if there. */

            if( strlen( db->cfg->id ) > 0 )
            {
                char *id = db->cfg->id;
                osi_scan_db_store_header_item( db, OSI_DB_HEADER_ITEM_CONFIG_ID,
                                               id, strlen( id ) );
            }

            /* store all config file data if there. */

            if( db->cfg->data != NULL )
            {
                string_list *lines = db->cfg->data;

                if( lines->size > 0 )
                {
                    int index;
                    int size;
                    char *data;

                    size = ( ( lines->size ) * MAX_LINE_LENGTH );

                    data = osi_malloc( size );
                    memset( data, 0, size );

                    for( index = 0; (unsigned int)index < lines->size; index++ )
                    {
                        char *line = lines->list[index];
                        osi_strlcat( data, line, size );
                    }

                    osi_scan_db_store_header_item( db,
                                              OSI_DB_HEADER_ITEM_CONFIG_DATA,
                                              data, strlen( data ) );
                    osi_free( data );
                }
            }
        }

        /* now close the databases in this file. */

        db->error_id = 0;
        osi_scan_db_close( db );

        result = OSI_DB_OK;
    }

    return result;
}


int osi_scan_db_open( OSI_DB *db, int mode )
{
    int result = OSI_ERROR_UNKNOWN;

    int db_result = 0;
    int flags = 0;

    if( db == NULL )
    {
        return OSI_ERROR_NULL_ARGUMENT;
    }

    /* get db handles for all of the databases in the file. */

    db_result |= db_create( &( db->header ), NULL, 0 );
    db_result |= db_create( &( db->errors ), NULL, 0 );
    db_result |= db_create( &( db->records ), NULL, 0 );
    db_result |= db_create( &( db->system ), NULL, 0 );

    if( db_result != 0 )
    {
        /* something bad happend when initializing one of these DBs. */
        /* we don't care which one it was, we bail here.             */

        return OSI_ERROR_DB_INIT_FAILED;
    }

    if( strlen( db->passphrase ) != 0 )
    {
        (db->header)->set_encrypt( (db->header), db->passphrase,
                                   DB_ENCRYPT_AES );

        (db->errors)->set_encrypt( (db->errors), db->passphrase,
                                   DB_ENCRYPT_AES );

        (db->records)->set_encrypt( (db->records), db->passphrase,
                                    DB_ENCRYPT_AES );

        (db->system)->set_encrypt( (db->system), db->passphrase,
                                    DB_ENCRYPT_AES );
    }

    /* now open the databases based upon the mode. we never create them. */

    switch( mode )
    {
        case OSI_DB_READ:

            flags = ( flags | DB_RDONLY );

            db_result |= ( db->header )->open( db->header, NULL,
                           db->path, OSI_DB_NAME_HEADER, DB_UNKNOWN,
                           flags, OSI_DB_PERMISSIONS );

            db_result |= ( db->errors )->open( db->errors, NULL,
                           db->path, OSI_DB_NAME_ERRORS, DB_UNKNOWN,
                           flags, OSI_DB_PERMISSIONS );

            db_result |= ( db->records )->open( db->records, NULL,
                           db->path, OSI_DB_NAME_RECORDS, DB_UNKNOWN,
                           flags, OSI_DB_PERMISSIONS );

            db_result |= ( db->system )->open( db->system, NULL,
                           db->path, OSI_DB_NAME_SYSTEM, DB_UNKNOWN,
                           flags, OSI_DB_PERMISSIONS );
            break;

        case OSI_DB_WRITE:

            db_result |= ( db->header )->open( db->header, NULL,
                           db->path, OSI_DB_NAME_HEADER, DB_UNKNOWN,
                           flags, OSI_DB_PERMISSIONS );

            db_result |= ( db->errors )->open( db->errors, NULL,
                           db->path, OSI_DB_NAME_ERRORS, DB_UNKNOWN,
                           flags, OSI_DB_PERMISSIONS );

            db_result |= ( db->records )->open( db->records, NULL,
                           db->path, OSI_DB_NAME_RECORDS, DB_UNKNOWN,
                           flags, OSI_DB_PERMISSIONS );

            db_result |= ( db->system )->open( db->system, NULL,
                           db->path, OSI_DB_NAME_SYSTEM, DB_UNKNOWN,
                           flags, OSI_DB_PERMISSIONS );

            break;

        default:
            break;
    }

    /* now check our result, if we were unable to open any of them */
    /* we close them and bail.                                     */

    if( db_result == 0 )
    {
        db->open = TRUE;
        result   = OSI_DB_OK;
    }

    else
    {
        osi_scan_db_close( db );
        result = OSI_ERROR_DB_OPEN_FAILED;
    }

    
    /* WE NEED TO DELETE THE DB OR TRUNCATE IT HERE! */
    /* IF WE ARE OPENING FOR WRITE.                  */

    return result;
}


int osi_scan_db_close( OSI_DB *db )
{
    int result = OSI_ERROR_UNKNOWN;

    if( db != NULL )
    {
        result = OSI_DB_OK;

        if( db->header != NULL )
        {
            (db->header)->close( db->header, 0 );
            db->header = NULL;
        }

        if( db->errors != NULL )
        {
            (db->errors)->close( db->errors, 0 );
            db->errors = NULL;
        }

        if( db->records != NULL )
        {
            (db->records)->close( db->records, 0 );
            db->records = NULL;
        }

        if( db->system != NULL )
        {
            (db->system)->close( db->system, 0 );
            db->system = NULL;
        }

        db->open = FALSE;
    }

    return result;
}


int osi_scan_db_delete( OSI_DB *db )
{
    int result = OSI_ERROR_UNKNOWN;

    if( db != NULL )
    {
        osi_scan_db_close( db );
        result = osi_remove_file( db->path );
    }
    
    return result;
}


long osi_scan_db_get_error_count( OSI_DB *db )
{
    long count = -1;

    if( ( db != NULL ) && ( db->open ) )
    {
        DB_HASH_STAT *db_stats = NULL;

        if( db->errors->stat( db->errors, &db_stats, 0 ) == 0 )
        {
            if( db_stats != NULL )
            {
                count = (long)db_stats->hash_ndata;
                osi_free( db_stats );
            }
        }
    }

    return count;
}

long osi_scan_db_get_record_count( OSI_DB *db )
{
    long count = -1;

    if( ( db != NULL ) && ( db->open ) )
    {
        DB_HASH_STAT *db_stats = NULL;

        if( db->records->stat( db->records, &db_stats, 0 ) == 0 )
        {
            if( db_stats != NULL )
            {
                count = (long)db_stats->hash_ndata;
                osi_free( db_stats );
            }
        }
    }

    return count;
}


int osi_scan_db_store_record( OSI_DB *db, void *record )
{
    int result = OSI_ERROR_UNKNOWN;
    int size = 0;

    char *name = NULL;
    unsigned char buffer[MAX_SCAN_RECORD_LENGTH];

    if( ( db == NULL ) || ( record == NULL ) )
    {
        return result;
    }

    name = get_scan_record_path( (SCAN_RECORD *)record );

    wrap_scan_record( (SCAN_RECORD *)record );
    size = pack_scan_record( (SCAN_RECORD *)record, buffer, sizeof( buffer ) );

    result = osi_db_store_data_with_key( ( db->records ), 
                                         (void *)buffer, size, name );

    return result;
}

int osi_scan_db_get_record_with_name( OSI_DB *db, void *record,
                                      int record_size, char *name )
{
    int size;
    int result = OSI_ERROR_UNKNOWN;

    unsigned char buffer[MAX_SCAN_RECORD_LENGTH];

    if( ( db == NULL ) || ( record == NULL ) || ( name == NULL ) )
    {
        return result;
    }


    result = osi_db_get_data_with_key( ( db->records ), buffer,
                                       sizeof( buffer ), &size, name );

    /* unpack the data into our passed in record buffer. */

    if( result == OSI_DB_OK )
    {
        /* unpack and unwrap before we return the record. */

        unpack_scan_record( (SCAN_RECORD *)record, buffer, size );
        unwrap_scan_record( (SCAN_RECORD *)record );
    }

    return result;
}

int osi_scan_db_store_header_item( OSI_DB *db, char *item, void *data,
                                   int data_size )
{
    int result = OSI_ERROR_UNKNOWN;

    if( data == NULL )
    {
        return OSI_ERROR_NULL_ARGUMENT;
    }

    if( db != NULL )
    {
        
        if( ( item == OSI_DB_HEADER_ITEM_COMPLETE ) ||
            ( item == OSI_DB_HEADER_ITEM_RECORD_TYPE ) )
        {
            osi_uint64 value;

            /* must be 64 bit value, or we don't wrap, caller must be smart. */
            
            if( data_size == sizeof( osi_uint64 ) )
            {
                memcpy( &value, data, sizeof( value ) );
                value = HTONLL( value );
                
                result = osi_db_store_data_with_key( ( db->header ),
                                                     &value,
                                                     sizeof( value ), item );
            }
        }
        
        else if( item == OSI_DB_HEADER_ITEM_SCAN_RESULTS )
        {
            OSI_SCAN_RESULTS scan_results;
            
            /* must have actual scan results structure passed in. */
            /* copy it and wrap it for storage.                   */
            
            if( data_size == sizeof( OSI_SCAN_RESULTS ) )
            {
                memcpy( &scan_results, data, sizeof( scan_results ) );
                wrap_scan_results( ( (OSI_SCAN_RESULTS *)&scan_results ) );
                
                result = osi_db_store_data_with_key( ( db->header ),
                                                     &scan_results,
                                                     sizeof( scan_results ),
                                                     item );
            }
        }
        
        /* if we were aren't wrapping, we just store the data raw. */
        
        else
        {
            result = osi_db_store_data_with_key( ( db->header ), data,
                                                 data_size, item );
        }
    }

    /* force this write to disk. */

    db->header->sync( db->header, 0 );

    return result;
}

int osi_scan_db_get_header_item( OSI_DB *db, char *item, void *data,
                                 int max_data, int *result_size )
{
    int result = OSI_ERROR_UNKNOWN;

    if( data == NULL )
    {
        return OSI_ERROR_NULL_ARGUMENT;
    }
    
    if( db != NULL )
    {
        result = osi_db_get_data_with_key( ( db->header ), data, max_data,
                                           result_size, item );

        if( ( item == OSI_DB_HEADER_ITEM_COMPLETE ) ||
            ( item == OSI_DB_HEADER_ITEM_RECORD_TYPE ) )
        {
            osi_uint64 value;

            /* must be 64 bit value, or we don't unwrap, caller beware. */
            
            if( (*result_size) == sizeof( osi_uint64 ) )
            {
                memcpy( &value, data, sizeof( value ) );
                value = NTOHLL( value );
                memcpy( data, &value, sizeof( value ) );
            }
        }
        
        else if( item == OSI_DB_HEADER_ITEM_SCAN_RESULTS )
        {
            /* if the fetched data is the size of a scan result */
            /* structure, we unwrap it before we return.        */
            
            if( (*result_size) == sizeof( OSI_SCAN_RESULTS ) )
            {
                unwrap_scan_results( ( (OSI_SCAN_RESULTS *)data ) );
            }
        }
    }

    return result;
}

int osi_scan_db_get_first_record( OSI_DB *db, void *record, int record_size )
{
    int result = OSI_ERROR_UNKNOWN;

    if( ( db != NULL ) && ( record != NULL ) )
    {
        DBT key;
        DBT data;

        result = db->records->cursor( db->records, NULL, &db->r_cursor, 0 );

        /* we can't do much without a cursor. */

        if( ( result != 0 ) || ( db->r_cursor == NULL ) )
        {
            return OSI_ERROR_DB_INVALID_CURSOR;
        }

	    /* initialize the key/data pair. */

	    memset( &key, 0, sizeof( key ) );
	    memset( &data, 0, sizeof( data ) );

        result = db->r_cursor->c_get( db->r_cursor, &key, &data, DB_FIRST );

        /* if we just hit the end, inform caller. */

        if( result == DB_NOTFOUND )
        {
            return OSI_ERROR_DB_LAST_ITEM;
        }

        if( result != 0 )
        {
            /* we failed to retreive with the cursor,  so */
            /* set the error, close cursor, and bail.     */

            db->r_cursor->c_close( db->r_cursor );
            db->r_cursor = NULL;

            return OSI_ERROR_DB_RETRIEVING;
        }

        /* we have the data, set fields, make sure it will fit first. */

        if( data.size > (unsigned int)record_size )
        {
            return OSI_ERROR_DB_DATA_TOO_LARGE;
        }

        /* unpack the data into our passed in record buffer. */

        unpack_scan_record( (SCAN_RECORD *)record, data.data, data.size );
        
        /* unwrap this record for the local byte order. */
        
        unwrap_scan_record( (SCAN_RECORD *)record );

        /* it will fit, copy it, set the size and return success. */

        result = OSI_DB_OK;
    }

    return result;
}

int osi_scan_db_get_next_record( OSI_DB *db, void *record, int record_size )
{
    int result = OSI_ERROR_UNKNOWN;

    if( ( db != NULL ) && ( record != NULL ) )
    {
        DBT key;
        DBT data;

        /* verify our cursor first. */

        if( db->r_cursor == NULL )
        {
            return OSI_ERROR_DB_INVALID_CURSOR;
        }

	    /* initialize the key/data pair. */

	    memset( &key, 0, sizeof( key ) );
	    memset( &data, 0, sizeof( data ) );

        result = db->r_cursor->c_get( db->r_cursor, &key, &data, DB_NEXT );

        /* if we hit the end, close cursor and inform caller. */

        if( result == DB_NOTFOUND )
        {
            db->r_cursor->c_close( db->r_cursor );
            db->r_cursor = NULL;

            return OSI_ERROR_DB_LAST_ITEM;
        }

        if( result != 0 )
        {
            /* we failed to retreive record, bail here. */

            return OSI_ERROR_DB_RETRIEVING;
        }

        /* we have the data, set fields, make sure it will fit first. */

        if( data.size > (unsigned int)record_size )
        {
            return OSI_ERROR_DB_DATA_TOO_LARGE;
        }

        /* unpack the data into our passed in record buffer. */

        unpack_scan_record( (SCAN_RECORD *)record, data.data, data.size );

        /* unwrap this record for the local byte order. */
        
        unwrap_scan_record( (SCAN_RECORD *)record );

        /* it will fit, copy it, set the size and return success. */

        result = OSI_DB_OK;
    }

    return result;
}


int osi_scan_db_store_error( OSI_DB *db, OSI_ERROR *error )
{
    int result = OSI_ERROR_UNKNOWN;
    char buffer[sizeof(OSI_ERROR)];

    if( ( db != NULL ) && ( error != NULL ) )
    {
        /* get error id. */

        char id[32] = "";
        osi_snprintf( id, sizeof( id ), "%d", db->error_id );

        /* push current time into the error message if it isn't there. */

        if( error->time == 0 )
        {
            error->time = (osi_uint64)osi_get_time();
        }
        
        /* wrap this error for storage, preserve byte order, then store it. */
        
        wrap_error( error );
        pack_error( error, buffer, sizeof(buffer) );

        result = osi_db_store_data_with_key( ( db->errors ), error,
                                             sizeof( OSI_ERROR ), id );

        /* increment error id. */

        if( result == OSI_DB_OK )
        {
            db->error_id += 1;
        }
    }

    return result;
}


int osi_scan_db_get_first_error( OSI_DB *db, void *error, int max_size,
                                 int *result_size )
{
    int result = OSI_ERROR_UNKNOWN;

    if( ( db != NULL ) && ( error != NULL ) && ( result_size != NULL ) )
    {
        DBT key;
        DBT data;

        result = db->errors->cursor( db->errors, NULL, &db->e_cursor, 0 );

        /* we can't do much without a cursor. */

        if( ( result != 0 ) || ( db->e_cursor == NULL ) )
        {
            result = OSI_ERROR_DB_INVALID_CURSOR;
            return result;
        }

        /* initialize the key/data pair. */

        memset( &key, 0, sizeof( key ) );
        memset( &data, 0, sizeof( data ) );

        result = db->e_cursor->c_get( db->e_cursor, &key, &data, DB_NEXT );

        /* if we just hit the end, inform caller. */

        if( result == DB_NOTFOUND )
        {
            return OSI_ERROR_DB_LAST_ITEM;
        }

        if( result != 0 )
        {
            /* we failed to retreive with the cursor,  so */
            /* set the error, close cursor, and bail.     */

            db->e_cursor->c_close( db->e_cursor );
            db->e_cursor = NULL;

            return OSI_ERROR_DB_RETRIEVING;
        }

        (*result_size) = data.size;

        /* we have the data, set fields, make sure it will fit first. */

        if( data.size > (unsigned int)max_size )
        {
            return OSI_ERROR_DB_DATA_TOO_LARGE;
        }
        
        /* unwrap this error for the local byte order. */
        
        unwrap_error( (OSI_ERROR *)data.data );
        unpack_error( error, data.data, data.size );

        result = OSI_DB_OK;
    }

    return result;
}

int osi_scan_db_get_next_error( OSI_DB *db, void *error, int max_size,
                                int *result_size )
{
    int result = OSI_ERROR_UNKNOWN;

    if( ( db != NULL ) && ( error != NULL ) && ( result_size != NULL ) )
    {
        DBT key;
        DBT data;

        /* verify our cursor first. */

        if( db->e_cursor == NULL )
        {
            return OSI_ERROR_DB_INVALID_CURSOR;
        }

        /* initialize the key/data pair. */

        memset( &key, 0, sizeof( key ) );
        memset( &data, 0, sizeof( data ) );

        result = db->e_cursor->c_get( db->e_cursor, &key, &data, DB_NEXT );

        /* if we hit the end, close cursor and inform caller. */

        if( result == DB_NOTFOUND )
        {
            db->e_cursor->c_close( db->e_cursor );
            db->e_cursor = NULL;

            return OSI_ERROR_DB_LAST_ITEM;
        }

        if( result != 0 )
        {
            /* we failed to retreive record, bail here. */

            (*result_size) = 0;
            return OSI_ERROR_DB_RETRIEVING;
        }

        (*result_size) = data.size;

        /* we have the data, set fields, make sure it will fit first. */

        if( data.size > (unsigned int)max_size )
        {
            return OSI_ERROR_DB_DATA_TOO_LARGE;
        }
        
        /* unwrap this error for the local byte order. */
        
        unwrap_error( (OSI_ERROR *)data.data );
        unpack_error( error, data.data, data.size );

        result = OSI_DB_OK;
    }

    return result;
}


int osi_db_store_data_with_key( DB *db, void *data, int data_size, char *key )
{
    int result = OSI_ERROR_UNKNOWN;

    if( db != NULL )
    {
        if( ( key != NULL ) && ( data != NULL ) )
        {
            DBT db_key;
            DBT db_data;

            memset( &db_key, 0, sizeof( db_key ) );
            memset( &db_data, 0, sizeof( db_data ) );

            db_key.data  = key;
            db_key.size  = strlen( key );

            db_data.data = data;
            db_data.size = data_size;

            result = db->put( db, NULL, &db_key, &db_data, 0 );

            if( result == 0 )
            {
                result = OSI_DB_OK;
            }

            else
            {
                if( result == DB_KEYEXIST )
                {
                    result = OSI_ERROR_DB_ITEM_EXISTS;
                }

                else
                {
                    result = OSI_ERROR_DB_STORING;
                }
            }
        }

        else
        {
            result = OSI_ERROR_NULL_ARGUMENT;
        }
    }

    else
    {
        result = OSI_ERROR_DB_NOT_OPENED;
    }

    return result;
}

int osi_db_get_data_with_key( DB *db, void *data, int max_data,
                              int *result_size, char *key )
{
    int result = OSI_ERROR_UNKNOWN;

    DBT db_key;
    DBT db_data;

    if( db == NULL )
    {
        return OSI_ERROR_DB_NOT_OPENED;
    }
 
    if( ( key == NULL ) || ( data == NULL ) )
    {
        return OSI_ERROR_NULL_ARGUMENT;
    }

    memset( &db_key, 0, sizeof( db_key ) );
    memset( &db_data, 0, sizeof( db_data ) );

    db_key.data  = key;
    db_key.size  = strlen( key );

    result = db->get( db, NULL, &db_key, &db_data, 0 );

    if( result == 0 )
    {
        /* copy data into buffer, as much as we can, and set size. */

        if( db_data.size <= (unsigned int)max_data )
        {
            memcpy( data, db_data.data, db_data.size );
            (*result_size) = db_data.size;

            result = OSI_DB_OK;
        }

        else
        {
            result = OSI_ERROR_DB_DATA_TOO_LARGE;
        }
    }

    else if( result == DB_NOTFOUND )
    {
        result = OSI_ERROR_DB_ITEM_NOT_FOUND;
    }

    else
    {
        result = OSI_ERROR_DB_RETRIEVING;
    }

    return result;
}


int osi_scan_db_get_record_type( OSI_DB *db )
{
    int type = -1;

    if( ( db != NULL ) && ( db->open ) )
    {
        int result;
        int size;
        osi_uint64 record_type;

        result = osi_scan_db_get_header_item( db,
                                         OSI_DB_HEADER_ITEM_RECORD_TYPE,
                                         &record_type, sizeof( record_type ),
                                         &size );

        if( result == OSI_DB_OK )
        {
            type = (int)record_type;
        }
    }

    return type;
}


void osi_wrap_scan_db_header( OSI_DB_HEADER *header )
{
    if( header != NULL )
    {
        header->complete    = HTONLL( header->complete );
        header->record_type = HTONLL( header->record_type );
        
        wrap_scan_results( &( header->scan_results ) );
    }
}

void osi_unwrap_scan_db_header( OSI_DB_HEADER *header )
{
    if( header != NULL )
    {
        header->complete    = NTOHLL( header->complete );
        header->record_type = NTOHLL( header->record_type );
        
        unwrap_scan_results( &( header->scan_results ) );
    }
} 



