/* * fy-win32.h - Windows compatibility layer * * Copyright (c) 2024-2026 libfyaml contributors * * SPDX-License-Identifier: MIT * * This header provides Unix-like APIs on Windows to allow libfyaml to * build and run on Windows platforms. */ #ifndef FY_WIN32_H #define FY_WIN32_H #ifdef _WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #include /* Undefine Windows macros that conflict with common identifiers */ #ifdef near #undef near #endif #ifdef far #undef far #endif #include #include #include #include #include #include #include #include #include #include #include #include #include /* ssize_t is not defined on Windows */ #ifndef _SSIZE_T_DEFINED #define _SSIZE_T_DEFINED #ifdef _WIN64 typedef __int64 ssize_t; #else typedef int ssize_t; #endif #endif /* POSIX types that MSVC only provides with underscore prefix */ #ifndef _OFF_T_DEFINED_AS_OFF_T #define _OFF_T_DEFINED_AS_OFF_T typedef _off_t off_t; #endif #ifndef _DEV_T_DEFINED_AS_DEV_T #define _DEV_T_DEFINED_AS_DEV_T typedef _dev_t dev_t; #endif /* Provide SSIZE_MAX if not defined */ #ifndef SSIZE_MAX #ifdef _WIN64 #define SSIZE_MAX _I64_MAX #else #define SSIZE_MAX INT_MAX #endif #endif /* * Memory mapping emulation using Windows Virtual Memory APIs */ /* mmap protection flags */ #ifndef PROT_NONE #define PROT_NONE 0x0 #define PROT_READ 0x1 #define PROT_WRITE 0x2 #define PROT_EXEC 0x4 #endif /* mmap flags */ #ifndef MAP_SHARED #define MAP_SHARED 0x01 #define MAP_PRIVATE 0x02 #define MAP_ANONYMOUS 0x20 #define MAP_ANON MAP_ANONYMOUS #define MAP_FAILED ((void *)-1) #endif static inline void *fy_win32_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) { DWORD flProtect = 0; DWORD dwDesiredAccess = 0; HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hMapping = NULL; void *ptr = NULL; (void)addr; /* Hint address is ignored */ /* Anonymous mapping - use VirtualAlloc */ if (flags & MAP_ANONYMOUS) { DWORD flAllocationType = MEM_RESERVE | MEM_COMMIT; DWORD flAllocProtect = 0; if (prot == PROT_NONE) flAllocProtect = PAGE_NOACCESS; else if (prot & PROT_EXEC) { if (prot & PROT_WRITE) flAllocProtect = PAGE_EXECUTE_READWRITE; else flAllocProtect = PAGE_EXECUTE_READ; } else if (prot & PROT_WRITE) flAllocProtect = PAGE_READWRITE; else flAllocProtect = PAGE_READONLY; ptr = VirtualAlloc(NULL, length, flAllocationType, flAllocProtect); if (!ptr) return MAP_FAILED; return ptr; } /* File mapping */ if (fd < 0) { errno = EBADF; return MAP_FAILED; } hFile = (HANDLE)_get_osfhandle(fd); if (hFile == INVALID_HANDLE_VALUE) { errno = EBADF; return MAP_FAILED; } /* Set protection and access flags */ if (prot == PROT_NONE) { flProtect = PAGE_NOACCESS; dwDesiredAccess = 0; } else if (prot & PROT_EXEC) { if (prot & PROT_WRITE) { flProtect = PAGE_EXECUTE_READWRITE; dwDesiredAccess = FILE_MAP_WRITE | FILE_MAP_EXECUTE; } else { flProtect = PAGE_EXECUTE_READ; dwDesiredAccess = FILE_MAP_READ | FILE_MAP_EXECUTE; } } else if (prot & PROT_WRITE) { if (flags & MAP_PRIVATE) { flProtect = PAGE_WRITECOPY; dwDesiredAccess = FILE_MAP_COPY; } else { flProtect = PAGE_READWRITE; dwDesiredAccess = FILE_MAP_WRITE; } } else { flProtect = PAGE_READONLY; dwDesiredAccess = FILE_MAP_READ; } hMapping = CreateFileMapping(hFile, NULL, flProtect, 0, 0, NULL); if (!hMapping) { errno = ENOMEM; return MAP_FAILED; } ptr = MapViewOfFile(hMapping, dwDesiredAccess, (DWORD)((uint64_t)offset >> 32), (DWORD)offset, length); CloseHandle(hMapping); if (!ptr) { errno = ENOMEM; return MAP_FAILED; } return ptr; } static inline int fy_win32_munmap(void *addr, size_t length) { (void)length; /* * We try both VirtualFree (for anonymous mappings) and * UnmapViewOfFile (for file mappings). One of them will succeed. */ if (UnmapViewOfFile(addr)) return 0; if (VirtualFree(addr, 0, MEM_RELEASE)) return 0; errno = EINVAL; return -1; } /* Define mmap/munmap macros that use our implementations */ #define mmap(addr, length, prot, flags, fd, offset) \ fy_win32_mmap(addr, length, prot, flags, fd, offset) #define munmap(addr, length) \ fy_win32_munmap(addr, length) /* mremap is not supported on Windows - will never be defined */ /* * File descriptor operations */ /* open() flags that may be missing */ #ifndef O_RDONLY #define O_RDONLY _O_RDONLY #endif #ifndef O_WRONLY #define O_WRONLY _O_WRONLY #endif #ifndef O_RDWR #define O_RDWR _O_RDWR #endif #ifndef O_CREAT #define O_CREAT _O_CREAT #endif #ifndef O_TRUNC #define O_TRUNC _O_TRUNC #endif #ifndef O_APPEND #define O_APPEND _O_APPEND #endif #ifndef O_BINARY #define O_BINARY _O_BINARY #endif #ifndef O_EXCL #define O_EXCL _O_EXCL #endif /* Standard file descriptor numbers */ #ifndef STDIN_FILENO #define STDIN_FILENO 0 #endif #ifndef STDOUT_FILENO #define STDOUT_FILENO 1 #endif #ifndef STDERR_FILENO #define STDERR_FILENO 2 #endif static inline int fy_win32_open(const char *pathname, int flags, ...) { /* always use O_BINARY to avoid CRLF mangling */ return _open(pathname, flags | O_BINARY); } /* Use _open, _close on Windows */ #ifndef open #define open fy_win32_open #endif #ifndef close #define close _close #endif /* * Windows _read/_write use unsigned int (32-bit) for count. * Provide wrappers that handle size_t properly by chunking large I/O. */ static inline ssize_t fy_win32_read(int fd, void *buf, size_t count) { unsigned char *p = (unsigned char *)buf; size_t remaining = count; ssize_t total = 0; while (remaining > 0) { unsigned int chunk = (remaining > UINT_MAX) ? UINT_MAX : (unsigned int)remaining; int ret = _read(fd, p, chunk); if (ret < 0) return total > 0 ? total : -1; if (ret == 0) break; total += ret; p += ret; remaining -= ret; if ((unsigned int)ret < chunk) break; /* short read */ } return total; } static inline ssize_t fy_win32_write(int fd, const void *buf, size_t count) { const unsigned char *p = (const unsigned char *)buf; size_t remaining = count; ssize_t total = 0; while (remaining > 0) { unsigned int chunk = (remaining > UINT_MAX) ? UINT_MAX : (unsigned int)remaining; int ret = _write(fd, p, chunk); if (ret < 0) return total > 0 ? total : -1; if (ret == 0) break; total += ret; p += ret; remaining -= ret; if ((unsigned int)ret < chunk) break; /* short write */ } return total; } #ifndef read #define read fy_win32_read #endif #ifndef write #define write fy_win32_write #endif #ifndef lseek #define lseek _lseek #endif #ifndef fileno #define fileno _fileno #endif #ifndef fdopen #define fdopen _fdopen #endif #ifndef dup #define dup _dup #endif #ifndef dup2 #define dup2 _dup2 #endif #ifndef access #define access _access #endif /* stat structure and functions */ #ifndef stat #define stat _stat64 #endif #ifndef fstat #define fstat _fstat64 #endif #ifndef isatty static inline int fy_win32_isatty(int fd) { HANDLE h; DWORD mode; h = (HANDLE)_get_osfhandle(fd); if (h == INVALID_HANDLE_VALUE || h == NULL) return 0; return GetConsoleMode(h, &mode) != 0; } #define isatty(fd) fy_win32_isatty(fd) #endif /* S_ISREG and S_ISDIR macros */ #ifndef S_ISREG #define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) #endif #ifndef S_ISDIR #define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) #endif #ifndef S_IFREG #define S_IFREG _S_IFREG #endif #ifndef S_IFMT #define S_IFMT _S_IFMT #endif /* Unix file permission macros - Windows doesn't have these */ #ifndef S_IRUSR #define S_IRUSR _S_IREAD #endif #ifndef S_IWUSR #define S_IWUSR _S_IWRITE #endif #ifndef S_IXUSR #define S_IXUSR _S_IEXEC #endif /* Group/other permissions don't exist on Windows, define as 0 */ #ifndef S_IRGRP #define S_IRGRP 0 #endif #ifndef S_IWGRP #define S_IWGRP 0 #endif #ifndef S_IXGRP #define S_IXGRP 0 #endif #ifndef S_IROTH #define S_IROTH 0 #endif #ifndef S_IWOTH #define S_IWOTH 0 #endif #ifndef S_IXOTH #define S_IXOTH 0 #endif /* * System configuration - sysconf() emulation */ #ifndef _SC_PAGESIZE #define _SC_PAGESIZE 1 #endif #ifndef _SC_NPROCESSORS_ONLN #define _SC_NPROCESSORS_ONLN 2 #endif static inline long fy_win32_sysconf(int name) { SYSTEM_INFO si; switch (name) { case _SC_PAGESIZE: GetSystemInfo(&si); return (long)si.dwPageSize; case _SC_NPROCESSORS_ONLN: GetSystemInfo(&si); return (long)si.dwNumberOfProcessors; default: errno = EINVAL; return -1; } } #define sysconf(name) fy_win32_sysconf(name) /* * asprintf() implementation for Windows */ #ifndef HAVE_ASPRINTF static inline int fy_win32_vasprintf(char **strp, const char *fmt, va_list ap) { int len; va_list ap_copy; va_copy(ap_copy, ap); len = _vscprintf(fmt, ap_copy); va_end(ap_copy); if (len < 0) { *strp = NULL; return -1; } *strp = (char *)malloc(len + 1); if (!*strp) return -1; len = vsprintf(*strp, fmt, ap); if (len < 0) { free(*strp); *strp = NULL; return -1; } return len; } static inline int fy_win32_asprintf(char **strp, const char *fmt, ...) { va_list ap; int ret; va_start(ap, fmt); ret = fy_win32_vasprintf(strp, fmt, ap); va_end(ap); return ret; } #define asprintf fy_win32_asprintf #define vasprintf fy_win32_vasprintf #endif /* HAVE_ASPRINTF */ /* * Directory functions */ #ifndef getcwd #define getcwd _getcwd #endif #ifndef mkdir #define mkdir(path, mode) _mkdir(path) #endif #ifndef rmdir #define rmdir _rmdir #endif #ifndef chdir #define chdir _chdir #endif /* * Process functions */ #ifndef getpid #define getpid _getpid #endif /* * String functions */ #ifndef strcasecmp #define strcasecmp _stricmp #endif #ifndef strncasecmp #define strncasecmp _strnicmp #endif #ifndef strdup #define strdup _strdup #endif /* * Sleep functions */ static inline unsigned int fy_win32_sleep(unsigned int seconds) { Sleep(seconds * 1000); return 0; } static inline int fy_win32_usleep(unsigned int usec) { HANDLE timer; LARGE_INTEGER ft; ft.QuadPart = -(10 * (LONGLONG)usec); timer = CreateWaitableTimer(NULL, TRUE, NULL); if (!timer) return -1; SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); return 0; } #define sleep(s) fy_win32_sleep(s) #define usleep(us) fy_win32_usleep(us) /* * clock_gettime emulation for Windows */ #ifndef CLOCK_MONOTONIC #define CLOCK_MONOTONIC 1 #endif #ifndef CLOCK_REALTIME #define CLOCK_REALTIME 0 #endif /* * struct timespec is defined in UCRT's time.h for MSVC 2015+ (VS2015+) * time.h is included above to ensure it's available. */ static inline int fy_win32_clock_gettime(int clock_id, struct timespec *tp) { LARGE_INTEGER freq, count; FILETIME ft; ULARGE_INTEGER uli; if (clock_id == CLOCK_MONOTONIC) { if (!QueryPerformanceFrequency(&freq)) return -1; if (!QueryPerformanceCounter(&count)) return -1; tp->tv_sec = (time_t)(count.QuadPart / freq.QuadPart); tp->tv_nsec = (long)((count.QuadPart % freq.QuadPart) * 1000000000LL / freq.QuadPart); } else { /* CLOCK_REALTIME */ GetSystemTimeAsFileTime(&ft); uli.LowPart = ft.dwLowDateTime; uli.HighPart = ft.dwHighDateTime; /* FILETIME is 100-nanosecond intervals since January 1, 1601 */ /* Convert to Unix epoch (seconds since January 1, 1970) */ uli.QuadPart -= 116444736000000000ULL; tp->tv_sec = (time_t)(uli.QuadPart / 10000000ULL); tp->tv_nsec = (long)((uli.QuadPart % 10000000ULL) * 100); } return 0; } #define clock_gettime(clock_id, tp) fy_win32_clock_gettime(clock_id, tp) /* * EAGAIN may be different from EWOULDBLOCK on some systems */ #ifndef EAGAIN #define EAGAIN EWOULDBLOCK #endif /* * alloca is available as _alloca on Windows */ #ifndef alloca #define alloca _alloca #endif /* * iovec structure for scatter/gather I/O * Defined here since sys/uio.h doesn't exist on Windows */ #ifndef _FY_IOVEC_DEFINED #define _FY_IOVEC_DEFINED struct iovec { void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ }; #endif #if defined(_MSC_VER) && !defined(__clang__) typedef union { void* p; double d; __int64 i64; } max_align_t; #endif // yes, I know it's kinda bogus - it's fine, we don't export #ifndef PATH_MAX #define PATH_MAX 4096 #endif #endif /* _WIN32 */ #endif /* FY_WIN32_H */