/*
 *  Copyright (c) 1993, Intergraph Corporation
 *
 *  You may distribute under the terms of either the GNU General Public
 *  License or the Artistic License, as specified in the perl README file.
 *
 *  Various Unix compatibility functions and NT specific functions.
 *
 *  Some of this code was derived from the MSDOS port(s) and the OS/2 port.
 *
 */

#include "ruby.h"
#include "rubysig.h"
#include <fcntl.h>
#include <process.h>
#include <sys/stat.h>
/* #include <sys/wait.h> */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>

#include <windows.h>
#include <winbase.h>
#include <wincon.h>
#ifdef __MINGW32__
#include <mswsock.h>
#endif
#include "win32.h"
#include "win32/dir.h"
#ifdef _WIN32_WCE
#include "wince.h"
#endif
#ifndef index
#define index(x, y) strchr((x), (y))
#endif
#define isdirsep(x) ((x) == '/' || (x) == '\\')

#undef stat
#undef fclose
#undef close
#undef setsockopt

#ifndef bool
#define bool int
#endif

#ifdef _M_IX86
# define WIN95 1
#else
# undef  WIN95
#endif

#if defined __BORLANDC__ || defined _WIN32_WCE
#  define _filbuf _fgetc
#  define _flsbuf _fputc
#  define enough_to_get(n) (--(n) >= 0)
#  define enough_to_put(n) (++(n) < 0)
#else
#  define enough_to_get(n) (--(n) >= 0)
#  define enough_to_put(n) (--(n) >= 0)
#endif

#if HAVE_WSAWAITFORMULTIPLEEVENTS
# define USE_INTERRUPT_WINSOCK
#endif

#if USE_INTERRUPT_WINSOCK
# define WaitForMultipleEvents WSAWaitForMultipleEvents
# define CreateSignal() (HANDLE)WSACreateEvent()
# define SetSignal(ev) WSASetEvent(ev)
# define ResetSignal(ev) WSAResetEvent(ev)
#else  /* USE_INTERRUPT_WINSOCK */
# define WaitForMultipleEvents WaitForMultipleObjectsEx
# define CreateSignal() CreateEvent(NULL, FALSE, FALSE, NULL);
# define SetSignal(ev) SetEvent(ev)
# define ResetSignal(ev) (void)0
#endif /* USE_INTERRUPT_WINSOCK */

#ifdef WIN32_DEBUG
#define Debug(something) something
#else
#define Debug(something) /* nothing */
#endif

#define TO_SOCKET(x)	_get_osfhandle(x)

bool NtSyncProcess = TRUE;

static struct ChildRecord *CreateChild(char *, char *, SECURITY_ATTRIBUTES *, HANDLE, HANDLE, HANDLE);
static int make_cmdvector(const char *, char ***);
static bool has_redirection(const char *);
static void StartSockets ();
static DWORD wait_events(HANDLE event, DWORD timeout);
#if !defined(__BORLANDC__) && !defined(_WIN32_WCE)
static int rb_w32_open_osfhandle(long osfhandle, int flags);
#else
#define rb_w32_open_osfhandle(osfhandle, flags) _open_osfhandle(osfhandle, flags)
#endif

/* errno mapping */
static struct {
    DWORD winerr;
    int err;
} errmap[] = {
    {	ERROR_INVALID_FUNCTION,		EINVAL		},
    {	ERROR_FILE_NOT_FOUND,		ENOENT		},
    {	ERROR_PATH_NOT_FOUND,		ENOENT		},
    {	ERROR_TOO_MANY_OPEN_FILES,	EMFILE		},
    {	ERROR_ACCESS_DENIED,		EACCES		},
    {	ERROR_INVALID_HANDLE,		EBADF		},
    {	ERROR_ARENA_TRASHED,		ENOMEM		},
    {	ERROR_NOT_ENOUGH_MEMORY,	ENOMEM		},
    {	ERROR_INVALID_BLOCK,		ENOMEM		},
    {	ERROR_BAD_ENVIRONMENT,		E2BIG		},
    {	ERROR_BAD_FORMAT,		ENOEXEC		},
    {	ERROR_INVALID_ACCESS,		EINVAL		},
    {	ERROR_INVALID_DATA,		EINVAL		},
    {	ERROR_INVALID_DRIVE,		ENOENT		},
    {	ERROR_CURRENT_DIRECTORY,	EACCES		},
    {	ERROR_NOT_SAME_DEVICE,		EXDEV		},
    {	ERROR_NO_MORE_FILES,		ENOENT		},
    {	ERROR_WRITE_PROTECT,		EROFS		},
    {	ERROR_BAD_UNIT,			ENODEV		},
    {	ERROR_NOT_READY,		ENXIO		},
    {	ERROR_BAD_COMMAND,		EACCES		},
    {	ERROR_CRC,			EACCES		},
    {	ERROR_BAD_LENGTH,		EACCES		},
    {	ERROR_SEEK,			EIO		},
    {	ERROR_NOT_DOS_DISK,		EACCES		},
    {	ERROR_SECTOR_NOT_FOUND,		EACCES		},
    {	ERROR_OUT_OF_PAPER,		EACCES		},
    {	ERROR_WRITE_FAULT,		EIO		},
    {	ERROR_READ_FAULT,		EIO		},
    {	ERROR_GEN_FAILURE,		EACCES		},
    {	ERROR_LOCK_VIOLATION,		EACCES		},
    {	ERROR_SHARING_VIOLATION,	EACCES		},
    {	ERROR_WRONG_DISK,		EACCES		},
    {	ERROR_SHARING_BUFFER_EXCEEDED,	EACCES		},
    {	ERROR_BAD_NETPATH,		ENOENT		},
    {	ERROR_NETWORK_ACCESS_DENIED,	EACCES		},
    {	ERROR_BAD_NET_NAME,		ENOENT		},
    {	ERROR_FILE_EXISTS,		EEXIST		},
    {	ERROR_CANNOT_MAKE,		EACCES		},
    {	ERROR_FAIL_I24,			EACCES		},
    {	ERROR_INVALID_PARAMETER,	EINVAL		},
    {	ERROR_NO_PROC_SLOTS,		EAGAIN		},
    {	ERROR_DRIVE_LOCKED,		EACCES		},
    {	ERROR_BROKEN_PIPE,		EPIPE		},
    {	ERROR_DISK_FULL,		ENOSPC		},
    {	ERROR_INVALID_TARGET_HANDLE,	EBADF		},
    {	ERROR_INVALID_HANDLE,		EINVAL		},
    {	ERROR_WAIT_NO_CHILDREN,		ECHILD		},
    {	ERROR_CHILD_NOT_COMPLETE,	ECHILD		},
    {	ERROR_DIRECT_ACCESS_HANDLE,	EBADF		},
    {	ERROR_NEGATIVE_SEEK,		EINVAL		},
    {	ERROR_SEEK_ON_DEVICE,		EACCES		},
    {	ERROR_DIR_NOT_EMPTY,		ENOTEMPTY	},
    {	ERROR_NOT_LOCKED,		EACCES		},
    {	ERROR_BAD_PATHNAME,		ENOENT		},
    {	ERROR_MAX_THRDS_REACHED,	EAGAIN		},
    {	ERROR_LOCK_FAILED,		EACCES		},
    {	ERROR_ALREADY_EXISTS,		EEXIST		},
    {	ERROR_INVALID_STARTING_CODESEG,	ENOEXEC		},
    {	ERROR_INVALID_STACKSEG,		ENOEXEC		},
    {	ERROR_INVALID_MODULETYPE,	ENOEXEC		},
    {	ERROR_INVALID_EXE_SIGNATURE,	ENOEXEC		},
    {	ERROR_EXE_MARKED_INVALID,	ENOEXEC		},
    {	ERROR_BAD_EXE_FORMAT,		ENOEXEC		},
    {	ERROR_ITERATED_DATA_EXCEEDS_64k,ENOEXEC		},
    {	ERROR_INVALID_MINALLOCSIZE,	ENOEXEC		},
    {	ERROR_DYNLINK_FROM_INVALID_RING,ENOEXEC		},
    {	ERROR_IOPL_NOT_ENABLED,		ENOEXEC		},
    {	ERROR_INVALID_SEGDPL,		ENOEXEC		},
    {	ERROR_AUTODATASEG_EXCEEDS_64k,	ENOEXEC		},
    {	ERROR_RING2SEG_MUST_BE_MOVABLE,	ENOEXEC		},
    {	ERROR_RELOC_CHAIN_XEEDS_SEGLIM,	ENOEXEC		},
    {	ERROR_INFLOOP_IN_RELOC_CHAIN,	ENOEXEC		},
    {	ERROR_FILENAME_EXCED_RANGE,	ENOENT		},
    {	ERROR_NESTING_NOT_ALLOWED,	EAGAIN		},
    {	ERROR_NOT_ENOUGH_QUOTA,		ENOMEM		},
    {	0,				0		}
};

static int map_errno(void)
{
    DWORD winerr = GetLastError();
    int i;

    if (winerr == 0) {
	return 0;
    }

    for (i = 0; errmap[i].winerr; i++) {
	if (errmap[i].winerr == winerr) {
	    return errmap[i].err;
	}
    }

    return EINVAL;
}

static char *NTLoginName;

#ifdef WIN95
DWORD Win32System = (DWORD)-1;

static DWORD
IdOS(void)
{
    static OSVERSIONINFO osver;

    if (osver.dwPlatformId != Win32System) {
	memset(&osver, 0, sizeof(OSVERSIONINFO));
	osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&osver);
	Win32System = osver.dwPlatformId;
    }
    return (Win32System);
}

static int 
IsWin95(void) {
    return (IdOS() == VER_PLATFORM_WIN32_WINDOWS);
}

static int
IsWinNT(void) {
    return (IdOS() == VER_PLATFORM_WIN32_NT);
}
#else
# define IsWinNT() TRUE
# define IsWin95() FALSE
#endif

/* main thread constants */
static struct {
    HANDLE handle;
    DWORD id;
} main_thread;

/* interrupt stuff */
static HANDLE interrupted_event;

