/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

#include "primpl.h"
#include <ctype.h>
#include <string.h>

PRLogModuleInfo *_pr_clock_lm;
PRLogModuleInfo *_pr_cmon_lm;
PRLogModuleInfo *_pr_io_lm;
PRLogModuleInfo *_pr_cvar_lm;
PRLogModuleInfo *_pr_mon_lm;
PRLogModuleInfo *_pr_linker_lm;
PRLogModuleInfo *_pr_sched_lm;
PRLogModuleInfo *_pr_thread_lm;
PRLogModuleInfo *_pr_gc_lm;
PRLogModuleInfo *_pr_shm_lm;
PRLogModuleInfo *_pr_shma_lm;

PRFileDesc *_pr_stdin;
PRFileDesc *_pr_stdout;
PRFileDesc *_pr_stderr;

#if !defined(_PR_PTHREADS) && !defined(_PR_BTHREADS)

PRCList _pr_active_local_threadQ =
    PR_INIT_STATIC_CLIST(&_pr_active_local_threadQ);
PRCList _pr_active_global_threadQ =
    PR_INIT_STATIC_CLIST(&_pr_active_global_threadQ);

_MDLock  _pr_cpuLock;           /* lock for the CPU Q */
PRCList _pr_cpuQ = PR_INIT_STATIC_CLIST(&_pr_cpuQ);

PRUint32 _pr_utid;

PRInt32 _pr_userActive;
PRInt32 _pr_systemActive;
PRUintn _pr_maxPTDs;

#ifdef _PR_LOCAL_THREADS_ONLY

struct _PRCPU   *_pr_currentCPU;
PRThread    *_pr_currentThread;
PRThread    *_pr_lastThread;
PRInt32     _pr_intsOff;

#endif /* _PR_LOCAL_THREADS_ONLY */

/* Lock protecting all "termination" condition variables of all threads */
PRLock *_pr_terminationCVLock;

#endif /* !defined(_PR_PTHREADS) */

PRLock *_pr_sleeplock;  /* used in PR_Sleep(), classic and pthreads */

static void _PR_InitCallOnce(void);

PRBool _pr_initialized = PR_FALSE;


PR_IMPLEMENT(PRBool) PR_VersionCheck(const char *importedVersion)
{
    /*
    ** This is the secret handshake algorithm.
    **
    ** This release has a simple version compatibility
    ** check algorithm.  This release is not backward
    ** compatible with previous major releases.  It is
    ** not compatible with future major, minor, or
    ** patch releases.
    */
    int vmajor = 0, vminor = 0, vpatch = 0;
    const char *ptr = importedVersion;

    while (isdigit(*ptr)) {
        vmajor = 10 * vmajor + *ptr - '0';
        ptr++;
    }
    if (*ptr == '.') {
        ptr++;
        while (isdigit(*ptr)) {
            vminor = 10 * vminor + *ptr - '0';
            ptr++;
        }
        if (*ptr == '.') {
            ptr++;
            while (isdigit(*ptr)) {
                vpatch = 10 * vpatch + *ptr - '0';
                ptr++;
            }
        }
    }

    if (vmajor != PR_VMAJOR) {
        return PR_FALSE;
    }
    if (vmajor == PR_VMAJOR && vminor > PR_VMINOR) {
        return PR_FALSE;
    }
    if (vmajor == PR_VMAJOR && vminor == PR_VMINOR && vpatch > PR_VPATCH) {
        return PR_FALSE;
    }
    return PR_TRUE;
}  /* PR_VersionCheck */

PR_IMPLEMENT(const char*) PR_GetVersion(void)
{
    return PR_VERSION;
}

PR_IMPLEMENT(PRBool) PR_Initialized(void)
{
    return _pr_initialized;
}

PRInt32 _native_threads_only = 0;

