// 
// 
// Copyright (C) 2004 SIPfoundry Inc.
// Licensed by SIPfoundry under the LGPL license.
// 
// Copyright (C) 2004 Pingtel Corp.
// Licensed to SIPfoundry under a Contributor Agreement.
// 
// $$
//////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES
#include <assert.h>
#include <limits.h>

// APPLICATION INCLUDES
#include "utl/UtlInt.h"
#include "os/OsLock.h"
#include "os/OsFS.h"
#include "os/OsSysLog.h"
#include "os/OsTask.h"
#include "xmlparser/tinyxml.h"
#include "sipdb/ResultSet.h"
#include "sipdb/SIPDBManager.h"
#include "sipdb/AliasDB.h"
#include "sipdb/CallerAliasDB.h"
#include "sipdb/CredentialDB.h"
#include "sipdb/ExtensionDB.h"
#include "sipdb/HuntgroupDB.h"
#include "sipdb/PermissionDB.h"
#include "sipdb/RegistrationDB.h"
#include "sipdb/SubscriptionDB.h"

// DEFINES

REGISTER( TableInfo );

// MACROS
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES

// Declared in fastdb/sync.h, but including that header file is problematic
extern char const* keyFileDir;

// CONSTANTS

// environment variable name for static data 

static const char* DefaultVarPath = SIPX_TMPDIR;
static const char* DefaultCfgPath = SIPX_DBDIR;

// STRUCTS
// TYPEDEFS
// FORWARD DECLARATIONS

// Static Initializers
SIPDBManager* SIPDBManager::spInstance = NULL;
dbDatabase*   SIPDBManager::spFastDB = NULL;
OsMutex       SIPDBManager::sLockMutex (OsMutex::Q_FIFO);

/* Helper method to return the process id */
int 
getPid()
{
    return static_cast< int >
#ifdef WIN32
    (GetCurrentProcessId());
#else
    (getpid());
#endif
}

/* ============================ CREATORS ================================== */

SIPDBManager::SIPDBManager () 
    : m_absWorkingDirectory("")
    , m_absConfigDirectory("")
{
    // Get the working and config file paths
    m_absWorkingDirectory = getVarPath() ;
    m_absConfigDirectory = getCfgPath() ;

    // Override the default fastdb tmp dir if SIPX_DB_VAR_PATH was set
    setFastdbTempDir();
}

SIPDBManager::~SIPDBManager ()
{
    // Critical Section here
    OsLock lock( sLockMutex );

    int pid = getPid();

    // it is possible that the database was never opened
    // as it is opened only the first time a request for
    // one of its managed databases exists
    if ( spFastDB != NULL )
    {
        // Thread Local Storage
        spFastDB->attach();

        // deregister all database's associated with the PID
        dbCursor< TableInfo > cursor( dbCursorForUpdate );

        dbQuery query;
        query="pid=",pid;

        if ( cursor.select( query ) > 0 )
        {
            cursor.removeAllSelected();
        }
        spFastDB->detach(0);

        // close() is MultiProcesss aware - always call it
        spFastDB->close();
        // put this here
        delete spFastDB;
        spFastDB = NULL;
    }

    // reset the static instance pointer to NULL
    spInstance = NULL;
}

OsStatus
SIPDBManager::getProcessCount ( int& rProcessCount ) const
{
    // Critical Section here
    OsLock lock( sLockMutex );

    // Ensure that the database is opened first
    if ( spFastDB == NULL )
    {
        // Lazy Open the database without attaching a particular
        // process to the tableInfo table
        spFastDB = openDatabase ();
    }

    OsStatus result = OS_FAILED;

    // it is possible that the database was never opened
    // as it is opened only the first time a request for
    // one of its managed databases exists
    if ( spFastDB != NULL )
    {
        int pid = getPid();

        // Thread Local Storage
        spFastDB->attach();

        // Initialize the process count to zero
        rProcessCount = 0;

        // Now count the number or processes
        dbCursor< TableInfo > cursor;
        dbQuery query;
        query="pid=",pid,"order by pid";

        // see if the pid is identical for all rows
        if ( cursor.select() > 0 )
        {
            // Get the PID of the first process
            int lastpid = -1;
            do {
                // > 1 pid => not last process
                if ( lastpid != cursor->pid ) {
                    lastpid = cursor->pid;
                    rProcessCount += 1;
                }
            } while ( cursor.next() );
        }
        // Commit rows to memory - multiprocess workaround
        spFastDB->detach(0);

        result = OS_SUCCESS;
    } else 
    {
        rProcessCount = 0;
    }

    return result;
}

