root/win32/win32.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. rb_w32_map_errno
  2. get_version
  3. rb_w32_osid
  4. rb_w32_osver
  5. flock_winnt
  6. flock
  7. translate_wchar
  8. translate_char
  9. get_special_folder
  10. regulate_path
  11. get_proc_address
  12. rb_w32_special_folder
  13. rb_w32_system_tmpdir
  14. init_env
  15. invalid_parameter
  16. rtc_error_handler
  17. free_conlist
  18. constat_delete
  19. exit_handler
  20. StartSockets
  21. socklist_insert
  22. socklist_lookup
  23. socklist_delete
  24. rb_w32_sysinit
  25. getlogin
  26. FindChildSlot
  27. FindChildSlotByHandle
  28. CloseChildHandle
  29. FindFreeChildSlot
  30. internal_match
  31. is_command_com
  32. is_internal_cmd
  33. internal_cmd_match
  34. rb_w32_get_osfhandle
  35. join_argv
  36. check_spawn_mode
  37. child_result
  38. CreateChild
  39. is_batch
  40. w32_spawn
  41. rb_w32_spawn
  42. rb_w32_uspawn
  43. w32_aspawn_flags
  44. rb_w32_aspawn_flags
  45. rb_w32_uaspawn_flags
  46. rb_w32_aspawn
  47. rb_w32_uaspawn
  48. insert
  49. cmdglob
  50. has_redirection
  51. skipspace
  52. w32_cmdvector
  53. get_final_path
  54. open_special
  55. open_dir_handle
  56. w32_wopendir
  57. filecp
  58. rb_w32_wstr_to_mbstr
  59. rb_w32_mbstr_to_wstr
  60. rb_w32_opendir
  61. rb_w32_uopendir
  62. move_to_next_entry
  63. win32_direct_conv
  64. rb_w32_conv_from_wchar
  65. rb_w32_conv_from_wstr
  66. ruby_direct_conv
  67. readdir_internal
  68. rb_w32_readdir
  69. rb_w32_telldir
  70. rb_w32_seekdir
  71. rb_w32_rewinddir
  72. rb_w32_closedir
  73. set_pioinfo_extra
  74. _pioinfo
  75. rb_w32_io_cancelable_p
  76. rb_w32_open_osfhandle
  77. init_stdhandle
  78. is_socket
  79. rb_w32_is_socket
  80. rb_w32_strerror
  81. getuid
  82. geteuid
  83. getgid
  84. getegid
  85. setuid
  86. setgid
  87. ioctl
  88. rb_w32_fdset
  89. rb_w32_fdclr
  90. rb_w32_fdisset
  91. rb_w32_fd_copy
  92. rb_w32_fd_dup
  93. extract_fd
  94. copy_fd
  95. is_not_socket
  96. is_pipe
  97. is_readable_pipe
  98. is_console
  99. is_readable_console
  100. is_invalid_handle
  101. do_select
  102. rb_w32_time_subtract
  103. compare
  104. rb_w32_select_with_thread
  105. rb_w32_select
  106. get_wsa_extension_function
  107. rb_w32_accept
  108. rb_w32_bind
  109. rb_w32_connect
  110. rb_w32_getpeername
  111. rb_w32_getsockname
  112. rb_w32_getsockopt
  113. rb_w32_ioctlsocket
  114. rb_w32_listen
  115. finish_overlapped_socket
  116. overlapped_socket_io
  117. rb_w32_recv
  118. rb_w32_recvfrom
  119. rb_w32_send
  120. rb_w32_sendto
  121. recvmsg
  122. sendmsg
  123. rb_w32_setsockopt
  124. rb_w32_shutdown
  125. open_ifs_socket
  126. rb_w32_socket
  127. rb_w32_gethostbyaddr
  128. rb_w32_gethostbyname
  129. rb_w32_gethostname
  130. rb_w32_getprotobyname
  131. rb_w32_getprotobynumber
  132. rb_w32_getservbyname
  133. rb_w32_getservbyport
  134. socketpair_internal
  135. socketpair
  136. str2guid
  137. getifaddrs
  138. freeifaddrs
  139. endhostent
  140. endnetent
  141. endprotoent
  142. endservent
  143. getnetent
  144. getnetbyaddr
  145. getnetbyname
  146. getprotoent
  147. getservent
  148. sethostent
  149. setnetent
  150. setprotoent
  151. setservent
  152. setfl
  153. dupfd
  154. fcntl
  155. rb_w32_set_nonblock
  156. poll_child_status
  157. waitpid
  158. filetime_to_timeval
  159. gettimeofday
  160. clock_gettime
  161. clock_getres
  162. rb_w32_getcwd
  163. chown
  164. rb_w32_uchown
  165. lchown
  166. rb_w32_ulchown
  167. kill
  168. wlink
  169. rb_w32_ulink
  170. link
  171. reparse_symlink
  172. rb_w32_reparse_symlink_p
  173. rb_w32_read_reparse_point
  174. rb_strlen_lit
  175. w32_readlink
  176. rb_w32_ureadlink
  177. readlink
  178. w32_symlink
  179. rb_w32_usymlink
  180. symlink
  181. wait
  182. w32_getenv
  183. rb_w32_ugetenv
  184. rb_w32_getenv
  185. get_attr_vsn
  186. wrename
  187. rb_w32_urename
  188. rb_w32_rename
  189. isUNCRoot
  190. stati64_set_inode
  191. stati64_set_inode_handle
  192. rb_w32_fstat
  193. rb_w32_fstati64
  194. stati64_handle
  195. filetime_to_unixtime
  196. fileattr_to_unixmode
  197. check_valid_dir
  198. stat_by_find
  199. path_drive
  200. winnt_stat
  201. winnt_lstat
  202. rb_w32_stat
  203. wstati64
  204. wlstati64
  205. name_for_stat
  206. rb_w32_ustati64
  207. rb_w32_stati64
  208. w32_stati64
  209. rb_w32_ulstati64
  210. rb_w32_lstati64
  211. w32_lstati64
  212. rb_w32_access
  213. rb_w32_uaccess
  214. rb_chsize
  215. w32_truncate
  216. rb_w32_utruncate
  217. rb_w32_truncate
  218. rb_w32_ftruncate
  219. filetime_to_clock
  220. rb_w32_times
  221. call_asynchronous
  222. rb_w32_asynchronize
  223. rb_w32_get_environ
  224. rb_w32_free_environ
  225. rb_w32_getpid
  226. rb_w32_getppid
  227. rb_w32_dup2
  228. rb_w32_uopen
  229. check_if_wdir
  230. rb_w32_open
  231. rb_w32_wopen
  232. w32_wopen
  233. rb_w32_fclose
  234. rb_w32_pipe
  235. console_emulator_p
  236. constat_handle
  237. constat_reset
  238. constat_attr
  239. constat_apply
  240. constat_parse
  241. rb_w32_close
  242. setup_overlapped
  243. finish_overlapped
  244. rb_w32_read
  245. rb_w32_write
  246. rb_w32_write_console
  247. unixtime_to_filetime
  248. wutime
  249. rb_w32_uutime
  250. rb_w32_utime
  251. rb_w32_uchdir
  252. wmkdir
  253. rb_w32_umkdir
  254. rb_w32_mkdir
  255. wrmdir
  256. rb_w32_rmdir
  257. rb_w32_urmdir
  258. wunlink
  259. rb_w32_uunlink
  260. rb_w32_unlink
  261. rb_w32_uchmod
  262. fchmod
  263. rb_w32_isatty
  264. _ftol2
  265. _ftol2_sse
  266. signbit
  267. rb_w32_inet_ntop
  268. rb_w32_inet_pton
  269. rb_w32_fd_is_text
  270. unixtime_to_systemtime
  271. systemtime_to_tm
  272. systemtime_to_localtime
  273. gmtime_r
  274. localtime_r
  275. rb_w32_wrap_io_handle
  276. rb_w32_unwrap_io_handle
  277. rb_w32_pow

/*
 *  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.
 *
 */
/*
  The parts licensed under above copyright notice are marked as "Artistic or
  GPL".
  Another parts are licensed under Ruby's License.

  Copyright (C) 1993-2011 Yukihiro Matsumoto
  Copyright (C) 2000  Network Applied Communication Laboratory, Inc.
  Copyright (C) 2000  Information-technology Promotion Agency, Japan
 */

#undef __STRICT_ANSI__

#include "ruby/ruby.h"
#include "ruby/encoding.h"
#include "ruby/util.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 <ctype.h>

#include <windows.h>
#include <winbase.h>
#include <wincon.h>
#include <share.h>
#include <shlobj.h>
#include <mbstring.h>
#include <shlwapi.h>
#if _MSC_VER >= 1400
#include <crtdbg.h>
#include <rtcapi.h>
#endif
#ifdef __MINGW32__
#include <mswsock.h>
#endif
#include "ruby/win32.h"
#include "win32/dir.h"
#include "win32/file.h"
#include "internal.h"
#include "encindex.h"
#define isdirsep(x) ((x) == '/' || (x) == '\\')

#if defined _MSC_VER && _MSC_VER <= 1200
# define CharNextExA(cp, p, flags) CharNextExA((WORD)(cp), (p), (flags))
#endif

static int w32_wopen(const WCHAR *file, int oflag, int perm);
static int w32_stati64(const char *path, struct stati64 *st, UINT cp);
static int w32_lstati64(const char *path, struct stati64 *st, UINT cp);
static char *w32_getenv(const char *name, UINT cp);

#undef getenv
#define DLN_FIND_EXTRA_ARG_DECL ,UINT cp
#define DLN_FIND_EXTRA_ARG ,cp
#define rb_w32_stati64(path, st) w32_stati64(path, st, cp)
#define getenv(name) w32_getenv(name, cp)
#undef CharNext
#define CharNext(p) CharNextExA(cp, (p), 0)
#define dln_find_exe_r rb_w32_udln_find_exe_r
#define dln_find_file_r rb_w32_udln_find_file_r
#include "dln.h"
#include "dln_find.c"
#undef MAXPATHLEN
#undef rb_w32_stati64
#undef dln_find_exe_r
#undef dln_find_file_r
#define dln_find_exe_r(fname, path, buf, size) rb_w32_udln_find_exe_r(fname, path, buf, size, cp)
#define dln_find_file_r(fname, path, buf, size) rb_w32_udln_find_file_r(fname, path, buf, size, cp)
#undef CharNext                 /* no default cp version */

#ifndef PATH_MAX
# if defined MAX_PATH
#   define PATH_MAX MAX_PATH
# elif defined HAVE_SYS_PARAM_H
#   include <sys/param.h>
#   define PATH_MAX MAXPATHLEN
# endif
#endif
#define ENV_MAX 512

#undef stat
#undef fclose
#undef close
#undef setsockopt
#undef dup2
#undef strdup

#if RUBY_MSVCRT_VERSION >= 140
# define _filbuf _fgetc_nolock
# define _flsbuf _fputc_nolock
#endif
#define enough_to_get(n) (--(n) >= 0)
#define enough_to_put(n) (--(n) >= 0)

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

#define TO_SOCKET(x)    _get_osfhandle(x)

int rb_w32_reparse_symlink_p(const WCHAR *path);

static struct ChildRecord *CreateChild(const WCHAR *, const WCHAR *, SECURITY_ATTRIBUTES *, HANDLE, HANDLE, HANDLE, DWORD);
static int has_redirection(const char *, UINT);
int rb_w32_wait_events(HANDLE *events, int num, DWORD timeout);
static int rb_w32_open_osfhandle(intptr_t osfhandle, int flags);
static int wstati64(const WCHAR *path, struct stati64 *st);
static int wlstati64(const WCHAR *path, struct stati64 *st);
VALUE rb_w32_conv_from_wchar(const WCHAR *wstr, rb_encoding *enc);
int ruby_brace_glob_with_enc(const char *str, int flags, ruby_glob_func *func, VALUE arg, rb_encoding *enc);
static FARPROC get_proc_address(const char *module, const char *func, HANDLE *mh);

#define RUBY_CRITICAL if (0) {} else /* just remark */

/* 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_DIRECTORY,                ENOTDIR         },
    {   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          },
#ifndef ERROR_PIPE_LOCAL
#define ERROR_PIPE_LOCAL        229L
#endif
    {   ERROR_PIPE_LOCAL,               EPIPE           },
    {   ERROR_BAD_PIPE,                 EPIPE           },
    {   ERROR_PIPE_BUSY,                EAGAIN          },
    {   ERROR_NO_DATA,                  EPIPE           },
    {   ERROR_PIPE_NOT_CONNECTED,       EPIPE           },
    {   ERROR_OPERATION_ABORTED,        EINTR           },
    {   ERROR_NOT_ENOUGH_QUOTA,         ENOMEM          },
    {   ERROR_MOD_NOT_FOUND,            ENOENT          },
    {   ERROR_PRIVILEGE_NOT_HELD,       EACCES,         },
    {   ERROR_CANT_RESOLVE_FILENAME,    ELOOP,          },
    {   WSAEINTR,                       EINTR           },
    {   WSAEBADF,                       EBADF           },
    {   WSAEACCES,                      EACCES          },
    {   WSAEFAULT,                      EFAULT          },
    {   WSAEINVAL,                      EINVAL          },
    {   WSAEMFILE,                      EMFILE          },
    {   WSAEWOULDBLOCK,                 EWOULDBLOCK     },
    {   WSAEINPROGRESS,                 EINPROGRESS     },
    {   WSAEALREADY,                    EALREADY        },
    {   WSAENOTSOCK,                    ENOTSOCK        },
    {   WSAEDESTADDRREQ,                EDESTADDRREQ    },
    {   WSAEMSGSIZE,                    EMSGSIZE        },
    {   WSAEPROTOTYPE,                  EPROTOTYPE      },
    {   WSAENOPROTOOPT,                 ENOPROTOOPT     },
    {   WSAEPROTONOSUPPORT,             EPROTONOSUPPORT },
    {   WSAESOCKTNOSUPPORT,             ESOCKTNOSUPPORT },
    {   WSAEOPNOTSUPP,                  EOPNOTSUPP      },
    {   WSAEPFNOSUPPORT,                EPFNOSUPPORT    },
    {   WSAEAFNOSUPPORT,                EAFNOSUPPORT    },
    {   WSAEADDRINUSE,                  EADDRINUSE      },
    {   WSAEADDRNOTAVAIL,               EADDRNOTAVAIL   },
    {   WSAENETDOWN,                    ENETDOWN        },
    {   WSAENETUNREACH,                 ENETUNREACH     },
    {   WSAENETRESET,                   ENETRESET       },
    {   WSAECONNABORTED,                ECONNABORTED    },
    {   WSAECONNRESET,                  ECONNRESET      },
    {   WSAENOBUFS,                     ENOBUFS         },
    {   WSAEISCONN,                     EISCONN         },
    {   WSAENOTCONN,                    ENOTCONN        },
    {   WSAESHUTDOWN,                   ESHUTDOWN       },
    {   WSAETOOMANYREFS,                ETOOMANYREFS    },
    {   WSAETIMEDOUT,                   ETIMEDOUT       },
    {   WSAECONNREFUSED,                ECONNREFUSED    },
    {   WSAELOOP,                       ELOOP           },
    {   WSAENAMETOOLONG,                ENAMETOOLONG    },
    {   WSAEHOSTDOWN,                   EHOSTDOWN       },
    {   WSAEHOSTUNREACH,                EHOSTUNREACH    },
    {   WSAEPROCLIM,                    EPROCLIM        },
    {   WSAENOTEMPTY,                   ENOTEMPTY       },
    {   WSAEUSERS,                      EUSERS          },
    {   WSAEDQUOT,                      EDQUOT          },
    {   WSAESTALE,                      ESTALE          },
    {   WSAEREMOTE,                     EREMOTE         },
};

/* License: Ruby's */
int
rb_w32_map_errno(DWORD winerr)
{
    int i;

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

    for (i = 0; i < (int)(sizeof(errmap) / sizeof(*errmap)); i++) {
        if (errmap[i].winerr == winerr) {
            return errmap[i].err;
        }
    }

    if (winerr >= WSABASEERR) {
        return winerr;
    }
    return EINVAL;
}

#define map_errno rb_w32_map_errno

static const char *NTLoginName;

static OSVERSIONINFO osver;

/* License: Artistic or GPL */
static void
get_version(void)
{
    memset(&osver, 0, sizeof(OSVERSIONINFO));
    osver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx(&osver);
}

#ifdef _M_IX86
/* License: Artistic or GPL */
DWORD
rb_w32_osid(void)
{
    return osver.dwPlatformId;
}
#endif

/* License: Artistic or GPL */
DWORD
rb_w32_osver(void)
{
    return osver.dwMajorVersion;
}

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

/* License: Artistic or GPL */
#define LK_ERR(f,i) \
    do {                                                                \
        if (f)                                                          \
            i = 0;                                                      \
        else {                                                          \
            DWORD err = GetLastError();                                 \
            if (err == ERROR_LOCK_VIOLATION || err == ERROR_IO_PENDING) \
                errno = EWOULDBLOCK;                                    \
            else if (err == ERROR_NOT_LOCKED)                           \
                i = 0;                                                  \
            else                                                        \
                errno = map_errno(err);                                 \
        }                                                               \
    } while (0)
#define LK_LEN      ULONG_MAX

/* License: Artistic or GPL */
static uintptr_t
flock_winnt(uintptr_t self, int argc, uintptr_t* 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 */
      case LOCK_UN|LOCK_NB:     /* unlock is always non-blocking, I hope */
        LK_ERR(UnlockFileEx(fh, 0, LK_LEN, LK_LEN, &o), i);
        break;
      default:            /* unknown */
        errno = EINVAL;
        break;
    }
    return i;
}

#undef LK_ERR

/* License: Artistic or GPL */
int
flock(int fd, int oper)
{
    const asynchronous_func_t locker = flock_winnt;

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

/* License: Ruby's */
static inline WCHAR *
translate_wchar(WCHAR *p, int from, int to)
{
    for (; *p; p++) {
        if (*p == from)
            *p = to;
    }
    return p;
}

/* License: Ruby's */
static inline char *
translate_char(char *p, int from, int to, UINT cp)
{
    while (*p) {
        if ((unsigned char)*p == from)
            *p = to;
        p = CharNextExA(cp, p, 0);
    }
    return p;
}

#ifndef CSIDL_LOCAL_APPDATA
#define CSIDL_LOCAL_APPDATA 28
#endif
#ifndef CSIDL_COMMON_APPDATA
#define CSIDL_COMMON_APPDATA 35
#endif
#ifndef CSIDL_WINDOWS
#define CSIDL_WINDOWS   36
#endif
#ifndef CSIDL_SYSTEM
#define CSIDL_SYSTEM    37
#endif
#ifndef CSIDL_PROFILE
#define CSIDL_PROFILE 40
#endif

/* License: Ruby's */
static BOOL
get_special_folder(int n, WCHAR *buf, size_t len)
{
    LPITEMIDLIST pidl;
    LPMALLOC alloc;
    BOOL f = FALSE;
    typedef BOOL (*get_path_func)(LPITEMIDLIST, WCHAR*, DWORD, int);
    static get_path_func func = (get_path_func)-1;

    if (func == (get_path_func)-1) {
        func = (get_path_func)
            get_proc_address("shell32", "SHGetPathFromIDListEx", NULL);
    }
    if (!func && len < MAX_PATH) return FALSE;

    if (SHGetSpecialFolderLocation(NULL, n, &pidl) == 0) {
        if (func) {
            f = func(pidl, buf, len, 0);
        }
        else {
            f = SHGetPathFromIDListW(pidl, buf);
        }
        SHGetMalloc(&alloc);
        alloc->lpVtbl->Free(alloc, pidl);
        alloc->lpVtbl->Release(alloc);
    }
    return f;
}

/* License: Ruby's */
static void
regulate_path(WCHAR *path)
{
    WCHAR *p = translate_wchar(path, L'\\', L'/');
    if (p - path == 2 && path[1] == L':') {
        *p++ = L'/';
        *p = L'\0';
    }
}

/* License: Ruby's */
static FARPROC
get_proc_address(const char *module, const char *func, HANDLE *mh)
{
    HANDLE h;
    FARPROC ptr;

    if (mh)
        h = LoadLibrary(module);
    else
        h = GetModuleHandle(module);
    if (!h)
        return NULL;

    ptr = GetProcAddress(h, func);
    if (mh) {
        if (ptr)
            *mh = h;
        else
            FreeLibrary(h);
    }
    return ptr;
}

/* License: Ruby's */
VALUE
rb_w32_special_folder(int type)
{
    WCHAR path[PATH_MAX];

    if (!get_special_folder(type, path, numberof(path))) return Qnil;
    regulate_path(path);
    return rb_w32_conv_from_wchar(path, rb_filesystem_encoding());
}

/* License: Ruby's */
UINT
rb_w32_system_tmpdir(WCHAR *path, UINT len)
{
    static const WCHAR temp[] = L"temp";
    WCHAR *p;

    if (!get_special_folder(CSIDL_LOCAL_APPDATA, path, len)) {
        if (GetSystemWindowsDirectoryW(path, len)) return 0;
    }
    p = translate_wchar(path, L'\\', L'/');
    if (*(p - 1) != L'/') *p++ = L'/';
    if ((UINT)(p - path + numberof(temp)) >= len) return 0;
    memcpy(p, temp, sizeof(temp));
    return (UINT)(p - path + numberof(temp) - 1);
}

/* License: Ruby's */
static void
init_env(void)
{
    static const WCHAR TMPDIR[] = L"TMPDIR";
    struct {WCHAR name[6], eq, val[ENV_MAX];} wk;
    DWORD len;
    BOOL f;
#define env wk.val
#define set_env_val(vname) do { \
        typedef char wk_name_offset[(numberof(wk.name) - (numberof(vname) - 1)) * 2 + 1]; \
        WCHAR *const buf = wk.name + sizeof(wk_name_offset) / 2; \
        MEMCPY(buf, vname, WCHAR, numberof(vname) - 1); \
        _wputenv(buf); \
    } while (0)

    wk.eq = L'=';

    if (!GetEnvironmentVariableW(L"HOME", env, numberof(env))) {
        f = FALSE;
        if (GetEnvironmentVariableW(L"HOMEDRIVE", env, numberof(env)))
            len = lstrlenW(env);
        else
            len = 0;
        if (GetEnvironmentVariableW(L"HOMEPATH", env + len, numberof(env) - len) || len) {
            f = TRUE;
        }
        else if (GetEnvironmentVariableW(L"USERPROFILE", env, numberof(env))) {
            f = TRUE;
        }
        else if (get_special_folder(CSIDL_PROFILE, env, numberof(env))) {
            f = TRUE;
        }
        else if (get_special_folder(CSIDL_PERSONAL, env, numberof(env))) {
            f = TRUE;
        }
        if (f) {
            regulate_path(env);
            set_env_val(L"HOME");
        }
    }

    if (!GetEnvironmentVariableW(L"USER", env, numberof(env))) {
        if (!GetEnvironmentVariableW(L"USERNAME", env, numberof(env)) &&
            !GetUserNameW(env, (len = numberof(env), &len))) {
            NTLoginName = "<Unknown>";
        }
        else {
            set_env_val(L"USER");
            NTLoginName = rb_w32_wstr_to_mbstr(CP_UTF8, env, -1, NULL);
        }
    }
    else {
        NTLoginName = rb_w32_wstr_to_mbstr(CP_UTF8, env, -1, NULL);
    }

    if (!GetEnvironmentVariableW(TMPDIR, env, numberof(env)) &&
        !GetEnvironmentVariableW(L"TMP", env, numberof(env)) &&
        !GetEnvironmentVariableW(L"TEMP", env, numberof(env)) &&
        rb_w32_system_tmpdir(env, numberof(env))) {
        set_env_val(TMPDIR);
    }

#undef env
#undef set_env_val
}

static void init_stdhandle(void);

#if RUBY_MSVCRT_VERSION >= 80
/* License: Ruby's */
static void
invalid_parameter(const wchar_t *expr, const wchar_t *func, const wchar_t *file, unsigned int line, uintptr_t dummy)
{
    // nothing to do
}

int ruby_w32_rtc_error;

/* License: Ruby's */
static int __cdecl
rtc_error_handler(int e, const char *src, int line, const char *exe, const char *fmt, ...)
{
    va_list ap;
    VALUE str;

    if (!ruby_w32_rtc_error) return 0;
    str = rb_sprintf("%s:%d: ", src, line);
    va_start(ap, fmt);
    rb_str_vcatf(str, fmt, ap);
    va_end(ap);
    rb_str_cat(str, "\n", 1);
    rb_write_error2(RSTRING_PTR(str), RSTRING_LEN(str));
    return 0;
}
#endif

static CRITICAL_SECTION select_mutex;
static int NtSocketsInitialized = 0;
static st_table *socklist = NULL;
static st_table *conlist = NULL;
#define conlist_disabled ((st_table *)-1)
static char *uenvarea;

/* License: Ruby's */
struct constat {
    struct {
        int state, seq[16], reverse;
        WORD attr;
        COORD saved;
    } vt100;
};
enum {constat_init = -2, constat_esc = -1, constat_seq = 0};

/* License: Ruby's */
static int
free_conlist(st_data_t key, st_data_t val, st_data_t arg)
{
    xfree((struct constat *)val);
    return ST_DELETE;
}

/* License: Ruby's */
static void
constat_delete(HANDLE h)
{
    if (conlist && conlist != conlist_disabled) {
        st_data_t key = (st_data_t)h, val;
        st_delete(conlist, &key, &val);
        xfree((struct constat *)val);
    }
}

/* License: Ruby's */
static void
exit_handler(void)
{
    if (NtSocketsInitialized) {
        WSACleanup();
        if (socklist) {
            st_free_table(socklist);
            socklist = NULL;
        }
        DeleteCriticalSection(&select_mutex);
        NtSocketsInitialized = 0;
    }
    if (conlist && conlist != conlist_disabled) {
        st_foreach(conlist, free_conlist, 0);
        st_free_table(conlist);
        conlist = NULL;
    }
    if (uenvarea) {
        free(uenvarea);
        uenvarea = NULL;
    }
}