HANDLE GetCurrentThreadHandle(void)
{
    static HANDLE current_process_handle = NULL;
    HANDLE h;

    if (!current_process_handle)
	current_process_handle = GetCurrentProcess();
    if (!DuplicateHandle(current_process_handle, GetCurrentThread(),
			 current_process_handle, &h,
			 0, FALSE, DUPLICATE_SAME_ACCESS))
	return NULL;
    return h;
}

/* simulate flock by locking a range on the file */


#define LK_ERR(f,i) ((f) ? (i = 0) : (errno = GetLastError() == ERROR_LOCK_VIOLATION ? EWOULDBLOCK : EACCES))
#define LK_LEN      ULONG_MAX

static VALUE
flock_winnt(VALUE self, int argc, VALUE* argv)
{
    OVERLAPPED o;
    int i = -1;
    const HANDLE fh = (HANDLE)self;
    const int oper = argc;

    memset(&o, 0, sizeof(o));

    switch(oper) {
      case LOCK_SH:		/* shared lock */
	LK_ERR(LockFileEx(fh, 0, 0, LK_LEN, LK_LEN, &o), i);
	break;
      case LOCK_EX:		/* exclusive lock */
	LK_ERR(LockFileEx(fh, LOCKFILE_EXCLUSIVE_LOCK, 0, LK_LEN, LK_LEN, &o), i);
	break;
      case LOCK_SH|LOCK_NB:	/* non-blocking shared lock */
	LK_ERR(LockFileEx(fh, LOCKFILE_FAIL_IMMEDIATELY, 0, LK_LEN, LK_LEN, &o), i);
	break;
      case LOCK_EX|LOCK_NB:	/* non-blocking exclusive lock */
	LK_ERR(LockFileEx(fh,
			  LOCKFILE_EXCLUSIVE_LOCK|LOCKFILE_FAIL_IMMEDIATELY,
			  0, LK_LEN, LK_LEN, &o), i);
	break;
      case LOCK_UN:		/* unlock lock */
	LK_ERR(UnlockFileEx(fh, 0, LK_LEN, LK_LEN, &o), i);
	break;
      default:            /* unknown */
	errno = EINVAL;
	break;
    }
    return i;
}

#ifdef WIN95
static VALUE
flock_win95(VALUE self, int argc, VALUE* argv)
{
    int i = -1;
    const HANDLE fh = (HANDLE)self;
    const int oper = argc;

    switch(oper) {
      case LOCK_EX:
	do {
	    LK_ERR(LockFile(fh, 0, 0, LK_LEN, LK_LEN), i);
	} while (i && errno == EWOULDBLOCK);
	break;
      case LOCK_EX|LOCK_NB:
	LK_ERR(LockFile(fh, 0, 0, LK_LEN, LK_LEN), i);
	break;
      case LOCK_UN:
	LK_ERR(UnlockFile(fh, 0, 0, LK_LEN, LK_LEN), i);
	break;
      default:
	errno = EINVAL;
	break;
    }
    return i;
}
#endif

#undef LK_ERR

int
flock(int fd, int oper)
{
#ifdef WIN95
    static asynchronous_func_t locker = NULL;

    if (!locker) {
	if (IsWinNT())
	    locker = flock_winnt;
	else
	    locker = flock_win95;
    }
#else
    const asynchronous_func_t locker = flock_winnt;
#endif

    return rb_w32_asynchronize(locker,
			      (VALUE)_get_osfhandle(fd), oper, NULL,
			      (DWORD)-1);
}

//
// Initialization stuff
//
void
NtInitialize(int *argc, char ***argv)
{

    WORD version;
    int ret;

#ifdef __BORLANDC__
    _controlfp(0x5, 0x5);
#endif

    //
    // subvert cmd.exe's feeble attempt at command line parsing
    //
    *argc = make_cmdvector(GetCommandLine(), argv);

    //
    // Now set up the correct time stuff
    //

    tzset();

    // Initialize Winsock
    StartSockets();

#ifdef _WIN32_WCE
    // free commandline buffer
    wce_FreeCommandLine();
#endif
}

char *getlogin()
{
    char buffer[200];
    DWORD len = 200;
    extern char *NTLoginName;

    if (NTLoginName == NULL) {
	if (GetUserName(buffer, &len)) {
	    NTLoginName = ALLOC_N(char, len+1);
	    strncpy(NTLoginName, buffer, len);
	    NTLoginName[len] = '\0';
	}
	else {
	    NTLoginName = "<Unknown>";
	}
    }
    return NTLoginName;
}

#define MAXCHILDNUM 256	/* max num of child processes */

static struct ChildRecord {
    HANDLE hProcess;	/* process handle */
    pid_t pid;		/* process id */
} ChildRecord[MAXCHILDNUM];

#define FOREACH_CHILD(v) do { \
    struct ChildRecord* v; \
    for (v = ChildRecord; v < ChildRecord + sizeof(ChildRecord) / sizeof(ChildRecord[0]); ++v)
#define END_FOREACH_CHILD } while (0)

static struct ChildRecord *
FindFirstChildSlot(void)
{
    FOREACH_CHILD(child) {
	if (child->pid) return child;
    } END_FOREACH_CHILD;
    return NULL;
}

static struct ChildRecord *
FindChildSlot(pid_t pid)
{

    FOREACH_CHILD(child) {
	if (child->pid == pid) {
	    return child;
	}
    } END_FOREACH_CHILD;
    return NULL;
}

static void
CloseChildHandle(struct ChildRecord *child)
{
    HANDLE h = child->hProcess;
    child->hProcess = NULL;
    child->pid = 0;
    CloseHandle(h);
}

static struct ChildRecord *
FindFreeChildSlot(void)
{
    FOREACH_CHILD(child) {
	if (!child->pid) {
	    child->pid = -1;	/* lock the slot */
	    child->hProcess = NULL;
	    return child;
	}
    } END_FOREACH_CHILD;
    return NULL;
}


int SafeFree(char **vec, int vecc)
{
    //   vec
    //   |
    //   V       ^---------------------V
    //   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //   |   |       | ....  |  NULL |   | ..... |\0 |   | ..... |\0 |...
    //   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //   |-  elements+1             -| ^ 1st element   ^ 2nd element

	char *p;

	p = (char *)vec;
	free(p);

	return 0;
}


static char *szInternalCmds[] = {
  "append",
  "break",
  "call",
  "cd",
  "chdir",
  "cls",
  "copy",
  "date",
  "del",
  "dir",
  "echo",
  "erase",
  "label",
  "md",
  "mkdir",
  "path",
  "pause",
  "rd",
  "rem",
  "ren",
  "rename",
  "rmdir",
  "set",
  "start",
  "time",
  "type",
  "ver",
  "vol",
};

static int
internal_match(const void *key, const void *elem)
{
    return strcmp(key, *(const char *const *)elem);
}

static int
isInternalCmd(const char *cmd)
{
    int i;
    char cmdname[8], *b = cmdname, c;

    do {
	if (!(c = *cmd++)) return 0;
    } while (isspace(c));
    while (isalpha(c)) {
	*b++ = tolower(c);
	if (b == cmdname + sizeof(cmdname)) return 0;
	c = *cmd++;
    }
    if (c == '.') c = *cmd;
    switch (c) {
      case '<': case '>': case '|':
	return 1;
      case '\0': case ' ': case '\t': case '\n':
	break;
      default:
	return 0;
    }
    *b = 0;
    if (!bsearch(cmdname, szInternalCmds,
		 sizeof(szInternalCmds) / sizeof(*szInternalCmds),
		 sizeof(*szInternalCmds),
		 internal_match))
	return 0;
    return 1;
}


SOCKET
rb_w32_get_osfhandle(int fh)
{
    return _get_osfhandle(fh);
}