#ifdef WINNT
static void _pr_SetNativeThreadsOnlyMode(void)
{
    HMODULE mainExe;
    PRBool *globalp;
    char *envp;

    mainExe = GetModuleHandle(NULL);
    PR_ASSERT(NULL != mainExe);
    globalp = (PRBool *) GetProcAddress(mainExe, "nspr_native_threads_only");
    if (globalp) {
        _native_threads_only = (*globalp != PR_FALSE);
    } else if (envp = getenv("NSPR_NATIVE_THREADS_ONLY")) {
        _native_threads_only = (atoi(envp) == 1);
    }
}
#endif

static void _PR_InitStuff(void)
{

    if (_pr_initialized) {
        return;
    }
    _pr_initialized = PR_TRUE;
#ifdef _PR_ZONE_ALLOCATOR
    _PR_InitZones();
#endif
#ifdef WINNT
    _pr_SetNativeThreadsOnlyMode();
#endif


    (void) PR_GetPageSize();

    _pr_clock_lm = PR_NewLogModule("clock");
    _pr_cmon_lm = PR_NewLogModule("cmon");
    _pr_io_lm = PR_NewLogModule("io");
    _pr_mon_lm = PR_NewLogModule("mon");
    _pr_linker_lm = PR_NewLogModule("linker");
    _pr_cvar_lm = PR_NewLogModule("cvar");
    _pr_sched_lm = PR_NewLogModule("sched");
    _pr_thread_lm = PR_NewLogModule("thread");
    _pr_gc_lm = PR_NewLogModule("gc");
    _pr_shm_lm = PR_NewLogModule("shm");
    _pr_shma_lm = PR_NewLogModule("shma");

    /* NOTE: These init's cannot depend on _PR_MD_CURRENT_THREAD() */
    _PR_MD_EARLY_INIT();

    _PR_InitLocks();
    _PR_InitAtomic();
    _PR_InitSegs();
    _PR_InitStacks();
    _PR_InitTPD();
    _PR_InitEnv();
    _PR_InitLayerCache();
    _PR_InitClock();

    _pr_sleeplock = PR_NewLock();
    PR_ASSERT(NULL != _pr_sleeplock);

    _PR_InitThreads(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);

#ifdef WIN16
    {
        PRInt32 top;   /* artificial top of stack, win16 */
        _pr_top_of_task_stack = (char *) &top;
    }
#endif

#ifndef _PR_GLOBAL_THREADS_ONLY
    _PR_InitCPUs();
#endif

    /*
     * XXX: call _PR_InitMem only on those platforms for which nspr implements
     *  malloc, for now.
     */
#ifdef _PR_OVERRIDE_MALLOC
    _PR_InitMem();
#endif

    _PR_InitCMon();
    _PR_InitIO();
    _PR_InitNet();
    _PR_InitTime();
    _PR_InitLog();
    _PR_InitLinker();
    _PR_InitCallOnce();
    _PR_InitDtoa();
    _PR_InitMW();
    _PR_InitRWLocks();

    nspr_InitializePRErrorTable();

    _PR_MD_FINAL_INIT();
}

void _PR_ImplicitInitialization(void)
{
    _PR_InitStuff();

    /* Enable interrupts */
#if !defined(_PR_PTHREADS) && !defined(_PR_GLOBAL_THREADS_ONLY)
    _PR_MD_START_INTERRUPTS();
#endif

}

PR_IMPLEMENT(void) PR_DisableClockInterrupts(void)
{
#if !defined(_PR_PTHREADS) && !defined(_PR_BTHREADS)
    if (!_pr_initialized) {
        _PR_InitStuff();
    } else {
        _PR_MD_DISABLE_CLOCK_INTERRUPTS();
    }
#endif
}

PR_IMPLEMENT(void) PR_EnableClockInterrupts(void)
{
#if !defined(_PR_PTHREADS) && !defined(_PR_BTHREADS)
    if (!_pr_initialized) {
        _PR_InitStuff();
    }
    _PR_MD_ENABLE_CLOCK_INTERRUPTS();
#endif
}

