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

/*
 * arena.c
 *
 * This contains the implementation of NSS's thread-safe arenas.
 */

#ifndef BASE_H
#include "base.h"
#endif /* BASE_H */

#ifdef ARENA_THREADMARK
#include "prthread.h"
#endif /* ARENA_THREADMARK */

#include "prlock.h"
#include "plarena.h"

#include <string.h>

/*
 * NSSArena
 *
 * This is based on NSPR's arena code, but it is threadsafe.
 *
 * The public methods relating to this type are:
 *
 *  NSSArena_Create  -- constructor
 *  NSSArena_Destroy
 *  NSS_ZAlloc
 *  NSS_ZRealloc
 *  NSS_ZFreeIf
 *
 * The nonpublic methods relating to this type are:
 *
 *  nssArena_Create  -- constructor
 *  nssArena_Destroy
 *  nssArena_Mark
 *  nssArena_Release
 *  nssArena_Unmark
 *
 *  nss_ZAlloc
 *  nss_ZFreeIf
 *  nss_ZRealloc
 *
 * In debug builds, the following calls are available:
 *
 *  nssArena_verifyPointer
 *  nssArena_registerDestructor
 *  nssArena_deregisterDestructor
 */

struct NSSArenaStr {
    PLArenaPool pool;
    PRLock *lock;
#ifdef ARENA_THREADMARK
    PRThread *marking_thread;
    nssArenaMark *first_mark;
    nssArenaMark *last_mark;
#endif /* ARENA_THREADMARK */
#ifdef ARENA_DESTRUCTOR_LIST
    struct arena_destructor_node *first_destructor;
    struct arena_destructor_node *last_destructor;
#endif /* ARENA_DESTRUCTOR_LIST */
};

/*
 * nssArenaMark
 *
 * This type is used to mark the current state of an NSSArena.
 */

struct nssArenaMarkStr {
    PRUint32 magic;
    void *mark;
#ifdef ARENA_THREADMARK
    nssArenaMark *next;
#endif /* ARENA_THREADMARK */
#ifdef ARENA_DESTRUCTOR_LIST
    struct arena_destructor_node *next_destructor;
    struct arena_destructor_node *prev_destructor;
#endif /* ARENA_DESTRUCTOR_LIST */
};

#define MARK_MAGIC 0x4d41524b /* "MARK" how original */

/*
 * But first, the pointer-tracking code
 */
#ifdef DEBUG
extern const NSSError NSS_ERROR_INTERNAL_ERROR;

static nssPointerTracker arena_pointer_tracker;

static PRStatus
arena_add_pointer(const NSSArena *arena)
{
    PRStatus rv;

    rv = nssPointerTracker_initialize(&arena_pointer_tracker);
    if (PR_SUCCESS != rv) {
        return rv;
    }

    rv = nssPointerTracker_add(&arena_pointer_tracker, arena);
    if (PR_SUCCESS != rv) {
        NSSError e = NSS_GetError();
        if (NSS_ERROR_NO_MEMORY != e) {
            nss_SetError(NSS_ERROR_INTERNAL_ERROR);
        }

        return rv;
    }

    return PR_SUCCESS;
}

static PRStatus
arena_remove_pointer(const NSSArena *arena)
{
    PRStatus rv;

    rv = nssPointerTracker_remove(&arena_pointer_tracker, arena);
    if (PR_SUCCESS != rv) {
        nss_SetError(NSS_ERROR_INTERNAL_ERROR);
    }

    return rv;
}

/*
 * nssArena_verifyPointer
 *
 * This method is only present in debug builds.
 *
 * If the specified pointer is a valid pointer to an NSSArena object,
 * this routine will return PR_SUCCESS.  Otherwise, it will put an
 * error on the error stack and return PR_FAILURE.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *
 * Return value:
 *  PR_SUCCESS if the pointer is valid
 *  PR_FAILURE if it isn't
 */