pid_t
pipe_exec(char *cmd, int mode, FILE **fpr, FILE **fpw)
{
    struct ChildRecord* child;
    HANDLE hReadIn, hReadOut;
    HANDLE hWriteIn, hWriteOut;
    HANDLE hDupInFile, hDupOutFile;
    HANDLE hCurProc;
    SECURITY_ATTRIBUTES sa;
    BOOL fRet;
    BOOL reading, writing;
    int fd;
    int pipemode;
    char modes[3];
    int ret;

    /* Figure out what we're doing... */
    writing = (mode & (O_WRONLY | O_RDWR)) ? TRUE : FALSE;
    reading = ((mode & O_RDWR) || !writing) ? TRUE : FALSE;
    if (mode & O_BINARY) {
	pipemode = O_BINARY;
	modes[1] = 'b';
	modes[2] = '\0';
    }
    else {
	pipemode = O_TEXT;
	modes[1] = '\0';
    }

    sa.nLength              = sizeof (SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle       = TRUE;
    ret = -1;
    hWriteIn = hReadOut = NULL;

    RUBY_CRITICAL(do {
	/* create pipe */
	hCurProc = GetCurrentProcess();
	if (reading) {
	    fRet = CreatePipe(&hReadIn, &hReadOut, &sa, 2048L);
	    if (!fRet) {
		errno = map_errno();
		break;
	    }
	    if (!DuplicateHandle(hCurProc, hReadIn, hCurProc, &hDupInFile, 0,
				 FALSE, DUPLICATE_SAME_ACCESS)) {
		errno = map_errno();
		CloseHandle(hReadIn);
		CloseHandle(hReadOut);
		CloseHandle(hCurProc);
		break;
	    }
	    CloseHandle(hReadIn);
	}
	if (writing) {
	    fRet = CreatePipe(&hWriteIn, &hWriteOut, &sa, 2048L);
	    if (!fRet) {
		errno = map_errno();
	      write_pipe_failed:
		if (reading) {
		    CloseHandle(hDupInFile);
		    CloseHandle(hReadOut);
		}
		break;
	    }
	    if (!DuplicateHandle(hCurProc, hWriteOut, hCurProc, &hDupOutFile, 0,
				 FALSE, DUPLICATE_SAME_ACCESS)) {
		errno = map_errno();
		CloseHandle(hWriteIn);
		CloseHandle(hWriteOut);
		CloseHandle(hCurProc);
		goto write_pipe_failed;
	    }
	    CloseHandle(hWriteOut);
	}
	CloseHandle(hCurProc);

	/* create child process */
	child = CreateChild(cmd, NULL, &sa, hWriteIn, hReadOut, NULL);
	if (!child) {
	    if (reading) {
		CloseHandle(hReadOut);
		CloseHandle(hDupInFile);
	    }
	    if (writing) {
		CloseHandle(hWriteIn);
		CloseHandle(hDupOutFile);
	    }
	    break;
	}

	/* associate handle to fp */
	if (reading) {
	    fd = rb_w32_open_osfhandle((long)hDupInFile,
				       (_O_RDONLY | pipemode));
	    CloseHandle(hReadOut);
	    if (fd == -1) {
		CloseHandle(hDupInFile);
	      read_open_failed:
		if (writing) {
		    CloseHandle(hWriteIn);
		    CloseHandle(hDupOutFile);
		}
		CloseChildHandle(child);
		break;
	    }
	    modes[0] = 'r';
	    if ((*fpr = (FILE *)fdopen(fd, modes)) == NULL) {
		_close(fd);
		goto read_open_failed;
	    }
	}
	if (writing) {
	    fd = rb_w32_open_osfhandle((long)hDupOutFile,
				       (_O_WRONLY | pipemode));
	    CloseHandle(hWriteIn);
	    if (fd == -1) {
		CloseHandle(hDupOutFile);
	      write_open_failed:
		if (reading) {
		    fclose(*fpr);
		}
		CloseChildHandle(child);
		break;
	    }
	    modes[0] = 'w';
	    if ((*fpw = (FILE *)fdopen(fd, modes)) == NULL) {
		_close(fd);
		goto write_open_failed;
	    }
	}
	ret = child->pid;
    } while (0));

    return ret;
}

extern VALUE rb_last_status;

int
do_spawn(mode, cmd)
int mode;
char *cmd;
{
    struct ChildRecord *child;

    switch (mode) {
      case P_WAIT:
      case P_NOWAIT:
      case P_OVERLAY:
	break;
      default:
	errno = EINVAL;
	return -1;
    }

    child = CreateChild(cmd, NULL, NULL, NULL, NULL, NULL);
    if (!child) {
	return -1;
    }

    switch (mode) {
      case P_WAIT:
	rb_syswait(child->pid);
	return NUM2INT(rb_last_status);
      case P_NOWAIT:
	return child->pid;
      case P_OVERLAY:
	exit(0);
      default:
	return -1;	/* not reached */
    }
}

int
do_aspawn(mode, prog, argv)
int mode;
char *prog;
char **argv;
{
    char *cmd, *p, *q, *s, **t;
    int len, n, bs, quote;
    struct ChildRecord *child;

    switch (mode) {
      case P_WAIT:
      case P_NOWAIT:
      case P_OVERLAY:
	break;
      default:
	errno = EINVAL;
	return -1;
    }

    for (t = argv, len = 0; *t; t++) {
	for (p = *t, n = quote = bs = 0; *p; ++p) {
	    switch (*p) {
	      case '\\':
		++bs;
		break;
	      case '"':
		n += bs + 1; bs = 0;
		quote = 1;
		break;
	      case ' ': case '\t':
		quote = 1;
	      default:
		bs = 0;
		p = CharNext(p) - 1;
		break;
	    }
	}
	len += p - *t + n + 1;
	if (quote) len += 2;
    }
    cmd = ALLOCA_N(char, len);
    for (t = argv, q = cmd; p = *t; t++) {
	quote = 0;
	s = p;
	if (!*p || strpbrk(p, " \t\"")) {
	    quote = 1;
	    *q++ = '"';
	}
	for (bs = 0; *p; ++p) {
	    switch (*p) {
	      case '\\':
		++bs;
		break;
	      case '"':
		memcpy(q, s, n = p - s); q += n; s = p;
		memset(q, '\\', ++bs); q += bs; bs = 0;
		break;
	      default:
		bs = 0;
		p = CharNext(p) - 1;
		break;
	    }
	}
	memcpy(q, s, n = p - s);
	q += n;
	if (quote) *q++ = '"';
	*q++ = ' ';
    }
    if (q > cmd) --q;
    *q = '\0';

    child = CreateChild(cmd, prog, NULL, NULL, NULL, NULL);
    if (!child) {
	return -1;
    }

    switch (mode) {
      case P_WAIT:
	rb_syswait(child->pid);
	return NUM2INT(rb_last_status);
      case P_NOWAIT:
	return child->pid;
      case P_OVERLAY:
	exit(0);
      default:
	return -1;	/* not reached */
    }
}

static struct ChildRecord *
CreateChild(char *cmd, char *prog, SECURITY_ATTRIBUTES *psa, HANDLE hInput, HANDLE hOutput, HANDLE hError)
{
    BOOL fRet;
    DWORD  dwCreationFlags;
    STARTUPINFO aStartupInfo;
    PROCESS_INFORMATION aProcessInformation;
    SECURITY_ATTRIBUTES sa;
    char *shell;
    struct ChildRecord *child;

    if (!cmd && !prog) {
	errno = EFAULT;
	return NULL;
    }

    child = FindFreeChildSlot();
    if (!child) {
	errno = EAGAIN;
	return NULL;
    }

    if (!psa) {
	sa.nLength              = sizeof (SECURITY_ATTRIBUTES);
	sa.lpSecurityDescriptor = NULL;
	sa.bInheritHandle       = TRUE;
	psa = &sa;
    }

    memset(&aStartupInfo, 0, sizeof (STARTUPINFO));
    memset(&aProcessInformation, 0, sizeof (PROCESS_INFORMATION));
    aStartupInfo.cb = sizeof (STARTUPINFO);
    if (hInput || hOutput || hError) {
	aStartupInfo.dwFlags = STARTF_USESTDHANDLES;
	if (hInput) {
	    aStartupInfo.hStdInput  = hInput;
	}
	else {
	    aStartupInfo.hStdInput  = GetStdHandle(STD_INPUT_HANDLE);
	}
	if (hOutput) {
	    aStartupInfo.hStdOutput = hOutput;
	}
	else {
	    aStartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	}
	if (hError) {
	    aStartupInfo.hStdError = hError;
	}
	else {
	    aStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
	}
    }

    dwCreationFlags = (NORMAL_PRIORITY_CLASS);

    if (prog) {
	shell = prog;
    }
    else {
	int redir = -1;
	if ((shell = getenv("RUBYSHELL")) && (redir = has_redirection(cmd))) {
	    char *tmp = ALLOCA_N(char, strlen(shell) + strlen(cmd) +
				 sizeof (" -c "));
	    sprintf(tmp, "%s -c %s", shell, cmd);
	    cmd = tmp;
	}
	else if ((shell = getenv("COMSPEC")) &&
		 ((redir < 0 ? has_redirection(cmd) : redir) || isInternalCmd(cmd))) {
	    char *tmp = ALLOCA_N(char, strlen(shell) + strlen(cmd) +
				 sizeof (" /c "));
	    sprintf(tmp, "%s /c %s", shell, cmd);
	    cmd = tmp;
	}
	else {
	    shell = NULL;
	}
    }

    RUBY_CRITICAL({
	fRet = CreateProcess(shell, cmd, psa, psa,
			     psa->bInheritHandle, dwCreationFlags, NULL, NULL,
			     &aStartupInfo, &aProcessInformation);
	errno = map_errno();
    });

    if (!fRet) {
	child->pid = 0;		/* release the slot */
	return NULL;
    }

    CloseHandle(aProcessInformation.hThread);

    child->hProcess = aProcessInformation.hProcess;
    child->pid = (pid_t)aProcessInformation.dwProcessId;

    if (!IsWinNT()) {
	/* On Win9x, make pid positive similarly to cygwin and perl */
	child->pid = -child->pid;
    }

    return child;
}

typedef struct _NtCmdLineElement {
    struct _NtCmdLineElement *next;
    char *str;
    int len;
    int flags;
} NtCmdLineElement;

//
// Possible values for flags
//

#define NTGLOB   0x1	// element contains a wildcard
#define NTMALLOC 0x2	// string in element was malloc'ed
#define NTSTRING 0x4	// element contains a quoted string

static void
insert(const char *path, VALUE vinfo)
{
    NtCmdLineElement *tmpcurr;
    NtCmdLineElement ***tail = (NtCmdLineElement ***)vinfo;

    tmpcurr = ALLOC(NtCmdLineElement);
    MEMZERO(tmpcurr, NtCmdLineElement, 1);
    tmpcurr->len = strlen(path);
    tmpcurr->str = ALLOC_N(char, tmpcurr->len + 1);
    tmpcurr->flags |= NTMALLOC;
    strcpy(tmpcurr->str, path);
    **tail = tmpcurr;
    *tail = &tmpcurr->next;
}

#ifdef HAVE_SYS_PARAM_H
# include <sys/param.h>
#else
# define MAXPATHLEN 512
#endif


static NtCmdLineElement **
cmdglob(NtCmdLineElement *patt, NtCmdLineElement **tail)
{
    char buffer[MAXPATHLEN], *buf = buffer;
    char *p;
    NtCmdLineElement **last = tail;

    if (patt->len >= MAXPATHLEN)
	buf = ruby_xmalloc(patt->len + 1);

    strncpy (buf, patt->str, patt->len);
    buf[patt->len] = '\0';
    for (p = buf; *p; p = CharNext(p))
	if (*p == '\\')
	    *p = '/';
    rb_globi(buf, insert, (VALUE)&tail);
    if (buf != buffer)
	free(buf);

    if (last == tail) return 0;
    if (patt->flags & NTMALLOC)
	free(patt->str);
    free(patt);
    return tail;
}

// 
// Check a command string to determine if it has I/O redirection
// characters that require it to be executed by a command interpreter
//

static bool
has_redirection(const char *cmd)
{
    char quote = '\0';
    const char *ptr;

    //
    // Scan the string, looking for redirection (< or >) or pipe 
    // characters (|) that are not in a quoted string
    //

    for (ptr = cmd; *ptr;) {
	switch (*ptr) {
	  case '\'':
	  case '\"':
	    if (!quote)
		quote = *ptr;
	    else if (quote == *ptr)
		quote = '\0';
	    ptr++;
	    break;

	  case '>':
	  case '<':
	  case '|':
	    if (!quote)
		return TRUE;
	    ptr++;
	    break;

	  case '\\':
	    ptr++;
	  default:
	    ptr = CharNext(ptr);
	    break;
	}
    }
    return FALSE;
}

static inline char *
skipspace(char *ptr)
{
    while (ISSPACE(*ptr))
	ptr++;
    return ptr;
}

static int 
make_cmdvector(const char *cmd, char ***vec)
{
    int cmdlen, globbing, len, i;
    int elements, strsz, done;
    int slashes, escape;
    char *ptr, *base, *buffer, *cmdline;
    char **vptr;
    char quote;
    NtCmdLineElement *curr, **tail;
    NtCmdLineElement *cmdhead = NULL, **cmdtail = &cmdhead;

    //
    // just return if we don't have a command line
    //

    while (ISSPACE(*cmd))
	cmd++;
    if (!*cmd) {
	*vec = NULL;
	return 0;
    }

    ptr = cmdline = strdup(cmd);

    //
    // Ok, parse the command line, building a list of CmdLineElements.
    // When we've finished, and it's an input command (meaning that it's
    // the processes argv), we'll do globing and then build the argument 
    // vector.
    // The outer loop does one interation for each element seen. 
    // The inner loop does one interation for each character in the element.
    //

    while (*(ptr = skipspace(ptr))) {
	base = ptr;
	quote = slashes = globbing = escape = 0;
	for (done = 0; !done && *ptr; ) {
	    //
	    // Switch on the current character. We only care about the
	    // white-space characters, the  wild-card characters, and the
	    // quote characters.
	    //

	    switch (*ptr) {
	      case '\\':
		slashes++;
	        break;

	      case ' ':
	      case '\t':
	      case '\n':
		//
		// if we're not in a string, then we're finished with this
		// element
		//

		if (!quote) {
		    *ptr = 0;
		    done = 1;
		}
		break;

	      case '*':
	      case '?':
	      case '[':
	      case '{':
		// 
		// record the fact that this element has a wildcard character
		// N.B. Don't glob if inside a single quoted string
		//

		if (quote != '\'')
		    globbing++;
		ptr++;
		slashes = 0;
		break;

	      case '\'':
	      case '\"':
		//
		// if we're already in a string, see if this is the
		// terminating close-quote. If it is, we're finished with 
		// the string, but not neccessarily with the element.
		// If we're not already in a string, start one.
		//

		if (!(slashes & 1)) {
		    if (!quote)
			quote = *ptr;
		    else if (quote == *ptr)
			quote = '\0';
		    escape++;
		}
		slashes = 0;
		break;

	      default:
		ptr = CharNext(ptr);
		slashes = 0;
		continue;
	    }
	    ptr++;
	}

	//
	// when we get here, we've got a pair of pointers to the element,
	// base and ptr. Base points to the start of the element while ptr
	// points to the character following the element.
	//

	len = ptr - base;
	if (quote) escape = 0;
	else if (done) --len;

	//
	// if it's an input vector element and it's enclosed by quotes, 
	// we can remove them.
	//

	if (escape) {
	    char *p = base;
	    slashes = quote = 0;
	    while (p < base + len) {
		switch (*p) {
		  case '\\':
		    p++;
		    slashes++;
		    break;

		  case '\'':
		  case '"':
		    if (!(slashes & 1)) {
			if (!quote)
			    quote = *p;
			else if (quote == *p)
			    quote = '\0';
			else {
			    p++;
			    slashes = 0;
			    break;
			}
		    }
		    if (base + slashes == p) {
			base += slashes >> 1;
			len -= slashes >> 1;
			slashes &= 1;
		    }
		    if (base == p) {
			base = ++p;
			--len;
		    }
		    else {
			memcpy(p - ((slashes + 1) >> 1), p + (~slashes & 1), base + len - p);
			slashes >>= 1;
			p -= slashes;
			len -= slashes + 1;
			slashes = 0;
		    }
		    break;

		  default:
		    p = CharNext(p);
		    slashes = 0;
		    break;
		}
	    }
	}

	curr = ALLOC(NtCmdLineElement);
	MEMZERO(curr, NtCmdLineElement, 1);
	curr->str = base;
	curr->len = len;

	if (globbing && (tail = cmdglob(curr, cmdtail))) {
	    cmdtail = tail;
	}
	else {
	    *cmdtail = curr;
	    cmdtail = &curr->next;
	}
    }

    //
    // Almost done! 
    // Count up the elements, then allocate space for a vector of pointers
    // (argv) and a string table for the elements.
    // 

    for (elements = 0, strsz = 0, curr = cmdhead; curr; curr = curr->next) {
	elements++;
	strsz += (curr->len + 1);
    }

    len = (elements+1)*sizeof(char *) + strsz;
    buffer = ALLOC_N(char, len);
    
    //
    // make vptr point to the start of the buffer
    // and ptr point to the area we'll consider the string table.
    //
    //   buffer (*vec)
    //   |
    //   V       ^---------------------V
    //   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //   |   |       | ....  | NULL  |   | ..... |\0 |   | ..... |\0 |...
    //   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
    //   |-  elements+1             -| ^ 1st element   ^ 2nd element

    vptr = (char **) buffer;

    ptr = buffer + (elements+1) * sizeof(char *);

    while (curr = cmdhead) {
	strncpy (ptr, curr->str, curr->len);
	ptr[curr->len] = '\0';
	*vptr++ = ptr;
	ptr += curr->len + 1;
	cmdhead = curr->next;
	if (curr->flags & NTMALLOC) free(curr->str);
	free(curr);
    }
    *vptr = 0;

    *vec = (char **) buffer;
    free(cmdline);
    return elements;
}

//
// UNIX compatible directory access functions for NT
//

#define PATHLEN 1024

//
// The idea here is to read all the directory names into a string table
// (separated by nulls) and when one of the other dir functions is called
// return the pointer to the current file name. 
//

DIR *
rb_w32_opendir(const char *filename)
{
    DIR               *p;
    long               len;
    long               idx;
    char               scannamespc[PATHLEN];
    char	      *scanname = scannamespc;
    struct stat	       sbuf;
    struct _finddata_t fd;
    long               fh;

    //
    // check to see if we've got a directory
    //

    if (rb_w32_stat(filename, &sbuf) < 0)
	return NULL;
    if (!(sbuf.st_mode & S_IFDIR) &&
	(!ISALPHA(filename[0]) || filename[1] != ':' || filename[2] != '\0' ||
	((1 << (filename[0] & 0x5f) - 'A') & GetLogicalDrives()) == 0)) {
	errno = ENOTDIR;
	return NULL;
    }

    //
    // Get us a DIR structure
    //

    p = xcalloc(sizeof(DIR), 1);
    if (p == NULL)
	return NULL;
    
    //
    // Create the search pattern
    //

    strcpy(scanname, filename);

    if (index("/\\:", *CharPrev(scanname, scanname + strlen(scanname))) == NULL)
	strcat(scanname, "/*");
    else
	strcat(scanname, "*");

    //
    // do the FindFirstFile call
    //

    fh = _findfirst(scanname, &fd);
    if (fh == -1) {
	return NULL;
    }

    //
    // now allocate the first part of the string table for the
    // filenames that we find.
    //

    idx = strlen(fd.name)+1;
    p->start = ALLOC_N(char, idx);
    strcpy(p->start, fd.name);
    p->nfiles++;
    
    //
    // loop finding all the files that match the wildcard
    // (which should be all of them in this directory!).
    // the variable idx should point one past the null terminator
    // of the previous string found.
    //
    while (_findnext(fh, &fd) == 0) {
	len = strlen(fd.name);

	//
	// bump the string table size by enough for the
	// new name and it's null terminator 
	//

	#define Renew(x, y, z) (x = (z *)realloc(x, y))

	Renew (p->start, idx+len+1, char);
	if (p->start == NULL) {
            rb_fatal ("opendir: malloc failed!\n");
	}
	strcpy(&p->start[idx], fd.name);
	p->nfiles++;
	idx += len+1;
    }
    _findclose(fh);
    p->size = idx;
    p->curr = p->start;
    return p;
}


//
// Readdir just returns the current string pointer and bumps the
// string pointer to the next entry.
//

struct direct  *
rb_w32_readdir(DIR *dirp)
{
    int         len;
    static int  dummy = 0;

    if (dirp->curr) {

	//
	// first set up the structure to return
	//

	len = strlen(dirp->curr);
	strcpy(dirp->dirstr.d_name, dirp->curr);
	dirp->dirstr.d_namlen = len;

	//
	// Fake inode
	//
	dirp->dirstr.d_ino = dummy++;

	//
	// Now set up for the next call to readdir
	//

	dirp->curr += len + 1;
	if (dirp->curr >= (dirp->start + dirp->size)) {
	    dirp->curr = NULL;
	}

	return &(dirp->dirstr);

    } else
	return NULL;
}

//
// Telldir returns the current string pointer position
//

long
rb_w32_telldir(DIR *dirp)
{
	return (long) dirp->curr;	/* ouch! pointer to long cast */
}

//
// Seekdir moves the string pointer to a previously saved position
// (Saved by telldir).

void
rb_w32_seekdir(DIR *dirp, long loc)
{
	dirp->curr = (char *) loc;	/* ouch! long to pointer cast */
}

//
// Rewinddir resets the string pointer to the start
//

void
rb_w32_rewinddir(DIR *dirp)
{
	dirp->curr = dirp->start;
}

//
// This just free's the memory allocated by opendir
//

void
rb_w32_closedir(DIR *dirp)
{
	free(dirp->start);
	free(dirp);
}

EXTERN_C void __cdecl _lock_fhandle(int);
EXTERN_C void __cdecl _unlock_fhandle(int);
EXTERN_C void __cdecl _unlock(int);

#if (defined _MT || defined __MSVCRT__) && !defined __BORLANDC__
#define MSVCRT_THREADS
#endif
#ifdef MSVCRT_THREADS
# define MTHREAD_ONLY(x) x
# define STHREAD_ONLY(x)
#elif defined(__BORLANDC__)
# define MTHREAD_ONLY(x)
# define STHREAD_ONLY(x)
#else
# define MTHREAD_ONLY(x)
# define STHREAD_ONLY(x) x
#endif

typedef struct	{
    long osfhnd;    /* underlying OS file HANDLE */
    char osfile;    /* attributes of file (e.g., open in text mode?) */
    char pipech;    /* one char buffer for handles opened on pipes */
#ifdef MSVCRT_THREADS
    int lockinitflag;
    CRITICAL_SECTION lock;
#endif
}	ioinfo;

#if !defined _CRTIMP || defined __MINGW32__
#undef _CRTIMP
#define _CRTIMP __declspec(dllimport)
#endif

#if !defined(__BORLANDC__) && !defined(_WIN32_WCE)
EXTERN_C _CRTIMP ioinfo * __pioinfo[];

#define IOINFO_L2E			5
#define IOINFO_ARRAY_ELTS	(1 << IOINFO_L2E)
#define _pioinfo(i)	(__pioinfo[i >> IOINFO_L2E] + (i & (IOINFO_ARRAY_ELTS - 1)))
#define _osfhnd(i)  (_pioinfo(i)->osfhnd)
#define _osfile(i)  (_pioinfo(i)->osfile)
#define _pipech(i)  (_pioinfo(i)->pipech)

#define _set_osfhnd(fh, osfh) (void)(_osfhnd(fh) = osfh)
#define _set_osflags(fh, flags) (_osfile(fh) = (flags))

#define FOPEN			0x01	/* file handle open */
#define FNOINHERIT		0x10	/* file handle opened O_NOINHERIT */
#define FAPPEND			0x20	/* file handle opened O_APPEND */
#define FDEV			0x40	/* file handle refers to device */
#define FTEXT			0x80	/* file handle is in text mode */

static int
rb_w32_open_osfhandle(long osfhandle, int flags)
{
    int fh;
    char fileflags;		/* _osfile flags */
    HANDLE hF;

    /* copy relevant flags from second parameter */
    fileflags = FDEV;

    if (flags & O_APPEND)
	fileflags |= FAPPEND;

    if (flags & O_TEXT)
	fileflags |= FTEXT;

    if (flags & O_NOINHERIT)
	fileflags |= FNOINHERIT;

    /* attempt to allocate a C Runtime file handle */
    hF = CreateFile("NUL", 0, 0, NULL, OPEN_ALWAYS, 0, NULL);
    fh = _open_osfhandle((long)hF, 0);
    CloseHandle(hF);
    if (fh == -1) {
	errno = EMFILE;		/* too many open files */
	_doserrno = 0L;		/* not an OS error */
    }
    else {

	MTHREAD_ONLY(EnterCriticalSection(&(_pioinfo(fh)->lock)));
	/* the file is open. now, set the info in _osfhnd array */
	_set_osfhnd(fh, osfhandle);

	fileflags |= FOPEN;		/* mark as open */

	_set_osflags(fh, fileflags); /* set osfile entry */
	MTHREAD_ONLY(LeaveCriticalSection(&_pioinfo(fh)->lock));
    }
    return fh;			/* return handle */
}
#else

#define _set_osfhnd(fh, osfh) (void)((fh), (osfh))
#define _set_osflags(fh, flags) (void)((fh), (flags))

#endif

#undef getsockopt

static int
is_socket(SOCKET fd)
{
    char sockbuf[80];
    int optlen;
    int retval;
    int result = TRUE;

    optlen = sizeof(sockbuf);
    RUBY_CRITICAL({
	retval = getsockopt(fd, SOL_SOCKET, SO_TYPE, sockbuf, &optlen);
	if (retval == SOCKET_ERROR) {
	    int iRet;
	    iRet = WSAGetLastError();
	    if (iRet == WSAENOTSOCK || iRet == WSANOTINITIALISED)
		result = FALSE;
	}
    });

    //
    // If we get here, then fd is actually a socket.
    //

    return result;
}

//
// Since the errors returned by the socket error function 
// WSAGetLastError() are not known by the library routine strerror
// we have to roll our own.
//

#undef strerror

char *
rb_w32_strerror(int e)
{
    static char buffer[512];
#if !defined __MINGW32__
    extern int sys_nerr;
#endif
    DWORD source = 0;
    char *p;

    if (e < 0 || e > sys_nerr) {
	if (e < 0)
	    e = GetLastError();
	if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
			  FORMAT_MESSAGE_IGNORE_INSERTS, &source, e, 0,
			  buffer, 512, NULL) == 0) {
	    strcpy(buffer, "Unknown Error");
	}
	for (p = buffer + strlen(buffer) - 1; buffer <= p; p--) {
	    if (*p != '\r' && *p != '\n') break;
	    *p = 0;
	}
	return buffer;
    }
    return strerror(e);
}

