SchOS/include/socket.h
Nexoniarz 1dd4ba0186 debugger: the kernel can be asked a question
docs/plan/debugger.md, milestones D0, D1 and D2. What they replace is the
ninety-second loop -- add a cprintf, rebuild, boot, read the serial log --
which answers exactly the one question you thought of before the rebuild.

The rule the shape follows: **it observes, it does not control.** No
stepping, no software breakpoints, no stopping the world. Half the bugs
this kernel produces are timing -- an interrupt storm, a level-triggered
line nobody acknowledges, a driver that faults only on its second start --
and every one of them disappears when you halt the machine to look. And
the layering rule, which is the circularity rule the display already has:
a debugger that needs lwIP cannot debug lwIP.

D0 -- symbols. tools/ksyms.pl turns the first link pass into a table the
second pass carries. Two passes is enough because .ksym is placed after
.text and .rodata, so carrying the table cannot move anything recorded in
it; the build lists both passes' text symbols and refuses to link if they
ever disagree, because a symbol table that is subtly wrong gives confident
false answers. ksym.c reads it through weak symbols, so pass one -- which
has no table -- still links. A crash chain now reads panic+0x5f <-
proc_sysrq_cmd+0x99 <- ... <- syscall+0xf6.

D1 -- a log that outlives the kernel. Every character through consputc()
lands in a byte ring with a timestamp per line, readable as /proc/kmsg.
It sits in a region carved before the page allocator gets anything, with a
magic header, so a kernel finds the previous kernel's log still there --
including the whole PSOGM report. panic.c's own comment said "there is no
core dump and no log to go back to"; that is no longer true.

  The obvious place for it is the top of RAM beside the DMA arena. That
  was the first attempt and the magic came back as ASCII on the reboot,
  because the top of usable memory is exactly where the firmware makes its
  own allocations. It lives above the kernel image instead, which is a
  stable address across boots of the same build.

D2 -- the introspection registry. A subsystem publishes its state with one
line at the bottom of the file that owns the data:

  dbg_register("dma", dma_dump, "the DMA arena's free extents");

Seven views -- dma, mem, proc, vma, claims, pci, sock -- reachable three
ways: sysrq 'g' (always, no process and no filesystem), /dev/dbg (echo a
line, cat the answer), and schdbg. The protocol is lines of text because
the same bytes have to work where nothing can allocate or frame a record,
and because a protocol you cannot drive by hand is one you cannot debug.
struct sbuf moved out of kernel/dev/procfs.c into include/sbuf.h rather
than being written a second time.

Several views exist because of a specific bug. `vma` is the table that
decides whether exit() frees a page, and the DMA ownership bug that made
this worth building was a block that was never in it.

Also fixed, found by building with clang for the first time in a while:
struct iovec was defined in both posix.h and socket.h, and every file that
included both was a redefinition. GCC 15 defaults to C23, which permits an
identical struct redeclaration, so the gcc path never noticed -- while
clang defaults to gnu17 and rejected it, breaking the one toolchain
shell.nix exists to provide and the UEFI loader requires. Both are now
guarded.

D3 (`whose <addr>`, the cross-subsystem ownership query) and D4 (hardware
watchpoints on DR0-DR3) are not built and are described honestly as
missing in the architecture doc's "What it does not do".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-14 00:58:45 +02:00

342 lines
12 KiB
C

