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

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