//
// various stubs
//


// Ownership
//
// Just pretend that everyone is a superuser. NT will let us know if
// we don't really have permission to do something.
//

#define ROOT_UID	0
#define ROOT_GID	0

UIDTYPE
getuid(void)
{
	return ROOT_UID;
}

UIDTYPE
geteuid(void)
{
	return ROOT_UID;
}

GIDTYPE
getgid(void)
{
	return ROOT_GID;
}

GIDTYPE
getegid(void)
{
    return ROOT_GID;
}

int
setuid(int uid)
{ 
    return (uid == ROOT_UID ? 0 : -1);
}

int
setgid(int gid)
{
    return (gid == ROOT_GID ? 0 : -1);
}

//
// File system stuff
//

int
/* ioctl(int i, unsigned int u, char *data) */
#ifdef __BORLANDC__
  ioctl(int i, int u, ...)
#else
  ioctl(int i, unsigned int u, long data)
#endif
{
    return -1;
}

#undef FD_SET

void
rb_w32_fdset(int fd, fd_set *set)
{
    unsigned int i;
    SOCKET s = TO_SOCKET(fd);

    for (i = 0; i < set->fd_count; i++) {
        if (set->fd_array[i] == s) {
            return;
        }
    }
    if (i == set->fd_count) {
        if (set->fd_count < FD_SETSIZE) {
            set->fd_array[i] = s;
            set->fd_count++;
        }
    }
}