PR_IMPLEMENT(void) PR_BlockClockInterrupts(void)
{
#if !defined(_PR_PTHREADS) && !defined(_PR_BTHREADS)
    _PR_MD_BLOCK_CLOCK_INTERRUPTS();
#endif
}

PR_IMPLEMENT(void) PR_UnblockClockInterrupts(void)
{
#if !defined(_PR_PTHREADS) && !defined(_PR_BTHREADS)
    _PR_MD_UNBLOCK_CLOCK_INTERRUPTS();
#endif
}

PR_IMPLEMENT(void) PR_Init(
    PRThreadType type, PRThreadPriority priority, PRUintn maxPTDs)
{
    _PR_ImplicitInitialization();
}

PR_IMPLEMENT(PRIntn) PR_Initialize(
    PRPrimordialFn prmain, PRIntn argc, char **argv, PRUintn maxPTDs)
{
    PRIntn rv;
    _PR_ImplicitInitialization();
    rv = prmain(argc, argv);
    PR_Cleanup();
    return rv;
}  /* PR_Initialize */

/*
 *-----------------------------------------------------------------------
 *
 * _PR_CleanupBeforeExit --
 *
 *   Perform the cleanup work before exiting the process.
 *   We first do the cleanup generic to all platforms.  Then
 *   we call _PR_MD_CLEANUP_BEFORE_EXIT(), where platform-dependent
 *   cleanup is done.  This function is used by PR_Cleanup().
 *
 * See also: PR_Cleanup().
 *
 *-----------------------------------------------------------------------
 */
#if defined(_PR_PTHREADS) || defined(_PR_BTHREADS)
/* see ptthread.c */
#else
static void
_PR_CleanupBeforeExit(void)
{
    /*
    Do not make any calls here other than to destroy resources.  For example,
    do not make any calls that eventually may end up in PR_Lock.  Because the
    thread is destroyed, can not access current thread any more.
    */
    _PR_CleanupTPD();
    if (_pr_terminationCVLock)
        /*
         * In light of the comment above, this looks real suspicious.
         * I'd go so far as to say it's just a problem waiting to happen.
         */
    {
        PR_DestroyLock(_pr_terminationCVLock);
    }

    _PR_MD_CLEANUP_BEFORE_EXIT();
}
#endif /* defined(_PR_PTHREADS) */

/*
 *----------------------------------------------------------------------
 *
 * PR_Cleanup --
 *
 *   Perform a graceful shutdown of the NSPR runtime.  PR_Cleanup() may
 *   only be called from the primordial thread, typically at the
 *   end of the main() function.  It returns when it has completed
 *   its platform-dependent duty and the process must not make any other
 *   NSPR library calls prior to exiting from main().
 *
 *   PR_Cleanup() first blocks the primordial thread until all the
 *   other user (non-system) threads, if any, have terminated.
 *   Then it performs cleanup in preparation for exiting the process.
 *   PR_Cleanup() does not exit the primordial thread (which would
 *   in turn exit the process).
 *
 *   PR_Cleanup() only responds when it is called by the primordial
 *   thread. Calls by any other thread are silently ignored.
 *
 * See also: PR_ExitProcess()
 *
 *----------------------------------------------------------------------
 */
#if defined(_PR_PTHREADS) || defined(_PR_BTHREADS)
/* see ptthread.c */
#else

