// SafeC Standard Library — Non-blocking file/socket helpers for the reactor
//
// Thin wrappers around real OS syscalls (open/socket/accept/connect) that
// enable non-blocking mode, meant to be paired with std::Reactor/
// std::TaskScheduler (see reactor.h): a task calls one of these, gets
// EAGAIN/EWOULDBLOCK back immediately instead of blocking the whole
// program, and awaits readiness via
// TaskScheduler::await_fd(fd, SCHED_READ/SCHED_WRITE) instead.
//
// This header declares only the genuinely portable pieces (AF_INET/
// SOCK_STREAM/O_RDONLY/O_WRONLY/O_RDWR share the same values across every
// backend below — a historical BSD-sockets accident every later API,
// including Winsock, deliberately preserved for source compatibility) and
// the function signatures every backend implements identically. Anything
// that actually differs by platform — sockaddr_in's layout (BSD has an
// extra sin_len byte Linux/Windows don't), O_NONBLOCK's value (or lack of
// a direct equivalent at all, on Windows), the non-blocking-mode syscall
// itself (fcntl vs. ioctlsocket) — lives in the matching backend file
// instead, picked the same way as reactor.h's own backends:
// io_nb_bsd.sc — macOS, iOS, FreeBSD
// io_nb_linux.sc — Linux, Android
// io_nb_win32.sc — Windows (fd_open_nb is best-effort there — see its
// own comment; Windows has no non-blocking-file-open
// equivalent to O_NONBLOCK, only non-blocking sockets)
#pragma once
#include <std/sched/reactor.h>
#define SCHED_AF_INET 2
#define SCHED_SOCK_STREAM 1
#define SCHED_O_RDONLY 0
#define SCHED_O_WRONLY 1
#define SCHED_O_RDWR 2
namespace std {
unsigned short sched_htons(unsigned short host16);
unsigned int sched_htonl(unsigned int host32);
// Packs four host-order octets (e.g. 127,0,0,1) into a network-order
// address suitable for a backend's sockaddr_in sin_addr field — avoids
// callers needing to hand-compute the byte order themselves for the
// common "literal IP" case.
unsigned int sched_ipv4(unsigned char a, unsigned char b,
unsigned char c, unsigned char d);
// Puts an already-open socket fd into non-blocking mode (fcntl+O_NONBLOCK
// on BSD/Linux, ioctlsocket+FIONBIO on Windows). Returns 0 on success.
int fd_set_nonblocking(int fd);
// Puts an already-open socket fd back into blocking mode — the inverse of
// fd_set_nonblocking, for callers (e.g. std::http, see std/http/http.h)
// that want simple synchronous request/response semantics on top of
// tcp_listen_nb/tcp_accept_nb/tcp_connect_nb's portable socket setup
// rather than pairing them with std::Reactor/std::TaskScheduler. Returns 0
// on success. On a socket from tcp_connect_nb, call tcp_wait_connected
// first (or connect with tcp_connect_timeout instead): Darwin and Winsock
// fail send/recv with ENOTCONN while the handshake is still in flight
// instead of waiting for it.
int fd_set_blocking(int fd);
// open() in non-blocking mode where the platform supports it for regular
// files (BSD/Linux: O_NONBLOCK; Windows: no equivalent — see
// io_nb_win32.sc). Returns fd, or -1 on failure.
int fd_open_nb(const &char path, int flags, int mode);
// socket()+bind()+listen(), non-blocking. Returns listening fd, or -1.
int tcp_listen_nb(unsigned short port);
// Non-blocking accept(): returns a connected client fd (itself set
// non-blocking), or -1 with errno EAGAIN/EWOULDBLOCK if none is pending
// yet — the caller should await_fd(listenfd, SCHED_READ) and retry.
int tcp_accept_nb(int listenfd);
// socket()+connect(), non-blocking: connect() on a non-blocking socket
// returns immediately with EINPROGRESS rather than waiting for the
// handshake — the caller should await_fd(fd, SCHED_WRITE) and treat the
// fd becoming writable as "connect finished (successfully or not; check
// SO_ERROR if you need to distinguish)". Returns the socket fd, or -1 on
// immediate failure.
int tcp_connect_nb(unsigned int addr_network_order, unsigned short port);
// Blocks until a tcp_connect_nb socket's handshake finishes (poll for
// writable, then SO_ERROR), with no time limit of its own. Returns 0 if
// connected, -1 if the connect failed (refused, unreachable, ...). For
// synchronous callers; a std::Reactor task awaits SCHED_WRITE instead.
int tcp_wait_connected(int fd);
// socket()+connect() that waits at most 'timeout_ms' (<= 0: no limit
// beyond the kernel's own SYN retries, ~2 minutes on Linux) for the TCP
// handshake: non-blocking connect(), poll()/select() for writability,
// then SO_ERROR. Returns a connected socket already switched back to
// blocking mode, or -1 with the socket closed and the error left in
// errno (BSD/Linux) or WSAGetLastError() (Windows): ETIMEDOUT/
// WSAETIMEDOUT on timeout, otherwise the connect error itself
// (ECONNREFUSED, ENETUNREACH, ...).
int tcp_connect_timeout(unsigned int addr_network_order, unsigned short port,
int timeout_ms);
// Sets SO_RCVTIMEO and SO_SNDTIMEO on a blocking socket, so that a single
// read/write that makes no progress for 'timeout_ms' fails (-1, EAGAIN/
// EWOULDBLOCK, or WSAETIMEDOUT on Windows) instead of blocking forever.
// It bounds each call, not a whole request. 'timeout_ms' <= 0 clears both
// timeouts. Returns 0 on success.
int sock_set_io_timeout(int fd, int timeout_ms);
// True if the most recent non-blocking socket call (accept/recv/send/
// connect/...) failed only because it would have blocked — i.e. "not
// ready yet, try again after await_fd()", not a real error. Checking this
// is genuinely platform-specific in a way none of the other functions
// here are: BSD/Linux report it through errno (EAGAIN/EWOULDBLOCK, and
// std/errno.h's ERRNO_EAGAIN()/ERRNO_EWOULDBLOCK() only recently started
// giving the right numbers per platform there — see that header),
// Windows sockets never touch errno at all and report it through a
// separate per-thread slot, WSAGetLastError() == WSAEWOULDBLOCK (10035).
// Call this immediately after the failing call, before anything else
// that might itself set errno/WSAGetLastError() and clobber it.
int sock_would_block();
} // namespace std