#undef FD_CLR

void
rb_w32_fdclr(int fd, fd_set *set)
{
    unsigned int i;
    SOCKET s = TO_SOCKET(fd);

    for (i = 0; i < set->fd_count; i++) {
        if (set->fd_array[i] == s) {
            while (i < set->fd_count - 1) {
                set->fd_array[i] = set->fd_array[i + 1];
                i++;
            }
            set->fd_count--;
            break;
        }
    }
}

#undef FD_ISSET

int
rb_w32_fdisset(int fd, fd_set *set)
{
       return __WSAFDIsSet(TO_SOCKET(fd), set);
}

//
// Networking trampolines
// These are used to avoid socket startup/shutdown overhead in case 
// the socket routines aren't used.
//

#undef select

static int NtSocketsInitialized = 0;

static int
extract_file_fd(fd_set *set, fd_set *fileset)
{
    int idx;

    fileset->fd_count = 0;
    if (!set)
	return 0;
    for (idx = 0; idx < set->fd_count; idx++) {
	SOCKET fd = set->fd_array[idx];

	if (!is_socket(fd)) {
	    int i;

	    for (i = 0; i < fileset->fd_count; i++) {
		if (fileset->fd_array[i] == fd) {
		    break;
		}
	    }
	    if (i == fileset->fd_count) {
		if (fileset->fd_count < FD_SETSIZE) {
		    fileset->fd_array[i] = fd;
		    fileset->fd_count++;
		}
	    }
	}
    }
    return fileset->fd_count;
}

long 
rb_w32_select (int nfds, fd_set *rd, fd_set *wr, fd_set *ex,
	       struct timeval *timeout)
{
    long r;
    fd_set file_rd;
    fd_set file_wr;
#ifdef USE_INTERRUPT_WINSOCK
    fd_set trap;
#endif /* USE_INTERRUPT_WINSOCK */
    int file_nfds;

    if (!NtSocketsInitialized) {
	StartSockets();
    }
    r = 0;
    if (rd && rd->fd_count > r) r = rd->fd_count;
    if (wr && wr->fd_count > r) r = wr->fd_count;
    if (ex && ex->fd_count > r) r = ex->fd_count;
    if (nfds > r) nfds = r;
    if (nfds == 0 && timeout) {
	Sleep(timeout->tv_sec * 1000 + timeout->tv_usec / 1000);
	return 0;
    }
    file_nfds = extract_file_fd(rd, &file_rd);
    file_nfds += extract_file_fd(wr, &file_wr);
    if (file_nfds)
    {
	// assume normal files are always readable/writable
	// fake read/write fd_set and return value
	if (rd) *rd = file_rd;
	if (wr) *wr = file_wr;
	return file_nfds;
    }

#if USE_INTERRUPT_WINSOCK
    if (ex)
	trap = *ex;
    else
	trap.fd_count = 0;
    if (trap.fd_count < FD_SETSIZE)
	trap.fd_array[trap.fd_count++] = (SOCKET)interrupted_event;
    // else unable to catch interrupt.
    ex = &trap;
#endif /* USE_INTERRUPT_WINSOCK */

    RUBY_CRITICAL({
	r = select(nfds, rd, wr, ex, timeout);
	if (r == SOCKET_ERROR) {
	    errno = WSAGetLastError() - WSABASEERR;
	}
    });
    return r;
}

static void
StartSockets ()
{
    WORD version;
    WSADATA retdata;
    int ret;
    int iSockOpt;
    
    //
    // initalize the winsock interface and insure that it's
    // cleaned up at exit.
    //
    version = MAKEWORD(1, 1);
    if (ret = WSAStartup(version, &retdata))
	rb_fatal ("Unable to locate winsock library!\n");
    if (LOBYTE(retdata.wVersion) != 1)
	rb_fatal("could not find version 1 of winsock dll\n");

    if (HIBYTE(retdata.wVersion) != 1)
	rb_fatal("could not find version 1 of winsock dll\n");

    atexit((void (*)(void)) WSACleanup);

#ifndef SO_SYNCHRONOUS_NONALERT
#define SO_SYNCHRONOUS_NONALERT 0x20
#endif

    iSockOpt = SO_SYNCHRONOUS_NONALERT;
    /*
     * Enable the use of sockets as filehandles
     */
#ifndef SO_OPENTYPE
#define SO_OPENTYPE     0x7008
#endif

    setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE,
	       (char *)&iSockOpt, sizeof(iSockOpt));

    main_thread.handle = GetCurrentThreadHandle();
    main_thread.id = GetCurrentThreadId();

    interrupted_event = CreateSignal();
    if (!interrupted_event)
	rb_fatal("Unable to create interrupt event!\n");
    NtSocketsInitialized = 1;
}

