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

/*
 * ntmisc.c
 *
 */

#include "primpl.h"
#include <math.h> /* for fabs() */
#include <windows.h>

char* _PR_MD_GET_ENV(const char* name) { return getenv(name); }

/*
** _PR_MD_PUT_ENV() -- add or change environment variable
**
**
*/
PRIntn _PR_MD_PUT_ENV(const char* name) { return (putenv(name)); }

/*
 **************************************************************************
 **************************************************************************
 **
 **     Date and time routines
 **
 **************************************************************************
 **************************************************************************
 */

/*
 * The NSPR epoch (00:00:00 1 Jan 1970 UTC) in FILETIME.
 * We store the value in a PRTime variable for convenience.
 */
#ifdef __GNUC__
const PRTime _pr_filetime_offset = 116444736000000000LL;
const PRTime _pr_filetime_divisor = 10LL;
#else
const PRTime _pr_filetime_offset = 116444736000000000i64;
const PRTime _pr_filetime_divisor = 10i64;
#endif

#ifdef WINCE

#  define FILETIME_TO_INT64(ft) \
    (((PRInt64)ft.dwHighDateTime) << 32 | (PRInt64)ft.dwLowDateTime)

static void LowResTime(LPFILETIME lpft) { GetCurrentFT(lpft); }

typedef struct CalibrationData {
  long double freq;         /* The performance counter frequency */
  long double offset;       /* The low res 'epoch' */
  long double timer_offset; /* The high res 'epoch' */

  /* The last high res time that we returned since recalibrating */
  PRInt64 last;

  PRBool calibrated;

  CRITICAL_SECTION data_lock;
  CRITICAL_SECTION calibration_lock;
  PRInt64 granularity;
} CalibrationData;

static CalibrationData calibration;

typedef void (*GetSystemTimeAsFileTimeFcn)(LPFILETIME);
static GetSystemTimeAsFileTimeFcn ce6_GetSystemTimeAsFileTime = NULL;

static void NowCalibrate(void) {
  FILETIME ft, ftStart;
  LARGE_INTEGER liFreq, now;

  if (calibration.freq == 0.0) {
    if (!QueryPerformanceFrequency(&liFreq)) {
      /* High-performance timer is unavailable */
      calibration.freq = -1.0;
    } else {
      calibration.freq = (long double)liFreq.QuadPart;
    }
  }
  if (calibration.freq > 0.0) {
    PRInt64 calibrationDelta = 0;
    /*
     * By wrapping a timeBegin/EndPeriod pair of calls around this loop,
     * the loop seems to take much less time (1 ms vs 15ms) on Vista.
     */
    timeBeginPeriod(1);
    LowResTime(&ftStart);
    do {
      LowResTime(&ft);
    } while (memcmp(&ftStart, &ft, sizeof(ft)) == 0);
    timeEndPeriod(1);

    calibration.granularity =
        (FILETIME_TO_INT64(ft) - FILETIME_TO_INT64(ftStart)) / 10;

    QueryPerformanceCounter(&now);

    calibration.offset = (long double)FILETIME_TO_INT64(ft);
    calibration.timer_offset = (long double)now.QuadPart;
    /*
     * The windows epoch is around 1600. The unix epoch is around 1970.
     * _pr_filetime_offset is the difference (in windows time units which
     * are 10 times more highres than the JS time unit)
     */
    calibration.offset -= _pr_filetime_offset;
    calibration.offset *= 0.1;
    calibration.last = 0;

    calibration.calibrated = PR_TRUE;
  }
}

#  define CALIBRATIONLOCK_SPINCOUNT 0
#  define DATALOCK_SPINCOUNT 4096
#  define LASTLOCK_SPINCOUNT 4096

void _MD_InitTime(void) {
  /* try for CE6 GetSystemTimeAsFileTime first */
  HANDLE h = GetModuleHandleW(L"coredll.dll");
  ce6_GetSystemTimeAsFileTime =
      (GetSystemTimeAsFileTimeFcn)GetProcAddressA(h, "GetSystemTimeAsFileTime");

  /* otherwise go the slow route */
  if (ce6_GetSystemTimeAsFileTime == NULL) {
    memset(&calibration, 0, sizeof(calibration));
    NowCalibrate();
    InitializeCriticalSection(&calibration.calibration_lock);
    InitializeCriticalSection(&calibration.data_lock);
  }
}

void _MD_CleanupTime(void) {
  if (ce6_GetSystemTimeAsFileTime == NULL) {
    DeleteCriticalSection(&calibration.calibration_lock);
    DeleteCriticalSection(&calibration.data_lock);
  }
}