/* License: Artistic or GPL */
static void
StartSockets(void)
{
    WORD version;
    WSADATA retdata;

    //
    // initialize the winsock interface and insure that it's
    // cleaned up at exit.
    //
    version = MAKEWORD(2, 0);
    if (WSAStartup(version, &retdata))
        rb_fatal ("Unable to locate winsock library!\n");
    if (LOBYTE(retdata.wVersion) != 2)
        rb_fatal("could not find version 2 of winsock dll\n");

    InitializeCriticalSection(&select_mutex);

    NtSocketsInitialized = 1;
}

#define MAKE_SOCKDATA(af, fl)   ((int)((((int)af)<<4)|((fl)&0xFFFF)))
#define GET_FAMILY(v)           ((int)(((v)>>4)&0xFFFF))
#define GET_FLAGS(v)            ((int)((v)&0xFFFF))

/* License: Ruby's */
static inline int
socklist_insert(SOCKET sock, int flag)
{
    if (!socklist)
        socklist = st_init_numtable();
    return st_insert(socklist, (st_data_t)sock, (st_data_t)flag);
}

/* License: Ruby's */
static inline int
socklist_lookup(SOCKET sock, int *flagp)
{
    st_data_t data;
    int ret;

    if (!socklist)
        return 0;
    ret = st_lookup(socklist, (st_data_t)sock, (st_data_t *)&data);
    if (ret && flagp)
        *flagp = (int)data;

    return ret;
}

/* License: Ruby's */
static inline int
socklist_delete(SOCKET *sockp, int *flagp)
{
    st_data_t key;
    st_data_t data;
    int ret;

    if (!socklist)
        return 0;
    key = (st_data_t)*sockp;
    if (flagp)
        data = (st_data_t)*flagp;
    ret = st_delete(socklist, &key, &data);
    if (ret) {
        *sockp = (SOCKET)key;
        if (flagp)
            *flagp = (int)data;
    }

    return ret;
}

static int w32_cmdvector(const WCHAR *, char ***, UINT, rb_encoding *);
//
// Initialization stuff
//
/* License: Ruby's */
void
rb_w32_sysinit(int *argc, char ***argv)
{
#if RUBY_MSVCRT_VERSION >= 80
    static void set_pioinfo_extra(void);

    _CrtSetReportMode(_CRT_ASSERT, 0);
    _set_invalid_parameter_handler(invalid_parameter);
    _RTC_SetErrorFunc(rtc_error_handler);
    set_pioinfo_extra();
#else
    SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX);
#endif

    get_version();

    //
    // subvert cmd.exe's feeble attempt at command line parsing
    //
    *argc = w32_cmdvector(GetCommandLineW(), argv, CP_UTF8, &OnigEncodingUTF_8);

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

    tzset();

    init_env();

    init_stdhandle();

    atexit(exit_handler);

    // Initialize Winsock
    StartSockets();
}

char *
getlogin(void)
{
    return (char *)NTLoginName;
}

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

/* License: Ruby's */
static struct ChildRecord {
    HANDLE hProcess;    /* process handle */
    rb_pid_t pid;       /* process id */
} ChildRecord[MAXCHILDNUM];

/* License: Ruby's */
#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)

/* License: Ruby's */
static struct ChildRecord *
FindChildSlot(rb_pid_t pid)
{

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

/* License: Ruby's */
static struct ChildRecord *
FindChildSlotByHandle(HANDLE h)
{

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

/* License: Ruby's */
static void
CloseChildHandle(struct ChildRecord *child)
{
    HANDLE h = child->hProcess;
    child->hProcess = NULL;
    child->pid = 0;
    CloseHandle(h);
}

/* License: Ruby's */
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;
}


/*
  ruby -lne 'BEGIN{$cmds = Hash.new(0); $mask = 1}'
   -e '$cmds[$_.downcase] |= $mask' -e '$mask <<= 1 if ARGF.eof'
   -e 'END{$cmds.sort.each{|n,f|puts "    \"\\#{f.to_s(8)}\" #{n.dump} + 1,"}}'
   98cmd ntcmd
 */
#define InternalCmdsMax 8
static const char szInternalCmds[][InternalCmdsMax+2] = {
    "\2" "assoc",
    "\3" "break",
    "\3" "call",
    "\3" "cd",
    "\1" "chcp",
    "\3" "chdir",
    "\3" "cls",
    "\2" "color",
    "\3" "copy",
    "\1" "ctty",
    "\3" "date",
    "\3" "del",
    "\3" "dir",
    "\3" "echo",
    "\2" "endlocal",
    "\3" "erase",
    "\3" "exit",
    "\3" "for",
    "\2" "ftype",
    "\3" "goto",
    "\3" "if",
    "\1" "lfnfor",
    "\1" "lh",
    "\1" "lock",
    "\3" "md",
    "\3" "mkdir",
    "\2" "move",
    "\3" "path",
    "\3" "pause",
    "\2" "popd",
    "\3" "prompt",
    "\2" "pushd",
    "\3" "rd",
    "\3" "rem",
    "\3" "ren",
    "\3" "rename",
    "\3" "rmdir",
    "\3" "set",
    "\2" "setlocal",
    "\3" "shift",
    "\2" "start",
    "\3" "time",
    "\2" "title",
    "\1" "truename",
    "\3" "type",
    "\1" "unlock",
    "\3" "ver",
    "\3" "verify",
    "\3" "vol",
};

/* License: Ruby's */
static int
internal_match(const void *key, const void *elem)
{
    return strncmp(key, ((const char *)elem) + 1, InternalCmdsMax);
}

/* License: Ruby's */
static int
is_command_com(const char *interp)
{
    int i = strlen(interp) - 11;

    if ((i == 0 || (i > 0 && isdirsep(interp[i-1]))) &&
        strcasecmp(interp+i, "command.com") == 0) {
        return 1;
    }
    return 0;
}

static int internal_cmd_match(const char *cmdname, int nt);

/* License: Ruby's */
static int
is_internal_cmd(const char *cmd, int nt)
{
    char cmdname[9], *b = cmdname, c;

    do {
        if (!(c = *cmd++)) return 0;
    } while (isspace(c));
    if (c == '@')
        return 1;
    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;
    return internal_cmd_match(cmdname, nt);
}

/* License: Ruby's */
static int
internal_cmd_match(const char *cmdname, int nt)
{
    char *nm;

    nm = bsearch(cmdname, szInternalCmds,
                 sizeof(szInternalCmds) / sizeof(*szInternalCmds),
                 sizeof(*szInternalCmds),
                 internal_match);
    if (!nm || !(nm[0] & (nt ? 2 : 1)))
        return 0;
    return 1;
}

/* License: Ruby's */
SOCKET
rb_w32_get_osfhandle(int fh)
{
    return _get_osfhandle(fh);
}

/* License: Ruby's */
static int
join_argv(char *cmd, char *const *argv, BOOL escape, UINT cp, int backslash)
{
    const char *p, *s;
    char *q, *const *t;
    int len, n, bs, quote;

    for (t = argv, q = cmd, len = 0; (p = *t) != 0; t++) {
        quote = 0;
        s = p;
        if (!*p || strpbrk(p, " \t\"'")) {
            quote = 1;
            len++;
            if (q) *q++ = '"';
        }
        for (bs = 0; *p; ++p) {
            switch (*p) {
              case '\\':
                ++bs;
                break;
              case '"':
                len += n = p - s;
                if (q) {
                    memcpy(q, s, n);
                    q += n;
                }
                s = p;
                len += ++bs;
                if (q) {
                    memset(q, '\\', bs);
                    q += bs;
                }
                bs = 0;
                break;
              case '<': case '>': case '|': case '^':
                if (escape && !quote) {
                    len += (n = p - s) + 1;
                    if (q) {
                        memcpy(q, s, n);
                        q += n;
                        *q++ = '^';
                    }
                    s = p;
                    break;
                }
              default:
                bs = 0;
                p = CharNextExA(cp, p, 0) - 1;
                break;
            }
        }
        len += (n = p - s) + 1;
        if (quote) len++;
        if (q) {
            memcpy(q, s, n);
            if (backslash > 0) {
                --backslash;
                q[n] = 0;
                translate_char(q, '/', '\\', cp);
            }
            q += n;
            if (quote) *q++ = '"';
            *q++ = ' ';
        }
    }
    if (q > cmd) --len;
    if (q) {
        if (q > cmd) --q;
        *q = '\0';
    }
    return len;
}

/* License: Ruby's */
#define STRNDUPV(ptr, v, src, len)                                      \
    (((char *)memcpy(((ptr) = ALLOCV((v), (len) + 1)), (src), (len)))[len] = 0)

/* License: Ruby's */
static int
check_spawn_mode(int mode)
{
    switch (mode) {
      case P_NOWAIT:
      case P_OVERLAY:
        return 0;
      default:
        errno = EINVAL;
        return -1;
    }
}

/* License: Ruby's */
static rb_pid_t
child_result(struct ChildRecord *child, int mode)
{
    DWORD exitcode;

    if (!child) {
        return -1;
    }

    if (mode == P_OVERLAY) {
        WaitForSingleObject(child->hProcess, INFINITE);
        GetExitCodeProcess(child->hProcess, &exitcode);
        CloseChildHandle(child);
        _exit(exitcode);
    }
    return child->pid;
}

/* License: Ruby's */
static struct ChildRecord *
CreateChild(const WCHAR *cmd, const WCHAR *prog, SECURITY_ATTRIBUTES *psa,
            HANDLE hInput, HANDLE hOutput, HANDLE hError, DWORD dwCreationFlags)
{
    BOOL fRet;
    STARTUPINFOW aStartupInfo;
    PROCESS_INFORMATION aProcessInformation;
    SECURITY_ATTRIBUTES sa;
    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(aStartupInfo));
    memset(&aProcessInformation, 0, sizeof(aProcessInformation));
    aStartupInfo.cb = sizeof(aStartupInfo);
    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 (lstrlenW(cmd) > 32767) {
        child->pid = 0;         /* release the slot */
        errno = E2BIG;
        return NULL;
    }

    RUBY_CRITICAL {
        fRet = CreateProcessW(prog, (WCHAR *)cmd, psa, psa,
                              psa->bInheritHandle, dwCreationFlags, NULL, NULL,
                              &aStartupInfo, &aProcessInformation);
        errno = map_errno(GetLastError());
    }

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

    CloseHandle(aProcessInformation.hThread);

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

    return child;
}

/* License: Ruby's */
static int
is_batch(const char *cmd)
{
    int len = strlen(cmd);
    if (len <= 4) return 0;
    cmd += len - 4;
    if (*cmd++ != '.') return 0;
    if (strcasecmp(cmd, "bat") == 0) return 1;
    if (strcasecmp(cmd, "cmd") == 0) return 1;
    return 0;
}

UINT rb_w32_filecp(void);
#define filecp rb_w32_filecp
#define mbstr_to_wstr rb_w32_mbstr_to_wstr
#define wstr_to_mbstr rb_w32_wstr_to_mbstr
#define acp_to_wstr(str, plen) mbstr_to_wstr(CP_ACP, str, -1, plen)
#define wstr_to_acp(str, plen) wstr_to_mbstr(CP_ACP, str, -1, plen)
#define filecp_to_wstr(str, plen) mbstr_to_wstr(filecp(), str, -1, plen)
#define wstr_to_filecp(str, plen) wstr_to_mbstr(filecp(), str, -1, plen)
#define utf8_to_wstr(str, plen) mbstr_to_wstr(CP_UTF8, str, -1, plen)
#define wstr_to_utf8(str, plen) wstr_to_mbstr(CP_UTF8, str, -1, plen)

/* License: Artistic or GPL */
static rb_pid_t
w32_spawn(int mode, const char *cmd, const char *prog, UINT cp)
{
    char fbuf[PATH_MAX];
    char *p = NULL;
    const char *shell = NULL;
    WCHAR *wcmd = NULL, *wshell = NULL;
    int e = 0;
    rb_pid_t ret = -1;
    VALUE v = 0;
    VALUE v2 = 0;
    int sep = 0;
    char *cmd_sep = NULL;

    if (check_spawn_mode(mode)) return -1;

    if (prog) {
        if (!(p = dln_find_exe_r(prog, NULL, fbuf, sizeof(fbuf)))) {
            shell = prog;
        }
        else {
            shell = p;
            translate_char(p, '/', '\\', cp);
        }
    }
    else {
        int redir = -1;
        int nt;
        while (ISSPACE(*cmd)) cmd++;
        if ((shell = getenv("RUBYSHELL")) && (redir = has_redirection(cmd, cp))) {
            size_t shell_len = strlen(shell);
            char *tmp = ALLOCV(v, shell_len + strlen(cmd) + sizeof(" -c ") + 2);
            memcpy(tmp, shell, shell_len + 1);
            translate_char(tmp, '/', '\\', cp);
            sprintf(tmp + shell_len, " -c \"%s\"", cmd);
            cmd = tmp;
        }
        else if ((shell = getenv("COMSPEC")) &&
                 (nt = !is_command_com(shell),
                  (redir < 0 ? has_redirection(cmd, cp) : redir) ||
                  is_internal_cmd(cmd, nt))) {
            char *tmp = ALLOCV(v, strlen(shell) + strlen(cmd) + sizeof(" /c ") + (nt ? 2 : 0));
            sprintf(tmp, nt ? "%s /c \"%s\"" : "%s /c %s", shell, cmd);
            cmd = tmp;
        }
        else {
            int len = 0, quote = (*cmd == '"') ? '"' : (*cmd == '\'') ? '\'' : 0;
            int slash = 0;
            for (prog = cmd + !!quote;; prog = CharNextExA(cp, prog, 0)) {
                if (*prog == '/') slash = 1;
                if (!*prog) {
                    len = prog - cmd;
                    if (slash) {
                        STRNDUPV(p, v2, cmd, len);
                        cmd = p;
                    }
                    shell = cmd;
                    break;
                }
                if ((unsigned char)*prog == quote) {
                    len = prog++ - cmd - 1;
                    STRNDUPV(p, v2, cmd + 1, len);
                    shell = p;
                    break;
                }
                if (quote) continue;
                if (ISSPACE(*prog) || strchr("<>|*?\"", *prog)) {
                    len = prog - cmd;
                    STRNDUPV(p, v2, cmd, len + (slash ? strlen(prog) : 0));
                    if (slash) {
                        cmd = p;
                        sep = *(cmd_sep = &p[len]);
                        *cmd_sep = '\0';
                    }
                    shell = p;
                    break;
                }
            }
            shell = dln_find_exe_r(shell, NULL, fbuf, sizeof(fbuf));
            if (p && slash) translate_char(p, '/', '\\', cp);
            if (!shell) {
                shell = p ? p : cmd;
            }
            else {
                len = strlen(shell);
                if (strchr(shell, ' ')) quote = -1;
                if (shell == fbuf) {
                    p = fbuf;
                }
                else if (shell != p && strchr(shell, '/')) {
                    STRNDUPV(p, v2, shell, len);
                    shell = p;
                }
                if (p) translate_char(p, '/', '\\', cp);
                if (is_batch(shell)) {
                    int alen = strlen(prog);
                    cmd = p = ALLOCV(v, len + alen + (quote ? 2 : 0) + 1);
                    if (quote) *p++ = '"';
                    memcpy(p, shell, len);
                    p += len;
                    if (quote) *p++ = '"';
                    memcpy(p, prog, alen + 1);
                    shell = 0;
                }
            }
        }
    }

    if (!e && shell && !(wshell = mbstr_to_wstr(cp, shell, -1, NULL))) e = E2BIG;
    if (cmd_sep) *cmd_sep = sep;
    if (!e && cmd && !(wcmd = mbstr_to_wstr(cp, cmd, -1, NULL))) e = E2BIG;
    if (v2) ALLOCV_END(v2);
    if (v) ALLOCV_END(v);

    if (!e) {
        ret = child_result(CreateChild(wcmd, wshell, NULL, NULL, NULL, NULL, 0), mode);
    }
    free(wshell);
    free(wcmd);
    if (e) errno = e;
    return ret;
}

/* License: Ruby's */
rb_pid_t
rb_w32_spawn(int mode, const char *cmd, const char *prog)
{
    /* assume ACP */
    return w32_spawn(mode, cmd, prog, filecp());
}

/* License: Ruby's */
rb_pid_t
rb_w32_uspawn(int mode, const char *cmd, const char *prog)
{
    return w32_spawn(mode, cmd, prog, CP_UTF8);
}

/* License: Artistic or GPL */
static rb_pid_t
w32_aspawn_flags(int mode, const char *prog, char *const *argv, DWORD flags, UINT cp)
{
    int c_switch = 0;
    size_t len;
    BOOL ntcmd = FALSE, tmpnt;
    const char *shell;
    char *cmd, fbuf[PATH_MAX];
    WCHAR *wcmd = NULL, *wprog = NULL;
    int e = 0;
    rb_pid_t ret = -1;
    VALUE v = 0;

    if (check_spawn_mode(mode)) return -1;

    if (!prog) prog = argv[0];
    if ((shell = getenv("COMSPEC")) &&
        internal_cmd_match(prog, tmpnt = !is_command_com(shell))) {
        ntcmd = tmpnt;
        prog = shell;
        c_switch = 1;
    }
    else if ((cmd = dln_find_exe_r(prog, NULL, fbuf, sizeof(fbuf)))) {
        if (cmd == prog) strlcpy(cmd = fbuf, prog, sizeof(fbuf));
        translate_char(cmd, '/', '\\', cp);
        prog = cmd;
    }
    else if (strchr(prog, '/')) {
        len = strlen(prog);
        if (len < sizeof(fbuf))
            strlcpy(cmd = fbuf, prog, sizeof(fbuf));
        else
            STRNDUPV(cmd, v, prog, len);
        translate_char(cmd, '/', '\\', cp);
        prog = cmd;
    }
    if (c_switch || is_batch(prog)) {
        char *progs[2];
        progs[0] = (char *)prog;
        progs[1] = NULL;
        len = join_argv(NULL, progs, ntcmd, cp, 1);
        if (c_switch) len += 3;
        else ++argv;
        if (argv[0]) len += join_argv(NULL, argv, ntcmd, cp, 0);
        cmd = ALLOCV(v, len);
        join_argv(cmd, progs, ntcmd, cp, 1);
        if (c_switch) strlcat(cmd, " /c", len);
        if (argv[0]) join_argv(cmd + strlcat(cmd, " ", len), argv, ntcmd, cp, 0);
        prog = c_switch ? shell : 0;
    }
    else {
        len = join_argv(NULL, argv, FALSE, cp, 1);
        cmd = ALLOCV(v, len);
        join_argv(cmd, argv, FALSE, cp, 1);
    }

    if (!e && cmd && !(wcmd = mbstr_to_wstr(cp, cmd, -1, NULL))) e = E2BIG;
    if (v) ALLOCV_END(v);
    if (!e && prog && !(wprog = mbstr_to_wstr(cp, prog, -1, NULL))) e = E2BIG;

    if (!e) {
        ret = child_result(CreateChild(wcmd, wprog, NULL, NULL, NULL, NULL, flags), mode);
    }
    free(wprog);
    free(wcmd);
    if (e) errno = e;
    return ret;
}

/* License: Ruby's */
rb_pid_t
rb_w32_aspawn_flags(int mode, const char *prog, char *const *argv, DWORD flags)
{
    /* assume ACP */
    return w32_aspawn_flags(mode, prog, argv, flags, filecp());
}

/* License: Ruby's */
rb_pid_t
rb_w32_uaspawn_flags(int mode, const char *prog, char *const *argv, DWORD flags)
{
    return w32_aspawn_flags(mode, prog, argv, flags, CP_UTF8);
}

/* License: Ruby's */
rb_pid_t
rb_w32_aspawn(int mode, const char *prog, char *const *argv)
{
    return rb_w32_aspawn_flags(mode, prog, argv, 0);
}

/* License: Ruby's */
rb_pid_t
rb_w32_uaspawn(int mode, const char *prog, char *const *argv)
{
    return rb_w32_uaspawn_flags(mode, prog, argv, 0);
}