#undef accept

int
rb_w32_accept(int s, struct sockaddr *addr, int *addrlen)
{
    SOCKET r;

    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = accept(TO_SOCKET(s), addr, addrlen);
	if (r == INVALID_SOCKET) {
	    errno = WSAGetLastError() - WSABASEERR;
	    s = -1;
	}
	else {
	    s = rb_w32_open_osfhandle(r, O_RDWR|O_BINARY);
	}
    });
    return s;
}

#undef bind

int 
rb_w32_bind(int s, struct sockaddr *addr, int addrlen)
{
    int r;

    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = bind(TO_SOCKET(s), addr, addrlen);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef connect

int 
rb_w32_connect(int s, struct sockaddr *addr, int addrlen)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = connect(TO_SOCKET(s), addr, addrlen);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}


#undef getpeername

int 
rb_w32_getpeername(int s, struct sockaddr *addr, int *addrlen)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = getpeername(TO_SOCKET(s), addr, addrlen);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef getsockname

int 
rb_w32_getsockname(int s, struct sockaddr *addr, int *addrlen)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = getsockname(TO_SOCKET(s), addr, addrlen);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

int 
rb_w32_getsockopt(int s, int level, int optname, char *optval, int *optlen)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = getsockopt(TO_SOCKET(s), level, optname, optval, optlen);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef ioctlsocket

int 
rb_w32_ioctlsocket(int s, long cmd, u_long *argp)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = ioctlsocket(TO_SOCKET(s), cmd, argp);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef listen

int 
rb_w32_listen(int s, int backlog)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = listen(TO_SOCKET(s), backlog);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef recv

int 
rb_w32_recv(int s, char *buf, int len, int flags)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = recv(TO_SOCKET(s), buf, len, flags);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef recvfrom

int 
rb_w32_recvfrom(int s, char *buf, int len, int flags, 
		struct sockaddr *from, int *fromlen)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = recvfrom(TO_SOCKET(s), buf, len, flags, from, fromlen);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef send

int 
rb_w32_send(int s, char *buf, int len, int flags)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = send(TO_SOCKET(s), buf, len, flags);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef sendto

int 
rb_w32_sendto(int s, char *buf, int len, int flags, 
	      struct sockaddr *to, int tolen)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = sendto(TO_SOCKET(s), buf, len, flags, to, tolen);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef setsockopt

int 
rb_w32_setsockopt(int s, int level, int optname, char *optval, int optlen)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = setsockopt(TO_SOCKET(s), level, optname, optval, optlen);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}
    
#undef shutdown

int 
rb_w32_shutdown(int s, int how)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = shutdown(TO_SOCKET(s), how);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef socket

int 
rb_w32_socket(int af, int type, int protocol)
{
    SOCKET s;
    int fd;

    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	s = socket(af, type, protocol);
	if (s == INVALID_SOCKET) {
	    errno = WSAGetLastError() - WSABASEERR;
	    fd = -1;
	}
	else {
	    fd = rb_w32_open_osfhandle(s, O_RDWR|O_BINARY);
	}
    });
    return fd;
}

#undef gethostbyaddr