#pragma once
#include "types.h"
// Unix domain sockets.
//
// These exist for Schcom. A compositor and its clients have to exchange
// messages, and a pipe cannot do it: a pipe is one-directional, has no
// notion of a connection, and gives no way for a server to be *found* by a
// client that starts later. A socket with a name in the filesystem is
// exactly the missing piece.
//
// AF_INET arrived later, and exactly as planned: underneath the same syscalls
// rather than reshaping them. A socket carries its domain and every entry point
// dispatches on it -- kernel/net/unix.c for AF_UNIX, kernel/net/inet.c (lwIP)
// for AF_INET.
#define AF_UNIX 1
#define AF_LOCAL 1
#define AF_INET 2
// Linux's number, because a musl program passes Linux's constant. getifaddrs()
// is implemented over this, which is why it exists here: enumerating interfaces
// is something every networked program does and nothing else offers.
#define AF_NETLINK 16
#define PF_NETLINK AF_NETLINK
#define NETLINK_ROUTE 0
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
#define SOCK_RAW 3
// Flags ORed into socket()'s and accept4()'s `type`/`flags` argument. Linux's
// values, because a musl program passes Linux's constants.
#define SOCK_NONBLOCK 0x800 // O_NONBLOCK
#define SOCK_CLOEXEC 0x80000 // O_CLOEXEC
// setsockopt/getsockopt levels and names, Linux's numbering.
//
// Only what is listed here is accepted; anything else is answered with
// ENOPROTOOPT rather than a success it did not earn. See the comment above
// sys_setsockopt for why that is worth the risk of failing a caller that never
// checked before.
#define SOL_SOCKET 1
#define IPPROTO_ICMP 1
#define IPPROTO_TCP 6
#define SO_REUSEADDR 2
#define SO_TYPE 3
#define SO_ERROR 4
#define SO_BROADCAST 6
#define SO_SNDBUF 7
#define SO_RCVBUF 8
#define SO_KEEPALIVE 9
#define SO_ACCEPTCONN 30
#define TCP_NODELAY 1
// ---------------------------------------------------------------------------
// Ancillary data -- sendmsg/recvmsg, and SCM_RIGHTS
//
// A descriptor is the only thing that can be passed between two processes that
// is not a number: it names an object the kernel already knows, and handing it
// over is the difference between "here is a buffer" and "here is a *copy* of a
// buffer". Wayland's wl_shm_create_pool passes one and has no integer-shaped
// alternative; docs/plan/hybrid.md's mechanism 1 is this and nothing else.
//
// Every structure below is Linux's, byte for byte, because a musl-linked
// program builds them with Linux's headers and we do not get a vote. The
// offsets that matter on x86-64 are, for msghdr:
//
// 0 msg_name 8 msg_namelen(+4 pad) 16 msg_iov 24 msg_iovlen
// 32 msg_control 40 msg_controllen 48 msg_flags
//
// -- 56 bytes total, which is what musl's int-typed msg_iovlen plus a pad word
// adds up to as well.
// ---------------------------------------------------------------------------
#define MSG_OOB 0x0001
#define MSG_PEEK 0x0002
#define MSG_DONTROUTE 0x0004
#define MSG_CTRUNC 0x0008 // some ancillary data was dropped
#define MSG_TRUNC 0x0020
#define MSG_DONTWAIT 0x0040
#define MSG_EOR 0x0080
#define MSG_WAITALL 0x0100
#define MSG_NOSIGNAL 0x4000
#define MSG_CMSG_CLOEXEC 0x40000000
// The only control message that exists here. SCM_CREDENTIALS is deliberately
// absent rather than stubbed: a caller told it succeeded would read a uid out
// of uninitialised memory and act on it.
#define SCM_RIGHTS 1
// The most descriptors one message may carry. A cap has to exist -- the kernel
// holds a reference to every one of them until it is received -- and saying so
// here means a sender can size its control buffer against the same number the
// kernel will enforce.
#define SCM_MAXFD 8
// struct iovec is declared in two headers on purpose, and guarded so that
// including both is not an error.
//
// posix.h has it because readv/writev need it; this file has it because a
// userspace program including <socket.h> for sendmsg must get a complete
// struct msghdr without also having to know about a kernel header. Both
// definitions are identical and both are Linux's layout.
//
// The guard is not cosmetic. C23 permits redeclaring a struct with identical
// members and GCC 15 defaults to C23, so the duplicate was invisible on the
// gcc path -- while clang defaults to gnu17 and rejected it, which broke the
// one toolchain shell.nix exists to provide and which the UEFI loader needs.
#ifndef SCH_IOVEC_DEFINED
#define SCH_IOVEC_DEFINED
struct iovec {
void *iov_base;
uint64 iov_len;
};
#endif
struct msghdr {
void *msg_name; // struct sockaddr *, or 0
uint32 msg_namelen;
uint32 __pad1;
struct iovec *msg_iov;
uint64 msg_iovlen;
void *msg_control; // struct cmsghdr *, or 0
uint64 msg_controllen;
int msg_flags; // out on recvmsg: MSG_CTRUNC
};
struct cmsghdr {
uint64 cmsg_len; // CMSG_LEN(payload), NOT including padding
int cmsg_level; // SOL_SOCKET
int cmsg_type; // SCM_RIGHTS
};
// cmsg_len counts the header plus the payload; CMSG_SPACE additionally counts
// the padding to the next header. Conflating the two is the classic way to
// build a control buffer the kernel then rejects, which is why both exist.
#define CMSG_ALIGN(n) (((uint64)(n) + sizeof(uint64) - 1) & ~(uint64)(sizeof(uint64) - 1))
#define CMSG_LEN(n) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (uint64)(n))
#define CMSG_SPACE(n) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(n))
#define CMSG_DATA(c) ((unsigned char *)((struct cmsghdr *)(c) + 1))
#define CMSG_FIRSTHDR(m) \
((m)->msg_control && (m)->msg_controllen >= sizeof(struct cmsghdr) \
? (struct cmsghdr *)(m)->msg_control \
: (struct cmsghdr *)0)
#define CMSG_NXTHDR(m, c) \
((c)->cmsg_len < sizeof(struct cmsghdr) || \
(uint64)((char *)(c) + CMSG_ALIGN((c)->cmsg_len) + sizeof(struct cmsghdr) \
- (char *)(m)->msg_control) > (m)->msg_controllen \
? (struct cmsghdr *)0 \
: (struct cmsghdr *)((char *)(c) + CMSG_ALIGN((c)->cmsg_len)))
#define UNIX_PATH_MAX 108
#define SHUT_RD 0
#define SHUT_WR 1
#define SHUT_RDWR 2
struct sockaddr_un {
uint16 sun_family;
char sun_path[UNIX_PATH_MAX];
};
// The generic form syscalls take. Same shape as Linux's, so a musl-linked
// program's struct lines up byte for byte.
struct sockaddr {
uint16 sa_family;
char sa_data[UNIX_PATH_MAX];
};
// The AF_INET address. Same layout as Linux's, so a musl-linked program's
// struct lines up byte for byte. sin_port and sin_addr are network byte order
// and stay that way: lwIP wants them so, and converting on the way in and out
// again would be two swaps that cancel.
struct sockaddr_in {
uint16 sin_family;
uint16 sin_port;
uint32 sin_addr;
char sin_zero[8];
};
// ---------------------------------------------------------------------------
// rtnetlink, enough of it for getifaddrs(3)
//
// Every structure below is Linux's, byte for byte, because musl parses what it
// is handed with Linux's own headers. They are not ours to design.
//
// What musl actually does (src/network/netlink.c) is the whole specification
// this has to meet: open PF_NETLINK/SOCK_RAW/NETLINK_ROUTE, send an
// nlmsghdr+rtgenmsg asking RTM_GETLINK with NLM_F_DUMP, read replies until
// NLMSG_DONE, then do it again for RTM_GETADDR on the same socket.
// ---------------------------------------------------------------------------
struct nlmsghdr {
uint32 nlmsg_len;
uint16 nlmsg_type;
uint16 nlmsg_flags;
uint32 nlmsg_seq;
uint32 nlmsg_pid;
};
struct rtgenmsg { uchar rtgen_family; };
struct ifinfomsg {
uchar ifi_family;
uchar __ifi_pad;
uint16 ifi_type; // ARPHRD_*
int32 ifi_index;
uint32 ifi_flags; // IFF_*
uint32 ifi_change;
};
struct ifaddrmsg {
uchar ifa_family;
uchar ifa_prefixlen;
uchar ifa_flags;
uchar ifa_scope;
uint32 ifa_index;
};
struct rtattr {
uint16 rta_len;
uint16 rta_type;
};
#define NLMSG_ERROR 2
#define NLMSG_DONE 3
#define NLM_F_REQUEST 0x001
#define NLM_F_MULTI 0x002
#define NLM_F_DUMP 0x300
#define RTM_NEWLINK 16
#define RTM_GETLINK 18
#define RTM_NEWADDR 20
#define RTM_GETADDR 22
#define IFLA_ADDRESS 1
#define IFLA_IFNAME 3
#define IFA_ADDRESS 1
#define IFA_LOCAL 2
#define IFA_LABEL 3
#define ARPHRD_ETHER 1
#define ARPHRD_LOOPBACK 772
#define IFF_UP 0x1
#define IFF_LOOPBACK 0x8
#define IFF_RUNNING 0x40
#define IFF_BROADCAST 0x2
#define IFF_MULTICAST 0x1000
// Both are four-byte aligned on every architecture Linux supports.
#define NLMSG_ALIGN(n) (((n) + 3) & ~3)
#define RTA_ALIGN(n) (((n) + 3) & ~3)
#ifdef _KERNEL_SOCKET_INTERNALS
#define NSOCK 32 // sockets in the system
#define SOCKBUF 4096 // receive buffer per connected socket
#define NBACKLOG 8
// Descriptors in flight.
//
// A batch is pinned to the stream position of the first byte of the message
// that carried it, and that is the whole delivery rule: a reader that arrives
// at that position gets the descriptors, and a reader that has not reached it
// yet is stopped *short* of it rather than allowed to read past. Without the
// second half a program that read 4096 bytes in one call would swallow three
// messages and be handed one message's descriptors, with no way to tell which.
//
// Eight batches in flight is not a guess: a sender that has eight unreceived
// messages carrying descriptors and keeps going is not going to be rescued by
// a ninth slot, and ENOBUFS says so where a larger array would only move the
// same failure further away.
#define NSCM 8 // messages carrying descriptors, in flight
struct file;
struct scm_fds {
uint pos; // stream position of the message's first byte
int n;
struct file *f[SCM_MAXFD]; // references held by the kernel, not by anyone
};
enum sockstate {
SS_FREE = 0,
SS_UNBOUND,
SS_BOUND,
SS_LISTENING,
SS_CONNECTED,
};
// Every field here is protected by the socket table's single lock in
// kernel/net/unix.c -- there is deliberately no per-socket lock; see the
// comment at the top of that file for why.
struct socket {
int ref;
int type;
int state;
int domain; // AF_UNIX or AF_INET
// For AF_INET: the lwIP-backed socket in kernel/net/inet.c. Opaque here so
// that socket.h does not have to drag lwIP's headers into every file that
// wants a struct socket.
void *inet;
char name[UNIX_PATH_MAX]; // the bound path, for a listener
// A connected socket's peer. Writing to a socket appends to the *peer's*
// buffer, which is what makes one buffer per socket enough for a
// bidirectional connection.
struct socket *peer;
int peer_closed;
char data[SOCKBUF];
uint nread, nwrite;
// Connections waiting to be accepted, on a listener.
struct socket *queue[NBACKLOG];
int nqueue;
// Socket options that are genuinely honoured. Kept here rather than only in
// lwIP's pcb because getsockopt has to answer for an AF_UNIX socket too, and
// because a flag set before bind() has to survive until there is a pcb to
// apply it to.
int o_reuseaddr;
int o_keepalive;
int o_broadcast;
int o_nodelay;
// Descriptors sent to this socket and not yet received. They live on the
// *receiving* side, next to the bytes they were sent with, because that is
// the only side that can say when they have been delivered.
struct scm_fds scm[NSCM];
int nscm;
};
#endif