/* License: Artistic or GPL */
typedef struct _NtCmdLineElement {
    struct _NtCmdLineElement *next;
    char *str;
    long 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

/* License: Ruby's */
static int
insert(const char *path, VALUE vinfo, void *enc)
{
    NtCmdLineElement *tmpcurr;
    NtCmdLineElement ***tail = (NtCmdLineElement ***)vinfo;

    tmpcurr = (NtCmdLineElement *)malloc(sizeof(NtCmdLineElement));
    if (!tmpcurr) return -1;
    MEMZERO(tmpcurr, NtCmdLineElement, 1);
    tmpcurr->len = strlen(path);
    tmpcurr->str = strdup(path);
    if (!tmpcurr->str) return -1;
    tmpcurr->flags |= NTMALLOC;
    **tail = tmpcurr;
    *tail = &tmpcurr->next;

    return 0;
}

/* License: Artistic or GPL */
static NtCmdLineElement **
cmdglob(NtCmdLineElement *patt, NtCmdLineElement **tail, UINT cp, rb_encoding *enc)
{
    char buffer[PATH_MAX], *buf = buffer;
    NtCmdLineElement **last = tail;
    int status;

    if (patt->len >= PATH_MAX)
        if (!(buf = malloc(patt->len + 1))) return 0;

    strlcpy(buf, patt->str, patt->len + 1);
    buf[patt->len] = '\0';
    translate_char(buf, '\\', '/', cp);
    status = ruby_brace_glob_with_enc(buf, 0, insert, (VALUE)&tail, enc);
    if (buf != buffer)
        free(buf);

    if (status || 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
//

/* License: Artistic or GPL */
static int
has_redirection(const char *cmd, UINT cp)
{
    char quote = '\0';
    const char *ptr;

    //
    // Scan the string, looking for redirection characters (< or >), pipe
    // character (|) or newline (\n) 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 '|':
          case '&':
          case '\n':
            if (!quote)
                return TRUE;
            ptr++;
            break;

          case '%':
            if (*++ptr != '_' && !ISALPHA(*ptr)) break;
            while (*++ptr == '_' || ISALNUM(*ptr));
            if (*ptr++ == '%') return TRUE;
            break;

          case '\\':
            ptr++;
          default:
            ptr = CharNextExA(cp, ptr, 0);
            break;
        }
    }
    return FALSE;
}

/* License: Ruby's */
static inline WCHAR *
skipspace(WCHAR *ptr)
{
    while (iswspace(*ptr))
        ptr++;
    return ptr;
}

/* License: Artistic or GPL */
static int
w32_cmdvector(const WCHAR *cmd, char ***vec, UINT cp, rb_encoding *enc)
{
    int globbing, len;
    int elements, strsz, done;
    int slashes, escape;
    WCHAR *ptr, *base, *cmdline;
    char *cptr, *buffer;
    char **vptr;
    WCHAR quote;
    NtCmdLineElement *curr, **tail;
    NtCmdLineElement *cmdhead = NULL, **cmdtail = &cmdhead;

    //
    // just return if we don't have a command line
    //
    while (iswspace(*cmd))
        cmd++;
    if (!*cmd) {
        *vec = NULL;
        return 0;
    }

    ptr = cmdline = wcsdup(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 iteration for each element seen.
    // The inner loop does one iteration 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 L'\\':
                if (quote != L'\'') slashes++;
                break;

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

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

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

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

              case L'\'':
              case L'\"':
                //
                // 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 necessarily with the element.
                // If we're not already in a string, start one.
                //

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

              default:
                ptr = CharNextW(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 (done) --len;

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

        if (escape) {
            WCHAR *p = base, c;
            slashes = quote = 0;
            while (p < base + len) {
                switch (c = *p) {
                  case L'\\':
                    p++;
                    if (quote != L'\'') slashes++;
                    break;

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

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

        curr = (NtCmdLineElement *)calloc(sizeof(NtCmdLineElement), 1);
        if (!curr) goto do_nothing;
        curr->str = rb_w32_wstr_to_mbstr(cp, base, len, &curr->len);
        curr->flags |= NTMALLOC;

        if (globbing && (tail = cmdglob(curr, cmdtail, cp, enc))) {
            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 = (char *)malloc(len);
    if (!buffer) {
      do_nothing:
        while ((curr = cmdhead) != 0) {
            cmdhead = curr->next;
            if (curr->flags & NTMALLOC) free(curr->str);
            free(curr);
        }
        free(cmdline);
        for (vptr = *vec; *vptr; ++vptr);
        return vptr - *vec;
    }

    //
    // make vptr point to the start of the buffer
    // and cptr 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;

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

    while ((curr = cmdhead) != 0) {
        strlcpy(cptr, curr->str, curr->len + 1);
        *vptr++ = cptr;
        cptr += 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
//

static DWORD
get_final_path(HANDLE f, WCHAR *buf, DWORD len, DWORD flag)
{
    typedef DWORD (WINAPI *get_final_path_func)(HANDLE, WCHAR*, DWORD, DWORD);
    static get_final_path_func func = (get_final_path_func)-1;

    if (func == (get_final_path_func)-1) {
        func = (get_final_path_func)
            get_proc_address("kernel32", "GetFinalPathNameByHandleW", NULL);
    }

    if (!func) return 0;
    return func(f, buf, len, flag);
}

/* License: Ruby's */
/* TODO: better name */
static HANDLE
open_special(const WCHAR *path, DWORD access, DWORD flags)
{
    const DWORD share_mode =
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
    return CreateFileW(path, access, share_mode, NULL, OPEN_EXISTING,
                       FILE_FLAG_BACKUP_SEMANTICS|flags, NULL);
}

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

/* License: Ruby's */
#define GetBit(bits, i) ((bits)[(i) / CHAR_BIT] &  (1 << (i) % CHAR_BIT))
#define SetBit(bits, i) ((bits)[(i) / CHAR_BIT] |= (1 << (i) % CHAR_BIT))

#define BitOfIsDir(n) ((n) * 2)
#define BitOfIsRep(n) ((n) * 2 + 1)
#define DIRENT_PER_CHAR (CHAR_BIT / 2)

/* License: Artistic or GPL */
static HANDLE
open_dir_handle(const WCHAR *filename, WIN32_FIND_DATAW *fd)
{
    HANDLE fh;
    WCHAR fullname[PATH_MAX + rb_strlen_lit("\\*")];
    WCHAR *p;
    int len = 0;

    //
    // Create the search pattern
    //

    fh = open_special(filename, 0, 0);
    if (fh != INVALID_HANDLE_VALUE) {
        len = get_final_path(fh, fullname, PATH_MAX, 0);
        CloseHandle(fh);
    }
    if (!len) {
        len = lstrlenW(filename);
        if (len >= PATH_MAX) {
            errno = ENAMETOOLONG;
            return INVALID_HANDLE_VALUE;
        }
        MEMCPY(fullname, filename, WCHAR, len);
    }
    p = &fullname[len-1];
    if (!(isdirsep(*p) || *p == L':')) *++p = L'\\';
    *++p = L'*';
    *++p = L'\0';

    //
    // do the FindFirstFile call
    //
    fh = FindFirstFileW(fullname, fd);
    if (fh == INVALID_HANDLE_VALUE) {
        errno = map_errno(GetLastError());
    }
    return fh;
}

/* License: Artistic or GPL */
static DIR *
w32_wopendir(const WCHAR *wpath)
{
    struct stati64 sbuf;
    WIN32_FIND_DATAW fd;
    HANDLE fh;
    DIR *p;
    long pathlen;
    long len;
    long altlen;
    long idx;
    WCHAR *tmpW;
    char *tmp;

    //
    // check to see if we've got a directory
    //
    if (wstati64(wpath, &sbuf) < 0) {
        return NULL;
    }
    if (!(sbuf.st_mode & S_IFDIR) &&
        (!ISALPHA(wpath[0]) || wpath[1] != L':' || wpath[2] != L'\0' ||
         ((1 << ((wpath[0] & 0x5f) - 'A')) & GetLogicalDrives()) == 0)) {
        errno = ENOTDIR;
        return NULL;
    }
    fh = open_dir_handle(wpath, &fd);
    if (fh == INVALID_HANDLE_VALUE) {
        return NULL;
    }

    //
    // Get us a DIR structure
    //
    p = calloc(sizeof(DIR), 1);
    if (p == NULL)
        return NULL;

    pathlen = lstrlenW(wpath);
    idx = 0;

    //
    // 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.
    //
    do {
        len = lstrlenW(fd.cFileName) + 1;
        altlen = lstrlenW(fd.cAlternateFileName) + 1;

        //
        // bump the string table size by enough for the
        // new name and it's null terminator
        //
        tmpW = realloc(p->start, (idx + len + altlen) * sizeof(WCHAR));
        if (!tmpW) {
          error:
            rb_w32_closedir(p);
            FindClose(fh);
            errno = ENOMEM;
            return NULL;
        }

        p->start = tmpW;
        memcpy(&p->start[idx], fd.cFileName, len * sizeof(WCHAR));
        memcpy(&p->start[idx + len], fd.cAlternateFileName, altlen * sizeof(WCHAR));

        if (p->nfiles % DIRENT_PER_CHAR == 0) {
            tmp = realloc(p->bits, p->nfiles / DIRENT_PER_CHAR + 1);
            if (!tmp)
                goto error;
            p->bits = tmp;
            p->bits[p->nfiles / DIRENT_PER_CHAR] = 0;
        }
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            SetBit(p->bits, BitOfIsDir(p->nfiles));
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
            WCHAR *tmppath = malloc((pathlen + len + 1) * sizeof(WCHAR));
            memcpy(tmppath, wpath, pathlen * sizeof(WCHAR));
            tmppath[pathlen] = L'\\';
            memcpy(tmppath + pathlen + 1, fd.cFileName, len * sizeof(WCHAR));
            if (rb_w32_reparse_symlink_p(tmppath))
                SetBit(p->bits, BitOfIsRep(p->nfiles));
            free(tmppath);
        }

        p->nfiles++;
        idx += len + altlen;
    } while (FindNextFileW(fh, &fd));
    FindClose(fh);
    p->size = idx;
    p->curr = p->start;
    return p;
}

/* License: Ruby's */
UINT
filecp(void)
{
    UINT cp = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
    return cp;
}

/* License: Ruby's */
char *
rb_w32_wstr_to_mbstr(UINT cp, const WCHAR *wstr, int clen, long *plen)
{
    char *ptr;
    int len = WideCharToMultiByte(cp, 0, wstr, clen, NULL, 0, NULL, NULL);
    if (!(ptr = malloc(len))) return 0;
    WideCharToMultiByte(cp, 0, wstr, clen, ptr, len, NULL, NULL);
    if (plen) {
        /* exclude NUL only if NUL-terminated string */
        if (clen == -1) --len;
        *plen = len;
    }
    return ptr;
}

/* License: Ruby's */
WCHAR *
rb_w32_mbstr_to_wstr(UINT cp, const char *str, int clen, long *plen)
{
    WCHAR *ptr;
    int len = MultiByteToWideChar(cp, 0, str, clen, NULL, 0);
    if (!(ptr = malloc(sizeof(WCHAR) * len))) return 0;
    MultiByteToWideChar(cp, 0, str, clen, ptr, len);
    if (plen) {
        /* exclude NUL only if NUL-terminated string */
        if (clen == -1) --len;
        *plen = len;
    }
    return ptr;
}

/* License: Ruby's */
DIR *
rb_w32_opendir(const char *filename)
{
    DIR *ret;
    WCHAR *wpath = filecp_to_wstr(filename, NULL);
    if (!wpath)
        return NULL;
    ret = w32_wopendir(wpath);
    free(wpath);
    return ret;
}

/* License: Ruby's */
DIR *
rb_w32_uopendir(const char *filename)
{
    DIR *ret;
    WCHAR *wpath = utf8_to_wstr(filename, NULL);
    if (!wpath)
        return NULL;
    ret = w32_wopendir(wpath);
    free(wpath);
    return ret;
}

//
// Move to next entry
//

/* License: Artistic or GPL */
static void
move_to_next_entry(DIR *dirp)
{
    if (dirp->curr) {
        dirp->loc++;
        dirp->curr += lstrlenW(dirp->curr) + 1;
        dirp->curr += lstrlenW(dirp->curr) + 1;
        if (dirp->curr >= (dirp->start + dirp->size)) {
            dirp->curr = NULL;
        }
    }
}

//
// Readdir just returns the current string pointer and bumps the
// string pointer to the next entry.
//
/* License: Ruby's */
static BOOL
win32_direct_conv(const WCHAR *file, const WCHAR *alt, struct direct *entry, const void *enc)
{
    UINT cp = *((UINT *)enc);
    if (!(entry->d_name = wstr_to_mbstr(cp, file, -1, &entry->d_namlen)))
        return FALSE;
    if (alt && *alt) {
        long altlen = 0;
        entry->d_altname = wstr_to_mbstr(cp, alt, -1, &altlen);
        entry->d_altlen = altlen;
    }
    return TRUE;
}

/* License: Ruby's */
VALUE
rb_w32_conv_from_wchar(const WCHAR *wstr, rb_encoding *enc)
{
    VALUE src;
    long len = lstrlenW(wstr);
    int encindex = rb_enc_to_index(enc);

    if (encindex == ENCINDEX_UTF_16LE) {
        return rb_enc_str_new((char *)wstr, len * sizeof(WCHAR), enc);
    }
    else {
#if SIZEOF_INT < SIZEOF_LONG
# error long should equal to int on Windows
#endif
        int clen = rb_long2int(len);
        len = WideCharToMultiByte(CP_UTF8, 0, wstr, clen, NULL, 0, NULL, NULL);
        src = rb_enc_str_new(0, len, rb_enc_from_index(ENCINDEX_UTF_8));
        WideCharToMultiByte(CP_UTF8, 0, wstr, clen, RSTRING_PTR(src), len, NULL, NULL);
    }
    switch (encindex) {
      case ENCINDEX_ASCII:
      case ENCINDEX_US_ASCII:
        /* assume UTF-8 */
      case ENCINDEX_UTF_8:
        /* do nothing */
        return src;
    }
    return rb_str_conv_enc_opts(src, NULL, enc, ECONV_UNDEF_REPLACE, Qnil);
}

/* License: Ruby's */
char *
rb_w32_conv_from_wstr(const WCHAR *wstr, long *lenp, rb_encoding *enc)
{
    VALUE str = rb_w32_conv_from_wchar(wstr, enc);
    long len;
    char *ptr;

    if (NIL_P(str)) return wstr_to_filecp(wstr, lenp);
    *lenp = len = RSTRING_LEN(str);
    memcpy(ptr = malloc(len + 1), RSTRING_PTR(str), len);
    ptr[len] = '\0';
    return ptr;
}

/* License: Ruby's */
static BOOL
ruby_direct_conv(const WCHAR *file, const WCHAR *alt, struct direct *entry, const void *enc)
{
    if (!(entry->d_name = rb_w32_conv_from_wstr(file, &entry->d_namlen, enc)))
        return FALSE;
    if (alt && *alt) {
        long altlen = 0;
        entry->d_altname = rb_w32_conv_from_wstr(alt, &altlen, enc);
        entry->d_altlen = altlen;
    }
    return TRUE;
}

/* License: Artistic or GPL */
static struct direct *
readdir_internal(DIR *dirp, BOOL (*conv)(const WCHAR *, const WCHAR *, struct direct *, const void *), const void *enc)
{
    static int dummy = 0;

    if (dirp->curr) {

        //
        // first set up the structure to return
        //
        if (dirp->dirstr.d_name)
            free(dirp->dirstr.d_name);
        if (dirp->dirstr.d_altname)
            free(dirp->dirstr.d_altname);
        dirp->dirstr.d_altname = 0;
        dirp->dirstr.d_altlen = 0;
        conv(dirp->curr, dirp->curr + lstrlenW(dirp->curr) + 1, &dirp->dirstr, enc);

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

        //
        // Attributes
        //
        /* ignore FILE_ATTRIBUTE_DIRECTORY as unreliable for reparse points */
        if (GetBit(dirp->bits, BitOfIsRep(dirp->loc)))
            dirp->dirstr.d_type = DT_LNK;
        else if (GetBit(dirp->bits, BitOfIsDir(dirp->loc)))
            dirp->dirstr.d_type = DT_DIR;
        else
            dirp->dirstr.d_type = DT_REG;

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

        move_to_next_entry(dirp);

        return &(dirp->dirstr);

    }
    else
        return NULL;
}

/* License: Ruby's */
struct direct  *
rb_w32_readdir(DIR *dirp, rb_encoding *enc)
{
    int idx = rb_enc_to_index(enc);
    if (idx == ENCINDEX_ASCII) {
        const UINT cp = filecp();
        return readdir_internal(dirp, win32_direct_conv, &cp);
    }
    else if (idx == ENCINDEX_UTF_8) {
        const UINT cp = CP_UTF8;
        return readdir_internal(dirp, win32_direct_conv, &cp);
    }
    else
        return readdir_internal(dirp, ruby_direct_conv, enc);
}

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

/* License: Artistic or GPL */
long
rb_w32_telldir(DIR *dirp)
{
    return dirp->loc;
}

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

/* License: Ruby's */
void
rb_w32_seekdir(DIR *dirp, long loc)
{
    if (dirp->loc > loc) rb_w32_rewinddir(dirp);

    while (dirp->curr && dirp->loc < loc) {
        move_to_next_entry(dirp);
    }
}

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

/* License: Artistic or GPL */
void
rb_w32_rewinddir(DIR *dirp)
{
    dirp->curr = dirp->start;
    dirp->loc = 0;
}

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

/* License: Artistic or GPL */
void
rb_w32_closedir(DIR *dirp)
{
    if (dirp) {
        if (dirp->dirstr.d_name)
            free(dirp->dirstr.d_name);
        if (dirp->dirstr.d_altname)
            free(dirp->dirstr.d_altname);
        if (dirp->start)
            free(dirp->start);
        if (dirp->bits)
            free(dirp->bits);
        free(dirp);
    }
}

#if RUBY_MSVCRT_VERSION >= 140
typedef struct {
    union
    {
        FILE  _public_file;
        char* _ptr;
    };

    char*            _base;
    int              _cnt;
    long             _flags;
    long             _file;
    int              _charbuf;
    int              _bufsiz;
    char*            _tmpfname;
    CRITICAL_SECTION _lock;
} vcruntime_file;
#define FILE_COUNT(stream) ((vcruntime_file*)stream)->_cnt
#define FILE_READPTR(stream) ((vcruntime_file*)stream)->_ptr
#define FILE_FILENO(stream) ((vcruntime_file*)stream)->_file
#else
#define FILE_COUNT(stream) stream->_cnt
#define FILE_READPTR(stream) stream->_ptr
#define FILE_FILENO(stream) stream->_file
#endif

/* License: Ruby's */
#if RUBY_MSVCRT_VERSION >= 140
typedef struct {
    CRITICAL_SECTION           lock;
    intptr_t                   osfhnd;          // underlying OS file HANDLE
    __int64                    startpos;        // File position that matches buffer start
    unsigned char              osfile;          // Attributes of file (e.g., open in text mode?)
    char      textmode;
    char _pipe_lookahead;

    uint8_t unicode          : 1; // Was the file opened as unicode?
    uint8_t utf8translations : 1; // Buffer contains translations other than CRLF
    uint8_t dbcsBufferUsed   : 1; // Is the dbcsBuffer in use?
    char    dbcsBuffer;           // Buffer for the lead byte of DBCS when converting from DBCS to Unicode
} ioinfo;
#else
typedef struct  {
    intptr_t 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 */
    int lockinitflag;
    CRITICAL_SECTION lock;
#if RUBY_MSVCRT_VERSION >= 80
    char textmode;
    char pipech2[2];
#endif
}       ioinfo;
#endif

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

#if RUBY_MSVCRT_VERSION >= 140
static ioinfo ** __pioinfo = NULL;
#define IOINFO_L2E 6
#else
EXTERN_C _CRTIMP ioinfo * __pioinfo[];
#define IOINFO_L2E 5
#endif
static inline ioinfo* _pioinfo(int);

#define IOINFO_ARRAY_ELTS       (1 << IOINFO_L2E)
#define _osfhnd(i)  (_pioinfo(i)->osfhnd)
#define _osfile(i)  (_pioinfo(i)->osfile)
#define _pipech(i)  (_pioinfo(i)->pipech)
#define rb_acrt_lowio_lock_fh(i)   EnterCriticalSection(&_pioinfo(i)->lock)
#define rb_acrt_lowio_unlock_fh(i) LeaveCriticalSection(&_pioinfo(i)->lock)

#if RUBY_MSVCRT_VERSION >= 80
static size_t pioinfo_extra = 0;        /* workaround for VC++8 SP1 */

/* License: Ruby's */
static void
set_pioinfo_extra(void)
{
#if RUBY_MSVCRT_VERSION >= 140
    /* get __pioinfo addr with _isatty */
    char *p = (char*)get_proc_address("ucrtbase.dll", "_isatty", NULL);
    char *pend = p + 100;
    /* _osfile(fh) & FDEV */
#if _WIN64
    int32_t rel;
    char *rip;
    /* lea rdx,[__pioinfo's addr in RIP-relative 32bit addr] */
# define PIOINFO_MARK "\x48\x8d\x15"
#else
    /* mov eax,dword ptr [eax*4+100EB430h] */
# define PIOINFO_MARK "\x8B\x04\x85"
#endif
    for (p; p < pend; p++) {
        if (memcmp(p, PIOINFO_MARK, strlen(PIOINFO_MARK)) == 0) {
            goto found;
        }
    }
    fprintf(stderr, "unexpected ucrtbase.dll\n");
    _exit(1);

    found:
    p += strlen(PIOINFO_MARK);
#if _WIN64
    rel = *(int32_t*)(p);
    rip = p + sizeof(int32_t);
    __pioinfo = (ioinfo**)(rip + rel);
#else
    __pioinfo = (ioinfo**)(p);
#endif
#else
    int fd;

    fd = _open("NUL", O_RDONLY);
    for (pioinfo_extra = 0; pioinfo_extra <= 64; pioinfo_extra += sizeof(void *)) {
        if (_osfhnd(fd) == _get_osfhandle(fd)) {
            break;
        }
    }
    _close(fd);

    if (pioinfo_extra > 64) {
        /* not found, maybe something wrong... */
        pioinfo_extra = 0;
    }
#endif
}
#else
#define pioinfo_extra 0
#endif

static inline ioinfo*
_pioinfo(int fd)
{
    const size_t sizeof_ioinfo = sizeof(ioinfo) + pioinfo_extra;
    return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] +
                     (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo);
}

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

#define FOPEN                   0x01    /* file handle open */
#define FEOFLAG                 0x02    /* end of file has been encountered */
#define FPIPE                   0x08    /* file handle refers to a pipe */
#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 is_socket(SOCKET);
static int is_console(SOCKET);

/* License: Ruby's */
int
rb_w32_io_cancelable_p(int fd)
{
    return is_socket(TO_SOCKET(fd)) || !is_console(TO_SOCKET(fd));
}

/* License: Ruby's */
static int
rb_w32_open_osfhandle(intptr_t 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((intptr_t)hF, 0);
    CloseHandle(hF);
    if (fh == -1) {
        errno = EMFILE;         /* too many open files */
        _doserrno = 0L;         /* not an OS error */
    }
    else {

        rb_acrt_lowio_lock_fh(fh);
        /* 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 */
        rb_acrt_lowio_unlock_fh(fh);
    }
    return fh;                  /* return handle */
}

/* License: Ruby's */
static void
init_stdhandle(void)
{
    int nullfd = -1;
    int keep = 0;
#define open_null(fd)                                           \
    (((nullfd < 0) ?                                            \
      (nullfd = open("NUL", O_RDWR)) : 0),              \
     ((nullfd == (fd)) ? (keep = 1) : dup2(nullfd, fd)),        \
     (fd))

    if (fileno(stdin) < 0) {
        FILE_FILENO(stdin) = open_null(0);
    }
    else {
        setmode(fileno(stdin), O_BINARY);
    }
    if (fileno(stdout) < 0) {
        FILE_FILENO(stdout) = open_null(1);
    }
    if (fileno(stderr) < 0) {
        FILE_FILENO(stderr) = open_null(2);
    }
    if (nullfd >= 0 && !keep) close(nullfd);
    setvbuf(stderr, NULL, _IONBF, 0);
}

#undef getsockopt

/* License: Ruby's */
static int
is_socket(SOCKET sock)
{
    if (socklist_lookup(sock, NULL))
        return TRUE;
    else
        return FALSE;
}

/* License: Ruby's */
int
rb_w32_is_socket(int fd)
{
    return is_socket(TO_SOCKET(fd));
}

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

/* License: Artistic or GPL */
char *
rb_w32_strerror(int e)
{
    static char buffer[512];
    DWORD source = 0;
    char *p;

    if (e < 0 || e > sys_nerr) {
        if (e < 0)
            e = GetLastError();
#if WSAEWOULDBLOCK != EWOULDBLOCK
        else if (e >= EADDRINUSE && e <= EWOULDBLOCK) {
            static int s = -1;
            int i;
            if (s < 0)
                for (s = 0; s < (int)(sizeof(errmap)/sizeof(*errmap)); s++)
                    if (errmap[s].winerr == WSAEWOULDBLOCK)
                        break;
            for (i = s; i < (int)(sizeof(errmap)/sizeof(*errmap)); i++)
                if (errmap[i].err == e) {
                    e = errmap[i].winerr;
                    break;
                }
        }
#endif
        if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                          FORMAT_MESSAGE_IGNORE_INSERTS, &source, e,
                          MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
                          buffer, sizeof(buffer), NULL) == 0 &&
            FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                          FORMAT_MESSAGE_IGNORE_INSERTS, &source, e, 0,
                          buffer, sizeof(buffer), NULL) == 0)
            strlcpy(buffer, "Unknown Error", sizeof(buffer));
    }
    else
        strlcpy(buffer, strerror(e), sizeof(buffer));

    p = buffer;
    while ((p = strpbrk(p, "\r\n")) != NULL) {
        memmove(p, p + 1, strlen(p));
    }
    return buffer;
}

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

/* License: Artistic or GPL */
rb_uid_t
getuid(void)
{
        return ROOT_UID;
}

/* License: Artistic or GPL */
rb_uid_t
geteuid(void)
{
        return ROOT_UID;
}

/* License: Artistic or GPL */
rb_gid_t
getgid(void)
{
        return ROOT_GID;
}

/* License: Artistic or GPL */
rb_gid_t
getegid(void)
{
    return ROOT_GID;
}

/* License: Artistic or GPL */
int
setuid(rb_uid_t uid)
{
    return (uid == ROOT_UID ? 0 : -1);
}

/* License: Artistic or GPL */
int
setgid(rb_gid_t gid)
{
    return (gid == ROOT_GID ? 0 : -1);
}

//
// File system stuff
//

/* License: Artistic or GPL */
int
ioctl(int i, int u, ...)
{
    errno = EINVAL;
    return -1;
}

void
rb_w32_fdset(int fd, fd_set *set)
{
    FD_SET(fd, set);
}

#undef FD_CLR

/* License: Ruby's */
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) {
            memmove(&set->fd_array[i], &set->fd_array[i+1],
                    sizeof(set->fd_array[0]) * (--set->fd_count - i));
            break;
        }
    }
}

#undef FD_ISSET

/* License: Ruby's */
int
rb_w32_fdisset(int fd, fd_set *set)
{
    int ret;
    SOCKET s = TO_SOCKET(fd);
    if (s == (SOCKET)INVALID_HANDLE_VALUE)
        return 0;
    RUBY_CRITICAL(ret = __WSAFDIsSet(s, set));
    return ret;
}

/* License: Ruby's */
void
rb_w32_fd_copy(rb_fdset_t *dst, const fd_set *src, int max)
{
    max = min(src->fd_count, (UINT)max);
    if ((UINT)dst->capa < (UINT)max) {
        dst->capa = (src->fd_count / FD_SETSIZE + 1) * FD_SETSIZE;
        dst->fdset = xrealloc(dst->fdset, sizeof(unsigned int) + sizeof(SOCKET) * dst->capa);
    }

    memcpy(dst->fdset->fd_array, src->fd_array,
           max * sizeof(src->fd_array[0]));
    dst->fdset->fd_count = src->fd_count;
}

/* License: Ruby's */
void
rb_w32_fd_dup(rb_fdset_t *dst, const rb_fdset_t *src)
{
    if ((UINT)dst->capa < src->fdset->fd_count) {
        dst->capa = (src->fdset->fd_count / FD_SETSIZE + 1) * FD_SETSIZE;
        dst->fdset = xrealloc(dst->fdset, sizeof(unsigned int) + sizeof(SOCKET) * dst->capa);
    }

    memcpy(dst->fdset->fd_array, src->fdset->fd_array,
           src->fdset->fd_count * sizeof(src->fdset->fd_array[0]));
    dst->fdset->fd_count = src->fdset->fd_count;
}

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

#undef select

/* License: Ruby's */
static int
extract_fd(rb_fdset_t *dst, fd_set *src, int (*func)(SOCKET))
{
    unsigned int s = 0;
    unsigned int m = 0;
    if (!src) return 0;

    while (s < src->fd_count) {
        SOCKET fd = src->fd_array[s];

        if (!func || (*func)(fd)) {
            if (dst) { /* move it to dst */
                unsigned int d;

                for (d = 0; d < dst->fdset->fd_count; d++) {
                    if (dst->fdset->fd_array[d] == fd)
                        break;
                }
                if (d == dst->fdset->fd_count) {
                    if ((int)dst->fdset->fd_count >= dst->capa) {
                        dst->capa = (dst->fdset->fd_count / FD_SETSIZE + 1) * FD_SETSIZE;
                        dst->fdset = xrealloc(dst->fdset, sizeof(unsigned int) + sizeof(SOCKET) * dst->capa);
                    }
                    dst->fdset->fd_array[dst->fdset->fd_count++] = fd;
                }
                memmove(
                    &src->fd_array[s],
                    &src->fd_array[s+1],
                    sizeof(src->fd_array[0]) * (--src->fd_count - s));
            }
            else {
                m++;
                s++;
            }
        }
        else s++;
    }

    return dst ? dst->fdset->fd_count : m;
}

/* License: Ruby's */
static int
copy_fd(fd_set *dst, fd_set *src)
{
    unsigned int s;
    if (!src || !dst) return 0;

    for (s = 0; s < src->fd_count; ++s) {
        SOCKET fd = src->fd_array[s];
        unsigned int d;
        for (d = 0; d < dst->fd_count; ++d) {
            if (dst->fd_array[d] == fd)
                break;
        }
        if (d == dst->fd_count && d < FD_SETSIZE) {
            dst->fd_array[dst->fd_count++] = fd;
        }
    }

    return dst->fd_count;
}

/* License: Ruby's */
static int
is_not_socket(SOCKET sock)
{
    return !is_socket(sock);
}

/* License: Ruby's */
static int
is_pipe(SOCKET sock) /* DONT call this for SOCKET! it claims it is PIPE. */
{
    int ret;

    RUBY_CRITICAL {
        ret = (GetFileType((HANDLE)sock) == FILE_TYPE_PIPE);
    }

    return ret;
}

/* License: Ruby's */
static int
is_readable_pipe(SOCKET sock) /* call this for pipe only */
{
    int ret;
    DWORD n = 0;

    RUBY_CRITICAL {
        if (PeekNamedPipe((HANDLE)sock, NULL, 0, NULL, &n, NULL)) {
            ret = (n > 0);
        }
        else {
            ret = (GetLastError() == ERROR_BROKEN_PIPE); /* pipe was closed */
        }
    }

    return ret;
}

/* License: Ruby's */
static int
is_console(SOCKET sock) /* DONT call this for SOCKET! */
{
    int ret;
    DWORD n = 0;
    INPUT_RECORD ir;

    RUBY_CRITICAL {
        ret = (PeekConsoleInput((HANDLE)sock, &ir, 1, &n));
    }

    return ret;
}

/* License: Ruby's */
static int
is_readable_console(SOCKET sock) /* call this for console only */
{
    int ret = 0;
    DWORD n = 0;
    INPUT_RECORD ir;

    RUBY_CRITICAL {
        if (PeekConsoleInput((HANDLE)sock, &ir, 1, &n) && n > 0) {
            if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown &&
                ir.Event.KeyEvent.uChar.AsciiChar) {
                ret = 1;
            }
            else {
                ReadConsoleInput((HANDLE)sock, &ir, 1, &n);
            }
        }
    }

    return ret;
}