OsStatus
SIPDBManager::pingDatabase ( 
    const int& rTransactionLockSecs, 
    const UtlBoolean& rTestWriteLock ) const
{
    OsStatus result = OS_FAILED;

    // Ensure that the database is opened first
    if ( spFastDB == NULL )
    {
        // Lazy Open the database without attaching a particular
        // process to the tableInfo table
        spFastDB = openDatabase ();
    }

    // it is possible that the database was never opened
    // as it is opened only the first time a request for
    // one of its managed databases exists
    if ( spFastDB != NULL )
    {
        int pid = getPid();
        spFastDB->attach();
        // create a readonly cursor
        dbCursor< TableInfo > cursor;
        dbCursor< TableInfo > cursorWithWriteLock( dbCursorForUpdate );

        dbQuery query;
        query="pid=",pid;

        // If we want to test the IMDB for read access the cursor should be used
        // otherwise the cursorWithWriteLock requests access to a new tx with exclusive
        // write access (waiting in effect for the other readers to cease beforehand
        if (rTestWriteLock == TRUE)
        {
            cursorWithWriteLock.select( query );
        } else
        {
            cursor.select( query );
        }
        // in order to test the Ping utility we need a way
        // of holding onto the transaction lock for a configurable
        // number of seconds
        if ( rTransactionLockSecs > 0 )
        {
            OsTask::delay( rTransactionLockSecs * 1000);
        }
        spFastDB->detach(0);

        // ping succeeded
        result = OS_SUCCESS;

    }
    return result;
}

void
SIPDBManager::getAllTableProcesses ( ResultSet& rResultSet ) const
{
    // Critical Section here
    OsLock lock( sLockMutex );

    // Clear the out any previous records
    rResultSet.destroyAll();

    // Ensure that the database is opened first
    if ( spFastDB == NULL )
    {
        // Lazy Open the database without attaching a particular
        // process to the tableInfo table
        spFastDB = openDatabase ();
    }

    if ( spFastDB != NULL )
    {
        // must do this first to ensure process/tls integrity
        spFastDB->attach();

        dbCursor< TableInfo > cursor;
        if ( cursor.select() > 0 )
        {
            do {
                UtlHashMap record;
                UtlString* tablenameValue = 
                    new UtlString ( cursor->tablename );
                UtlInt* pidValue = 
                    new UtlInt ( cursor->pid );
                UtlInt* loadchecksumValue = 
                    new UtlInt ( cursor->loadchecksum );

                UtlString* tablenameKey = new UtlString( "tablename" );
                UtlString* pidKey = new UtlString( "pid" );
                UtlString* loadchecksumKey = new UtlString( "loadchecksum" );

                record.insertKeyAndValue ( tablenameKey, tablenameValue );
                record.insertKeyAndValue ( pidKey, pidValue );
                record.insertKeyAndValue ( loadchecksumKey, loadchecksumValue );

                rResultSet.addValue(record);
            } while (cursor.next());
        }
        // commit rows and also ensure process/tls integrity
        spFastDB->detach(0);
    }
}

int
SIPDBManager::getNumDatabaseProcesses ( const UtlString& tablename ) const
{
    // Critical Section here
    OsLock lock( sLockMutex );

    int numUsers = 0;

    // Ensure that the database is opened first
    if ( spFastDB == NULL )
    {
        // Lazy Open the database without attaching a particular
        // process to the tableInfo table
        spFastDB = openDatabase ();
    }

    if ( spFastDB != NULL )
    {
        // Thread Local Storage
        spFastDB->attach();

        // Now count the number or processes
        dbCursor< TableInfo > cursor;
        dbQuery query;
        query="tablename=",tablename;

        // count the processes attached to this DB
        numUsers = cursor.select(query);

        // Commit rows to memory - multiprocess workaround
        spFastDB->detach(0);
    }

    return numUsers;
}