#  define MUTEX_SETSPINCOUNT(m, c)

/*
 *-----------------------------------------------------------------------
 *
 * PR_Now --
 *
 *     Returns the current time in microseconds since the epoch.
 *     The epoch is midnight January 1, 1970 GMT.
 *     The implementation is machine dependent.  This is the
 *     implementation for Windows.
 *     Cf. time_t time(time_t *tp)
 *
 *-----------------------------------------------------------------------
 */

PR_IMPLEMENT(PRTime)
PR_Now(void) {
  long double lowresTime, highresTimerValue;
  FILETIME ft;
  LARGE_INTEGER now;
  PRBool calibrated = PR_FALSE;
  PRBool needsCalibration = PR_FALSE;
  PRInt64 returnedTime;
  long double cachedOffset = 0.0;

  if (ce6_GetSystemTimeAsFileTime) {
    union {
      FILETIME ft;
      PRTime prt;
    } currentTime;

    PR_ASSERT(sizeof(FILETIME) == sizeof(PRTime));

    ce6_GetSystemTimeAsFileTime(&currentTime.ft);

    /* written this way on purpose, since the second term becomes
     * a constant, and the entire expression is faster to execute.
     */
    return currentTime.prt / _pr_filetime_divisor -
           _pr_filetime_offset / _pr_filetime_divisor;
  }

  do {
    if (!calibration.calibrated || needsCalibration) {
      EnterCriticalSection(&calibration.calibration_lock);
      EnterCriticalSection(&calibration.data_lock);

      /* Recalibrate only if no one else did before us */
      if (calibration.offset == cachedOffset) {
        /*
         * Since calibration can take a while, make any other
         * threads immediately wait
         */
        MUTEX_SETSPINCOUNT(&calibration.data_lock, 0);

        NowCalibrate();

        calibrated = PR_TRUE;

        /* Restore spin count */
        MUTEX_SETSPINCOUNT(&calibration.data_lock, DATALOCK_SPINCOUNT);
      }
      LeaveCriticalSection(&calibration.data_lock);
      LeaveCriticalSection(&calibration.calibration_lock);
    }

    /* Calculate a low resolution time */
    LowResTime(&ft);
    lowresTime =
        ((long double)(FILETIME_TO_INT64(ft) - _pr_filetime_offset)) * 0.1;

    if (calibration.freq > 0.0) {
      long double highresTime, diff;
      DWORD timeAdjustment, timeIncrement;
      BOOL timeAdjustmentDisabled;

      /* Default to 15.625 ms if the syscall fails */
      long double skewThreshold = 15625.25;

      /* Grab high resolution time */
      QueryPerformanceCounter(&now);
      highresTimerValue = (long double)now.QuadPart;

      EnterCriticalSection(&calibration.data_lock);
      highresTime = calibration.offset +
                    1000000L * (highresTimerValue - calibration.timer_offset) /
                        calibration.freq;
      cachedOffset = calibration.offset;

      /*
       * On some dual processor/core systems, we might get an earlier
       * time so we cache the last time that we returned.
       */
      calibration.last = PR_MAX(calibration.last, (PRInt64)highresTime);
      returnedTime = calibration.last;
      LeaveCriticalSection(&calibration.data_lock);

      /* Get an estimate of clock ticks per second from our own test */
      skewThreshold = calibration.granularity;
      /* Check for clock skew */
      diff = lowresTime - highresTime;

      /*
       * For some reason that I have not determined, the skew can be
       * up to twice a kernel tick. This does not seem to happen by
       * itself, but I have only seen it triggered by another program
       * doing some kind of file I/O. The symptoms are a negative diff
       * followed by an equally large positive diff.
       */
      if (fabs(diff) > 2 * skewThreshold) {
        if (calibrated) {
          /*
           * If we already calibrated once this instance, and the
           * clock is still skewed, then either the processor(s) are
           * wildly changing clockspeed or the system is so busy that
           * we get switched out for long periods of time. In either
           * case, it would be infeasible to make use of high
           * resolution results for anything, so let's resort to old
           * behavior for this call. It's possible that in the
           * future, the user will want the high resolution timer, so
           * we don't disable it entirely.
           */
          returnedTime = (PRInt64)lowresTime;
          needsCalibration = PR_FALSE;
        } else {
          /*
           * It is possible that when we recalibrate, we will return
           * a value less than what we have returned before; this is
           * unavoidable. We cannot tell the different between a
           * faulty QueryPerformanceCounter implementation and user
           * changes to the operating system time. Since we must
           * respect user changes to the operating system time, we
           * cannot maintain the invariant that Date.now() never
           * decreases; the old implementation has this behavior as
           * well.
           */
          needsCalibration = PR_TRUE;
        }
      } else {
        /* No detectable clock skew */
        returnedTime = (PRInt64)highresTime;
        needsCalibration = PR_FALSE;
      }
    } else {
      /* No high resolution timer is available, so fall back */
      returnedTime = (PRInt64)lowresTime;
    }
  } while (needsCalibration);

  return returnedTime;
}