/* License: Ruby's */
static int
is_invalid_handle(SOCKET sock)
{
    return (HANDLE)sock == INVALID_HANDLE_VALUE;
}

/* License: Artistic or GPL */
static int
do_select(int nfds, fd_set *rd, fd_set *wr, fd_set *ex,
          struct timeval *timeout)
{
    int r = 0;

    if (nfds == 0) {
        if (timeout)
            rb_w32_sleep(timeout->tv_sec * 1000 + timeout->tv_usec / 1000);
        else
            rb_w32_sleep(INFINITE);
    }
    else {
        if (!NtSocketsInitialized)
            StartSockets();

        RUBY_CRITICAL {
            EnterCriticalSection(&select_mutex);
            r = select(nfds, rd, wr, ex, timeout);
            LeaveCriticalSection(&select_mutex);
            if (r == SOCKET_ERROR) {
                errno = map_errno(WSAGetLastError());
                r = -1;
            }
        }
    }

    return r;
}

/*
 * rest -= wait
 * return 0 if rest is smaller than wait.
 */
/* License: Ruby's */
int
rb_w32_time_subtract(struct timeval *rest, const struct timeval *wait)
{
    if (rest->tv_sec < wait->tv_sec) {
        return 0;
    }
    while (rest->tv_usec < wait->tv_usec) {
        if (rest->tv_sec <= wait->tv_sec) {
            return 0;
        }
        rest->tv_sec -= 1;
        rest->tv_usec += 1000 * 1000;
    }
    rest->tv_sec -= wait->tv_sec;
    rest->tv_usec -= wait->tv_usec;
    return rest->tv_sec != 0 || rest->tv_usec != 0;
}

/* License: Ruby's */
static inline int
compare(const struct timeval *t1, const struct timeval *t2)
{
    if (t1->tv_sec < t2->tv_sec)
        return -1;
    if (t1->tv_sec > t2->tv_sec)
        return 1;
    if (t1->tv_usec < t2->tv_usec)
        return -1;
    if (t1->tv_usec > t2->tv_usec)
        return 1;
    return 0;
}

#undef Sleep

int rb_w32_check_interrupt(void *);     /* @internal */

/* @internal */
/* License: Ruby's */
int
rb_w32_select_with_thread(int nfds, fd_set *rd, fd_set *wr, fd_set *ex,
                          struct timeval *timeout, void *th)
{
    int r;
    rb_fdset_t pipe_rd;
    rb_fdset_t cons_rd;
    rb_fdset_t else_rd;
    rb_fdset_t else_wr;
    rb_fdset_t except;
    int nonsock = 0;
    struct timeval limit = {0, 0};

    if (nfds < 0 || (timeout && (timeout->tv_sec < 0 || timeout->tv_usec < 0))) {
        errno = EINVAL;
        return -1;
    }

    if (timeout) {
        if (timeout->tv_sec < 0 ||
            timeout->tv_usec < 0 ||
            timeout->tv_usec >= 1000000) {
            errno = EINVAL;
            return -1;
        }
        gettimeofday(&limit, NULL);
        limit.tv_sec += timeout->tv_sec;
        limit.tv_usec += timeout->tv_usec;
        if (limit.tv_usec >= 1000000) {
            limit.tv_usec -= 1000000;
            limit.tv_sec++;
        }
    }

    // assume else_{rd,wr} (other than socket, pipe reader, console reader)
    // are always readable/writable. but this implementation still has
    // problem. if pipe's buffer is full, writing to pipe will block
    // until some data is read from pipe. but ruby is single threaded system,
    // so whole system will be blocked forever.

    rb_fd_init(&else_rd);
    nonsock += extract_fd(&else_rd, rd, is_not_socket);

    rb_fd_init(&else_wr);
    nonsock += extract_fd(&else_wr, wr, is_not_socket);

    // check invalid handles
    if (extract_fd(NULL, else_rd.fdset, is_invalid_handle) > 0 ||
        extract_fd(NULL, else_wr.fdset, is_invalid_handle) > 0) {
        rb_fd_term(&else_wr);
        rb_fd_term(&else_rd);
        errno = EBADF;
        return -1;
    }

    rb_fd_init(&pipe_rd);
    extract_fd(&pipe_rd, else_rd.fdset, is_pipe); // should not call is_pipe for socket

    rb_fd_init(&cons_rd);
    extract_fd(&cons_rd, else_rd.fdset, is_console); // ditto

    rb_fd_init(&except);
    extract_fd(&except, ex, is_not_socket); // drop only

    r = 0;
    if (rd && (int)rd->fd_count > r) r = (int)rd->fd_count;
    if (wr && (int)wr->fd_count > r) r = (int)wr->fd_count;
    if (ex && (int)ex->fd_count > r) r = (int)ex->fd_count;
    if (nfds > r) nfds = r;

    {
        struct timeval rest;
        const struct timeval wait = {0, 10 * 1000}; // 10ms
        struct timeval zero = {0, 0};               // 0ms
        for (;;) {
            if (th && rb_w32_check_interrupt(th) != WAIT_TIMEOUT) {
                r = -1;
                break;
            }
            if (nonsock) {
                // modifying {else,pipe,cons}_rd is safe because
                // if they are modified, function returns immediately.
                extract_fd(&else_rd, pipe_rd.fdset, is_readable_pipe);
                extract_fd(&else_rd, cons_rd.fdset, is_readable_console);
            }

            if (else_rd.fdset->fd_count || else_wr.fdset->fd_count) {
                r = do_select(nfds, rd, wr, ex, &zero); // polling
                if (r < 0) break; // XXX: should I ignore error and return signaled handles?
                r += copy_fd(rd, else_rd.fdset);
                r += copy_fd(wr, else_wr.fdset);
                if (ex)
                    r += ex->fd_count;
                break;
            }
            else {
                const struct timeval *dowait = &wait;

                fd_set orig_rd;
                fd_set orig_wr;
                fd_set orig_ex;

                FD_ZERO(&orig_rd);
                FD_ZERO(&orig_wr);
                FD_ZERO(&orig_ex);

                if (rd) copy_fd(&orig_rd, rd);
                if (wr) copy_fd(&orig_wr, wr);
                if (ex) copy_fd(&orig_ex, ex);
                r = do_select(nfds, rd, wr, ex, &zero); // polling
                if (r != 0) break; // signaled or error
                if (rd) copy_fd(rd, &orig_rd);
                if (wr) copy_fd(wr, &orig_wr);
                if (ex) copy_fd(ex, &orig_ex);

                if (timeout) {
                    struct timeval now;
                    gettimeofday(&now, NULL);
                    rest = limit;
                    if (!rb_w32_time_subtract(&rest, &now)) break;
                    if (compare(&rest, &wait) < 0) dowait = &rest;
                }
                Sleep(dowait->tv_sec * 1000 + (dowait->tv_usec + 999) / 1000);
            }
        }
    }

    rb_fd_term(&except);
    rb_fd_term(&cons_rd);
    rb_fd_term(&pipe_rd);
    rb_fd_term(&else_wr);
    rb_fd_term(&else_rd);

    return r;
}

/* License: Ruby's */
int WSAAPI
rb_w32_select(int nfds, fd_set *rd, fd_set *wr, fd_set *ex,
              struct timeval *timeout)
{
    return rb_w32_select_with_thread(nfds, rd, wr, ex, timeout, 0);
}

/* License: Ruby's */
static FARPROC
get_wsa_extension_function(SOCKET s, GUID *guid)
{
    DWORD dmy;
    FARPROC ptr = NULL;

    WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, guid, sizeof(*guid),
             &ptr, sizeof(ptr), &dmy, NULL, NULL);
    if (!ptr)
        errno = ENOSYS;
    return ptr;
}

#undef accept

/* License: Artistic or GPL */
int WSAAPI
rb_w32_accept(int s, struct sockaddr *addr, int *addrlen)
{
    SOCKET r;
    int fd;

    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = accept(TO_SOCKET(s), addr, addrlen);
        if (r != INVALID_SOCKET) {
            SetHandleInformation((HANDLE)r, HANDLE_FLAG_INHERIT, 0);
            fd = rb_w32_open_osfhandle((intptr_t)r, O_RDWR|O_BINARY|O_NOINHERIT);
            if (fd != -1)
                socklist_insert(r, 0);
            else
                closesocket(r);
        }
        else {
            errno = map_errno(WSAGetLastError());
            fd = -1;
        }
    }
    return fd;
}

#undef bind

/* License: Artistic or GPL */
int WSAAPI
rb_w32_bind(int s, const struct sockaddr *addr, int addrlen)
{
    int r;

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

#undef connect

/* License: Artistic or GPL */
int WSAAPI
rb_w32_connect(int s, const struct sockaddr *addr, int addrlen)
{
    int r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = connect(TO_SOCKET(s), addr, addrlen);
        if (r == SOCKET_ERROR) {
            int err = WSAGetLastError();
            if (err != WSAEWOULDBLOCK)
                errno = map_errno(err);
            else
                errno = EINPROGRESS;
        }
    }
    return r;
}


#undef getpeername

/* License: Artistic or GPL */
int WSAAPI
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 = map_errno(WSAGetLastError());
    }
    return r;
}

#undef getsockname

/* License: Artistic or GPL */
int WSAAPI
rb_w32_getsockname(int fd, struct sockaddr *addr, int *addrlen)
{
    int sock;
    int r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        sock = TO_SOCKET(fd);
        r = getsockname(sock, addr, addrlen);
        if (r == SOCKET_ERROR) {
            DWORD wsaerror = WSAGetLastError();
            if (wsaerror == WSAEINVAL) {
                int flags;
                if (socklist_lookup(sock, &flags)) {
                    int af = GET_FAMILY(flags);
                    if (af) {
                        memset(addr, 0, *addrlen);
                        addr->sa_family = af;
                        return 0;
                    }
                }
            }
            errno = map_errno(wsaerror);
        }
    }
    return r;
}

#undef getsockopt

/* License: Artistic or GPL */
int WSAAPI
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 = map_errno(WSAGetLastError());
    }
    return r;
}

#undef ioctlsocket

/* License: Artistic or GPL */
int WSAAPI
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 = map_errno(WSAGetLastError());
    }
    return r;
}

#undef listen

/* License: Artistic or GPL */
int WSAAPI
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 = map_errno(WSAGetLastError());
    }
    return r;
}

#undef recv
#undef recvfrom
#undef send
#undef sendto

/* License: Ruby's */
static int
finish_overlapped_socket(BOOL input, SOCKET s, WSAOVERLAPPED *wol, int result, DWORD *len, DWORD size)
{
    DWORD flg;
    int err;

    if (result != SOCKET_ERROR)
        *len = size;
    else if ((err = WSAGetLastError()) == WSA_IO_PENDING) {
        switch (rb_w32_wait_events_blocking(&wol->hEvent, 1, INFINITE)) {
          case WAIT_OBJECT_0:
            RUBY_CRITICAL {
                result = WSAGetOverlappedResult(s, wol, &size, TRUE, &flg);
            }
            if (result) {
                result = 0;
                *len = size;
                break;
            }
            result = SOCKET_ERROR;
            /* thru */
          default:
            if ((err = WSAGetLastError()) == WSAECONNABORTED && !input)
                errno = EPIPE;
            else if (err == WSAEMSGSIZE && input) {
                result = 0;
                *len = size;
                break;
            }
            else
                errno = map_errno(err);
            /* thru */
          case WAIT_OBJECT_0 + 1:
            /* interrupted */
            *len = -1;
            CancelIo((HANDLE)s);
            break;
        }
    }
    else {
        if (err == WSAECONNABORTED && !input)
            errno = EPIPE;
        else
            errno = map_errno(err);
        *len = -1;
    }
    CloseHandle(wol->hEvent);

    return result;
}

/* License: Artistic or GPL */
static int
overlapped_socket_io(BOOL input, int fd, char *buf, int len, int flags,
                     struct sockaddr *addr, int *addrlen)
{
    int r;
    int ret;
    int mode = 0;
    DWORD flg;
    WSAOVERLAPPED wol;
    WSABUF wbuf;
    SOCKET s;

    if (!NtSocketsInitialized)
        StartSockets();

    s = TO_SOCKET(fd);
    socklist_lookup(s, &mode);
    if (GET_FLAGS(mode) & O_NONBLOCK) {
        RUBY_CRITICAL {
            if (input) {
                if (addr && addrlen)
                    r = recvfrom(s, buf, len, flags, addr, addrlen);
                else
                    r = recv(s, buf, len, flags);
                if (r == SOCKET_ERROR)
                    errno = map_errno(WSAGetLastError());
            }
            else {
                if (addr && addrlen)
                    r = sendto(s, buf, len, flags, addr, *addrlen);
                else
                    r = send(s, buf, len, flags);
                if (r == SOCKET_ERROR) {
                    DWORD err = WSAGetLastError();
                    if (err == WSAECONNABORTED)
                        errno = EPIPE;
                    else
                        errno = map_errno(err);
                }
            }
        }
    }
    else {
        DWORD size;
        DWORD rlen;
        wbuf.len = len;
        wbuf.buf = buf;
        memset(&wol, 0, sizeof(wol));
        RUBY_CRITICAL {
            wol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
            if (input) {
                flg = flags;
                if (addr && addrlen)
                    ret = WSARecvFrom(s, &wbuf, 1, &size, &flg, addr, addrlen,
                                      &wol, NULL);
                else
                    ret = WSARecv(s, &wbuf, 1, &size, &flg, &wol, NULL);
            }
            else {
                if (addr && addrlen)
                    ret = WSASendTo(s, &wbuf, 1, &size, flags, addr, *addrlen,
                                    &wol, NULL);
                else
                    ret = WSASend(s, &wbuf, 1, &size, flags, &wol, NULL);
            }
        }

        finish_overlapped_socket(input, s, &wol, ret, &rlen, size);
        r = (int)rlen;
    }

    return r;
}

/* License: Ruby's */
int WSAAPI
rb_w32_recv(int fd, char *buf, int len, int flags)
{
    return overlapped_socket_io(TRUE, fd, buf, len, flags, NULL, NULL);
}

/* License: Ruby's */
int WSAAPI
rb_w32_recvfrom(int fd, char *buf, int len, int flags,
                struct sockaddr *from, int *fromlen)
{
    return overlapped_socket_io(TRUE, fd, buf, len, flags, from, fromlen);
}

/* License: Ruby's */
int WSAAPI
rb_w32_send(int fd, const char *buf, int len, int flags)
{
    return overlapped_socket_io(FALSE, fd, (char *)buf, len, flags, NULL, NULL);
}

/* License: Ruby's */
int WSAAPI
rb_w32_sendto(int fd, const char *buf, int len, int flags,
              const struct sockaddr *to, int tolen)
{
    return overlapped_socket_io(FALSE, fd, (char *)buf, len, flags,
                                (struct sockaddr *)to, &tolen);
}

#if !defined(MSG_TRUNC) && !defined(__MINGW32__)
/* License: Ruby's */
typedef struct {
    SOCKADDR *name;
    int namelen;
    WSABUF *lpBuffers;
    DWORD dwBufferCount;
    WSABUF Control;
    DWORD dwFlags;
} WSAMSG;
#endif
#ifndef WSAID_WSARECVMSG
#define WSAID_WSARECVMSG {0xf689d7c8,0x6f1f,0x436b,{0x8a,0x53,0xe5,0x4f,0xe3,0x51,0xc3,0x22}}
#endif
#ifndef WSAID_WSASENDMSG
#define WSAID_WSASENDMSG {0xa441e712,0x754f,0x43ca,{0x84,0xa7,0x0d,0xee,0x44,0xcf,0x60,0x6d}}
#endif

/* License: Ruby's */
#define msghdr_to_wsamsg(msg, wsamsg) \
    do { \
        int i; \
        (wsamsg)->name = (msg)->msg_name; \
        (wsamsg)->namelen = (msg)->msg_namelen; \
        (wsamsg)->lpBuffers = ALLOCA_N(WSABUF, (msg)->msg_iovlen); \
        (wsamsg)->dwBufferCount = (msg)->msg_iovlen; \
        for (i = 0; i < (msg)->msg_iovlen; ++i) { \
            (wsamsg)->lpBuffers[i].buf = (msg)->msg_iov[i].iov_base; \
            (wsamsg)->lpBuffers[i].len = (msg)->msg_iov[i].iov_len; \
        } \
        (wsamsg)->Control.buf = (msg)->msg_control; \
        (wsamsg)->Control.len = (msg)->msg_controllen; \
        (wsamsg)->dwFlags = (msg)->msg_flags; \
    } while (0)

/* License: Ruby's */
int
recvmsg(int fd, struct msghdr *msg, int flags)
{
    typedef int (WSAAPI *WSARecvMsg_t)(SOCKET, WSAMSG *, DWORD *, WSAOVERLAPPED *, LPWSAOVERLAPPED_COMPLETION_ROUTINE);
    static WSARecvMsg_t pWSARecvMsg = NULL;
    WSAMSG wsamsg;
    SOCKET s;
    int mode = 0;
    DWORD len;
    int ret;

    if (!NtSocketsInitialized)
        StartSockets();

    s = TO_SOCKET(fd);

    if (!pWSARecvMsg) {
        static GUID guid = WSAID_WSARECVMSG;
        pWSARecvMsg = (WSARecvMsg_t)get_wsa_extension_function(s, &guid);
        if (!pWSARecvMsg)
            return -1;
    }

    msghdr_to_wsamsg(msg, &wsamsg);
    wsamsg.dwFlags |= flags;

    socklist_lookup(s, &mode);
    if (GET_FLAGS(mode) & O_NONBLOCK) {
        RUBY_CRITICAL {
            if ((ret = pWSARecvMsg(s, &wsamsg, &len, NULL, NULL)) == SOCKET_ERROR) {
                errno = map_errno(WSAGetLastError());
                len = -1;
            }
        }
    }
    else {
        DWORD size;
        WSAOVERLAPPED wol;
        memset(&wol, 0, sizeof(wol));
        RUBY_CRITICAL {
            wol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
            ret = pWSARecvMsg(s, &wsamsg, &size, &wol, NULL);
        }

        ret = finish_overlapped_socket(TRUE, s, &wol, ret, &len, size);
    }
    if (ret == SOCKET_ERROR)
        return -1;

    /* WSAMSG to msghdr */
    msg->msg_name = wsamsg.name;
    msg->msg_namelen = wsamsg.namelen;
    msg->msg_flags = wsamsg.dwFlags;

    return len;
}

/* License: Ruby's */
int
sendmsg(int fd, const struct msghdr *msg, int flags)
{
    typedef int (WSAAPI *WSASendMsg_t)(SOCKET, const WSAMSG *, DWORD, DWORD *, WSAOVERLAPPED *, LPWSAOVERLAPPED_COMPLETION_ROUTINE);
    static WSASendMsg_t pWSASendMsg = NULL;
    WSAMSG wsamsg;
    SOCKET s;
    int mode = 0;
    DWORD len;
    int ret;

    if (!NtSocketsInitialized)
        StartSockets();

    s = TO_SOCKET(fd);

    if (!pWSASendMsg) {
        static GUID guid = WSAID_WSASENDMSG;
        pWSASendMsg = (WSASendMsg_t)get_wsa_extension_function(s, &guid);
        if (!pWSASendMsg)
            return -1;
    }

    msghdr_to_wsamsg(msg, &wsamsg);

    socklist_lookup(s, &mode);
    if (GET_FLAGS(mode) & O_NONBLOCK) {
        RUBY_CRITICAL {
            if ((ret = pWSASendMsg(s, &wsamsg, flags, &len, NULL, NULL)) == SOCKET_ERROR) {
                errno = map_errno(WSAGetLastError());
                len = -1;
            }
        }
    }
    else {
        DWORD size;
        WSAOVERLAPPED wol;
        memset(&wol, 0, sizeof(wol));
        RUBY_CRITICAL {
            wol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
            ret = pWSASendMsg(s, &wsamsg, flags, &size, &wol, NULL);
        }

        finish_overlapped_socket(FALSE, s, &wol, ret, &len, size);
    }

    return len;
}

#undef setsockopt

/* License: Artistic or GPL */
int WSAAPI
rb_w32_setsockopt(int s, int level, int optname, const 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 = map_errno(WSAGetLastError());
    }
    return r;
}

#undef shutdown

/* License: Artistic or GPL */
int WSAAPI
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 = map_errno(WSAGetLastError());
    }
    return r;
}

/* License: Ruby's */
static SOCKET
open_ifs_socket(int af, int type, int protocol)
{
    unsigned long proto_buffers_len = 0;
    int error_code;
    SOCKET out = INVALID_SOCKET;

    if (WSAEnumProtocols(NULL, NULL, &proto_buffers_len) == SOCKET_ERROR) {
        error_code = WSAGetLastError();
        if (error_code == WSAENOBUFS) {
            WSAPROTOCOL_INFO *proto_buffers;
            int protocols_available = 0;

            proto_buffers = (WSAPROTOCOL_INFO *)malloc(proto_buffers_len);
            if (!proto_buffers) {
                WSASetLastError(WSA_NOT_ENOUGH_MEMORY);
                return INVALID_SOCKET;
            }

            protocols_available =
                WSAEnumProtocols(NULL, proto_buffers, &proto_buffers_len);
            if (protocols_available != SOCKET_ERROR) {
                int i;
                for (i = 0; i < protocols_available; i++) {
                    if ((af != AF_UNSPEC && af != proto_buffers[i].iAddressFamily) ||
                        (type != proto_buffers[i].iSocketType) ||
                        (protocol != 0 && protocol != proto_buffers[i].iProtocol))
                        continue;

                    if ((proto_buffers[i].dwServiceFlags1 & XP1_IFS_HANDLES) == 0)
                        continue;

                    out = WSASocket(af, type, protocol, &(proto_buffers[i]), 0,
                                    WSA_FLAG_OVERLAPPED);
                    break;
                }
                if (out == INVALID_SOCKET)
                    out = WSASocket(af, type, protocol, NULL, 0, 0);
                if (out != INVALID_SOCKET)
                    SetHandleInformation((HANDLE)out, HANDLE_FLAG_INHERIT, 0);
            }

            free(proto_buffers);
        }
    }

    return out;
}

#undef socket

/* License: Artistic or GPL */
int WSAAPI
rb_w32_socket(int af, int type, int protocol)
{
    SOCKET s;
    int fd;

    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        s = open_ifs_socket(af, type, protocol);
        if (s == INVALID_SOCKET) {
            errno = map_errno(WSAGetLastError());
            fd = -1;
        }
        else {
            fd = rb_w32_open_osfhandle(s, O_RDWR|O_BINARY|O_NOINHERIT);
            if (fd != -1)
                socklist_insert(s, MAKE_SOCKDATA(af, 0));
            else
                closesocket(s);
        }
    }
    return fd;
}

#undef gethostbyaddr

/* License: Artistic or GPL */
struct hostent * WSAAPI
rb_w32_gethostbyaddr(const char *addr, int len, int type)
{
    struct hostent *r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = gethostbyaddr(addr, len, type);
        if (r == NULL)
            errno = map_errno(WSAGetLastError());
    }
    return r;
}

#undef gethostbyname

/* License: Artistic or GPL */
struct hostent * WSAAPI
rb_w32_gethostbyname(const char *name)
{
    struct hostent *r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = gethostbyname(name);
        if (r == NULL)
            errno = map_errno(WSAGetLastError());
    }
    return r;
}

#undef gethostname

/* License: Artistic or GPL */
int WSAAPI
rb_w32_gethostname(char *name, int len)
{
    int r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = gethostname(name, len);
        if (r == SOCKET_ERROR)
            errno = map_errno(WSAGetLastError());
    }
    return r;
}

#undef getprotobyname

/* License: Artistic or GPL */
struct protoent * WSAAPI
rb_w32_getprotobyname(const char *name)
{
    struct protoent *r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = getprotobyname(name);
        if (r == NULL)
            errno = map_errno(WSAGetLastError());
    }
    return r;
}

#undef getprotobynumber

/* License: Artistic or GPL */
struct protoent * WSAAPI
rb_w32_getprotobynumber(int num)
{
    struct protoent *r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = getprotobynumber(num);
        if (r == NULL)
            errno = map_errno(WSAGetLastError());
    }
    return r;
}

#undef getservbyname

/* License: Artistic or GPL */
struct servent * WSAAPI
rb_w32_getservbyname(const char *name, const char *proto)
{
    struct servent *r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = getservbyname(name, proto);
        if (r == NULL)
            errno = map_errno(WSAGetLastError());
    }
    return r;
}

#undef getservbyport

/* License: Artistic or GPL */
struct servent * WSAAPI
rb_w32_getservbyport(int port, const char *proto)
{
    struct servent *r;
    if (!NtSocketsInitialized) {
        StartSockets();
    }
    RUBY_CRITICAL {
        r = getservbyport(port, proto);
        if (r == NULL)
            errno = map_errno(WSAGetLastError());
    }
    return r;
}

