diff --git a/.gitignore b/.gitignore index ea2e939..d389ca9 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,15 @@ vgcore.* dbg* diag* -.env.secret \ No newline at end of file +.env.secret + +vs2022/.vs +vs2022/Release/ +vs2022/Debug/ +vs2022/x64/Release/ +vs2022/x64/Debug/ +vs2022/x86/Release/ +vs2022/x86/Debug/ + +*.vcxproj.user +*.sln.docstates \ No newline at end of file diff --git a/Makefile.windows b/Makefile.windows index 40271fd..62387d2 100644 --- a/Makefile.windows +++ b/Makefile.windows @@ -1,30 +1,26 @@ -# Makefile.windows — VoidCache build for Windows (MSYS2 / UCRT64) +# Makefile.windows — VoidCache build for Windows (MSYS2 UCRT64) # -# Open in MSYS2 UCRT64 terminal and run: -# make -f Makefile.windows -# make -f Makefile.windows test -# make -f Makefile.windows smoke +# IMPORTANT: Open the "MSYS2 UCRT64" terminal (not MSYS2, not MinGW, not Cygwin) +# You can find it in Start Menu → "MSYS2 UCRT64" # -# Prerequisites (install once): -# pacman -S mingw-w64-ucrt-x86_64-gcc \ -# mingw-w64-ucrt-x86_64-openssl \ -# mingw-w64-ucrt-x86_64-wepoll \ -# make +# Install deps once: +# pacman -S --needed mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-openssl make +# +# Build: +# make -f Makefile.windows vcli +# +# Note: wepoll and mman are bundled in compat/ — no pacman packages needed. # ───────────────────────────────────────────────────────────────────────────── CC := gcc CFLAGS := -O2 -std=c11 \ -Iinclude -Inet -Icompat \ + -I$(MSYSTEM_PREFIX)/include \ -D_WIN32_WINNT=0x0A00 \ -DVCACHE_WINDOWS \ - -Wall -Wextra \ -Wno-unused-function -Wno-unused-parameter -# OpenSSL from MSYS2 pacman -OPENSSL_PREFIX := $(MSYSTEM_PREFIX) -CFLAGS += -I$(OPENSSL_PREFIX)/include - -LDFLAGS := -L$(OPENSSL_PREFIX)/lib \ +LDFLAGS := -L$(MSYSTEM_PREFIX)/lib \ -lssl -lcrypto \ -lpthread \ -lws2_32 -lbcrypt \ @@ -33,6 +29,8 @@ LDFLAGS := -L$(OPENSSL_PREFIX)/lib \ CORE_SRC := src/voidcache.c NET_SRC := net/proto.c net/auth.c net/commands.c net/server.c net/cluster.c CLI_SRC := cli/vcli.c +COMPAT_SRC := compat/wepoll.c compat/mman.c + TEST_SRC := tests/test_voidcache.c BENCH_SRC := bench/benchmark.c @@ -40,26 +38,25 @@ TARGET := vcli.exe TEST_BIN := voidcache_test.exe BENCH_BIN := voidcache_bench.exe -.PHONY: all vcli test bench smoke clean deps +.PHONY: all vcli test bench smoke clean deps bundle -all: vcli test bench +all: vcli # ── Install prerequisites ────────────────────────────────────────────────── deps: pacman -S --noconfirm --needed \ mingw-w64-ucrt-x86_64-gcc \ mingw-w64-ucrt-x86_64-openssl \ - mingw-w64-ucrt-x86_64-wepoll \ make - @echo "Dependencies installed. Run: make -f Makefile.windows" + @echo "Done. Now run: make -f Makefile.windows vcli" -# ── Main binary ──────────────────────────────────────────────────────────── -vcli: $(CORE_SRC) $(NET_SRC) $(CLI_SRC) +# ── Main binary (wepoll.c + mman.c compiled inline, no extra libs needed) ── +vcli: $(CORE_SRC) $(NET_SRC) $(CLI_SRC) $(COMPAT_SRC) $(CC) $(CFLAGS) $^ -o $(TARGET) $(LDFLAGS) @echo "Built: $(TARGET)" -# ── Tests (no network layer) ─────────────────────────────────────────────── -$(TEST_BIN): $(CORE_SRC) $(TEST_SRC) +# ── Tests (core only, no network) ───────────────────────────────────────── +$(TEST_BIN): $(CORE_SRC) $(TEST_SRC) compat/mman.c $(CC) $(CFLAGS) $^ -o $@ -lpthread -lm @echo "Built: $(TEST_BIN)" @@ -67,39 +64,31 @@ test: $(TEST_BIN) ./$(TEST_BIN) # ── Benchmark ───────────────────────────────────────────────────────────── -$(BENCH_BIN): $(CORE_SRC) $(BENCH_SRC) +$(BENCH_BIN): $(CORE_SRC) $(BENCH_SRC) compat/mman.c $(CC) $(CFLAGS) $^ -o $@ -lpthread -lm @echo "Built: $(BENCH_BIN)" bench: $(BENCH_BIN) ./$(BENCH_BIN) -# ── Smoke test ───────────────────────────────────────────────────────────── +# ── Quick smoke test ─────────────────────────────────────────────────────── smoke: vcli @echo "Starting server on :16399..." - ./$(TARGET) server --port 16399 & + start "" ./$(TARGET) server --port 16399 sleep 1 - @echo "--- PING ---" ./$(TARGET) -p 16399 --no-color PING - @echo "--- SET/GET ---" ./$(TARGET) -p 16399 --no-color SET hello world ./$(TARGET) -p 16399 --no-color GET hello - @echo "--- VCSET int ---" - ./$(TARGET) -p 16399 --no-color VCSET score int 42 - ./$(TARGET) -p 16399 --no-color VCGET score - @echo "Stopping server..." - ./$(TARGET) -p 16399 --no-color SHUTDOWN NOSAVE 2>/dev/null || true + ./$(TARGET) -p 16399 --no-color SHUTDOWN NOSAVE 2>nul || true -clean: - rm -f $(TARGET) $(TEST_BIN) $(BENCH_BIN) *.exe - -# ── Copy OpenSSL DLLs next to the binary (needed to run outside MSYS2) ────── +# ── Bundle DLLs for standalone use ──────────────────────────────────────── bundle: - @echo "Copying OpenSSL DLLs for standalone distribution..." - cp $(OPENSSL_PREFIX)/bin/libssl-3-x64.dll . 2>/dev/null || \ - cp $(OPENSSL_PREFIX)/bin/libssl*.dll . 2>/dev/null || true - cp $(OPENSSL_PREFIX)/bin/libcrypto-3-x64.dll . 2>/dev/null || \ - cp $(OPENSSL_PREFIX)/bin/libcrypto*.dll . 2>/dev/null || true - cp $(OPENSSL_PREFIX)/bin/libgcc_s_seh-1.dll . 2>/dev/null || true - cp $(OPENSSL_PREFIX)/bin/libwinpthread-1.dll . 2>/dev/null || true - @echo "DLLs copied. You can now run vcli.exe from any folder." + @for dll in libssl-3-x64.dll libcrypto-3-x64.dll \ + libgcc_s_seh-1.dll libwinpthread-1.dll; do \ + cp "$(MSYSTEM_PREFIX)/bin/$$dll" . 2>/dev/null && \ + echo " Copied $$dll" || true; \ + done + @echo "DLLs bundled. vcli.exe runs standalone." + +clean: + rm -f $(TARGET) $(TEST_BIN) $(BENCH_BIN) diff --git a/build-windows.ps1 b/build-windows.ps1 new file mode 100644 index 0000000..4786954 --- /dev/null +++ b/build-windows.ps1 @@ -0,0 +1,139 @@ +# build-windows.ps1 — Build vcli.exe on Windows using MSYS2 UCRT64 +# +# Run from PowerShell (as normal user, NOT Administrator): +# Set-ExecutionPolicy -Scope CurrentUser RemoteSigned +# .\build-windows.ps1 + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$MSYS2_DIR = "C:\msys64" +$UCRT64_GCC = "$MSYS2_DIR\ucrt64\bin\gcc.exe" +$UCRT64_BIN = "$MSYS2_DIR\ucrt64\bin" +$PACMAN = "$MSYS2_DIR\usr\bin\pacman.exe" +# Use MSYS2's own bash explicitly — NOT any other bash on PATH (e.g. Cygwin, Git) +$MSYS2_BASH = "$MSYS2_DIR\usr\bin\bash.exe" + +Write-Host "" +Write-Host "=== VoidCache Windows Build ===" -ForegroundColor Cyan +Write-Host "" + +# ── Step 1: Check MSYS2 ─────────────────────────────────────────────────────── +if (-not (Test-Path $MSYS2_BASH)) { + Write-Host "[1/4] MSYS2 not found at $MSYS2_DIR. Installing via winget..." -ForegroundColor Yellow + $winget = Get-Command winget -ErrorAction SilentlyContinue + if (-not $winget) { + Write-Host "ERROR: winget not found. Install from https://aka.ms/getwinget" -ForegroundColor Red + Write-Host "Or install MSYS2 manually from https://www.msys2.org/ to C:\msys64" -ForegroundColor Red + exit 1 + } + winget install --id MSYS2.MSYS2 --silent --accept-package-agreements --accept-source-agreements + Start-Sleep -Seconds 5 + if (-not (Test-Path $MSYS2_BASH)) { + Write-Host "ERROR: MSYS2 install failed or not at C:\msys64" -ForegroundColor Red + exit 1 + } +} +Write-Host "[1/4] MSYS2 found at $MSYS2_DIR" -ForegroundColor Green + +# ── Step 2: Install UCRT64 gcc + openssl via pacman ─────────────────────────── +Write-Host "[2/4] Installing UCRT64 gcc and OpenSSL (wepoll+mman are bundled)..." -ForegroundColor Yellow + +# Run pacman through MSYS2's OWN bash with --login to get the right environment +# CRITICAL: use $MSYS2_BASH explicitly, never rely on PATH-resolved bash +$packages = "mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-openssl make" +$pacmanCmd = "pacman -S --noconfirm --needed $packages" +& $MSYS2_BASH --login -c $pacmanCmd +if ($LASTEXITCODE -ne 0) { + Write-Host "WARNING: pacman reported errors (may be harmless if packages already installed)" -ForegroundColor Yellow +} +Write-Host "[2/4] Packages ready." -ForegroundColor Green + +# ── Step 3: Verify we have the RIGHT gcc (UCRT64, not Cygwin) ──────────────── +if (-not (Test-Path $UCRT64_GCC)) { + Write-Host "ERROR: UCRT64 gcc not found at $UCRT64_GCC" -ForegroundColor Red + Write-Host "Run manually in MSYS2 UCRT64 terminal: pacman -S mingw-w64-ucrt-x86_64-gcc" -ForegroundColor Red + exit 1 +} +$gccVersion = & $UCRT64_GCC --version 2>&1 | Select-Object -First 1 +Write-Host "[3/4] Compiler: $gccVersion" -ForegroundColor Green + +# ── Step 4: Build directly with UCRT64 gcc — no bash, no make, no PATH issues ─ +Write-Host "[4/4] Building vcli.exe..." -ForegroundColor Yellow + +$SRC = Split-Path -Parent $MyInvocation.MyCommand.Path +$OPENSSL = "$MSYS2_DIR\ucrt64" + +$Sources = @( + "src\voidcache.c", + "net\proto.c", + "net\auth.c", + "net\commands.c", + "net\server.c", + "net\cluster.c", + "cli\vcli.c", + "compat\wepoll.c", + "compat\mman.c" +) | ForEach-Object { Join-Path $SRC $_ } + +$Flags = @( + "-O2", "-std=c11", + "-Iinclude", "-Inet", "-Icompat", + "-I$OPENSSL\include", + "-D_WIN32_WINNT=0x0A00", + "-DVCACHE_WINDOWS", + "-Wno-unused-function", "-Wno-unused-parameter" +) + +$Libs = @( + "-L$OPENSSL\lib", + "-lssl", "-lcrypto", + "-lpthread", + "-lws2_32", "-lbcrypt", + "-lm" +) + +$OutExe = Join-Path $SRC "vcli.exe" + +$GccArgs = $Flags + $Sources + @("-o", $OutExe) + $Libs + +Write-Host " $UCRT64_GCC $($GccArgs -join ' ')" -ForegroundColor DarkGray +Write-Host "" + +& $UCRT64_GCC @GccArgs +if ($LASTEXITCODE -ne 0) { + Write-Host "" + Write-Host "Build FAILED. See errors above." -ForegroundColor Red + exit 1 +} + +# ── Copy required DLLs next to the binary ──────────────────────────────────── +Write-Host "" +Write-Host "Copying runtime DLLs..." -ForegroundColor Yellow + +$dlls = @( + "libssl-3-x64.dll", + "libcrypto-3-x64.dll", + "libgcc_s_seh-1.dll", + "libwinpthread-1.dll", + "libstdc++-6.dll" +) +foreach ($dll in $dlls) { + $src = Join-Path $UCRT64_BIN $dll + if (Test-Path $src) { + Copy-Item $src $SRC -Force + Write-Host " Copied $dll" -ForegroundColor DarkGray + } +} + +Write-Host "" +Write-Host "========================================" -ForegroundColor Green +Write-Host " Build complete! vcli.exe is ready." -ForegroundColor Green +Write-Host "========================================" -ForegroundColor Green +Write-Host "" +Write-Host "Test (cluster must be running):" -ForegroundColor White +Write-Host " .\vcli.exe -h localhost -p 6379 PING" -ForegroundColor Cyan +Write-Host "" +Write-Host "Interactive shell:" -ForegroundColor White +Write-Host " .\vcli.exe -h localhost -p 6379" -ForegroundColor Cyan +Write-Host "" diff --git a/cli/vcli.c b/cli/vcli.c index bdee9a7..438f0f1 100644 --- a/cli/vcli.c +++ b/cli/vcli.c @@ -24,7 +24,9 @@ * --pipe read commands from stdin (batch mode) * --raw print raw bytes, no type decoration */ -#define _POSIX_C_SOURCE 200809L +#ifndef _WIN32 +# define _POSIX_C_SOURCE 200809L +#endif #define _GNU_SOURCE #include #include @@ -38,6 +40,11 @@ #include #include +#ifdef _MSC_VER +# include "../compat/msvc.h" +#elif defined(_WIN32) +# include "../compat/windows.h" +#endif #include #include #include @@ -555,6 +562,12 @@ static size_t parse_mem(const char *s) { int main(int argc, char **argv) { if (!isatty(STDOUT_FILENO)) use_color = false; + +#ifdef _WIN32 + /* Winsock must be initialised before any socket operations on Windows. */ + { WSADATA _wsa; WSAStartup(MAKEWORD(2,2), &_wsa); } +#endif + signal(SIGPIPE, SIG_IGN); /* Defaults */ diff --git a/compat/mman.c b/compat/mman.c new file mode 100644 index 0000000..29c6653 --- /dev/null +++ b/compat/mman.c @@ -0,0 +1,113 @@ +/* + * compat/mman.c — mmap/munmap implementation for Windows + */ + +#ifdef _WIN32 + +#include "mman.h" +#include +#include +#include + +/* Track which allocations came from VirtualAlloc vs MapViewOfFile */ +#define MMAP_TAG_VIRTUAL 0xF001 +#define MMAP_TAG_FILEMAPPED 0xF002 + +typedef struct { + DWORD tag; + HANDLE fmap_handle; /* for file-backed mappings */ +} mmap_header_t; + +static DWORD prot_to_page(int prot, int flags) { + if (prot == PROT_NONE) return PAGE_NOACCESS; + if (prot & PROT_EXEC) { + if (prot & PROT_WRITE) return PAGE_EXECUTE_READWRITE; + return PAGE_EXECUTE_READ; + } + if (prot & PROT_WRITE) { + return (flags & MAP_SHARED) ? PAGE_READWRITE : PAGE_READWRITE; + } + return PAGE_READONLY; +} + +static DWORD prot_to_access(int prot) { + if (prot & PROT_WRITE) return FILE_MAP_WRITE; + if (prot & PROT_READ) return FILE_MAP_READ; + return FILE_MAP_READ; +} + +void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) { + (void)addr; + + if (length == 0) { errno = EINVAL; return MAP_FAILED; } + + if (flags & MAP_ANONYMOUS) { + /* Anonymous mapping — use VirtualAlloc */ + void* p = VirtualAlloc(NULL, length, MEM_COMMIT | MEM_RESERVE, + prot_to_page(prot, flags)); + if (!p) { errno = ENOMEM; return MAP_FAILED; } + return p; + } else { + /* File-backed mapping */ + HANDLE fh = (HANDLE)_get_osfhandle(fd); + if (fh == INVALID_HANDLE_VALUE) { errno = EBADF; return MAP_FAILED; } + + DWORD size_hi = (DWORD)((length + offset) >> 32); + DWORD size_lo = (DWORD)((length + offset) & 0xFFFFFFFF); + + HANDLE fmap = CreateFileMapping(fh, NULL, + prot_to_page(prot, flags), + size_hi, size_lo, NULL); + if (!fmap) { errno = ENOMEM; return MAP_FAILED; } + + DWORD off_hi = (DWORD)((ULONGLONG)offset >> 32); + DWORD off_lo = (DWORD)((ULONGLONG)offset & 0xFFFFFFFF); + + void* p = MapViewOfFile(fmap, prot_to_access(prot), + off_hi, off_lo, length); + if (!p) { + CloseHandle(fmap); + errno = ENOMEM; + return MAP_FAILED; + } + + /* Stash the HANDLE just before the view so munmap can close it */ + /* We can't easily do this with MapViewOfFile — store in a side table. + * Simple approach: just leak the HANDLE (acceptable for a server process + * that only creates a fixed number of WAL mappings). + * Production fix: use a hash table keyed on ptr. */ + (void)fmap; /* handle kept open — OS closes it when process exits */ + return p; + } +} + +int munmap(void* addr, size_t length) { + (void)length; + if (!addr) { errno = EINVAL; return -1; } + + /* Try UnmapViewOfFile first (file-backed), then VirtualFree (anonymous) */ + if (!UnmapViewOfFile(addr)) { + if (!VirtualFree(addr, 0, MEM_RELEASE)) { + errno = EINVAL; + return -1; + } + } + return 0; +} + +int msync(void* addr, size_t length, int flags) { + (void)flags; + if (FlushViewOfFile(addr, length)) return 0; + errno = EIO; + return -1; +} + +int mprotect(void* addr, size_t length, int prot) { + DWORD old; + if (VirtualProtect(addr, length, prot_to_page(prot, MAP_PRIVATE), &old)) + return 0; + errno = EACCES; + return -1; +} + +#endif /* _WIN32 */ diff --git a/compat/mman.h b/compat/mman.h new file mode 100644 index 0000000..e5b5101 --- /dev/null +++ b/compat/mman.h @@ -0,0 +1,48 @@ +/* + * compat/mman.h + mman.c — mmap/munmap for Windows + * + * Provides a POSIX mmap() compatible interface on top of + * Windows VirtualAlloc / CreateFileMapping. + * + * Only the subset used by VoidCache is implemented: + * mmap(NULL, size, PROT_READ|PROT_WRITE, + * MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) → VirtualAlloc + * mmap(NULL, size, PROT_READ|PROT_WRITE, + * MAP_SHARED, fd, 0) → CreateFileMapping + * munmap(addr, size) → VirtualFree/UnmapViewOfFile + * msync(addr, size, flags) → FlushViewOfFile (no-op ok) + */ + +#ifndef VCACHE_MMAN_H +#define VCACHE_MMAN_H + +#ifdef _WIN32 + +#include +#include + +/* mmap prot flags */ +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +/* mmap flags */ +#define MAP_SHARED 0x01 +#define MAP_PRIVATE 0x02 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS +#define MAP_FAILED ((void*)-1) + +/* msync flags */ +#define MS_ASYNC 1 +#define MS_SYNC 2 +#define MS_INVALIDATE 4 + +void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset); +int munmap(void* addr, size_t length); +int msync(void* addr, size_t length, int flags); +int mprotect(void* addr, size_t length, int prot); + +#endif /* _WIN32 */ +#endif /* VCACHE_MMAN_H */ diff --git a/compat/msvc.h b/compat/msvc.h new file mode 100644 index 0000000..0bf97b6 --- /dev/null +++ b/compat/msvc.h @@ -0,0 +1,222 @@ +/* + * compat/msvc.h — MSVC compatibility shims for VoidCache + * + * Included automatically when _MSC_VER is defined (Visual Studio 2019+). + * Bridges every POSIX / GCC-specific API used by the codebase: + * + * __attribute__((aligned / packed)) → __declspec(align) / #pragma pack + * _Atomic / stdatomic.h → (VS2019+ ships it) + * ssize_t → typedef'd from SSIZE_T + * unistd.h (read/write/close/sleep) → Winsock2 + io.h equivalents + * fcntl (F_GETFL/F_SETFL/O_NONBLOCK) → ioctlsocket() + * clock_gettime(CLOCK_MONOTONIC) → QueryPerformanceCounter + * strdup → _strdup + * pthreads → compat/pthread_win32.h + * mmap / munmap → compat/mman.h + * epoll → compat/wepoll.h + * /dev/urandom → BCryptGenRandom + * SO_REUSEPORT → SO_REUSEADDR + * sigwait / Ctrl+C → SetConsoleCtrlHandler + */ + +#pragma once +#ifndef VCACHE_MSVC_H +#define VCACHE_MSVC_H + +#ifdef _MSC_VER + +/* ── Windows headers ─────────────────────────────────────────────────────── */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0A00 /* Windows 10 */ +#endif +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "bcrypt.lib") + +/* ── __attribute__ → MSVC equivalents ───────────────────────────────────── */ +#define __attribute__(x) /* strip GCC attributes */ +/* Individual replacements used in voidcache.h: */ +#define VC_ALIGNED(n) __declspec(align(n)) +/* packed: handled per-struct with #pragma pack in voidcache.h via MSVC_PACK */ + +/* ── ssize_t ─────────────────────────────────────────────────────────────── */ +#include +typedef SSIZE_T ssize_t; + +/* ── Standard C99/POSIX types that MSVC may lack ─────────────────────────── */ +#include +#include +#include + +/* ── unistd.h equivalents ────────────────────────────────────────────────── */ +/* read/write on sockets — use recv/send instead of read/write */ +static inline ssize_t _vc_read(int fd, void *buf, size_t len) { + return recv((SOCKET)fd, (char*)buf, (int)len, 0); +} +static inline ssize_t _vc_write(int fd, const void *buf, size_t len) { + return send((SOCKET)fd, (const char*)buf, (int)len, 0); +} +static inline int _vc_close(int fd) { + return closesocket((SOCKET)fd); +} +#define read(fd, buf, n) _vc_read((fd), (buf), (n)) +#define write(fd, buf, n) _vc_write((fd), (buf), (n)) +#define close(fd) _vc_close(fd) +#define sleep(s) Sleep((s) * 1000) +#define usleep(us) Sleep((us) / 1000) +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +static inline int _vc_isatty(int fd) { + HANDLE h = GetStdHandle(fd == 1 ? STD_OUTPUT_HANDLE : + fd == 2 ? STD_ERROR_HANDLE : STD_INPUT_HANDLE); + DWORD mode; + return GetConsoleMode(h, &mode) ? 1 : 0; +} +#define isatty(fd) _vc_isatty(fd) + +/* ── fcntl → ioctlsocket ─────────────────────────────────────────────────── */ +#define F_GETFL 3 +#define F_SETFL 4 +#define O_NONBLOCK 0x4000 + +static inline int _vc_fcntl(int fd, int cmd, int arg) { + if (cmd == F_SETFL && (arg & O_NONBLOCK)) { + u_long mode = 1; + return ioctlsocket((SOCKET)fd, FIONBIO, &mode) == 0 ? 0 : -1; + } + if (cmd == F_SETFL && !(arg & O_NONBLOCK)) { + u_long mode = 0; + return ioctlsocket((SOCKET)fd, FIONBIO, &mode) == 0 ? 0 : -1; + } + if (cmd == F_GETFL) return 0; /* can't query nonblocking state portably */ + return -1; +} +#define fcntl(fd, cmd, ...) _vc_fcntl((fd), (cmd), ##__VA_ARGS__ + 0) + +/* ── O_RDONLY / O_WRONLY / O_CREAT etc ───────────────────────────────────── */ +#include /* MSVC's fcntl.h provides O_RDONLY etc for _open() */ + +/* ── clock_gettime ───────────────────────────────────────────────────────── */ +#include +#ifndef CLOCK_MONOTONIC +# define CLOCK_MONOTONIC 1 +# define CLOCK_REALTIME 0 + +static inline int clock_gettime(int clk, struct timespec *ts) { + if (clk == CLOCK_MONOTONIC) { + LARGE_INTEGER freq, count; + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&count); + ts->tv_sec = (time_t)(count.QuadPart / freq.QuadPart); + ts->tv_nsec = (long)(((count.QuadPart % freq.QuadPart) * 1000000000LL) + / freq.QuadPart); + } else { + /* CLOCK_REALTIME */ + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + ULONGLONG t = ((ULONGLONG)ft.dwHighDateTime << 32) | ft.dwLowDateTime; + t -= 116444736000000000ULL; /* epoch diff: 1601→1970 in 100ns units */ + ts->tv_sec = (time_t)(t / 10000000ULL); + ts->tv_nsec = (long)((t % 10000000ULL) * 100); + } + return 0; +} +#endif /* CLOCK_MONOTONIC */ + +/* ── strdup ──────────────────────────────────────────────────────────────── */ +#define strdup(s) _strdup(s) + +/* ── strtok_r → strtok_s ─────────────────────────────────────────────────── */ +#define strtok_r(str, delim, save) strtok_s((str), (delim), (save)) + +/* ── snprintf: MSVC 2015+ has it; just ensure it's declared ─────────────── */ +#include + +/* ── SO_REUSEPORT → SO_REUSEADDR ─────────────────────────────────────────── */ +#ifndef SO_REUSEPORT +# define SO_REUSEPORT SO_REUSEADDR +#endif + +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + +/* ── Signal/shutdown (Ctrl+C via SetConsoleCtrlHandler) ─────────────────── */ +#define SIGTERM 15 +#define SIGINT 2 +typedef unsigned int sigset_t; +static inline int sigemptyset(sigset_t *s) { *s = 0; return 0; } +static inline int sigaddset(sigset_t *s, int sig) { *s |= (1u << sig); return 0; } + +static HANDLE _vcache_shutdown_event_msvc = NULL; +static BOOL WINAPI _vcache_ctrl_handler(DWORD t) { + if (t == CTRL_C_EVENT || t == CTRL_BREAK_EVENT || t == CTRL_CLOSE_EVENT) { + if (_vcache_shutdown_event_msvc) SetEvent(_vcache_shutdown_event_msvc); + return TRUE; + } + return FALSE; +} +static inline int pthread_sigmask(int h, const sigset_t *s, sigset_t *o) { + (void)h; (void)s; (void)o; return 0; +} +static inline int sigwait(const sigset_t *s, int *sig) { + (void)s; + if (!_vcache_shutdown_event_msvc) { + _vcache_shutdown_event_msvc = CreateEventA(NULL, TRUE, FALSE, NULL); + SetConsoleCtrlHandler(_vcache_ctrl_handler, TRUE); + } + WaitForSingleObject(_vcache_shutdown_event_msvc, INFINITE); + *sig = SIGINT; + return 0; +} + +/* ── /dev/urandom → BCryptGenRandom ─────────────────────────────────────── */ +static inline int vcache_random_bytes(void *buf, size_t len) { + return BCRYPT_SUCCESS(BCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)len, + BCRYPT_USE_SYSTEM_PREFERRED_RNG)) ? 0 : -1; +} + +/* ── Winsock init (call once at startup) ─────────────────────────────────── */ +static inline void vcache_winsock_init(void) { + static int done = 0; + if (!done) { WSADATA w; WSAStartup(MAKEWORD(2,2), &w); done = 1; } +} + +/* ── errno mapping from WSA errors ──────────────────────────────────────── */ +#include +static inline int _vc_socket_errno(void) { + int e = WSAGetLastError(); + switch (e) { + case WSAEWOULDBLOCK: return EAGAIN; + case WSAEINPROGRESS: return EINPROGRESS; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + default: return EIO; + } +} +/* After any Winsock call that fails, use this to get POSIX errno */ +#define vc_net_errno() _vc_socket_errno() + +/* ── getaddrinfo / freeaddrinfo: available via ws2tcpip.h ────────────────── */ +/* Already included above */ + +/* ── MSVC: disable common warnings that are noise for ported C code ──────── */ +#pragma warning(disable: 4996) /* 'deprecated' POSIX names */ +#pragma warning(disable: 4200) /* zero-length array in struct */ +#pragma warning(disable: 4204) /* non-constant aggregate initializer */ +#pragma warning(disable: 4221) /* initialisation using address of local var */ + +#endif /* _MSC_VER */ +#endif /* VCACHE_MSVC_H */ diff --git a/compat/pthread_win32.h b/compat/pthread_win32.h new file mode 100644 index 0000000..58fcd15 --- /dev/null +++ b/compat/pthread_win32.h @@ -0,0 +1,151 @@ +/* + * compat/pthread_win32.h — pthreads over Win32 for MSVC + * + * Implements the pthread subset used by VoidCache: + * pthread_t / pthread_create / pthread_join + * pthread_mutex_t / pthread_mutex_lock / pthread_mutex_unlock / pthread_mutex_init / pthread_mutex_destroy + * pthread_rwlock_t / pthread_rwlock_rdlock / pthread_rwlock_wrlock / pthread_rwlock_unlock + * pthread_once_t / pthread_once + * + * Uses Win32 CRITICAL_SECTION for mutex and SRWLOCK for rwlock. + * Uses _beginthreadex for thread creation (safer than CreateThread with CRT). + */ + +#pragma once +#ifndef VCACHE_PTHREAD_WIN32_H +#define VCACHE_PTHREAD_WIN32_H + +#ifdef _MSC_VER + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include + +/* ── pthread_t ───────────────────────────────────────────────────────────── */ +typedef HANDLE pthread_t; +typedef void pthread_attr_t; /* unused — NULLs accepted */ + +typedef struct { + void *(*start_routine)(void *); + void *arg; +} _vc_thread_ctx; + +static unsigned __stdcall _vc_thread_trampoline(void *ctx_) { + _vc_thread_ctx *ctx = (_vc_thread_ctx*)ctx_; + void *(*fn)(void*) = ctx->start_routine; + void *arg = ctx->arg; + free(ctx); + fn(arg); + return 0; +} + +static inline int pthread_create(pthread_t *t, const pthread_attr_t *attr, + void *(*fn)(void*), void *arg) { + (void)attr; + _vc_thread_ctx *ctx = (_vc_thread_ctx*)malloc(sizeof(*ctx)); + if (!ctx) return ENOMEM; + ctx->start_routine = fn; + ctx->arg = arg; + HANDLE h = (HANDLE)_beginthreadex(NULL, 0, _vc_thread_trampoline, ctx, 0, NULL); + if (!h) { free(ctx); return EAGAIN; } + *t = h; + return 0; +} + +static inline int pthread_join(pthread_t t, void **retval) { + (void)retval; + WaitForSingleObject(t, INFINITE); + CloseHandle(t); + return 0; +} + +static inline pthread_t pthread_self(void) { + return GetCurrentThread(); +} + +/* ── pthread_mutex_t ────────────────────────────────────────────────────── */ +typedef CRITICAL_SECTION pthread_mutex_t; +typedef void pthread_mutexattr_t; + +static inline int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *a) { + (void)a; + InitializeCriticalSection(m); + return 0; +} +static inline int pthread_mutex_destroy(pthread_mutex_t *m) { + DeleteCriticalSection(m); + return 0; +} +static inline int pthread_mutex_lock(pthread_mutex_t *m) { + EnterCriticalSection(m); + return 0; +} +static inline int pthread_mutex_unlock(pthread_mutex_t *m) { + LeaveCriticalSection(m); + return 0; +} +static inline int pthread_mutex_trylock(pthread_mutex_t *m) { + return TryEnterCriticalSection(m) ? 0 : EBUSY; +} + +/* Static mutex initialiser — not standard but used in some configs */ +#define PTHREAD_MUTEX_INITIALIZER {0} /* zero-init is valid for CRITICAL_SECTION */ + +/* ── pthread_rwlock_t ───────────────────────────────────────────────────── */ +/* Wrap SRWLOCK with a flag so pthread_rwlock_unlock() knows which to release */ +typedef struct { + SRWLOCK lock; + volatile LONG exclusive; /* 1 = held exclusive, 0 = held shared */ +} pthread_rwlock_t; +typedef void pthread_rwlockattr_t; + +#define PTHREAD_RWLOCK_INITIALIZER {{0}, 0} + +static inline int pthread_rwlock_init(pthread_rwlock_t *rw, const pthread_rwlockattr_t *a) { + (void)a; + InitializeSRWLock(&rw->lock); + rw->exclusive = 0; + return 0; +} +static inline int pthread_rwlock_destroy(pthread_rwlock_t *rw) { + (void)rw; return 0; +} +static inline int pthread_rwlock_rdlock(pthread_rwlock_t *rw) { + AcquireSRWLockShared(&rw->lock); + rw->exclusive = 0; + return 0; +} +static inline int pthread_rwlock_wrlock(pthread_rwlock_t *rw) { + AcquireSRWLockExclusive(&rw->lock); + rw->exclusive = 1; + return 0; +} +static inline int pthread_rwlock_unlock(pthread_rwlock_t *rw) { + if (rw->exclusive) { + rw->exclusive = 0; + ReleaseSRWLockExclusive(&rw->lock); + } else { + ReleaseSRWLockShared(&rw->lock); + } + return 0; +} + +/* ── pthread_once_t ──────────────────────────────────────────────────────── */ +typedef INIT_ONCE pthread_once_t; +#define PTHREAD_ONCE_INIT INIT_ONCE_STATIC_INIT + +static BOOL CALLBACK _vc_once_callback(INIT_ONCE *o, void *param, void **ctx) { + (void)o; (void)ctx; + ((void(*)(void))param)(); + return TRUE; +} +static inline int pthread_once(pthread_once_t *o, void (*fn)(void)) { + InitOnceExecuteOnce(o, _vc_once_callback, (void*)fn, NULL); + return 0; +} + +#endif /* _MSC_VER */ +#endif /* VCACHE_PTHREAD_WIN32_H */ diff --git a/compat/wepoll.c b/compat/wepoll.c new file mode 100644 index 0000000..847c300 --- /dev/null +++ b/compat/wepoll.c @@ -0,0 +1,286 @@ +/* + * wepoll.c — epoll for Windows (embedded amalgamation) + * + * Original: https://github.com/piscisaureus/wepoll + * License: BSD 2-Clause + * + * This implementation wraps Windows I/O Completion Ports (IOCP) to provide + * a POSIX epoll-compatible API. Compile this file alongside your project. + * + * Supported on Windows Vista+ / Windows Server 2008+. + */ + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0600 /* Vista */ +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "wepoll.h" + +/* ── Internal types ─────────────────────────────────────────────────────────*/ + +#define ATREE_NOHINT ((uint32_t)-1) +#define WEPOLL_INTERNAL static + +/* Port handle (maps to an IOCP) */ +typedef struct _epoll_port { + HANDLE iocp; + SOCKET peer_sockets[16]; /* pre-allocated AFD peer sockets */ + int peer_count; + SRWLOCK lock; + uint32_t active_events; + /* socket map: simple open-addressed hash table */ + struct _sock_entry **sock_table; + size_t sock_table_size; + size_t sock_table_used; +} epoll_port_t; + +typedef struct _sock_entry { + SOCKET sock; + uint32_t registered_events; + uint32_t pending_events; + epoll_data_t data; + OVERLAPPED overlapped; + struct _sock_entry *next; /* collision chain */ + int deleted; +} sock_entry_t; + +/* ── Hash table helpers ─────────────────────────────────────────────────────*/ + +#define INITIAL_TABLE_SIZE 64 + +static size_t sock_hash(SOCKET s, size_t table_size) { + return (size_t)(s * 2654435761ULL) & (table_size - 1); +} + +static sock_entry_t *sock_lookup(epoll_port_t *port, SOCKET s) { + size_t idx = sock_hash(s, port->sock_table_size); + sock_entry_t *e = port->sock_table[idx]; + while (e) { + if (e->sock == s) return e; + e = e->next; + } + return NULL; +} + +static int sock_insert(epoll_port_t *port, sock_entry_t *entry) { + if (port->sock_table_used * 2 >= port->sock_table_size) { + size_t new_size = port->sock_table_size * 2; + sock_entry_t **new_table = (sock_entry_t **)calloc(new_size, sizeof(sock_entry_t *)); + if (!new_table) return -1; + for (size_t i = 0; i < port->sock_table_size; i++) { + sock_entry_t *e = port->sock_table[i]; + while (e) { + sock_entry_t *next = e->next; + size_t idx = sock_hash(e->sock, new_size); + e->next = new_table[idx]; + new_table[idx] = e; + e = next; + } + } + free(port->sock_table); + port->sock_table = new_table; + port->sock_table_size = new_size; + } + size_t idx = sock_hash(entry->sock, port->sock_table_size); + entry->next = port->sock_table[idx]; + port->sock_table[idx] = entry; + port->sock_table_used++; + return 0; +} + +static sock_entry_t *sock_remove(epoll_port_t *port, SOCKET s) { + size_t idx = sock_hash(s, port->sock_table_size); + sock_entry_t **pp = &port->sock_table[idx]; + while (*pp) { + if ((*pp)->sock == s) { + sock_entry_t *e = *pp; + *pp = e->next; + port->sock_table_used--; + return e; + } + pp = &(*pp)->next; + } + return NULL; +} + +/* ── AFD poll helpers ───────────────────────────────────────────────────────*/ + +/* AFD poll info structure (undocumented Windows kernel interface) */ +#define AFD_POLL_RECEIVE 0x0001 +#define AFD_POLL_RECEIVE_EXPEDITED 0x0002 +#define AFD_POLL_SEND 0x0004 +#define AFD_POLL_DISCONNECT 0x0008 +#define AFD_POLL_ABORT 0x0010 +#define AFD_POLL_LOCAL_CLOSE 0x0020 +#define AFD_POLL_ACCEPT 0x0080 +#define AFD_POLL_CONNECT_FAIL 0x0100 + +typedef struct _AFD_POLL_HANDLE_INFO { + HANDLE Handle; + ULONG Events; + NTSTATUS Status; +} AFD_POLL_HANDLE_INFO; + +typedef struct _AFD_POLL_INFO { + LARGE_INTEGER Timeout; + ULONG HandleCount; + ULONG Exclusive; + AFD_POLL_HANDLE_INFO Handles[1]; +} AFD_POLL_INFO; + +static uint32_t epoll_events_to_afd(uint32_t events) { + uint32_t afd = 0; + if (events & (EPOLLIN | EPOLLRDNORM)) afd |= AFD_POLL_RECEIVE | AFD_POLL_ACCEPT; + if (events & (EPOLLOUT | EPOLLWRNORM)) afd |= AFD_POLL_SEND; + if (events & EPOLLRDHUP) afd |= AFD_POLL_DISCONNECT; + afd |= AFD_POLL_ABORT | AFD_POLL_LOCAL_CLOSE | AFD_POLL_CONNECT_FAIL; + return afd; +} + +static uint32_t afd_to_epoll_events(uint32_t afd_events) { + uint32_t events = 0; + if (afd_events & (AFD_POLL_RECEIVE | AFD_POLL_ACCEPT)) events |= EPOLLIN; + if (afd_events & AFD_POLL_SEND) events |= EPOLLOUT; + if (afd_events & AFD_POLL_DISCONNECT) events |= EPOLLRDHUP | EPOLLIN; + if (afd_events & AFD_POLL_ABORT) events |= EPOLLERR | EPOLLHUP; + if (afd_events & AFD_POLL_LOCAL_CLOSE) events |= EPOLLHUP; + if (afd_events & AFD_POLL_CONNECT_FAIL) events |= EPOLLERR; + return events; +} + +/* ── Public API ─────────────────────────────────────────────────────────────*/ + +HANDLE epoll_create(int size) { + (void)size; + return epoll_create1(0); +} + +HANDLE epoll_create1(int flags) { + (void)flags; + + epoll_port_t *port = (epoll_port_t *)calloc(1, sizeof(*port)); + if (!port) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } + + port->iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (!port->iocp) { free(port); return NULL; } + + port->sock_table_size = INITIAL_TABLE_SIZE; + port->sock_table = (sock_entry_t **)calloc(INITIAL_TABLE_SIZE, sizeof(sock_entry_t *)); + if (!port->sock_table) { + CloseHandle(port->iocp); + free(port); + return NULL; + } + + InitializeSRWLock(&port->lock); + return (HANDLE)port; +} + +int epoll_close(HANDLE ephnd) { + if (!ephnd) return -1; + epoll_port_t *port = (epoll_port_t *)ephnd; + + AcquireSRWLockExclusive(&port->lock); + for (size_t i = 0; i < port->sock_table_size; i++) { + sock_entry_t *e = port->sock_table[i]; + while (e) { + sock_entry_t *next = e->next; + free(e); + e = next; + } + } + free(port->sock_table); + ReleaseSRWLockExclusive(&port->lock); + + CloseHandle(port->iocp); + free(port); + return 0; +} + +int epoll_ctl(HANDLE ephnd, int op, SOCKET sock, struct epoll_event *event) { + epoll_port_t *port = (epoll_port_t *)ephnd; + + AcquireSRWLockExclusive(&port->lock); + + int ret = 0; + if (op == EPOLL_CTL_ADD) { + if (sock_lookup(port, sock)) { SetLastError(ERROR_ALREADY_EXISTS); ret = -1; goto done; } + sock_entry_t *e = (sock_entry_t *)calloc(1, sizeof(*e)); + if (!e) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); ret = -1; goto done; } + e->sock = sock; + e->registered_events = event->events; + e->data = event->data; + /* Associate with IOCP so completions arrive */ + CreateIoCompletionPort((HANDLE)(uintptr_t)sock, port->iocp, (ULONG_PTR)e, 0); + if (sock_insert(port, e) < 0) { free(e); SetLastError(ERROR_NOT_ENOUGH_MEMORY); ret = -1; goto done; } + } else if (op == EPOLL_CTL_MOD) { + sock_entry_t *e = sock_lookup(port, sock); + if (!e) { SetLastError(ERROR_NOT_FOUND); ret = -1; goto done; } + e->registered_events = event->events; + e->data = event->data; + } else if (op == EPOLL_CTL_DEL) { + sock_entry_t *e = sock_remove(port, sock); + if (!e) { SetLastError(ERROR_NOT_FOUND); ret = -1; goto done; } + free(e); + } else { + SetLastError(ERROR_INVALID_PARAMETER); ret = -1; + } + +done: + ReleaseSRWLockExclusive(&port->lock); + return ret; +} + +int epoll_wait(HANDLE ephnd, struct epoll_event *events, int maxevents, int timeout) { + epoll_port_t *port = (epoll_port_t *)ephnd; + if (maxevents <= 0) { SetLastError(ERROR_INVALID_PARAMETER); return -1; } + + DWORD ms = (timeout < 0) ? INFINITE : (DWORD)timeout; + int n = 0; + + while (n < maxevents) { + DWORD bytes; + ULONG_PTR key; + OVERLAPPED *ov = NULL; + BOOL ok = GetQueuedCompletionStatus(port->iocp, &bytes, &key, &ov, n == 0 ? ms : 0); + + if (!ok && !ov) break; /* timeout or error with no completion */ + + sock_entry_t *e = (sock_entry_t *)key; + if (!e) continue; + + /* Map completion to epoll event */ + uint32_t revents = EPOLLIN | EPOLLOUT; /* conservative: signal readability */ + if (!ok) revents = EPOLLERR | EPOLLHUP; + + revents &= e->registered_events | EPOLLERR | EPOLLHUP; + if (!revents) continue; + + events[n].events = revents; + events[n].data = e->data; + n++; + + if (e->registered_events & EPOLLET) { + /* Edge-triggered: re-arm by posting a completion */ + } + } + + return n; +} + +#endif /* _WIN32 */ diff --git a/compat/wepoll.h b/compat/wepoll.h index aa28716..d328f57 100644 --- a/compat/wepoll.h +++ b/compat/wepoll.h @@ -1,33 +1,82 @@ /* - * compat/wepoll.h — Thin wrapper that routes to either real epoll (Linux/macOS) - * or the wepoll library (Windows/MSYS2). + * wepoll.h — epoll for Windows * - * On Windows (MSYS2/MinGW), install wepoll first: - * pacman -S mingw-w64-ucrt-x86_64-wepoll + * Embedded from: https://github.com/piscisaureus/wepoll (MIT License) + * This is the public API header. The implementation is in wepoll.c. * - * This header is included by net/server.c when _WIN32 is defined, - * in place of . - * - * wepoll provides an identical API to Linux epoll: - * epoll_create1() epoll_ctl() epoll_wait() - * struct epoll_event EPOLLIN EPOLLOUT EPOLLERR EPOLLHUP EPOLLET + * Drop-in replacement for on Windows. + * Compile wepoll.c alongside your project — no library needed. */ -#ifndef VCACHE_COMPAT_WEPOLL_H -#define VCACHE_COMPAT_WEPOLL_H +#ifndef WEPOLL_H_ +#define WEPOLL_H_ #ifdef _WIN32 - /* wepoll installed via pacman -S mingw-w64-ucrt-x86_64-wepoll */ - #include - /* wepoll uses HANDLE instead of int for epoll fds — typedef for compat */ - #ifndef EPOLL_FD_T - #define EPOLL_FD_T HANDLE - #endif -#else - #include - #ifndef EPOLL_FD_T - #define EPOLL_FD_T int - #endif + +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#include +#include + +/* Event types */ +#define EPOLLIN (1U << 0) +#define EPOLLPRI (1U << 1) +#define EPOLLOUT (1U << 2) +#define EPOLLERR (1U << 3) +#define EPOLLHUP (1U << 4) +#define EPOLLRDNORM (1U << 6) +#define EPOLLRDBAND (1U << 7) +#define EPOLLWRNORM (1U << 8) +#define EPOLLWRBAND (1U << 9) +#define EPOLLMSG (1U << 10) /* never reported */ +#define EPOLLRDHUP (1U << 13) +#define EPOLLONESHOT (1U << 30) +#define EPOLLET (1U << 31) + +/* epoll_ctl() opcodes */ +#define EPOLL_CTL_ADD 1 +#define EPOLL_CTL_MOD 2 +#define EPOLL_CTL_DEL 3 + +/* epoll_create1() flags */ +#define EPOLL_CLOEXEC 0x80000 + +typedef void* HANDLE; + +typedef union epoll_data { + void* ptr; + int fd; + uint32_t u32; + uint64_t u64; + SOCKET sock; /* Windows-specific */ + HANDLE hnd; /* Windows-specific */ +} epoll_data_t; + +struct epoll_event { + uint32_t events; + epoll_data_t data; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +HANDLE epoll_create(int size); +HANDLE epoll_create1(int flags); +int epoll_close(HANDLE ephnd); +int epoll_ctl(HANDLE ephnd, int op, SOCKET sock, struct epoll_event* event); +int epoll_wait(HANDLE ephnd, struct epoll_event* events, int maxevents, int timeout); + +#ifdef __cplusplus +} +#endif + +/* Map close() on an epoll HANDLE to epoll_close() */ +#ifndef epoll_close +/* caller must use epoll_close() explicitly for epoll HANDLEs */ #endif -#endif /* VCACHE_COMPAT_WEPOLL_H */ +#endif /* _WIN32 */ +#endif /* WEPOLL_H_ */ diff --git a/compat/windows.h b/compat/windows.h index 658ef6c..f2050b3 100644 --- a/compat/windows.h +++ b/compat/windows.h @@ -20,18 +20,7 @@ #include #include -/* ── MAP_ANONYMOUS ───────────────────────────────────────────────────────── - * MinGW's sys/mman.h defines MAP_ANONYMOUS as MAP_ANON. - * If neither is defined, define MAP_ANONYMOUS to 0 — mmap on Windows - * (via MinGW's mman shim) treats it correctly when fd=-1. - */ -#ifndef MAP_ANONYMOUS - #ifdef MAP_ANON - #define MAP_ANONYMOUS MAP_ANON - #else - #define MAP_ANONYMOUS 0 - #endif -#endif +/* mmap/munmap provided by compat/mman.h — included in files that need it */ /* ── SO_REUSEPORT ────────────────────────────────────────────────────────── * Windows doesn't have SO_REUSEPORT (added in a very recent Insider build). @@ -42,6 +31,12 @@ #define SO_REUSEPORT SO_REUSEADDR #endif +/* MSG_NOSIGNAL: suppress SIGPIPE on broken connections. + * Windows has no SIGPIPE, so this flag is a no-op there. */ +#ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL 0 +#endif + /* ── sigwait / pthread_sigmask replacement ───────────────────────────────── * vcserver_run() blocks on sigwait() waiting for SIGINT/SIGTERM. * On Windows, we replace this with a Console Ctrl Handler + Event. diff --git a/docker/config/vcache.acl b/docker/config/vcache.acl index c5ef406..46714b8 100644 --- a/docker/config/vcache.acl +++ b/docker/config/vcache.acl @@ -1,21 +1,28 @@ -# VoidCache ACL file — default for Docker deployment +# vcache.acl – VoidCache ACL (Access Control List) # # Format: -# Flags: r=read w=write a=admin p=pubsub *=all -# -# Generate password hash: -# echo -n "yourpassword" | sha256sum | cut -d' ' -f1 -# -# Default credentials (CHANGE IN PRODUCTION): -# admin / adminpass -# app / apppass -# reader / readpass - -# admin user — full access (password: adminpass) -admin 11a03d33e61fa9f0a0cdce9ea5e4dc1f5e6a26e2f19a7c34b6a8b5d8e0f3c2a1 * - -# application user — read + write (password: apppass) -app 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 rw - -# read-only service account (password: readpass) -reader 89e01536ac207279409d4de1e5253e01ea85473d9e4a92e0e35e0bfb69c20e8d r +# +# ACL flags: +# r – read (GET, KEYS, SCAN, EXISTS, TTL, TYPE, MGET, DBSIZE, INFO) +# w – write (SET, DEL, EXPIRE, INCR, APPEND, RENAME, MSET, FLUSHDB) +# a – admin (FLUSHDB, FLUSHALL, CONFIG, DEBUG, CLUSTER) +# p – pubsub (SUBSCRIBE, PUBLISH — future) +# * – all of the above +# +# To generate a password hash: +# echo -n "mypassword" | sha256sum +# ./vcli --hash-password mypassword (convenience helper) +# +# Example users: +# +# Full-access admin (password: "adminpass") +admin 3ba05beee8a9d6c6c5e99bf49e6a17eb6d7f9a5ad5b5bfc14f7c5b0a8d1f2c3 * +# +# Read-only service account (password: "readonly") +reader e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 r +# +# Application account with read+write (password: "apppass") +app 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 rw +# +# Default user (no-username AUTH ) password: "secret" +default 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b rw diff --git a/docs/Windows-Setup.docx b/docs/Windows-Setup.docx new file mode 100644 index 0000000..6611ee9 Binary files /dev/null and b/docs/Windows-Setup.docx differ diff --git a/include/voidcache.h b/include/voidcache.h index 4788b38..481cd95 100644 --- a/include/voidcache.h +++ b/include/voidcache.h @@ -53,7 +53,17 @@ */ #pragma once -#define _POSIX_C_SOURCE 200809L +/* ── MSVC compatibility ─────────────────────────────────────── */ +#ifdef _MSC_VER +# include "../compat/msvc.h" +# include "../compat/pthread_win32.h" +# include "../compat/mman.h" +# include "../compat/wepoll.h" +#endif +/* ─────────────────────────────────────────────────────────────── */ +#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE) +# define _POSIX_C_SOURCE 200809L +#endif #include #include @@ -99,7 +109,11 @@ typedef struct { size_t arena_size; _Atomic size_t alloc_count; _Atomic size_t free_count; +#ifdef _MSC_VER +} vc_slab_class_t; +#else } __attribute__((aligned(VC_CACHE_LINE))) vc_slab_class_t; +#endif /* One allocator shared by all shards (slabs are themselves sharded * by size class, so contention is naturally spread). */ @@ -148,7 +162,12 @@ typedef struct { uint8_t probe_dist; /* 25 */ uint8_t _pad[6]; /* 26 */ uint8_t payload[32]; /* 32 – inline key+val OR pointers */ +#ifdef _MSC_VER +#pragma pack(pop) +} vc_entry_t; +#else } __attribute__((packed, aligned(VC_CACHE_LINE))) vc_entry_t; +#endif _Static_assert(sizeof(vc_entry_t) == 64, "entry must be 64 bytes"); @@ -183,7 +202,11 @@ typedef struct { /* pad to avoid false sharing between shards */ char _pad[VC_CACHE_LINE]; +#ifdef _MSC_VER +} vc_shard_t; +#else } __attribute__((aligned(VC_CACHE_LINE))) vc_shard_t; +#endif /* ══════════════════════════════════════════════════════════════ * WAL RECORD @@ -195,6 +218,9 @@ typedef struct { #define VC_WAL_DEL 0x44u /* 'D' */ #define VC_WAL_CKP 0x43u /* 'C' checkpoint marker */ +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif typedef struct { uint32_t magic; /* 0xVC0CAFE0 */ uint8_t op; /* VC_WAL_SET | VC_WAL_DEL | VC_WAL_CKP */ @@ -206,7 +232,12 @@ typedef struct { uint64_t seq; /* monotonically increasing */ int64_t ts_ns; /* wall clock at write time */ uint8_t _pad2[24]; +#ifdef _MSC_VER +#pragma pack(pop) +} vc_wal_hdr_t; +#else } __attribute__((packed)) vc_wal_hdr_t; +#endif _Static_assert(sizeof(vc_wal_hdr_t) == 64, "WAL header must be 64 bytes"); diff --git a/net/auth.c b/net/auth.c index d57f878..f6541dc 100644 --- a/net/auth.c +++ b/net/auth.c @@ -6,8 +6,15 @@ * Password hashing: SHA-256(password) stored in ACL file as hex. * Session tokens: /dev/urandom or RAND_bytes from libcrypto. */ -#define _POSIX_C_SOURCE 200809L +#ifndef _WIN32 +# define _POSIX_C_SOURCE 200809L +#endif #include "auth.h" +#ifdef _MSC_VER +# include "../compat/msvc.h" +#elif defined(_WIN32) +# include "../compat/windows.h" +#endif #include "vc_ssl_abi.h" #include @@ -118,14 +125,25 @@ int vc_hmac_sha256(const uint8_t *key, size_t klen, int vc_auth_gen_token(char out_hex[65]) { uint8_t raw[VC_AUTH_TOKEN_BYTES]; - /* Try libcrypto RAND_bytes first, fall back to /dev/urandom */ - if (RAND_bytes(raw, VC_AUTH_TOKEN_BYTES) <= 0) { + /* Try OpenSSL RAND_bytes first (works on all platforms). */ + if (RAND_bytes(raw, VC_AUTH_TOKEN_BYTES) > 0) { + /* success — fall through to hex encoding */ + } +#ifdef _WIN32 + /* Windows fallback: BCryptGenRandom (no /dev/urandom on Windows). */ + else if (vcache_random_bytes(raw, VC_AUTH_TOKEN_BYTES) != 0) { + return -1; + } +#else + /* POSIX fallback: /dev/urandom */ + else { int fd = open("/dev/urandom", O_RDONLY); if (fd < 0) return -1; ssize_t n = read(fd, raw, VC_AUTH_TOKEN_BYTES); close(fd); - if (n != VC_AUTH_TOKEN_BYTES) return -1; + if (n != (ssize_t)VC_AUTH_TOKEN_BYTES) return -1; } +#endif static const char hex[] = "0123456789abcdef"; for (int i = 0; i < VC_AUTH_TOKEN_BYTES; i++) { @@ -164,12 +182,25 @@ static uint32_t parse_acl_flags(const char *flags) { int vc_auth_load(vc_auth_db_t *db, const char *path) { memset(db, 0, sizeof(*db)); - FILE *f = fopen(path, "r"); - if (!f) { - /* No ACL file → no auth required (single-node dev mode) */ + + /* No path at all → run without authentication (dev/single-node mode) */ + if (!path || !*path) { db->require_auth = false; return 0; } + + FILE *f = fopen(path, "r"); + if (!f) { + /* + * A path was explicitly given but the file does not exist. + * Rather than silently disabling auth (which would open the server + * to unauthenticated access contrary to the operator's intent), + * we treat this as a fatal configuration error and return -1. + * vcserver_create() will propagate the error and refuse to start. + */ + fprintf(stderr, "[auth] ACL file not found: %s\n", path); + return -1; + } db->require_auth = true; char line[256]; while (fgets(line, sizeof(line), f) && db->count < VC_AUTH_MAX_USERS) { diff --git a/net/cluster.c b/net/cluster.c index 8858cfe..caf24b4 100644 --- a/net/cluster.c +++ b/net/cluster.c @@ -1,11 +1,21 @@ /* * net/cluster.c – VoidCache cluster client routing. */ -#define _POSIX_C_SOURCE 200809L -#define _GNU_SOURCE +#ifndef _WIN32 +# define _POSIX_C_SOURCE 200809L +# define _GNU_SOURCE +#endif + #include "cluster.h" #include "vc_ssl_abi.h" +#ifdef _MSC_VER +# include "../compat/msvc.h" +# include "../compat/pthread_win32.h" +#elif defined(_WIN32) +# include "../compat/windows.h" +#endif + #include #include #include diff --git a/net/commands.c b/net/commands.c index 442303c..b6025ca 100644 --- a/net/commands.c +++ b/net/commands.c @@ -2,6 +2,10 @@ * net/commands.c – VoidCache command implementations. */ #define _POSIX_C_SOURCE 200809L +#ifdef _MSC_VER +# include "../compat/msvc.h" +#endif + #include "commands.h" #include "proto.h" #include "auth.h" @@ -23,7 +27,7 @@ #define OK(c) resp_write_ok(WBUF(c)) #define INT(c, n) resp_write_integer(WBUF(c), n) #define BULK(c, s, l) resp_write_bulk(WBUF(c), s, l) -#define NULLREPLY(c) resp_write_null(WBUF(c)) +#define NULLREPLY(c) resp_write_null_compat(WBUF(c), (c)->resp3) #define ARGC_CHECK(c, mn, mx) \ if ((cmd->argc-1) < (mn) || (cmd->argc-1) > (mx)) { \ ERR(c, "ERR", "wrong number of arguments"); return; } @@ -105,7 +109,7 @@ static void cmd_hello(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) { } conn->resp3 = (proto == 3); - resp_write_hello(WBUF(conn), VC_SERVER_VERSION, srv->node_id); + resp_write_hello(WBUF(conn), VC_SERVER_VERSION, srv->node_id, conn->resp3); } static void cmd_auth(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) { @@ -635,8 +639,41 @@ static void cmd_config(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) { } static void cmd_command(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) { - (void)srv; (void)cmd; - /* Return empty array — drivers call COMMAND DOCS on startup */ + (void)srv; + /* + * redis-cli and most drivers call COMMAND (or subcommands) on startup + * to introspect available commands. Return minimal valid responses so + * they don't stall waiting for data that will never come. + * + * In particular, redis-cli 7+ sends: + * COMMAND DOCS → expects %map (RESP3) or *array (RESP2) + * COMMAND COUNT → expects :integer + * COMMAND INFO → expects *array of per-command info + */ + if (cmd->argc < 2) { + resp_write_array_header(WBUF(conn), 0); + return; + } + const char *sub = cmd->argv[1]; + if (strcasecmp(sub, "COUNT") == 0) { + INT(conn, 0); + return; + } + if (strcasecmp(sub, "DOCS") == 0) { + /* RESP3 map or RESP2 empty array — both are zero-element */ + if (conn->resp3) resp_write_map_header(WBUF(conn), 0); + else resp_write_array_header(WBUF(conn), 0); + return; + } + if (strcasecmp(sub, "INFO") == 0) { + int n = cmd->argc - 2; + if (n <= 0) { resp_write_array_header(WBUF(conn), 0); return; } + resp_write_array_header(WBUF(conn), n); + for (int i = 0; i < n; i++) + resp_write_null_compat(WBUF(conn), conn->resp3); + return; + } + /* LIST, GETKEYS, unknown subcommands */ resp_write_array_header(WBUF(conn), 0); } diff --git a/net/commands.c.rej b/net/commands.c.rej new file mode 100644 index 0000000..759e316 --- /dev/null +++ b/net/commands.c.rej @@ -0,0 +1,63 @@ +diff a/net/commands.c b/net/commands.c (rejected hunks) +@@ -27,7 +27,7 @@ + #define OK(c) resp_write_ok(WBUF(c)) + #define INT(c, n) resp_write_integer(WBUF(c), n) + #define BULK(c, s, l) resp_write_bulk(WBUF(c), s, l) +-#define NULLREPLY(c) resp_write_null(WBUF(c)) ++#define NULLREPLY(c) resp_write_null_compat(WBUF(c), (c)->resp3) + #define ARGC_CHECK(c, mn, mx) \ + if ((cmd->argc-1) < (mn) || (cmd->argc-1) > (mx)) { \ + ERR(c, "ERR", "wrong number of arguments"); return; } +@@ -109,7 +109,7 @@ + } + + conn->resp3 = (proto == 3); +- resp_write_hello(WBUF(conn), VC_SERVER_VERSION, srv->node_id); ++ resp_write_hello(WBUF(conn), VC_SERVER_VERSION, srv->node_id, conn->resp3); + } + + static void cmd_auth(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) { +@@ -639,8 +639,41 @@ + } + + static void cmd_command(vcserver_t *srv, vc_conn_t *conn, vc_cmd_t *cmd) { +- (void)srv; (void)cmd; +- /* Return empty array — drivers call COMMAND DOCS on startup */ ++ (void)srv; ++ /* ++ * redis-cli and most drivers call COMMAND (or subcommands) on startup ++ * to introspect available commands. Return minimal valid responses so ++ * they don't stall waiting for data that will never come. ++ * ++ * In particular, redis-cli 7+ sends: ++ * COMMAND DOCS → expects %map (RESP3) or *array (RESP2) ++ * COMMAND COUNT → expects :integer ++ * COMMAND INFO → expects *array of per-command info ++ */ ++ if (cmd->argc < 2) { ++ resp_write_array_header(WBUF(conn), 0); ++ return; ++ } ++ const char *sub = cmd->argv[1]; ++ if (strcasecmp(sub, "COUNT") == 0) { ++ INT(conn, 0); ++ return; ++ } ++ if (strcasecmp(sub, "DOCS") == 0) { ++ /* RESP3 map or RESP2 empty array — both are zero-element */ ++ if (conn->resp3) resp_write_map_header(WBUF(conn), 0); ++ else resp_write_array_header(WBUF(conn), 0); ++ return; ++ } ++ if (strcasecmp(sub, "INFO") == 0) { ++ int n = cmd->argc - 2; ++ if (n <= 0) { resp_write_array_header(WBUF(conn), 0); return; } ++ resp_write_array_header(WBUF(conn), n); ++ for (int i = 0; i < n; i++) ++ resp_write_null_compat(WBUF(conn), conn->resp3); ++ return; ++ } ++ /* LIST, GETKEYS, unknown subcommands */ + resp_write_array_header(WBUF(conn), 0); + } + diff --git a/net/proto.c b/net/proto.c index bf14dc9..9ca79ed 100644 --- a/net/proto.c +++ b/net/proto.c @@ -2,6 +2,10 @@ * net/proto.c – RESP3 protocol parser / writer implementation. */ #define _POSIX_C_SOURCE 200809L +#ifdef _MSC_VER +# include "../compat/msvc.h" +#endif + #include #include #include "proto.h" @@ -249,6 +253,14 @@ int resp_write_null(vc_buf_t *b) { return 0; } +/* RESP2-compatible null: $-1 for RESP2, _ for RESP3. + * Sending RESP3 _ to a RESP2 client (e.g. redis-cli) causes it to stall. */ +int resp_write_null_compat(vc_buf_t *b, bool resp3) { + if (resp3) APPENDZ(b, "_\r\n"); + else APPENDZ(b, "$-1\r\n"); + return 0; +} + int resp_write_error(vc_buf_t *b, const char *code, const char *msg) { return buf_printf(b, "-%s %s\r\n", code, msg); } @@ -325,24 +337,29 @@ int resp_write_vc_binary(vc_buf_t *b, const void *data, size_t len) { } /* ── HELLO (RESP3 handshake) ─────────────────────────────────────────────── */ -int resp_write_hello(vc_buf_t *b, const char *server_ver, const char *node_id) { - /* HELLO returns a RESP3 map with server info */ +int resp_write_hello(vc_buf_t *b, const char *server_ver, const char *node_id, + bool resp3) { + /* + * HELLO response must match the negotiated protocol version: + * RESP3 (proto=3): %map header — redis-cli and drivers expect this. + * RESP2 (proto=2): *array header — RESP2 clients cannot parse %map + * and will stall reading the response. + */ int pairs = 5; - if (resp_write_map_header(b, pairs) < 0) return -1; - /* server */ + if (resp3) { + if (resp_write_map_header(b, pairs) < 0) return -1; + } else { + if (resp_write_array_header(b, pairs * 2) < 0) return -1; + } resp_write_bulk(b, "server", 6); resp_write_bulk(b, "voidcache", 9); - /* version */ resp_write_bulk(b, "version", 7); resp_write_bulk(b, server_ver, strlen(server_ver)); - /* proto */ resp_write_bulk(b, "proto", 5); - resp_write_integer(b, 3); - /* id */ + resp_write_integer(b, resp3 ? 3 : 2); resp_write_bulk(b, "id", 2); resp_write_bulk(b, node_id, strlen(node_id)); - /* mode */ resp_write_bulk(b, "mode", 4); - resp_write_bulk(b, "cluster", 7); + resp_write_bulk(b, "standalone", 10); return 0; } diff --git a/net/proto.c.rej b/net/proto.c.rej new file mode 100644 index 0000000..9516a53 --- /dev/null +++ b/net/proto.c.rej @@ -0,0 +1,41 @@ +diff a/net/proto.c b/net/proto.c (rejected hunks) +@@ -329,24 +337,29 @@ + } + + /* ── HELLO (RESP3 handshake) ─────────────────────────────────────────────── */ +-int resp_write_hello(vc_buf_t *b, const char *server_ver, const char *node_id) { +- /* HELLO returns a RESP3 map with server info */ ++int resp_write_hello(vc_buf_t *b, const char *server_ver, const char *node_id, ++ bool resp3) { ++ /* ++ * HELLO response must match the negotiated protocol version: ++ * RESP3 (proto=3): %map header — redis-cli and drivers expect this. ++ * RESP2 (proto=2): *array header — RESP2 clients cannot parse %map ++ * and will stall reading the response. ++ */ + int pairs = 5; +- if (resp_write_map_header(b, pairs) < 0) return -1; +- /* server */ ++ if (resp3) { ++ if (resp_write_map_header(b, pairs) < 0) return -1; ++ } else { ++ if (resp_write_array_header(b, pairs * 2) < 0) return -1; ++ } + resp_write_bulk(b, "server", 6); + resp_write_bulk(b, "voidcache", 9); +- /* version */ + resp_write_bulk(b, "version", 7); + resp_write_bulk(b, server_ver, strlen(server_ver)); +- /* proto */ + resp_write_bulk(b, "proto", 5); +- resp_write_integer(b, 3); +- /* id */ ++ resp_write_integer(b, resp3 ? 3 : 2); + resp_write_bulk(b, "id", 2); + resp_write_bulk(b, node_id, strlen(node_id)); +- /* mode */ + resp_write_bulk(b, "mode", 4); +- resp_write_bulk(b, "cluster", 7); ++ resp_write_bulk(b, "standalone", 10); + return 0; + } diff --git a/net/proto.h b/net/proto.h index 4a9ab53..a309b89 100644 --- a/net/proto.h +++ b/net/proto.h @@ -27,7 +27,9 @@ * New VoidCache commands extend the command set (see commands.h). * ─────────────────────────────────────────────────────────────────────────── */ #pragma once -#define _POSIX_C_SOURCE 200809L +#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE) +# define _POSIX_C_SOURCE 200809L +#endif #include #include @@ -146,6 +148,7 @@ void vc_cmd_free(vc_cmd_t *cmd); /* ── RESP3 writers (append to vc_buf_t) ─────────────────────────────────── */ int resp_write_ok(vc_buf_t *b); int resp_write_null(vc_buf_t *b); +int resp_write_null_compat(vc_buf_t *b, bool resp3); int resp_write_error(vc_buf_t *b, const char *code, const char *msg); int resp_write_simple(vc_buf_t *b, const char *s); int resp_write_integer(vc_buf_t *b, int64_t n); @@ -163,4 +166,4 @@ int resp_write_vc_json(vc_buf_t *b, const char *json, size_t len); int resp_write_vc_binary(vc_buf_t *b, const void *data, size_t len); /* HELLO response (RESP3 handshake) */ -int resp_write_hello(vc_buf_t *b, const char *server_ver, const char *node_id); +int resp_write_hello(vc_buf_t *b, const char *server_ver, const char *node_id, bool resp3); diff --git a/net/proto.h.rej b/net/proto.h.rej new file mode 100644 index 0000000..e5ce0d8 --- /dev/null +++ b/net/proto.h.rej @@ -0,0 +1,15 @@ +diff a/net/proto.h b/net/proto.h (rejected hunks) +@@ -148,6 +148,7 @@ + /* ── RESP3 writers (append to vc_buf_t) ─────────────────────────────────── */ + int resp_write_ok(vc_buf_t *b); + int resp_write_null(vc_buf_t *b); ++int resp_write_null_compat(vc_buf_t *b, bool resp3); + int resp_write_error(vc_buf_t *b, const char *code, const char *msg); + int resp_write_simple(vc_buf_t *b, const char *s); + int resp_write_integer(vc_buf_t *b, int64_t n); +@@ -165,4 +166,4 @@ + int resp_write_vc_binary(vc_buf_t *b, const void *data, size_t len); + + /* HELLO response (RESP3 handshake) */ +-int resp_write_hello(vc_buf_t *b, const char *server_ver, const char *node_id); ++int resp_write_hello(vc_buf_t *b, const char *server_ver, const char *node_id, bool resp3); diff --git a/net/server.c b/net/server.c index b57d2c8..a01d20f 100644 --- a/net/server.c +++ b/net/server.c @@ -17,7 +17,11 @@ #include "auth.h" #include "vc_ssl_abi.h" -#ifdef _WIN32 +#ifdef _MSC_VER +# include "../compat/msvc.h" +# include "../compat/pthread_win32.h" +# include "../compat/wepoll.h" +#elif defined(_WIN32) # include "../compat/windows.h" # include "../compat/wepoll.h" #else @@ -291,9 +295,16 @@ static void *worker_thread(void *arg) { while (1) { struct sockaddr_storage sa; socklen_t salen = sizeof(sa); +#ifdef _WIN32 + int cfd = accept(lfd, (struct sockaddr *)&sa, &salen); + if (cfd < 0) break; + /* Set non-blocking on Windows via ioctlsocket */ + { u_long _nb = 1; ioctlsocket((SOCKET)cfd, FIONBIO, &_nb); } +#else int cfd = accept4(lfd, (struct sockaddr *)&sa, &salen, SOCK_NONBLOCK | SOCK_CLOEXEC); if (cfd < 0) break; +#endif set_tcp_nodelay(cfd); @@ -438,8 +449,13 @@ vcserver_t *vcserver_create(const vc_server_cfg_t *cfg) { vcserver_destroy(srv); return NULL; } +#ifdef _WIN32 + int lfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP); + { u_long _nb = 1; ioctlsocket((SOCKET)lfd, FIONBIO, &_nb); } +#else int lfd = socket(res->ai_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); +#endif if (lfd < 0) { freeaddrinfo(res); vcserver_destroy(srv); return NULL; } set_reuseaddr(lfd); @@ -493,9 +509,14 @@ int vcserver_start(vcserver_t *srv) { char portstr[8]; snprintf(portstr, sizeof(portstr), "%u", port); getaddrinfo(addr, portstr, &hints, &res); +#ifdef _WIN32 + int wlfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP); + { u_long _nb2 = 1; ioctlsocket((SOCKET)wlfd, FIONBIO, &_nb2); } +#else int wlfd = socket(res->ai_family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); +#endif set_reuseaddr(wlfd); set_reuseport(wlfd); bind(wlfd, res->ai_addr, res->ai_addrlen); diff --git a/src/voidcache.c b/src/voidcache.c index 309df83..fabfd1e 100644 --- a/src/voidcache.c +++ b/src/voidcache.c @@ -2,19 +2,33 @@ * voidcache.c – VoidCache core implementation * * Build flags: -O3 -march=native -funroll-loops + * Windows: -O2 -std=c11 -DVCACHE_WINDOWS (via Makefile.windows) */ -#define _POSIX_C_SOURCE 200809L -#define _GNU_SOURCE +#ifndef _WIN32 +# define _POSIX_C_SOURCE 200809L +# define _GNU_SOURCE +#endif #include "voidcache.h" +#ifdef _MSC_VER +# include "../compat/msvc.h" +# include "../compat/pthread_win32.h" +# include "../compat/mman.h" +# include "../compat/wepoll.h" +#elif defined(_WIN32) +# include "../compat/windows.h" +# include "../compat/mman.h" +#else +# include +#endif + #include #include #include #include #include #include -#include #include #include #include diff --git a/vcache.acl b/vcache.acl new file mode 100644 index 0000000..46714b8 --- /dev/null +++ b/vcache.acl @@ -0,0 +1,28 @@ +# vcache.acl – VoidCache ACL (Access Control List) +# +# Format: +# +# ACL flags: +# r – read (GET, KEYS, SCAN, EXISTS, TTL, TYPE, MGET, DBSIZE, INFO) +# w – write (SET, DEL, EXPIRE, INCR, APPEND, RENAME, MSET, FLUSHDB) +# a – admin (FLUSHDB, FLUSHALL, CONFIG, DEBUG, CLUSTER) +# p – pubsub (SUBSCRIBE, PUBLISH — future) +# * – all of the above +# +# To generate a password hash: +# echo -n "mypassword" | sha256sum +# ./vcli --hash-password mypassword (convenience helper) +# +# Example users: +# +# Full-access admin (password: "adminpass") +admin 3ba05beee8a9d6c6c5e99bf49e6a17eb6d7f9a5ad5b5bfc14f7c5b0a8d1f2c3 * +# +# Read-only service account (password: "readonly") +reader e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 r +# +# Application account with read+write (password: "apppass") +app 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 rw +# +# Default user (no-username AUTH ) password: "secret" +default 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b rw diff --git a/vcache.conf b/vcache.conf new file mode 100644 index 0000000..b840da8 --- /dev/null +++ b/vcache.conf @@ -0,0 +1,59 @@ +# vcache.conf – VoidCache Server Configuration +# +# Start with: ./vcli server --port 6379 +# Or load this file by passing flags directly (all options are CLI flags). +# +# ───────────────────────────────────────────────────────────────────────────── + +# ── Network ─────────────────────────────────────────────────────────────────── + +# TCP port (default: 6379 — drop-in Redis replacement) +port 6379 + +# Bind address (default: 0.0.0.0) +# bind 127.0.0.1 + +# Worker threads (default: 4). Set to number of CPU cores for best throughput. +# threads 8 + +# ── TLS (optional) ──────────────────────────────────────────────────────────── +# Provide cert + key to enable TLS 1.2/1.3. +# Clients connect with: ./vcli --tls -h host -p port +# Or use stunnel/nginx as a TLS proxy (no cert needed here). +# +# tls-cert /etc/vcache/server.crt +# tls-key /etc/vcache/server.key + +# ── Authentication ──────────────────────────────────────────────────────────── + +# Simple password (Redis-compatible AUTH ). +# Maps to a built-in "default" user with rwa permissions. +# requirepass your_strong_password_here + +# Full ACL file (see vcache.acl.example for format). +# When set, requirepass is ignored in favor of the ACL file. +# acl-file /etc/vcache/vcache.acl + +# ── Memory ──────────────────────────────────────────────────────────────────── + +# Soft memory cap. When exceeded, CLOCK eviction reclaims entries. +# Supports suffixes: k, m, g (e.g. 512m, 2g) +# maxmemory 256m + +# ── Persistence (WAL) ───────────────────────────────────────────────────────── + +# Write-Ahead Log path. Every SET/DEL is persisted before in-memory mutation. +# On restart, replay the WAL to recover state. +# Leave unset for pure in-memory mode (fastest, no durability). +# wal /var/lib/vcache/vcache.wal + +# ── Cluster ─────────────────────────────────────────────────────────────────── + +# Enable Redis Cluster protocol. Clients with cluster-aware drivers route +# automatically. Run multiple vcli server instances and they announce +# themselves via CLUSTER NODES. +# cluster yes + +# Address and port this node announces to peers and clients. +# announce-addr 10.0.0.1 +# announce-port 6379 diff --git a/vs2022/README.md b/vs2022/README.md new file mode 100644 index 0000000..1dd2ad1 --- /dev/null +++ b/vs2022/README.md @@ -0,0 +1,170 @@ +# VoidCache — Visual Studio 2022 Build Guide + +## Prerequisites + +| Tool | Where to get | Required? | +|---|---|---| +| VS2022 with **"Desktop development with C++"** | [visualstudio.microsoft.com](https://visualstudio.microsoft.com/downloads/) | ✅ Yes | +| OpenSSL for Windows (x64) | via vcpkg (easiest) or manual | ✅ Yes | +| Git | [git-scm.com](https://git-scm.com) | Only if using vcpkg | + +--- + +## Quick Start (automated) + +Open **PowerShell** in this `vs2022\` folder: + +```powershell +Set-ExecutionPolicy -Scope CurrentUser RemoteSigned # once only +.\setup.ps1 +``` + +The script installs OpenSSL via vcpkg and builds `vcli.exe` automatically. +Output: `vs2022\x64\Release\vcli.exe` + +--- + +## Manual Setup + +### Step 1 — Install OpenSSL + +**Option A: vcpkg (recommended)** +```powershell +git clone https://github.com/microsoft/vcpkg C:\vcpkg --depth=1 +C:\vcpkg\bootstrap-vcpkg.bat -disableMetrics +C:\vcpkg\vcpkg.exe install openssl:x64-windows +C:\vcpkg\vcpkg.exe integrate install +``` + +**Option B: Pre-built installer** +1. Download the Win64 OpenSSL installer from [slproweb.com/products/Win32OpenSSL.html](https://slproweb.com/products/Win32OpenSSL.html) +2. Install to `C:\Program Files\OpenSSL-Win64` (the default) +3. The project finds it automatically at that path + +### Step 2 — Open and Build + +``` +File → Open → Project/Solution → vcli.sln +``` + +Select **Release | x64** in the toolbar, then **Build → Build Solution** (Ctrl+Shift+B). + +Or from the **Developer Command Prompt for VS2022**: +```bat +cd vs2022 +msbuild vcli.sln /p:Configuration=Release /p:Platform=x64 /m +``` + +### Step 3 — Run + +```powershell +.\x64\Release\vcli.exe -h localhost -p 6379 PING + +# Interactive shell +.\x64\Release\vcli.exe -h localhost -p 6379 + +# Run as server +.\x64\Release\vcli.exe server --port 6379 +``` + +--- + +## Project Structure + +``` +VoidCache/ +├── vs2022/ +│ ├── vcli.sln ← Open this in VS2022 +│ ├── vcli.vcxproj ← Project file (all settings here) +│ ├── setup.ps1 ← Automated first-time setup +│ └── README.md ← This file +│ +├── src/voidcache.c ← Core cache engine (sharded HT, WAL, slab alloc) +├── net/ ← Network layer (RESP3, epoll, TLS, cluster) +├── cli/vcli.c ← CLI + server entry point +├── include/voidcache.h ← Public API +│ +└── compat/ ← Windows/MSVC compatibility shims + ├── msvc.h ← Master shim (ssize_t, fcntl, clock_gettime, etc.) + ├── pthread_win32.h ← pthreads → Win32 CRITICAL_SECTION / SRWLOCK + ├── wepoll.h + .c ← epoll → Windows IOCP + └── mman.h + .c ← mmap/munmap → VirtualAlloc / MapViewOfFile +``` + +--- + +## What the MSVC shims do + +The codebase was written for Linux/GCC. Four layers of shims make it compile under MSVC: + +### `compat/msvc.h` — POSIX API bridges +| POSIX | MSVC replacement | +|---|---| +| `ssize_t` | `typedef SSIZE_T ssize_t` | +| `unistd.h` (`read`/`write`/`close`) | `recv`/`send`/`closesocket` | +| `fcntl(F_SETFL, O_NONBLOCK)` | `ioctlsocket(FIONBIO)` | +| `clock_gettime(CLOCK_MONOTONIC)` | `QueryPerformanceCounter` | +| `strdup` | `_strdup` | +| `sigwait` / Ctrl+C | `SetConsoleCtrlHandler` | +| `/dev/urandom` | `BCryptGenRandom` | +| `SO_REUSEPORT` | `SO_REUSEADDR` | + +### `compat/pthread_win32.h` — pthreads → Win32 +| pthread | Win32 | +|---|---| +| `pthread_t` + `pthread_create/join` | `HANDLE` + `_beginthreadex` | +| `pthread_mutex_t` | `CRITICAL_SECTION` | +| `pthread_rwlock_t` | `SRWLOCK` (with exclusive-tracking wrapper) | +| `pthread_once_t` | `INIT_ONCE` | + +### `compat/wepoll.h/.c` — epoll → IOCP +Drop-in `epoll_create/ctl/wait` using Windows I/O Completion Ports. + +### `compat/mman.h/.c` — mmap → VirtualAlloc +- `MAP_ANONYMOUS` → `VirtualAlloc(MEM_COMMIT | MEM_RESERVE)` +- `MAP_SHARED` + file fd → `CreateFileMapping` + `MapViewOfFile` +- `munmap` → `UnmapViewOfFile` or `VirtualFree` + +### `voidcache.h` — struct attributes +```c +// GCC: +} __attribute__((packed, aligned(64))) vc_entry_t; + +// MSVC (via #ifdef guards in the header): +#pragma pack(push, 1) +typedef struct { ... } vc_entry_t; +#pragma pack(pop) +``` + +--- + +## Troubleshooting + +**`Cannot open include file: 'openssl/ssl.h'`** +→ OpenSSL path not found. Set it in `vcli.vcxproj` under ``, or run `setup.ps1`. + +**`LNK1181: cannot open input file 'libssl.lib'`** +→ OpenSSL lib path wrong. Check that `$(OpenSSLRoot)\lib\libssl.lib` exists. + For vcpkg installs: lib is at `C:\vcpkg\installed\x64-windows\lib\libssl.lib` + +**`LNK2019: unresolved external symbol BCryptGenRandom`** +→ Add `bcrypt.lib` to Additional Dependencies (already in the project, check it's there). + +**Application crashes on startup** +→ OpenSSL DLLs not found at runtime. The post-build event copies them; if missing, copy manually: + `libssl-3-x64.dll` and `libcrypto-3-x64.dll` from `$(OpenSSLRoot)\bin\` to the `.exe` folder. + +**`C4024: different types for formal and actual parameter`** (wepoll) +→ wepoll uses `HANDLE` for the epoll fd; ensure `compat/wepoll.h` is included before `server.h`. + +--- + +## Debug Build + +Select **Debug | x64** in VS2022. The debug build: +- Links against `/MTd` (static debug CRT) +- Disables optimisation (`/Od`) +- Generates a `.pdb` for full debugger symbols + +Set a breakpoint anywhere in `vcli.c` or `voidcache.c` and press F5 to start under the debugger. +You can inspect cache shards, WAL state, and connection objects directly in the Watch window. diff --git a/vs2022/setup.ps1 b/vs2022/setup.ps1 new file mode 100644 index 0000000..e3154be --- /dev/null +++ b/vs2022/setup.ps1 @@ -0,0 +1,111 @@ +# vs2022\setup.ps1 — First-time setup: install OpenSSL via vcpkg then build +# +# Run from the vs2022\ folder in PowerShell (not as Administrator): +# Set-ExecutionPolicy -Scope CurrentUser RemoteSigned +# cd vs2022 +# .\setup.ps1 +# +# What it does: +# 1. Finds or installs vcpkg +# 2. Installs OpenSSL x64-windows via vcpkg +# 3. Integrates vcpkg with Visual Studio +# 4. Opens the solution in VS2022 (or builds from CLI if VS CLI available) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) +$VS2022 = "$Root\vs2022" + +Write-Host "" +Write-Host "=== VoidCache VS2022 Setup ===" -ForegroundColor Cyan +Write-Host "Root: $Root" +Write-Host "" + +# ── 1. Locate or install vcpkg ──────────────────────────────────────────────── +$VcpkgDir = $env:VCPKG_ROOT +if (-not $VcpkgDir -or -not (Test-Path "$VcpkgDir\vcpkg.exe")) { + # Common install locations + foreach ($loc in @("C:\vcpkg", "C:\src\vcpkg", "$env:USERPROFILE\vcpkg", + "C:\dev\vcpkg", "D:\vcpkg")) { + if (Test-Path "$loc\vcpkg.exe") { $VcpkgDir = $loc; break } + } +} + +if (-not $VcpkgDir -or -not (Test-Path "$VcpkgDir\vcpkg.exe")) { + Write-Host "[1/4] vcpkg not found. Cloning to C:\vcpkg..." -ForegroundColor Yellow + $git = Get-Command git -ErrorAction SilentlyContinue + if (-not $git) { + Write-Host "ERROR: git not found. Install Git from https://git-scm.com" -ForegroundColor Red + Write-Host "Or install OpenSSL manually from https://slproweb.com/products/Win32OpenSSL.html" -ForegroundColor Yellow + Write-Host "Install to C:\Program Files\OpenSSL-Win64 and the project will find it." -ForegroundColor Yellow + exit 1 + } + git clone https://github.com/microsoft/vcpkg C:\vcpkg --depth=1 + & C:\vcpkg\bootstrap-vcpkg.bat -disableMetrics + $VcpkgDir = "C:\vcpkg" + $env:VCPKG_ROOT = $VcpkgDir +} else { + Write-Host "[1/4] vcpkg found at $VcpkgDir" -ForegroundColor Green +} + +# ── 2. Install OpenSSL ──────────────────────────────────────────────────────── +Write-Host "[2/4] Installing OpenSSL x64-windows via vcpkg..." -ForegroundColor Yellow +& "$VcpkgDir\vcpkg.exe" install openssl:x64-windows +if ($LASTEXITCODE -ne 0) { + Write-Host "WARNING: vcpkg install may have had issues, continuing..." -ForegroundColor Yellow +} +Write-Host "[2/4] OpenSSL installed." -ForegroundColor Green + +# ── 3. vcpkg integrate ─────────────────────────────────────────────────────── +Write-Host "[3/4] Integrating vcpkg with Visual Studio..." -ForegroundColor Yellow +& "$VcpkgDir\vcpkg.exe" integrate install +Write-Host "[3/4] vcpkg integrated." -ForegroundColor Green + +# ── 4. Build or open in VS2022 ─────────────────────────────────────────────── +Write-Host "[4/4] Building with MSBuild..." -ForegroundColor Yellow + +# Find MSBuild +$msbuild = $null +$msbuildPaths = @( + "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe", + "C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe", + "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe", + "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\MSBuild\Current\Bin\MSBuild.exe" +) +foreach ($p in $msbuildPaths) { + if (Test-Path $p) { $msbuild = $p; break } +} + +# Try PATH +if (-not $msbuild) { + $msbuild = (Get-Command msbuild -ErrorAction SilentlyContinue)?.Source +} + +if ($msbuild) { + Write-Host " Using MSBuild: $msbuild" -ForegroundColor DarkGray + & $msbuild "$VS2022\vcli.sln" /p:Configuration=Release /p:Platform=x64 /m /nologo ` + /p:VcpkgOpenSSL="$VcpkgDir\installed\x64-windows" + if ($LASTEXITCODE -eq 0) { + $exe = "$VS2022\x64\Release\vcli.exe" + Write-Host "" + Write-Host "========================================" -ForegroundColor Green + Write-Host " Build complete! vcli.exe is ready." -ForegroundColor Green + Write-Host "========================================" -ForegroundColor Green + Write-Host "" + Write-Host "Binary: $exe" -ForegroundColor Cyan + Write-Host "" + Write-Host "Test:" -ForegroundColor White + Write-Host " $exe -h localhost -p 6379 PING" -ForegroundColor Gray + } else { + Write-Host "" + Write-Host "Build failed. Opening solution in VS2022..." -ForegroundColor Yellow + Start-Process "$VS2022\vcli.sln" + } +} else { + Write-Host "MSBuild not found on PATH." -ForegroundColor Yellow + Write-Host "Opening solution in Visual Studio 2022..." -ForegroundColor Yellow + Start-Process "$VS2022\vcli.sln" + Write-Host "" + Write-Host "In VS2022: select Release | x64, then Build → Build Solution (Ctrl+Shift+B)" -ForegroundColor Cyan +} diff --git a/vs2022/vcli.sln b/vs2022/vcli.sln new file mode 100644 index 0000000..79f2b1b --- /dev/null +++ b/vs2022/vcli.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vcli", "vcli.vcxproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64 + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/vs2022/vcli.vcxproj b/vs2022/vcli.vcxproj new file mode 100644 index 0000000..8485281 --- /dev/null +++ b/vs2022/vcli.vcxproj @@ -0,0 +1,214 @@ + + + + + + Debug + x64 + + + Release + x64 + + + + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890} + Win32Proj + vcli + 10.0 + vcli + + + + + Application + false + v143 + + true + MultiByte + + + + Application + true + v143 + MultiByte + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\obj\ + + + + + + $(VCPKG_ROOT)\installed\x64-windows + + C:\Program Files\OpenSSL-Win64 + + $(VcpkgOpenSSL) + $(ManualOpenSSL) + C:\Program Files\OpenSSL-Win64 + + + + + + _CRT_SECURE_NO_WARNINGS; + _WINSOCK_DEPRECATED_NO_WARNINGS; + WIN32_LEAN_AND_MEAN; + _WIN32_WINNT=0x0A00; + VCACHE_WINDOWS; + NDEBUG; + %(PreprocessorDefinitions) + + + $(SolutionDir)..\include; + $(SolutionDir)..\net; + $(SolutionDir)..\compat; + $(OpenSSLRoot)\include; + %(AdditionalIncludeDirectories) + + c++17 + + MaxSpeed + true + Speed + MultiThreaded + + Level3 + 4996;4200;4204;4221;4100;4101 + + + + + ws2_32.lib;bcrypt.lib; + libssl.lib;libcrypto.lib; + %(AdditionalDependencies) + + + $(OpenSSLRoot)\lib; + $(OpenSSLRoot)\lib\VC\x64\MT; + %(AdditionalLibraryDirectories) + + true + true + Console + MachineX64 + + + + + if exist "$(OpenSSLRoot)\bin\libssl-3-x64.dll" ( + copy /Y "$(OpenSSLRoot)\bin\libssl-3-x64.dll" "$(OutDir)" + copy /Y "$(OpenSSLRoot)\bin\libcrypto-3-x64.dll" "$(OutDir)" + ) else if exist "$(OpenSSLRoot)\bin\libssl-3.dll" ( + copy /Y "$(OpenSSLRoot)\bin\libssl-3.dll" "$(OutDir)" + copy /Y "$(OpenSSLRoot)\bin\libcrypto-3.dll" "$(OutDir)" + ) + echo OpenSSL DLLs copied to $(OutDir) + + + + + + + _CRT_SECURE_NO_WARNINGS; + _WINSOCK_DEPRECATED_NO_WARNINGS; + WIN32_LEAN_AND_MEAN; + _WIN32_WINNT=0x0A00; + VCACHE_WINDOWS; + _DEBUG; + %(PreprocessorDefinitions) + + + $(SolutionDir)..\include; + $(SolutionDir)..\net; + $(SolutionDir)..\compat; + $(OpenSSLRoot)\include; + %(AdditionalIncludeDirectories) + + stdcpp17 + Disabled + MultiThreadedDebug + + Level3 + 4996;4200;4204;4221;4100;4101 + stdc17 + + + + ws2_32.lib;bcrypt.lib; + libssl.lib;libcrypto.lib; + %(AdditionalDependencies) + + + $(OpenSSLRoot)\lib; + $(OpenSSLRoot)\lib\VC\x64\MDd; + %(AdditionalLibraryDirectories) + + Console + MachineX64 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file