NSS_IMPLEMENT PRStatus
nssArena_verifyPointer(const NSSArena *arena)
{
    PRStatus rv;

    rv = nssPointerTracker_initialize(&arena_pointer_tracker);
    if (PR_SUCCESS != rv) {
        /*
         * This is a little disingenious.  We have to initialize the
         * tracker, because someone could "legitimately" try to verify
         * an arena pointer before one is ever created.  And this step
         * might fail, due to lack of memory.  But the only way that
         * this step can fail is if it's doing the call_once stuff,
         * (later calls just no-op).  And if it didn't no-op, there
         * aren't any valid arenas.. so the argument certainly isn't one.
         */
        nss_SetError(NSS_ERROR_INVALID_ARENA);
        return PR_FAILURE;
    }

    rv = nssPointerTracker_verify(&arena_pointer_tracker, arena);
    if (PR_SUCCESS != rv) {
        nss_SetError(NSS_ERROR_INVALID_ARENA);
        return PR_FAILURE;
    }

    return PR_SUCCESS;
}
#endif /* DEBUG */

#ifdef ARENA_DESTRUCTOR_LIST

struct arena_destructor_node {
    struct arena_destructor_node *next;
    struct arena_destructor_node *prev;
    void (*destructor)(void *argument);
    void *arg;
};

/*
 * nssArena_registerDestructor
 *
 * This routine stores a pointer to a callback and an arbitrary
 * pointer-sized argument in the arena, at the current point in
 * the mark stack.  If the arena is destroyed, or an "earlier"
 * mark is released, then this destructor will be called at that
 * time.  Note that the destructor will be called with the arena
 * locked, which means the destructor may free memory in that
 * arena, but it may not allocate or cause to be allocated any
 * memory.  This callback facility was included to support our
 * debug-version pointer-tracker feature; overuse runs counter to
 * the the original intent of arenas.  This routine returns a
 * PRStatus value; if successful, it will return PR_SUCCESS.  If
 * unsuccessful, it will set an error on the error stack and
 * return PR_FAILURE.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  PR_SUCCESS
 *  PR_FAILURE
 */

NSS_IMPLEMENT PRStatus
nssArena_registerDestructor(NSSArena *arena, void (*destructor)(void *argument),
                            void *arg)
{
    struct arena_destructor_node *it;

#ifdef NSSDEBUG
    if (PR_SUCCESS != nssArena_verifyPointer(arena)) {
        return PR_FAILURE;
    }
#endif /* NSSDEBUG */

    it = nss_ZNEW(arena, struct arena_destructor_node);
    if ((struct arena_destructor_node *)NULL == it) {
        return PR_FAILURE;
    }

    it->prev = arena->last_destructor;
    arena->last_destructor->next = it;
    arena->last_destructor = it;
    it->destructor = destructor;
    it->arg = arg;

    if ((nssArenaMark *)NULL != arena->last_mark) {
        arena->last_mark->prev_destructor = it->prev;
        arena->last_mark->next_destructor = it->next;
    }

    return PR_SUCCESS;
}

NSS_IMPLEMENT PRStatus
nssArena_deregisterDestructor(NSSArena *arena,
                              void (*destructor)(void *argument), void *arg)
{
    struct arena_destructor_node *it;

#ifdef NSSDEBUG
    if (PR_SUCCESS != nssArena_verifyPointer(arena)) {
        return PR_FAILURE;
    }
#endif /* NSSDEBUG */

    for (it = arena->first_destructor; it; it = it->next) {
        if ((it->destructor == destructor) && (it->arg == arg)) {
            break;
        }
    }

    if ((struct arena_destructor_node *)NULL == it) {
        nss_SetError(NSS_ERROR_NOT_FOUND);
        return PR_FAILURE;
    }

    if (it == arena->first_destructor) {
        arena->first_destructor = it->next;
    }

    if (it == arena->last_destructor) {
        arena->last_destructor = it->prev;
    }

    if ((struct arena_destructor_node *)NULL != it->prev) {
        it->prev->next = it->next;
    }

    if ((struct arena_destructor_node *)NULL != it->next) {
        it->next->prev = it->prev;
    }

    {
        nssArenaMark *m;
        for (m = arena->first_mark; m; m = m->next) {
            if (m->next_destructor == it) {
                m->next_destructor = it->next;
            }
            if (m->prev_destructor == it) {
                m->prev_destructor = it->prev;
            }
        }
    }

    nss_ZFreeIf(it);
    return PR_SUCCESS;
}

static void
nss_arena_call_destructor_chain(struct arena_destructor_node *it)
{
    for (; it; it = it->next) {
        (*(it->destructor))(it->arg);
    }
}