PR_IMPLEMENT(PRStatus) PR_Cleanup()
{
    PRThread *me = PR_GetCurrentThread();
    PR_ASSERT((NULL != me) && (me->flags & _PR_PRIMORDIAL));
    if ((NULL != me) && (me->flags & _PR_PRIMORDIAL))
    {
        PR_LOG(_pr_thread_lm, PR_LOG_MIN, ("PR_Cleanup: shutting down NSPR"));

        /*
         * No more recycling of threads
         */
        _pr_recycleThreads = 0;

        /*
         * Wait for all other user (non-system/daemon) threads
         * to terminate.
         */
        PR_Lock(_pr_activeLock);
        while (_pr_userActive > _pr_primordialExitCount) {
            PR_WaitCondVar(_pr_primordialExitCVar, PR_INTERVAL_NO_TIMEOUT);
        }
        if (me->flags & _PR_SYSTEM) {
            _pr_systemActive--;
        } else {
            _pr_userActive--;
        }
        PR_Unlock(_pr_activeLock);

        _PR_MD_EARLY_CLEANUP();

        _PR_CleanupMW();
        _PR_CleanupTime();
        _PR_CleanupDtoa();
        _PR_CleanupCallOnce();
        _PR_ShutdownLinker();
        _PR_CleanupNet();
        _PR_CleanupIO();
        /* Release the primordial thread's private data, etc. */
        _PR_CleanupThread(me);

        _PR_MD_STOP_INTERRUPTS();

        PR_LOG(_pr_thread_lm, PR_LOG_MIN,
               ("PR_Cleanup: clean up before destroying thread"));
        _PR_LogCleanup();

        /*
         * This part should look like the end of _PR_NativeRunThread
         * and _PR_UserRunThread.
         */
        if (_PR_IS_NATIVE_THREAD(me)) {
            _PR_MD_EXIT_THREAD(me);
            _PR_NativeDestroyThread(me);
        } else {
            _PR_UserDestroyThread(me);
            PR_DELETE(me->stack);
            PR_DELETE(me);
        }

        /*
         * XXX: We are freeing the heap memory here so that Purify won't
         * complain, but we should also free other kinds of resources
         * that are allocated by the _PR_InitXXX() functions.
         * Ideally, for each _PR_InitXXX(), there should be a corresponding
         * _PR_XXXCleanup() that we can call here.
         */
#ifdef WINNT
        _PR_CleanupCPUs();
#endif
        _PR_CleanupThreads();
        _PR_CleanupCMon();
        PR_DestroyLock(_pr_sleeplock);
        _pr_sleeplock = NULL;
        _PR_CleanupLayerCache();
        _PR_CleanupEnv();
        _PR_CleanupStacks();
        _PR_CleanupBeforeExit();
        _pr_initialized = PR_FALSE;
        return PR_SUCCESS;
    }
    return PR_FAILURE;
}
#endif /* defined(_PR_PTHREADS) */

/*
 *------------------------------------------------------------------------
 * PR_ProcessExit --
 *
 *   Cause an immediate, nongraceful, forced termination of the process.
 *   It takes a PRIntn argument, which is the exit status code of the
 *   process.
 *
 * See also: PR_Cleanup()
 *
 *------------------------------------------------------------------------
 */

#if defined(_PR_PTHREADS) || defined(_PR_BTHREADS)
/* see ptthread.c */
#else
PR_IMPLEMENT(void) PR_ProcessExit(PRIntn status)
{
    _PR_MD_EXIT(status);
}

#endif /* defined(_PR_PTHREADS) */

PR_IMPLEMENT(PRProcessAttr *)
PR_NewProcessAttr(void)
{
    PRProcessAttr *attr;

    attr = PR_NEWZAP(PRProcessAttr);
    if (!attr) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    }
    return attr;
}

PR_IMPLEMENT(void)
PR_ResetProcessAttr(PRProcessAttr *attr)
{
    PR_FREEIF(attr->currentDirectory);
    PR_FREEIF(attr->fdInheritBuffer);
    memset(attr, 0, sizeof(*attr));
}

PR_IMPLEMENT(void)
PR_DestroyProcessAttr(PRProcessAttr *attr)
{
    PR_FREEIF(attr->currentDirectory);
    PR_FREEIF(attr->fdInheritBuffer);
    PR_DELETE(attr);
}

PR_IMPLEMENT(void)
PR_ProcessAttrSetStdioRedirect(
    PRProcessAttr *attr,
    PRSpecialFD stdioFd,
    PRFileDesc *redirectFd)
{
    switch (stdioFd) {
        case PR_StandardInput:
            attr->stdinFd = redirectFd;
            break;
        case PR_StandardOutput:
            attr->stdoutFd = redirectFd;
            break;
        case PR_StandardError:
            attr->stderrFd = redirectFd;
            break;
        default:
            PR_ASSERT(0);
    }
}

