/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
 * pkix_logger.c
 *
 * Logger Object Functions
 *
 */

#include "pkix_logger.h"
#ifndef PKIX_ERROR_DESCRIPTION
#include "prprf.h"
#endif

/* Global variable to keep PKIX_Logger List */
PKIX_List *pkixLoggers = NULL;

/*
 * Once the Logger has been set, for any logging related operations, we have
 * to go through the List to find a match, and if found, issue the
 * corresponding callback. The overhead to check for DEBUG and TRACE in each
 * PKIX function entering and exiting is very expensive (400X), and redundant
 * if they are not the interest of the Logger. Therefore, the PKIX_Logger List
 * pkixLoggers is separated into two lists based on its Loggers' trace level.
 *
 * Whenever the pkixLoggers List is updated by PKIX_Logger_AddLogger() or
 * PKIX_Logger_SetLoggers(), we destroy and reconstruct pkixLoggersErrors
 * and pkixLoggersDebugTrace Logger Lists. The ERROR, FATAL_ERROR and
 * WARNING goes to pkixLoggersErrors and the DEBUG and TRACE goes to
 * pkixLoggersDebugTrace.
 *
 * Currently we provide five logging levels and the default setting are by:
 *
 *     PKIX_FATAL_ERROR() macro invokes pkix_Logger_Check of FATAL_ERROR level
 *     PKIX_ERROR() macro invokes pkix_Logger_Check of ERROR level
 *     WARNING is not invoked as default
 *     PKIX_DEBUG() macro invokes pkix_Logger_Check of DEBUG level. This needs
 *         compilation -DPKIX_<component>DEBUG flag to turn on 
 *     PKIX_ENTER() and PKIX_RETURN() macros invoke pkix_Logger_Check of TRACE
 *         level. TRACE provides duplicate information of DEBUG, but needs no
 *         recompilation and cannot choose component. To allow application
 *         to use DEBUG level, TRACE is put as last.
 *
 */
PKIX_List *pkixLoggersErrors = NULL;
PKIX_List *pkixLoggersDebugTrace = NULL;

/* To ensure atomic update on pkixLoggers lists */
PKIX_PL_MonitorLock *pkixLoggerLock = NULL;

/* --Private-Functions-------------------------------------------- */

/*
 * FUNCTION: pkix_Logger_CheckErrors
 * DESCRIPTION:
 *
 *  This function goes through each PKIX_Logger at "pkixLoggersList" and
 *  checks if "maxLevel" and "logComponent" satisfies what is specified in the
 *  PKIX_Logger. If satisfies, it invokes the callback in PKIX_Logger and
 *  passes a PKIX_PL_String that is the concatenation of "message" and 
 *  "message2" to the application for processing. 
 *  Since this call is inserted into a handful of PKIX macros, no macros are
 *  applied in this function, to avoid infinite recursion.
 *  If an error occurs, this call is aborted.
 *
 * PARAMETERS:
 *  "pkixLoggersList"
 *      A list of PKIX_Loggers to be examined for invoking callback. Must be
 *      non-NULL.
 *  "message"
 *      Address of "message" to be logged. Must be non-NULL.
 *  "message2"
 *      Address of "message2" to be concatenated and logged. May be NULL.
 *  "logComponent"
 *      A PKIX_UInt32 that indicates the component the message is from.
 *  "maxLevel"
 *      A PKIX_UInt32 that represents the level of severity of the message.
 *  "plContext"
 *      Platform-specific context pointer.
 * THREAD SAFETY:
 *  Thread Safe (see Thread Safety Definitions in Programmer's Guide)
 * RETURNS:
 *  Returns NULL if the function succeeds
 *  Returns a Fatal Error if the function fails in an unrecoverable way
 */