#endif /* ARENA_DESTRUCTOR_LIST */

/*
 * NSSArena_Create
 *
 * This routine creates a new memory arena.  This routine may return
 * NULL upon error, in which case it will have created an error stack.
 *
 * The top-level error may be one of the following values:
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  A pointer to an NSSArena upon success
 */

NSS_IMPLEMENT NSSArena *
NSSArena_Create(void)
{
    nss_ClearErrorStack();
    return nssArena_Create();
}

/*
 * nssArena_Create
 *
 * This routine creates a new memory arena.  This routine may return
 * NULL upon error, in which case it will have set an error on the
 * error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_NO_MEMORY
 *
 * Return value:
 *  NULL upon error
 *  A pointer to an NSSArena upon success
 */

NSS_IMPLEMENT NSSArena *
nssArena_Create(void)
{
    NSSArena *rv = (NSSArena *)NULL;

    rv = nss_ZNEW((NSSArena *)NULL, NSSArena);
    if ((NSSArena *)NULL == rv) {
        nss_SetError(NSS_ERROR_NO_MEMORY);
        return (NSSArena *)NULL;
    }

    rv->lock = PR_NewLock();
    if ((PRLock *)NULL == rv->lock) {
        (void)nss_ZFreeIf(rv);
        nss_SetError(NSS_ERROR_NO_MEMORY);
        return (NSSArena *)NULL;
    }

    /*
     * Arena sizes.  The current security code has 229 occurrences of
     * PORT_NewArena.  The default chunksizes specified break down as
     *
     *  Size    Mult.   Specified as
     *   512       1    512
     *  1024       7    1024
     *  2048       5    2048
     *  2048       5    CRMF_DEFAULT_ARENA_SIZE
     *  2048     190    DER_DEFAULT_CHUNKSIZE
     *  2048      20    SEC_ASN1_DEFAULT_ARENA_SIZE
     *  4096       1    4096
     *
     * Obviously this "default chunksize" flexibility isn't very
     * useful to us, so I'll just pick 2048.
     */

    PL_InitArenaPool(&rv->pool, "NSS", 2048, sizeof(double));

#ifdef DEBUG
    {
        PRStatus st;
        st = arena_add_pointer(rv);
        if (PR_SUCCESS != st) {
            PL_FinishArenaPool(&rv->pool);
            PR_DestroyLock(rv->lock);
            (void)nss_ZFreeIf(rv);
            return (NSSArena *)NULL;
        }
    }
#endif /* DEBUG */

    return rv;
}

/*
 * NSSArena_Destroy
 *
 * This routine will destroy the specified arena, freeing all memory
 * allocated from it.  This routine returns a PRStatus value; if
 * successful, it will return PR_SUCCESS.  If unsuccessful, it will
 * create an error stack and return PR_FAILURE.
 *
 * The top-level error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *
 * Return value:
 *  PR_SUCCESS upon success
 *  PR_FAILURE upon failure
 */

NSS_IMPLEMENT PRStatus
NSSArena_Destroy(NSSArena *arena)
{
    nss_ClearErrorStack();

#ifdef DEBUG
    if (PR_SUCCESS != nssArena_verifyPointer(arena)) {
        return PR_FAILURE;
    }
#endif /* DEBUG */

    return nssArena_Destroy(arena);
}

/*
 * nssArena_Destroy
 *
 * This routine will destroy the specified arena, freeing all memory
 * allocated from it.  This routine returns a PRStatus value; if
 * successful, it will return PR_SUCCESS.  If unsuccessful, it will
 * set an error on the error stack and return PR_FAILURE.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *
 * Return value:
 *  PR_SUCCESS
 *  PR_FAILURE
 */

