/* 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/. */

/*
 * error.c
 *
 * This file contains the code implementing the per-thread error
 * stacks upon which most NSS routines report their errors.
 */

#ifndef BASE_H
#include "base.h"
#endif              /* BASE_H */
#include <limits.h> /* for UINT_MAX */
#include <string.h> /* for memmove */

#if defined(__MINGW32__)
#include <windows.h>
#endif

#define NSS_MAX_ERROR_STACK_COUNT 16 /* error codes */

/*
 * The stack itself has a header, and a sequence of integers.
 * The header records the amount of space (as measured in stack
 * slots) already allocated for the stack, and the count of the
 * number of records currently being used.
 */

struct stack_header_str {
    PRUint16 space;
    PRUint16 count;
};

struct error_stack_str {
    struct stack_header_str header;
    PRInt32 stack[1];
};
typedef struct error_stack_str error_stack;

/*
 * error_stack_index
 *
 * Thread-private data must be indexed.  This is that index.
 * See PR_NewThreadPrivateIndex for more information.
 *
 * Thread-private data indexes are in the range [0, 127].
 */

#define INVALID_TPD_INDEX UINT_MAX
static PRUintn error_stack_index = INVALID_TPD_INDEX;

/*
 * call_once
 *
 * The thread-private index must be obtained (once!) at runtime.
 * This block is used for that one-time call.
 */

static PRCallOnceType error_call_once;
static const PRCallOnceType error_call_again;

/*
 * error_once_function
 *
 * This is the once-called callback.
 */
static PRStatus
error_once_function(void)
{

/*
 * This #ifdef function is redundant. It performs the same thing as the
 * else case.
 *
 * However, the MinGW version looks up the function from nss3's export
 * table, and on MinGW _that_ behaves differently than passing a
 * function pointer in a different module because MinGW has
 * -mnop-fun-dllimport specified, which generates function thunks for
 * cross-module calls. And when a module (like nssckbi) gets unloaded,
 * and you try to call into that thunk (which is now missing) you'll
 * crash. So we do this bit of ugly to avoid that crash. Fortunately
 * this is the only place we've had to do this.
 */
#if defined(__MINGW32__)
    HMODULE nss3 = GetModuleHandleW(L"nss3");
    if (nss3) {
        FARPROC freePtr = GetProcAddress(nss3, "PR_Free");
        if (freePtr) {
            return PR_NewThreadPrivateIndex(&error_stack_index, freePtr);
        }
    }
    return PR_NewThreadPrivateIndex(&error_stack_index, PR_Free);
#else
    return PR_NewThreadPrivateIndex(&error_stack_index, PR_Free);
#endif
}

/*
 * error_get_my_stack
 *
 * This routine returns the calling thread's error stack, creating
 * it if necessary.  It may return NULL upon error, which implicitly
 * means that it ran out of memory.
 */

static error_stack *
error_get_my_stack(void)
{
    PRStatus st;
    error_stack *rv;
    PRUintn new_size;
    PRUint32 new_bytes;
    error_stack *new_stack;

    if (INVALID_TPD_INDEX == error_stack_index) {
        st = PR_CallOnce(&error_call_once, error_once_function);
        if (PR_SUCCESS != st) {
            return (error_stack *)NULL;
        }
    }

    rv = (error_stack *)PR_GetThreadPrivate(error_stack_index);
    if ((error_stack *)NULL == rv) {
        /* Doesn't exist; create one */
        new_size = 16;
    } else if (rv->header.count == rv->header.space &&
               rv->header.count < NSS_MAX_ERROR_STACK_COUNT) {
        /* Too small, expand it */
        new_size = PR_MIN(rv->header.space * 2, NSS_MAX_ERROR_STACK_COUNT);
    } else {
        /* Okay, return it */
        return rv;
    }

    new_bytes = (new_size * sizeof(PRInt32)) + sizeof(error_stack);
    /* Use NSPR's calloc/realloc, not NSS's, to avoid loops! */
    new_stack = PR_Calloc(1, new_bytes);

    if ((error_stack *)NULL != new_stack) {
        if ((error_stack *)NULL != rv) {
            (void)nsslibc_memcpy(new_stack, rv, rv->header.space);
        }
        new_stack->header.space = new_size;
    }

    /* Set the value, whether or not the allocation worked */
    PR_SetThreadPrivate(error_stack_index, new_stack);
    return new_stack;
}