dbDatabase*
SIPDBManager::openDatabase () const
{
    // Critical Section here
    OsLock lock( sLockMutex );

    int pid = getPid();

    dbDatabase* database = 
        new dbDatabase( dbDatabase::dbAllAccess 
                        /*, 8*1024*1024 */ );

    // open the fastdb proprietary persistent file
    UtlString imdbFileName = 
        getWorkingDirectory() + 
        OsPath::separator + "imdb.odb";

    // test for successful return code
    if ( database->open( "imdb", imdbFileName /*, 60000 */) )
    {
        // Begin TX
        database->attach();

        // This is the first process to use the fastDB
        // fastDB restores its old contents from "imdb.odb" so
        // we must assume that everything associated with this pid
        // should be deleted
        dbCursor< TableInfo > cursor ( dbCursorForUpdate );
        dbQuery query;
        query="pid=",pid;
        if ( cursor.select( query ) > 0 )
        {
            cursor.removeAllSelected();
        }
        // End TX
        database->detach(0);
    } else // failed to open IMDB delete datbase & return NULL
    {
        // call the destructor on the IMDB
        delete database;
        database = NULL;
    }
    return database;
}

dbDatabase*
SIPDBManager::getDatabase ( const UtlString& tablename ) const
{
    // Critical Section here
    OsLock lock( sLockMutex );

    int pid = getPid();

    // Only construct one dbDatabase object per process
    if ( spFastDB == NULL )
    {
        // Absolute Size required in diskless mode, assume 8Megs sufficient
        spFastDB = new dbDatabase( 
            dbDatabase::dbAllAccess /*, 8*1024*1024 */ );
    } 
    
    // test to see if the IMDB was already opened
    if ( !spFastDB->isOpen() )
    {
        // the file name used for persistent storage 
        // (using fastDB proprietary format)
        UtlString imdbFileName = getWorkingDirectory() + OsPath::separator + "imdb.odb";

        // test for successful return code
        if ( spFastDB->open( "imdb", imdbFileName /*, 60000 */) )
        {
            // Begin TX
            spFastDB->attach();

            // This is the first process to use the fastDB
            // fastDB restores its old contents from "imdb.odb" so
            // we must assume that the table is dirty from previous
            // calls to  close (which persists the memory to disk)
            dbCursor< TableInfo > cursor ( dbCursorForUpdate );
            dbQuery query;
            query="tablename=",tablename,"and pid=",pid;
            if ( cursor.select( query ) > 0 )
            {
                cursor.removeAllSelected();
            }

            // End TX
            spFastDB->detach(0);
        } else // failed to open IMDB delete datbase & return NULL
        {
            // call the destructor on the IMDB
            delete spFastDB;

            // reset the global pointer to NULL so that we 
            // always come through this code path
            spFastDB = NULL;
        }
    }

    // potentially the spFastDB can be NULL above if the open fails
    if (spFastDB != NULL) 
    {
        // fastDB must be both non null and also opened successfully at this stage

        // If the tablename/pid do not match an existing row, insert a new 
        // pid/tablename to track process usage of the tables
        // Begin Tx
        spFastDB->attach();

        // ensure that the table is registered with the process id.
        dbCursor< TableInfo > cursor ( dbCursorForUpdate );
        dbQuery query;
        query="tablename=",tablename,"and pid=",pid;
        if ( cursor.select( query ) > 0 )
        {
            do {
                // this code should not be exercised
                cursor->changed = TRUE;
                cursor->loadchecksum = 0;
                cursor.update();
            } while ( cursor.next() );
        } else 
        {
            TableInfo tableInfo;
            tableInfo.tablename = tablename;
            tableInfo.pid = pid;
            tableInfo.loadchecksum = 0;
            tableInfo.changed = TRUE;
            // Implicit Attach here
            insert( tableInfo );
        }
        // End Tx
        spFastDB->detach(0);

    }
    OsSysLog::flush();
    return spFastDB;
}

