/* ** io_hal.c - Windows HAL implementation for mruby-io ** ** See Copyright Notice in mruby.h ** ** Windows implementation for I/O operations using Win32 APIs. ** Supported platforms: Windows, MinGW */ #include #include "io_hal.h" #include #include #include #include #include #include #include #include #include #include /* Maximum path length */ #ifndef PATH_MAX #define PATH_MAX 4096 #endif /* * Helper Functions */ /* Convert Windows struct _stat64 to mrb_io_stat */ static void convert_stat(const struct _stat64 *src, mrb_io_stat *dst) { dst->st_dev = (uint64_t)src->st_dev; dst->st_ino = (uint64_t)src->st_ino; dst->st_mode = (uint32_t)src->st_mode; dst->st_nlink = (uint32_t)src->st_nlink; dst->st_uid = 0; /* Windows doesn't have Unix-style UIDs */ dst->st_gid = 0; /* Windows doesn't have Unix-style GIDs */ dst->st_rdev = (uint64_t)src->st_rdev; dst->st_size = (int64_t)src->st_size; dst->st_atime = (int64_t)src->st_atime; dst->st_mtime = (int64_t)src->st_mtime; dst->st_ctime = (int64_t)src->st_ctime; dst->st_blksize = 512; dst->st_blocks = (dst->st_size + 511) / 512; } /* Convert errno to Windows errno */ static void set_errno_from_win_error(DWORD error) { switch (error) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: errno = ENOENT; break; case ERROR_ACCESS_DENIED: errno = EACCES; break; case ERROR_NOT_ENOUGH_MEMORY: errno = ENOMEM; break; case ERROR_INVALID_HANDLE: errno = EBADF; break; case ERROR_ALREADY_EXISTS: errno = EEXIST; break; default: errno = EIO; break; } } /* * File Operations */ int mrb_hal_io_stat(mrb_state *mrb, const char *path, mrb_io_stat *st) { struct _stat64 s; (void)mrb; if (_stat64(path, &s) == -1) { return -1; } convert_stat(&s, st); return 0; } int mrb_hal_io_fstat(mrb_state *mrb, int fd, mrb_io_stat *st) { struct _stat64 s; (void)mrb; if (_fstat64(fd, &s) == -1) { return -1; } convert_stat(&s, st); return 0; } int mrb_hal_io_lstat(mrb_state *mrb, const char *path, mrb_io_stat *st) { /* Windows doesn't distinguish lstat from stat */ return mrb_hal_io_stat(mrb, path, st); } int mrb_hal_io_chmod(mrb_state *mrb, const char *path, uint32_t mode) { (void)mrb; return _chmod(path, (int)mode); } uint32_t mrb_hal_io_umask(mrb_state *mrb, int32_t mask) { int old; (void)mrb; if (mask < 0) { /* Just query current value */ old = _umask(0); _umask(old); } else { old = _umask((int)mask); } return (uint32_t)old; } int mrb_hal_io_ftruncate(mrb_state *mrb, int fd, int64_t length) { (void)mrb; return _chsize_s(fd, length); } int mrb_hal_io_flock(mrb_state *mrb, int fd, int operation) { HANDLE h; OVERLAPPED overlapped; DWORD flags = 0; (void)mrb; h = (HANDLE)_get_osfhandle(fd); if (h == INVALID_HANDLE_VALUE) { errno = EBADF; return -1; } memset(&overlapped, 0, sizeof(overlapped)); if (operation & MRB_IO_LOCK_UN) { if (!UnlockFileEx(h, 0, MAXDWORD, MAXDWORD, &overlapped)) { set_errno_from_win_error(GetLastError()); return -1; } return 0; } if (operation & MRB_IO_LOCK_EX) { flags |= LOCKFILE_EXCLUSIVE_LOCK; } if (operation & MRB_IO_LOCK_NB) { flags |= LOCKFILE_FAIL_IMMEDIATELY; } if (!LockFileEx(h, flags, 0, MAXDWORD, MAXDWORD, &overlapped)) { set_errno_from_win_error(GetLastError()); return -1; } return 0; } int mrb_hal_io_unlink(mrb_state *mrb, const char *path) { (void)mrb; return _unlink(path); } int mrb_hal_io_rename(mrb_state *mrb, const char *oldpath, const char *newpath) { (void)mrb; return rename(oldpath, newpath); } int mrb_hal_io_symlink(mrb_state *mrb, const char *target, const char *linkpath) { (void)target; (void)linkpath; /* Symlinks require special privileges on Windows */ mrb_raise(mrb, E_NOTIMP_ERROR, "symlink is not supported on Windows"); return -1; /* not reached */ } int64_t mrb_hal_io_readlink(mrb_state *mrb, const char *path, char *buf, size_t bufsize) { (void)path; (void)buf; (void)bufsize; /* Symlinks require special handling on Windows */ mrb_raise(mrb, E_NOTIMP_ERROR, "readlink is not supported on Windows"); return -1; /* not reached */ } char* mrb_hal_io_realpath(mrb_state *mrb, const char *path, char *resolved) { DWORD ret; (void)mrb; ret = GetFullPathName(path, PATH_MAX, resolved, NULL); if (ret == 0 || ret >= PATH_MAX) { set_errno_from_win_error(GetLastError()); return NULL; } return resolved; } char* mrb_hal_io_getcwd(mrb_state *mrb, char *buf, size_t size) { (void)mrb; return _getcwd(buf, (int)size); } const char* mrb_hal_io_getenv(mrb_state *mrb, const char *name) { (void)mrb; return getenv(name); } const char* mrb_hal_io_gethome(mrb_state *mrb, const char *username) { const char *home; (void)mrb; if (username != NULL && *username != '\0') { /* Windows doesn't have a simple way to get other users' home directories */ errno = ENOSYS; return NULL; } /* Try USERPROFILE first, then HOMEDRIVE+HOMEPATH */ home = getenv("USERPROFILE"); if (home == NULL) { const char *homedrive = getenv("HOMEDRIVE"); const char *homepath = getenv("HOMEPATH"); if (homedrive && homepath) { static char homebuf[PATH_MAX]; snprintf(homebuf, PATH_MAX, "%s%s", homedrive, homepath); return homebuf; } errno = ENOENT; return NULL; } return home; } /* * Core I/O Operations */ int mrb_hal_io_open(mrb_state *mrb, const char *path, int flags, uint32_t mode) { int fd; (void)mrb; /* Windows uses _open with slightly different flags */ fd = _open(path, flags | _O_BINARY, (int)mode); if (fd == -1) { return -1; } /* Set close-on-exec for non-standard descriptors */ if (fd > 2) { SetHandleInformation((HANDLE)_get_osfhandle(fd), HANDLE_FLAG_INHERIT, 0); } return fd; } int mrb_hal_io_close(mrb_state *mrb, int fd) { (void)mrb; return _close(fd); } int64_t mrb_hal_io_read(mrb_state *mrb, int fd, void *buf, size_t count) { int n; (void)mrb; n = _read(fd, buf, (unsigned int)count); return (int64_t)n; } int64_t mrb_hal_io_write(mrb_state *mrb, int fd, const void *buf, size_t count) { int n; (void)mrb; n = _write(fd, buf, (unsigned int)count); return (int64_t)n; } int64_t mrb_hal_io_lseek(mrb_state *mrb, int fd, int64_t offset, int whence) { __int64 pos; int win_whence; (void)mrb; /* Convert MRB_IO_SEEK_* to Windows SEEK_* */ switch (whence) { case MRB_IO_SEEK_SET: win_whence = SEEK_SET; break; case MRB_IO_SEEK_CUR: win_whence = SEEK_CUR; break; case MRB_IO_SEEK_END: win_whence = SEEK_END; break; default: errno = EINVAL; return -1; } pos = _lseeki64(fd, (__int64)offset, win_whence); return (int64_t)pos; } int mrb_hal_io_dup(mrb_state *mrb, int fd) { int new_fd; (void)mrb; new_fd = _dup(fd); if (new_fd == -1) { return -1; } /* Set close-on-exec */ if (new_fd > 2) { SetHandleInformation((HANDLE)_get_osfhandle(new_fd), HANDLE_FLAG_INHERIT, 0); } return new_fd; } int mrb_hal_io_fcntl(mrb_state *mrb, int fd, int cmd, int arg) { /* Windows has limited fcntl support */ (void)mrb; (void)fd; (void)cmd; (void)arg; errno = ENOSYS; return -1; } int mrb_hal_io_isatty(mrb_state *mrb, int fd) { (void)mrb; return _isatty(fd) ? 1 : 0; } int mrb_hal_io_pipe(mrb_state *mrb, int fds[2]) { int ret; (void)mrb; ret = _pipe(fds, 4096, _O_BINARY); if (ret == -1) { return -1; } /* Set close-on-exec on both ends */ SetHandleInformation((HANDLE)_get_osfhandle(fds[0]), HANDLE_FLAG_INHERIT, 0); SetHandleInformation((HANDLE)_get_osfhandle(fds[1]), HANDLE_FLAG_INHERIT, 0); return 0; } /* * Process Operations */ int mrb_hal_io_spawn_process(mrb_state *mrb, const char *cmd, int stdin_fd, int stdout_fd, int stderr_fd, int *pid) { STARTUPINFO si; PROCESS_INFORMATION pi; HANDLE h_stdin = INVALID_HANDLE_VALUE; HANDLE h_stdout = INVALID_HANDLE_VALUE; HANDLE h_stderr = INVALID_HANDLE_VALUE; char cmdline[8192]; BOOL ret; (void)mrb; /* Skip leading whitespace */ while (*cmd == ' ' || *cmd == '\t' || *cmd == '\n') { cmd++; } if (!*cmd) { errno = ENOENT; return -1; } /* Build command line - use cmd.exe to execute */ snprintf(cmdline, sizeof(cmdline), "cmd.exe /c %s", cmd); /* Setup startup info */ memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; /* Convert file descriptors to handles and make them inheritable */ if (stdin_fd != -1) { h_stdin = (HANDLE)_get_osfhandle(stdin_fd); SetHandleInformation(h_stdin, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); si.hStdInput = h_stdin; } else { si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); } if (stdout_fd != -1) { h_stdout = (HANDLE)_get_osfhandle(stdout_fd); SetHandleInformation(h_stdout, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); si.hStdOutput = h_stdout; } else { si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); } if (stderr_fd != -1) { h_stderr = (HANDLE)_get_osfhandle(stderr_fd); SetHandleInformation(h_stderr, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); si.hStdError = h_stderr; } else { si.hStdError = GetStdHandle(STD_ERROR_HANDLE); } /* Create process */ memset(&pi, 0, sizeof(pi)); ret = CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); if (!ret) { set_errno_from_win_error(GetLastError()); return -1; } /* Close thread handle - we don't need it */ CloseHandle(pi.hThread); /* Store process handle as PID (will be used in waitpid) */ *pid = (int)(intptr_t)pi.hProcess; return 0; } int mrb_hal_io_waitpid(mrb_state *mrb, int pid, int *status, int options) { HANDLE h = (HANDLE)(intptr_t)pid; DWORD wait_result; DWORD exit_code; DWORD timeout; (void)mrb; /* Convert options to timeout */ timeout = (options != 0) ? 0 : INFINITE; wait_result = WaitForSingleObject(h, timeout); if (wait_result == WAIT_TIMEOUT) { return 0; /* Non-blocking wait, no change */ } if (wait_result != WAIT_OBJECT_0) { set_errno_from_win_error(GetLastError()); return -1; } /* Get exit code */ if (!GetExitCodeProcess(h, &exit_code)) { set_errno_from_win_error(GetLastError()); return -1; } if (status != NULL) { /* Store exit code in status (shifted to match Unix convention) */ *status = (int)(exit_code << 8); } /* Close process handle */ CloseHandle(h); return pid; } /* * I/O Multiplexing */ struct mrb_io_fdset { fd_set fds; }; mrb_io_fdset* mrb_hal_io_fdset_alloc(mrb_state *mrb) { mrb_io_fdset *fdset = (mrb_io_fdset*)mrb_malloc(mrb, sizeof(mrb_io_fdset)); FD_ZERO(&fdset->fds); return fdset; } void mrb_hal_io_fdset_free(mrb_state *mrb, mrb_io_fdset *fdset) { if (fdset) { mrb_free(mrb, fdset); } } void mrb_hal_io_fdset_zero(mrb_state *mrb, mrb_io_fdset *fdset) { (void)mrb; if (fdset) { FD_ZERO(&fdset->fds); } } void mrb_hal_io_fdset_set(mrb_state *mrb, int fd, mrb_io_fdset *fdset) { (void)mrb; if (fdset->fds.fd_count >= FD_SETSIZE) { mrb_raise(mrb, E_ARGUMENT_ERROR, "too many sockets for fd_set"); return; } if (fdset) { FD_SET(fd, &fdset->fds); } } int mrb_hal_io_fdset_isset(mrb_state *mrb, int fd, mrb_io_fdset *fdset) { (void)mrb; if (fdset) { return FD_ISSET(fd, &fdset->fds); } return 0; } int mrb_hal_io_select(mrb_state *mrb, int nfds, mrb_io_fdset *readfds, mrb_io_fdset *writefds, mrb_io_fdset *errorfds, mrb_io_timeval *timeout) { fd_set *r = readfds ? &readfds->fds : NULL; fd_set *w = writefds ? &writefds->fds : NULL; fd_set *e = errorfds ? &errorfds->fds : NULL; struct timeval *tv = NULL; struct timeval tv_storage; (void)mrb; (void)nfds; /* Windows select() doesn't use nfds */ if (timeout) { tv_storage.tv_sec = (long)timeout->tv_sec; tv_storage.tv_usec = (long)timeout->tv_usec; tv = &tv_storage; } return select(0, r, w, e, tv); } /* * HAL Initialization/Finalization */ void mrb_hal_io_init(mrb_state *mrb) { (void)mrb; /* Initialize Winsock for select() support */ WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); } void mrb_hal_io_final(mrb_state *mrb) { (void)mrb; /* Cleanup Winsock */ WSACleanup(); } /* * Gem initialization */ void mrb_hal_win_io_gem_init(mrb_state *mrb) { (void)mrb; /* HAL interface functions are called by mruby-io gem */ } void mrb_hal_win_io_gem_final(mrb_state *mrb) { (void)mrb; /* Cleanup handled by mrb_hal_io_final called from mruby-io */ }