/*
 * The error stack
 *
 * The public methods relating to the error stack are:
 *
 *  NSS_GetError
 *  NSS_GetErrorStack
 *
 * The nonpublic methods relating to the error stack are:
 *
 *  nss_SetError
 *  nss_ClearErrorStack
 *
 */

/*
 * NSS_GetError
 *
 * This routine returns the highest-level (most general) error set
 * by the most recent NSS library routine called by the same thread
 * calling this routine.
 *
 * This routine cannot fail.  However, it may return zero, which
 * indicates that the previous NSS library call did not set an error.
 *
 * Return value:
 *  0 if no error has been set
 *  A nonzero error number
 */

NSS_IMPLEMENT PRInt32
NSS_GetError(void)
{
    error_stack *es = error_get_my_stack();

    if ((error_stack *)NULL == es) {
        return NSS_ERROR_NO_MEMORY; /* Good guess! */
    }

    if (0 == es->header.count) {
        return 0;
    }

    return es->stack[es->header.count - 1];
}

/*
 * NSS_GetErrorStack
 *
 * This routine returns a pointer to an array of integers, containing
 * the entire sequence or "stack" of errors set by the most recent NSS
 * library routine called by the same thread calling this routine.
 * NOTE: the caller DOES NOT OWN the memory pointed to by the return
 * value.  The pointer will remain valid until the calling thread
 * calls another NSS routine.  The lowest-level (most specific) error
 * is first in the array, and the highest-level is last.  The array is
 * zero-terminated.  This routine may return NULL upon error; this
 * indicates a low-memory situation.
 *
 * Return value:
 *  NULL upon error, which is an implied NSS_ERROR_NO_MEMORY
 *  A NON-caller-owned pointer to an array of integers
 */

NSS_IMPLEMENT PRInt32 *
NSS_GetErrorStack(void)
{
    error_stack *es = error_get_my_stack();

    if ((error_stack *)NULL == es) {
        return (PRInt32 *)NULL;
    }

    /* Make sure it's terminated */
    es->stack[es->header.count] = 0;

    return es->stack;
}

/*
 * nss_SetError
 *
 * This routine places a new error code on the top of the calling
 * thread's error stack.  Calling this routine wiht an error code
 * of zero will clear the error stack.
 */

NSS_IMPLEMENT void
nss_SetError(PRUint32 error)
{
    error_stack *es;

    if (0 == error) {
        nss_ClearErrorStack();
        return;
    }

    es = error_get_my_stack();
    if ((error_stack *)NULL == es) {
        /* Oh, well. */
        return;
    }

    if (es->header.count < es->header.space) {
        es->stack[es->header.count++] = error;
    } else {
        memmove(es->stack, es->stack + 1,
                (es->header.space - 1) * (sizeof es->stack[0]));
        es->stack[es->header.space - 1] = error;
    }
    return;
}

/*
 * nss_ClearErrorStack
 *
 * This routine clears the calling thread's error stack.
 */

NSS_IMPLEMENT void
nss_ClearErrorStack(void)
{
    error_stack *es = error_get_my_stack();
    if ((error_stack *)NULL == es) {
        /* Oh, well. */
        return;
    }

    es->header.count = 0;
    es->stack[0] = 0;
    return;
}

/*
 * nss_DestroyErrorStack
 *
 * This routine frees the calling thread's error stack.
 */

NSS_IMPLEMENT void
nss_DestroyErrorStack(void)
{
    if (INVALID_TPD_INDEX != error_stack_index) {
        PR_SetThreadPrivate(error_stack_index, NULL);
        error_stack_index = INVALID_TPD_INDEX;
        error_call_once = error_call_again; /* allow to init again */
    }
    return;
}