/* License: Ruby's */
static int
socketpair_internal(int af, int type, int protocol, SOCKET *sv)
{
    SOCKET svr = INVALID_SOCKET, r = INVALID_SOCKET, w = INVALID_SOCKET;
    struct sockaddr_in sock_in4;
#ifdef INET6
    struct sockaddr_in6 sock_in6;
#endif
    struct sockaddr *addr;
    int ret = -1;
    int len;

    if (!NtSocketsInitialized) {
        StartSockets();
    }

    switch (af) {
      case AF_INET:
#if defined PF_INET && PF_INET != AF_INET
      case PF_INET:
#endif
        sock_in4.sin_family = AF_INET;
        sock_in4.sin_port = 0;
        sock_in4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        addr = (struct sockaddr *)&sock_in4;
        len = sizeof(sock_in4);
        break;
#ifdef INET6
      case AF_INET6:
        memset(&sock_in6, 0, sizeof(sock_in6));
        sock_in6.sin6_family = AF_INET6;
        sock_in6.sin6_addr = IN6ADDR_LOOPBACK_INIT;
        addr = (struct sockaddr *)&sock_in6;
        len = sizeof(sock_in6);
        break;
#endif
      default:
        errno = EAFNOSUPPORT;
        return -1;
    }
    if (type != SOCK_STREAM) {
        errno = EPROTOTYPE;
        return -1;
    }

    sv[0] = (SOCKET)INVALID_HANDLE_VALUE;
    sv[1] = (SOCKET)INVALID_HANDLE_VALUE;
    RUBY_CRITICAL {
        do {
            svr = open_ifs_socket(af, type, protocol);
            if (svr == INVALID_SOCKET)
                break;
            if (bind(svr, addr, len) < 0)
                break;
            if (getsockname(svr, addr, &len) < 0)
                break;
            if (type == SOCK_STREAM)
                listen(svr, 5);

            w = open_ifs_socket(af, type, protocol);
            if (w == INVALID_SOCKET)
                break;
            if (connect(w, addr, len) < 0)
                break;

            r = accept(svr, addr, &len);
            if (r == INVALID_SOCKET)
                break;
            SetHandleInformation((HANDLE)r, HANDLE_FLAG_INHERIT, 0);

            ret = 0;
        } while (0);

        if (ret < 0) {
            errno = map_errno(WSAGetLastError());
            if (r != INVALID_SOCKET)
                closesocket(r);
            if (w != INVALID_SOCKET)
                closesocket(w);
        }
        else {
            sv[0] = r;
            sv[1] = w;
        }
        if (svr != INVALID_SOCKET)
            closesocket(svr);
    }

    return ret;
}

/* License: Ruby's */
int
socketpair(int af, int type, int protocol, int *sv)
{
    SOCKET pair[2];

    if (socketpair_internal(af, type, protocol, pair) < 0)
        return -1;
    sv[0] = rb_w32_open_osfhandle(pair[0], O_RDWR|O_BINARY|O_NOINHERIT);
    if (sv[0] == -1) {
        closesocket(pair[0]);
        closesocket(pair[1]);
        return -1;
    }
    sv[1] = rb_w32_open_osfhandle(pair[1], O_RDWR|O_BINARY|O_NOINHERIT);
    if (sv[1] == -1) {
        rb_w32_close(sv[0]);
        closesocket(pair[1]);
        return -1;
    }
    socklist_insert(pair[0], MAKE_SOCKDATA(af, 0));
    socklist_insert(pair[1], MAKE_SOCKDATA(af, 0));

    return 0;
}

#if !defined(_MSC_VER) || _MSC_VER >= 1400
/* License: Ruby's */
static void
str2guid(const char *str, GUID *guid)
{
#define hex2byte(str) \
    ((isdigit(*(str)) ? *(str) - '0' : toupper(*(str)) - 'A' + 10) << 4 | (isdigit(*((str) + 1)) ? *((str) + 1) - '0' : toupper(*((str) + 1)) - 'A' + 10))
    char *end;
    int i;
    if (*str == '{') str++;
    guid->Data1 = (long)strtoul(str, &end, 16);
    str += 9;
    guid->Data2 = (unsigned short)strtoul(str, &end, 16);
    str += 5;
    guid->Data3 = (unsigned short)strtoul(str, &end, 16);
    str += 5;
    guid->Data4[0] = hex2byte(str);
    str += 2;
    guid->Data4[1] = hex2byte(str);
    str += 3;
    for (i = 0; i < 6; i++) {
        guid->Data4[i + 2] = hex2byte(str);
        str += 2;
    }
}

/* License: Ruby's */
#ifndef HAVE_TYPE_NET_LUID
    typedef struct {
        uint64_t Value;
        struct {
            uint64_t Reserved :24;
            uint64_t NetLuidIndex :24;
            uint64_t IfType :16;
        } Info;
    } NET_LUID;
#endif
typedef DWORD (WINAPI *cigl_t)(const GUID *, NET_LUID *);
typedef DWORD (WINAPI *cilnA_t)(const NET_LUID *, char *, size_t);
static cigl_t pConvertInterfaceGuidToLuid = NULL;
static cilnA_t pConvertInterfaceLuidToNameA = NULL;

int
getifaddrs(struct ifaddrs **ifap)
{
    ULONG size = 0;
    ULONG ret;
    IP_ADAPTER_ADDRESSES *root, *addr;
    struct ifaddrs *prev;

    ret = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, NULL, &size);
    if (ret != ERROR_BUFFER_OVERFLOW) {
        errno = map_errno(ret);
        return -1;
    }
    root = ruby_xmalloc(size);
    ret = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, root, &size);
    if (ret != ERROR_SUCCESS) {
        errno = map_errno(ret);
        ruby_xfree(root);
        return -1;
    }

    if (!pConvertInterfaceGuidToLuid)
        pConvertInterfaceGuidToLuid =
            (cigl_t)get_proc_address("iphlpapi.dll",
                                     "ConvertInterfaceGuidToLuid", NULL);
    if (!pConvertInterfaceLuidToNameA)
        pConvertInterfaceLuidToNameA =
            (cilnA_t)get_proc_address("iphlpapi.dll",
                                      "ConvertInterfaceLuidToNameA", NULL);

    for (prev = NULL, addr = root; addr; addr = addr->Next) {
        struct ifaddrs *ifa = ruby_xcalloc(1, sizeof(*ifa));
        char name[IFNAMSIZ];
        GUID guid;
        NET_LUID luid;

        if (prev)
            prev->ifa_next = ifa;
        else
            *ifap = ifa;

        str2guid(addr->AdapterName, &guid);
        if (pConvertInterfaceGuidToLuid && pConvertInterfaceLuidToNameA &&
            pConvertInterfaceGuidToLuid(&guid, &luid) == NO_ERROR &&
            pConvertInterfaceLuidToNameA(&luid, name, sizeof(name)) == NO_ERROR) {
            ifa->ifa_name = ruby_strdup(name);
        }
        else {
            ifa->ifa_name = ruby_strdup(addr->AdapterName);
        }

        if (addr->IfType & IF_TYPE_SOFTWARE_LOOPBACK)
            ifa->ifa_flags |= IFF_LOOPBACK;
        if (addr->OperStatus == IfOperStatusUp) {
            ifa->ifa_flags |= IFF_UP;

            if (addr->FirstUnicastAddress) {
                IP_ADAPTER_UNICAST_ADDRESS *cur;
                int added = 0;
                for (cur = addr->FirstUnicastAddress; cur; cur = cur->Next) {
                    if (cur->Flags & IP_ADAPTER_ADDRESS_TRANSIENT ||
                        cur->DadState == IpDadStateDeprecated) {
                        continue;
                    }
                    if (added) {
                        prev = ifa;
                        ifa = ruby_xcalloc(1, sizeof(*ifa));
                        prev->ifa_next = ifa;
                        ifa->ifa_name = ruby_strdup(prev->ifa_name);
                        ifa->ifa_flags = prev->ifa_flags;
                    }
                    ifa->ifa_addr = ruby_xmalloc(cur->Address.iSockaddrLength);
                    memcpy(ifa->ifa_addr, cur->Address.lpSockaddr,
                           cur->Address.iSockaddrLength);
                    added = 1;
                }
            }
        }

        prev = ifa;
    }

    ruby_xfree(root);
    return 0;
}

/* License: Ruby's */
void
freeifaddrs(struct ifaddrs *ifp)
{
    while (ifp) {
        struct ifaddrs *next = ifp->ifa_next;
        if (ifp->ifa_addr) ruby_xfree(ifp->ifa_addr);
        if (ifp->ifa_name) ruby_xfree(ifp->ifa_name);
        ruby_xfree(ifp);
        ifp = next;
    }
}
#endif

//
// Networking stubs
//

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

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

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

struct netent *getnetbyname(const char *name) {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) {}

/* License: Ruby's */
static int
setfl(SOCKET sock, int arg)
{
    int ret;
    int af = 0;
    int flag = 0;
    u_long ioctlArg;

    socklist_lookup(sock, &flag);
    af = GET_FAMILY(flag);
    flag = GET_FLAGS(flag);
    if (arg & O_NONBLOCK) {
        flag |= O_NONBLOCK;
        ioctlArg = 1;
    }
    else {
        flag &= ~O_NONBLOCK;
        ioctlArg = 0;
    }
    RUBY_CRITICAL {
        ret = ioctlsocket(sock, FIONBIO, &ioctlArg);
        if (ret == 0)
            socklist_insert(sock, MAKE_SOCKDATA(af, flag));
        else
            errno = map_errno(WSAGetLastError());
    }

    return ret;
}

/* License: Ruby's */
static int
dupfd(HANDLE hDup, int flags, int minfd)
{
    int save_errno;
    int ret;
    int fds[32];
    int filled = 0;

    do {
        ret = _open_osfhandle((intptr_t)hDup, flags | FOPEN);
        if (ret == -1) {
            goto close_fds_and_return;
        }
        if (ret >= minfd) {
            goto close_fds_and_return;
        }
        fds[filled++] = ret;
    } while (filled < (int)numberof(fds));

    ret = dupfd(hDup, flags, minfd);

  close_fds_and_return:
    save_errno = errno;
    while (filled > 0) {
        int fd = fds[--filled];
        _set_osfhnd(fd, (intptr_t)INVALID_HANDLE_VALUE);
        close(fd);
    }
    errno = save_errno;

    return ret;
}

/* License: Ruby's */
int
fcntl(int fd, int cmd, ...)
{
    va_list va;
    int arg;
    DWORD flag;

    switch (cmd) {
      case F_SETFL: {
        SOCKET sock = TO_SOCKET(fd);
        if (!is_socket(sock)) {
            errno = EBADF;
            return -1;
        }

        va_start(va, cmd);
        arg = va_arg(va, int);
        va_end(va);
        return setfl(sock, arg);
      }
      case F_DUPFD: case F_DUPFD_CLOEXEC: {
        int ret;
        HANDLE hDup;
        flag = _osfile(fd);
        if (!(DuplicateHandle(GetCurrentProcess(), (HANDLE)_get_osfhandle(fd),
                              GetCurrentProcess(), &hDup, 0L,
                              cmd == F_DUPFD && !(flag & FNOINHERIT),
                              DUPLICATE_SAME_ACCESS))) {
            errno = map_errno(GetLastError());
            return -1;
        }

        va_start(va, cmd);
        arg = va_arg(va, int);
        va_end(va);

        if (cmd != F_DUPFD)
            flag |= FNOINHERIT;
        else
            flag &= ~FNOINHERIT;
        if ((ret = dupfd(hDup, flag, arg)) == -1)
            CloseHandle(hDup);
        return ret;
      }
      case F_GETFD: {
        SIGNED_VALUE h = _get_osfhandle(fd);
        if (h == -1) return -1;
        if (!GetHandleInformation((HANDLE)h, &flag)) {
            errno = map_errno(GetLastError());
            return -1;
        }
        return (flag & HANDLE_FLAG_INHERIT) ? 0 : FD_CLOEXEC;
      }
      case F_SETFD: {
        SIGNED_VALUE h = _get_osfhandle(fd);
        if (h == -1) return -1;
        va_start(va, cmd);
        arg = va_arg(va, int);
        va_end(va);
        if (!SetHandleInformation((HANDLE)h, HANDLE_FLAG_INHERIT,
                                  (arg & FD_CLOEXEC) ? 0 : HANDLE_FLAG_INHERIT)) {
            errno = map_errno(GetLastError());
            return -1;
        }
        if (arg & FD_CLOEXEC)
            _osfile(fd) |= FNOINHERIT;
        else
            _osfile(fd) &= ~FNOINHERIT;
        return 0;
      }
      default:
        errno = EINVAL;
        return -1;
    }
}

/* License: Ruby's */
int
rb_w32_set_nonblock(int fd)
{
    SOCKET sock = TO_SOCKET(fd);
    if (is_socket(sock)) {
        return setfl(sock, O_NONBLOCK);
    }
    else if (is_pipe(sock)) {
        DWORD state;
        if (!GetNamedPipeHandleState((HANDLE)sock, &state, NULL, NULL, NULL, NULL, 0)) {
            errno = map_errno(GetLastError());
            return -1;
        }
        state |= PIPE_NOWAIT;
        if (!SetNamedPipeHandleState((HANDLE)sock, &state, NULL, NULL)) {
            errno = map_errno(GetLastError());
            return -1;
        }
        return 0;
    }
    else {
        errno = EBADF;
        return -1;
    }
}

#ifndef WNOHANG
#define WNOHANG -1
#endif

/* License: Ruby's */
static rb_pid_t
poll_child_status(struct ChildRecord *child, int *stat_loc)
{
    DWORD exitcode;
    DWORD err;

    if (!GetExitCodeProcess(child->hProcess, &exitcode)) {
        /* If an error occurred, return immediately. */
    error_exit:
        err = GetLastError();
        switch (err) {
          case ERROR_INVALID_PARAMETER:
            errno = ECHILD;
            break;
          case ERROR_INVALID_HANDLE:
            errno = EINVAL;
            break;
          default:
            errno = map_errno(err);
            break;
        }
        CloseChildHandle(child);
        return -1;
    }
    if (exitcode != STILL_ACTIVE) {
        rb_pid_t pid;
        /* If already died, wait process's real termination. */
        if (rb_w32_wait_events_blocking(&child->hProcess, 1, INFINITE) != WAIT_OBJECT_0) {
            goto error_exit;
        }
        pid = child->pid;
        CloseChildHandle(child);
        if (stat_loc) {
            *stat_loc = exitcode << 8;
            if (exitcode & 0xC0000000) {
                static const struct {
                    DWORD status;
                    int sig;
                } table[] = {
                    {STATUS_ACCESS_VIOLATION,        SIGSEGV},
                    {STATUS_ILLEGAL_INSTRUCTION,     SIGILL},
                    {STATUS_PRIVILEGED_INSTRUCTION,  SIGILL},
                    {STATUS_FLOAT_DENORMAL_OPERAND,  SIGFPE},
                    {STATUS_FLOAT_DIVIDE_BY_ZERO,    SIGFPE},
                    {STATUS_FLOAT_INEXACT_RESULT,    SIGFPE},
                    {STATUS_FLOAT_INVALID_OPERATION, SIGFPE},
                    {STATUS_FLOAT_OVERFLOW,          SIGFPE},
                    {STATUS_FLOAT_STACK_CHECK,       SIGFPE},
                    {STATUS_FLOAT_UNDERFLOW,         SIGFPE},
#ifdef STATUS_FLOAT_MULTIPLE_FAULTS
                    {STATUS_FLOAT_MULTIPLE_FAULTS,   SIGFPE},
#endif
#ifdef STATUS_FLOAT_MULTIPLE_TRAPS
                    {STATUS_FLOAT_MULTIPLE_TRAPS,    SIGFPE},
#endif
                    {STATUS_CONTROL_C_EXIT,          SIGINT},
                };
                int i;
                for (i = 0; i < (int)numberof(table); i++) {
                    if (table[i].status == exitcode) {
                        *stat_loc |= table[i].sig;
                        break;
                    }
                }
                // if unknown status, assume SEGV
                if (i >= (int)numberof(table))
                    *stat_loc |= SIGSEGV;
            }
        }
        return pid;
    }
    return 0;
}

/* License: Artistic or GPL */
rb_pid_t
waitpid(rb_pid_t pid, int *stat_loc, int options)
{
    DWORD timeout;

    /* Artistic or GPL part start */
    if (options == WNOHANG) {
        timeout = 0;
    }
    else {
        timeout = INFINITE;
    }
    /* Artistic or GPL part end */

    if (pid == -1) {
        int count = 0;
        int ret;
        HANDLE events[MAXCHILDNUM];
        struct ChildRecord* cause;

        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;
        }

        ret = rb_w32_wait_events_blocking(events, count, timeout);
        if (ret == WAIT_TIMEOUT) return 0;
        if ((ret -= WAIT_OBJECT_0) == count) {
            return -1;
        }
        if (ret > count) {
            errno = map_errno(GetLastError());
            return -1;
        }

        cause = FindChildSlotByHandle(events[ret]);
        if (!cause) {
            errno = ECHILD;
            return -1;
        }
        return poll_child_status(cause, stat_loc);
    }
    else {
        struct ChildRecord* child = FindChildSlot(pid);
        int retried = 0;
        if (!child) {
            errno = ECHILD;
            return -1;
        }

        while (!(pid = poll_child_status(child, stat_loc))) {
            /* wait... */
            int ret = rb_w32_wait_events_blocking(&child->hProcess, 1, timeout);
            if (ret == WAIT_OBJECT_0 + 1) return -1; /* maybe EINTR */
            if (ret != WAIT_OBJECT_0) {
                /* still active */
                if (options & WNOHANG) {
                    pid = 0;
                    break;
                }
                ++retried;
            }
        }
        if (pid == -1 && retried) pid = 0;
    }

    return pid;
}

#include <sys/timeb.h>

/* License: Ruby's */
static int
filetime_to_timeval(const FILETIME* ft, struct timeval *tv)
{
    ULARGE_INTEGER tmp;
    unsigned LONG_LONG lt;

    tmp.LowPart = ft->dwLowDateTime;
    tmp.HighPart = ft->dwHighDateTime;
    lt = tmp.QuadPart;

    /* lt is now 100-nanosec intervals since 1601/01/01 00:00:00 UTC,
       convert it into UNIX time (since 1970/01/01 00:00:00 UTC).
       the first leap second is at 1972/06/30, so we doesn't need to think
       about it. */
    lt /= 10;   /* to usec */
    lt -= (LONG_LONG)((1970-1601)*365.2425) * 24 * 60 * 60 * 1000 * 1000;

    tv->tv_sec = (long)(lt / (1000 * 1000));
    tv->tv_usec = (long)(lt % (1000 * 1000));

    return tv->tv_sec > 0 ? 0 : -1;
}

/* License: Ruby's */
int __cdecl
gettimeofday(struct timeval *tv, struct timezone *tz)
{
    FILETIME ft;

    GetSystemTimeAsFileTime(&ft);
    filetime_to_timeval(&ft, tv);

    return 0;
}

/* License: Ruby's */
int
clock_gettime(clockid_t clock_id, struct timespec *sp)
{
    switch (clock_id) {
      case CLOCK_REALTIME:
        {
            struct timeval tv;
            gettimeofday(&tv, NULL);
            sp->tv_sec = tv.tv_sec;
            sp->tv_nsec = tv.tv_usec * 1000;
            return 0;
        }
      case CLOCK_MONOTONIC:
        {
            LARGE_INTEGER freq;
            LARGE_INTEGER count;
            if (!QueryPerformanceFrequency(&freq)) {
                errno = map_errno(GetLastError());
                return -1;
            }
            if (!QueryPerformanceCounter(&count)) {
                errno = map_errno(GetLastError());
                return -1;
            }
            sp->tv_sec = count.QuadPart / freq.QuadPart;
            if (freq.QuadPart < 1000000000)
                sp->tv_nsec = (count.QuadPart % freq.QuadPart) * 1000000000 / freq.QuadPart;
            else
                sp->tv_nsec = (long)((count.QuadPart % freq.QuadPart) * (1000000000.0 / freq.QuadPart));
            return 0;
        }
      default:
        errno = EINVAL;
        return -1;
    }
}

/* License: Ruby's */
int
clock_getres(clockid_t clock_id, struct timespec *sp)
{
    switch (clock_id) {
      case CLOCK_REALTIME:
        {
            sp->tv_sec = 0;
            sp->tv_nsec = 1000;
            return 0;
        }
      case CLOCK_MONOTONIC:
        {
            LARGE_INTEGER freq;
            if (!QueryPerformanceFrequency(&freq)) {
                errno = map_errno(GetLastError());
                return -1;
            }
            sp->tv_sec = 0;
            sp->tv_nsec = (long)(1000000000.0 / freq.QuadPart);
            return 0;
        }
      default:
        errno = EINVAL;
        return -1;
    }
}

/* License: Ruby's */
char *
rb_w32_getcwd(char *buffer, int size)
{
    char *p = buffer;
    int len;

    len = GetCurrentDirectory(0, NULL);
    if (!len) {
        errno = map_errno(GetLastError());
        return NULL;
    }

    if (p) {
        if (size < len) {
            errno = ERANGE;
            return NULL;
        }
    }
    else {
        p = malloc(len);
        size = len;
        if (!p) {
            errno = ENOMEM;
            return NULL;
        }
    }

    if (!GetCurrentDirectory(size, p)) {
        errno = map_errno(GetLastError());
        if (!buffer)
            free(p);
        return NULL;
    }

    translate_char(p, '\\', '/', filecp());

    return p;
}

/* License: Artistic or GPL */
int
chown(const char *path, int owner, int group)
{
    return 0;
}

/* License: Artistic or GPL */
int
rb_w32_uchown(const char *path, int owner, int group)
{
    return 0;
}

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

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