/*
 * OBSOLETE
 */
PR_IMPLEMENT(void)
PR_SetStdioRedirect(
    PRProcessAttr *attr,
    PRSpecialFD stdioFd,
    PRFileDesc *redirectFd)
{
#if defined(DEBUG)
    static PRBool warn = PR_TRUE;
    if (warn) {
        warn = _PR_Obsolete("PR_SetStdioRedirect()",
                            "PR_ProcessAttrSetStdioRedirect()");
    }
#endif
    PR_ProcessAttrSetStdioRedirect(attr, stdioFd, redirectFd);
}

PR_IMPLEMENT(PRStatus)
PR_ProcessAttrSetCurrentDirectory(
    PRProcessAttr *attr,
    const char *dir)
{
    PR_FREEIF(attr->currentDirectory);
    attr->currentDirectory = (char *) PR_MALLOC(strlen(dir) + 1);
    if (!attr->currentDirectory) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
        return PR_FAILURE;
    }
    strcpy(attr->currentDirectory, dir);
    return PR_SUCCESS;
}

PR_IMPLEMENT(PRStatus)
PR_ProcessAttrSetInheritableFD(
    PRProcessAttr *attr,
    PRFileDesc *fd,
    const char *name)
{
    /* We malloc the fd inherit buffer in multiples of this number. */
#define FD_INHERIT_BUFFER_INCR 128
    /* The length of "NSPR_INHERIT_FDS=" */
#define NSPR_INHERIT_FDS_STRLEN 17
    /* The length of osfd (PROsfd) printed in hexadecimal with 0x prefix */
#ifdef _WIN64
#define OSFD_STRLEN 18
#else
#define OSFD_STRLEN 10
#endif
    /* The length of fd type (PRDescType) printed in decimal */
#define FD_TYPE_STRLEN 1
    PRSize newSize;
    int remainder;
    char *newBuffer;
    int nwritten;
    char *cur;
    int freeSize;

    if (fd->identity != PR_NSPR_IO_LAYER) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return PR_FAILURE;
    }
    if (fd->secret->inheritable == _PR_TRI_UNKNOWN) {
        _PR_MD_QUERY_FD_INHERITABLE(fd);
    }
    if (fd->secret->inheritable != _PR_TRI_TRUE) {
        PR_SetError(PR_NO_ACCESS_RIGHTS_ERROR, 0);
        return PR_FAILURE;
    }

    /*
     * We also need to account for the : separators and the
     * terminating null byte.
     */
    if (NULL == attr->fdInheritBuffer) {
        /* The first time, we print "NSPR_INHERIT_FDS=<name>:<type>:<val>" */
        newSize = NSPR_INHERIT_FDS_STRLEN + strlen(name)
                  + FD_TYPE_STRLEN + OSFD_STRLEN + 2 + 1;
    } else {
        /* At other times, we print ":<name>:<type>:<val>" */
        newSize = attr->fdInheritBufferUsed + strlen(name)
                  + FD_TYPE_STRLEN + OSFD_STRLEN + 3 + 1;
    }
    if (newSize > attr->fdInheritBufferSize) {
        /* Make newSize a multiple of FD_INHERIT_BUFFER_INCR */
        remainder = newSize % FD_INHERIT_BUFFER_INCR;
        if (remainder != 0) {
            newSize += (FD_INHERIT_BUFFER_INCR - remainder);
        }
        if (NULL == attr->fdInheritBuffer) {
            newBuffer = (char *) PR_MALLOC(newSize);
        } else {
            newBuffer = (char *) PR_REALLOC(attr->fdInheritBuffer, newSize);
        }
        if (NULL == newBuffer) {
            PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
            return PR_FAILURE;
        }
        attr->fdInheritBuffer = newBuffer;
        attr->fdInheritBufferSize = newSize;
    }
    cur = attr->fdInheritBuffer + attr->fdInheritBufferUsed;
    freeSize = attr->fdInheritBufferSize - attr->fdInheritBufferUsed;
    if (0 == attr->fdInheritBufferUsed) {
        nwritten = PR_snprintf(cur, freeSize,
                               "NSPR_INHERIT_FDS=%s:%d:0x%" PR_PRIxOSFD,
                               name, (PRIntn)fd->methods->file_type, fd->secret->md.osfd);
    } else {
        nwritten = PR_snprintf(cur, freeSize, ":%s:%d:0x%" PR_PRIxOSFD,
                               name, (PRIntn)fd->methods->file_type, fd->secret->md.osfd);
    }
    attr->fdInheritBufferUsed += nwritten;
    return PR_SUCCESS;
}