void
SIPDBManager::removeDatabase ( const UtlString& tablename ) const
{
    // remove all rows from the imdb with this tablename
    OsLock lock( sLockMutex );

    // one dbDatabase construction call allowed per process
    if ( spFastDB != NULL )
    {
        int pid = getPid();

        spFastDB->attach();
        dbCursor< TableInfo > cursor (dbCursorForUpdate);

        dbQuery query;
        query="tablename=",tablename,"and pid=",pid;
        // cannot be 0 rows at this stage
        if (cursor.select(query) > 0)
        {
            cursor.removeAllSelected();
        }
        // Commit rows to memory - multiprocess workaround
        spFastDB->detach(0);
    }
}

void 
SIPDBManager::setDatabaseChangedFlag (
    const UtlString& tablename, 
    bool changed ) const
{
    // Critical Section here
    OsLock lock( sLockMutex );

    if ( spFastDB != NULL ) 
    {
        spFastDB->attach();
        dbCursor< TableInfo > cursor ( dbCursorForUpdate );
        dbQuery query;
        query="tablename=",tablename;
        if ( cursor.select( query ) > 0 )
        {
            do {
                // reset the checksum & flag for all processes
                cursor->loadchecksum = 0;
                cursor->changed = changed;
                cursor.update();
            } while ( cursor.next() );
        } else
        {
            OsSysLog::add(FAC_DB, PRI_ERR, 
                "SIPDBManager::setDatabaseChangedFlag - "
                "ERROR database %s not in TableInfo table", tablename.data());
        }
        // Commit rows to memory - multiprocess workaround
        spFastDB->detach(0);
    }

}

OsStatus 
SIPDBManager::getDatabaseInfo( UtlString& rDatabaseInfo ) const
{

    // Ensure that the database is opened first
    if ( spFastDB == NULL )
    {
        // Lazy Open the database without attaching a particular
        // process to the tableInfo table
        spFastDB = openDatabase ();
    }

    OsStatus result = OS_FAILED;

    // it is possible that the database was never opened
    // as it is opened only the first time a request for
    // one of its managed databases exists
    if ( spFastDB != NULL )
    {
        spFastDB->attach();

        long allocatedSize = spFastDB->getAllocatedSize();
        long databaseSize  = spFastDB->getDatabaseSize();
        int numReaders = spFastDB->getNumberOfReaders();
        int numWriters = spFastDB->getNumberOfWriters();
        int numBlockedReaders = spFastDB->getNumberOfBlockedReaders();
        int numBlockedWriters = spFastDB->getNumberOfBlockedWriters();
        int numUsers = spFastDB->getNumberOfUsers();

        char temp[300];

        const char* outputStringFormat = 
            "Database Meta Info\n==================\nAllocated Size:\t\t%d\nDatabase Size:\t\t%d\nReaders:\t\t%d\nWriters:\t\t%d\nBlocked Readers:\t%d\nBlocked Writers:\t%d\nUsers:\t\t\t%d\n";

        sprintf (
            temp, outputStringFormat, 
            allocatedSize, 
            databaseSize, 
            numReaders, 
            numWriters, 
            numBlockedReaders, 
            numBlockedWriters, 
            numUsers );
        rDatabaseInfo = temp;

        spFastDB->detach(0);
        result = OS_SUCCESS;
    }
    return result;
}

bool 
SIPDBManager::getDatabaseChangedFlag (
    const UtlString& tablename ) const
{
    // Critical Section here
    OsLock lock( sLockMutex );

    // Ensure that the database is opened first
    if ( spFastDB == NULL )
    {
        // Lazy Open the database
        spFastDB = openDatabase ();
    }

    bool result = TRUE;

    if ( spFastDB != NULL ) 
    {
        spFastDB->attach();
        dbCursor< TableInfo > cursor ( dbCursorForUpdate );
        dbQuery query;
        query="tablename=",tablename;
        if ( cursor.select( query ) > 0 )
        {
            result = cursor->changed;
        }
        // Commit rows to memory - multiprocess workaround
        spFastDB->detach(0);
    }

    return result;
}

