/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#ifdef DEBUG
static const char CVS_ID[] = "@(#) $RCSfile: error.c,v $ $Revision: 1.9 $ $Date: 2008/05/17 03:44:39 $";
#endif /* DEBUG */

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

#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;

/*
 * error_once_function
 *
 * This is the once-called callback.
 */
static PRStatus
error_once_function ( void)
{
  return PR_NewThreadPrivateIndex(&error_stack_index, PR_Free);
}

/*
 * 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);
  }
  return;
}
