// calogPlatform.h -- OS abstraction for calog's small platform surface. // // calog's core (values, broker, actor, engines) is portable C11. The only OS-specific surface is // the socket layer used by the net/ssh/http libraries: BSD sockets on Linux and macOS, Winsock on // Windows. pthreads and getaddrinfo are available on all three (on Windows via mingw-w64's // winpthreads + Winsock), so only the socket type, close/error accessors, non-blocking toggle, and // a one-time subsystem init differ. This header papers over exactly those. // // POSIX (Linux/macOS): everything maps to the standard names; init/shutdown are no-ops. // Windows: maps to Winsock (closesocket / WSAGetLastError / ioctlsocket) and WSAStartup/WSACleanup. #ifndef CALOG_PLATFORM_H #define CALOG_PLATFORM_H #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0601 // Windows 7+: exposes WSAPoll and struct pollfd #endif #include #include #include #include // poll on a small fd set (WSAPoll is API-compatible with poll for our single-socket use). #define calogPoll WSAPoll typedef SOCKET CalogSocketT; #define CALOG_INVALID_SOCKET INVALID_SOCKET #define CALOG_SOCKET_ERROR SOCKET_ERROR static inline int calogSockClose(CalogSocketT s) { return closesocket(s); } static inline int calogSockErrno(void) { return WSAGetLastError(); } static inline int calogSockWouldBlock(int err) { return err == WSAEWOULDBLOCK; } static inline int calogSockInProgress(int err) { return err == WSAEWOULDBLOCK || err == WSAEINPROGRESS; } static inline int calogSockSetNonblock(CalogSocketT s, int on) { u_long mode = (u_long)(on != 0); return ioctlsocket(s, FIONBIO, &mode); } static inline int calogPlatformNetInit(void) { WSADATA data; return WSAStartup(MAKEWORD(2, 2), &data); } static inline void calogPlatformNetShutdown(void) { WSACleanup(); } // Best-effort message for the last socket error (Winsock has no errno; strerror does not map it). // Uses a thread-local buffer so concurrent context threads do not clobber each other. static inline const char *calogSockErrStr(void) { // _Thread_local (C11) rather than __declspec(thread): the clang/mingw cross-toolchain silently // ignores __declspec(thread) here, which would make this buffer process-global and let // concurrent context threads clobber each other; _Thread_local is honored and keeps it // per-thread. Windows-only code (inside the _WIN32 branch), so POSIX builds are unaffected. static _Thread_local char buffer[256]; int err; DWORD written; err = WSAGetLastError(); written = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD)err, 0, buffer, (DWORD)sizeof(buffer), NULL); if (written == 0) { snprintf(buffer, sizeof(buffer), "winsock error %d", err); } return buffer; } #else #include #include #include #include #include #include #include #include #include #define calogPoll poll typedef int CalogSocketT; #define CALOG_INVALID_SOCKET (-1) #define CALOG_SOCKET_ERROR (-1) static inline int calogSockClose(CalogSocketT s) { return close(s); } static inline int calogSockErrno(void) { return errno; } static inline int calogSockWouldBlock(int err) { return err == EWOULDBLOCK || err == EAGAIN; } static inline int calogSockInProgress(int err) { return err == EINPROGRESS; } static inline int calogSockSetNonblock(CalogSocketT s, int on) { int flags = fcntl(s, F_GETFL, 0); if (flags < 0) { return -1; } return fcntl(s, F_SETFL, on ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); } static inline const char *calogSockErrStr(void) { return strerror(errno); } static inline int calogPlatformNetInit(void) { return 0; } static inline void calogPlatformNetShutdown(void) { (void)0; } #endif // SIGPIPE suppression on send(): Linux uses the MSG_NOSIGNAL send() flag (CALOG_MSG_NOSIGNAL below); // macOS has no MSG_NOSIGNAL, so the SO_NOSIGPIPE socket option (set via calogSockNoSigpipe at socket // creation) is the per-socket guard there; Windows has no SIGPIPE at all. Falls back to 0 where absent. #ifdef MSG_NOSIGNAL #define CALOG_MSG_NOSIGNAL MSG_NOSIGNAL #else #define CALOG_MSG_NOSIGNAL 0 #endif // Suppress SIGPIPE for writes to socket `s`. On macOS this is the ONLY per-socket guard (no // MSG_NOSIGNAL), so an embedder linking a calog transport without the CLI's process-wide // signal(SIGPIPE, SIG_IGN) stays safe. No-op on Linux (MSG_NOSIGNAL covers it) and Windows (no SIGPIPE). static inline void calogSockNoSigpipe(CalogSocketT s) { #ifdef SO_NOSIGPIPE int on = 1; setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (const char *)&on, sizeof(on)); #else (void)s; #endif } #endif