mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
c836b096af
This should fix it for Windows.
647 lines
13 KiB
C
647 lines
13 KiB
C
/*
|
|
** 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 <mruby.h>
|
|
#include "io_hal.h"
|
|
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <direct.h>
|
|
#include <stdint.h>
|
|
|
|
/* 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 */
|
|
}
|