void 
SIPDBManager::updateDatabaseInfo ( 
    const UtlString& tablename, 
    const int& checksum ) const
{
    // Critical Section here
    OsLock lock( sLockMutex );

    int pid = getPid();

    if (spFastDB != NULL)
    {
        spFastDB->attach();
        dbCursor< TableInfo > cursor ( dbCursorForUpdate );

        dbQuery query;
        query="tablename=",tablename,"and pid=",pid;
        // cannot be 0 rows at this stage
        if ( cursor.select( query ) > 0 )
        {
            do {
                if ( cursor->loadchecksum != checksum )
                {
                    cursor->loadchecksum = checksum; 
                    cursor->changed = TRUE;
                    cursor.update();
                } else // checksum matches
                {
                    if ( cursor->changed == TRUE )
                    {
                        cursor->changed = FALSE;
                        cursor.update();
                    }
                }
            } while ( cursor.next() );

        } else // this block should never be run
        {
            // Insert or Update the process related info
            TableInfo tableInfo;
            tableInfo.tablename = tablename;
            tableInfo.pid = pid;
            tableInfo.loadchecksum = 0;
            tableInfo.changed = TRUE;
            // Implicit Attach here
            insert( tableInfo );
        }
        // Commit rows to memory - multiprocess workaround
        spFastDB->detach(0);
    }

}

const UtlString& 
SIPDBManager::getWorkingDirectory () const
{
   return m_absWorkingDirectory;
}

const UtlString& 
SIPDBManager::getConfigDirectory () const
{
   return m_absConfigDirectory;
}

SIPDBManager*
SIPDBManager::getInstance()
{
    // Critical Section here
    OsLock lock( sLockMutex );

    // See if this is the first time through for this process
    // Note that this being null => pgDatabase is also null
    if ( spInstance == NULL )
    {   // Create the singleton class for clients to use
        spInstance = new SIPDBManager();
    }

    return spInstance;
}

void
SIPDBManager::getFieldValue ( 
    const unsigned char* base,
    const dbFieldDescriptor* fd, 
    UtlString& textValue)
{
    char tempString[100];

    // The only data types that are currently known to work are "char*", "int4",
    // and "db_int8".  All other data types are likely to be broken, so comment
    // out that code for now.  If the caller tries to use a broken data type,
    // log an error and assert(false) rather than silently returning the wrong answer.
    // In the cases that work, we're using "fd->appOffs" rather than "fd->dbsOffs"
    // so perhaps we should make this change everywhere.  But that may be wrong,
    // and would be pointless without a unit test for this method, which we should
    // implement regardless.

    switch (fd->type) {
/*
    case dbField::tpBool:
        textValue = (char*) (*(bool*)(base + fd->dbsOffs))? "true" : "false";
        break;

    case dbField::tpInt1:
        sprintf ( tempString, "%d", *(int1*)(base + fd->dbsOffs) );
        textValue = tempString;
        break;

    case dbField::tpInt2:
        sprintf ( tempString, "%d", *(int2*)(base + fd->dbsOffs) );
        textValue = tempString;
        break;
*/
    case dbField::tpInt4:
        sprintf ( tempString, "%d", *(int4*)(base + fd->appOffs) );
        textValue = tempString;
        break;

    case dbField::tpInt8:
       sprintf ( tempString,"%0#16llx", *(int8*)(base + fd->appOffs) );
        textValue = tempString;
        break;
/*
    case dbField::tpReal4:
        sprintf ( tempString,  "%g", *(real4*)(base + fd->dbsOffs) );
        textValue = tempString;
        break;

    case dbField::tpReal8:
        sprintf ( tempString,  "%g", *(real8*)(base + fd->dbsOffs) );
        textValue = tempString;
        break;

    case dbField::tpRawBinary:
        if ( fd->dbsSize < 100 )
        {
            memcpy(tempString, base + fd->dbsOffs, fd->dbsSize);
        }
        break;
*/
    case dbField::tpString:
        textValue = *(char**)(base + fd->appOffs);
        break;

    default:
        OsSysLog::add(FAC_DB, PRI_ERR, 
                      "SIPDBManager::getFieldValue - "
                      "ERROR unsupported data type: %d", fd->type);
        assert(false);
        break;
    }
}

