calog/src/calogPlatform.h

117 lines
4.9 KiB
C

// 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 <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdio.h>
// 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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <poll.h>
#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