#else

PR_IMPLEMENT(PRTime)
PR_Now(void) {
  PRTime prt;
  FILETIME ft;
  SYSTEMTIME st;

  GetSystemTime(&st);
  SystemTimeToFileTime(&st, &ft);
  _PR_FileTimeToPRTime(&ft, &prt);
  return prt;
}

#endif

/*
 ***********************************************************************
 ***********************************************************************
 *
 * Process creation routines
 *
 ***********************************************************************
 ***********************************************************************
 */

/*
 * Assemble the command line by concatenating the argv array.
 * On success, this function returns 0 and the resulting command
 * line is returned in *cmdLine.  On failure, it returns -1.
 */
static int assembleCmdLine(char* const* argv, char** cmdLine) {
  char* const* arg;
  char *p, *q;
  size_t cmdLineSize;
  int numBackslashes;
  int i;
  int argNeedQuotes;

  /*
   * Find out how large the command line buffer should be.
   */
  cmdLineSize = 0;
  for (arg = argv; *arg; arg++) {
    /*
     * \ and " need to be escaped by a \.  In the worst case,
     * every character is a \ or ", so the string of length
     * may double.  If we quote an argument, that needs two ".
     * Finally, we need a space between arguments, and
     * a null byte at the end of command line.
     */
    cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */
                   + 2              /* we quote every argument */
                   + 1;             /* space in between, or final null */
  }
  p = *cmdLine = PR_MALLOC((PRUint32)cmdLineSize);
  if (p == NULL) {
    return -1;
  }

  for (arg = argv; *arg; arg++) {
    /* Add a space to separates the arguments */
    if (arg != argv) {
      *p++ = ' ';
    }
    q = *arg;
    numBackslashes = 0;
    argNeedQuotes = 0;

    /*
     * If the argument is empty or contains white space, it needs to
     * be quoted.
     */
    if (**arg == '\0' || strpbrk(*arg, " \f\n\r\t\v")) {
      argNeedQuotes = 1;
    }

    if (argNeedQuotes) {
      *p++ = '"';
    }
    while (*q) {
      if (*q == '\\') {
        numBackslashes++;
        q++;
      } else if (*q == '"') {
        if (numBackslashes) {
          /*
           * Double the backslashes since they are followed
           * by a quote
           */
          for (i = 0; i < 2 * numBackslashes; i++) {
            *p++ = '\\';
          }
          numBackslashes = 0;
        }
        /* To escape the quote */
        *p++ = '\\';
        *p++ = *q++;
      } else {
        if (numBackslashes) {
          /*
           * Backslashes are not followed by a quote, so
           * don't need to double the backslashes.
           */
          for (i = 0; i < numBackslashes; i++) {
            *p++ = '\\';
          }
          numBackslashes = 0;
        }
        *p++ = *q++;
      }
    }

    /* Now we are at the end of this argument */
    if (numBackslashes) {
      /*
       * Double the backslashes if we have a quote string
       * delimiter at the end.
       */
      if (argNeedQuotes) {
        numBackslashes *= 2;
      }
      for (i = 0; i < numBackslashes; i++) {
        *p++ = '\\';
      }
    }
    if (argNeedQuotes) {
      *p++ = '"';
    }
  }

  *p = '\0';
  return 0;
}

/*
 * Assemble the environment block by concatenating the envp array
 * (preserving the terminating null byte in each array element)
 * and adding a null byte at the end.
 *
 * Returns 0 on success.  The resulting environment block is returned
 * in *envBlock.  Note that if envp is NULL, a NULL pointer is returned
 * in *envBlock.  Returns -1 on failure.
 */
