chore(compat): add windows portability shims and header guards#524
chore(compat): add windows portability shims and header guards#524somethingwithproof wants to merge 3 commits intoCacti:developfrom
Conversation
Mechanical #ifdef _WIN32 changes for Windows compatibility: - Guard POSIX-only headers in common.h (sys/wait, netinet/*, syslog, etc.) - Add Win32 function macros in spine.h (usleep, getpid, localtime_r, etc.) - Add CLOSE_SOCKET macro for socket/pipe distinction - Guard POSIX-only signals in error.c (SIGPIPE, SIGBUS, SIGQUIT, SIGSYS) - Guard syslog calls in util.c - Add QueryPerformanceCounter for get_time_as_double on Windows - Replace close() with CLOSE_SOCKET() for sockets in ping.c - Guard spawn.h includes in php.c and nft_popen.c Zero behavioral change on POSIX. All existing tests pass unchanged. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces Windows-specific header guards and portability shims to move the Spine C codebase closer to native Windows compilation, while aiming to keep POSIX behavior unchanged.
Changes:
- Add
_WIN32-guarded includes incommon.hand POSIX-only include guards across the codebase. - Introduce Windows portability macros/types in
spine.h(Sleep/usleep mappings, env/time/string shims, socket close macro). - Adjust platform-specific behavior in logging/timing (
util.c), signal handling (error.c), socket I/O (ping.c), and spawn header usage (php.c,nft_popen.c).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| common.h | Adds Windows header set and guards POSIX-only headers |
| spine.h | Adds Windows portability shims/macros and config defaults |
| error.c | Guards non-Windows signals and sigaction usage |
| util.c | Guards syslog usage and adds Windows high-res timing path |
| ping.c | Replaces socket close calls with CLOSE_SOCKET() and uses recv() on Windows |
| php.c | Guards <spawn.h> include on Windows |
| nft_popen.c | Guards <spawn.h> include on Windows |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| QueryPerformanceFrequency(&freq); | ||
| QueryPerformanceCounter(&count); | ||
| return (double)count.QuadPart / (double)freq.QuadPart; |
There was a problem hiding this comment.
get_time_as_double() uses QueryPerformanceFrequency/QueryPerformanceCounter without checking for failure. If QueryPerformanceFrequency fails or returns 0, this will divide by zero. Suggest validating the return values and providing a fallback (e.g., GetTickCount64 or time-based path) when high-resolution counters are unavailable.
| QueryPerformanceFrequency(&freq); | |
| QueryPerformanceCounter(&count); | |
| return (double)count.QuadPart / (double)freq.QuadPart; | |
| if (QueryPerformanceFrequency(&freq) && freq.QuadPart != 0 && | |
| QueryPerformanceCounter(&count)) { | |
| return (double)count.QuadPart / (double)freq.QuadPart; | |
| } | |
| return (double)GetTickCount64() / 1000.0; |
There was a problem hiding this comment.
Valid. Will add error check and cache the frequency in a static variable in the next iteration.
| LARGE_INTEGER freq, count; | ||
| QueryPerformanceFrequency(&freq); |
There was a problem hiding this comment.
QueryPerformanceFrequency is typically constant for the lifetime of the process, but this calls it on every get_time_as_double() invocation. Given how frequently get_time_as_double() is used in polling loops, consider caching the frequency (e.g., static init-on-first-use) to reduce overhead on Windows.
| LARGE_INTEGER freq, count; | |
| QueryPerformanceFrequency(&freq); | |
| static LARGE_INTEGER freq; | |
| static int freq_initialized = 0; | |
| LARGE_INTEGER count; | |
| if (!freq_initialized) { | |
| QueryPerformanceFrequency(&freq); | |
| freq_initialized = 1; | |
| } |
There was a problem hiding this comment.
Agreed. Will cache in a static local.
| #include <windows.h> | ||
| #include <iphlpapi.h> | ||
| #include <process.h> | ||
| #include <io.h> |
There was a problem hiding this comment.
Winsock requires calling WSAStartup() (and WSACleanup() on shutdown) before using socket()/select()/closesocket(). I don’t see any WSAStartup call in the codebase, so even if this compiles the network paths will fail at runtime on native Windows. Consider adding a small platform init/teardown routine (e.g., in spine.c startup) that runs when _WIN32 is set.
| #include <io.h> | |
| #include <io.h> | |
| static void spine_winsock_cleanup(void) { | |
| WSACleanup(); | |
| } | |
| static BOOL CALLBACK spine_winsock_init_once(PINIT_ONCE init_once, PVOID parameter, PVOID *context) { | |
| WSADATA wsa_data; | |
| int result; | |
| (void)init_once; | |
| (void)parameter; | |
| (void)context; | |
| result = WSAStartup(MAKEWORD(2, 2), &wsa_data); | |
| if (result != 0) { | |
| WSASetLastError(result); | |
| return FALSE; | |
| } | |
| atexit(spine_winsock_cleanup); | |
| return TRUE; | |
| } | |
| static int spine_winsock_init(void) { | |
| static INIT_ONCE spine_winsock_init_once_control = INIT_ONCE_STATIC_INIT; | |
| return InitOnceExecuteOnce(&spine_winsock_init_once_control, spine_winsock_init_once, NULL, NULL) ? 0 : SOCKET_ERROR; | |
| } | |
| static SOCKET spine_socket(int af, int type, int protocol) { | |
| if (spine_winsock_init() != 0) { | |
| return INVALID_SOCKET; | |
| } | |
| return socket(af, type, protocol); | |
| } | |
| static int spine_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout) { | |
| if (spine_winsock_init() != 0) { | |
| return SOCKET_ERROR; | |
| } | |
| return select(nfds, readfds, writefds, exceptfds, timeout); | |
| } | |
| static int spine_closesocket(SOCKET s) { | |
| if (spine_winsock_init() != 0) { | |
| return SOCKET_ERROR; | |
| } | |
| return closesocket(s); | |
| } | |
| #define socket(af, type, protocol) spine_socket((af), (type), (protocol)) | |
| #define select(nfds, readfds, writefds, exceptfds, timeout) spine_select((nfds), (readfds), (writefds), (exceptfds), (timeout)) | |
| #define closesocket(s) spine_closesocket((s)) |
There was a problem hiding this comment.
WSAStartup is handled by Net-SNMP SOCK_STARTUP macro already called in spine.c. Documenting this dependency.
|
|
||
| #include "common.h" | ||
| #include "spine.h" | ||
| #ifndef _WIN32 |
There was a problem hiding this comment.
This guards out <spawn.h> on _WIN32, but php.c still uses posix_spawn()/posix_spawn_file_actions_t later. With _WIN32 defined, that will typically fail to compile due to missing type/function declarations. Either include the appropriate header based on a feature test (e.g., HAVE_SPAWN_H) or #ifdef out the posix_spawn-based implementation on Windows until a replacement is added.
| #ifndef _WIN32 | |
| #ifdef HAVE_SPAWN_H |
There was a problem hiding this comment.
Correct. The spawn.h guard is a prerequisite. The CreateProcess replacement is tracked separately in the libuv migration work.
|
|
||
| #include "common.h" | ||
| #include "spine.h" | ||
| #ifndef _WIN32 |
There was a problem hiding this comment.
This guards out <spawn.h> on _WIN32, but nft_popen() uses posix_spawn()/posix_spawn_file_actions_t later. That will generally not compile on Windows because the types/prototypes won’t be declared. Suggest using a feature-test macro (HAVE_SPAWN_H) rather than _WIN32 for the include, or wrapping the posix_spawn implementation in #ifndef _WIN32 until a Windows path is added.
| #ifndef _WIN32 | |
| #ifdef HAVE_SPAWN_H |
There was a problem hiding this comment.
Same as above. Guard-only for now, full replacement deferred.
| /* Type portability */ | ||
| typedef int pid_t; |
There was a problem hiding this comment.
The Windows shim unconditionally typedefs pid_t as int. However, common.h may already include <sys/types.h> (when HAVE_UNISTD_H is true), which typically defines pid_t; this can cause a redefinition compile error on some Windows toolchains (e.g., MinGW/MSYS2). Prefer only defining pid_t when it’s not already provided by the environment (feature-test macro / guard) or relying on the platform headers instead.
| /* Type portability */ | |
| typedef int pid_t; | |
| /* Type portability */ | |
| #if !defined(_PID_T_) && !defined(_PID_T_DEFINED) && !defined(__pid_t_defined) | |
| typedef int pid_t; | |
| #endif |
There was a problem hiding this comment.
Will add a guard: typedef only when pid_t is not already defined.
|
|
||
| if (return_code == -1 && (errno == EHOSTUNREACH || errno == ECONNRESET || errno == ECONNREFUSED)) { | ||
| if (is_debug_device(host->id)) { |
There was a problem hiding this comment.
Under _WIN32 you call recv(), but this error check still relies on errno and POSIX error codes. Winsock reports socket errors via WSAGetLastError() and WSA* constants (and recv returns SOCKET_ERROR), so this condition won’t behave correctly on Windows. Consider using WSAGetLastError()/WSAECONNRESET/WSAECONNREFUSED/WSAEHOSTUNREACH in the _WIN32 path.
There was a problem hiding this comment.
Valid. Will use WSAGetLastError() and Winsock error codes in the _WIN32 path.
ping.c
Outdated
| if (udp_socket != -1) CLOSE_SOCKET(udp_socket); | ||
| return HOST_DOWN; |
There was a problem hiding this comment.
On Windows, socket handles are SOCKET and the invalid sentinel is INVALID_SOCKET (and errors are SOCKET_ERROR), not -1. This branch still uses udp_socket != -1, which is not type-correct for Winsock and can break on 64-bit. Consider using INVALID_SOCKET/SOCKET_ERROR (and a SOCKET-typed variable) under _WIN32.
There was a problem hiding this comment.
Valid. CLOSE_SOCKET handles closesocket vs close, but SOCKET type and INVALID_SOCKET sentinel need addressing. Will fix in follow-up.
spine.h depends on types from common.h (MYSQL, pid_t, size_t, pthread types, RESULTS_BUFFER from config.h). Add a compile-time guard that produces a clear error if spine.h is included without common.h, rather than cascading type errors. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Summary
Mechanical #ifdef _WIN32 changes preparing Spine for native Windows compilation. Zero behavioral change on POSIX. No new dependencies.
Changes
common.h -- Header guards for POSIX-only includes:
spine.h -- Portability macros:
error.c -- Signal handling:
util.c -- Runtime:
ping.c -- Socket I/O:
php.c, nft_popen.c -- Build:
Scope
This PR covers LOW and MEDIUM risk changes only. HIGH risk items (CreateProcess for process spawning, IcmpSendEcho for ICMP ping, seteuid privilege management) are deferred to a follow-up PR.
Test plan