117 lines
4.9 KiB
C
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
|