static int assembleEnvBlock(char** envp, char** envBlock) {
  char* p;
  char* q;
  char** env;
  char* curEnv;
  char *cwdStart, *cwdEnd;
  size_t envBlockSize;

  if (envp == NULL) {
    *envBlock = NULL;
    return 0;
  }

#ifdef WINCE
  {
    PRUnichar* wideCurEnv = mozce_GetEnvString();
    int len =
        WideCharToMultiByte(CP_ACP, 0, wideCurEnv, -1, NULL, 0, NULL, NULL);
    curEnv = (char*)PR_MALLOC(len * sizeof(char));
    WideCharToMultiByte(CP_ACP, 0, wideCurEnv, -1, curEnv, len, NULL, NULL);
    free(wideCurEnv);
  }
#else
  curEnv = GetEnvironmentStrings();
#endif

  cwdStart = curEnv;
  while (*cwdStart) {
    if (cwdStart[0] == '=' && cwdStart[1] != '\0' && cwdStart[2] == ':' &&
        cwdStart[3] == '=') {
      break;
    }
    cwdStart += strlen(cwdStart) + 1;
  }
  cwdEnd = cwdStart;
  if (*cwdEnd) {
    cwdEnd += strlen(cwdEnd) + 1;
    while (*cwdEnd) {
      if (cwdEnd[0] != '=' || cwdEnd[1] == '\0' || cwdEnd[2] != ':' ||
          cwdEnd[3] != '=') {
        break;
      }
      cwdEnd += strlen(cwdEnd) + 1;
    }
  }
  envBlockSize = cwdEnd - cwdStart;

  for (env = envp; *env; env++) {
    envBlockSize += strlen(*env) + 1;
  }
  envBlockSize++;

  p = *envBlock = PR_MALLOC((PRUint32)envBlockSize);
  if (p == NULL) {
#ifdef WINCE
    PR_Free(curEnv);
#else
    FreeEnvironmentStrings(curEnv);
#endif
    return -1;
  }

  q = cwdStart;
  while (q < cwdEnd) {
    *p++ = *q++;
  }
#ifdef WINCE
  PR_Free(curEnv);
#else
  FreeEnvironmentStrings(curEnv);
#endif

  for (env = envp; *env; env++) {
    q = *env;
    while (*q) {
      *p++ = *q++;
    }
    *p++ = '\0';
  }
  *p = '\0';
  return 0;
}

/*
 * For qsort.  We sort (case-insensitive) the environment strings
 * before generating the environment block.
 */
static int compare(const void* arg1, const void* arg2) {
  return _stricmp(*(char**)arg1, *(char**)arg2);
}

