diff options
Diffstat (limited to 'win32/win32.c')
-rw-r--r-- | win32/win32.c | 525 |
1 files changed, 405 insertions, 120 deletions
diff --git a/win32/win32.c b/win32/win32.c index edf89be4b1..c51d53595f 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -49,6 +49,9 @@ #ifdef __MINGW32__ #include <mswsock.h> #endif +#ifdef HAVE_AFUNIX_H +# include <afunix.h> +#endif #include "ruby/win32.h" #include "ruby/vm.h" #include "win32/dir.h" @@ -541,7 +544,7 @@ rb_w32_system_tmpdir(WCHAR *path, UINT len) afterwards with xfree. Try: - HOME, HOMEDRIVE + HOMEPATH and USERPROFILE environment variables + HOME, USERPROFILE, HOMEDRIVE + HOMEPATH environment variables Special Folders - Profile and Personal */ WCHAR * @@ -550,13 +553,17 @@ 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_NONE, ENV_HOME, ENV_USERPROFILE, ENV_DRIVEPATH } home_type = HOME_NONE; if ((len = GetEnvironmentVariableW(L"HOME", NULL, 0)) != 0) { buffer_len = len; home_type = ENV_HOME; } + else if ((len = GetEnvironmentVariableW(L"USERPROFILE", NULL, 0)) != 0) { + buffer_len = len; + home_type = ENV_USERPROFILE; + } else if ((len = GetEnvironmentVariableW(L"HOMEDRIVE", NULL, 0)) != 0) { buffer_len = len; if ((len = GetEnvironmentVariableW(L"HOMEPATH", NULL, 0)) != 0) { @@ -564,10 +571,6 @@ rb_w32_home_dir(void) 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); @@ -576,13 +579,13 @@ rb_w32_home_dir(void) case ENV_HOME: GetEnvironmentVariableW(L"HOME", buffer, buffer_len); break; + case ENV_USERPROFILE: + GetEnvironmentVariableW(L"USERPROFILE", 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)) { @@ -619,21 +622,24 @@ init_env(void) 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))) { + if (GetEnvironmentVariableW(L"USERPROFILE", env, numberof(env))) { f = TRUE; } - else if (get_special_folder(CSIDL_PERSONAL, env, numberof(env))) { - f = TRUE; + else { + 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 (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); @@ -678,7 +684,10 @@ invalid_parameter(const wchar_t *expr, const wchar_t *func, const wchar_t *file, int ruby_w32_rtc_error; +# ifndef __MINGW32__ /* License: Ruby's */ +RBIMPL_ATTR_NONNULL((5)) +RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 5, 6) static int __cdecl rtc_error_handler(int e, const char *src, int line, const char *exe, const char *fmt, ...) { @@ -694,6 +703,7 @@ rtc_error_handler(int e, const char *src, int line, const char *exe, const char rb_write_error2(RSTRING_PTR(str), RSTRING_LEN(str)); return 0; } +# endif #endif static CRITICAL_SECTION select_mutex; @@ -1345,10 +1355,10 @@ is_batch(const char *cmd) #define wstr_to_utf8(str, plen) wstr_to_mbstr(CP_UTF8, str, -1, plen) /* License: Ruby's */ -MJIT_FUNC_EXPORTED HANDLE +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 + /* NOTE: This function is used by RJIT 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. */ @@ -1416,18 +1426,20 @@ w32_spawn(int mode, const char *cmd, const char *prog, UINT cp) 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); + size_t cmd_len = strlen(cmd) + sizeof(" -c ") + 2; + char *tmp = ALLOCV(v, shell_len + cmd_len); memcpy(tmp, shell, shell_len + 1); translate_char(tmp, '/', '\\', cp); - sprintf(tmp + shell_len, " -c \"%s\"", cmd); + snprintf(tmp + shell_len, cmd_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); + size_t cmd_len = strlen(shell) + strlen(cmd) + sizeof(" /c ") + (nt ? 2 : 0); + char *tmp = ALLOCV(v, cmd_len); + snprintf(tmp, cmd_len, nt ? "%s /c \"%s\"" : "%s /c %s", shell, cmd); cmd = tmp; } else { @@ -1528,7 +1540,8 @@ rb_w32_uspawn(int mode, const char *cmd, const char *prog) /* License: Artistic or GPL */ static rb_pid_t -w32_aspawn_flags(int mode, const char *prog, char *const *argv, DWORD flags, UINT cp) +w32_spawn_process(int mode, const char *prog, char *const *argv, + int in_fd, int out_fd, int err_fd, DWORD flags, UINT cp) { int c_switch = 0; size_t len; @@ -1539,9 +1552,20 @@ w32_aspawn_flags(int mode, const char *prog, char *const *argv, DWORD flags, UIN int e = 0; rb_pid_t ret = -1; VALUE v = 0; + HANDLE in_handle = NULL, out_handle = NULL, err_handle = NULL; if (check_spawn_mode(mode)) return -1; + if (in_fd >= 0) { + in_handle = (HANDLE)rb_w32_get_osfhandle(in_fd); + } + if (out_fd >= 0) { + out_handle = (HANDLE)rb_w32_get_osfhandle(out_fd); + } + if (err_fd >= 0) { + err_handle = (HANDLE)rb_w32_get_osfhandle(err_fd); + } + if (!prog) prog = argv[0]; if ((shell = w32_getenv("COMSPEC", cp)) && internal_cmd_match(prog, tmpnt = !is_command_com(shell))) { @@ -1589,7 +1613,7 @@ w32_aspawn_flags(int mode, const char *prog, char *const *argv, DWORD flags, UIN if (!e) { struct ChildRecord *child = FindFreeChildSlot(); - if (CreateChild(child, wcmd, wprog, NULL, NULL, NULL, flags)) { + if (CreateChild(child, wcmd, wprog, in_handle, out_handle, err_handle, flags)) { ret = child_result(child, mode); } } @@ -1604,21 +1628,21 @@ 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()); + return w32_spawn_process(mode, prog, argv, -1, -1, -1, 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); + return w32_spawn_process(mode, prog, argv, -1, -1, -1, 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()); + return w32_spawn_process(mode, prog, argv, -1, -1, -1, 0, filecp()); } /* License: Ruby's */ @@ -1628,6 +1652,15 @@ rb_w32_uaspawn(int mode, const char *prog, char *const *argv) return rb_w32_uaspawn_flags(mode, prog, argv, 0); } +/* License: Ruby's */ +rb_pid_t +rb_w32_uspawn_process(int mode, const char *prog, char *const *argv, + int in_fd, int out_fd, int err_fd, DWORD flags) +{ + return w32_spawn_process(mode, prog, argv, in_fd, out_fd, err_fd, + flags, CP_UTF8); +} + /* License: Artistic or GPL */ typedef struct _NtCmdLineElement { struct _NtCmdLineElement *next; @@ -2213,7 +2246,7 @@ rb_w32_wstr_to_mbstr(UINT cp, const WCHAR *wstr, int clen, long *plen) 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. */ + /* This is used by RJIT 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; @@ -2360,10 +2393,8 @@ readdir_internal(DIR *dirp, BOOL (*conv)(const WCHAR *, const WCHAR *, struct di // // 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); + free(dirp->dirstr.d_name); + 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); @@ -2469,14 +2500,10 @@ 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->dirstr.d_name); + free(dirp->dirstr.d_altname); + free(dirp->start); + free(dirp->bits); free(dirp); } } @@ -2575,10 +2602,86 @@ set_pioinfo_extra(void) # define UCRTBASE "ucrtbase.dll" # endif /* get __pioinfo addr with _isatty */ + /* + * Why Ruby depends to _pioinfo is + * * to associate socket and fd: CRuby creates fd with dummy file handle + * and set socket to emulate Unix-like behavior. Without __pioinfo + * we need something which manages the fd number allocation + * * to implement overlapped I/O for Windows 2000/XP + * * to emulate fcntl(2) + * + * see also + * * https://bugs.ruby-lang.org/issues/11118 + * * https://bugs.ruby-lang.org/issues/18605 + */ char *p = (char*)get_proc_address(UCRTBASE, "_isatty", NULL); - char *pend = p; /* _osfile(fh) & FDEV */ +#ifdef _M_ARM64 +#define IS_INSN(pc, name) ((*(pc) & name##_mask) == name##_id) + const int max_num_inst = 500; + uint32_t *start = (uint32_t*)p; + uint32_t *end_limit = (start + max_num_inst); + uint32_t *pc = start; + + if (!p) { + fprintf(stderr, "_isatty proc not found in " UCRTBASE "\n"); + _exit(1); + } + + /* end of function */ + const uint32_t ret_id = 0xd65f0000; + const uint32_t ret_mask = 0xfffffc1f; + for(; pc < end_limit; pc++) { + if (IS_INSN(pc, ret)) { + break; + } + } + if (pc == end_limit) { + fprintf(stderr, "end of _isatty not found in " UCRTBASE "\n"); + _exit(1); + } + + /* pioinfo instruction mark */ + const uint32_t adrp_id = 0x90000000; + const uint32_t adrp_mask = 0x9f000000; + const uint32_t add_id = 0x11000000; + const uint32_t add_mask = 0x3fc00000; + for(; pc > start; pc--) { + if (IS_INSN(pc, adrp) && IS_INSN(pc + 1, add)) { + break; + } + } + if(pc == start) { + fprintf(stderr, "pioinfo mark not found in " UCRTBASE "\n"); + _exit(1); + } + + /* We now point to instructions that load address of __pioinfo: + * adrp x8, 0x1801d8000 + * add x8, x8, #0xdb0 + * https://devblogs.microsoft.com/oldnewthing/20220809-00/?p=106955 + * The last adrp/add sequence before ret is what we are looking for. + */ + const uint32_t adrp_insn = *pc; + const uint32_t adrp_immhi = (adrp_insn & 0x00ffffe0) >> 5; + const uint32_t adrp_immlo = (adrp_insn & 0x60000000) >> (5 + 19 + 5); + /* imm = immhi:immlo:Zeros(12), 64 */ + const uint64_t adrp_imm = ((adrp_immhi << 2) | adrp_immlo) << 12; + /* base = PC64<63:12>:Zeros(12) */ + const uint64_t adrp_base = (uint64_t)pc & 0xfffffffffffff000; + + const uint32_t add_insn = *(pc + 1); + const uint32_t add_sh = (add_insn & 0x400000) >> (12 + 5 + 5); + /* case sh of + when '0' imm = ZeroExtend(imm12, datasize); + when '1' imm = ZeroExtend(imm12:Zeros(12), datasize); */ + const uint64_t add_imm = ((add_insn & 0x3ffc00) >> (5 + 5)) << (add_sh ? 12 : 0); + + __pioinfo = (ioinfo**)(adrp_base + adrp_imm + add_imm); +#else /* _M_ARM64 */ + char *pend = p; + # ifdef _WIN64 int32_t rel; char *rip; @@ -2628,7 +2731,8 @@ set_pioinfo_extra(void) #else __pioinfo = *(ioinfo***)(p); #endif -#endif +#endif /* _M_ARM64 */ +#endif /* RUBY_MSVCRT_VERSION */ int fd; fd = _open("NUL", O_RDONLY); @@ -3102,7 +3206,12 @@ is_readable_console(SOCKET sock) /* call this for console only */ 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) { + ir.Event.KeyEvent.uChar.UnicodeChar) { + ret = 1; + } + else if (ir.EventType == KEY_EVENT && !ir.Event.KeyEvent.bKeyDown && + ir.Event.KeyEvent.wVirtualKeyCode == VK_MENU /* ALT key */ && + ir.Event.KeyEvent.uChar.UnicodeChar) { ret = 1; } else { @@ -4013,15 +4122,93 @@ rb_w32_getservbyport(int port, const char *proto) return r; } +#ifdef HAVE_AFUNIX_H + +/* License: Ruby's */ +static size_t +socketpair_unix_path(struct sockaddr_un *sock_un) +{ + SOCKET listener; + WCHAR wpath[sizeof(sock_un->sun_path)/sizeof(*sock_un->sun_path)] = L""; + + /* AF_UNIX/SOCK_STREAM became available in Windows 10 + * See https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows + */ + listener = socket(AF_UNIX, SOCK_STREAM, 0); + if (listener == INVALID_SOCKET) + return 0; + + memset(sock_un, 0, sizeof(*sock_un)); + sock_un->sun_family = AF_UNIX; + + /* Abstract sockets (filesystem-independent) don't work, contrary to + * the claims of the aforementioned blog post: + * https://github.com/microsoft/WSL/issues/4240#issuecomment-549663217 + * + * So we must use a named path, and that comes with all the attendant + * problems of permissions and collisions. Trying various temporary + * directories and putting high-res time and PID in the filename. + */ + for (int try = 0; ; try++) { + LARGE_INTEGER ticks; + size_t path_len = 0; + const size_t maxpath = sizeof(sock_un->sun_path)/sizeof(*sock_un->sun_path); + + switch (try) { + case 0: + /* user temp dir from TMP or TEMP env var, it ends with a backslash */ + path_len = GetTempPathW(maxpath, wpath); + break; + case 1: + wcsncpy(wpath, L"C:/Temp/", maxpath); + path_len = lstrlenW(wpath); + break; + case 2: + /* Current directory */ + path_len = 0; + break; + case 3: + closesocket(listener); + return 0; + } + + /* Windows UNIXSocket implementation expects UTF-8 instead of UTF16 */ + path_len = WideCharToMultiByte(CP_UTF8, 0, wpath, path_len, sock_un->sun_path, maxpath, NULL, NULL); + QueryPerformanceCounter(&ticks); + path_len += snprintf(sock_un->sun_path + path_len, + maxpath - path_len, + "%lld-%ld.($)", + ticks.QuadPart, + GetCurrentProcessId()); + + /* Convert to UTF16 for DeleteFileW */ + MultiByteToWideChar(CP_UTF8, 0, sock_un->sun_path, -1, wpath, sizeof(wpath)/sizeof(*wpath)); + + if (bind(listener, (struct sockaddr *)sock_un, sizeof(*sock_un)) != SOCKET_ERROR) + break; + } + closesocket(listener); + DeleteFileW(wpath); + return sizeof(*sock_un); +} +#endif + /* 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 + +#ifdef HAVE_AFUNIX_H + struct sockaddr_un sock_un = {0, {0}}; + WCHAR wpath[sizeof(sock_un.sun_path)/sizeof(*sock_un.sun_path)] = L""; +#endif + struct sockaddr *addr; int ret = -1; int len; @@ -4046,6 +4233,15 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv) len = sizeof(sock_in6); break; #endif +#ifdef HAVE_AFUNIX_H + case AF_UNIX: + addr = (struct sockaddr *)&sock_un; + len = socketpair_unix_path(&sock_un); + MultiByteToWideChar(CP_UTF8, 0, sock_un.sun_path, -1, wpath, sizeof(wpath)/sizeof(*wpath)); + if (len) + break; + /* fall through */ +#endif default: errno = EAFNOSUPPORT; return -1; @@ -4096,6 +4292,10 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv) } if (svr != INVALID_SOCKET) closesocket(svr); +#ifdef HAVE_AFUNIX_H + if (sock_un.sun_family == AF_UNIX) + DeleteFileW(wpath); +#endif } return ret; @@ -4261,8 +4461,8 @@ 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->ifa_addr); + ruby_xfree(ifp->ifa_name); ruby_xfree(ifp); ifp = next; } @@ -5139,32 +5339,35 @@ rb_w32_read_reparse_point(const WCHAR *path, rb_w32_reparse_buffer_t *rp, static ssize_t w32_readlink(UINT cp, const char *path, char *buf, size_t bufsize) { - VALUE wtmp; + VALUE rp_buf, rp_buf_bigger = 0; 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); + size_t size = rb_w32_reparse_buffer_size(bufsize); + WCHAR *wname; + WCHAR *wpath = ALLOCV(rp_buf, sizeof(WCHAR) * len + size); 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); + if (e == ERROR_MORE_DATA) { + size = rb_w32_reparse_buffer_size(len + 1); + rp = ALLOCV(rp_buf_bigger, size); + e = rb_w32_read_reparse_point(wpath, rp, size, &wname, &len); + } + if (e) { + ALLOCV_END(rp_buf); + ALLOCV_END(rp_buf_bigger); + errno = e == -1 ? EINVAL : map_errno(e); return -1; } - len = lstrlenW(wname) + 1; + len = lstrlenW(wname); ret = WideCharToMultiByte(cp, 0, wname, len, buf, bufsize, NULL, NULL); - ALLOCV_END(wtmp); - if (e) { + ALLOCV_END(rp_buf); + ALLOCV_END(rp_buf_bigger); + if (!ret) { ret = bufsize; } - else if (!ret) { - e = GetLastError(); - errno = map_errno(e); - ret = -1; - } return ret; } @@ -5624,10 +5827,8 @@ fileattr_to_unixmode(DWORD attr, const WCHAR *path, unsigned mode) /* format is already set */ } else if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { - if (rb_w32_reparse_symlink_p(path)) - mode |= S_IFLNK | S_IEXEC; - else - mode |= S_IFDIR | S_IEXEC; + /* Only used by stat_by_find in the case the file can not be opened. + * In this case we can't get more details. */ } else if (attr & FILE_ATTRIBUTE_DIRECTORY) { mode |= S_IFDIR | S_IEXEC; @@ -5702,14 +5903,6 @@ 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); @@ -5745,9 +5938,24 @@ 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]; + int open_error; memset(st, 0, sizeof(*st)); f = open_special(path, 0, flags); + open_error = GetLastError(); + if (f == INVALID_HANDLE_VALUE && !lstat) { + /* Support stat (not only lstat) of UNIXSocket */ + FILE_ATTRIBUTE_TAG_INFO attr_info; + DWORD e; + + f = open_special(path, 0, FILE_FLAG_OPEN_REPARSE_POINT); + e = GetFileInformationByHandleEx( f, FileAttributeTagInfo, + &attr_info, sizeof(attr_info)); + if (!e || attr_info.ReparseTag != IO_REPARSE_TAG_AF_UNIX) { + CloseHandle(f); + f = INVALID_HANDLE_VALUE; + } + } if (f != INVALID_HANDLE_VALUE) { DWORD attr = stati128_handle(f, st); const DWORD len = get_final_path(f, finalname, numberof(finalname), 0); @@ -5759,15 +5967,28 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) case FILE_TYPE_PIPE: mode = S_IFIFO; break; + default: + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + FILE_ATTRIBUTE_TAG_INFO attr_info; + DWORD e; + + e = GetFileInformationByHandleEx( f, FileAttributeTagInfo, + &attr_info, sizeof(attr_info)); + if (e && attr_info.ReparseTag == IO_REPARSE_TAG_AF_UNIX) { + st->st_size = 0; + mode |= S_IFSOCK; + } + else if (rb_w32_reparse_symlink_p(path)) { + /* TODO: size in which encoding? */ + st->st_size = 0; + mode |= S_IFLNK | S_IEXEC; + } + else { + mode |= S_IFDIR | S_IEXEC; + } + } } 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; } @@ -5780,6 +6001,12 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) } } else { + if ((open_error == ERROR_FILE_NOT_FOUND) || (open_error == ERROR_INVALID_NAME) + || (open_error == ERROR_PATH_NOT_FOUND || (open_error == ERROR_BAD_NETPATH))) { + errno = map_errno(open_error); + return -1; + } + if (stat_by_find(path, st)) return -1; } @@ -5899,8 +6126,8 @@ rb_w32_lstati128(const char *path, struct stati128 *st) } /* License: Ruby's */ -off_t -rb_w32_lseek(int fd, off_t ofs, int whence) +rb_off_t +rb_w32_lseek(int fd, rb_off_t ofs, int whence) { SOCKET sock = TO_SOCKET(fd); if (is_socket(sock) || is_pipe(sock)) { @@ -5941,7 +6168,7 @@ rb_w32_uaccess(const char *path, int mode) /* License: Ruby's */ static int -rb_chsize(HANDLE h, off_t size) +rb_chsize(HANDLE h, rb_off_t size) { long upos, lpos, usize, lsize; int ret = -1; @@ -5970,7 +6197,7 @@ rb_chsize(HANDLE h, off_t size) /* License: Ruby's */ static int -w32_truncate(const char *path, off_t length, UINT cp) +w32_truncate(const char *path, rb_off_t length, UINT cp) { HANDLE h; int ret; @@ -5992,21 +6219,21 @@ w32_truncate(const char *path, off_t length, UINT cp) /* License: Ruby's */ int -rb_w32_utruncate(const char *path, off_t length) +rb_w32_utruncate(const char *path, rb_off_t length) { return w32_truncate(path, length, CP_UTF8); } /* License: Ruby's */ int -rb_w32_truncate(const char *path, off_t length) +rb_w32_truncate(const char *path, rb_off_t length) { return w32_truncate(path, length, filecp()); } /* License: Ruby's */ int -rb_w32_ftruncate(int fd, off_t length) +rb_w32_ftruncate(int fd, rb_off_t length) { HANDLE h; @@ -7037,21 +7264,43 @@ rb_w32_close(int fd) return 0; } +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif + static int -setup_overlapped(OVERLAPPED *ol, int fd, int iswrite) +setup_overlapped(OVERLAPPED *ol, int fd, int iswrite, rb_off_t *_offset) { memset(ol, 0, sizeof(*ol)); - if (!(_osfile(fd) & (FDEV | FPIPE))) { + + // 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 seek_method = ((_osfile(fd) & FAPPEND) && iswrite) ? FILE_END : FILE_CURRENT; + + if (_offset) { + // Explicit offset was provided (pread/pwrite) - use it: + uint64_t offset = *_offset; + ol->Offset = (uint32_t)(offset & 0xFFFFFFFFLL); + ol->OffsetHigh = (uint32_t)((offset & 0xFFFFFFFF00000000LL) >> 32); + + // Update _offset with the current offset: + LARGE_INTEGER seek_offset = {0}, current_offset = {0}; + if (!SetFilePointerEx((HANDLE)_osfhnd(fd), seek_offset, ¤t_offset, seek_method)) { + DWORD last_error = GetLastError(); + if (last_error != NO_ERROR) { + errno = map_errno(last_error); + return -1; + } + } + + // As we need to restore the current offset later, we save it here: + *_offset = current_offset.QuadPart; + } + else 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 + DWORD low = SetFilePointer((HANDLE)_osfhnd(fd), 0, &high, seek_method); + if (low == INVALID_SET_FILE_POINTER) { DWORD err = GetLastError(); if (err != NO_ERROR) { @@ -7059,9 +7308,11 @@ setup_overlapped(OVERLAPPED *ol, int fd, int iswrite) return -1; } } + ol->Offset = low; ol->OffsetHigh = high; } + ol->hEvent = CreateEvent(NULL, TRUE, TRUE, NULL); if (!ol->hEvent) { errno = map_errno(GetLastError()); @@ -7071,11 +7322,22 @@ setup_overlapped(OVERLAPPED *ol, int fd, int iswrite) } static void -finish_overlapped(OVERLAPPED *ol, int fd, DWORD size) +finish_overlapped(OVERLAPPED *ol, int fd, DWORD size, rb_off_t *_offset) { CloseHandle(ol->hEvent); - if (!(_osfile(fd) & (FDEV | FPIPE))) { + if (_offset) { + // If we were doing a `pread`/`pwrite`, we need to restore the current that was saved in setup_overlapped: + DWORD seek_method = (_osfile(fd) & FAPPEND) ? FILE_END : FILE_BEGIN; + + LARGE_INTEGER seek_offset = {0}; + if (seek_method == FILE_BEGIN) { + seek_offset.QuadPart = *_offset; + } + + SetFilePointerEx((HANDLE)_osfhnd(fd), seek_offset, NULL, seek_method); + } + else if (!(_osfile(fd) & (FDEV | FPIPE))) { LONG high = ol->OffsetHigh; DWORD low = ol->Offset + size; if (low < ol->Offset) @@ -7086,8 +7348,8 @@ finish_overlapped(OVERLAPPED *ol, int fd, DWORD size) #undef read /* License: Ruby's */ -ssize_t -rb_w32_read(int fd, void *buf, size_t size) +static ssize_t +rb_w32_read_internal(int fd, void *buf, size_t size, rb_off_t *offset) { SOCKET sock = TO_SOCKET(fd); DWORD read; @@ -7108,7 +7370,7 @@ rb_w32_read(int fd, void *buf, size_t size) return -1; } - if (_osfile(fd) & FTEXT) { + if (!offset && _osfile(fd) & FTEXT) { return _read(fd, buf, size); } @@ -7142,7 +7404,7 @@ rb_w32_read(int fd, void *buf, size_t size) len = size; size -= len; - if (setup_overlapped(&ol, fd, FALSE)) { + if (setup_overlapped(&ol, fd, FALSE, offset)) { rb_acrt_lowio_unlock_fh(fd); return -1; } @@ -7205,7 +7467,7 @@ rb_w32_read(int fd, void *buf, size_t size) errno = map_errno(err); } - finish_overlapped(&ol, fd, read); + finish_overlapped(&ol, fd, read, offset); ret += read; if (read >= len) { @@ -7225,8 +7487,8 @@ rb_w32_read(int fd, void *buf, size_t size) #undef write /* License: Ruby's */ -ssize_t -rb_w32_write(int fd, const void *buf, size_t size) +static ssize_t +rb_w32_write_internal(int fd, const void *buf, size_t size, rb_off_t *offset) { SOCKET sock = TO_SOCKET(fd); DWORD written; @@ -7244,7 +7506,8 @@ rb_w32_write(int fd, const void *buf, size_t size) return -1; } - if ((_osfile(fd) & FTEXT) && + // If an offset is given, we can't use `_write`. + if (!offset && (_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) { @@ -7266,7 +7529,8 @@ rb_w32_write(int fd, const void *buf, size_t size) size -= len; retry2: - if (setup_overlapped(&ol, fd, TRUE)) { + // Provide the requested offset. + if (setup_overlapped(&ol, fd, TRUE, offset)) { rb_acrt_lowio_unlock_fh(fd); return -1; } @@ -7305,7 +7569,7 @@ rb_w32_write(int fd, const void *buf, size_t size) } } - finish_overlapped(&ol, fd, written); + finish_overlapped(&ol, fd, written, offset); ret += written; if (written == len) { @@ -7329,6 +7593,30 @@ rb_w32_write(int fd, const void *buf, size_t size) return ret; } +ssize_t +rb_w32_read(int fd, void *buf, size_t size) +{ + return rb_w32_read_internal(fd, buf, size, NULL); +} + +ssize_t +rb_w32_write(int fd, const void *buf, size_t size) +{ + return rb_w32_write_internal(fd, buf, size, NULL); +} + +ssize_t +rb_w32_pread(int descriptor, void *base, size_t size, rb_off_t offset) +{ + return rb_w32_read_internal(descriptor, base, size, &offset); +} + +ssize_t +rb_w32_pwrite(int descriptor, const void *base, size_t size, rb_off_t offset) +{ + return rb_w32_write_internal(descriptor, base, size, &offset); +} + /* License: Ruby's */ long rb_w32_write_console(uintptr_t strarg, int fd) @@ -7388,7 +7676,7 @@ rb_w32_write_console(uintptr_t strarg, int fd) } } RB_GC_GUARD(str); - if (wbuffer) free(wbuffer); + free(wbuffer); return (long)reslen; } @@ -8064,10 +8352,7 @@ w32_io_info(VALUE *file, w32_io_info_t *st) 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); + f = (HANDLE)rb_w32_get_osfhandle(rb_io_descriptor(tmp)); if (f == (HANDLE)-1) return INVALID_HANDLE_VALUE; } else { @@ -8214,7 +8499,7 @@ VALUE (*const rb_f_notimplement_)(int, const VALUE *, VALUE, VALUE) = rb_f_notim #endif void * -rb_w32_mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) +rb_w32_mmap(void *addr, size_t len, int prot, int flags, int fd, rb_off_t offset) { void *ptr; //DWORD protect = 0; |