/* License: Ruby's */
int
kill(int pid, int sig)
{
    int ret = 0;
    DWORD err;

    if (pid < 0 || (pid == 0 && sig != SIGINT)) {
        errno = EINVAL;
        return -1;
    }

    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 {
            DWORD ctrlEvent = CTRL_C_EVENT;
            if (pid != 0) {
                /* CTRL+C signal cannot be generated for process groups.
                 * Instead, we use CTRL+BREAK signal. */
                ctrlEvent = CTRL_BREAK_EVENT;
            }
            if (!GenerateConsoleCtrlEvent(ctrlEvent, (DWORD)pid)) {
                if ((err = GetLastError()) == 0)
                    errno = EPERM;
                else
                    errno = map_errno(GetLastError());
                ret = -1;
            }
        }
        break;

      case SIGKILL:
        RUBY_CRITICAL {
            HANDLE hProc;
            struct ChildRecord* child = FindChildSlot(pid);
            if (child) {
                hProc = child->hProcess;
            }
            else {
                hProc = OpenProcess(PROCESS_TERMINATE | 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 {
                DWORD status;
                if (!GetExitCodeProcess(hProc, &status)) {
                    errno = map_errno(GetLastError());
                    ret = -1;
                }
                else if (status == STILL_ACTIVE) {
                    if (!TerminateProcess(hProc, 0)) {
                        errno = EPERM;
                        ret = -1;
                    }
                }
                else {
                    errno = ESRCH;
                    ret = -1;
                }
                if (!child) {
                    CloseHandle(hProc);
                }
            }
        }
        break;

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

    return ret;
}

/* License: Ruby's */
static int
wlink(const WCHAR *from, const WCHAR *to)
{
    if (!CreateHardLinkW(to, from, NULL)) {
        errno = map_errno(GetLastError());
        return -1;
    }

    return 0;
}

/* License: Ruby's */
int
rb_w32_ulink(const char *from, const char *to)
{
    WCHAR *wfrom;
    WCHAR *wto;
    int ret;

    if (!(wfrom = utf8_to_wstr(from, NULL)))
        return -1;
    if (!(wto = utf8_to_wstr(to, NULL))) {
        free(wfrom);
        return -1;
    }
    ret = wlink(wfrom, wto);
    free(wto);
    free(wfrom);
    return ret;
}

/* License: Ruby's */
int
link(const char *from, const char *to)
{
    WCHAR *wfrom;
    WCHAR *wto;
    int ret;

    if (!(wfrom = filecp_to_wstr(from, NULL)))
        return -1;
    if (!(wto = filecp_to_wstr(to, NULL))) {
        free(wfrom);
        return -1;
    }
    ret = wlink(wfrom, wto);
    free(wto);
    free(wfrom);
    return ret;
}

/* License: Public Domain, copied from mingw headers */
#ifndef FILE_DEVICE_FILE_SYSTEM
# define FILE_DEVICE_FILE_SYSTEM 0x00000009
#endif
#ifndef FSCTL_GET_REPARSE_POINT
# define FSCTL_GET_REPARSE_POINT ((0x9<<16)|(42<<2))
#endif
#ifndef IO_REPARSE_TAG_SYMLINK
# define IO_REPARSE_TAG_SYMLINK 0xA000000CL
#endif

/* License: Ruby's */
static int
reparse_symlink(const WCHAR *path, rb_w32_reparse_buffer_t *rp, size_t size)
{
    HANDLE f;
    DWORD ret;
    int e = 0;

    f = open_special(path, 0, FILE_FLAG_OPEN_REPARSE_POINT);
    if (f == INVALID_HANDLE_VALUE) {
        return GetLastError();
    }

    if (!DeviceIoControl(f, FSCTL_GET_REPARSE_POINT, NULL, 0,
                           rp, size, &ret, NULL)) {
        e = GetLastError();
    }
    else if (rp->ReparseTag != IO_REPARSE_TAG_SYMLINK &&
             rp->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) {
        e = ERROR_INVALID_PARAMETER;
    }
    CloseHandle(f);
    return e;
}

/* License: Ruby's */
int
rb_w32_reparse_symlink_p(const WCHAR *path)
{
    VALUE wtmp = 0;
    rb_w32_reparse_buffer_t rbuf, *rp = &rbuf;
    WCHAR *wbuf;
    DWORD len;
    int e;

    e = rb_w32_read_reparse_point(path, rp, sizeof(rbuf), &wbuf, &len);
    if (e == ERROR_MORE_DATA) {
        size_t size = rb_w32_reparse_buffer_size(len + 1);
        rp = ALLOCV(wtmp, size);
        e = rb_w32_read_reparse_point(path, rp, size, &wbuf, &len);
        ALLOCV_END(wtmp);
    }
    switch (e) {
      case 0:
      case ERROR_MORE_DATA:
        return TRUE;
    }
    return FALSE;
}

/* License: Ruby's */
int
rb_w32_read_reparse_point(const WCHAR *path, rb_w32_reparse_buffer_t *rp,
                          size_t bufsize, WCHAR **result, DWORD *len)
{
    int e = reparse_symlink(path, rp, bufsize);
    DWORD ret = 0;

    if (!e || e == ERROR_MORE_DATA) {
        void *name;
        if (rp->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
            name = ((char *)rp->SymbolicLinkReparseBuffer.PathBuffer +
                    rp->SymbolicLinkReparseBuffer.PrintNameOffset);
            ret = rp->SymbolicLinkReparseBuffer.PrintNameLength;
            *len = ret / sizeof(WCHAR);
        }
        else { /* IO_REPARSE_TAG_MOUNT_POINT */
            static const WCHAR *volume = L"Volume{";
            enum {volume_prefix_len = rb_strlen_lit("\\??\\")};
            name = ((char *)rp->MountPointReparseBuffer.PathBuffer +
                    rp->MountPointReparseBuffer.SubstituteNameOffset +
                    volume_prefix_len * sizeof(WCHAR));
            ret = rp->MountPointReparseBuffer.SubstituteNameLength;
            *len = ret / sizeof(WCHAR);
            ret -= volume_prefix_len * sizeof(WCHAR);
            if (ret > sizeof(volume) - 1 * sizeof(WCHAR) &&
                memcmp(name, volume, sizeof(volume) - 1 * sizeof(WCHAR)) == 0)
                return -1;
        }
        *result = name;
        if (e) {
            if ((char *)name + ret + sizeof(WCHAR) > (char *)rp + bufsize)
                return e;
            /* SubstituteName is not used */
        }
        ((WCHAR *)name)[ret/sizeof(WCHAR)] = L'\0';
        translate_wchar(name, L'\\', L'/');
        return 0;
    }
    else {
        return e;
    }
}

/* License: Ruby's */
static ssize_t
w32_readlink(UINT cp, const char *path, char *buf, size_t bufsize)
{
    VALUE wtmp;
    DWORD len = MultiByteToWideChar(cp, 0, path, -1, NULL, 0);
    size_t size = rb_w32_reparse_buffer_size(len);
    WCHAR *wname, *wpath = ALLOCV(wtmp, size + sizeof(WCHAR) * len);
    rb_w32_reparse_buffer_t *rp = (void *)(wpath + len);
    ssize_t ret;
    int e;

    MultiByteToWideChar(cp, 0, path, -1, wpath, len);
    e = rb_w32_read_reparse_point(wpath, rp, size, &wname, &len);
    if (e && e != ERROR_MORE_DATA) {
        ALLOCV_END(wtmp);
        errno = map_errno(e);
        return -1;
    }
    len = lstrlenW(wname) + 1;
    ret = WideCharToMultiByte(cp, 0, wname, len, buf, bufsize, NULL, NULL);
    ALLOCV_END(wtmp);
    if (e) {
        ret = bufsize;
    }
    else if (!ret) {
        e = GetLastError();
        errno = map_errno(e);
        ret = -1;
    }
    return ret;
}

/* License: Ruby's */
ssize_t
rb_w32_ureadlink(const char *path, char *buf, size_t bufsize)
{
    return w32_readlink(CP_UTF8, path, buf, bufsize);
}

/* License: Ruby's */
ssize_t
readlink(const char *path, char *buf, size_t bufsize)
{
    return w32_readlink(filecp(), path, buf, bufsize);
}

#ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
#define SYMBOLIC_LINK_FLAG_DIRECTORY (0x1)
#endif

/* License: Ruby's */
static int
w32_symlink(UINT cp, const char *src, const char *link)
{
    int atts, len1, len2;
    VALUE buf;
    WCHAR *wsrc, *wlink;
    DWORD flag = 0;
    BOOLEAN ret;

    typedef DWORD (WINAPI *create_symbolic_link_func)(WCHAR*, WCHAR*, DWORD);
    static create_symbolic_link_func create_symbolic_link =
        (create_symbolic_link_func)-1;

    if (create_symbolic_link == (create_symbolic_link_func)-1) {
        create_symbolic_link = (create_symbolic_link_func)
            get_proc_address("kernel32", "CreateSymbolicLinkW", NULL);
    }
    if (!create_symbolic_link) {
        errno = ENOSYS;
        return -1;
    }

    len1 = MultiByteToWideChar(cp, 0, src, -1, NULL, 0);
    len2 = MultiByteToWideChar(cp, 0, link, -1, NULL, 0);
    wsrc = ALLOCV_N(WCHAR, buf, len1+len2);
    wlink = wsrc + len1;
    MultiByteToWideChar(cp, 0, src, -1, wsrc, len1);
    MultiByteToWideChar(cp, 0, link, -1, wlink, len2);
    translate_wchar(wsrc, L'/', L'\\');

    atts = GetFileAttributesW(wsrc);
    if (atts != -1 && atts & FILE_ATTRIBUTE_DIRECTORY)
        flag = SYMBOLIC_LINK_FLAG_DIRECTORY;
    ret = create_symbolic_link(wlink, wsrc, flag);
    ALLOCV_END(buf);

    if (!ret) {
        int e = GetLastError();
        errno = map_errno(e);
        return -1;
    }
    return 0;
}

/* License: Ruby's */
int
rb_w32_usymlink(const char *src, const char *link)
{
    return w32_symlink(CP_UTF8, src, link);
}

/* License: Ruby's */
int
symlink(const char *src, const char *link)
{
    return w32_symlink(filecp(), src, link);
}

/* License: Ruby's */
int
wait(int *status)
{
    return waitpid(-1, status, 0);
}

/* License: Ruby's */
static char *
w32_getenv(const char *name, UINT cp)
{
    WCHAR *wenvarea, *wenv;
    int len = strlen(name);
    char *env;
    int wlen;

    if (len == 0) return NULL;

    if (uenvarea) {
        free(uenvarea);
        uenvarea = NULL;
    }
    wenvarea = GetEnvironmentStringsW();
    if (!wenvarea) {
        map_errno(GetLastError());
        return NULL;
    }
    for (wenv = wenvarea, wlen = 1; *wenv; wenv += lstrlenW(wenv) + 1)
        wlen += lstrlenW(wenv) + 1;
    uenvarea = wstr_to_mbstr(cp, wenvarea, wlen, NULL);
    FreeEnvironmentStringsW(wenvarea);
    if (!uenvarea)
        return NULL;

    for (env = uenvarea; *env; env += strlen(env) + 1)
        if (strncasecmp(env, name, len) == 0 && *(env + len) == '=')
            return env + len + 1;

    return NULL;
}

/* License: Ruby's */
char *
rb_w32_ugetenv(const char *name)
{
    return w32_getenv(name, CP_UTF8);
}

/* License: Ruby's */
char *
rb_w32_getenv(const char *name)
{
    return w32_getenv(name, CP_ACP);
}

/* License: Ruby's */
static DWORD
get_attr_vsn(const WCHAR *path, DWORD *atts, DWORD *vsn)
{
    BY_HANDLE_FILE_INFORMATION st = {0};
    DWORD e = 0;
    HANDLE h = open_special(path, 0, FILE_FLAG_OPEN_REPARSE_POINT);

    if (h == INVALID_HANDLE_VALUE) {
        ASSUME(e = GetLastError());
        return e;
    }
    if (!GetFileInformationByHandle(h, &st)) {
        ASSUME(e = GetLastError());
    }
    else {
        *atts = st.dwFileAttributes;
        *vsn = st.dwVolumeSerialNumber;
    }
    CloseHandle(h);
    return e;
}

/* License: Artistic or GPL */
static int
wrename(const WCHAR *oldpath, const WCHAR *newpath)
{
    int res = 0;
    DWORD oldatts, newatts = (DWORD)-1;
    DWORD oldvsn = 0, newvsn = 0, e;

    e = get_attr_vsn(oldpath, &oldatts, &oldvsn);
    if (e) {
        errno = map_errno(e);
        return -1;
    }
    if (oldatts & FILE_ATTRIBUTE_REPARSE_POINT) {
        HANDLE fh = open_special(oldpath, 0, 0);
        if (fh == INVALID_HANDLE_VALUE) {
            e = GetLastError();
            if (e == ERROR_CANT_RESOLVE_FILENAME) {
                errno = ELOOP;
                return -1;
            }
        }
        CloseHandle(fh);
    }
    get_attr_vsn(newpath, &newatts, &newvsn);

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

        if (!MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
            res = -1;

        if (res) {
            DWORD e = GetLastError();
            if ((e == ERROR_ACCESS_DENIED) && (oldatts & FILE_ATTRIBUTE_DIRECTORY) &&
                oldvsn != newvsn)
                errno = EXDEV;
            else
                errno = map_errno(e);
        }
        else
            SetFileAttributesW(newpath, oldatts);
    }

    return res;
}

/* License: Ruby's */
int rb_w32_urename(const char *from, const char *to)
{
    WCHAR *wfrom;
    WCHAR *wto;
    int ret = -1;

    if (!(wfrom = utf8_to_wstr(from, NULL)))
        return -1;
    if (!(wto = utf8_to_wstr(to, NULL))) {
        free(wfrom);
        return -1;
    }
    ret = wrename(wfrom, wto);
    free(wto);
    free(wfrom);
    return ret;
}

/* License: Ruby's */
int rb_w32_rename(const char *from, const char *to)
{
    WCHAR *wfrom;
    WCHAR *wto;
    int ret = -1;

    if (!(wfrom = filecp_to_wstr(from, NULL)))
        return -1;
    if (!(wto = filecp_to_wstr(to, NULL))) {
        free(wfrom);
        return -1;
    }
    ret = wrename(wfrom, wto);
    free(wto);
    free(wfrom);
    return ret;
}

/* License: Ruby's */
static int
isUNCRoot(const WCHAR *path)
{
    if (path[0] == L'\\' && path[1] == L'\\') {
        const WCHAR *p = path + 2;
        if (p[0] == L'?' && p[1] == L'\\') {
            p += 2;
        }
        for (; *p; p++) {
            if (*p == L'\\')
                break;
        }
        if (p[0] && p[1]) {
            for (p++; *p; p++) {
                if (*p == L'\\')
                    break;
            }
            if (!p[0] || !p[1] || (p[1] == L'.' && !p[2]))
                return 1;
        }
    }
    return 0;
}

#define COPY_STAT(src, dest, size_cast) do {    \
        (dest).st_dev   = (src).st_dev;         \
        (dest).st_ino   = (src).st_ino;         \
        (dest).st_mode  = (src).st_mode;        \
        (dest).st_nlink = (src).st_nlink;       \
        (dest).st_uid   = (src).st_uid;         \
        (dest).st_gid   = (src).st_gid;         \
        (dest).st_rdev  = (src).st_rdev;        \
        (dest).st_size  = size_cast(src).st_size; \
        (dest).st_atime = (src).st_atime;       \
        (dest).st_mtime = (src).st_mtime;       \
        (dest).st_ctime = (src).st_ctime;       \
    } while (0)

static time_t filetime_to_unixtime(const FILETIME *ft);
static WCHAR *name_for_stat(WCHAR *buf, const WCHAR *path);
static DWORD stati64_handle(HANDLE h, struct stati64 *st);

/* License: Ruby's */
static void
stati64_set_inode(BY_HANDLE_FILE_INFORMATION *pinfo, struct stati64 *st)
{
    /* struct stati64 layout
     *
     * dev: 0-3
     * ino: 4-5
     * mode: 6-7
     * nlink: 8-9
     * uid: 10-11
     * gid: 12-13
     * _: 14-15
     * rdev: 16-19
     * _: 20-23
     * size: 24-31
     * atime: 32-39
     * mtime: 40-47
     * ctime: 48-55
     *
     */
    unsigned short *p2 = (unsigned short *)st;
    unsigned int *p4 = (unsigned int *)st;
    DWORD high = pinfo->nFileIndexHigh;
    p2[2] = high >> 16;
    p2[7] = high & 0xFFFF;
    p4[5] = pinfo->nFileIndexLow;
}

/* License: Ruby's */
static DWORD
stati64_set_inode_handle(HANDLE h, struct stati64 *st)
{
    BY_HANDLE_FILE_INFORMATION info;
    DWORD attr = (DWORD)-1;

    if (GetFileInformationByHandle(h, &info)) {
        stati64_set_inode(&info, st);
    }
    return attr;
}

#undef fstat
/* License: Ruby's */
int
rb_w32_fstat(int fd, struct stat *st)
{
    BY_HANDLE_FILE_INFORMATION info;
    int ret = fstat(fd, st);

    if (ret) return ret;
    if (GetEnvironmentVariableW(L"TZ", NULL, 0) == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) return ret;
    if (GetFileInformationByHandle((HANDLE)_get_osfhandle(fd), &info)) {
        st->st_atime = filetime_to_unixtime(&info.ftLastAccessTime);
        st->st_mtime = filetime_to_unixtime(&info.ftLastWriteTime);
        st->st_ctime = filetime_to_unixtime(&info.ftCreationTime);
    }
    return ret;
}

/* License: Ruby's */
int
rb_w32_fstati64(int fd, struct stati64 *st)
{
    struct stat tmp;
    int ret;

    if (GetEnvironmentVariableW(L"TZ", NULL, 0) == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
        ret = _fstati64(fd, st);
        stati64_set_inode_handle((HANDLE)_get_osfhandle(fd), st);
        return ret;
    }
    ret = fstat(fd, &tmp);

    if (ret) return ret;
    COPY_STAT(tmp, *st, +);
    stati64_handle((HANDLE)_get_osfhandle(fd), st);
    return ret;
}

/* License: Ruby's */
static DWORD
stati64_handle(HANDLE h, struct stati64 *st)
{
    BY_HANDLE_FILE_INFORMATION info;
    DWORD attr = (DWORD)-1;

    if (GetFileInformationByHandle(h, &info)) {
        st->st_size = ((__int64)info.nFileSizeHigh << 32) | info.nFileSizeLow;
        st->st_atime = filetime_to_unixtime(&info.ftLastAccessTime);
        st->st_mtime = filetime_to_unixtime(&info.ftLastWriteTime);
        st->st_ctime = filetime_to_unixtime(&info.ftCreationTime);
        st->st_nlink = info.nNumberOfLinks;
        attr = info.dwFileAttributes;
        stati64_set_inode(&info, st);
    }
    return attr;
}

/* License: Ruby's */
static time_t
filetime_to_unixtime(const FILETIME *ft)
{
    struct timeval tv;

    if (filetime_to_timeval(ft, &tv) == (time_t)-1)
        return 0;
    else
        return tv.tv_sec;
}

/* License: Ruby's */
static unsigned
fileattr_to_unixmode(DWORD attr, const WCHAR *path)
{
    unsigned mode = 0;

    if (attr & FILE_ATTRIBUTE_READONLY) {
        mode |= S_IREAD;
    }
    else {
        mode |= S_IREAD | S_IWRITE | S_IWUSR;
    }

    if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
        if (rb_w32_reparse_symlink_p(path))
            mode |= S_IFLNK | S_IEXEC;
        else
            mode |= S_IFDIR | S_IEXEC;
    }
    else if (attr & FILE_ATTRIBUTE_DIRECTORY) {
        mode |= S_IFDIR | S_IEXEC;
    }
    else {
        mode |= S_IFREG;
    }

    if (path && (mode & S_IFREG)) {
        const WCHAR *end = path + lstrlenW(path);
        while (path < end) {
            end = CharPrevW(path, end);
            if (*end == L'.') {
                if ((_wcsicmp(end, L".bat") == 0) ||
                    (_wcsicmp(end, L".cmd") == 0) ||
                    (_wcsicmp(end, L".com") == 0) ||
                    (_wcsicmp(end, L".exe") == 0)) {
                    mode |= S_IEXEC;
                }
                break;
            }
            if (!iswalnum(*end)) break;
        }
    }

    mode |= (mode & 0500) >> 3;
    mode |= (mode & 0500) >> 6;

    return mode;
}

/* License: Ruby's */
static int
check_valid_dir(const WCHAR *path)
{
    WIN32_FIND_DATAW fd;
    HANDLE fh;
    WCHAR full[PATH_MAX];
    WCHAR *dmy;
    WCHAR *p, *q;

    /* GetFileAttributes() determines "..." as directory. */
    /* We recheck it by FindFirstFile(). */
    if (!(p = wcsstr(path, L"...")))
        return 0;
    q = p + wcsspn(p, L".");
    if ((p == path || wcschr(L":/\\", *(p - 1))) &&
        (!*q || wcschr(L":/\\", *q))) {
        errno = ENOENT;
        return -1;
    }

    /* if the specified path is the root of a drive and the drive is empty, */
    /* FindFirstFile() returns INVALID_HANDLE_VALUE. */
    if (!GetFullPathNameW(path, sizeof(full) / sizeof(WCHAR), full, &dmy)) {
        errno = map_errno(GetLastError());
        return -1;
    }
    if (full[1] == L':' && !full[3] && GetDriveTypeW(full) != DRIVE_NO_ROOT_DIR)
        return 0;

    fh = open_dir_handle(path, &fd);
    if (fh == INVALID_HANDLE_VALUE)
        return -1;
    FindClose(fh);
    return 0;
}

/* License: Ruby's */
static int
stat_by_find(const WCHAR *path, struct stati64 *st)
{
    HANDLE h;
    WIN32_FIND_DATAW wfd;
    /* GetFileAttributesEx failed; check why. */
    int e = GetLastError();

    if ((e == ERROR_FILE_NOT_FOUND) || (e == ERROR_INVALID_NAME)
        || (e == ERROR_PATH_NOT_FOUND || (e == ERROR_BAD_NETPATH))) {
        errno = map_errno(e);
        return -1;
    }

    /* Fall back to FindFirstFile for ERROR_SHARING_VIOLATION */
    h = FindFirstFileW(path, &wfd);
    if (h == INVALID_HANDLE_VALUE) {
        errno = map_errno(GetLastError());
        return -1;
    }
    FindClose(h);
    st->st_mode  = fileattr_to_unixmode(wfd.dwFileAttributes, path);
    st->st_atime = filetime_to_unixtime(&wfd.ftLastAccessTime);
    st->st_mtime = filetime_to_unixtime(&wfd.ftLastWriteTime);
    st->st_ctime = filetime_to_unixtime(&wfd.ftCreationTime);
    st->st_size = ((__int64)wfd.nFileSizeHigh << 32) | wfd.nFileSizeLow;
    st->st_nlink = 1;
    return 0;
}

/* License: Ruby's */
static int
path_drive(const WCHAR *path)
{
    return (iswalpha(path[0]) && path[1] == L':') ?
        towupper(path[0]) - L'A' : _getdrive() - 1;
}

static const WCHAR namespace_prefix[] = {L'\\', L'\\', L'?', L'\\'};

/* License: Ruby's */
static int
winnt_stat(const WCHAR *path, struct stati64 *st)
{
    HANDLE f;

    memset(st, 0, sizeof(*st));
    f = open_special(path, 0, 0);
    if (f != INVALID_HANDLE_VALUE) {
        WCHAR finalname[PATH_MAX];
        const DWORD attr = stati64_handle(f, st);
        const DWORD len = get_final_path(f, finalname, numberof(finalname), 0);
        CloseHandle(f);
        if (attr & FILE_ATTRIBUTE_DIRECTORY) {
            if (check_valid_dir(path)) return -1;
        }
        st->st_mode = fileattr_to_unixmode(attr, path);
        if (len) {
            finalname[len] = L'\0';
            path = finalname;
            if (wcsncmp(path, namespace_prefix, numberof(namespace_prefix)) == 0)
                path += numberof(namespace_prefix);
        }
    }
    else {
        if (stat_by_find(path, st)) return -1;
    }

    st->st_dev = st->st_rdev = path_drive(path);

    return 0;
}

/* License: Ruby's */
static int
winnt_lstat(const WCHAR *path, struct stati64 *st)
{
    WIN32_FILE_ATTRIBUTE_DATA wfa;
    const WCHAR *p = path;

    memset(st, 0, sizeof(*st));
    st->st_nlink = 1;

    if (wcsncmp(p, namespace_prefix, numberof(namespace_prefix)) == 0)
        p += numberof(namespace_prefix);
    if (wcspbrk(p, L"?*")) {
        errno = ENOENT;
        return -1;
    }
    if (GetFileAttributesExW(path, GetFileExInfoStandard, (void*)&wfa)) {
        if (wfa.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
            /* TODO: size in which encoding? */
            if (rb_w32_reparse_symlink_p(path))
                st->st_size = 0;
            else
                wfa.dwFileAttributes &= ~FILE_ATTRIBUTE_REPARSE_POINT;
        }
        if (wfa.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            if (check_valid_dir(path)) return -1;
            st->st_size = 0;
        }
        else {
            st->st_size = ((__int64)wfa.nFileSizeHigh << 32) | wfa.nFileSizeLow;
        }
        st->st_mode  = fileattr_to_unixmode(wfa.dwFileAttributes, path);
        st->st_atime = filetime_to_unixtime(&wfa.ftLastAccessTime);
        st->st_mtime = filetime_to_unixtime(&wfa.ftLastWriteTime);
        st->st_ctime = filetime_to_unixtime(&wfa.ftCreationTime);
    }
    else {
        if (stat_by_find(path, st)) return -1;
    }

    st->st_dev = st->st_rdev = path_drive(path);

    return 0;
}

/* License: Ruby's */
int
rb_w32_stat(const char *path, struct stat *st)
{
    struct stati64 tmp;

    if (rb_w32_stati64(path, &tmp)) return -1;
    COPY_STAT(tmp, *st, (_off_t));
    return 0;
}

/* License: Ruby's */
static int
wstati64(const WCHAR *path, struct stati64 *st)
{
    WCHAR *buf1;
    int ret, size;
    VALUE v;

    if (!path || !st) {
        errno = EFAULT;
        return -1;
    }
    size = lstrlenW(path) + 2;
    buf1 = ALLOCV_N(WCHAR, v, size);
    if (!(path = name_for_stat(buf1, path)))
        return -1;
    ret = winnt_stat(path, st);
    if (v)
        ALLOCV_END(v);

    return ret;
}

/* License: Ruby's */
static int
wlstati64(const WCHAR *path, struct stati64 *st)
{
    WCHAR *buf1;
    int ret, size;
    VALUE v;

    if (!path || !st) {
        errno = EFAULT;
        return -1;
    }
    size = lstrlenW(path) + 2;
    buf1 = ALLOCV_N(WCHAR, v, size);
    if (!(path = name_for_stat(buf1, path)))
        return -1;
    ret = winnt_lstat(path, st);
    if (v)
        ALLOCV_END(v);

    return ret;
}

/* License: Ruby's */
static WCHAR *
name_for_stat(WCHAR *buf1, const WCHAR *path)
{
    const WCHAR *p;
    WCHAR *s, *end;
    int len;

    for (p = path, s = buf1; *p; p++, s++) {
        if (*p == L'/')
            *s = L'\\';
        else
            *s = *p;
    }
    *s = '\0';
    len = s - buf1;
    if (!len || L'\"' == *(--s)) {
        errno = ENOENT;
        return NULL;
    }
    end = buf1 + len - 1;

    if (isUNCRoot(buf1)) {
        if (*end == L'.')
            *end = L'\0';
        else if (*end != L'\\')
            lstrcatW(buf1, L"\\");
    }
    else if (*end == L'\\' || (buf1 + 1 == end && *end == L':'))
        lstrcatW(buf1, L".");

    return buf1;
}

/* License: Ruby's */
int
rb_w32_ustati64(const char *path, struct stati64 *st)
{
    return w32_stati64(path, st, CP_UTF8);
}

/* License: Ruby's */
int
rb_w32_stati64(const char *path, struct stati64 *st)
{
    return w32_stati64(path, st, filecp());
}

/* License: Ruby's */
static int
w32_stati64(const char *path, struct stati64 *st, UINT cp)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = mbstr_to_wstr(cp, path, -1, NULL)))
        return -1;
    ret = wstati64(wpath, st);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
rb_w32_ulstati64(const char *path, struct stati64 *st)
{
    return w32_lstati64(path, st, CP_UTF8);
}

/* License: Ruby's */
int
rb_w32_lstati64(const char *path, struct stati64 *st)
{
    return w32_lstati64(path, st, filecp());
}

/* License: Ruby's */
static int
w32_lstati64(const char *path, struct stati64 *st, UINT cp)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = mbstr_to_wstr(cp, path, -1, NULL)))
        return -1;
    ret = wlstati64(wpath, st);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
rb_w32_access(const char *path, int mode)
{
    struct stati64 stat;
    if (rb_w32_stati64(path, &stat) != 0)
        return -1;
    mode <<= 6;
    if ((stat.st_mode & mode) != mode) {
        errno = EACCES;
        return -1;
    }
    return 0;
}

/* License: Ruby's */
int
rb_w32_uaccess(const char *path, int mode)
{
    struct stati64 stat;
    if (rb_w32_ustati64(path, &stat) != 0)
        return -1;
    mode <<= 6;
    if ((stat.st_mode & mode) != mode) {
        errno = EACCES;
        return -1;
    }
    return 0;
}

/* License: Ruby's */
static int
rb_chsize(HANDLE h, off_t size)
{
    long upos, lpos, usize, lsize;
    int ret = -1;
    DWORD e;

    if ((lpos = SetFilePointer(h, 0, (upos = 0, &upos), SEEK_CUR)) == -1L &&
        (e = GetLastError())) {
        errno = map_errno(e);
        return -1;
    }
    usize = (long)(size >> 32);
    lsize = (long)size;
    if (SetFilePointer(h, lsize, &usize, SEEK_SET) == (DWORD)-1L &&
        (e = GetLastError())) {
        errno = map_errno(e);
    }
    else if (!SetEndOfFile(h)) {
        errno = map_errno(GetLastError());
    }
    else {
        ret = 0;
    }
    SetFilePointer(h, lpos, &upos, SEEK_SET);
    return ret;
}

/* License: Ruby's */
static int
w32_truncate(const char *path, off_t length, UINT cp)
{
    HANDLE h;
    int ret;
    WCHAR *wpath;

    if (!(wpath = mbstr_to_wstr(cp, path, -1, NULL)))
        return -1;
    h = CreateFileW(wpath, GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
    if (h == INVALID_HANDLE_VALUE) {
        errno = map_errno(GetLastError());
        free(wpath);
        return -1;
    }
    free(wpath);
    ret = rb_chsize(h, length);
    CloseHandle(h);
    return ret;
}

/* License: Ruby's */
int
rb_w32_utruncate(const char *path, off_t length)
{
    return w32_truncate(path, length, CP_UTF8);
}

/* License: Ruby's */
int
rb_w32_truncate(const char *path, off_t length)
{
    return w32_truncate(path, length, filecp());
}

/* License: Ruby's */
int
rb_w32_ftruncate(int fd, off_t length)
{
    HANDLE h;

    h = (HANDLE)_get_osfhandle(fd);
    if (h == (HANDLE)-1) return -1;
    return rb_chsize(h, length);
}

/* License: Ruby's */
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;
}

