packages feed

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);
        }
    }
}