NSS_IMPLEMENT PRStatus
nssArena_Destroy(NSSArena *arena)
{
    PRLock *lock;

#ifdef NSSDEBUG
    if (PR_SUCCESS != nssArena_verifyPointer(arena)) {
        return PR_FAILURE;
    }
#endif /* NSSDEBUG */

    if ((PRLock *)NULL == arena->lock) {
        /* Just got destroyed */
        nss_SetError(NSS_ERROR_INVALID_ARENA);
        return PR_FAILURE;
    }
    PR_Lock(arena->lock);

#ifdef DEBUG
    if (PR_SUCCESS != arena_remove_pointer(arena)) {
        PR_Unlock(arena->lock);
        return PR_FAILURE;
    }
#endif /* DEBUG */

#ifdef ARENA_DESTRUCTOR_LIST
    /* Note that the arena is locked at this time */
    nss_arena_call_destructor_chain(arena->first_destructor);
#endif /* ARENA_DESTRUCTOR_LIST */

    PL_FinishArenaPool(&arena->pool);
    lock = arena->lock;
    arena->lock = (PRLock *)NULL;
    PR_Unlock(lock);
    PR_DestroyLock(lock);
    (void)nss_ZFreeIf(arena);
    return PR_SUCCESS;
}

static void *nss_zalloc_arena_locked(NSSArena *arena, PRUint32 size);

/*
 * nssArena_Mark
 *
 * This routine "marks" the current state of an arena.  Space
 * allocated after the arena has been marked can be freed by
 * releasing the arena back to the mark with nssArena_Release,
 * or committed by calling nssArena_Unmark.  When successful,
 * this routine returns a valid nssArenaMark pointer.  This
 * routine may return NULL upon error, in which case it will
 * have set an error on the error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *  NSS_ERROR_NO_MEMORY
 *  NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD
 *
 * Return value:
 *  NULL upon failure
 *  An nssArenaMark pointer upon success
 */

NSS_IMPLEMENT nssArenaMark *
nssArena_Mark(NSSArena *arena)
{
    nssArenaMark *rv;
    void *p;

#ifdef NSSDEBUG
    if (PR_SUCCESS != nssArena_verifyPointer(arena)) {
        return (nssArenaMark *)NULL;
    }
#endif /* NSSDEBUG */

    if ((PRLock *)NULL == arena->lock) {
        /* Just got destroyed */
        nss_SetError(NSS_ERROR_INVALID_ARENA);
        return (nssArenaMark *)NULL;
    }
    PR_Lock(arena->lock);

#ifdef ARENA_THREADMARK
    if ((PRThread *)NULL == arena->marking_thread) {
        /* Unmarked.  Store our thread ID */
        arena->marking_thread = PR_GetCurrentThread();
        /* This call never fails. */
    } else {
        /* Marked.  Verify it's the current thread */
        if (PR_GetCurrentThread() != arena->marking_thread) {
            PR_Unlock(arena->lock);
            nss_SetError(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD);
            return (nssArenaMark *)NULL;
        }
    }
#endif /* ARENA_THREADMARK */

    p = PL_ARENA_MARK(&arena->pool);
    /* No error possible */

    /* Do this after the mark */
    rv = (nssArenaMark *)nss_zalloc_arena_locked(arena, sizeof(nssArenaMark));
    if ((nssArenaMark *)NULL == rv) {
        PR_Unlock(arena->lock);
        nss_SetError(NSS_ERROR_NO_MEMORY);
        return (nssArenaMark *)NULL;
    }

#ifdef ARENA_THREADMARK
    if ((nssArenaMark *)NULL == arena->first_mark) {
        arena->first_mark = rv;
        arena->last_mark = rv;
    } else {
        arena->last_mark->next = rv;
        arena->last_mark = rv;
    }
#endif /* ARENA_THREADMARK */

    rv->mark = p;
    rv->magic = MARK_MAGIC;

#ifdef ARENA_DESTRUCTOR_LIST
    rv->prev_destructor = arena->last_destructor;
#endif /* ARENA_DESTRUCTOR_LIST */

    PR_Unlock(arena->lock);

    return rv;
}

/*
 * nss_arena_unmark_release
 *
 * This static routine implements the routines nssArena_Release
 * ans nssArena_Unmark, which are almost identical.
 */