PKIX_Error *
pkix_Logger_Check(
        PKIX_List *pkixLoggersList,
        const char *message,
        const char *message2,
        PKIX_ERRORCLASS logComponent,
        PKIX_UInt32 currentLevel,
        void *plContext)
{
        PKIX_Logger *logger = NULL;
        PKIX_List *savedPkixLoggersErrors = NULL;
        PKIX_List *savedPkixLoggersDebugTrace = NULL;
        PKIX_PL_String *formatString = NULL;
        PKIX_PL_String *messageString = NULL;
        PKIX_PL_String *message2String = NULL;
        PKIX_PL_String *msgString = NULL;
        PKIX_Error *error = NULL;
        PKIX_Boolean needLogging = PKIX_FALSE;
        PKIX_UInt32 i, length;

        /*
         * We cannot use any the PKIX_ macros here, since this function is
         * called from some of these macros. It can create infinite recursion.
         */

        if ((pkixLoggersList == NULL) || (message == NULL)) {
                return(NULL);
        }

        /*
         * Disable all subsequent loggings to avoid recursion. The result is
         * if other thread is calling this function at the same time, there
         * won't be any logging because the pkixLoggersErrors and
         * pkixLoggersDebugTrace are set to null.
         * It would be nice if we provide control per thread (e.g. make
         * plContext threadable) then we can avoid the recursion by setting
         * flag at plContext. Then other thread's logging won't be affected.
         *
         * Also we need to use a reentrant Lock. Although we avoid recursion
         * for TRACE. When there is an ERROR occurs in subsequent call, this
         * function will be called.
         */

        error = PKIX_PL_MonitorLock_Enter(pkixLoggerLock, plContext);
        if (error) { return(NULL); }

        savedPkixLoggersDebugTrace = pkixLoggersDebugTrace;
        pkixLoggersDebugTrace = NULL;
        savedPkixLoggersErrors = pkixLoggersErrors;
        pkixLoggersErrors = NULL;

        /* Convert message and message2 to String */
        error = PKIX_PL_String_Create
                    (PKIX_ESCASCII, message, 0, &messageString, plContext);
        if (error) { goto cleanup; }

        if (message2) {
                error = PKIX_PL_String_Create
                    (PKIX_ESCASCII, message2, 0, &message2String, plContext);
                if (error) { goto cleanup; }
                error = PKIX_PL_String_Create
                    (PKIX_ESCASCII, "%s %s", 0, &formatString, plContext);
                if (error) { goto cleanup; }

        } else {
                error = PKIX_PL_String_Create
                    (PKIX_ESCASCII, "%s", 0, &formatString, plContext);
                if (error) { goto cleanup; }

        }

        error = PKIX_PL_Sprintf
                    (&msgString,
                    plContext,
                    formatString,
                    messageString,
                    message2String);
        if (error) { goto cleanup; }

        /* Go through the Logger list */

        error = PKIX_List_GetLength(pkixLoggersList, &length, plContext);
        if (error) { goto cleanup; }

        for (i = 0; i < length; i++) {

                error = PKIX_List_GetItem
                    (pkixLoggersList,
                    i,
                    (PKIX_PL_Object **) &logger,
                    plContext);
                if (error) { goto cleanup; }

                /* Intended logging level less or equal than the max */
                needLogging = (currentLevel <= logger->maxLevel);

                if (needLogging && (logger->callback)) {

                    /*
                     * We separate Logger into two lists based on log level
                     * but log level is not modified. We need to check here to
                     * avoid logging the higher log level (lower value) twice.
                     */
                    if (pkixLoggersList == pkixLoggersErrors) {
                            needLogging = needLogging && 
                                (currentLevel <= PKIX_LOGGER_LEVEL_WARNING);
                    } else if (pkixLoggersList == pkixLoggersDebugTrace) {
                            needLogging = needLogging && 
                                (currentLevel > PKIX_LOGGER_LEVEL_WARNING);
                    }
                
                    if (needLogging) {
                        if (logComponent == logger->logComponent) {
                            needLogging = PKIX_TRUE;
                        } else {
                            needLogging = PKIX_FALSE;
                        }
                    }

                    if (needLogging) {
                        error = logger->callback
                                (logger,
                                msgString,
                                currentLevel,
                                logComponent,
                                plContext);
                        if (error) { goto cleanup; }
                    }
                }

                error = PKIX_PL_Object_DecRef
                        ((PKIX_PL_Object *)logger, plContext);
                logger = NULL;
                if (error) { goto cleanup; }

        }

cleanup:

        if (formatString) {
                error = PKIX_PL_Object_DecRef
                        ((PKIX_PL_Object *)formatString, plContext);
        }

        if (messageString) {
                error = PKIX_PL_Object_DecRef
                         ((PKIX_PL_Object *)messageString, plContext);
        }

        if (message2String) {
                error = PKIX_PL_Object_DecRef
                        ((PKIX_PL_Object *)message2String, plContext);
        }

        if (msgString) {
                error = PKIX_PL_Object_DecRef
                        ((PKIX_PL_Object *)msgString, plContext);
        }

        if (logger) {
                error = PKIX_PL_Object_DecRef
                        ((PKIX_PL_Object *)logger, plContext);
        }

        if (pkixLoggersErrors == NULL && savedPkixLoggersErrors != NULL) {
                pkixLoggersErrors = savedPkixLoggersErrors;
        } 

        if (pkixLoggersDebugTrace == NULL && 
           savedPkixLoggersDebugTrace != NULL) {
                pkixLoggersDebugTrace = savedPkixLoggersDebugTrace;
        }

        error = PKIX_PL_MonitorLock_Exit(pkixLoggerLock, plContext);
        if (error) { return(NULL); }

        return(NULL);
}