PRProcess* _PR_CreateWindowsProcess(const char* path, char* const* argv,
                                    char* const* envp,
                                    const PRProcessAttr* attr) {
#ifdef WINCE
  STARTUPINFOW startupInfo;
  PRUnichar* wideCmdLine;
  PRUnichar* wideCwd;
  int len = 0;
#else
  STARTUPINFO startupInfo;
#endif
  DWORD creationFlags = 0;
  PROCESS_INFORMATION procInfo;
  BOOL retVal;
  char* cmdLine = NULL;
  char* envBlock = NULL;
  char** newEnvp = NULL;
  const char* cwd = NULL; /* current working directory */
  PRProcess* proc = NULL;
  PRBool hasFdInheritBuffer;

  proc = PR_NEW(PRProcess);
  if (!proc) {
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    goto errorExit;
  }

  if (assembleCmdLine(argv, &cmdLine) == -1) {
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    goto errorExit;
  }

#ifndef WINCE
  /*
   * If attr->fdInheritBuffer is not NULL, we need to insert
   * it into the envp array, so envp cannot be NULL.
   */
  hasFdInheritBuffer = (attr && attr->fdInheritBuffer);
  if ((envp == NULL) && hasFdInheritBuffer) {
    envp = environ;
  }

  if (envp != NULL) {
    int idx;
    int numEnv;
    PRBool found = PR_FALSE;

    numEnv = 0;
    while (envp[numEnv]) {
      numEnv++;
    }
    newEnvp = (char**)PR_MALLOC((numEnv + 2) * sizeof(char*));
    for (idx = 0; idx < numEnv; idx++) {
      newEnvp[idx] = envp[idx];
      if (hasFdInheritBuffer && !found &&
          !strncmp(newEnvp[idx], "NSPR_INHERIT_FDS=", 17)) {
        newEnvp[idx] = attr->fdInheritBuffer;
        found = PR_TRUE;
      }
    }
    if (hasFdInheritBuffer && !found) {
      newEnvp[idx++] = attr->fdInheritBuffer;
    }
    newEnvp[idx] = NULL;
    qsort((void*)newEnvp, (size_t)idx, sizeof(char*), compare);
  }
  if (assembleEnvBlock(newEnvp, &envBlock) == -1) {
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    goto errorExit;
  }

  ZeroMemory(&startupInfo, sizeof(startupInfo));
  startupInfo.cb = sizeof(startupInfo);

  if (attr) {
    PRBool redirected = PR_FALSE;

    /*
     * XXX the default value for stdin, stdout, and stderr
     * should probably be the console input and output, not
     * those of the parent process.
     */
    startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    startupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    if (attr->stdinFd) {
      startupInfo.hStdInput = (HANDLE)attr->stdinFd->secret->md.osfd;
      redirected = PR_TRUE;
    }
    if (attr->stdoutFd) {
      startupInfo.hStdOutput = (HANDLE)attr->stdoutFd->secret->md.osfd;
      redirected = PR_TRUE;
      /*
       * If stdout is redirected, we can assume that the process will
       * not write anything useful to the console windows, and therefore
       * automatically set the CREATE_NO_WINDOW flag.
       */
      creationFlags |= CREATE_NO_WINDOW;
    }
    if (attr->stderrFd) {
      startupInfo.hStdError = (HANDLE)attr->stderrFd->secret->md.osfd;
      redirected = PR_TRUE;
    }
    if (redirected) {
      startupInfo.dwFlags |= STARTF_USESTDHANDLES;
    }
    cwd = attr->currentDirectory;
  }
#endif

#ifdef WINCE
  len = MultiByteToWideChar(CP_ACP, 0, cmdLine, -1, NULL, 0);
  wideCmdLine = (PRUnichar*)PR_MALLOC(len * sizeof(PRUnichar));
  MultiByteToWideChar(CP_ACP, 0, cmdLine, -1, wideCmdLine, len);
  len = MultiByteToWideChar(CP_ACP, 0, cwd, -1, NULL, 0);
  wideCwd = PR_MALLOC(len * sizeof(PRUnichar));
  MultiByteToWideChar(CP_ACP, 0, cwd, -1, wideCwd, len);
  retVal =
      CreateProcessW(NULL, wideCmdLine, NULL, /* security attributes for the new
                                               * process */
                     NULL, /* security attributes for the primary
                            * thread in the new process */
                     TRUE, /* inherit handles */
                     creationFlags, envBlock, /* an environment block,
                                               * consisting of a null-terminated
                                               * block of null-terminated
                                               * strings.  Each string is in the
                                               * form: name=value
                                               * XXX: usually NULL */
                     wideCwd,                 /* current drive and directory */
                     &startupInfo, &procInfo);
  PR_Free(wideCmdLine);
  PR_Free(wideCwd);
#else
  retVal =
      CreateProcess(NULL, cmdLine, NULL, /* security attributes for the new
                                          * process */
                    NULL,                /* security attributes for the primary
                                          * thread in the new process */
                    TRUE,                /* inherit handles */
                    creationFlags, envBlock, /* an environment block, consisting
                                              * of a null-terminated block of
                                              * null-terminated strings.  Each
                                              * string is in the form:
                                              *     name=value
                                              * XXX: usually NULL */
                    cwd,                     /* current drive and directory */
                    &startupInfo, &procInfo);
#endif

  if (retVal == FALSE) {
    /* XXX what error code? */
    PR_SetError(PR_UNKNOWN_ERROR, GetLastError());
    goto errorExit;
  }

  CloseHandle(procInfo.hThread);
  proc->md.handle = procInfo.hProcess;
  proc->md.id = procInfo.dwProcessId;

  PR_DELETE(cmdLine);
  if (newEnvp) {
    PR_DELETE(newEnvp);
  }
  if (envBlock) {
    PR_DELETE(envBlock);
  }
  return proc;

errorExit:
  if (cmdLine) {
    PR_DELETE(cmdLine);
  }
  if (newEnvp) {
    PR_DELETE(newEnvp);
  }
  if (envBlock) {
    PR_DELETE(envBlock);
  }
  if (proc) {
    PR_DELETE(proc);
  }
  return NULL;
} /* _PR_CreateWindowsProcess */

PRStatus _PR_DetachWindowsProcess(PRProcess* process) {
  CloseHandle(process->md.handle);
  PR_DELETE(process);
  return PR_SUCCESS;
}

/*
 * XXX: This implementation is a temporary quick solution.
 * It can be called by native threads only (not by fibers).
 */