static PRStatus
nss_arena_unmark_release(NSSArena *arena, nssArenaMark *arenaMark,
                         PRBool release)
{
    void *inner_mark;

#ifdef NSSDEBUG
    if (PR_SUCCESS != nssArena_verifyPointer(arena)) {
        return PR_FAILURE;
    }
#endif /* NSSDEBUG */

    if (MARK_MAGIC != arenaMark->magic) {
        nss_SetError(NSS_ERROR_INVALID_ARENA_MARK);
        return PR_FAILURE;
    }

    if ((PRLock *)NULL == arena->lock) {
        /* Just got destroyed */
        nss_SetError(NSS_ERROR_INVALID_ARENA);
        return PR_FAILURE;
    }
    PR_Lock(arena->lock);

#ifdef ARENA_THREADMARK
    if ((PRThread *)NULL != arena->marking_thread) {
        if (PR_GetCurrentThread() != arena->marking_thread) {
            PR_Unlock(arena->lock);
            nss_SetError(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD);
            return PR_FAILURE;
        }
    }
#endif /* ARENA_THREADMARK */

    if (MARK_MAGIC != arenaMark->magic) {
        /* Just got released */
        PR_Unlock(arena->lock);
        nss_SetError(NSS_ERROR_INVALID_ARENA_MARK);
        return PR_FAILURE;
    }

    arenaMark->magic = 0;
    inner_mark = arenaMark->mark;

#ifdef ARENA_THREADMARK
    {
        nssArenaMark **pMark = &arena->first_mark;
        nssArenaMark *rest;
        nssArenaMark *last = (nssArenaMark *)NULL;

        /* Find this mark */
        while (*pMark != arenaMark) {
            last = *pMark;
            pMark = &(*pMark)->next;
        }

        /* Remember the pointer, then zero it */
        rest = (*pMark)->next;
        *pMark = (nssArenaMark *)NULL;

        arena->last_mark = last;

        /* Invalidate any later marks being implicitly released */
        for (; (nssArenaMark *)NULL != rest; rest = rest->next) {
            rest->magic = 0;
        }

        /* If we just got rid of the first mark, clear the thread ID */
        if ((nssArenaMark *)NULL == arena->first_mark) {
            arena->marking_thread = (PRThread *)NULL;
        }
    }
#endif /* ARENA_THREADMARK */

    if (release) {
#ifdef ARENA_DESTRUCTOR_LIST
        if ((struct arena_destructor_node *)NULL !=
            arenaMark->prev_destructor) {
            arenaMark->prev_destructor->next =
                (struct arena_destructor_node *)NULL;
        }
        arena->last_destructor = arenaMark->prev_destructor;

        /* Note that the arena is locked at this time */
        nss_arena_call_destructor_chain(arenaMark->next_destructor);
#endif /* ARENA_DESTRUCTOR_LIST */

        PL_ARENA_RELEASE(&arena->pool, inner_mark);
        /* No error return */
    }

    PR_Unlock(arena->lock);
    return PR_SUCCESS;
}

/*
 * nssArena_Release
 *
 * This routine invalidates and releases all memory allocated from
 * the specified arena after the point at which the specified mark
 * was obtained.  This routine returns a PRStatus value; if successful,
 * it will return PR_SUCCESS.  If unsuccessful, it will set an error
 * on the error stack and return PR_FAILURE.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *  NSS_ERROR_INVALID_ARENA_MARK
 *  NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD
 *
 * Return value:
 *  PR_SUCCESS
 *  PR_FAILURE
 */

NSS_IMPLEMENT PRStatus
nssArena_Release(NSSArena *arena, nssArenaMark *arenaMark)
{
    return nss_arena_unmark_release(arena, arenaMark, PR_TRUE);
}

/*
 * nssArena_Unmark
 *
 * This routine "commits" the indicated mark and any marks after
 * it, making them unreleasable.  Note that any earlier marks can
 * still be released, and such a release will invalidate these
 * later unmarked regions.  If an arena is to be safely shared by
 * more than one thread, all marks must be either released or
 * unmarked.  This routine returns a PRStatus value; if successful,
 * it will return PR_SUCCESS.  If unsuccessful, it will set an error
 * on the error stack and return PR_FAILURE.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *  NSS_ERROR_INVALID_ARENA_MARK
 *  NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD
 *
 * Return value:
 *  PR_SUCCESS
 *  PR_FAILURE
 */

NSS_IMPLEMENT PRStatus
nssArena_Unmark(NSSArena *arena, nssArenaMark *arenaMark)
{
    return nss_arena_unmark_release(arena, arenaMark, PR_FALSE);
}

/*
 * We prefix this header to all allocated blocks.  It is a multiple
 * of the alignment size.  Note that this usage of a header may make
 * purify spew bogus warnings about "potentially leaked blocks" of
 * memory; if that gets too annoying we can add in a pointer to the
 * header in the header itself.  There's not a lot of safety here;
 * maybe we should add a magic value?
 */