struct hostent *
rb_w32_gethostbyaddr (char *addr, int len, int type)
{
    struct hostent *r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = gethostbyaddr(addr, len, type);
	if (r == NULL)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef gethostbyname

struct hostent *
rb_w32_gethostbyname (char *name)
{
    struct hostent *r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = gethostbyname(name);
	if (r == NULL)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef gethostname

int
rb_w32_gethostname (char *name, int len)
{
    int r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = gethostname(name, len);
	if (r == SOCKET_ERROR)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef getprotobyname

struct protoent *
rb_w32_getprotobyname (char *name)
{
    struct protoent *r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = getprotobyname(name);
	if (r == NULL)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef getprotobynumber

struct protoent *
rb_w32_getprotobynumber (int num)
{
    struct protoent *r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = getprotobynumber(num);
	if (r == NULL)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef getservbyname

struct servent *
rb_w32_getservbyname (char *name, char *proto)
{
    struct servent *r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = getservbyname(name, proto);
	if (r == NULL)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

#undef getservbyport

struct servent *
rb_w32_getservbyport (int port, char *proto)
{
    struct servent *r;
    if (!NtSocketsInitialized) {
	StartSockets();
    }
    RUBY_CRITICAL({
	r = getservbyport(port, proto);
	if (r == NULL)
	    errno = WSAGetLastError() - WSABASEERR;
    });
    return r;
}

//
// Networking stubs
//

void endhostent() {}
void endnetent() {}
void endprotoent() {}
void endservent() {}

struct netent *getnetent (void) {return (struct netent *) NULL;}

struct netent *getnetbyaddr(char *name) {return (struct netent *)NULL;}

struct netent *getnetbyname(long net, int type) {return (struct netent *)NULL;}

struct protoent *getprotoent (void) {return (struct protoent *) NULL;}

struct servent *getservent (void) {return (struct servent *) NULL;}

void sethostent (int stayopen) {}

void setnetent (int stayopen) {}

void setprotoent (int stayopen) {}

void setservent (int stayopen) {}

#ifndef WNOHANG
#define WNOHANG -1
#endif

static pid_t
poll_child_status(struct ChildRecord *child, int *stat_loc)
{
    DWORD exitcode;
    DWORD err;

    if (!GetExitCodeProcess(child->hProcess, &exitcode)) {
	/* If an error occured, return immediatly. */
	err = GetLastError();
	if (err == ERROR_INVALID_PARAMETER)
	    errno = ECHILD;
	else
	    errno = map_errno();
	CloseChildHandle(child);
	return -1;
    }
    if (exitcode != STILL_ACTIVE) {
	/* If already died, return immediatly. */
	pid_t pid = child->pid;
	CloseChildHandle(child);
	if (stat_loc) *stat_loc = exitcode << 8;
	return pid;
    }
    return 0;
}

pid_t
waitpid (pid_t pid, int *stat_loc, int options)
{
    DWORD timeout;

    if (options == WNOHANG) {
	timeout = 0;
    } else {
	timeout = INFINITE;
    }

    if (pid == -1) {
	int count = 0;
	DWORD ret;
	HANDLE events[MAXCHILDNUM + 1];

	FOREACH_CHILD(child) {
	    if (!child->pid || child->pid < 0) continue;
	    if ((pid = poll_child_status(child, stat_loc))) return pid;
	    events[count++] = child->hProcess;
	} END_FOREACH_CHILD;
	if (!count) {
	    errno = ECHILD;
	    return -1;
	}
	events[count] = interrupted_event;

	ret = WaitForMultipleEvents(count + 1, events, FALSE, timeout, TRUE);
	if (ret == WAIT_TIMEOUT) return 0;
	if ((ret -= WAIT_OBJECT_0) == count) {
	    ResetSignal(interrupted_event);
	    errno = EINTR;
	    return -1;
	}
	if (ret > count) {
	    errno = map_errno();
	    return -1;
	}

	return poll_child_status(ChildRecord + ret, stat_loc);
    }
    else {
	struct ChildRecord* child = FindChildSlot(pid);
	if (!child) {
	    errno = ECHILD;
	    return -1;
	}

	while (!(pid = poll_child_status(child, stat_loc))) {
	    /* wait... */
	    if (wait_events(child->hProcess, timeout) != WAIT_OBJECT_0) {
		/* still active */
		pid = 0;
		break;
	    }
	}
    }

    return pid;
}

#include <sys/timeb.h>

int _cdecl
gettimeofday(struct timeval *tv, struct timezone *tz)
{
    SYSTEMTIME st;
    time_t t;
    struct tm tm;

    GetLocalTime(&st);
    tm.tm_sec = st.wSecond;
    tm.tm_min = st.wMinute;
    tm.tm_hour = st.wHour;
    tm.tm_mday = st.wDay;
    tm.tm_mon = st.wMonth - 1;
    tm.tm_year = st.wYear - 1900;
    tm.tm_isdst = -1;
    t = mktime(&tm);
    tv->tv_sec = t;
    tv->tv_usec = st.wMilliseconds * 1000;

    return 0;
}

char *
rb_w32_getcwd(buffer, size)
    char *buffer;
    int size;
{
    int length;
    char *bp;

#undef getcwd
    if (getcwd(buffer, size) == NULL) {
        return NULL;
    }
    length = strlen(buffer);
    if (length >= size) {
        return NULL;
    }

    for (bp = buffer; *bp != '\0'; bp = CharNext(bp)) {
	if (*bp == '\\') {
	    *bp = '/';
	}
    }
    return buffer;
}

int
chown(const char *path, int owner, int group)
{
	return 0;
}

int
kill(int pid, int sig)
{
    int ret = 0;
    DWORD err;

    if (pid <= 0) {
	errno = EINVAL;
	return -1;
    }

    if (IsWin95()) pid = -pid;
    if ((unsigned int)pid == GetCurrentProcessId() &&
	(sig != 0 && sig != SIGKILL)) {
	if ((ret = raise(sig)) != 0) {
	    /* MSVCRT doesn't set errno... */
	    errno = EINVAL;
	}
	return ret;
    }

    switch (sig) {
      case 0:
	RUBY_CRITICAL({
	    HANDLE hProc =
		OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, (DWORD)pid);
	    if (hProc == NULL || hProc == INVALID_HANDLE_VALUE) {
		if (GetLastError() == ERROR_INVALID_PARAMETER) {
		    errno = ESRCH;
		}
		else {
		    errno = EPERM;
		}
		ret = -1;
	    }
	    else {
		CloseHandle(hProc);
	    }
	});
	break;

      case SIGINT:
	RUBY_CRITICAL({
	    if (!GenerateConsoleCtrlEvent(CTRL_C_EVENT, (DWORD)pid)) {
		if ((err = GetLastError()) == 0)
		    errno = EPERM;
		else
		    errno = map_errno();
		ret = -1;
	    }
	});
	break;

      case SIGKILL:
	RUBY_CRITICAL({
	    HANDLE hProc = OpenProcess(PROCESS_TERMINATE, FALSE, (DWORD)pid);
	    if (hProc == NULL || hProc == INVALID_HANDLE_VALUE) {
		if (GetLastError() == ERROR_INVALID_PARAMETER) {
		    errno = ESRCH;
		}
		else {
		    errno = EPERM;
		}
		ret = -1;
	    }
	    else {
		if (!TerminateProcess(hProc, 0)) {
		    errno = EPERM;
		    ret = -1;
		}
		CloseHandle(hProc);
	    }
	});
	break;

      default:
	errno = EINVAL;
	ret = -1;
	break;
    }

    return ret;
}

int
link(char *from, char *to)
{
    static BOOL (WINAPI *pCreateHardLink)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES) = NULL;
    static int myerrno = 0;

    if (!pCreateHardLink && !myerrno) {
	HANDLE hKernel;

	hKernel = GetModuleHandle("kernel32.dll");
	if (hKernel) {
	    pCreateHardLink = (BOOL (WINAPI *)(LPCTSTR, LPCTSTR, LPSECURITY_ATTRIBUTES))GetProcAddress(hKernel, "CreateHardLinkA");
	    if (!pCreateHardLink) {
		myerrno = map_errno();
	    }
	    CloseHandle(hKernel);
	}
	else {
	    myerrno = map_errno();
	}
    }
    if (!pCreateHardLink) {
	errno = myerrno;
	return -1;
    }

    if (!pCreateHardLink(to, from, NULL)) {
	errno = map_errno();
	return -1;
    }

    return 0;
}

int
wait()
{
	return 0;
}

char *
rb_w32_getenv(const char *name)
{
    static char *curitem = NULL;
    static DWORD curlen = 0;
    DWORD needlen;

    if (curitem == NULL || curlen == 0) {
	curlen = 512;
	curitem = ALLOC_N(char, curlen);
    }

    needlen = GetEnvironmentVariable(name, curitem, curlen);
    if (needlen != 0) {
	while (needlen > curlen) {
	    REALLOC_N(curitem, char, needlen);
	    curlen = needlen;
	    needlen = GetEnvironmentVariable(name, curitem, curlen);
	}
    }
    else {
	return NULL;
    }

    return curitem;
}

int
rb_w32_rename(const char *oldpath, const char *newpath)
{
    int res = 0;
    int oldatts;
    int newatts;

    oldatts = GetFileAttributes(oldpath);
    newatts = GetFileAttributes(newpath);

    if (oldatts == -1) {
	errno = map_errno();
	return -1;
    }

    RUBY_CRITICAL({
	if (newatts != -1 && newatts & FILE_ATTRIBUTE_READONLY)
	    SetFileAttributesA(newpath, newatts & ~ FILE_ATTRIBUTE_READONLY);

	if (!MoveFile(oldpath, newpath))
	    res = -1;

	if (res) {
	    switch (GetLastError()) {
	      case ERROR_ALREADY_EXISTS:
	      case ERROR_FILE_EXISTS:
		if (IsWinNT()) {
		    if (MoveFileEx(oldpath, newpath, MOVEFILE_REPLACE_EXISTING))
			res = 0;
		} else {
		    for (;;) {
			if (!DeleteFile(newpath) && GetLastError() != ERROR_FILE_NOT_FOUND)
			    break;
			else if (MoveFile(oldpath, newpath)) {
			    res = 0;
			    break;
			}
		    }
		}
	    }
	}

	if (res)
	    errno = map_errno();
	else
	    SetFileAttributes(newpath, oldatts);
    });

    return res;
}

static int
isUNCRoot(const char *path)
{
    if (path[0] == '\\' && path[1] == '\\') {
	const char *p;
	for (p = path + 3; *p; p = CharNext(p)) {
	    if (*p == '\\')
		break;
	}
	if (p[0] && p[1]) {
	    for (p++; *p; p = CharNext(p)) {
		if (*p == '\\')
		    break;
	    }
	    if (!p[0] || !p[1])
		return 1;
	}
    }
    return 0;
}

int
rb_w32_stat(const char *path, struct stat *st)
{
    const char *p;
    char *buf1, *buf2, *s;
    int len;
    int ret;

    if (!path || !st) {
	errno = EFAULT;
	return -1;
    }
    buf1 = ALLOCA_N(char, strlen(path) + 2);
    for (p = path, s = buf1; *p; p++, s++) {
	if (*p == '/')
	    *s = '\\';
	else
	    *s = *p;
    }
    *s = '\0';
    len = s - buf1;
    if (!len || '\"' == *(--s)) {
	errno = ENOENT;
	return -1;
    }
    p = CharPrev(buf1, buf1 + len);

    if (isUNCRoot(buf1)) {
	if (*p != '\\')
	    strcat(buf1, "\\");
    } else if (*p == '\\' || *p == ':')
	strcat(buf1, ".");
    buf2 = ALLOCA_N(char, MAXPATHLEN);
    if (_fullpath(buf2, buf1, MAXPATHLEN)) {
	ret = stat(buf2, st);
	if (ret == 0) {
	    st->st_mode &= ~(S_IWGRP | S_IWOTH);
	}
	return ret;
    }
    else
	return -1;
}

static long
filetime_to_clock(FILETIME *ft)
{
    __int64 qw = ft->dwHighDateTime;
    qw <<= 32;
    qw |= ft->dwLowDateTime;
    qw /= 10000;  /* File time ticks at 0.1uS, clock at 1mS */
    return (long) qw;
}

int
rb_w32_times(struct tms *tmbuf)
{
    FILETIME create, exit, kernel, user;

    if (GetProcessTimes(GetCurrentProcess(),&create, &exit, &kernel, &user)) {
	tmbuf->tms_utime = filetime_to_clock(&user);
	tmbuf->tms_stime = filetime_to_clock(&kernel);
	tmbuf->tms_cutime = 0;
	tmbuf->tms_cstime = 0;
    }
    else {
	tmbuf->tms_utime = clock();
	tmbuf->tms_stime = 0;
	tmbuf->tms_cutime = 0;
	tmbuf->tms_cstime = 0;
    }
    return 0;
}

#undef Sleep
#define yield_once() Sleep(0)
#define yield_until(condition) do yield_once(); while (!(condition))

static DWORD wait_events(HANDLE event, DWORD timeout)
{
    HANDLE events[2];
    int count = 0;
    DWORD ret;

    if (event) {
	events[count++] = event;
    }
    events[count++] = interrupted_event;

    ret = WaitForMultipleEvents(count, events, FALSE, timeout, TRUE);

    if (ret == WAIT_OBJECT_0 + count - 1) {
	ResetSignal(interrupted_event);
	errno = EINTR;
    }

    return ret;
}

static CRITICAL_SECTION* system_state(void)
{
    static int initialized = 0;
    static CRITICAL_SECTION syssect;

    if (!initialized) {
	InitializeCriticalSection(&syssect);
	initialized = 1;
    }
    return &syssect;
}

static LONG flag_interrupt = -1;
static volatile DWORD tlsi_interrupt = TLS_OUT_OF_INDEXES;

void rb_w32_enter_critical(void)
{
    if (IsWinNT()) {
	EnterCriticalSection(system_state());
	return;
    }

    if (tlsi_interrupt == TLS_OUT_OF_INDEXES) {
	tlsi_interrupt = TlsAlloc();
    }

    {
	DWORD ti = (DWORD)TlsGetValue(tlsi_interrupt);
	while (InterlockedIncrement(&flag_interrupt) > 0 && !ti) {
	    InterlockedDecrement(&flag_interrupt);
	    Sleep(1);
	}
	TlsSetValue(tlsi_interrupt, (PVOID)++ti);
    }
}

void rb_w32_leave_critical(void)
{
    if (IsWinNT()) {
	LeaveCriticalSection(system_state());
	return;
    }

    InterlockedDecrement(&flag_interrupt);
    TlsSetValue(tlsi_interrupt, (PVOID)((DWORD)TlsGetValue(tlsi_interrupt) - 1));
}

struct handler_arg_t {
    void (*handler)(int);
    int arg;
    int status;
    int finished;
    HANDLE handshake;
};

static void rb_w32_call_handler(struct handler_arg_t* h)
{
    int status;
    RUBY_CRITICAL(rb_protect((VALUE (*)(VALUE))h->handler, (VALUE)h->arg, &h->status);
		  status = h->status;
		  SetEvent(h->handshake));
    if (status) {
	rb_jump_tag(status);
    }
    h->finished = 1;
    Sleep(INFINITE);		/* safe on Win95? */
}

static struct handler_arg_t* setup_handler(struct handler_arg_t *harg,
					   int arg,
					   void (*handler)(int),
					   HANDLE handshake)
{
    harg->handler = handler;
    harg->arg = arg;
    harg->status = 0;
    harg->finished = 0;
    harg->handshake = handshake;
    return harg;
}

static void setup_call(CONTEXT* ctx, struct handler_arg_t *harg)
{
#ifdef _M_IX86
    DWORD *esp = (DWORD *)ctx->Esp;
    *--esp = (DWORD)harg;
    *--esp = ctx->Eip;
    ctx->Esp = (DWORD)esp;
    ctx->Eip = (DWORD)rb_w32_call_handler;
#else
#ifndef _WIN32_WCE
#error unsupported processor
#endif
#endif
}

int rb_w32_main_context(int arg, void (*handler)(int))
{
    static HANDLE interrupt_done = NULL;
    struct handler_arg_t harg;
    CONTEXT ctx_orig;
    HANDLE current_thread = GetCurrentThread();
    int old_priority = GetThreadPriority(current_thread);

    if (GetCurrentThreadId() == main_thread.id) return FALSE;

    SetSignal(interrupted_event);

    RUBY_CRITICAL({		/* the main thread must be in user state */
	CONTEXT ctx;

	SuspendThread(main_thread.handle);
	SetThreadPriority(current_thread, GetThreadPriority(main_thread.handle));

	ZeroMemory(&ctx, sizeof(CONTEXT));
	ctx.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
	GetThreadContext(main_thread.handle, &ctx);
	ctx_orig = ctx;

	/* handler context setup */
	if (!interrupt_done) {
	    interrupt_done = CreateEvent(NULL, FALSE, FALSE, NULL);
	    /* anonymous one-shot event */
	}
	else {
	    ResetEvent(interrupt_done);
	}
	setup_call(&ctx, setup_handler(&harg, arg, handler, interrupt_done));

	ctx.ContextFlags = CONTEXT_CONTROL;
	SetThreadContext(main_thread.handle, &ctx);
	ResumeThread(main_thread.handle);
    });

    /* give a chance to the main thread */
    yield_once();
    WaitForSingleObject(interrupt_done, INFINITE); /* handshaking */

    if (!harg.status) {
	/* no exceptions raised, restore old context. */
	RUBY_CRITICAL({
	    /* ensure the main thread is in user state. */
	    yield_until(harg.finished);

	    SuspendThread(main_thread.handle);
	    ctx_orig.ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT;
	    SetThreadContext(main_thread.handle, &ctx_orig);
	    ResumeThread(main_thread.handle);
	});
    }
    /* otherwise leave the main thread raised */

    SetThreadPriority(current_thread, old_priority);

    return TRUE;
}

int rb_w32_sleep(unsigned long msec)
{
    DWORD ret;
    RUBY_CRITICAL(ret = wait_events(NULL, msec));
    yield_once();
    CHECK_INTS;
    return ret != WAIT_TIMEOUT;
}

static void catch_interrupt(void)
{
    yield_once();
    RUBY_CRITICAL(wait_events(NULL, 0));
    CHECK_INTS;
}

#undef fgetc
int rb_w32_getc(FILE* stream)
{
    int c, trap_immediate = rb_trap_immediate;
#ifndef _WIN32_WCE
    if (enough_to_get(stream->FILE_COUNT)) {
	c = (unsigned char)*stream->FILE_READPTR++;
	rb_trap_immediate = trap_immediate;
    }
    else 
#endif
    {
	c = _filbuf(stream);
#if defined __BORLANDC__ || defined _WIN32_WCE
        if ((c == EOF) && (errno == EPIPE)) {
	    clearerr(stream);
        }
#endif
	rb_trap_immediate = trap_immediate;
	catch_interrupt();
    }
    return c;
}

#undef fputc
int rb_w32_putc(int c, FILE* stream)
{
    int trap_immediate = rb_trap_immediate;
#ifndef _WIN32_WCE
    if (enough_to_put(stream->FILE_COUNT)) {
	c = (unsigned char)(*stream->FILE_READPTR++ = (char)c);
	rb_trap_immediate = trap_immediate;
    }
    else 
#endif
    {
	c = _flsbuf(c, stream);
	rb_trap_immediate = trap_immediate;
	catch_interrupt();
    }
    return c;
}

struct asynchronous_arg_t {
    /* output field */
    void* stackaddr;
    int errnum;

    /* input field */
    VALUE (*func)(VALUE self, int argc, VALUE* argv);
    VALUE self;
    int argc;
    VALUE* argv;
};

static DWORD WINAPI
call_asynchronous(PVOID argp)
{
    DWORD ret;
    struct asynchronous_arg_t *arg = argp;
    arg->stackaddr = &argp;
    ret = (DWORD)arg->func(arg->self, arg->argc, arg->argv);
    arg->errnum = errno;
    return ret;
}

VALUE
rb_w32_asynchronize(asynchronous_func_t func, VALUE self,
		    int argc, VALUE* argv, VALUE intrval)
{
    DWORD val;
    BOOL interrupted = FALSE;
    HANDLE thr;

    RUBY_CRITICAL({
	struct asynchronous_arg_t arg;

	arg.stackaddr = NULL;
	arg.errnum = 0;
	arg.func = func;
	arg.self = self;
	arg.argc = argc;
	arg.argv = argv;

	thr = CreateThread(NULL, 0, call_asynchronous, &arg, 0, &val);

	if (thr) {
	    yield_until(arg.stackaddr);

	    if (wait_events(thr, INFINITE) != WAIT_OBJECT_0) {
		interrupted = TRUE;

		if (TerminateThread(thr, intrval)) {
		    yield_once();
		}
	    }

	    GetExitCodeThread(thr, &val);
	    CloseHandle(thr);

	    if (interrupted) {
		/* must release stack of killed thread, why doesn't Windows? */
		MEMORY_BASIC_INFORMATION m;

		memset(&m, 0, sizeof(m));
		if (!VirtualQuery(arg.stackaddr, &m, sizeof(m))) {
		    Debug(fprintf(stderr, "couldn't get stack base:%p:%d\n",
				  arg.stackaddr, GetLastError()));
		}
		else if (!VirtualFree(m.AllocationBase, 0, MEM_RELEASE)) {
		    Debug(fprintf(stderr, "couldn't release stack:%p:%d\n",
				  m.AllocationBase, GetLastError()));
		}
		errno = EINTR;
	    }
	    else {
		errno = arg.errnum;
	    }
	}
    });

    if (!thr) {
	rb_fatal("failed to launch waiter thread:%d", GetLastError());
    }

    if (interrupted) {
	CHECK_INTS;
    }

    return val;
}

char **rb_w32_get_environ(void)
{
    char *envtop, *env;
    char **myenvtop, **myenv;
    int num;

    /*
     * We avoid values started with `='. If you want to deal those values,
     * change this function, and some functions in hash.c which recognize
     * `=' as delimiter or rb_w32_getenv() and ruby_setenv().
     * CygWin deals these values by changing first `=' to '!'. But we don't
     * use such trick and follow cmd.exe's way that just doesn't show these
     * values.
     * (U.N. 2001-11-15)
     */
    envtop = GetEnvironmentStrings();
    for (env = envtop, num = 0; *env; env += strlen(env) + 1)
	if (*env != '=') num++;

    myenvtop = ALLOC_N(char*, num + 1);
    for (env = envtop, myenv = myenvtop; *env; env += strlen(env) + 1) {
	if (*env != '=') {
	    *myenv = ALLOC_N(char, strlen(env) + 1);
	    strcpy(*myenv, env);
	    myenv++;
	}
    }
    *myenv = NULL;
    FreeEnvironmentStrings(envtop);

    return myenvtop;
}

void rb_w32_free_environ(char **env)
{
    char **t = env;

    while (*t) free(*t++);
    free(env);
}

pid_t rb_w32_getpid(void)
{
    pid_t pid;

    pid = _getpid();
    if (IsWin95()) pid = -pid;

    return pid;
}

int
rb_w32_fclose(FILE *fp)
{
    int fd = fileno(fp);
    SOCKET sock = TO_SOCKET(fd);

    if (fflush(fp)) return -1;
    if (!is_socket(sock)) {
	UnlockFile((HANDLE)sock, 0, 0, LK_LEN, LK_LEN);
	return fclose(fp);
    }
    _set_osfhnd(fd, (SOCKET)INVALID_HANDLE_VALUE);
    fclose(fp);
    if (closesocket(sock) == SOCKET_ERROR) {
	errno = WSAGetLastError() - WSABASEERR;
	return -1;
    }
    return 0;
}

int
rb_w32_close(int fd)
{
    SOCKET sock = TO_SOCKET(fd);

    if (!is_socket(sock)) {
	UnlockFile((HANDLE)sock, 0, 0, LK_LEN, LK_LEN);
	return _close(fd);
    }
    if (closesocket(sock) == SOCKET_ERROR) {
	errno = WSAGetLastError() - WSABASEERR;
	return -1;
    }
    return 0;
}

static int
unixtime_to_filetime(time_t time, FILETIME *ft)
{
    struct tm *tm;
    SYSTEMTIME st;

    tm = gmtime(&time);
    st.wYear = tm->tm_year + 1900;
    st.wMonth = tm->tm_mon + 1;
    st.wDayOfWeek = tm->tm_wday;
    st.wDay = tm->tm_mday;
    st.wHour = tm->tm_hour;
    st.wMinute = tm->tm_min;
    st.wSecond = tm->tm_sec;
    st.wMilliseconds = 0;
    if (!SystemTimeToFileTime(&st, ft)) {
	errno = map_errno();
	return -1;
    }
    return 0;
}

#undef utime
#ifdef __BORLANDC__
#define utime _utime
#endif
int
rb_w32_utime(const char *path, struct utimbuf *times)
{
    HANDLE hFile;
    SYSTEMTIME st;
    FILETIME atime, mtime;
    struct tm *tm;
    struct stat stat;
    int ret = 0;

    if (rb_w32_stat(path, &stat)) {
	return -1;
    }

    if (unixtime_to_filetime(times->actime, &atime)) {
	return -1;
    }
    if (unixtime_to_filetime(times->modtime, &mtime)) {
	return -1;
    }

    RUBY_CRITICAL({
	hFile = CreateFile(path, GENERIC_WRITE, 0, 0, OPEN_EXISTING,
			   IsWin95() ? 0 : FILE_FLAG_BACKUP_SEMANTICS, 0);
	if (hFile == INVALID_HANDLE_VALUE) {
	    errno = map_errno();
	    ret = -1;
	}
	else {
	    if (!SetFileTime(hFile, NULL, &atime, &mtime)) {
		errno = map_errno();
		ret = -1;
	    }
	    CloseHandle(hFile);
	}
    });

    return ret;
}

int
rb_w32_vsnprintf(char *buf, size_t size, const char *format, va_list va)
{
    int ret = _vsnprintf(buf, size, format, va);
    if (size > 0) buf[size - 1] = 0;
    return ret;
}

int
rb_w32_snprintf(char *buf, size_t size, const char *format, ...)
{
    int ret;
    va_list va;

    va_start(va, format);
    ret = vsnprintf(buf, size, format, va);
    va_end(va);
    return ret;
}