PRStatus _PR_WaitWindowsProcess(PRProcess* process, PRInt32* exitCode) {
  DWORD dwRetVal;

  dwRetVal = WaitForSingleObject(process->md.handle, INFINITE);
  if (dwRetVal == WAIT_FAILED) {
    PR_SetError(PR_UNKNOWN_ERROR, GetLastError());
    return PR_FAILURE;
  }
  PR_ASSERT(dwRetVal == WAIT_OBJECT_0);
  if (exitCode != NULL &&
      GetExitCodeProcess(process->md.handle, exitCode) == FALSE) {
    PR_SetError(PR_UNKNOWN_ERROR, GetLastError());
    return PR_FAILURE;
  }
  CloseHandle(process->md.handle);
  PR_DELETE(process);
  return PR_SUCCESS;
}

PRStatus _PR_KillWindowsProcess(PRProcess* process) {
  /*
   * On Unix, if a process terminates normally, its exit code is
   * between 0 and 255.  So here on Windows, we use the exit code
   * 256 to indicate that the process is killed.
   */
  if (TerminateProcess(process->md.handle, 256)) {
    return PR_SUCCESS;
  }
  PR_SetError(PR_UNKNOWN_ERROR, GetLastError());
  return PR_FAILURE;
}

PRStatus _MD_WindowsGetHostName(char* name, PRUint32 namelen) {
  PRIntn rv;
  PRInt32 syserror;

  rv = gethostname(name, (PRInt32)namelen);
  if (0 == rv) {
    return PR_SUCCESS;
  }
  syserror = WSAGetLastError();
  PR_ASSERT(WSANOTINITIALISED != syserror);
  _PR_MD_MAP_GETHOSTNAME_ERROR(syserror);
  return PR_FAILURE;
}

PRStatus _MD_WindowsGetSysInfo(PRSysInfo cmd, char* name, PRUint32 namelen) {
  OSVERSIONINFO osvi;

  PR_ASSERT((cmd == PR_SI_SYSNAME) || (cmd == PR_SI_RELEASE) ||
            (cmd == PR_SI_RELEASE_BUILD));

  ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

  if (!GetVersionEx(&osvi)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    return PR_FAILURE;
  }

  switch (osvi.dwPlatformId) {
    case VER_PLATFORM_WIN32_NT:
      if (PR_SI_SYSNAME == cmd) {
        (void)PR_snprintf(name, namelen, "Windows_NT");
      } else if (PR_SI_RELEASE == cmd) {
        (void)PR_snprintf(name, namelen, "%d.%d", osvi.dwMajorVersion,
                          osvi.dwMinorVersion);
      } else if (PR_SI_RELEASE_BUILD == cmd) {
        (void)PR_snprintf(name, namelen, "%d", osvi.dwBuildNumber);
      }
      break;
    case VER_PLATFORM_WIN32_WINDOWS:
      if (PR_SI_SYSNAME == cmd) {
        if ((osvi.dwMajorVersion > 4) ||
            ((osvi.dwMajorVersion == 4) && (osvi.dwMinorVersion > 0))) {
          (void)PR_snprintf(name, namelen, "Windows_98");
        } else {
          (void)PR_snprintf(name, namelen, "Windows_95");
        }
      } else if (PR_SI_RELEASE == cmd) {
        (void)PR_snprintf(name, namelen, "%d.%d", osvi.dwMajorVersion,
                          osvi.dwMinorVersion);
      } else if (PR_SI_RELEASE_BUILD == cmd) {
        (void)PR_snprintf(name, namelen, "%d", osvi.dwBuildNumber);
      }
      break;
#ifdef VER_PLATFORM_WIN32_CE
    case VER_PLATFORM_WIN32_CE:
      if (PR_SI_SYSNAME == cmd) {
        (void)PR_snprintf(name, namelen, "Windows_CE");
      } else if (PR_SI_RELEASE == cmd) {
        (void)PR_snprintf(name, namelen, "%d.%d", osvi.dwMajorVersion,
                          osvi.dwMinorVersion);
      } else if (PR_SI_RELEASE_BUILD == cmd) {
        if (namelen) {
          *name = 0;
        }
      }
      break;
#endif
    default:
      if (PR_SI_SYSNAME == cmd) {
        (void)PR_snprintf(name, namelen, "Windows_Unknown");
      } else if (PR_SI_RELEASE == cmd) {
        (void)PR_snprintf(name, namelen, "%d.%d", 0, 0);
      } else if (PR_SI_RELEASE_BUILD == cmd) {
        if (namelen) {
          *name = 0;
        }
      }
      break;
  }
  return PR_SUCCESS;
}