struct pointer_header {
    NSSArena *arena;
    PRUint32 size;
};

static void *
nss_zalloc_arena_locked(NSSArena *arena, PRUint32 size)
{
    void *p;
    void *rv;
    struct pointer_header *h;
    PRUint32 my_size = size + sizeof(struct pointer_header);
    PL_ARENA_ALLOCATE(p, &arena->pool, my_size);
    if ((void *)NULL == p) {
        nss_SetError(NSS_ERROR_NO_MEMORY);
        return (void *)NULL;
    }
    /*
     * Do this before we unlock.  This way if the user is using
     * an arena in one thread while destroying it in another, he'll
     * fault/FMR in his code, not ours.
     */
    h = (struct pointer_header *)p;
    h->arena = arena;
    h->size = size;
    rv = (void *)((char *)h + sizeof(struct pointer_header));
    (void)nsslibc_memset(rv, 0, size);
    return rv;
}

/*
 * NSS_ZAlloc
 *
 * This routine allocates and zeroes a section of memory of the
 * size, and returns to the caller a pointer to that memory.  If
 * the optional arena argument is non-null, the memory will be
 * obtained from that arena; otherwise, the memory will be obtained
 * from the heap.  This routine may return NULL upon error, in
 * which case it will have set an error upon the error stack.  The
 * value specified for size may be zero; in which case a valid
 * zero-length block of memory will be allocated.  This block may
 * be expanded by calling NSS_ZRealloc.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *  NSS_ERROR_NO_MEMORY
 *  NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD
 *
 * Return value:
 *  NULL upon error
 *  A pointer to the new segment of zeroed memory
 */

NSS_IMPLEMENT void *
NSS_ZAlloc(NSSArena *arenaOpt, PRUint32 size)
{
    return nss_ZAlloc(arenaOpt, size);
}

/*
 * nss_ZAlloc
 *
 * This routine allocates and zeroes a section of memory of the
 * size, and returns to the caller a pointer to that memory.  If
 * the optional arena argument is non-null, the memory will be
 * obtained from that arena; otherwise, the memory will be obtained
 * from the heap.  This routine may return NULL upon error, in
 * which case it will have set an error upon the error stack.  The
 * value specified for size may be zero; in which case a valid
 * zero-length block of memory will be allocated.  This block may
 * be expanded by calling nss_ZRealloc.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_ARENA
 *  NSS_ERROR_NO_MEMORY
 *  NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD
 *
 * Return value:
 *  NULL upon error
 *  A pointer to the new segment of zeroed memory
 */

NSS_IMPLEMENT void *
nss_ZAlloc(NSSArena *arenaOpt, PRUint32 size)
{
    struct pointer_header *h;
    PRUint32 my_size = size + sizeof(struct pointer_header);

    if (my_size < sizeof(struct pointer_header)) {
        /* Wrapped */
        nss_SetError(NSS_ERROR_NO_MEMORY);
        return (void *)NULL;
    }

    if ((NSSArena *)NULL == arenaOpt) {
        /* Heap allocation, no locking required. */
        h = (struct pointer_header *)PR_Calloc(1, my_size);
        if ((struct pointer_header *)NULL == h) {
            nss_SetError(NSS_ERROR_NO_MEMORY);
            return (void *)NULL;
        }

        h->arena = (NSSArena *)NULL;
        h->size = size;
        /* We used calloc: it's already zeroed */

        return (void *)((char *)h + sizeof(struct pointer_header));
    } else {
        void *rv;
/* Arena allocation */
#ifdef NSSDEBUG
        if (PR_SUCCESS != nssArena_verifyPointer(arenaOpt)) {
            return (void *)NULL;
        }
#endif /* NSSDEBUG */

        if ((PRLock *)NULL == arenaOpt->lock) {
            /* Just got destroyed */
            nss_SetError(NSS_ERROR_INVALID_ARENA);
            return (void *)NULL;
        }
        PR_Lock(arenaOpt->lock);

#ifdef ARENA_THREADMARK
        if ((PRThread *)NULL != arenaOpt->marking_thread) {
            if (PR_GetCurrentThread() != arenaOpt->marking_thread) {
                nss_SetError(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD);
                PR_Unlock(arenaOpt->lock);
                return (void *)NULL;
            }
        }
#endif /* ARENA_THREADMARK */

        rv = nss_zalloc_arena_locked(arenaOpt, size);

        PR_Unlock(arenaOpt->lock);
        return rv;
    }
    /*NOTREACHED*/
}

