MicroHs-0.16.0.0: src/runtime/windows/posix_emul.c
#include <windows.h>
#include <winsock2.h>
#include <io.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
typedef int posix_fd_t;
typedef int32_t ssize_t;
typedef size_t nfds_t;
#ifndef STDIN_FILENO
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#endif
#ifndef O_ACCMODE
#define O_ACCMODE 3
#endif
#define MAX_FD 1024
typedef enum {
FD_FREE = 0,
FD_CONSOLE,
FD_FILE,
FD_SOCKET,
FD_PIPE
} fd_type_t;
typedef struct {
fd_type_t type;
HANDLE os_handle;
SOCKET os_sock;
WSAEVENT os_event;
long pending_events;
} fd_entry_t;
static fd_entry_t fd_table[MAX_FD] = {0};
static int initialized = 0;
void uni_init(void) {
if (initialized) return;
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
fd_table[STDIN_FILENO] = (fd_entry_t){FD_CONSOLE, GetStdHandle(STD_INPUT_HANDLE), INVALID_SOCKET, WSA_INVALID_EVENT, 0};
fd_table[STDOUT_FILENO] = (fd_entry_t){FD_CONSOLE, GetStdHandle(STD_OUTPUT_HANDLE), INVALID_SOCKET, WSA_INVALID_EVENT, 0};
fd_table[STDERR_FILENO] = (fd_entry_t){FD_CONSOLE, GetStdHandle(STD_ERROR_HANDLE), INVALID_SOCKET, WSA_INVALID_EVENT, 0};
initialized = 1;
}
static int alloc_fd(fd_type_t type) {
for (int i = 3; i < MAX_FD; i++) {
if (fd_table[i].type == FD_FREE) {
fd_table[i].type = type;
fd_table[i].os_handle = INVALID_HANDLE_VALUE;
fd_table[i].os_sock = INVALID_SOCKET;
fd_table[i].os_event = WSA_INVALID_EVENT;
fd_table[i].pending_events = 0;
return i;
}
}
return -1;
}
posix_fd_t uni_open(const char *pathname, int flags, ...) {
DWORD access = GENERIC_READ;
if ((flags & O_ACCMODE) == O_WRONLY) access = GENERIC_WRITE;
else if ((flags & O_ACCMODE) == O_RDWR) access = GENERIC_READ | GENERIC_WRITE;
DWORD creation = (flags & O_CREAT) ? OPEN_ALWAYS : OPEN_EXISTING;
HANDLE h = CreateFileA(pathname, access, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
creation, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) return -1;
int fd = alloc_fd(FD_FILE);
if (fd < 0) {
CloseHandle(h);
return -1;
}
fd_table[fd].os_handle = h;
return fd;
}
int uni_pipe(posix_fd_t pipefd[2]) {
static volatile long pipe_counter = 0;
char pipe_name[128];
sprintf(pipe_name, "\\\\.\\pipe\\uni_pipe_%08lx_%08lx", GetCurrentProcessId(), InterlockedIncrement(&pipe_counter));
// Ensure pipes can be inherited by child processes (standard POSIX behavior)
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
HANDLE r_handle = CreateNamedPipeA(
pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1, 4096, 4096, 0, &sa
);
if (r_handle == INVALID_HANDLE_VALUE) return -1;
HANDLE w_handle = CreateFileA(
pipe_name, GENERIC_WRITE, 0, &sa, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL
);
if (w_handle == INVALID_HANDLE_VALUE) {
CloseHandle(r_handle);
return -1;
}
OVERLAPPED ov = {0};
ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!ConnectNamedPipe(r_handle, &ov)) {
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING) {
DWORD dummy;
GetOverlappedResult(r_handle, &ov, &dummy, TRUE);
}
}
CloseHandle(ov.hEvent);
int fd_r = alloc_fd(FD_PIPE);
int fd_w = alloc_fd(FD_PIPE);
if (fd_r < 0 || fd_w < 0) {
if (fd_r >= 0) fd_table[fd_r].type = FD_FREE;
if (fd_w >= 0) fd_table[fd_w].type = FD_FREE;
CloseHandle(r_handle);
CloseHandle(w_handle);
return -1;
}
fd_table[fd_r].os_handle = r_handle;
fd_table[fd_w].os_handle = w_handle;
pipefd[0] = fd_r;
pipefd[1] = fd_w;
return 0;
}
posix_fd_t uni_socket(int domain, int type, int protocol) {
SOCKET s = socket(domain, type, protocol);
if (s == INVALID_SOCKET) return -1;
int fd = alloc_fd(FD_SOCKET);
if (fd < 0) {
closesocket(s);
return -1;
}
fd_table[fd].os_sock = s;
fd_table[fd].os_event = WSACreateEvent();
fd_table[fd].pending_events = FD_WRITE;
WSAEventSelect(s, fd_table[fd].os_event, FD_READ | FD_WRITE | FD_ACCEPT | FD_CLOSE | FD_CONNECT);
return fd;
}
posix_fd_t uni_accept(posix_fd_t sockfd, struct sockaddr *addr, socklen_t *addrlen) {
if (sockfd < 0 || sockfd >= MAX_FD || fd_table[sockfd].type != FD_SOCKET) return -1;
SOCKET client_s = accept(fd_table[sockfd].os_sock, addr, addrlen);
if (client_s == INVALID_SOCKET) {
if (WSAGetLastError() == WSAEWOULDBLOCK) errno = EAGAIN;
return -1;
}
int fd = alloc_fd(FD_SOCKET);
if (fd < 0) {
closesocket(client_s);
return -1;
}
fd_table[fd].os_sock = client_s;
fd_table[fd].os_event = WSACreateEvent();
fd_table[fd].pending_events = FD_WRITE;
WSAEventSelect(client_s, fd_table[fd].os_event, FD_READ | FD_WRITE | FD_ACCEPT | FD_CLOSE | FD_CONNECT);
return fd;
}
int uni_connect(posix_fd_t sockfd, const struct sockaddr *addr, socklen_t addrlen) {
if (sockfd < 0 || sockfd >= MAX_FD || fd_table[sockfd].type != FD_SOCKET) return -1;
int ret = connect(fd_table[sockfd].os_sock, addr, (int)addrlen);
if (ret == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK) {
errno = EINPROGRESS;
}
return ret;
}
int uni_close(posix_fd_t fd) {
if (fd < 0 || fd >= MAX_FD) return -1;
int ret = 0;
if (fd_table[fd].type == FD_FILE || fd_table[fd].type == FD_CONSOLE || fd_table[fd].type == FD_PIPE) {
ret = CloseHandle(fd_table[fd].os_handle) ? 0 : -1;
} else if (fd_table[fd].type == FD_SOCKET) {
WSAEventSelect(fd_table[fd].os_sock, NULL, 0);
WSACloseEvent(fd_table[fd].os_event);
ret = closesocket(fd_table[fd].os_sock);
}
fd_table[fd].type = FD_FREE;
return ret;
}
ssize_t uni_read(posix_fd_t fd, void *buf, size_t count) {
if (fd < 0 || fd >= MAX_FD) return -1;
if (fd_table[fd].type == FD_SOCKET) {
int ret = recv(fd_table[fd].os_sock, buf, (int)count, 0);
if (ret == SOCKET_ERROR) {
if (WSAGetLastError() == WSAEWOULDBLOCK) {
fd_table[fd].pending_events &= ~(FD_READ | FD_ACCEPT);
errno = EAGAIN;
}
return -1;
}
return ret;
} else if (fd_table[fd].type == FD_PIPE) {
DWORD avail = 0;
if (!PeekNamedPipe(fd_table[fd].os_handle, NULL, 0, NULL, &avail, NULL)) {
if (GetLastError() == ERROR_BROKEN_PIPE) return 0;
errno = EIO; return -1;
}
if (avail == 0) {
errno = EAGAIN;
return -1;
}
OVERLAPPED ov = {0};
DWORD read_bytes = 0;
if (!ReadFile(fd_table[fd].os_handle, buf, (DWORD)count, &read_bytes, &ov)) {
if (GetLastError() == ERROR_IO_PENDING) {
// Safely cancel and wait if the read unexpectedly blocks
if (!GetOverlappedResult(fd_table[fd].os_handle, &ov, &read_bytes, FALSE)) {
if (GetLastError() == ERROR_IO_INCOMPLETE) {
CancelIoEx(fd_table[fd].os_handle, &ov);
GetOverlappedResult(fd_table[fd].os_handle, &ov, &read_bytes, TRUE);
errno = EAGAIN;
return -1;
}
}
} else {
return -1;
}
}
return (ssize_t)read_bytes;
} else {
DWORD read_bytes = 0;
if (!ReadFile(fd_table[fd].os_handle, buf, (DWORD)count, &read_bytes, NULL)) return -1;
return (ssize_t)read_bytes;
}
}
ssize_t uni_write(posix_fd_t fd, const void *buf, size_t count) {
if (fd < 0 || fd >= MAX_FD) return -1;
if (fd_table[fd].type == FD_SOCKET) {
int ret = send(fd_table[fd].os_sock, buf, (int)count, 0);
if (ret == SOCKET_ERROR) {
if (WSAGetLastError() == WSAEWOULDBLOCK) {
fd_table[fd].pending_events &= ~FD_WRITE;
errno = EAGAIN;
}
return -1;
}
return ret;
} else if (fd_table[fd].type == FD_PIPE) {
OVERLAPPED ov = {0};
DWORD written_bytes = 0;
if (!WriteFile(fd_table[fd].os_handle, buf, (DWORD)count, &written_bytes, &ov)) {
if (GetLastError() == ERROR_IO_PENDING) {
if (!GetOverlappedResult(fd_table[fd].os_handle, &ov, &written_bytes, FALSE)) {
if (GetLastError() == ERROR_IO_INCOMPLETE) {
CancelIoEx(fd_table[fd].os_handle, &ov);
GetOverlappedResult(fd_table[fd].os_handle, &ov, &written_bytes, TRUE);
errno = EAGAIN;
return -1;
}
}
} else {
return -1;
}
}
return (ssize_t)written_bytes;
} else {
DWORD written_bytes = 0;
if (!WriteFile(fd_table[fd].os_handle, buf, (DWORD)count, &written_bytes, NULL)) return -1;
return (ssize_t)written_bytes;
}
}
int uni_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
if (nfds <= 0 || nfds > MAXIMUM_WAIT_OBJECTS) return -1;
ULONGLONG start_time = GetTickCount64();
DWORD dw_timeout = (timeout < 0) ? INFINITE : (DWORD)timeout;
while (1) {
int active_count = 0;
HANDLE socket_events[MAXIMUM_WAIT_OBJECTS];
DWORD socket_count = 0;
for (nfds_t i = 0; i < nfds; i++) {
int fd = fds[i].fd;
if (fd < 0) {
fds[i].revents = 0;
continue;
}
if (fd >= MAX_FD || fd_table[fd].type == FD_FREE) {
fds[i].revents = POLLNVAL;
continue;
}
if (fd_table[fd].type == FD_SOCKET) {
WSANETWORKEVENTS net_events;
if (WSAEnumNetworkEvents(fd_table[fd].os_sock, fd_table[fd].os_event, &net_events) == 0) {
fd_table[fd].pending_events |= net_events.lNetworkEvents;
}
if (socket_count < MAXIMUM_WAIT_OBJECTS) {
socket_events[socket_count++] = fd_table[fd].os_event;
}
}
}
for (nfds_t i = 0; i < nfds; i++) {
int fd = fds[i].fd;
if (fd < 0 || fd >= MAX_FD || fd_table[fd].type == FD_FREE) continue;
fds[i].revents = 0;
if (fd_table[fd].type == FD_SOCKET) {
long evs = fd_table[fd].pending_events;
if ((fds[i].events & POLLIN) && (evs & (FD_READ | FD_ACCEPT | FD_CLOSE))) fds[i].revents |= POLLIN;
if ((fds[i].events & POLLOUT) && (evs & (FD_WRITE | FD_CONNECT))) fds[i].revents |= POLLOUT;
if (evs & FD_CLOSE) fds[i].revents |= POLLHUP;
}
else if (fd_table[fd].type == FD_PIPE) {
DWORD avail = 0;
if (!PeekNamedPipe(fd_table[fd].os_handle, NULL, 0, NULL, &avail, NULL)) {
DWORD err = GetLastError();
if (err == ERROR_BROKEN_PIPE || err == ERROR_PIPE_NOT_CONNECTED) {
fds[i].revents |= POLLHUP;
}
} else {
if ((fds[i].events & POLLIN) && avail > 0) fds[i].revents |= POLLIN;
if (fds[i].events & POLLOUT) fds[i].revents |= POLLOUT;
}
}
else if (fd_table[fd].type == FD_CONSOLE) {
if (fd == STDIN_FILENO) {
DWORD events = 0;
GetNumberOfConsoleInputEvents(fd_table[fd].os_handle, &events);
if (events > 0) {
// Filter out mouse/focus events to prevent thread blocking on read()
INPUT_RECORD ir[128];
DWORD read_ir = 0;
if (PeekConsoleInput(fd_table[fd].os_handle, ir, 128, &read_ir)) {
for (DWORD k = 0; k < read_ir; k++) {
if (ir[k].EventType == KEY_EVENT && ir[k].Event.KeyEvent.bKeyDown) {
if (fds[i].events & POLLIN) fds[i].revents |= POLLIN;
break;
}
}
}
}
} else {
if (fds[i].events & POLLOUT) fds[i].revents |= POLLOUT;
}
}
else if (fd_table[fd].type == FD_FILE) {
if (fds[i].events & POLLIN) fds[i].revents |= POLLIN;
if (fds[i].events & POLLOUT) fds[i].revents |= POLLOUT;
}
if (fds[i].revents) active_count++;
}
if (active_count > 0 || dw_timeout == 0) return active_count;
if (dw_timeout != INFINITE) {
ULONGLONG elapsed = GetTickCount64() - start_time;
if (elapsed >= dw_timeout) return 0;
dw_timeout -= (DWORD)elapsed;
start_time = GetTickCount64();
}
if (socket_count > 0) {
DWORD chunk = (dw_timeout == INFINITE) ? 10 : (dw_timeout < 10 ? dw_timeout : 10);
WaitForMultipleObjects(socket_count, socket_events, FALSE, chunk);
} else {
Sleep(1);
}
}
}