PR_IMPLEMENT(PRFileDesc *) PR_GetInheritedFD(
    const char *name)
{
    PRFileDesc *fd;
    const char *envVar;
    const char *ptr;
    int len = strlen(name);
    PROsfd osfd;
    int nColons;
    PRIntn fileType;

    envVar = PR_GetEnv("NSPR_INHERIT_FDS");
    if (NULL == envVar || '\0' == envVar[0]) {
        PR_SetError(PR_UNKNOWN_ERROR, 0);
        return NULL;
    }

    ptr = envVar;
    while (1) {
        if ((ptr[len] == ':') && (strncmp(ptr, name, len) == 0)) {
            ptr += len + 1;
            if (PR_sscanf(ptr, "%d:0x%" PR_SCNxOSFD, &fileType, &osfd) != 2) {
                PR_SetError(PR_UNKNOWN_ERROR, 0);
                return NULL;
            }
            switch ((PRDescType)fileType) {
                case PR_DESC_FILE:
                    fd = PR_ImportFile(osfd);
                    break;
                case PR_DESC_PIPE:
                    fd = PR_ImportPipe(osfd);
                    break;
                case PR_DESC_SOCKET_TCP:
                    fd = PR_ImportTCPSocket(osfd);
                    break;
                case PR_DESC_SOCKET_UDP:
                    fd = PR_ImportUDPSocket(osfd);
                    break;
                default:
                    PR_ASSERT(0);
                    PR_SetError(PR_UNKNOWN_ERROR, 0);
                    fd = NULL;
                    break;
            }
            if (fd) {
                /*
                 * An inherited FD is inheritable by default.
                 * The child process needs to call PR_SetFDInheritable
                 * to make it non-inheritable if so desired.
                 */
                fd->secret->inheritable = _PR_TRI_TRUE;
            }
            return fd;
        }
        /* Skip three colons */
        nColons = 0;
        while (*ptr) {
            if (*ptr == ':') {
                if (++nColons == 3) {
                    break;
                }
            }
            ptr++;
        }
        if (*ptr == '\0') {
            PR_SetError(PR_UNKNOWN_ERROR, 0);
            return NULL;
        }
        ptr++;
    }
}

PR_IMPLEMENT(PRProcess*) PR_CreateProcess(
    const char *path,
    char *const *argv,
    char *const *envp,
    const PRProcessAttr *attr)
{
    return _PR_MD_CREATE_PROCESS(path, argv, envp, attr);
}  /* PR_CreateProcess */