/*
 * NSS_ZFreeIf
 *
 * If the specified pointer is non-null, then the region of memory
 * to which it points -- which must have been allocated with
 * NSS_ZAlloc -- will be zeroed and released.  This routine
 * returns a PRStatus value; if successful, it will return PR_SUCCESS.
 * If unsuccessful, it will set an error on the error stack and return
 * PR_FAILURE.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_POINTER
 *
 * Return value:
 *  PR_SUCCESS
 *  PR_FAILURE
 */
NSS_IMPLEMENT PRStatus
NSS_ZFreeIf(void *pointer)
{
    return nss_ZFreeIf(pointer);
}

/*
 * nss_ZFreeIf
 *
 * If the specified pointer is non-null, then the region of memory
 * to which it points -- which must have been allocated with
 * nss_ZAlloc -- will be zeroed and released.  This routine
 * returns a PRStatus value; if successful, it will return PR_SUCCESS.
 * If unsuccessful, it will set an error on the error stack and return
 * PR_FAILURE.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_POINTER
 *
 * Return value:
 *  PR_SUCCESS
 *  PR_FAILURE
 */

NSS_IMPLEMENT PRStatus
nss_ZFreeIf(void *pointer)
{
    struct pointer_header *h;

    if ((void *)NULL == pointer) {
        return PR_SUCCESS;
    }

    h = (struct pointer_header *)((char *)pointer -
                                  sizeof(struct pointer_header));

    /* Check any magic here */

    if ((NSSArena *)NULL == h->arena) {
        /* Heap */
        (void)nsslibc_memset(pointer, 0, h->size);
        PR_Free(h);
        return PR_SUCCESS;
    } else {
/* Arena */
#ifdef NSSDEBUG
        if (PR_SUCCESS != nssArena_verifyPointer(h->arena)) {
            return PR_FAILURE;
        }
#endif /* NSSDEBUG */

        if ((PRLock *)NULL == h->arena->lock) {
            /* Just got destroyed.. so this pointer is invalid */
            nss_SetError(NSS_ERROR_INVALID_POINTER);
            return PR_FAILURE;
        }
        PR_Lock(h->arena->lock);

        (void)nsslibc_memset(pointer, 0, h->size);

        /* No way to "free" it within an NSPR arena. */

        PR_Unlock(h->arena->lock);
        return PR_SUCCESS;
    }
    /*NOTREACHED*/
}

/*
 * NSS_ZRealloc
 *
 * This routine reallocates a block of memory obtained by calling
 * nss_ZAlloc or nss_ZRealloc.  The portion of memory
 * between the new and old sizes -- which is either being newly
 * obtained or released -- is in either case zeroed.  This routine
 * may return NULL upon failure, in which case it will have placed
 * an error on the error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_POINTER
 *  NSS_ERROR_NO_MEMORY
 *  NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD
 *
 * Return value:
 *  NULL upon error
 *  A pointer to the replacement segment of memory
 */

NSS_EXTERN void *
NSS_ZRealloc(void *pointer, PRUint32 newSize)
{
    return nss_ZRealloc(pointer, newSize);
}

/*
 * nss_ZRealloc
 *
 * This routine reallocates a block of memory obtained by calling
 * nss_ZAlloc or nss_ZRealloc.  The portion of memory
 * between the new and old sizes -- which is either being newly
 * obtained or released -- is in either case zeroed.  This routine
 * may return NULL upon failure, in which case it will have placed
 * an error on the error stack.
 *
 * The error may be one of the following values:
 *  NSS_ERROR_INVALID_POINTER
 *  NSS_ERROR_NO_MEMORY
 *  NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD
 *
 * Return value:
 *  NULL upon error
 *  A pointer to the replacement segment of memory
 */