OsStatus
SIPDBManager::getAttributeValue ( 
    const TiXmlNode& node,
    const UtlString& key,
    UtlString& value )
{
    OsStatus result = OS_SUCCESS;
    TiXmlNode* configNode = (TiXmlNode*)node.FirstChild( key );

    if ( (configNode != NULL) && (configNode->Type() == TiXmlNode::ELEMENT) )
    {
        // convert the node to an element 
        TiXmlElement* elem = configNode->ToElement();
        if ( elem != NULL )
        {
            TiXmlNode* childNode = elem->FirstChild();
            if( childNode && childNode->Type() == TiXmlNode::TEXT )
            {
                TiXmlText* elementValue = childNode->ToText();
                if (elementValue)
                {
                    value = elementValue->Value();
                } else
                {
                    result = OS_FAILED;
                }
            } else
            {
                result = OS_FAILED;
            }
        } else
        {
            result = OS_FAILED;
        }
    } else
    {
        result = OS_FAILED;
    }
    return result;
}

OsStatus
SIPDBManager::preloadAllDatabase() const
{
    // Preload the following tables to ensure that
    // their reference count does not go to 0
    // causing an expensive load by another process
    
    // If called first thing during startup, this code 
    // will force the xml files to be loaded into imdb.

    CredentialDB*   pCredentialDB   = CredentialDB::getInstance();
    SubscriptionDB* pSubscriptionDB = SubscriptionDB::getInstance();
    RegistrationDB* pRegistrationDB = RegistrationDB::getInstance();
    HuntgroupDB*    pHuntgroupDB    = HuntgroupDB::getInstance();
    PermissionDB*   pPermissionDB   = PermissionDB::getInstance();
    ExtensionDB*    pExtensionDB    = ExtensionDB::getInstance();
    AliasDB*        pAliasDB        = AliasDB::getInstance();
   
    OsStatus res = OS_FAILED;

    if (pCredentialDB &&
    pSubscriptionDB &&
    pRegistrationDB &&
    pHuntgroupDB &&
    pPermissionDB &&
    pExtensionDB &&
    pAliasDB) res = OS_SUCCESS;

    return res;
}
 
OsStatus
SIPDBManager::releaseAllDatabase() const
{
    CredentialDB::releaseInstance();
    SubscriptionDB::releaseInstance();
    RegistrationDB::releaseInstance();
    HuntgroupDB::releaseInstance();
    PermissionDB::releaseInstance();
    ExtensionDB::releaseInstance();
    AliasDB::releaseInstance();

    return OS_SUCCESS;
}

/** Gets the absolute or relative path prefix for data files */
OsPath
SIPDBManager::getVarPath() 
{
   OsPath path;
   UtlBoolean found = FALSE;
   UtlString candidate;

   // If the environment variable configured via SIPX_DB_VAR_PATH has a value,
   // try it.
   char* pPath = getenv(SIPX_DB_VAR_PATH);
   if (pPath != NULL && pPath[0] != '\0')
   {
      OsSysLog::add(FAC_SIP, PRI_DEBUG, "SIPDBManager::getVarPath env variable %s set to %s"
                    ,SIPX_DB_VAR_PATH, pPath);
      candidate = pPath;

      // If the last character is a separator, strip it.
      if (candidate(candidate.length() -1) == OsPath::separator)
      {
         candidate = candidate(0, candidate.length()-1);
      }
      if (OsFileSystem::exists(candidate))
      {
         path = candidate;
         found = TRUE;
      }
      else
      {
         OsSysLog::add(FAC_SIP, PRI_ERR, "SIPDBManager::getVarPath env variable %s has value %s but is not valid"
                       ,SIPX_DB_VAR_PATH, pPath);
      }
   }

   // If that doesn't work, try the DefaultVarPath.
   if (!found)
   {
      OsSysLog::add(FAC_SIP, PRI_DEBUG, "SIPDBManager::getVarPath trying default %s"
                    ,DefaultVarPath);
      candidate = DefaultVarPath;
       
      if (OsFileSystem::exists(candidate))
      {
         path = candidate;
         found = TRUE;
      }
      else
      {
         OsSysLog::add(FAC_SIP, PRI_ERR, "SIPDBManager::getVarPath default %s is not valid"
                    ,DefaultVarPath);
      }
   }

   // Otherwise, use the working directory.
   if (!found)
   {
     OsFileSystem::getWorkingDirectory(path);
   }

   OsPath nativePath;
   path.getNativePath(nativePath);

   OsSysLog::add(FAC_SIP, PRI_DEBUG, "SIPDBManager::getVarPath returning %s"
                 ,nativePath.data());

   return nativePath;
}


