mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
5d90c60109
When ANSI versions of PeekConsoleInput read multibyte charactor partially, subsequent ReadFile returns wrong data on newer Windows 10 versions (probably since Windows Terminal introduced). To avoid this, use Unicode version of of PeekConsoleInput/ReadConsole.
8274 lines
188 KiB
C
8274 lines
188 KiB
C
/*
|
|
* 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/io.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 defined _MSC_VER && _MSC_VER >= 1400
|
|
#include <crtdbg.h>
|
|
#include <rtcapi.h>
|
|
#endif
|
|
#ifdef __MINGW32__
|
|
#include <mswsock.h>
|
|
#endif
|
|
#include "ruby/win32.h"
|
|
#include "ruby/vm.h"
|
|
#include "win32/dir.h"
|
|
#include "win32/file.h"
|
|
#include "id.h"
|
|
#include "internal.h"
|
|
#include "internal/enc.h"
|
|
#include "internal/object.h"
|
|
#include "internal/static_assert.h"
|
|
#include "ruby/internal/stdbool.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_stati128(const char *path, struct stati128 *st, UINT cp, BOOL lstat);
|
|
static char *w32_getenv(const char *name, UINT cp);
|
|
|
|
#undef getenv
|
|
/*
|
|
* Do not remove the macros to substitute functions in dln_find.c.
|
|
*/
|
|
#define DLN_FIND_EXTRA_ARG_DECL ,UINT cp
|
|
#define DLN_FIND_EXTRA_ARG ,cp
|
|
#define rb_w32_stati128(path, st) w32_stati128(path, st, cp, FALSE)
|
|
#define getenv(name) w32_getenv(name, cp) /* Necessarily For dln.c */
|
|
#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_stati128
|
|
#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 */
|
|
#undef getenv
|
|
|
|
#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 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 wstati128(const WCHAR *path, struct stati128 *st, BOOL lstat);
|
|
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 const 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 (WINAPI *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());
|
|
}
|
|
|
|
#if defined _MSC_VER && _MSC_VER <= 1200
|
|
/* License: Ruby's */
|
|
#define GetSystemWindowsDirectoryW GetWindowsDirectoryW
|
|
#endif
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/*
|
|
Return user's home directory using environment variables combinations.
|
|
Memory allocated by this function should be manually freed
|
|
afterwards with xfree.
|
|
|
|
Try:
|
|
HOME, HOMEDRIVE + HOMEPATH and USERPROFILE environment variables
|
|
Special Folders - Profile and Personal
|
|
*/
|
|
WCHAR *
|
|
rb_w32_home_dir(void)
|
|
{
|
|
WCHAR *buffer = NULL;
|
|
size_t buffer_len = MAX_PATH, len = 0;
|
|
enum {
|
|
HOME_NONE, ENV_HOME, ENV_DRIVEPATH, ENV_USERPROFILE
|
|
} home_type = HOME_NONE;
|
|
|
|
if ((len = GetEnvironmentVariableW(L"HOME", NULL, 0)) != 0) {
|
|
buffer_len = len;
|
|
home_type = ENV_HOME;
|
|
}
|
|
else if ((len = GetEnvironmentVariableW(L"HOMEDRIVE", NULL, 0)) != 0) {
|
|
buffer_len = len;
|
|
if ((len = GetEnvironmentVariableW(L"HOMEPATH", NULL, 0)) != 0) {
|
|
buffer_len += len;
|
|
home_type = ENV_DRIVEPATH;
|
|
}
|
|
}
|
|
else if ((len = GetEnvironmentVariableW(L"USERPROFILE", NULL, 0)) != 0) {
|
|
buffer_len = len;
|
|
home_type = ENV_USERPROFILE;
|
|
}
|
|
|
|
/* allocate buffer */
|
|
buffer = ALLOC_N(WCHAR, buffer_len);
|
|
|
|
switch (home_type) {
|
|
case ENV_HOME:
|
|
GetEnvironmentVariableW(L"HOME", buffer, buffer_len);
|
|
break;
|
|
case ENV_DRIVEPATH:
|
|
len = GetEnvironmentVariableW(L"HOMEDRIVE", buffer, buffer_len);
|
|
GetEnvironmentVariableW(L"HOMEPATH", buffer + len, buffer_len - len);
|
|
break;
|
|
case ENV_USERPROFILE:
|
|
GetEnvironmentVariableW(L"USERPROFILE", buffer, buffer_len);
|
|
break;
|
|
default:
|
|
if (!get_special_folder(CSIDL_PROFILE, buffer, buffer_len) &&
|
|
!get_special_folder(CSIDL_PERSONAL, buffer, buffer_len)) {
|
|
xfree(buffer);
|
|
return NULL;
|
|
}
|
|
REALLOC_N(buffer, WCHAR, lstrlenW(buffer) + 1);
|
|
break;
|
|
}
|
|
|
|
/* sanitize backslashes with forwardslashes */
|
|
regulate_path(buffer);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/* 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 CRITICAL_SECTION socklist_mutex;
|
|
static st_table *socklist = NULL;
|
|
|
|
static CRITICAL_SECTION conlist_mutex;
|
|
static st_table *conlist = NULL;
|
|
#define conlist_disabled ((st_table *)-1)
|
|
|
|
#define thread_exclusive(obj) \
|
|
for (bool exclusive_for_##obj = (EnterCriticalSection(&obj##_mutex), true); \
|
|
exclusive_for_##obj; \
|
|
exclusive_for_##obj = (LeaveCriticalSection(&obj##_mutex), false))
|
|
|
|
static CRITICAL_SECTION uenvarea_mutex;
|
|
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)
|
|
{
|
|
thread_exclusive(conlist) {
|
|
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)
|
|
{
|
|
WSACleanup();
|
|
DeleteCriticalSection(&select_mutex);
|
|
DeleteCriticalSection(&socklist_mutex);
|
|
DeleteCriticalSection(&conlist_mutex);
|
|
thread_exclusive(uenvarea) {
|
|
if (uenvarea) {
|
|
free(uenvarea);
|
|
uenvarea = NULL;
|
|
}
|
|
}
|
|
DeleteCriticalSection(&uenvarea_mutex);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static void
|
|
vm_exit_handler(ruby_vm_t *vm)
|
|
{
|
|
EnterCriticalSection(&socklist_mutex);
|
|
if (socklist) {
|
|
st_free_table(socklist);
|
|
socklist = NULL;
|
|
}
|
|
LeaveCriticalSection(&socklist_mutex);
|
|
|
|
EnterCriticalSection(&conlist_mutex);
|
|
if (conlist && conlist != conlist_disabled) {
|
|
st_foreach(conlist, free_conlist, 0);
|
|
st_free_table(conlist);
|
|
conlist = NULL;
|
|
}
|
|
LeaveCriticalSection(&conlist_mutex);
|
|
}
|
|
|
|
#define ATOMIC_LONG_CAS(var, oldval, newval) InterlockedCompareExchange(&(var), (newval), (oldval))
|
|
|
|
/* License: Ruby's */
|
|
static void
|
|
install_vm_exit_handler(void)
|
|
{
|
|
static LONG installed = 0;
|
|
LONG i;
|
|
|
|
while ((i = ATOMIC_LONG_CAS(installed, 0, -1)) != 1) {
|
|
if (i != 0) {
|
|
Sleep(1);
|
|
continue;
|
|
}
|
|
ruby_vm_at_exit(vm_exit_handler);
|
|
ATOMIC_LONG_CAS(installed, -1, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* 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!");
|
|
if (LOBYTE(retdata.wVersion) != 2)
|
|
rb_fatal("could not find version 2 of winsock dll");
|
|
|
|
InitializeCriticalSection(&select_mutex);
|
|
InitializeCriticalSection(&socklist_mutex);
|
|
InitializeCriticalSection(&conlist_mutex);
|
|
|
|
atexit(exit_handler);
|
|
}
|
|
|
|
#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)
|
|
{
|
|
int ret;
|
|
|
|
thread_exclusive(socklist) {
|
|
if (!socklist) {
|
|
socklist = st_init_numtable();
|
|
install_vm_exit_handler();
|
|
}
|
|
ret = st_insert(socklist, (st_data_t)sock, (st_data_t)flag);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static inline int
|
|
socklist_lookup(SOCKET sock, int *flagp)
|
|
{
|
|
st_data_t data;
|
|
int ret = 0;
|
|
|
|
thread_exclusive(socklist) {
|
|
if (!socklist) continue;
|
|
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 = 0;
|
|
|
|
thread_exclusive(socklist) {
|
|
if (!socklist) continue;
|
|
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;
|
|
}
|
|
|
|
#if RUBY_MSVCRT_VERSION >= 80
|
|
# ifdef __MINGW32__
|
|
# define _CrtSetReportMode(type,mode) ((void)0)
|
|
# define _RTC_SetErrorFunc(func) ((void)0)
|
|
# endif
|
|
static void set_pioinfo_extra(void);
|
|
#endif
|
|
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
|
|
|
|
_CrtSetReportMode(_CRT_ASSERT, 0);
|
|
_set_invalid_parameter_handler(invalid_parameter);
|
|
_RTC_SetErrorFunc(rtc_error_handler);
|
|
set_pioinfo_extra();
|
|
#endif
|
|
SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX);
|
|
|
|
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();
|
|
|
|
InitializeCriticalSection(&uenvarea_mutex);
|
|
init_env();
|
|
|
|
init_stdhandle();
|
|
|
|
// 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 int
|
|
CreateChild(struct ChildRecord *child, const WCHAR *cmd, const WCHAR *prog, HANDLE hInput, HANDLE hOutput, HANDLE hError, DWORD dwCreationFlags)
|
|
{
|
|
BOOL fRet;
|
|
STARTUPINFOW aStartupInfo;
|
|
PROCESS_INFORMATION aProcessInformation;
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
if (!cmd && !prog) {
|
|
errno = EFAULT;
|
|
return FALSE;
|
|
}
|
|
|
|
if (!child) {
|
|
errno = EAGAIN;
|
|
return FALSE;
|
|
}
|
|
|
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
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 FALSE;
|
|
}
|
|
|
|
RUBY_CRITICAL {
|
|
fRet = CreateProcessW(prog, (WCHAR *)cmd, &sa, &sa,
|
|
sa.bInheritHandle, dwCreationFlags, NULL, NULL,
|
|
&aStartupInfo, &aProcessInformation);
|
|
errno = map_errno(GetLastError());
|
|
}
|
|
|
|
if (!fRet) {
|
|
child->pid = 0; /* release the slot */
|
|
return FALSE;
|
|
}
|
|
|
|
CloseHandle(aProcessInformation.hThread);
|
|
|
|
child->hProcess = aProcessInformation.hProcess;
|
|
child->pid = (rb_pid_t)aProcessInformation.dwProcessId;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
#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: Ruby's */
|
|
MJIT_FUNC_EXPORTED HANDLE
|
|
rb_w32_start_process(const char *abspath, char *const *argv, int out_fd)
|
|
{
|
|
/* NOTE: This function is used by MJIT worker, so it can be used parallelly with
|
|
Ruby's main thread. So functions touching things shared with main thread can't
|
|
be used, like `ALLOCV` that may trigger GC or `FindFreeChildSlot` that finds
|
|
a slot from shared memory without atomic locks. */
|
|
struct ChildRecord child;
|
|
char *cmd;
|
|
size_t len;
|
|
WCHAR *wcmd = NULL, *wprog = NULL;
|
|
HANDLE outHandle = NULL;
|
|
|
|
if (out_fd) {
|
|
outHandle = (HANDLE)rb_w32_get_osfhandle(out_fd);
|
|
}
|
|
|
|
len = join_argv(NULL, argv, FALSE, filecp(), 1);
|
|
cmd = alloca(sizeof(char) * len);
|
|
join_argv(cmd, argv, FALSE, filecp(), 1);
|
|
|
|
if (!(wcmd = mbstr_to_wstr(filecp(), cmd, -1, NULL))) {
|
|
errno = E2BIG;
|
|
return NULL;
|
|
}
|
|
if (!(wprog = mbstr_to_wstr(filecp(), abspath, -1, NULL))) {
|
|
errno = E2BIG;
|
|
return NULL;
|
|
}
|
|
|
|
if (!CreateChild(&child, wcmd, wprog, NULL, outHandle, outHandle, 0)) {
|
|
return NULL;
|
|
}
|
|
|
|
free(wcmd);
|
|
free(wprog);
|
|
return child.hProcess;
|
|
}
|
|
|
|
/* 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 = w32_getenv("RUBYSHELL", cp)) && (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 = w32_getenv("COMSPEC", cp)) &&
|
|
(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) {
|
|
struct ChildRecord *child = FindFreeChildSlot();
|
|
if (CreateChild(child, wcmd, wshell, NULL, NULL, NULL, 0)) {
|
|
ret = child_result(child, 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 = w32_getenv("COMSPEC", cp)) &&
|
|
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) {
|
|
struct ChildRecord *child = FindFreeChildSlot();
|
|
if (CreateChild(child, wcmd, wprog, NULL, NULL, NULL, flags)) {
|
|
ret = child_result(child, 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 w32_aspawn_flags(mode, prog, argv, 0, filecp());
|
|
}
|
|
|
|
/* 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;
|
|
|
|
memcpy(buf, patt->str, patt->len);
|
|
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 (ISSPACE(*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 (ISSPACE(*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) {
|
|
memcpy(cptr, curr->str, curr->len);
|
|
cptr[curr->len] = '\0';
|
|
*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
|
|
//
|
|
|
|
typedef DWORD (WINAPI *get_final_path_func)(HANDLE, WCHAR*, DWORD, DWORD);
|
|
static get_final_path_func get_final_path;
|
|
|
|
static DWORD WINAPI
|
|
get_final_path_fail(HANDLE f, WCHAR *buf, DWORD len, DWORD flag)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static DWORD WINAPI
|
|
get_final_path_unknown(HANDLE f, WCHAR *buf, DWORD len, DWORD flag)
|
|
{
|
|
/* Since Windows Vista and Windows Server 2008 */
|
|
get_final_path_func func = (get_final_path_func)
|
|
get_proc_address("kernel32", "GetFinalPathNameByHandleW", NULL);
|
|
if (!func) func = get_final_path_fail;
|
|
get_final_path = func;
|
|
return func(f, buf, len, flag);
|
|
}
|
|
|
|
static get_final_path_func get_final_path = get_final_path_unknown;
|
|
|
|
/* 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)
|
|
|
|
static const WCHAR namespace_prefix[] = {L'\\', L'\\', L'?', L'\\'};
|
|
|
|
enum {FINAL_PATH_MAX = PATH_MAX + numberof(namespace_prefix)};
|
|
|
|
/* License: Artistic or GPL */
|
|
static HANDLE
|
|
open_dir_handle(const WCHAR *filename, WIN32_FIND_DATAW *fd)
|
|
{
|
|
HANDLE fh;
|
|
WCHAR fullname[FINAL_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, FINAL_PATH_MAX, 0);
|
|
CloseHandle(fh);
|
|
if (len >= FINAL_PATH_MAX) {
|
|
errno = ENAMETOOLONG;
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
}
|
|
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 stati128 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 (wstati128(wpath, &sbuf, FALSE) < 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)
|
|
{
|
|
/* This is used by MJIT worker. Do not trigger GC or call Ruby method here. */
|
|
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_utf8(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 long dummy_ino = 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 = (ino_t)(InterlockedIncrement(&dummy_ino) - 1);
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
struct direct *
|
|
rb_w32_ureaddir(DIR *dirp)
|
|
{
|
|
const UINT cp = CP_UTF8;
|
|
return readdir_internal(dirp, win32_direct_conv, &cp);
|
|
}
|
|
|
|
//
|
|
// 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 char lowio_text_mode;
|
|
typedef char lowio_pipe_lookahead[3];
|
|
|
|
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?)
|
|
lowio_text_mode textmode;
|
|
lowio_pipe_lookahead _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 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
|
|
# define FUNCTION_RET 0xc3 /* ret */
|
|
# ifdef _DEBUG
|
|
# define UCRTBASE "ucrtbased.dll"
|
|
# else
|
|
# define UCRTBASE "ucrtbase.dll"
|
|
# endif
|
|
/* get __pioinfo addr with _isatty */
|
|
char *p = (char*)get_proc_address(UCRTBASE, "_isatty", NULL);
|
|
char *pend = p;
|
|
/* _osfile(fh) & FDEV */
|
|
|
|
# ifdef _WIN64
|
|
int32_t rel;
|
|
char *rip;
|
|
/* add rsp, _ */
|
|
# define FUNCTION_BEFORE_RET_MARK "\x48\x83\xc4"
|
|
# define FUNCTION_SKIP_BYTES 1
|
|
# ifdef _DEBUG
|
|
/* lea rcx,[__pioinfo's addr in RIP-relative 32bit addr] */
|
|
# define PIOINFO_MARK "\x48\x8d\x0d"
|
|
# else
|
|
/* lea rdx,[__pioinfo's addr in RIP-relative 32bit addr] */
|
|
# define PIOINFO_MARK "\x48\x8d\x15"
|
|
# endif
|
|
|
|
# else /* x86 */
|
|
/* pop ebp */
|
|
# define FUNCTION_BEFORE_RET_MARK "\x5d"
|
|
# define FUNCTION_SKIP_BYTES 0
|
|
/* mov eax,dword ptr [eax*4+100EB430h] */
|
|
# define PIOINFO_MARK "\x8B\x04\x85"
|
|
# endif
|
|
if (p) {
|
|
for (pend += 10; pend < p + 300; pend++) {
|
|
// find end of function
|
|
if (memcmp(pend, FUNCTION_BEFORE_RET_MARK, sizeof(FUNCTION_BEFORE_RET_MARK) - 1) == 0 &&
|
|
(*(pend + (sizeof(FUNCTION_BEFORE_RET_MARK) - 1) + FUNCTION_SKIP_BYTES) & FUNCTION_RET) == FUNCTION_RET) {
|
|
// search backwards from end of function
|
|
for (pend -= (sizeof(PIOINFO_MARK) - 1); pend > p; pend--) {
|
|
if (memcmp(pend, PIOINFO_MARK, sizeof(PIOINFO_MARK) - 1) == 0) {
|
|
p = pend;
|
|
goto found;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(stderr, "unexpected " UCRTBASE "\n");
|
|
_exit(1);
|
|
|
|
found:
|
|
p += sizeof(PIOINFO_MARK) - 1;
|
|
#ifdef _WIN64
|
|
rel = *(int32_t*)(p);
|
|
rip = p + sizeof(int32_t);
|
|
__pioinfo = (ioinfo**)(rip + rel);
|
|
#else
|
|
__pioinfo = *(ioinfo***)(p);
|
|
#endif
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
#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);
|
|
|
|
{
|
|
HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
DWORD m;
|
|
if (GetConsoleMode(h, &m)) {
|
|
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
|
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x4
|
|
#endif
|
|
SetConsoleMode(h, m | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
|
}
|
|
}
|
|
}
|
|
|
|
#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 = (PeekConsoleInputW((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 (PeekConsoleInputW((HANDLE)sock, &ir, 1, &n) && n > 0) {
|
|
if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown &&
|
|
ir.Event.KeyEvent.uChar.AsciiChar) {
|
|
ret = 1;
|
|
}
|
|
else {
|
|
ReadConsoleInputW((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 {
|
|
RUBY_CRITICAL {
|
|
thread_exclusive(select) {
|
|
r = select(nfds, rd, wr, ex, timeout);
|
|
}
|
|
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;
|
|
|
|
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;
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
|
|
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;
|
|
|
|
s = TO_SOCKET(fd);
|
|
|
|
if (!pWSARecvMsg) {
|
|
static const 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;
|
|
|
|
s = TO_SOCKET(fd);
|
|
|
|
if (!pWSASendMsg) {
|
|
static const 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;
|
|
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;
|
|
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;
|
|
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
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;
|
|
|
|
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 = (cigl_t)-1;
|
|
static cilnA_t pConvertInterfaceLuidToNameA = (cilnA_t)-1;
|
|
|
|
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 == (cigl_t)-1)
|
|
pConvertInterfaceGuidToLuid =
|
|
(cigl_t)get_proc_address("iphlpapi.dll",
|
|
"ConvertInterfaceGuidToLuid", NULL);
|
|
if (pConvertInterfaceLuidToNameA == (cilnA_t)-1)
|
|
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
|
|
|
|
#if 0 // Have never been used
|
|
//
|
|
// 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) {}
|
|
#endif
|
|
|
|
/* 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_nonblock2(int fd, int nonblock)
|
|
{
|
|
SOCKET sock = TO_SOCKET(fd);
|
|
if (is_socket(sock)) {
|
|
return setfl(sock, nonblock ? O_NONBLOCK : 0);
|
|
}
|
|
else if (is_pipe(sock)) {
|
|
DWORD state;
|
|
if (!GetNamedPipeHandleState((HANDLE)sock, &state, NULL, NULL, NULL, NULL, 0)) {
|
|
errno = map_errno(GetLastError());
|
|
return -1;
|
|
}
|
|
if (nonblock) {
|
|
state |= PIPE_NOWAIT;
|
|
}
|
|
else {
|
|
state &= ~PIPE_NOWAIT;
|
|
}
|
|
if (!SetNamedPipeHandleState((HANDLE)sock, &state, NULL, NULL)) {
|
|
errno = map_errno(GetLastError());
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
else {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int
|
|
rb_w32_set_nonblock(int fd)
|
|
{
|
|
return rb_w32_set_nonblock2(fd, TRUE);
|
|
}
|
|
|
|
#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. */
|
|
err = GetLastError();
|
|
switch (err) {
|
|
case ERROR_INVALID_PARAMETER:
|
|
errno = ECHILD;
|
|
break;
|
|
case ERROR_INVALID_HANDLE:
|
|
errno = EINVAL;
|
|
break;
|
|
default:
|
|
errno = map_errno(err);
|
|
break;
|
|
}
|
|
error_exit:
|
|
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>
|
|
|
|
static int have_precisetime = -1;
|
|
|
|
static void
|
|
get_systemtime(FILETIME *ft)
|
|
{
|
|
typedef void (WINAPI *get_time_func)(FILETIME *ft);
|
|
static get_time_func func = (get_time_func)-1;
|
|
|
|
if (func == (get_time_func)-1) {
|
|
/* GetSystemTimePreciseAsFileTime is available since Windows 8 and Windows Server 2012. */
|
|
func = (get_time_func)get_proc_address("kernel32", "GetSystemTimePreciseAsFileTime", NULL);
|
|
if (func == NULL) {
|
|
func = GetSystemTimeAsFileTime;
|
|
have_precisetime = 0;
|
|
}
|
|
else
|
|
have_precisetime = 1;
|
|
}
|
|
if (!ft) return;
|
|
func(ft);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
/* split FILETIME value into UNIX time and sub-seconds in NT ticks */
|
|
static time_t
|
|
filetime_split(const FILETIME* ft, long *subsec)
|
|
{
|
|
ULARGE_INTEGER tmp;
|
|
unsigned LONG_LONG lt;
|
|
const unsigned LONG_LONG subsec_unit = (unsigned LONG_LONG)10 * 1000 * 1000;
|
|
|
|
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 -= (LONG_LONG)((1970-1601)*365.2425) * 24 * 60 * 60 * subsec_unit;
|
|
|
|
*subsec = (long)(lt % subsec_unit);
|
|
return (time_t)(lt / subsec_unit);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int __cdecl
|
|
gettimeofday(struct timeval *tv, struct timezone *tz)
|
|
{
|
|
FILETIME ft;
|
|
long subsec;
|
|
|
|
get_systemtime(&ft);
|
|
tv->tv_sec = filetime_split(&ft, &subsec);
|
|
tv->tv_usec = subsec / 10;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
clock_gettime(clockid_t clock_id, struct timespec *sp)
|
|
{
|
|
switch (clock_id) {
|
|
case CLOCK_REALTIME:
|
|
{
|
|
FILETIME ft;
|
|
long subsec;
|
|
|
|
get_systemtime(&ft);
|
|
sp->tv_sec = filetime_split(&ft, &subsec);
|
|
sp->tv_nsec = subsec * 100;
|
|
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 */
|
|
static char *
|
|
w32_getcwd(char *buffer, int size, UINT cp, void *alloc(int, void *), void *arg)
|
|
{
|
|
WCHAR *p;
|
|
int wlen, len;
|
|
|
|
len = GetCurrentDirectoryW(0, NULL);
|
|
if (!len) {
|
|
errno = map_errno(GetLastError());
|
|
return NULL;
|
|
}
|
|
|
|
if (buffer && size < len) {
|
|
errno = ERANGE;
|
|
return NULL;
|
|
}
|
|
|
|
p = ALLOCA_N(WCHAR, len);
|
|
if (!GetCurrentDirectoryW(len, p)) {
|
|
errno = map_errno(GetLastError());
|
|
return NULL;
|
|
}
|
|
|
|
wlen = translate_wchar(p, L'\\', L'/') - p + 1;
|
|
len = WideCharToMultiByte(cp, 0, p, wlen, NULL, 0, NULL, NULL);
|
|
if (buffer) {
|
|
if (size < len) {
|
|
errno = ERANGE;
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
buffer = (*alloc)(len, arg);
|
|
if (!buffer) {
|
|
errno = ENOMEM;
|
|
return NULL;
|
|
}
|
|
}
|
|
WideCharToMultiByte(cp, 0, p, wlen, buffer, len, NULL, NULL);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static void *
|
|
getcwd_alloc(int size, void *dummy)
|
|
{
|
|
return malloc(size);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
char *
|
|
rb_w32_getcwd(char *buffer, int size)
|
|
{
|
|
return w32_getcwd(buffer, size, filecp(), getcwd_alloc, NULL);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
char *
|
|
rb_w32_ugetcwd(char *buffer, int size)
|
|
{
|
|
return w32_getcwd(buffer, size, CP_UTF8, getcwd_alloc, NULL);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static void *
|
|
getcwd_value(int size, void *arg)
|
|
{
|
|
VALUE str = *(VALUE *)arg = rb_utf8_str_new(0, size - 1);
|
|
return RSTRING_PTR(str);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
VALUE
|
|
rb_dir_getwd_ospath(void)
|
|
{
|
|
VALUE cwd = Qnil;
|
|
w32_getcwd(NULL, 0, CP_UTF8, getcwd_value, &cwd);
|
|
return cwd;
|
|
}
|
|
|
|
/* 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(rb_pid_t 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 if (rp->ReparseTag == 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;
|
|
}
|
|
else {
|
|
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
|
|
#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
|
|
#define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x2)
|
|
#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;
|
|
int e;
|
|
|
|
typedef BOOLEAN (WINAPI *create_symbolic_link_func)(WCHAR*, WCHAR*, DWORD);
|
|
static create_symbolic_link_func create_symbolic_link =
|
|
(create_symbolic_link_func)-1;
|
|
static DWORD create_flag = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
|
|
|
if (create_symbolic_link == (create_symbolic_link_func)-1) {
|
|
/* Since Windows Vista and Windows Server 2008 */
|
|
create_symbolic_link = (create_symbolic_link_func)
|
|
get_proc_address("kernel32", "CreateSymbolicLinkW", NULL);
|
|
}
|
|
if (!create_symbolic_link) {
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
if (!*link) {
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
if (!*src) {
|
|
errno = EINVAL;
|
|
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 |= create_flag);
|
|
if (!ret &&
|
|
(e = GetLastError()) == ERROR_INVALID_PARAMETER &&
|
|
(flag & SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) {
|
|
create_flag = 0;
|
|
flag &= ~SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
|
|
ret = create_symbolic_link(wlink, wsrc, flag);
|
|
if (!ret) e = GetLastError();
|
|
}
|
|
ALLOCV_END(buf);
|
|
|
|
if (!ret) {
|
|
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 */
|
|
rb_pid_t
|
|
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, *found = NULL;
|
|
int wlen;
|
|
|
|
if (len == 0) return NULL;
|
|
|
|
if (!NTLoginName) {
|
|
/* initialized in init_env, uenvarea_mutex should have been
|
|
* initialized before it */
|
|
return getenv(name);
|
|
}
|
|
|
|
thread_exclusive(uenvarea) {
|
|
if (uenvarea) {
|
|
free(uenvarea);
|
|
uenvarea = NULL;
|
|
}
|
|
wenvarea = GetEnvironmentStringsW();
|
|
if (!wenvarea) {
|
|
map_errno(GetLastError());
|
|
continue;
|
|
}
|
|
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)
|
|
continue;
|
|
|
|
for (env = uenvarea; *env; env += strlen(env) + 1) {
|
|
if (strncasecmp(env, name, len) == 0 && *(env + len) == '=') {
|
|
found = env + len + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/* 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) {
|
|
e = GetLastError();
|
|
ASSUME(e);
|
|
return e;
|
|
}
|
|
if (!GetFileInformationByHandle(h, &st)) {
|
|
e = GetLastError();
|
|
ASSUME(e);
|
|
}
|
|
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 = 0, 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 long filetime_to_nsec(const FILETIME *ft);
|
|
static WCHAR *name_for_stat(WCHAR *buf, const WCHAR *path);
|
|
static DWORD stati128_handle(HANDLE h, struct stati128 *st);
|
|
|
|
#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_fstati128(int fd, struct stati128 *st)
|
|
{
|
|
struct stat tmp;
|
|
int ret = fstat(fd, &tmp);
|
|
|
|
if (ret) return ret;
|
|
COPY_STAT(tmp, *st, +);
|
|
stati128_handle((HANDLE)_get_osfhandle(fd), st);
|
|
return ret;
|
|
}
|
|
|
|
#if !defined FILE_INVALID_FILE_ID && !defined __MINGW32__
|
|
typedef struct {
|
|
BYTE Identifier[16];
|
|
} FILE_ID_128;
|
|
#endif
|
|
|
|
#if !defined(_WIN32_WINNT_WIN8) || _WIN32_WINNT < 0x602
|
|
#define FileIdInfo 0x12
|
|
|
|
typedef struct {
|
|
unsigned LONG_LONG VolumeSerialNumber;
|
|
FILE_ID_128 FileId;
|
|
} FILE_ID_INFO;
|
|
#endif
|
|
|
|
static DWORD
|
|
get_ino(HANDLE h, FILE_ID_INFO *id)
|
|
{
|
|
typedef BOOL (WINAPI *gfibhe_t)(HANDLE, int, void *, DWORD);
|
|
static gfibhe_t pGetFileInformationByHandleEx = (gfibhe_t)-1;
|
|
|
|
if (pGetFileInformationByHandleEx == (gfibhe_t)-1)
|
|
/* Since Windows Vista and Windows Server 2008 */
|
|
pGetFileInformationByHandleEx = (gfibhe_t)get_proc_address("kernel32", "GetFileInformationByHandleEx", NULL);
|
|
|
|
if (pGetFileInformationByHandleEx) {
|
|
if (pGetFileInformationByHandleEx(h, FileIdInfo, id, sizeof(*id)))
|
|
return 0;
|
|
else
|
|
return GetLastError();
|
|
}
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static DWORD
|
|
stati128_handle(HANDLE h, struct stati128 *st)
|
|
{
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
DWORD attr = (DWORD)-1;
|
|
|
|
if (GetFileInformationByHandle(h, &info)) {
|
|
FILE_ID_INFO fii;
|
|
st->st_size = ((__int64)info.nFileSizeHigh << 32) | info.nFileSizeLow;
|
|
st->st_atime = filetime_to_unixtime(&info.ftLastAccessTime);
|
|
st->st_atimensec = filetime_to_nsec(&info.ftLastAccessTime);
|
|
st->st_mtime = filetime_to_unixtime(&info.ftLastWriteTime);
|
|
st->st_mtimensec = filetime_to_nsec(&info.ftLastWriteTime);
|
|
st->st_ctime = filetime_to_unixtime(&info.ftCreationTime);
|
|
st->st_ctimensec = filetime_to_nsec(&info.ftCreationTime);
|
|
st->st_nlink = info.nNumberOfLinks;
|
|
attr = info.dwFileAttributes;
|
|
if (!get_ino(h, &fii)) {
|
|
st->st_ino = *((unsigned __int64 *)&fii.FileId);
|
|
st->st_inohigh = *((__int64 *)&fii.FileId + 1);
|
|
}
|
|
else {
|
|
st->st_ino = ((__int64)info.nFileIndexHigh << 32) | info.nFileIndexLow;
|
|
st->st_inohigh = 0;
|
|
}
|
|
}
|
|
return attr;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static time_t
|
|
filetime_to_unixtime(const FILETIME *ft)
|
|
{
|
|
long subsec;
|
|
time_t t = filetime_split(ft, &subsec);
|
|
|
|
if (t < 0) return 0;
|
|
return t;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static long
|
|
filetime_to_nsec(const FILETIME *ft)
|
|
{
|
|
if (have_precisetime <= 0)
|
|
return 0;
|
|
else {
|
|
ULARGE_INTEGER tmp;
|
|
tmp.LowPart = ft->dwLowDateTime;
|
|
tmp.HighPart = ft->dwHighDateTime;
|
|
return (long)(tmp.QuadPart % 10000000) * 100;
|
|
}
|
|
}
|
|
|
|
/* 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 stati128 *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_atimensec = filetime_to_nsec(&wfd.ftLastAccessTime);
|
|
st->st_mtime = filetime_to_unixtime(&wfd.ftLastWriteTime);
|
|
st->st_mtimensec = filetime_to_nsec(&wfd.ftLastWriteTime);
|
|
st->st_ctime = filetime_to_unixtime(&wfd.ftCreationTime);
|
|
st->st_ctimensec = filetime_to_nsec(&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;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static int
|
|
winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat)
|
|
{
|
|
DWORD flags = lstat ? FILE_FLAG_OPEN_REPARSE_POINT : 0;
|
|
HANDLE f;
|
|
WCHAR finalname[PATH_MAX];
|
|
|
|
memset(st, 0, sizeof(*st));
|
|
f = open_special(path, 0, flags);
|
|
if (f != INVALID_HANDLE_VALUE) {
|
|
DWORD attr = stati128_handle(f, st);
|
|
const DWORD len = get_final_path(f, finalname, numberof(finalname), 0);
|
|
CloseHandle(f);
|
|
if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
|
|
/* TODO: size in which encoding? */
|
|
if (rb_w32_reparse_symlink_p(path))
|
|
st->st_size = 0;
|
|
else
|
|
attr &= ~FILE_ATTRIBUTE_REPARSE_POINT;
|
|
}
|
|
if (attr & FILE_ATTRIBUTE_DIRECTORY) {
|
|
if (check_valid_dir(path)) return -1;
|
|
}
|
|
st->st_mode = fileattr_to_unixmode(attr, path);
|
|
if (len) {
|
|
finalname[min(len, numberof(finalname)-1)] = 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 */
|
|
int
|
|
rb_w32_stat(const char *path, struct stat *st)
|
|
{
|
|
struct stati128 tmp;
|
|
|
|
if (w32_stati128(path, &tmp, filecp(), FALSE)) return -1;
|
|
COPY_STAT(tmp, *st, (_off_t));
|
|
return 0;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static int
|
|
wstati128(const WCHAR *path, struct stati128 *st, BOOL lstat)
|
|
{
|
|
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, lstat);
|
|
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_ustati128(const char *path, struct stati128 *st)
|
|
{
|
|
return w32_stati128(path, st, CP_UTF8, FALSE);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_stati128(const char *path, struct stati128 *st)
|
|
{
|
|
return w32_stati128(path, st, filecp(), FALSE);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static int
|
|
w32_stati128(const char *path, struct stati128 *st, UINT cp, BOOL lstat)
|
|
{
|
|
WCHAR *wpath;
|
|
int ret;
|
|
|
|
if (!(wpath = mbstr_to_wstr(cp, path, -1, NULL)))
|
|
return -1;
|
|
ret = wstati128(wpath, st, lstat);
|
|
free(wpath);
|
|
return ret;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_ulstati128(const char *path, struct stati128 *st)
|
|
{
|
|
return w32_stati128(path, st, CP_UTF8, TRUE);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_lstati128(const char *path, struct stati128 *st)
|
|
{
|
|
return w32_stati128(path, st, filecp(), TRUE);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
off_t
|
|
rb_w32_lseek(int fd, off_t ofs, int whence)
|
|
{
|
|
SOCKET sock = TO_SOCKET(fd);
|
|
if (is_socket(sock) || is_pipe(sock)) {
|
|
errno = ESPIPE;
|
|
return -1;
|
|
}
|
|
return _lseeki64(fd, ofs, whence);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static int
|
|
w32_access(const char *path, int mode, UINT cp)
|
|
{
|
|
struct stati128 stat;
|
|
if (w32_stati128(path, &stat, cp, FALSE) != 0)
|
|
return -1;
|
|
mode <<= 6;
|
|
if ((stat.st_mode & mode) != mode) {
|
|
errno = EACCES;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_access(const char *path, int mode)
|
|
{
|
|
return w32_access(path, mode, filecp());
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_uaccess(const char *path, int mode)
|
|
{
|
|
return w32_access(path, mode, CP_UTF8);
|
|
}
|
|
|
|
/* 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 = (query_func *)-1;
|
|
rb_pid_t ppid = 0;
|
|
|
|
if (pNtQueryInformationProcess == (query_func *)-1)
|
|
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 long 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, InterlockedIncrement(&serial)-1);
|
|
|
|
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 = NULL;
|
|
thread_exclusive(conlist) {
|
|
if (!conlist) {
|
|
if (console_emulator_p()) {
|
|
conlist = conlist_disabled;
|
|
continue;
|
|
}
|
|
conlist = st_init_numtable();
|
|
install_vm_exit_handler();
|
|
}
|
|
else if (conlist == conlist_disabled) {
|
|
continue;
|
|
}
|
|
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;
|
|
thread_exclusive(conlist) {
|
|
if (!conlist || conlist == conlist_disabled) continue;
|
|
if (!st_lookup(conlist, (st_data_t)h, &data)) continue;
|
|
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_clear(HANDLE handle, WORD attr, DWORD len, COORD pos)
|
|
{
|
|
DWORD written;
|
|
|
|
FillConsoleOutputAttribute(handle, attr, len, pos, &written);
|
|
FillConsoleOutputCharacterW(handle, L' ', len, pos, &written);
|
|
}
|
|
|
|
/* 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 arg0, arg1 = 1;
|
|
COORD pos;
|
|
|
|
if (!GetConsoleScreenBufferInfo(handle, &csbi)) return;
|
|
arg0 = (count > 0 && seq[0] > 0);
|
|
if (arg0) 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 < csbi.srWindow.Top)
|
|
csbi.dwCursorPosition.Y = csbi.srWindow.Top;
|
|
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.srWindow.Bottom)
|
|
csbi.dwCursorPosition.Y = csbi.srWindow.Bottom;
|
|
SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
|
|
break;
|
|
case L'C':
|
|
csbi.dwCursorPosition.X += arg1;
|
|
if (csbi.dwCursorPosition.X >= csbi.srWindow.Right)
|
|
csbi.dwCursorPosition.X = csbi.srWindow.Right;
|
|
SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
|
|
break;
|
|
case L'D':
|
|
csbi.dwCursorPosition.X -= arg1;
|
|
if (csbi.dwCursorPosition.X < csbi.srWindow.Left)
|
|
csbi.dwCursorPosition.X = csbi.srWindow.Left;
|
|
SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
|
|
break;
|
|
case L'G':
|
|
case L'`':
|
|
arg1 += csbi.srWindow.Left;
|
|
if (arg1 > csbi.srWindow.Right)
|
|
arg1 = csbi.srWindow.Right;
|
|
csbi.dwCursorPosition.X = arg1;
|
|
SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
|
|
break;
|
|
case L'd':
|
|
arg1 += csbi.srWindow.Top;
|
|
if (arg1 > csbi.srWindow.Bottom)
|
|
arg1 = csbi.srWindow.Bottom;
|
|
csbi.dwCursorPosition.Y = arg1;
|
|
SetConsoleCursorPosition(handle, csbi.dwCursorPosition);
|
|
break;
|
|
case L'H':
|
|
case L'f':
|
|
pos.Y = arg1 + csbi.srWindow.Top - 1;
|
|
if (pos.Y > csbi.srWindow.Bottom) pos.Y = csbi.srWindow.Bottom;
|
|
if (count < 2 || (arg1 = seq[1]) <= 0) arg1 = 1;
|
|
pos.X = arg1 + csbi.srWindow.Left - 1;
|
|
if (pos.X > csbi.srWindow.Right) pos.X = csbi.srWindow.Right;
|
|
SetConsoleCursorPosition(handle, pos);
|
|
break;
|
|
case L'J':
|
|
switch (arg0 ? arg1 : 0) {
|
|
case 0: /* erase after cursor */
|
|
constat_clear(handle, csbi.wAttributes,
|
|
(csbi.dwSize.X * (csbi.srWindow.Bottom - csbi.dwCursorPosition.Y + 1)
|
|
- csbi.dwCursorPosition.X),
|
|
csbi.dwCursorPosition);
|
|
break;
|
|
case 1: /* erase before *and* cursor */
|
|
pos.X = 0;
|
|
pos.Y = csbi.srWindow.Top;
|
|
constat_clear(handle, csbi.wAttributes,
|
|
(csbi.dwSize.X * (csbi.dwCursorPosition.Y - csbi.srWindow.Top)
|
|
+ csbi.dwCursorPosition.X + 1),
|
|
pos);
|
|
break;
|
|
case 2: /* erase entire screen */
|
|
pos.X = 0;
|
|
pos.Y = csbi.srWindow.Top;
|
|
constat_clear(handle, csbi.wAttributes,
|
|
(csbi.dwSize.X * (csbi.srWindow.Bottom - csbi.srWindow.Top + 1)),
|
|
pos);
|
|
break;
|
|
case 3: /* erase entire screen */
|
|
pos.X = 0;
|
|
pos.Y = 0;
|
|
constat_clear(handle, csbi.wAttributes,
|
|
(csbi.dwSize.X * csbi.dwSize.Y),
|
|
pos);
|
|
break;
|
|
}
|
|
break;
|
|
case L'K':
|
|
switch (arg0 ? arg1 : 0) {
|
|
case 0: /* erase after cursor */
|
|
constat_clear(handle, csbi.wAttributes,
|
|
(csbi.dwSize.X - csbi.dwCursorPosition.X),
|
|
csbi.dwCursorPosition);
|
|
break;
|
|
case 1: /* erase before *and* cursor */
|
|
pos.X = 0;
|
|
pos.Y = csbi.dwCursorPosition.Y;
|
|
constat_clear(handle, csbi.wAttributes,
|
|
csbi.dwCursorPosition.X + 1, pos);
|
|
break;
|
|
case 2: /* erase entire line */
|
|
pos.X = 0;
|
|
pos.Y = csbi.dwCursorPosition.Y;
|
|
constat_clear(handle, csbi.wAttributes,
|
|
csbi.dwSize.X, pos);
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* get rid of console writing bug; assume WriteConsole and WriteFile
|
|
* on a console share the same limit. */
|
|
static const long MAXSIZE_CONSOLE_WRITING = 31366;
|
|
|
|
/* 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 if ((rest = *lenp - len) < MAXSIZE_CONSOLE_WRITING) {
|
|
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))) {
|
|
ssize_t w = _write(fd, buf, size);
|
|
if (w == (ssize_t)-1 && errno == EINVAL) {
|
|
errno = map_errno(GetLastError());
|
|
}
|
|
return w;
|
|
}
|
|
|
|
rb_acrt_lowio_lock_fh(fd);
|
|
|
|
if (!size || _osfile(fd) & FEOFLAG) {
|
|
rb_acrt_lowio_unlock_fh(fd);
|
|
return 0;
|
|
}
|
|
|
|
ret = 0;
|
|
retry:
|
|
len = (_osfile(fd) & FDEV) ? min(MAXSIZE_CONSOLE_WRITING, 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 & ENABLE_VIRTUAL_TERMINAL_PROCESSING) {
|
|
if (!WriteConsoleW(handle, ptr, len, &reslen, 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;
|
|
}
|
|
|
|
#if RUBY_MSVCRT_VERSION < 80 && !defined(HAVE__GMTIME64_S)
|
|
/* 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;
|
|
}
|
|
#endif
|
|
|
|
/* License: Ruby's */
|
|
static int
|
|
timespec_to_filetime(const struct timespec *ts, FILETIME *ft)
|
|
{
|
|
ULARGE_INTEGER tmp;
|
|
|
|
tmp.QuadPart = ((LONG_LONG)ts->tv_sec + (LONG_LONG)((1970-1601)*365.2425) * 24 * 60 * 60) * 10 * 1000 * 1000;
|
|
tmp.QuadPart += ts->tv_nsec / 100;
|
|
ft->dwLowDateTime = tmp.LowPart;
|
|
ft->dwHighDateTime = tmp.HighPart;
|
|
return 0;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
static int
|
|
wutimensat(int dirfd, const WCHAR *path, const struct timespec *times, int flags)
|
|
{
|
|
HANDLE hFile;
|
|
FILETIME atime, mtime;
|
|
struct stati128 stat;
|
|
int ret = 0;
|
|
|
|
/* TODO: When path is absolute, dirfd should be ignored. */
|
|
if (dirfd != AT_FDCWD) {
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
|
|
if (flags != 0) {
|
|
errno = EINVAL; /* AT_SYMLINK_NOFOLLOW isn't supported. */
|
|
return -1;
|
|
}
|
|
|
|
if (wstati128(path, &stat, FALSE)) {
|
|
return -1;
|
|
}
|
|
|
|
if (times) {
|
|
if (timespec_to_filetime(×[0], &atime)) {
|
|
return -1;
|
|
}
|
|
if (timespec_to_filetime(×[1], &mtime)) {
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
get_systemtime(&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 */
|
|
static int
|
|
w32_utimensat(int dirfd, const char *path, const struct timespec *times, int flags, UINT cp)
|
|
{
|
|
WCHAR *wpath = mbstr_to_wstr(cp, path, -1, NULL);
|
|
int ret = -1;
|
|
|
|
if (wpath) {
|
|
ret = wutimensat(dirfd, wpath, times, flags);
|
|
free(wpath);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_uutime(const char *path, const struct utimbuf *times)
|
|
{
|
|
struct timespec ts[2];
|
|
|
|
ts[0].tv_sec = times->actime;
|
|
ts[0].tv_nsec = 0;
|
|
ts[1].tv_sec = times->modtime;
|
|
ts[1].tv_nsec = 0;
|
|
return w32_utimensat(AT_FDCWD, path, ts, 0, CP_UTF8);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_utime(const char *path, const struct utimbuf *times)
|
|
{
|
|
struct timespec ts[2];
|
|
|
|
ts[0].tv_sec = times->actime;
|
|
ts[0].tv_nsec = 0;
|
|
ts[1].tv_sec = times->modtime;
|
|
ts[1].tv_nsec = 0;
|
|
return w32_utimensat(AT_FDCWD, path, ts, 0, filecp());
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_uutimes(const char *path, const struct timeval *times)
|
|
{
|
|
struct timespec ts[2];
|
|
|
|
ts[0].tv_sec = times[0].tv_sec;
|
|
ts[0].tv_nsec = times[0].tv_usec * 1000;
|
|
ts[1].tv_sec = times[1].tv_sec;
|
|
ts[1].tv_nsec = times[1].tv_usec * 1000;
|
|
return w32_utimensat(AT_FDCWD, path, ts, 0, CP_UTF8);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_utimes(const char *path, const struct timeval *times)
|
|
{
|
|
struct timespec ts[2];
|
|
|
|
ts[0].tv_sec = times[0].tv_sec;
|
|
ts[0].tv_nsec = times[0].tv_usec * 1000;
|
|
ts[1].tv_sec = times[1].tv_sec;
|
|
ts[1].tv_nsec = times[1].tv_usec * 1000;
|
|
return w32_utimensat(AT_FDCWD, path, ts, 0, filecp());
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_uutimensat(int dirfd, const char *path, const struct timespec *times, int flags)
|
|
{
|
|
return w32_utimensat(dirfd, path, times, flags, CP_UTF8);
|
|
}
|
|
|
|
/* License: Ruby's */
|
|
int
|
|
rb_w32_utimensat(int dirfd, const char *path, const struct timespec *times, int flags)
|
|
{
|
|
return w32_utimensat(dirfd, path, times, flags, filecp());
|
|
}
|
|
|
|
/* 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) {
|
|
/* Since Windows Vista and Windows Server 2008 */
|
|
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);
|
|
static inet_ntop_t *pInetNtop = (inet_ntop_t *)-1;
|
|
if (pInetNtop == (inet_ntop_t *)-1)
|
|
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 *);
|
|
static inet_pton_t *pInetPton = (inet_pton_t *)-1;
|
|
if (pInetPton == (inet_pton_t *)-1)
|
|
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
|
|
|
|
typedef struct {
|
|
BOOL file_id_p;
|
|
union {
|
|
BY_HANDLE_FILE_INFORMATION bhfi;
|
|
FILE_ID_INFO fii;
|
|
} info;
|
|
} w32_io_info_t;
|
|
|
|
static HANDLE
|
|
w32_io_info(VALUE *file, w32_io_info_t *st)
|
|
{
|
|
VALUE tmp;
|
|
HANDLE f, ret = 0;
|
|
|
|
tmp = rb_check_convert_type_with_id(*file, T_FILE, "IO", idTo_io);
|
|
if (!NIL_P(tmp)) {
|
|
rb_io_t *fptr;
|
|
|
|
GetOpenFile(tmp, fptr);
|
|
f = (HANDLE)rb_w32_get_osfhandle(fptr->fd);
|
|
if (f == (HANDLE)-1) return INVALID_HANDLE_VALUE;
|
|
}
|
|
else {
|
|
VALUE tmp;
|
|
WCHAR *ptr;
|
|
int len;
|
|
VALUE v;
|
|
|
|
FilePathValue(*file);
|
|
tmp = rb_str_encode_ospath(*file);
|
|
len = MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, NULL, 0);
|
|
ptr = ALLOCV_N(WCHAR, v, len);
|
|
MultiByteToWideChar(CP_UTF8, 0, RSTRING_PTR(tmp), -1, ptr, len);
|
|
f = CreateFileW(ptr, 0,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
ALLOCV_END(v);
|
|
if (f == INVALID_HANDLE_VALUE) return f;
|
|
ret = f;
|
|
}
|
|
if (GetFileType(f) == FILE_TYPE_DISK) {
|
|
DWORD err;
|
|
ZeroMemory(st, sizeof(*st));
|
|
err = get_ino(f, &st->info.fii);
|
|
if (!err) {
|
|
st->file_id_p = TRUE;
|
|
return ret;
|
|
}
|
|
else if (err != ERROR_INVALID_PARAMETER) {
|
|
CloseHandle(f);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
/* this API may not work at files on non Microsoft SMB
|
|
* server, fallback to old API then. */
|
|
if (GetFileInformationByHandle(f, &st->info.bhfi)) {
|
|
st->file_id_p = FALSE;
|
|
return ret;
|
|
}
|
|
}
|
|
if (ret) CloseHandle(ret);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
static VALUE
|
|
close_handle(VALUE h)
|
|
{
|
|
CloseHandle((HANDLE)h);
|
|
return Qfalse;
|
|
}
|
|
|
|
struct w32_io_info_args {
|
|
VALUE *fname;
|
|
w32_io_info_t *st;
|
|
};
|
|
|
|
static VALUE
|
|
call_w32_io_info(VALUE arg)
|
|
{
|
|
struct w32_io_info_args *p = (void *)arg;
|
|
return (VALUE)w32_io_info(p->fname, p->st);
|
|
}
|
|
|
|
VALUE
|
|
rb_w32_file_identical_p(VALUE fname1, VALUE fname2)
|
|
{
|
|
w32_io_info_t st1, st2;
|
|
HANDLE f1 = 0, f2 = 0;
|
|
|
|
f1 = w32_io_info(&fname1, &st1);
|
|
if (f1 == INVALID_HANDLE_VALUE) return Qfalse;
|
|
if (f1) {
|
|
struct w32_io_info_args arg;
|
|
arg.fname = &fname2;
|
|
arg.st = &st2;
|
|
f2 = (HANDLE)rb_ensure(call_w32_io_info, (VALUE)&arg, close_handle, (VALUE)f1);
|
|
}
|
|
else {
|
|
f2 = w32_io_info(&fname2, &st2);
|
|
}
|
|
if (f2 == INVALID_HANDLE_VALUE) return Qfalse;
|
|
if (f2) CloseHandle(f2);
|
|
|
|
if (st1.file_id_p != st2.file_id_p) return Qfalse;
|
|
if (!st1.file_id_p) {
|
|
if (st1.info.bhfi.dwVolumeSerialNumber == st2.info.bhfi.dwVolumeSerialNumber &&
|
|
st1.info.bhfi.nFileIndexHigh == st2.info.bhfi.nFileIndexHigh &&
|
|
st1.info.bhfi.nFileIndexLow == st2.info.bhfi.nFileIndexLow)
|
|
return Qtrue;
|
|
}
|
|
else {
|
|
if (st1.info.fii.VolumeSerialNumber == st2.info.fii.VolumeSerialNumber &&
|
|
memcmp(&st1.info.fii.FileId, &st2.info.fii.FileId, sizeof(FILE_ID_128)) == 0)
|
|
return Qtrue;
|
|
}
|
|
return Qfalse;
|
|
}
|
|
|
|
int
|
|
rb_w32_set_thread_description(HANDLE th, const WCHAR *name)
|
|
{
|
|
int result = FALSE;
|
|
typedef HRESULT (WINAPI *set_thread_description_func)(HANDLE, PCWSTR);
|
|
static set_thread_description_func set_thread_description =
|
|
(set_thread_description_func)-1;
|
|
if (set_thread_description == (set_thread_description_func)-1) {
|
|
/* Since Windows 10, version 1607 and Windows Server 2016 */
|
|
set_thread_description = (set_thread_description_func)
|
|
get_proc_address("kernel32", "SetThreadDescription", NULL);
|
|
}
|
|
if (set_thread_description) {
|
|
result = set_thread_description(th, name);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int
|
|
rb_w32_set_thread_description_str(HANDLE th, VALUE name)
|
|
{
|
|
int idx, result = FALSE;
|
|
WCHAR *s;
|
|
|
|
if (NIL_P(name)) {
|
|
return rb_w32_set_thread_description(th, L"");
|
|
}
|
|
s = (WCHAR *)StringValueCStr(name);
|
|
idx = rb_enc_get_index(name);
|
|
if (idx == ENCINDEX_UTF_16LE) {
|
|
result = rb_w32_set_thread_description(th, s);
|
|
}
|
|
else {
|
|
name = rb_str_conv_enc(name, rb_enc_from_index(idx), rb_utf8_encoding());
|
|
s = mbstr_to_wstr(CP_UTF8, RSTRING_PTR(name), RSTRING_LEN(name)+1, NULL);
|
|
result = rb_w32_set_thread_description(th, s);
|
|
free(s);
|
|
}
|
|
RB_GC_GUARD(name);
|
|
return result;
|
|
}
|
|
|
|
VALUE (*const rb_f_notimplement_)(int, const VALUE *, VALUE, VALUE) = rb_f_notimplement;
|
|
|
|
#if RUBY_MSVCRT_VERSION < 120
|
|
#include "missing/nextafter.c"
|
|
#endif
|
|
|
|
void *
|
|
rb_w32_mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
|
|
{
|
|
void *ptr;
|
|
//DWORD protect = 0;
|
|
DWORD protect = PAGE_EXECUTE_READWRITE;
|
|
|
|
if (fd > 0 || offset) {
|
|
/* not supported */
|
|
errno = EINVAL;
|
|
return MAP_FAILED;
|
|
}
|
|
|
|
/*
|
|
if (prot & PROT_EXEC) {
|
|
if (prot & PROT_WRITE) protect = PAGE_EXECUTE_READWRITE;
|
|
else if (prot & PROT_READ) protect = PAGE_EXECUTE_READ;
|
|
else protect = PAGE_EXECUTE;
|
|
}
|
|
else if (prot & PROT_WRITE) protect = PAGE_READWRITE;
|
|
else if (prot & PROT_READ) protect = PAGE_READONLY;
|
|
*/
|
|
ptr = VirtualAlloc(addr, len, MEM_RESERVE | MEM_COMMIT, protect);
|
|
if (!ptr) {
|
|
errno = rb_w32_map_errno(GetLastError());
|
|
return MAP_FAILED;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
int
|
|
rb_w32_munmap(void *addr, size_t len)
|
|
{
|
|
if (!VirtualFree(addr, 0, MEM_RELEASE)) {
|
|
errno = rb_w32_map_errno(GetLastError());
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
inline int
|
|
rb_w32_mprotect(void *addr, size_t len, int prot)
|
|
{
|
|
/*
|
|
DWORD protect = 0;
|
|
if (prot & PROT_EXEC) {
|
|
if (prot & PROT_WRITE) protect = PAGE_EXECUTE_READWRITE;
|
|
else if (prot & PROT_READ) protect = PAGE_EXECUTE_READ;
|
|
else protect = PAGE_EXECUTE;
|
|
}
|
|
else if (prot & PROT_WRITE) protect = PAGE_READWRITE;
|
|
else if (prot & PROT_READ) protect = PAGE_READONLY;
|
|
if (!VirtualProtect(addr, len, protect, NULL)) {
|
|
errno = rb_w32_map_errno(GetLastError());
|
|
return -1;
|
|
}
|
|
*/
|
|
if (prot & PROT_EXEC) {
|
|
if (!FlushInstructionCache(GetCurrentProcess(), addr, len)) {
|
|
errno = rb_w32_map_errno(GetLastError());
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|