/* License: Ruby's */
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;
}


/* License: Ruby's */
#define yield_once() Sleep(0)
#define yield_until(condition) do yield_once(); while (!(condition))

/* License: Ruby's */
struct asynchronous_arg_t {
    /* output field */
    void* stackaddr;
    int errnum;

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

/* License: Ruby's */
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;
}

/* License: Ruby's */
uintptr_t
rb_w32_asynchronize(asynchronous_func_t func, uintptr_t self,
                    int argc, uintptr_t* argv, uintptr_t 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 (rb_w32_wait_events_blocking(&thr, 1, 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:%ld", GetLastError());
    }

    return val;
}

/* License: Ruby's */
char **
rb_w32_get_environ(void)
{
    WCHAR *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.
     *
     * This function returns UTF-8 strings.
     */
    envtop = GetEnvironmentStringsW();
    for (env = envtop, num = 0; *env; env += lstrlenW(env) + 1)
        if (*env != '=') num++;

    myenvtop = (char **)malloc(sizeof(char *) * (num + 1));
    for (env = envtop, myenv = myenvtop; *env; env += lstrlenW(env) + 1) {
        if (*env != '=') {
            if (!(*myenv = wstr_to_utf8(env, NULL))) {
                break;
            }
            myenv++;
        }
    }
    *myenv = NULL;
    FreeEnvironmentStringsW(envtop);

    return myenvtop;
}

/* License: Ruby's */
void
rb_w32_free_environ(char **env)
{
    char **t = env;

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

/* License: Ruby's */
rb_pid_t
rb_w32_getpid(void)
{
    return GetCurrentProcessId();
}


/* License: Ruby's */
rb_pid_t
rb_w32_getppid(void)
{
    typedef long (WINAPI query_func)(HANDLE, int, void *, ULONG, ULONG *);
    static query_func *pNtQueryInformationProcess = NULL;
    rb_pid_t ppid = 0;

    if (!pNtQueryInformationProcess)
        pNtQueryInformationProcess = (query_func *)get_proc_address("ntdll.dll", "NtQueryInformationProcess", NULL);
    if (pNtQueryInformationProcess) {
        struct {
            long ExitStatus;
            void* PebBaseAddress;
            uintptr_t AffinityMask;
            uintptr_t BasePriority;
            uintptr_t UniqueProcessId;
            uintptr_t ParentProcessId;
        } pbi;
        ULONG len;
        long ret = pNtQueryInformationProcess(GetCurrentProcess(), 0, &pbi, sizeof(pbi), &len);
        if (!ret) {
            ppid = pbi.ParentProcessId;
        }
    }

    return ppid;
}

STATIC_ASSERT(std_handle, (STD_OUTPUT_HANDLE-STD_INPUT_HANDLE)==(STD_ERROR_HANDLE-STD_OUTPUT_HANDLE));

/* License: Ruby's */
#define set_new_std_handle(newfd, handle) do { \
        if ((unsigned)(newfd) > 2) break; \
        SetStdHandle(STD_INPUT_HANDLE+(STD_OUTPUT_HANDLE-STD_INPUT_HANDLE)*(newfd), \
                     (handle)); \
    } while (0)
#define set_new_std_fd(newfd) set_new_std_handle(newfd, (HANDLE)rb_w32_get_osfhandle(newfd))

/* License: Ruby's */
int
rb_w32_dup2(int oldfd, int newfd)
{
    int ret;

    if (oldfd == newfd) return newfd;
    ret = dup2(oldfd, newfd);
    if (ret < 0) return ret;
    set_new_std_fd(newfd);
    return newfd;
}

/* License: Ruby's */
int
rb_w32_uopen(const char *file, int oflag, ...)
{
    WCHAR *wfile;
    int ret;
    int pmode;

    va_list arg;
    va_start(arg, oflag);
    pmode = va_arg(arg, int);
    va_end(arg);

    if (!(wfile = utf8_to_wstr(file, NULL)))
        return -1;
    ret = w32_wopen(wfile, oflag, pmode);
    free(wfile);
    return ret;
}

/* License: Ruby's */
static int
check_if_wdir(const WCHAR *wfile)
{
    DWORD attr = GetFileAttributesW(wfile);
    if (attr == (DWORD)-1L ||
        !(attr & FILE_ATTRIBUTE_DIRECTORY) ||
        check_valid_dir(wfile)) {
        return FALSE;
    }
    errno = EISDIR;
    return TRUE;
}

/* License: Ruby's */
int
rb_w32_open(const char *file, int oflag, ...)
{
    WCHAR *wfile;
    int ret;
    int pmode;

    va_list arg;
    va_start(arg, oflag);
    pmode = va_arg(arg, int);
    va_end(arg);

    if (!(wfile = filecp_to_wstr(file, NULL)))
        return -1;
    ret = w32_wopen(wfile, oflag, pmode);
    free(wfile);
    return ret;
}

/* License: Ruby's */
int
rb_w32_wopen(const WCHAR *file, int oflag, ...)
{
    int pmode = 0;

    if (oflag & O_CREAT) {
        va_list arg;
        va_start(arg, oflag);
        pmode = va_arg(arg, int);
        va_end(arg);
    }

    return w32_wopen(file, oflag, pmode);
}

static int
w32_wopen(const WCHAR *file, int oflag, int pmode)
{
    char flags = 0;
    int fd;
    DWORD access;
    DWORD create;
    DWORD attr = FILE_ATTRIBUTE_NORMAL;
    SECURITY_ATTRIBUTES sec;
    HANDLE h;
    int share_delete;

    share_delete = oflag & O_SHARE_DELETE ? FILE_SHARE_DELETE : 0;
    oflag &= ~O_SHARE_DELETE;
    if ((oflag & O_TEXT) || !(oflag & O_BINARY)) {
        fd = _wopen(file, oflag, pmode);
        if (fd == -1) {
            switch (errno) {
              case EACCES:
                check_if_wdir(file);
                break;
              case EINVAL:
                errno = map_errno(GetLastError());
                break;
            }
        }
        return fd;
    }

    sec.nLength = sizeof(sec);
    sec.lpSecurityDescriptor = NULL;
    if (oflag & O_NOINHERIT) {
        sec.bInheritHandle = FALSE;
        flags |= FNOINHERIT;
    }
    else {
        sec.bInheritHandle = TRUE;
    }
    oflag &= ~O_NOINHERIT;

    /* always open with binary mode */
    oflag &= ~(O_BINARY | O_TEXT);

    switch (oflag & (O_RDWR | O_RDONLY | O_WRONLY)) {
      case O_RDWR:
        access = GENERIC_READ | GENERIC_WRITE;
        break;
      case O_RDONLY:
        access = GENERIC_READ;
        break;
      case O_WRONLY:
        access = GENERIC_WRITE;
        break;
      default:
        errno = EINVAL;
        return -1;
    }
    oflag &= ~(O_RDWR | O_RDONLY | O_WRONLY);

    switch (oflag & (O_CREAT | O_EXCL | O_TRUNC)) {
      case O_CREAT:
        create = OPEN_ALWAYS;
        break;
      case 0:
      case O_EXCL:
        create = OPEN_EXISTING;
        break;
      case O_CREAT | O_EXCL:
      case O_CREAT | O_EXCL | O_TRUNC:
        create = CREATE_NEW;
        break;
      case O_TRUNC:
      case O_TRUNC | O_EXCL:
        create = TRUNCATE_EXISTING;
        break;
      case O_CREAT | O_TRUNC:
        create = CREATE_ALWAYS;
        break;
      default:
        errno = EINVAL;
        return -1;
    }
    if (oflag & O_CREAT) {
        /* TODO: we need to check umask here, but it's not exported... */
        if (!(pmode & S_IWRITE))
            attr = FILE_ATTRIBUTE_READONLY;
    }
    oflag &= ~(O_CREAT | O_EXCL | O_TRUNC);

    if (oflag & O_TEMPORARY) {
        attr |= FILE_FLAG_DELETE_ON_CLOSE;
        access |= DELETE;
    }
    oflag &= ~O_TEMPORARY;

    if (oflag & _O_SHORT_LIVED)
        attr |= FILE_ATTRIBUTE_TEMPORARY;
    oflag &= ~_O_SHORT_LIVED;

    switch (oflag & (O_SEQUENTIAL | O_RANDOM)) {
      case 0:
        break;
      case O_SEQUENTIAL:
        attr |= FILE_FLAG_SEQUENTIAL_SCAN;
        break;
      case O_RANDOM:
        attr |= FILE_FLAG_RANDOM_ACCESS;
        break;
      default:
        errno = EINVAL;
        return -1;
    }
    oflag &= ~(O_SEQUENTIAL | O_RANDOM);

    if (oflag & ~O_APPEND) {
        errno = EINVAL;
        return -1;
    }

    /* allocate a C Runtime file handle */
    RUBY_CRITICAL {
        h = CreateFile("NUL", 0, 0, NULL, OPEN_ALWAYS, 0, NULL);
        fd = _open_osfhandle((intptr_t)h, 0);
        CloseHandle(h);
    }
    if (fd == -1) {
        errno = EMFILE;
        return -1;
    }
    RUBY_CRITICAL {
        rb_acrt_lowio_lock_fh(fd);
        _set_osfhnd(fd, (intptr_t)INVALID_HANDLE_VALUE);
        _set_osflags(fd, 0);

        h = CreateFileW(file, access, FILE_SHARE_READ | FILE_SHARE_WRITE | share_delete, &sec, create, attr, NULL);
        if (h == INVALID_HANDLE_VALUE) {
            DWORD e = GetLastError();
            if (e != ERROR_ACCESS_DENIED || !check_if_wdir(file))
                errno = map_errno(e);
            rb_acrt_lowio_unlock_fh(fd);
            fd = -1;
            goto quit;
        }

        switch (GetFileType(h)) {
          case FILE_TYPE_CHAR:
            flags |= FDEV;
            break;
          case FILE_TYPE_PIPE:
            flags |= FPIPE;
            break;
          case FILE_TYPE_UNKNOWN:
            errno = map_errno(GetLastError());
            CloseHandle(h);
            rb_acrt_lowio_unlock_fh(fd);
            fd = -1;
            goto quit;
        }
        if (!(flags & (FDEV | FPIPE)) && (oflag & O_APPEND))
            flags |= FAPPEND;

        _set_osfhnd(fd, (intptr_t)h);
        _set_osflags(fd, flags | FOPEN);

        rb_acrt_lowio_unlock_fh(fd);
      quit:
        ;
    }

    return fd;
}

/* License: Ruby's */
int
rb_w32_fclose(FILE *fp)
{
    int fd = fileno(fp);
    SOCKET sock = TO_SOCKET(fd);
    int save_errno = errno;

    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);
    errno = save_errno;
    if (closesocket(sock) == SOCKET_ERROR) {
        errno = map_errno(WSAGetLastError());
        return -1;
    }
    return 0;
}

/* License: Ruby's */
int
rb_w32_pipe(int fds[2])
{
    static DWORD serial = 0;
    static const char prefix[] = "\\\\.\\pipe\\ruby";
    enum {
        width_of_prefix = (int)sizeof(prefix) - 1,
        width_of_pid = (int)sizeof(rb_pid_t) * 2,
        width_of_serial = (int)sizeof(serial) * 2,
        width_of_ids = width_of_pid + 1 + width_of_serial + 1
    };
    char name[sizeof(prefix) + width_of_ids];
    SECURITY_ATTRIBUTES sec;
    HANDLE hRead, hWrite, h;
    int fdRead, fdWrite;
    int ret;

    memcpy(name, prefix, width_of_prefix);
    snprintf(name + width_of_prefix, width_of_ids, "%.*"PRI_PIDT_PREFIX"x-%.*lx",
             width_of_pid, rb_w32_getpid(), width_of_serial, serial++);

    sec.nLength = sizeof(sec);
    sec.lpSecurityDescriptor = NULL;
    sec.bInheritHandle = FALSE;

    RUBY_CRITICAL {
        hRead = CreateNamedPipe(name, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
                                0, 2, 65536, 65536, 0, &sec);
    }
    if (hRead == INVALID_HANDLE_VALUE) {
        DWORD err = GetLastError();
        if (err == ERROR_PIPE_BUSY)
            errno = EMFILE;
        else
            errno = map_errno(GetLastError());
        return -1;
    }

    RUBY_CRITICAL {
        hWrite = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, &sec,
                            OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    }
    if (hWrite == INVALID_HANDLE_VALUE) {
        errno = map_errno(GetLastError());
        CloseHandle(hRead);
        return -1;
    }

    RUBY_CRITICAL do {
        ret = 0;
        h = CreateFile("NUL", 0, 0, NULL, OPEN_ALWAYS, 0, NULL);
        fdRead = _open_osfhandle((intptr_t)h, 0);
        CloseHandle(h);
        if (fdRead == -1) {
            errno = EMFILE;
            CloseHandle(hWrite);
            CloseHandle(hRead);
            ret = -1;
            break;
        }

        rb_acrt_lowio_lock_fh(fdRead);
        _set_osfhnd(fdRead, (intptr_t)hRead);
        _set_osflags(fdRead, FOPEN | FPIPE | FNOINHERIT);
        rb_acrt_lowio_unlock_fh(fdRead);
    } while (0);
    if (ret)
        return ret;

    RUBY_CRITICAL do {
        h = CreateFile("NUL", 0, 0, NULL, OPEN_ALWAYS, 0, NULL);
        fdWrite = _open_osfhandle((intptr_t)h, 0);
        CloseHandle(h);
        if (fdWrite == -1) {
            errno = EMFILE;
            CloseHandle(hWrite);
            ret = -1;
            break;
        }
        rb_acrt_lowio_lock_fh(fdWrite);
        _set_osfhnd(fdWrite, (intptr_t)hWrite);
        _set_osflags(fdWrite, FOPEN | FPIPE | FNOINHERIT);
        rb_acrt_lowio_unlock_fh(fdWrite);
    } while (0);
    if (ret) {
        rb_w32_close(fdRead);
        return ret;
    }

    fds[0] = fdRead;
    fds[1] = fdWrite;

    return 0;
}

/* License: Ruby's */
static int
console_emulator_p(void)
{
#ifdef _WIN32_WCE
    return FALSE;
#else
    const void *const func = WriteConsoleW;
    HMODULE k;
    MEMORY_BASIC_INFORMATION m;

    memset(&m, 0, sizeof(m));
    if (!VirtualQuery(func, &m, sizeof(m))) {
        return FALSE;
    }
    k = GetModuleHandle("kernel32.dll");
    if (!k) return FALSE;
    return (HMODULE)m.AllocationBase != k;
#endif
}

/* License: Ruby's */
static struct constat *
constat_handle(HANDLE h)
{
    st_data_t data;
    struct constat *p;
    if (!conlist) {
        if (console_emulator_p()) {
            conlist = conlist_disabled;
            return NULL;
        }
        conlist = st_init_numtable();
    }
    else if (conlist == conlist_disabled) {
        return NULL;
    }
    if (st_lookup(conlist, (st_data_t)h, &data)) {
        p = (struct constat *)data;
    }
    else {
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        p = ALLOC(struct constat);
        p->vt100.state = constat_init;
        p->vt100.attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
        p->vt100.reverse = 0;
        p->vt100.saved.X = p->vt100.saved.Y = 0;
        if (GetConsoleScreenBufferInfo(h, &csbi)) {
            p->vt100.attr = csbi.wAttributes;
        }
        st_insert(conlist, (st_data_t)h, (st_data_t)p);
    }
    return p;
}

/* License: Ruby's */
static void
constat_reset(HANDLE h)
{
    st_data_t data;
    struct constat *p;
    if (!conlist || conlist == conlist_disabled) return;
    if (!st_lookup(conlist, (st_data_t)h, &data)) return;
    p = (struct constat *)data;
    p->vt100.state = constat_init;
}

#define FOREGROUND_MASK (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY)
#define BACKGROUND_MASK (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY)

#define constat_attr_color_reverse(attr) \
    ((attr) & ~(FOREGROUND_MASK | BACKGROUND_MASK)) | \
           (((attr) & FOREGROUND_MASK) << 4) | \
           (((attr) & BACKGROUND_MASK) >> 4)

/* License: Ruby's */
static WORD
constat_attr(int count, const int *seq, WORD attr, WORD default_attr, int *reverse)
{
    int rev = *reverse;
    WORD bold;

    if (!count) return attr;
    if (rev) attr = constat_attr_color_reverse(attr);
    bold = attr & FOREGROUND_INTENSITY;
    attr &= ~(FOREGROUND_INTENSITY | BACKGROUND_INTENSITY);

    while (count-- > 0) {
        switch (*seq++) {
          case 0:
            attr = default_attr;
            rev = 0;
            bold = 0;
            break;
          case 1:
            bold = FOREGROUND_INTENSITY;
            break;
          case 4:
#ifndef COMMON_LVB_UNDERSCORE
#define COMMON_LVB_UNDERSCORE 0x8000
#endif
            attr |= COMMON_LVB_UNDERSCORE;
            break;
          case 7:
            rev = 1;
            break;

          case 30:
            attr &= ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED);
            break;
          case 17:
          case 31:
            attr = (attr & ~(FOREGROUND_BLUE | FOREGROUND_GREEN)) | FOREGROUND_RED;
            break;
          case 18:
          case 32:
            attr = (attr & ~(FOREGROUND_BLUE | FOREGROUND_RED)) | FOREGROUND_GREEN;
            break;
          case 19:
          case 33:
            attr = (attr & ~FOREGROUND_BLUE) | FOREGROUND_GREEN | FOREGROUND_RED;
            break;
          case 20:
          case 34:
            attr = (attr & ~(FOREGROUND_GREEN | FOREGROUND_RED)) | FOREGROUND_BLUE;
            break;
          case 21:
          case 35:
            attr = (attr & ~FOREGROUND_GREEN) | FOREGROUND_BLUE | FOREGROUND_RED;
            break;
          case 22:
          case 36:
            attr = (attr & ~FOREGROUND_RED) | FOREGROUND_BLUE | FOREGROUND_GREEN;
            break;
          case 23:
          case 37:
            attr |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
            break;

          case 40:
            attr &= ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED);
            break;
          case 41:
            attr = (attr & ~(BACKGROUND_BLUE | BACKGROUND_GREEN)) | BACKGROUND_RED;
            break;
          case 42:
            attr = (attr & ~(BACKGROUND_BLUE | BACKGROUND_RED)) | BACKGROUND_GREEN;
            break;
          case 43:
            attr = (attr & ~BACKGROUND_BLUE) | BACKGROUND_GREEN | BACKGROUND_RED;
            break;
          case 44:
            attr = (attr & ~(BACKGROUND_GREEN | BACKGROUND_RED)) | BACKGROUND_BLUE;
            break;
          case 45:
            attr = (attr & ~BACKGROUND_GREEN) | BACKGROUND_BLUE | BACKGROUND_RED;
            break;
          case 46:
            attr = (attr & ~BACKGROUND_RED) | BACKGROUND_BLUE | BACKGROUND_GREEN;
            break;
          case 47:
            attr |= BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED;
            break;
        }
    }
    attr |= bold;
    if (rev) attr = constat_attr_color_reverse(attr);
    *reverse = rev;
    return attr;
}

/* License: Ruby's */
static void
constat_apply(HANDLE handle, struct constat *s, WCHAR w)
{
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    const int *seq = s->vt100.seq;
    int count = s->vt100.state;
    int arg1 = 1;
    COORD pos;
    DWORD written;

    if (!GetConsoleScreenBufferInfo(handle, &csbi)) return;
    if (count > 0 && seq[0] > 0) arg1 = seq[0];
    switch (w) {
      case L'm':
        SetConsoleTextAttribute(handle, constat_attr(count, seq, csbi.wAttributes, s->vt100.attr, &s->vt100.reverse));
        break;
      case L'F':
        csbi.dwCursorPosition.X = 0;
      case L'A':
        csbi.dwCursorPosition.Y -= arg1;
        if (csbi.dwCursorPosition.Y < 0)
            csbi.dwCursorPosition.Y = 0;
        SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
        break;
      case L'E':
        csbi.dwCursorPosition.X = 0;
      case L'B':
      case L'e':
        csbi.dwCursorPosition.Y += arg1;
        if (csbi.dwCursorPosition.Y >= csbi.dwSize.Y)
            csbi.dwCursorPosition.Y = csbi.dwSize.Y;
        SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
        break;
      case L'C':
        csbi.dwCursorPosition.X += arg1;
        if (csbi.dwCursorPosition.X >= csbi.dwSize.X)
            csbi.dwCursorPosition.X = csbi.dwSize.X;
        SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
        break;
      case L'D':
        csbi.dwCursorPosition.X -= arg1;
        if (csbi.dwCursorPosition.X < 0)
            csbi.dwCursorPosition.X = 0;
        SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
        break;
      case L'G':
      case L'`':
        csbi.dwCursorPosition.X = (arg1 > csbi.dwSize.X ? csbi.dwSize.X : arg1) - 1;
        SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
        break;
      case L'd':
        csbi.dwCursorPosition.Y = (arg1 > csbi.dwSize.Y ? csbi.dwSize.Y : arg1) - 1;
        SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
        break;
      case L'H':
      case L'f':
        pos.Y = (arg1 > csbi.dwSize.Y ? csbi.dwSize.Y : arg1) - 1;
        if (count < 2 || (arg1 = seq[1]) <= 0) arg1 = 1;
        pos.X = (arg1 > csbi.dwSize.X ? csbi.dwSize.X : arg1) - 1;
        SetConsoleCursorPosition(handle, pos);
        break;
      case L'J':
        switch (arg1) {
          case 0:       /* erase after cursor */
            FillConsoleOutputCharacterW(handle, L' ',
                                        csbi.dwSize.X * (csbi.dwSize.Y - csbi.dwCursorPosition.Y) - csbi.dwCursorPosition.X,
                                        csbi.dwCursorPosition, &written);
            break;
          case 1:       /* erase before cursor */
            pos.X = 0;
            pos.Y = csbi.dwCursorPosition.Y;
            FillConsoleOutputCharacterW(handle, L' ',
                                        csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X,
                                        pos, &written);
            break;
          case 2:       /* erase entire screen */
            pos.X = 0;
            pos.Y = 0;
            FillConsoleOutputCharacterW(handle, L' ', csbi.dwSize.X * csbi.dwSize.Y, pos, &written);
            break;
        }
        break;
      case L'K':
        switch (arg1) {
          case 0:       /* erase after cursor */
            FillConsoleOutputCharacterW(handle, L' ', csbi.dwSize.X - csbi.dwCursorPosition.X, csbi.dwCursorPosition, &written);
            break;
          case 1:       /* erase before cursor */
            pos.X = 0;
            pos.Y = csbi.dwCursorPosition.Y;
            FillConsoleOutputCharacterW(handle, L' ', csbi.dwCursorPosition.X, pos, &written);
            break;
          case 2:       /* erase entire line */
            pos.X = 0;
            pos.Y = csbi.dwCursorPosition.Y;
            FillConsoleOutputCharacterW(handle, L' ', csbi.dwSize.X, pos, &written);
            break;
        }
        break;
      case L's':
        s->vt100.saved = csbi.dwCursorPosition;
        break;
      case L'u':
        SetConsoleCursorPosition(handle, s->vt100.saved);
        break;
      case L'h':
        if (count >= 2 && seq[0] == -1 && seq[1] == 25) {
            CONSOLE_CURSOR_INFO cci;
            GetConsoleCursorInfo(handle, &cci);
            cci.bVisible = TRUE;
            SetConsoleCursorInfo(handle, &cci);
        }
        break;
      case L'l':
        if (count >= 2 && seq[0] == -1 && seq[1] == 25) {
            CONSOLE_CURSOR_INFO cci;
            GetConsoleCursorInfo(handle, &cci);
            cci.bVisible = FALSE;
            SetConsoleCursorInfo(handle, &cci);
        }
        break;
    }
}

/* License: Ruby's */
static long
constat_parse(HANDLE h, struct constat *s, const WCHAR **ptrp, long *lenp)
{
    const WCHAR *ptr = *ptrp;
    long rest, len = *lenp;
    while (len-- > 0) {
        WCHAR wc = *ptr++;
        if (wc == 0x1b) {
            rest = *lenp - len - 1;
            if (s->vt100.state == constat_esc) {
                rest++;         /* reuse this ESC */
            }
            s->vt100.state = constat_init;
            if (len > 0 && *ptr != L'[') continue;
            s->vt100.state = constat_esc;
        }
        else if (s->vt100.state == constat_esc) {
            if (wc != L'[') {
                /* TODO: supply dropped ESC at beginning */
                s->vt100.state = constat_init;
                continue;
            }
            rest = *lenp - len - 1;
            if (rest > 0) --rest;
            s->vt100.state = constat_seq;
            s->vt100.seq[0] = 0;
        }
        else if (s->vt100.state >= constat_seq) {
            if (wc >= L'0' && wc <= L'9') {
                if (s->vt100.state < (int)numberof(s->vt100.seq)) {
                    int *seq = &s->vt100.seq[s->vt100.state];
                    *seq = (*seq * 10) + (wc - L'0');
                }
            }
            else if (s->vt100.state == constat_seq && s->vt100.seq[0] == 0 && wc == L'?') {
                s->vt100.seq[s->vt100.state++] = -1;
            }
            else {
                do {
                    if (++s->vt100.state < (int)numberof(s->vt100.seq)) {
                        s->vt100.seq[s->vt100.state] = 0;
                    }
                    else {
                        s->vt100.state = (int)numberof(s->vt100.seq);
                    }
                } while (0);
                if (wc != L';') {
                    constat_apply(h, s, wc);
                    s->vt100.state = constat_init;
                }
            }
            rest = 0;
        }
        else {
            continue;
        }
        *ptrp = ptr;
        *lenp = len;
        return rest;
    }
    len = *lenp;
    *ptrp = ptr;
    *lenp = 0;
    return len;
}