NSS_EXTERN void *
nss_ZRealloc(void *pointer, PRUint32 newSize)
{
    NSSArena *arena;
    struct pointer_header *h, *new_h;
    PRUint32 my_newSize = newSize + sizeof(struct pointer_header);
    void *rv;

    if (my_newSize < sizeof(struct pointer_header)) {
        /* Wrapped */
        nss_SetError(NSS_ERROR_NO_MEMORY);
        return (void *)NULL;
    }

    if ((void *)NULL == pointer) {
        nss_SetError(NSS_ERROR_INVALID_POINTER);
        return (void *)NULL;
    }

    h = (struct pointer_header *)((char *)pointer -
                                  sizeof(struct pointer_header));

    /* Check any magic here */

    if (newSize == h->size) {
        /* saves thrashing */
        return pointer;
    }

    arena = h->arena;
    if (!arena) {
        /* Heap */
        new_h = (struct pointer_header *)PR_Calloc(1, my_newSize);
        if ((struct pointer_header *)NULL == new_h) {
            nss_SetError(NSS_ERROR_NO_MEMORY);
            return (void *)NULL;
        }

        new_h->arena = (NSSArena *)NULL;
        new_h->size = newSize;
        rv = (void *)((char *)new_h + sizeof(struct pointer_header));

        if (newSize > h->size) {
            (void)nsslibc_memcpy(rv, pointer, h->size);
            (void)nsslibc_memset(&((char *)rv)[h->size], 0,
                                 (newSize - h->size));
        } else {
            (void)nsslibc_memcpy(rv, pointer, newSize);
        }

        (void)nsslibc_memset(pointer, 0, h->size);
        h->size = 0;
        PR_Free(h);

        return rv;
    } else {
        void *p;
/* Arena */
#ifdef NSSDEBUG
        if (PR_SUCCESS != nssArena_verifyPointer(arena)) {
            return (void *)NULL;
        }
#endif /* NSSDEBUG */

        if (!arena->lock) {
            /* Just got destroyed.. so this pointer is invalid */
            nss_SetError(NSS_ERROR_INVALID_POINTER);
            return (void *)NULL;
        }
        PR_Lock(arena->lock);

#ifdef ARENA_THREADMARK
        if (arena->marking_thread) {
            if (PR_GetCurrentThread() != arena->marking_thread) {
                PR_Unlock(arena->lock);
                nss_SetError(NSS_ERROR_ARENA_MARKED_BY_ANOTHER_THREAD);
                return (void *)NULL;
            }
        }
#endif /* ARENA_THREADMARK */

        if (newSize < h->size) {
            /*
             * We have no general way of returning memory to the arena
             * (mark/release doesn't work because things may have been
             * allocated after this object), so the memory is gone
             * anyway.  We might as well just return the same pointer to
             * the user, saying "yeah, uh-hunh, you can only use less of
             * it now."  We'll zero the leftover part, of course.  And
             * in fact we might as well *not* adjust h->size-- this way,
             * if the user reallocs back up to something not greater than
             * the original size, then voila, there's the memory!  This
             * way a thrash big/small/big/small doesn't burn up the arena.
             */
            char *extra = &((char *)pointer)[newSize];
            (void)nsslibc_memset(extra, 0, (h->size - newSize));
            PR_Unlock(arena->lock);
            return pointer;
        }

        PL_ARENA_ALLOCATE(p, &arena->pool, my_newSize);
        if ((void *)NULL == p) {
            PR_Unlock(arena->lock);
            nss_SetError(NSS_ERROR_NO_MEMORY);
            return (void *)NULL;
        }

        new_h = (struct pointer_header *)p;
        new_h->arena = arena;
        new_h->size = newSize;
        rv = (void *)((char *)new_h + sizeof(struct pointer_header));
        if (rv != pointer) {
            (void)nsslibc_memcpy(rv, pointer, h->size);
            (void)nsslibc_memset(pointer, 0, h->size);
        }
        (void)nsslibc_memset(&((char *)rv)[h->size], 0, (newSize - h->size));
        h->arena = (NSSArena *)NULL;
        h->size = 0;
        PR_Unlock(arena->lock);
        return rv;
    }
    /*NOTREACHED*/
}

PRStatus
nssArena_Shutdown(void)
{
    PRStatus rv = PR_SUCCESS;
#ifdef DEBUG
    rv = nssPointerTracker_finalize(&arena_pointer_tracker);
#endif
    return rv;
}