PRStatus _MD_WindowsGetReleaseName(char* name, PRUint32 namelen) {
  OSVERSIONINFO osvi;

  ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

  if (!GetVersionEx(&osvi)) {
    _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
    return PR_FAILURE;
  }

  switch (osvi.dwPlatformId) {
    case VER_PLATFORM_WIN32_NT:
    case VER_PLATFORM_WIN32_WINDOWS:
      (void)PR_snprintf(name, namelen, "%d.%d", osvi.dwMajorVersion,
                        osvi.dwMinorVersion);
      break;
    default:
      (void)PR_snprintf(name, namelen, "%d.%d", 0, 0);
      break;
  }
  return PR_SUCCESS;
}

/*
 **********************************************************************
 *
 * Memory-mapped files
 *
 **********************************************************************
 */

PRStatus _MD_CreateFileMap(PRFileMap* fmap, PRInt64 size) {
  DWORD dwHi, dwLo;
  DWORD flProtect;
  PROsfd osfd;

  osfd = (fmap->fd == (PRFileDesc*)-1) ? -1 : fmap->fd->secret->md.osfd;

  dwLo = (DWORD)(size & 0xffffffff);
  dwHi = (DWORD)(((PRUint64)size >> 32) & 0xffffffff);

  if (fmap->prot == PR_PROT_READONLY) {
    flProtect = PAGE_READONLY;
    fmap->md.dwAccess = FILE_MAP_READ;
  } else if (fmap->prot == PR_PROT_READWRITE) {
    flProtect = PAGE_READWRITE;
    fmap->md.dwAccess = FILE_MAP_WRITE;
  } else {
    PR_ASSERT(fmap->prot == PR_PROT_WRITECOPY);
#ifdef WINCE
    /* WINCE does not have FILE_MAP_COPY. */
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
#else
    flProtect = PAGE_WRITECOPY;
    fmap->md.dwAccess = FILE_MAP_COPY;
#endif
  }

  fmap->md.hFileMap =
      CreateFileMapping((HANDLE)osfd, NULL, flProtect, dwHi, dwLo, NULL);

  if (fmap->md.hFileMap == NULL) {
    PR_SetError(PR_UNKNOWN_ERROR, GetLastError());
    return PR_FAILURE;
  }
  return PR_SUCCESS;
}

PRInt32 _MD_GetMemMapAlignment(void) {
  SYSTEM_INFO info;
  GetSystemInfo(&info);
  return info.dwAllocationGranularity;
}

extern PRLogModuleInfo* _pr_shma_lm;

void* _MD_MemMap(PRFileMap* fmap, PROffset64 offset, PRUint32 len) {
  DWORD dwHi, dwLo;
  void* addr;

  dwLo = (DWORD)(offset & 0xffffffff);
  dwHi = (DWORD)(((PRUint64)offset >> 32) & 0xffffffff);
  if ((addr = MapViewOfFile(fmap->md.hFileMap, fmap->md.dwAccess, dwHi, dwLo,
                            len)) == NULL) {
    {
      LPVOID lpMsgBuf;

      FormatMessage(
          FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
          GetLastError(),
          MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),  // Default language
          (LPTSTR)&lpMsgBuf, 0, NULL);
      PR_LOG(_pr_shma_lm, PR_LOG_DEBUG, ("md_memmap(): %s", lpMsgBuf));
    }
    PR_SetError(PR_UNKNOWN_ERROR, GetLastError());
  }
  return addr;
}

PRStatus _MD_MemUnmap(void* addr, PRUint32 len) {
  if (UnmapViewOfFile(addr)) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
  return PR_FAILURE;
}

PRStatus _MD_CloseFileMap(PRFileMap* fmap) {
  CloseHandle(fmap->md.hFileMap);
  PR_DELETE(fmap);
  return PR_SUCCESS;
}

PRStatus _MD_SyncMemMap(PRFileDesc* fd, void* addr, PRUint32 len) {
  PROsfd osfd = fd->secret->md.osfd;

  /* The FlushViewOfFile page on MSDN says:
   *  To flush all the dirty pages plus the metadata for the file and
   *  ensure that they are physically written to disk, call
   *  FlushViewOfFile and then call the FlushFileBuffers function.
   */
  if (FlushViewOfFile(addr, len) && FlushFileBuffers((HANDLE)osfd)) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_DEFAULT_ERROR(GetLastError());
  return PR_FAILURE;
}

/*
 ***********************************************************************
 *
 * Atomic increment and decrement operations for x86 processors
 *
 * We don't use InterlockedIncrement and InterlockedDecrement
 * because on NT 3.51 and Win95, they return a number with
 * the same sign as the incremented/decremented result, rather
 * than the result itself.  On NT 4.0 these functions do return
 * the incremented/decremented result.
 *
 * The result is returned in the eax register by the inline
 * assembly code.  We disable the harmless "no return value"
 * warning (4035) for these two functions.
 *
 ***********************************************************************
 */