/** Gets the absolute or relative path prefix for persistent config files */
OsPath
SIPDBManager::getCfgPath() 
{
   OsPath path;
   UtlBoolean found = FALSE;
   UtlString candidate;

   // If the environment variable configured via SIPX_DB_VAR_PATH has a value,
   // try it.
   char* pPath = getenv (SIPX_DB_CFG_PATH);
   if (pPath != NULL && pPath[0] != '\0')
   {
      OsSysLog::add(FAC_SIP, PRI_DEBUG, "SIPDBManager::getCfgPath env variable '%s' set to '%s'"
                    ,SIPX_DB_CFG_PATH, pPath);
      candidate = pPath;

      // If the last character is a separator, strip it.
      if (candidate(candidate.length() -1) == OsPath::separator)
      {
         candidate = candidate(0, candidate.length()-1);
      }
      
      if (OsFileSystem::exists(candidate))
      {
         path = candidate;
         found = TRUE;
      }
      else
      {
         OsSysLog::add(FAC_SIP, PRI_ERR, "SIPDBManager::getCfgPath env variable %s has value %s but is not valid"
                       ,SIPX_DB_CFG_PATH, pPath);
      }
   }

   // If that doesn't work, try the DefaultCfgPath.
   if (!found)
   {
      OsSysLog::add(FAC_SIP, PRI_DEBUG, "SIPDBManager::getCfgPath trying default %s"
                    ,DefaultCfgPath);
      candidate = DefaultCfgPath;

      if (OsFileSystem::exists(candidate))
      {
         path = candidate;
         found = TRUE;
      }
      else
      {
         OsSysLog::add(FAC_SIP, PRI_ERR, "SIPDBManager::getCfgPath default '%s' is not valid"
                    ,DefaultCfgPath);
      }
   }

   // Otherwise, use the working directory.
   if (!found)
   {
     OsFileSystem::getWorkingDirectory(path);
   }

   OsPath nativePath;
   path.getNativePath(nativePath);

   OsSysLog::add(FAC_SIP, PRI_DEBUG, "SIPDBManager::getCfgPath returning %s"
                 ,nativePath.data());

   return nativePath;
}


/// Override the default fastdb tmp dir if the env var SIPX_DB_VAR_PATH is set
void SIPDBManager::setFastdbTempDir()
{
   // This method must only be called once
   assert(m_FastDbTmpDirPath.isNull());

   char* pPath = getenv(SIPX_DB_VAR_PATH);
   if (pPath != NULL && pPath[0] != '\0')
   {
      m_FastDbTmpDirPath = pPath;
         
      // The path must end in a separator
      if (m_FastDbTmpDirPath(m_FastDbTmpDirPath.length() - 1) != OsPath::separator)
      {
         m_FastDbTmpDirPath.append(OsPath::separator);
      }

      // Pass the string to fastdb.  It's ugly to reference someone else's pointer
      // directly like this, but allows us to avoid changing the fastdb code for now.
      keyFileDir = m_FastDbTmpDirPath;
   }         
}


syntax highlighted by Code2HTML, v. 0.9.1