PR_IMPLEMENT(PRStatus) PR_CreateProcessDetached(
    const char *path,
    char *const *argv,
    char *const *envp,
    const PRProcessAttr *attr)
{
    PRProcess *process;
    PRStatus rv;

    process = PR_CreateProcess(path, argv, envp, attr);
    if (NULL == process) {
        return PR_FAILURE;
    }
    rv = PR_DetachProcess(process);
    PR_ASSERT(PR_SUCCESS == rv);
    if (rv == PR_FAILURE) {
        PR_DELETE(process);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}

PR_IMPLEMENT(PRStatus) PR_DetachProcess(PRProcess *process)
{
    return _PR_MD_DETACH_PROCESS(process);
}

PR_IMPLEMENT(PRStatus) PR_WaitProcess(PRProcess *process, PRInt32 *exitCode)
{
    return _PR_MD_WAIT_PROCESS(process, exitCode);
}  /* PR_WaitProcess */

PR_IMPLEMENT(PRStatus) PR_KillProcess(PRProcess *process)
{
    return _PR_MD_KILL_PROCESS(process);
}

/*
 ********************************************************************
 *
 * Module initialization
 *
 ********************************************************************
 */

static struct {
    PRLock *ml;
    PRCondVar *cv;
} mod_init;

static void _PR_InitCallOnce(void) {
    mod_init.ml = PR_NewLock();
    PR_ASSERT(NULL != mod_init.ml);
    mod_init.cv = PR_NewCondVar(mod_init.ml);
    PR_ASSERT(NULL != mod_init.cv);
}

void _PR_CleanupCallOnce()
{
    PR_DestroyLock(mod_init.ml);
    mod_init.ml = NULL;
    PR_DestroyCondVar(mod_init.cv);
    mod_init.cv = NULL;
}

PR_IMPLEMENT(PRStatus) PR_CallOnce(
    PRCallOnceType *once,
    PRCallOnceFN    func)
{
    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    PR_Lock(mod_init.ml);
    PRIntn initialized = once->initialized;
    PRStatus status = once->status;
    PR_Unlock(mod_init.ml);
    if (!initialized) {
        if (PR_ATOMIC_SET(&once->inProgress, 1) == 0) {
            status = (*func)();
            PR_Lock(mod_init.ml);
            once->status = status;
            once->initialized = 1;
            PR_NotifyAllCondVar(mod_init.cv);
            PR_Unlock(mod_init.ml);
        } else {
            PR_Lock(mod_init.ml);
            while (!once->initialized) {
                PR_WaitCondVar(mod_init.cv, PR_INTERVAL_NO_TIMEOUT);
            }
            status = once->status;
            PR_Unlock(mod_init.ml);
            if (PR_SUCCESS != status) {
                PR_SetError(PR_CALL_ONCE_ERROR, 0);
            }
        }
        return status;
    }
    if (PR_SUCCESS != status) {
        PR_SetError(PR_CALL_ONCE_ERROR, 0);
    }
    return status;
}

PR_IMPLEMENT(PRStatus) PR_CallOnceWithArg(
    PRCallOnceType      *once,
    PRCallOnceWithArgFN  func,
    void                *arg)
{
    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    PR_Lock(mod_init.ml);
    PRIntn initialized = once->initialized;
    PRStatus status = once->status;
    PR_Unlock(mod_init.ml);
    if (!initialized) {
        if (PR_ATOMIC_SET(&once->inProgress, 1) == 0) {
            status = (*func)(arg);
            PR_Lock(mod_init.ml);
            once->status = status;
            once->initialized = 1;
            PR_NotifyAllCondVar(mod_init.cv);
            PR_Unlock(mod_init.ml);
        } else {
            PR_Lock(mod_init.ml);
            while (!once->initialized) {
                PR_WaitCondVar(mod_init.cv, PR_INTERVAL_NO_TIMEOUT);
            }
            status = once->status;
            PR_Unlock(mod_init.ml);
            if (PR_SUCCESS != status) {
                PR_SetError(PR_CALL_ONCE_ERROR, 0);
            }
        }
        return status;
    }
    if (PR_SUCCESS != status) {
        PR_SetError(PR_CALL_ONCE_ERROR, 0);
    }
    return status;
}

PRBool _PR_Obsolete(const char *obsolete, const char *preferred)
{
#if defined(DEBUG)
    PR_fprintf(
        PR_STDERR, "'%s' is obsolete. Use '%s' instead.\n",
        obsolete, (NULL == preferred) ? "something else" : preferred);
#endif
    return PR_FALSE;
}  /* _PR_Obsolete */

/* prinit.c */