PKIX_Error *
pkix_Logger_CheckWithCode(
        PKIX_List *pkixLoggersList,
        PKIX_UInt32 errorCode,
        const char *message2,
        PKIX_ERRORCLASS logComponent,
        PKIX_UInt32 currentLevel,
        void *plContext)
{
    char error[32];
    char *errorString = NULL;

    PKIX_ENTER(LOGGER, "pkix_Logger_CheckWithCode");
#if defined PKIX_ERROR_DESCRIPTION
    errorString = PKIX_ErrorText[errorCode];
#else
    PR_snprintf(error, 32, "Error code: %d", errorCode);
    errorString = error;
#endif /* PKIX_ERROR_DESCRIPTION */

    pkixErrorResult = pkix_Logger_Check(pkixLoggersList, errorString,
                                        message2, logComponent,
                                        currentLevel, plContext);
    PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: pkix_Logger_Destroy
 * (see comments for PKIX_PL_DestructorCallback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_Logger_Destroy(
        PKIX_PL_Object *object,
        void *plContext)
{
        PKIX_Logger *logger = NULL;

        PKIX_ENTER(LOGGER, "pkix_Logger_Destroy");
        PKIX_NULLCHECK_ONE(object);

        /* Check that this object is a logger */
        PKIX_CHECK(pkix_CheckType(object, PKIX_LOGGER_TYPE, plContext),
                    PKIX_OBJECTNOTLOGGER);

        logger = (PKIX_Logger *)object;

        /* We have a valid logger. DecRef its item and recurse on next */

        logger->callback = NULL;
        PKIX_DECREF(logger->context);
        logger->logComponent = (PKIX_ERRORCLASS)0;

cleanup:

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: pkix_Logger_ToString
 * (see comments for PKIX_PL_ToStringCallback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_Logger_ToString(
        PKIX_PL_Object *object,
        PKIX_PL_String **pString,
        void *plContext)
{
        PKIX_Logger *logger = NULL;
        char *asciiFormat = NULL;
        PKIX_PL_String *formatString = NULL;
        PKIX_PL_String *contextString = NULL;
        PKIX_PL_String *componentString = NULL;
        PKIX_PL_String *loggerString = NULL;

        PKIX_ENTER(LOGGER, "pkix_Logger_ToString_Helper");
        PKIX_NULLCHECK_TWO(object, pString);

        /* Check that this object is a logger */
        PKIX_CHECK(pkix_CheckType(object, PKIX_LOGGER_TYPE, plContext),
                    PKIX_OBJECTNOTLOGGER);

        logger = (PKIX_Logger *)object;

        asciiFormat =
                "[\n"
                "\tLogger: \n"
                "\tContext:          %s\n"
                "\tMaximum Level:    %d\n"
                "\tComponent Name:   %s\n"
                "]\n";

        PKIX_CHECK(PKIX_PL_String_Create
                    (PKIX_ESCASCII,
                    asciiFormat,
                    0,
                    &formatString,
                    plContext),
                    PKIX_STRINGCREATEFAILED);

        PKIX_TOSTRING(logger->context, &contextString, plContext,
                PKIX_OBJECTTOSTRINGFAILED);

        PKIX_CHECK(PKIX_PL_String_Create
                (PKIX_ESCASCII,
                (void *)PKIX_ERRORCLASSNAMES[logger->logComponent],
                0,
                &componentString,
                plContext),
                PKIX_STRINGCREATEFAILED);

        PKIX_CHECK(PKIX_PL_Sprintf
                (&loggerString,
                plContext,
                formatString,
                contextString,
                logger->maxLevel,
                componentString),
                PKIX_SPRINTFFAILED);

        *pString = loggerString;

cleanup:

        PKIX_DECREF(formatString);
        PKIX_DECREF(contextString);
        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: pkix_Logger_Equals
 * (see comments for PKIX_PL_EqualsCallback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_Logger_Equals(
        PKIX_PL_Object *first,
        PKIX_PL_Object *second,
        PKIX_Boolean *pResult,
        void *plContext)
{
        PKIX_UInt32 secondType;
        PKIX_Boolean cmpResult;
        PKIX_Logger *firstLogger = NULL;
        PKIX_Logger *secondLogger = NULL;

        PKIX_ENTER(LOGGER, "pkix_Logger_Equals");
        PKIX_NULLCHECK_THREE(first, second, pResult);

        /* test that first is a Logger */
        PKIX_CHECK(pkix_CheckType(first, PKIX_LOGGER_TYPE, plContext),
                PKIX_FIRSTOBJECTNOTLOGGER);

        /*
         * Since we know first is a Logger, if both references are
         * identical, they must be equal
         */
        if (first == second){
                *pResult = PKIX_TRUE;
                goto cleanup;
        }

        /*
         * If second isn't a Logger, we don't throw an error.
         * We simply return a Boolean result of FALSE
         */
        *pResult = PKIX_FALSE;
        PKIX_CHECK(PKIX_PL_Object_GetType(second, &secondType, plContext),
                    PKIX_COULDNOTGETTYPEOFSECONDARGUMENT);
        if (secondType != PKIX_LOGGER_TYPE) goto cleanup;

        firstLogger = (PKIX_Logger *)first;
        secondLogger = (PKIX_Logger *)second;

        cmpResult = PKIX_FALSE;

        if (firstLogger->callback != secondLogger->callback) {
                goto cleanup;
        }

        if (firstLogger->logComponent != secondLogger->logComponent) {
                goto cleanup;
        }

        PKIX_EQUALS  
                (firstLogger->context,
                secondLogger->context,
                &cmpResult,
                plContext,
                PKIX_OBJECTEQUALSFAILED);

        if (cmpResult == PKIX_FALSE) {
                goto cleanup;
        }

        if (firstLogger->maxLevel != secondLogger->maxLevel) {
                goto cleanup;
        }

        *pResult = cmpResult;

cleanup:

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: pkix_Logger_Hashcode
 * (see comments for PKIX_PL_HashcodeCallback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_Logger_Hashcode(
        PKIX_PL_Object *object,
        PKIX_UInt32 *pHashcode,
        void *plContext)
{
        PKIX_Logger *logger = NULL;
        PKIX_UInt32 hash = 0;
        PKIX_UInt32 tempHash = 0;

        PKIX_ENTER(LOGGER, "pkix_Logger_Hashcode");
        PKIX_NULLCHECK_TWO(object, pHashcode);

        PKIX_CHECK(pkix_CheckType(object, PKIX_LOGGER_TYPE, plContext),
                    PKIX_OBJECTNOTLOGGER);

        logger = (PKIX_Logger *)object;

        PKIX_HASHCODE(logger->context, &tempHash, plContext,
                PKIX_OBJECTHASHCODEFAILED);

        hash = (((((PKIX_UInt32)((char *)logger->callback - (char *)NULL) + tempHash) << 7) +
                logger->maxLevel) << 7) + (PKIX_UInt32)logger->logComponent;

        *pHashcode = hash;

cleanup:

        PKIX_RETURN(LOGGER);
}


/*
 * FUNCTION: pkix_Logger_Duplicate
 * (see comments for PKIX_PL_DuplicateCallback in pkix_pl_system.h)
 */
static PKIX_Error *
pkix_Logger_Duplicate(
        PKIX_PL_Object *object,
        PKIX_PL_Object **pNewObject,
        void *plContext)
{
        PKIX_Logger *logger = NULL;
        PKIX_Logger *dupLogger = NULL;

        PKIX_ENTER(LOGGER, "pkix_Logger_Duplicate");
        PKIX_NULLCHECK_TWO(object, pNewObject);

        PKIX_CHECK(pkix_CheckType
                    ((PKIX_PL_Object *)object, PKIX_LOGGER_TYPE, plContext),
                    PKIX_OBJECTNOTLOGGER);

        logger = (PKIX_Logger *) object;

        PKIX_CHECK(PKIX_PL_Object_Alloc
                    (PKIX_LOGGER_TYPE,
                    sizeof (PKIX_Logger),
                    (PKIX_PL_Object **)&dupLogger,
                    plContext),
                    PKIX_COULDNOTCREATELOGGEROBJECT);

        dupLogger->callback = logger->callback;
        dupLogger->maxLevel = logger->maxLevel;
        
        PKIX_DUPLICATE
                    (logger->context,
                    &dupLogger->context,
                    plContext,
                    PKIX_OBJECTDUPLICATEFAILED);

        dupLogger->logComponent = logger->logComponent;

        *pNewObject = (PKIX_PL_Object *) dupLogger;

cleanup:

        if (PKIX_ERROR_RECEIVED){
                PKIX_DECREF(dupLogger);
        }

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: pkix_Logger_RegisterSelf
 * DESCRIPTION:
 *  Registers PKIX_LOGGER_TYPE and its related functions with systemClasses[]
 * THREAD SAFETY:
 *  Not Thread Safe - for performance and complexity reasons
 *
 *  Since this function is only called by PKIX_PL_Initialize, which should
 *  only be called once, it is acceptable that this function is not
 *  thread-safe.
 */
PKIX_Error *
pkix_Logger_RegisterSelf(void *plContext)
{
        extern pkix_ClassTable_Entry systemClasses[PKIX_NUMTYPES];
        pkix_ClassTable_Entry entry;

        PKIX_ENTER(LOGGER, "pkix_Logger_RegisterSelf");

        entry.description = "Logger";
        entry.objCounter = 0;
        entry.typeObjectSize = sizeof(PKIX_Logger);
        entry.destructor = pkix_Logger_Destroy;
        entry.equalsFunction = pkix_Logger_Equals;
        entry.hashcodeFunction = pkix_Logger_Hashcode;
        entry.toStringFunction = pkix_Logger_ToString;
        entry.comparator = NULL;
        entry.duplicateFunction = pkix_Logger_Duplicate;

        systemClasses[PKIX_LOGGER_TYPE] = entry;

        PKIX_RETURN(LOGGER);
}

/* --Public-Logger-Functions--------------------------------------------- */

/*
 * FUNCTION: PKIX_Logger_Create (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_Logger_Create(
        PKIX_Logger_LogCallback callback,
        PKIX_PL_Object *loggerContext,
        PKIX_Logger **pLogger,
        void *plContext)
{
        PKIX_Logger *logger = NULL;

        PKIX_ENTER(LOGGER, "PKIX_Logger_Create");
        PKIX_NULLCHECK_ONE(pLogger);

        PKIX_CHECK(PKIX_PL_Object_Alloc
                    (PKIX_LOGGER_TYPE,
                    sizeof (PKIX_Logger),
                    (PKIX_PL_Object **)&logger,
                    plContext),
                    PKIX_COULDNOTCREATELOGGEROBJECT);

        logger->callback = callback;
        logger->maxLevel = 0;
        logger->logComponent = (PKIX_ERRORCLASS)0;

        PKIX_INCREF(loggerContext);
        logger->context = loggerContext;

        *pLogger = logger;
        logger = NULL;

cleanup:

        PKIX_DECREF(logger);

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: PKIX_Logger_GetLogCallback (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_Logger_GetLogCallback(
        PKIX_Logger *logger,
        PKIX_Logger_LogCallback *pCallback,
        void *plContext)
{
        PKIX_ENTER(LOGGER, "PKIX_Logger_GetLogCallback");
        PKIX_NULLCHECK_TWO(logger, pCallback);

        *pCallback = logger->callback;

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: PKIX_Logger_GetLoggerContext (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_Logger_GetLoggerContext(
        PKIX_Logger *logger,
        PKIX_PL_Object **pLoggerContext,
        void *plContext)
{
        PKIX_ENTER(LOGGER, "PKIX_Logger_GetLoggerContex");
        PKIX_NULLCHECK_TWO(logger, pLoggerContext);

        PKIX_INCREF(logger->context);
        *pLoggerContext = logger->context;

cleanup:
        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: PKIX_Logger_GetMaxLoggingLevel (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_Logger_GetMaxLoggingLevel(
        PKIX_Logger *logger,
        PKIX_UInt32 *pLevel,
        void *plContext)
{
        PKIX_ENTER(LOGGER, "PKIX_Logger_GetMaxLoggingLevel");
        PKIX_NULLCHECK_TWO(logger, pLevel);

        *pLevel = logger->maxLevel;

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: PKIX_Logger_SetMaxLoggingLevel (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_Logger_SetMaxLoggingLevel(
        PKIX_Logger *logger,
        PKIX_UInt32 level,
        void *plContext)
{
        PKIX_ENTER(LOGGER, "PKIX_Logger_SetMaxLoggingLevel");
        PKIX_NULLCHECK_ONE(logger);

        if (level > PKIX_LOGGER_LEVEL_MAX) {
                PKIX_ERROR(PKIX_LOGGINGLEVELEXCEEDSMAXIMUM);
        } else {
                logger->maxLevel = level;
        }

cleanup:

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: PKIX_Logger_GetLoggingComponent (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_Logger_GetLoggingComponent(
        PKIX_Logger *logger,
        PKIX_ERRORCLASS *pComponent,
        void *plContext)
{
        PKIX_ENTER(LOGGER, "PKIX_Logger_GetLoggingComponent");
        PKIX_NULLCHECK_TWO(logger, pComponent);

        *pComponent = logger->logComponent;

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: PKIX_Logger_SetLoggingComponent (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_Logger_SetLoggingComponent(
        PKIX_Logger *logger,
        PKIX_ERRORCLASS component,
        void *plContext)
{
        PKIX_ENTER(LOGGER, "PKIX_Logger_SetLoggingComponent");
        PKIX_NULLCHECK_ONE(logger);

        logger->logComponent = component;

        PKIX_RETURN(LOGGER);
}


/*
 * Following PKIX_GetLoggers(), PKIX_SetLoggers() and PKIX_AddLogger() are
 * documented as not thread-safe. However they are thread-safe now. We need
 * the lock when accessing the logger lists.
 */

/*
 * FUNCTION: PKIX_Logger_GetLoggers (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_GetLoggers(
        PKIX_List **pLoggers,  /* list of PKIX_Logger */
        void *plContext)
{
        PKIX_List *list = NULL;
        PKIX_List *savedPkixLoggersDebugTrace = NULL;
        PKIX_List *savedPkixLoggersErrors = NULL;
        PKIX_Logger *logger = NULL;
        PKIX_Logger *dupLogger = NULL;
        PKIX_UInt32 i, length;
        PKIX_Boolean locked = PKIX_FALSE;

        PKIX_ENTER(LOGGER, "PKIX_Logger_GetLoggers");
        PKIX_NULLCHECK_ONE(pLoggers);

        PKIX_CHECK(PKIX_PL_MonitorLock_Enter(pkixLoggerLock, plContext),
                PKIX_MONITORLOCKENTERFAILED);
        locked = PKIX_TRUE;

        /*
         * Temporarily disable DEBUG/TRACE Logging to avoid possible
         * deadlock:
         * When the Logger List is being accessed, e.g. by PKIX_ENTER or
         * PKIX_DECREF, pkix_Logger_Check may check whether logging
         * is requested, creating a deadlock situation.
         */
        savedPkixLoggersDebugTrace = pkixLoggersDebugTrace;
        pkixLoggersDebugTrace = NULL;
        savedPkixLoggersErrors = pkixLoggersErrors;
        pkixLoggersErrors = NULL;

        if (pkixLoggers == NULL) {
                length = 0;
        } else {
                PKIX_CHECK(PKIX_List_GetLength
                    (pkixLoggers, &length, plContext),
                    PKIX_LISTGETLENGTHFAILED);
        }

        /* Create a list and copy the pkixLoggers item to the list */
        PKIX_CHECK(PKIX_List_Create(&list, plContext),
                    PKIX_LISTCREATEFAILED);

        for (i = 0; i < length; i++) {

            PKIX_CHECK(PKIX_List_GetItem
                        (pkixLoggers,
                        i,
                        (PKIX_PL_Object **) &logger,
                        plContext),
                        PKIX_LISTGETITEMFAILED);

            PKIX_CHECK(pkix_Logger_Duplicate
                        ((PKIX_PL_Object *)logger,
                        (PKIX_PL_Object **)&dupLogger,
                        plContext),
                        PKIX_LOGGERDUPLICATEFAILED);

            PKIX_CHECK(PKIX_List_AppendItem
                        (list,
                        (PKIX_PL_Object *) dupLogger,
                        plContext),
                        PKIX_LISTAPPENDITEMFAILED);

            PKIX_DECREF(logger);
            PKIX_DECREF(dupLogger);
        }

        /* Set the list to be immutable */
        PKIX_CHECK(PKIX_List_SetImmutable(list, plContext),
                        PKIX_LISTSETIMMUTABLEFAILED);

        *pLoggers = list;

cleanup:

        PKIX_DECREF(logger);

        /* Restore logging capability */
        pkixLoggersDebugTrace = savedPkixLoggersDebugTrace;
        pkixLoggersErrors = savedPkixLoggersErrors;

        if (locked) {
                PKIX_CHECK(PKIX_PL_MonitorLock_Exit(pkixLoggerLock, plContext),
                        PKIX_MONITORLOCKEXITFAILED);
        }

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: PKIX_Logger_SetLoggers (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_SetLoggers(
        PKIX_List *loggers,  /* list of PKIX_Logger */
        void *plContext)
{
        PKIX_List *list = NULL;
        PKIX_List *savedPkixLoggersErrors = NULL;
        PKIX_List *savedPkixLoggersDebugTrace = NULL;
        PKIX_Logger *logger = NULL;
        PKIX_Logger *dupLogger = NULL;
        PKIX_Boolean locked = PKIX_FALSE;
        PKIX_UInt32 i, length;

        PKIX_ENTER(LOGGER, "PKIX_SetLoggers");

        PKIX_CHECK(PKIX_PL_MonitorLock_Enter(pkixLoggerLock, plContext),
                PKIX_MONITORLOCKENTERFAILED);
        locked = PKIX_TRUE;

        /* Disable tracing, etc. to avoid recursion and deadlock */
        savedPkixLoggersDebugTrace = pkixLoggersDebugTrace;
        pkixLoggersDebugTrace = NULL;
        savedPkixLoggersErrors = pkixLoggersErrors;
        pkixLoggersErrors = NULL;

        /* discard any prior loggers */
        PKIX_DECREF(pkixLoggers);
        PKIX_DECREF(savedPkixLoggersErrors);
        PKIX_DECREF(savedPkixLoggersDebugTrace);

        if (loggers != NULL) {

                PKIX_CHECK(PKIX_List_Create(&list, plContext),
                    PKIX_LISTCREATEFAILED);

                PKIX_CHECK(PKIX_List_GetLength(loggers, &length, plContext),
                    PKIX_LISTGETLENGTHFAILED);

                for (i = 0; i < length; i++) {

                    PKIX_CHECK(PKIX_List_GetItem
                        (loggers,
                        i,
                        (PKIX_PL_Object **) &logger,
                        plContext),
                        PKIX_LISTGETITEMFAILED);

                    PKIX_CHECK(pkix_Logger_Duplicate
                        ((PKIX_PL_Object *)logger,
                        (PKIX_PL_Object **)&dupLogger,
                        plContext),
                        PKIX_LOGGERDUPLICATEFAILED);

                    PKIX_CHECK(PKIX_List_AppendItem
                        (list,
                        (PKIX_PL_Object *) dupLogger,
                        plContext),
                        PKIX_LISTAPPENDITEMFAILED);

                    /* Make two lists */

                    /* Put in pkixLoggersErrors in any case*/

                    if (savedPkixLoggersErrors == NULL) {

                        PKIX_CHECK(PKIX_List_Create
                                (&savedPkixLoggersErrors,
                                plContext),
                                PKIX_LISTCREATEFAILED);
                    }
        
                    PKIX_CHECK(PKIX_List_AppendItem
                            (savedPkixLoggersErrors,
                            (PKIX_PL_Object *) dupLogger,
                            plContext),
                            PKIX_LISTAPPENDITEMFAILED);

                    if (logger->maxLevel > PKIX_LOGGER_LEVEL_WARNING) {

                        /* Put in pkixLoggersDebugTrace */

                        if (savedPkixLoggersDebugTrace == NULL) {

                            PKIX_CHECK(PKIX_List_Create
                                    (&savedPkixLoggersDebugTrace,
                                    plContext),
                                    PKIX_LISTCREATEFAILED);
                        }
        
                        PKIX_CHECK(PKIX_List_AppendItem
                                (savedPkixLoggersDebugTrace,
                                (PKIX_PL_Object *) dupLogger,
                                plContext),
                                PKIX_LISTAPPENDITEMFAILED);
                    }
                    PKIX_DECREF(logger);
                    PKIX_DECREF(dupLogger);

                }

                pkixLoggers = list;
        }

cleanup:

        if (PKIX_ERROR_RECEIVED){
                PKIX_DECREF(list);
                PKIX_DECREF(savedPkixLoggersErrors);
                PKIX_DECREF(savedPkixLoggersDebugTrace);
                pkixLoggers = NULL;
        }

        PKIX_DECREF(logger);

        /* Reenable logging capability with new lists */
        pkixLoggersErrors = savedPkixLoggersErrors;
        pkixLoggersDebugTrace = savedPkixLoggersDebugTrace;

        if (locked) {
                PKIX_CHECK(PKIX_PL_MonitorLock_Exit(pkixLoggerLock, plContext),
                        PKIX_MONITORLOCKEXITFAILED);
        }

        PKIX_RETURN(LOGGER);
}

/*
 * FUNCTION: PKIX_Logger_AddLogger (see comments in pkix_util.h)
 */
PKIX_Error *
PKIX_AddLogger(
        PKIX_Logger *logger,
        void *plContext)
{
        PKIX_Logger *dupLogger = NULL;
        PKIX_Logger *addLogger = NULL;
        PKIX_List *savedPkixLoggersErrors = NULL;
        PKIX_List *savedPkixLoggersDebugTrace = NULL;
        PKIX_Boolean locked = PKIX_FALSE;
        PKIX_UInt32 i, length;

        PKIX_ENTER(LOGGER, "PKIX_Logger_AddLogger");
        PKIX_NULLCHECK_ONE(logger);

        PKIX_CHECK(PKIX_PL_MonitorLock_Enter(pkixLoggerLock, plContext),
                PKIX_MONITORLOCKENTERFAILED);
        locked = PKIX_TRUE;

        savedPkixLoggersDebugTrace = pkixLoggersDebugTrace;
        pkixLoggersDebugTrace = NULL;
        savedPkixLoggersErrors = pkixLoggersErrors;
        pkixLoggersErrors = NULL;

        PKIX_DECREF(savedPkixLoggersErrors);
        PKIX_DECREF(savedPkixLoggersDebugTrace);

        if (pkixLoggers == NULL) {

            PKIX_CHECK(PKIX_List_Create(&pkixLoggers, plContext),
                    PKIX_LISTCREATEFAILED);
        }

        PKIX_CHECK(pkix_Logger_Duplicate
                    ((PKIX_PL_Object *)logger,
                    (PKIX_PL_Object **)&dupLogger,
                    plContext),
                    PKIX_LOGGERDUPLICATEFAILED);

        PKIX_CHECK(PKIX_List_AppendItem
                    (pkixLoggers,
                    (PKIX_PL_Object *) dupLogger,
                    plContext),
                    PKIX_LISTAPPENDITEMFAILED);

        PKIX_CHECK(PKIX_List_GetLength(pkixLoggers, &length, plContext),
                    PKIX_LISTGETLENGTHFAILED);

        /* Reconstruct pkixLoggersErrors and pkixLoggersDebugTrace */
        for (i = 0; i < length; i++) {

                PKIX_CHECK(PKIX_List_GetItem
                        (pkixLoggers,
                        i,
                        (PKIX_PL_Object **) &addLogger,
                        plContext),
                        PKIX_LISTGETITEMFAILED);


                /* Put in pkixLoggersErrors */

                if (savedPkixLoggersErrors == NULL) {

                        PKIX_CHECK(PKIX_List_Create
                                    (&savedPkixLoggersErrors,
                                    plContext),
                                    PKIX_LISTCREATEFAILED);
                }
        
                PKIX_CHECK(PKIX_List_AppendItem
                        (savedPkixLoggersErrors,
                        (PKIX_PL_Object *) addLogger,
                        plContext),
                        PKIX_LISTAPPENDITEMFAILED);
                            
                if (addLogger->maxLevel > PKIX_LOGGER_LEVEL_WARNING) {

                        /* Put in pkixLoggersDebugTrace */

                        if (savedPkixLoggersDebugTrace == NULL) {

                            PKIX_CHECK(PKIX_List_Create
                                    (&savedPkixLoggersDebugTrace,
                                    plContext),
                                    PKIX_LISTCREATEFAILED);
                        }

                        PKIX_CHECK(PKIX_List_AppendItem
                                (savedPkixLoggersDebugTrace,
                                (PKIX_PL_Object *) addLogger,
                                plContext),
                                PKIX_LISTAPPENDITEMFAILED);
                }

                PKIX_DECREF(addLogger);

        }

cleanup:

        PKIX_DECREF(dupLogger);
        PKIX_DECREF(addLogger);

        /* Restore logging capability */
        pkixLoggersErrors = savedPkixLoggersErrors;
        pkixLoggersDebugTrace = savedPkixLoggersDebugTrace;

        if (locked) {
                PKIX_CHECK(PKIX_PL_MonitorLock_Exit(pkixLoggerLock, plContext),
                        PKIX_MONITORLOCKEXITFAILED);
        }

       PKIX_RETURN(LOGGER);
}