#if defined(_M_IX86) || defined(_X86_)

#  pragma warning(disable : 4035)
PRInt32 _PR_MD_ATOMIC_INCREMENT(PRInt32* val) {
#  if defined(__GNUC__)
  PRInt32 result;
  asm volatile("lock ; xadd %0, %1"
               : "=r"(result), "=m"(*val)
               : "0"(1), "m"(*val));
  return result + 1;
#  else
  __asm
  {
        mov ecx, val
        mov eax, 1
        lock xadd dword ptr [ecx], eax
        inc eax
  }
#  endif /* __GNUC__ */
}
#  pragma warning(default : 4035)

#  pragma warning(disable : 4035)
PRInt32 _PR_MD_ATOMIC_DECREMENT(PRInt32* val) {
#  if defined(__GNUC__)
  PRInt32 result;
  asm volatile("lock ; xadd %0, %1"
               : "=r"(result), "=m"(*val)
               : "0"(-1), "m"(*val));
  // asm volatile("lock ; xadd %0, %1" : "=m" (val), "=a" (result) : "-1" (1));
  return result - 1;
#  else
  __asm
  {
        mov ecx, val
        mov eax, 0ffffffffh
        lock xadd dword ptr [ecx], eax
        dec eax
  }
#  endif /* __GNUC__ */
}
#  pragma warning(default : 4035)

#  pragma warning(disable : 4035)
PRInt32 _PR_MD_ATOMIC_ADD(PRInt32* intp, PRInt32 val) {
#  if defined(__GNUC__)
  PRInt32 result;
  // asm volatile("lock ; xadd %1, %0" : "=m" (intp), "=a" (result) : "1"
  // (val));
  asm volatile("lock ; xadd %0, %1"
               : "=r"(result), "=m"(*intp)
               : "0"(val), "m"(*intp));
  return result + val;
#  else
  __asm
  {
        mov ecx, intp
        mov eax, val
        mov edx, eax
        lock xadd dword ptr [ecx], eax
        add eax, edx
  }
#  endif /* __GNUC__ */
}
#  pragma warning(default : 4035)

#  ifdef _PR_HAVE_ATOMIC_CAS

#    pragma warning(disable : 4035)
void PR_StackPush(PRStack* stack, PRStackElem* stack_elem) {
#    if defined(__GNUC__)
  void** tos = (void**)stack;
  void* tmp;

retry:
  if (*tos == (void*)-1) {
    goto retry;
  }

  __asm__("xchg %0,%1" : "=r"(tmp), "=m"(*tos) : "0"(-1), "m"(*tos));

  if (tmp == (void*)-1) {
    goto retry;
  }

  *(void**)stack_elem = tmp;
  __asm__("" : : : "memory");
  *tos = stack_elem;
#    else
  __asm
  {
        mov ebx, stack
        mov ecx, stack_elem
        retry:  mov eax,[ebx]
        cmp eax,-1
        je retry
        mov eax,-1
        xchg dword ptr [ebx], eax
        cmp eax,-1
        je  retry
        mov [ecx],eax
        mov [ebx],ecx
  }
#    endif /* __GNUC__ */
}
#    pragma warning(default : 4035)

#    pragma warning(disable : 4035)
PRStackElem* PR_StackPop(PRStack* stack) {
#    if defined(__GNUC__)
  void** tos = (void**)stack;
  void* tmp;

retry:
  if (*tos == (void*)-1) {
    goto retry;
  }

  __asm__("xchg %0,%1" : "=r"(tmp), "=m"(*tos) : "0"(-1), "m"(*tos));

  if (tmp == (void*)-1) {
    goto retry;
  }

  if (tmp != (void*)0) {
    void* next = *(void**)tmp;
    *tos = next;
    *(void**)tmp = 0;
  } else {
    *tos = tmp;
  }

  return tmp;
#    else
  __asm
  {
        mov ebx, stack
        retry:  mov eax,[ebx]
        cmp eax,-1
        je retry
        mov eax,-1
        xchg dword ptr [ebx], eax
        cmp eax,-1
        je  retry
        cmp eax,0
        je  empty
        mov ecx,[eax]
        mov [ebx],ecx
        mov [eax],0
        jmp done
        empty:
        mov [ebx],eax
        done:
  }
#    endif /* __GNUC__ */
}
#    pragma warning(default : 4035)

#  endif /* _PR_HAVE_ATOMIC_CAS */

#endif /* x86 processors */