/* License: Ruby's */
int
rb_w32_close(int fd)
{
    SOCKET sock = TO_SOCKET(fd);
    int save_errno = errno;

    if (!is_socket(sock)) {
        UnlockFile((HANDLE)sock, 0, 0, LK_LEN, LK_LEN);
        constat_delete((HANDLE)sock);
        return _close(fd);
    }
    _set_osfhnd(fd, (SOCKET)INVALID_HANDLE_VALUE);
    socklist_delete(&sock, NULL);
    _close(fd);
    errno = save_errno;
    if (closesocket(sock) == SOCKET_ERROR) {
        errno = map_errno(WSAGetLastError());
        return -1;
    }
    return 0;
}

static int
setup_overlapped(OVERLAPPED *ol, int fd, int iswrite)
{
    memset(ol, 0, sizeof(*ol));
    if (!(_osfile(fd) & (FDEV | FPIPE))) {
        LONG high = 0;
        /* On mode:a, it can write only FILE_END.
         * On mode:a+, though it can write only FILE_END,
         * it can read from everywhere.
         */
        DWORD method = ((_osfile(fd) & FAPPEND) && iswrite) ? FILE_END : FILE_CURRENT;
        DWORD low = SetFilePointer((HANDLE)_osfhnd(fd), 0, &high, method);
#ifndef INVALID_SET_FILE_POINTER
#define INVALID_SET_FILE_POINTER ((DWORD)-1)
#endif
        if (low == INVALID_SET_FILE_POINTER) {
            DWORD err = GetLastError();
            if (err != NO_ERROR) {
                errno = map_errno(err);
                return -1;
            }
        }
        ol->Offset = low;
        ol->OffsetHigh = high;
    }
    ol->hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
    if (!ol->hEvent) {
        errno = map_errno(GetLastError());
        return -1;
    }
    return 0;
}

static void
finish_overlapped(OVERLAPPED *ol, int fd, DWORD size)
{
    CloseHandle(ol->hEvent);

    if (!(_osfile(fd) & (FDEV | FPIPE))) {
        LONG high = ol->OffsetHigh;
        DWORD low = ol->Offset + size;
        if (low < ol->Offset)
            ++high;
        SetFilePointer((HANDLE)_osfhnd(fd), low, &high, FILE_BEGIN);
    }
}

#undef read
/* License: Ruby's */
ssize_t
rb_w32_read(int fd, void *buf, size_t size)
{
    SOCKET sock = TO_SOCKET(fd);
    DWORD read;
    DWORD wait;
    DWORD err;
    size_t len;
    size_t ret;
    OVERLAPPED ol;
    BOOL isconsole;
    BOOL islineinput = FALSE;
    int start = 0;

    if (is_socket(sock))
        return rb_w32_recv(fd, buf, size, 0);

    // validate fd by using _get_osfhandle() because we cannot access _nhandle
    if (_get_osfhandle(fd) == -1) {
        return -1;
    }

    if (_osfile(fd) & FTEXT) {
        return _read(fd, buf, size);
    }

    rb_acrt_lowio_lock_fh(fd);

    if (!size || _osfile(fd) & FEOFLAG) {
        _set_osflags(fd, _osfile(fd) & ~FEOFLAG);
        rb_acrt_lowio_unlock_fh(fd);
        return 0;
    }

    ret = 0;
    isconsole = is_console(_osfhnd(fd)) && (osver.dwMajorVersion < 6 || (osver.dwMajorVersion == 6 && osver.dwMinorVersion < 2));
    if (isconsole) {
        DWORD mode;
        GetConsoleMode((HANDLE)_osfhnd(fd),&mode);
        islineinput = (mode & ENABLE_LINE_INPUT) != 0;
    }
  retry:
    /* get rid of console reading bug */
    if (isconsole) {
        constat_reset((HANDLE)_osfhnd(fd));
        if (start)
            len = 1;
        else {
            len = 0;
            start = 1;
        }
    }
    else
        len = size;
    size -= len;

    if (setup_overlapped(&ol, fd, FALSE)) {
        rb_acrt_lowio_unlock_fh(fd);
        return -1;
    }

    if (!ReadFile((HANDLE)_osfhnd(fd), buf, len, &read, &ol)) {
        err = GetLastError();
        if (err == ERROR_NO_DATA && (_osfile(fd) & FPIPE)) {
            DWORD state;
            if (GetNamedPipeHandleState((HANDLE)_osfhnd(fd), &state, NULL, NULL, NULL, NULL, 0) && (state & PIPE_NOWAIT)) {
                errno = EWOULDBLOCK;
            }
            else {
                errno = map_errno(err);
            }
            rb_acrt_lowio_unlock_fh(fd);
            return -1;
        }
        else if (err != ERROR_IO_PENDING) {
            CloseHandle(ol.hEvent);
            if (err == ERROR_ACCESS_DENIED)
                errno = EBADF;
            else if (err == ERROR_BROKEN_PIPE || err == ERROR_HANDLE_EOF) {
                rb_acrt_lowio_unlock_fh(fd);
                return 0;
            }
            else
                errno = map_errno(err);

            rb_acrt_lowio_unlock_fh(fd);
            return -1;
        }

        wait = rb_w32_wait_events_blocking(&ol.hEvent, 1, INFINITE);
        if (wait != WAIT_OBJECT_0) {
            if (wait == WAIT_OBJECT_0 + 1)
                errno = EINTR;
            else
                errno = map_errno(GetLastError());
            CloseHandle(ol.hEvent);
            CancelIo((HANDLE)_osfhnd(fd));
            rb_acrt_lowio_unlock_fh(fd);
            return -1;
        }

        if (!GetOverlappedResult((HANDLE)_osfhnd(fd), &ol, &read, TRUE) &&
            (err = GetLastError()) != ERROR_HANDLE_EOF) {
            int ret = 0;
            if (err != ERROR_BROKEN_PIPE) {
                errno = map_errno(err);
                ret = -1;
            }
            CloseHandle(ol.hEvent);
            CancelIo((HANDLE)_osfhnd(fd));
            rb_acrt_lowio_unlock_fh(fd);
            return ret;
        }
    }
    else {
        err = GetLastError();
        errno = map_errno(err);
    }

    finish_overlapped(&ol, fd, read);

    ret += read;
    if (read >= len) {
        buf = (char *)buf + read;
        if (err != ERROR_OPERATION_ABORTED &&
            !(isconsole && len == 1 && (!islineinput || *((char *)buf - 1) == '\n')) && size > 0)
            goto retry;
    }
    if (read == 0)
        _set_osflags(fd, _osfile(fd) | FEOFLAG);


    rb_acrt_lowio_unlock_fh(fd);

    return ret;
}

#undef write
/* License: Ruby's */
ssize_t
rb_w32_write(int fd, const void *buf, size_t size)
{
    SOCKET sock = TO_SOCKET(fd);
    DWORD written;
    DWORD wait;
    DWORD err;
    size_t len;
    size_t ret;
    OVERLAPPED ol;

    if (is_socket(sock))
        return rb_w32_send(fd, buf, size, 0);

    // validate fd by using _get_osfhandle() because we cannot access _nhandle
    if (_get_osfhandle(fd) == -1) {
        return -1;
    }

    if ((_osfile(fd) & FTEXT) &&
        (!(_osfile(fd) & FPIPE) || fd == fileno(stdout) || fd == fileno(stderr))) {
        return _write(fd, buf, size);
    }

    rb_acrt_lowio_lock_fh(fd);

    if (!size || _osfile(fd) & FEOFLAG) {
        rb_acrt_lowio_unlock_fh(fd);
        return 0;
    }

    ret = 0;
  retry:
    /* get rid of console writing bug */
    len = (_osfile(fd) & FDEV) ? min(32 * 1024, size) : size;
    size -= len;
  retry2:

    if (setup_overlapped(&ol, fd, TRUE)) {
        rb_acrt_lowio_unlock_fh(fd);
        return -1;
    }

    if (!WriteFile((HANDLE)_osfhnd(fd), buf, len, &written, &ol)) {
        err = GetLastError();
        if (err != ERROR_IO_PENDING) {
            CloseHandle(ol.hEvent);
            if (err == ERROR_ACCESS_DENIED)
                errno = EBADF;
            else
                errno = map_errno(err);

            rb_acrt_lowio_unlock_fh(fd);
            return -1;
        }

        wait = rb_w32_wait_events_blocking(&ol.hEvent, 1, INFINITE);
        if (wait != WAIT_OBJECT_0) {
            if (wait == WAIT_OBJECT_0 + 1)
                errno = EINTR;
            else
                errno = map_errno(GetLastError());
            CloseHandle(ol.hEvent);
            CancelIo((HANDLE)_osfhnd(fd));
            rb_acrt_lowio_unlock_fh(fd);
            return -1;
        }

        if (!GetOverlappedResult((HANDLE)_osfhnd(fd), &ol, &written, TRUE)) {
            errno = map_errno(GetLastError());
            CloseHandle(ol.hEvent);
            CancelIo((HANDLE)_osfhnd(fd));
            rb_acrt_lowio_unlock_fh(fd);
            return -1;
        }
    }

    finish_overlapped(&ol, fd, written);

    ret += written;
    if (written == len) {
        buf = (const char *)buf + len;
        if (size > 0)
            goto retry;
    }
    if (ret == 0) {
        size_t newlen = len / 2;
        if (newlen > 0) {
            size += len - newlen;
            len = newlen;
            goto retry2;
        }
        ret = -1;
        errno = EWOULDBLOCK;
    }

    rb_acrt_lowio_unlock_fh(fd);

    return ret;
}

/* License: Ruby's */
long
rb_w32_write_console(uintptr_t strarg, int fd)
{
    HANDLE handle;
    DWORD dwMode, reslen;
    VALUE str = strarg;
    int encindex;
    WCHAR *wbuffer = 0;
    const WCHAR *ptr, *next;
    struct constat *s;
    long len;

    handle = (HANDLE)_osfhnd(fd);
    if (!GetConsoleMode(handle, &dwMode))
        return -1L;

    s = constat_handle(handle);
    if (!s) return -1L;
    encindex = ENCODING_GET(str);
    switch (encindex) {
      default:
        if (!rb_econv_has_convpath_p(rb_enc_name(rb_enc_from_index(encindex)), "UTF-8"))
            return -1L;
        str = rb_str_conv_enc_opts(str, NULL, rb_enc_from_index(ENCINDEX_UTF_8),
                                   ECONV_INVALID_REPLACE|ECONV_UNDEF_REPLACE, Qnil);
        /* fall through */
      case ENCINDEX_US_ASCII:
      case ENCINDEX_ASCII:
        /* assume UTF-8 */
      case ENCINDEX_UTF_8:
        ptr = wbuffer = mbstr_to_wstr(CP_UTF8, RSTRING_PTR(str), RSTRING_LEN(str), &len);
        if (!ptr) return -1L;
        break;
      case ENCINDEX_UTF_16LE:
        ptr = (const WCHAR *)RSTRING_PTR(str);
        len = RSTRING_LEN(str) / sizeof(WCHAR);
        break;
    }
    reslen = 0;
    if (dwMode & 4) {   /* ENABLE_VIRTUAL_TERMINAL_PROCESSING */
        DWORD written;
        if (!WriteConsoleW(handle, ptr, len, &written, NULL))
            reslen = (DWORD)-1L;
    }
    else {
        while (len > 0) {
            long curlen = constat_parse(handle, s, (next = ptr, &next), &len);
            reslen += next - ptr;
            if (curlen > 0) {
                DWORD written;
                if (!WriteConsoleW(handle, ptr, curlen, &written, NULL)) {
                    reslen = (DWORD)-1L;
                    break;
                }
            }
            ptr = next;
        }
    }
    RB_GC_GUARD(str);
    if (wbuffer) free(wbuffer);
    return (long)reslen;
}

/* License: Ruby's */
static int
unixtime_to_filetime(time_t time, FILETIME *ft)
{
    ULARGE_INTEGER tmp;

    tmp.QuadPart = ((LONG_LONG)time + (LONG_LONG)((1970-1601)*365.2425) * 24 * 60 * 60) * 10 * 1000 * 1000;
    ft->dwLowDateTime = tmp.LowPart;
    ft->dwHighDateTime = tmp.HighPart;
    return 0;
}

/* License: Ruby's */
static int
wutime(const WCHAR *path, const struct utimbuf *times)
{
    HANDLE hFile;
    FILETIME atime, mtime;
    struct stati64 stat;
    int ret = 0;

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

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

    RUBY_CRITICAL {
        const DWORD attr = GetFileAttributesW(path);
        if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_READONLY))
            SetFileAttributesW(path, attr & ~FILE_ATTRIBUTE_READONLY);
        hFile = open_special(path, GENERIC_WRITE, 0);
        if (hFile == INVALID_HANDLE_VALUE) {
            errno = map_errno(GetLastError());
            ret = -1;
        }
        else {
            if (!SetFileTime(hFile, NULL, &atime, &mtime)) {
                errno = map_errno(GetLastError());
                ret = -1;
            }
            CloseHandle(hFile);
        }
        if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_READONLY))
            SetFileAttributesW(path, attr);
    }

    return ret;
}

/* License: Ruby's */
int
rb_w32_uutime(const char *path, const struct utimbuf *times)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = utf8_to_wstr(path, NULL)))
        return -1;
    ret = wutime(wpath, times);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
rb_w32_utime(const char *path, const struct utimbuf *times)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = filecp_to_wstr(path, NULL)))
        return -1;
    ret = wutime(wpath, times);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
rb_w32_uchdir(const char *path)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = utf8_to_wstr(path, NULL)))
        return -1;
    ret = _wchdir(wpath);
    free(wpath);
    return ret;
}

/* License: Ruby's */
static int
wmkdir(const WCHAR *wpath, int mode)
{
    int ret = -1;

    RUBY_CRITICAL do {
        if (CreateDirectoryW(wpath, NULL) == FALSE) {
            errno = map_errno(GetLastError());
            break;
        }
        if (_wchmod(wpath, mode) == -1) {
            RemoveDirectoryW(wpath);
            break;
        }
        ret = 0;
    } while (0);
    return ret;
}

/* License: Ruby's */
int
rb_w32_umkdir(const char *path, int mode)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = utf8_to_wstr(path, NULL)))
        return -1;
    ret = wmkdir(wpath, mode);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
rb_w32_mkdir(const char *path, int mode)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = filecp_to_wstr(path, NULL)))
        return -1;
    ret = wmkdir(wpath, mode);
    free(wpath);
    return ret;
}

/* License: Ruby's */
static int
wrmdir(const WCHAR *wpath)
{
    int ret = 0;
    RUBY_CRITICAL {
        const DWORD attr = GetFileAttributesW(wpath);
        if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_READONLY)) {
            SetFileAttributesW(wpath, attr & ~FILE_ATTRIBUTE_READONLY);
        }
        if (RemoveDirectoryW(wpath) == FALSE) {
            errno = map_errno(GetLastError());
            ret = -1;
            if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_READONLY)) {
                SetFileAttributesW(wpath, attr);
            }
        }
    }
    return ret;
}

/* License: Ruby's */
int
rb_w32_rmdir(const char *path)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = filecp_to_wstr(path, NULL)))
        return -1;
    ret = wrmdir(wpath);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
rb_w32_urmdir(const char *path)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = utf8_to_wstr(path, NULL)))
        return -1;
    ret = wrmdir(wpath);
    free(wpath);
    return ret;
}

/* License: Ruby's */
static int
wunlink(const WCHAR *path)
{
    int ret = 0;
    const DWORD SYMLINKD = FILE_ATTRIBUTE_REPARSE_POINT|FILE_ATTRIBUTE_DIRECTORY;
    RUBY_CRITICAL {
        const DWORD attr = GetFileAttributesW(path);
        if (attr == (DWORD)-1) {
        }
        else if ((attr & SYMLINKD) == SYMLINKD) {
            ret = RemoveDirectoryW(path);
        }
        else {
            if (attr & FILE_ATTRIBUTE_READONLY) {
                SetFileAttributesW(path, attr & ~FILE_ATTRIBUTE_READONLY);
            }
            ret = DeleteFileW(path);
        }
        if (!ret) {
            errno = map_errno(GetLastError());
            ret = -1;
            if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_READONLY)) {
                SetFileAttributesW(path, attr);
            }
        }
    }
    return ret;
}

/* License: Ruby's */
int
rb_w32_uunlink(const char *path)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = utf8_to_wstr(path, NULL)))
        return -1;
    ret = wunlink(wpath);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
rb_w32_unlink(const char *path)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = filecp_to_wstr(path, NULL)))
        return -1;
    ret = wunlink(wpath);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
rb_w32_uchmod(const char *path, int mode)
{
    WCHAR *wpath;
    int ret;

    if (!(wpath = utf8_to_wstr(path, NULL)))
        return -1;
    ret = _wchmod(wpath, mode);
    free(wpath);
    return ret;
}

/* License: Ruby's */
int
fchmod(int fd, int mode)
{
    typedef BOOL (WINAPI *set_file_information_by_handle_func)
        (HANDLE, int, void*, DWORD);
    static set_file_information_by_handle_func set_file_info =
        (set_file_information_by_handle_func)-1;

    /* from winbase.h of the mingw-w64 runtime package. */
    struct {
        LARGE_INTEGER CreationTime;
        LARGE_INTEGER LastAccessTime;
        LARGE_INTEGER LastWriteTime;
        LARGE_INTEGER ChangeTime;
        DWORD         FileAttributes;
    } info = {{{0}}, {{0}}, {{0}},}; /* fields with 0 are unchanged */
    HANDLE h = (HANDLE)_get_osfhandle(fd);

    if (h == INVALID_HANDLE_VALUE) {
        errno = EBADF;
        return -1;
    }
    if (set_file_info == (set_file_information_by_handle_func)-1) {
        set_file_info = (set_file_information_by_handle_func)
            get_proc_address("kernel32", "SetFileInformationByHandle", NULL);
    }
    if (!set_file_info) {
        errno = ENOSYS;
        return -1;
    }

    info.FileAttributes = FILE_ATTRIBUTE_NORMAL;
    if (!(mode & 0200)) info.FileAttributes |= FILE_ATTRIBUTE_READONLY;
    if (!set_file_info(h, 0, &info, sizeof(info))) {
        errno = map_errno(GetLastError());
        return -1;
    }
    return 0;
}

/* License: Ruby's */
int
rb_w32_isatty(int fd)
{
    DWORD mode;

    // validate fd by using _get_osfhandle() because we cannot access _nhandle
    if (_get_osfhandle(fd) == -1) {
        return 0;
    }
    if (!GetConsoleMode((HANDLE)_osfhnd(fd), &mode)) {
        errno = ENOTTY;
        return 0;
    }
    return 1;
}

#if defined(_MSC_VER) && RUBY_MSVCRT_VERSION <= 60
extern long _ftol(double);
/* License: Ruby's */
long
_ftol2(double d)
{
    return _ftol(d);
}

/* License: Ruby's */
long
_ftol2_sse(double d)
{
    return _ftol(d);
}
#endif

#ifndef signbit
/* License: Ruby's */
int
signbit(double x)
{
    int *ip = (int *)(&x + 1) - 1;
    return *ip < 0;
}
#endif

/* License: Ruby's */
const char * WSAAPI
rb_w32_inet_ntop(int af, const void *addr, char *numaddr, size_t numaddr_len)
{
    typedef char *(WSAAPI inet_ntop_t)(int, void *, char *, size_t);
    inet_ntop_t *pInetNtop;
    pInetNtop = (inet_ntop_t *)get_proc_address("ws2_32", "inet_ntop", NULL);
    if (pInetNtop) {
        return pInetNtop(af, (void *)addr, numaddr, numaddr_len);
    }
    else {
        struct in_addr in;
        memcpy(&in.s_addr, addr, sizeof(in.s_addr));
        snprintf(numaddr, numaddr_len, "%s", inet_ntoa(in));
    }
    return numaddr;
}

/* License: Ruby's */
int WSAAPI
rb_w32_inet_pton(int af, const char *src, void *dst)
{
    typedef int (WSAAPI inet_pton_t)(int, const char*, void *);
    inet_pton_t *pInetPton;
    pInetPton = (inet_pton_t *)get_proc_address("ws2_32", "inet_pton", NULL);
    if (pInetPton) {
        return pInetPton(af, src, dst);
    }
    return 0;
}

/* License: Ruby's */
char
rb_w32_fd_is_text(int fd)
{
    return _osfile(fd) & FTEXT;
}

#if RUBY_MSVCRT_VERSION < 80 && !defined(HAVE__GMTIME64_S)
/* License: Ruby's */
static int
unixtime_to_systemtime(const time_t t, SYSTEMTIME *st)
{
    FILETIME ft;
    if (unixtime_to_filetime(t, &ft)) return -1;
    if (!FileTimeToSystemTime(&ft, st)) return -1;
    return 0;
}

/* License: Ruby's */
static void
systemtime_to_tm(const SYSTEMTIME *st, struct tm *t)
{
    int y = st->wYear, m = st->wMonth, d = st->wDay;
    t->tm_sec  = st->wSecond;
    t->tm_min  = st->wMinute;
    t->tm_hour = st->wHour;
    t->tm_mday = st->wDay;
    t->tm_mon  = st->wMonth - 1;
    t->tm_year = y - 1900;
    t->tm_wday = st->wDayOfWeek;
    switch (m) {
      case 1:
        break;
      case 2:
        d += 31;
        break;
      default:
        d += 31 + 28 + (!(y % 4) && ((y % 100) || !(y % 400)));
        d += ((m - 3) * 153 + 2) / 5;
        break;
    }
    t->tm_yday = d - 1;
}

/* License: Ruby's */
static int
systemtime_to_localtime(TIME_ZONE_INFORMATION *tz, SYSTEMTIME *gst, SYSTEMTIME *lst)
{
    TIME_ZONE_INFORMATION stdtz;
    SYSTEMTIME sst;

    if (!SystemTimeToTzSpecificLocalTime(tz, gst, lst)) return -1;
    if (!tz) {
        GetTimeZoneInformation(&stdtz);
        tz = &stdtz;
    }
    if (tz->StandardBias == tz->DaylightBias) return 0;
    if (!tz->StandardDate.wMonth) return 0;
    if (!tz->DaylightDate.wMonth) return 0;
    if (tz != &stdtz) stdtz = *tz;

    stdtz.StandardDate.wMonth = stdtz.DaylightDate.wMonth = 0;
    if (!SystemTimeToTzSpecificLocalTime(&stdtz, gst, &sst)) return 0;
    if (lst->wMinute == sst.wMinute && lst->wHour == sst.wHour)
        return 0;
    return 1;
}
#endif

#ifdef HAVE__GMTIME64_S
# ifndef HAVE__LOCALTIME64_S
/* assume same as _gmtime64_s() */
#  define HAVE__LOCALTIME64_S 1
# endif
# ifndef MINGW_HAS_SECURE_API
   _CRTIMP errno_t __cdecl _gmtime64_s(struct tm* tm, const __time64_t *time);
   _CRTIMP errno_t __cdecl _localtime64_s(struct tm* tm, const __time64_t *time);
# endif
# define gmtime_s _gmtime64_s
# define localtime_s _localtime64_s
#endif

/* License: Ruby's */
struct tm *
gmtime_r(const time_t *tp, struct tm *rp)
{
    int e = EINVAL;
    if (!tp || !rp) {
      error:
        errno = e;
        return NULL;
    }
#if RUBY_MSVCRT_VERSION >= 80 || defined(HAVE__GMTIME64_S)
    e = gmtime_s(rp, tp);
    if (e != 0) goto error;
#else
    {
        SYSTEMTIME st;
        if (unixtime_to_systemtime(*tp, &st)) goto error;
        rp->tm_isdst = 0;
        systemtime_to_tm(&st, rp);
    }
#endif
    return rp;
}

/* License: Ruby's */
struct tm *
localtime_r(const time_t *tp, struct tm *rp)
{
    int e = EINVAL;
    if (!tp || !rp) {
      error:
        errno = e;
        return NULL;
    }
#if RUBY_MSVCRT_VERSION >= 80 || defined(HAVE__LOCALTIME64_S)
    e = localtime_s(rp, tp);
    if (e) goto error;
#else
    {
        SYSTEMTIME gst, lst;
        if (unixtime_to_systemtime(*tp, &gst)) goto error;
        rp->tm_isdst = systemtime_to_localtime(NULL, &gst, &lst);
        systemtime_to_tm(&lst, rp);
    }
#endif
    return rp;
}

/* License: Ruby's */
int
rb_w32_wrap_io_handle(HANDLE h, int flags)
{
    BOOL tmp;
    int len = sizeof(tmp);
    int r = getsockopt((SOCKET)h, SOL_SOCKET, SO_DEBUG, (char *)&tmp, &len);
    if (r != SOCKET_ERROR || WSAGetLastError() != WSAENOTSOCK) {
        int f = 0;
        if (flags & O_NONBLOCK) {
            flags &= ~O_NONBLOCK;
            f = O_NONBLOCK;
        }
        socklist_insert((SOCKET)h, f);
    }
    else if (flags & O_NONBLOCK) {
        errno = EINVAL;
        return -1;
    }
    return rb_w32_open_osfhandle((intptr_t)h, flags);
}

/* License: Ruby's */
int
rb_w32_unwrap_io_handle(int fd)
{
    SOCKET sock = TO_SOCKET(fd);
    _set_osfhnd(fd, (SOCKET)INVALID_HANDLE_VALUE);
    if (!is_socket(sock)) {
        UnlockFile((HANDLE)sock, 0, 0, LK_LEN, LK_LEN);
        constat_delete((HANDLE)sock);
    }
    else {
        socklist_delete(&sock, NULL);
    }
    return _close(fd);
}

#if !defined(__MINGW64__) && defined(__MINGW64_VERSION_MAJOR)
/*
 * Set floating point precision for pow() of mingw-w64 x86.
 * With default precision the result is not proper on WinXP.
 */
double
rb_w32_pow(double x, double y)
{
#undef pow
    double r;
    unsigned int default_control = _controlfp(0, 0);
    _controlfp(_PC_64, _MCW_PC);
    r = pow(x, y);
    /* Restore setting */
    _controlfp(default_control, _MCW_PC);
    return r;
}
#endif

VALUE (*const rb_f_notimplement_)(int, const VALUE *, VALUE) = rb_f_notimplement;

#if RUBY_MSVCRT_VERSION < 120
#include "missing/nextafter.c"
#endif

/* [previous][next][first][last][top][bottom][index][help] */