SchOS/kernel/net/unix.c
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

934 lines
28 KiB
C

// Unix domain sockets.
//
// A connected pair is two buffers, not one. Writing to a socket appends to
// its *peer's* buffer and reading drains its own, which is what makes a
// socket bidirectional where a pipe is not, without needing two of anything.
//
// **One lock covers the whole table**, rather than a lock per socket. A pipe
// gets away with a lock per pipe because every operation touches exactly one
// object; a socket operation touches two, and two-object locking needs an
// ordering rule that every path obeys or it deadlocks. Worse, the wakeup
// races are subtle: a reader that tests `peer_closed` under its own lock while
// the closer sets it under the peer's has no interlock at all, and sleeps
// through the wakeup that was meant for it. With one lock the condition and
// the sleep are atomic with respect to every writer of that condition, which
// is the property sleep/wakeup actually requires. Thirty-two sockets carrying
// GUI messages will not notice the contention.
//
// **Names are held in a table here, not in the filesystem.** A real Unix
// socket appears in a directory listing as a special file, and binding one
// creates that node. Doing that properly means a new inode type threaded
// through both filesystems, iget, dirlookup and stat -- a VFS change, not a
// socket change. The table gives every behaviour that matters: a client
// finds a server by a name it agreed in advance, which is the whole job.
// What is lost is `ls -l /tmp/schcom` showing an `s`, and `unlink()` of a
// socket path failing with ENOENT. Both are worth fixing when the VFS next
// gets opened up; neither blocks anything.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "fs.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "file.h"
#include "posix.h"
#include "errno.h"
#define _KERNEL_SOCKET_INTERNALS
#include "socket.h"
#include "sbuf.h"
#include "dbg.h"
static struct {
struct spinlock lock;
struct socket sock[NSOCK];
} stab;
// Defined with the rest of the descriptor-passing code below; declared here
// because sockclose is the one caller that cannot be moved after it.
static void scm_drain(struct socket *s);
// The socket table.
//
// Two columns here are the ones worth having: `buf`, because a peer that has
// stopped reading is invisible until its buffer is full and the writer blocks;
// and `scm`, because a descriptor sent over a socket and never received holds
// a kernel reference open, and there is no garbage collector for it -- see
// "What it does not do" in docs/architecture/networking.md.
static void
sock_dump(struct sbuf *out, const char *arg)
{
(void)arg;
struct socket *s;
int used = 0;
sb_puts(out, " slot dom state ref buf peer scm name\n");
acquire(&stab.lock);
for (s = stab.sock; s < stab.sock + NSOCK; s++) {
if (s->state == SS_FREE && s->ref == 0)
continue;
used++;
sb_putu_pad(out, (int)(s - stab.sock), 6);
sb_puts(out, " ");
sb_pad(out, s->domain == AF_UNIX ? "unix" :
s->domain == AF_INET ? "inet" :
s->domain == AF_NETLINK ? "nl" : "?", 7);
sb_pad(out, s->state == SS_FREE ? "free" :
s->state == SS_UNBOUND ? "unbound" :
s->state == SS_BOUND ? "bound" :
s->state == SS_LISTENING ? "listening" :
s->state == SS_CONNECTED ? "connected" : "?", 11);
sb_putu_pad(out, s->ref, 3);
sb_putu_pad(out, s->nwrite - s->nread, 7);
sb_puts(out, " ");
if (s->peer)
sb_putu_pad(out, (int)(s->peer - stab.sock), 4);
else
sb_puts(out, s->peer_closed ? "gone" : " -");
sb_putu_pad(out, s->nscm, 5);
sb_puts(out, " ");
if (s->name[0])
sb_puts(out, s->name);
else if (s->nqueue)
sb_putu(out, s->nqueue);
sb_putc(out, '\n');
}
release(&stab.lock);
if (!used)
sb_puts(out, " (no sockets)\n");
sb_puts(out, "\n buf is bytes written and not yet read; scm is descriptors\n"
" sent and not yet received, which nothing reclaims.\n");
}
void
sockinit(void)
{
initlock(&stab.lock, "socktab");
dbg_register("sock", sock_dump,
"the socket table, with buffered bytes and descriptors in flight");
}
// Caller holds stab.lock.
static struct socket *
alloc_locked(int type)
{
struct socket *s;
for(s = stab.sock; s < stab.sock + NSOCK; s++){
if(s->state == SS_FREE && s->ref == 0){
memset(s, 0, sizeof(*s));
s->ref = 1;
s->type = type;
s->state = SS_UNBOUND;
return s;
}
}
return 0;
}
// Find a socket listening on this name. Caller holds stab.lock.
static struct socket *
find_listener(const char *name)
{
struct socket *s;
for(s = stab.sock; s < stab.sock + NSOCK; s++)
if(s->state == SS_LISTENING && strncmp(s->name, name, UNIX_PATH_MAX) == 0)
return s;
return 0;
}
// Detach s from its peer and tell the peer about it. Caller holds stab.lock.
static void
orphan_peer(struct socket *s)
{
struct socket *peer = s->peer;
if(peer == 0)
return;
peer->peer_closed = 1;
peer->peer = 0;
s->peer = 0;
// Both channels, because the peer may be blocked reading (waiting for data
// that will now never come) or writing (waiting for room in a buffer nobody
// will ever drain).
wakeup(&peer->nread);
wakeup(&peer->nwrite);
}
void
sockclose(struct socket *s)
{
int i;
if(s == 0)
return;
// An AF_INET socket's state lives in inet.c behind the lwIP lock, so its
// teardown happens there. The table entry below is freed either way.
if(s->domain == AF_INET){
acquire(&stab.lock);
int last = (--s->ref <= 0);
if(last)
s->state = SS_FREE;
release(&stab.lock);
if(last && s->inet){
inet_close(s->inet);
s->inet = 0;
}
return;
}
struct socket *q[NBACKLOG];
int nq;
acquire(&stab.lock);
if(--s->ref > 0){
release(&stab.lock);
return;
}
// A listener that goes away takes its unaccepted connections with it. Each
// queued entry is the *server* side of a connection whose client is already
// live and may be writing into it; that client has to be told, or it hangs
// on a socket that will never be read.
//
// Taking the backlog out here rather than walking it below is what lets the
// drain happen with the lock dropped. At this point the reference count is
// zero and the state is not yet SS_FREE, so nothing can find these sockets
// and alloc_locked will not reuse them either.
nq = s->nqueue;
for(i = 0; i < nq; i++)
q[i] = s->queue[i];
s->nqueue = 0;
release(&stab.lock);
// Descriptors that were sent to these sockets and never received have to be
// closed rather than leaked -- the kernel is holding the only reference.
scm_drain(s);
for(i = 0; i < nq; i++)
if(q[i])
scm_drain(q[i]);
acquire(&stab.lock);
orphan_peer(s);
for(i = 0; i < nq; i++){
if(q[i] == 0)
continue;
orphan_peer(q[i]);
q[i]->state = SS_FREE;
q[i]->ref = 0;
}
s->state = SS_FREE;
s->ref = 0;
wakeup(&s->nqueue); // an accept() blocked on this listener
release(&stab.lock);
}
void
sockdup(struct socket *s)
{
acquire(&stab.lock);
s->ref++;
release(&stab.lock);
}
// ---------------------------------------------------------------------------
// Socket options
//
// Only the options that are genuinely honoured appear here; everything else is
// answered ENOPROTOOPT by the switch's default. That is deliberate and it is a
// change of policy: both calls used to succeed unconditionally, which is the
// worst of the three possible answers. A caller told "no" adapts; a caller told
// "yes" falsely proceeds on the strength of it -- setting SO_RCVTIMEO and then
// blocking forever in a read it believes will time out.
//
// The flags are recorded here as well as pushed into lwIP so that getsockopt
// has one answer rather than two, and so that an AF_UNIX socket -- which has no
// pcb and for which none of these mean anything -- can still give back what was
// set, as POSIX requires.
int
sockopt_set(struct socket *s, int level, int name, int val)
{
int r = 0;
acquire(&stab.lock);
if (level == SOL_SOCKET) {
switch (name) {
case SO_REUSEADDR: s->o_reuseaddr = !!val; break;
case SO_KEEPALIVE: s->o_keepalive = !!val; break;
case SO_BROADCAST: s->o_broadcast = !!val; break;
// Accepted and left at the size we have. Not the same lie as before:
// Linux is explicitly free to clamp these, and sockopt_get below reports
// the real figure rather than the requested one.
case SO_SNDBUF:
case SO_RCVBUF: release(&stab.lock); return 0;
default: release(&stab.lock); return -ENOPROTOOPT;
}
} else if (level == IPPROTO_TCP && name == TCP_NODELAY) {
if (s->type != SOCK_STREAM) { release(&stab.lock); return -EINVAL; }
s->o_nodelay = !!val;
} else {
release(&stab.lock);
return -ENOPROTOOPT;
}
int inet = (s->domain == AF_INET);
void *h = s->inet;
release(&stab.lock);
// Outside the table lock: inet_setopt takes the lwIP lock, which is a
// sleeplock, and the same ordering rule the rest of this file follows.
if (inet && h)
r = inet_setopt(h, level, name, val);
return r;
}
int
sockopt_get(struct socket *s, int level, int name, int *out)
{
int r = 0;
acquire(&stab.lock);
if (level == IPPROTO_TCP && name == TCP_NODELAY) {
*out = s->o_nodelay;
} else if (level != SOL_SOCKET) {
r = -ENOPROTOOPT;
} else {
switch (name) {
case SO_TYPE: *out = s->type; break;
case SO_ACCEPTCONN: *out = (s->state == SS_LISTENING); break;
case SO_REUSEADDR: *out = s->o_reuseaddr; break;
case SO_KEEPALIVE: *out = s->o_keepalive; break;
case SO_BROADCAST: *out = s->o_broadcast; break;
// The real buffer, not a number somebody asked for.
case SO_SNDBUF:
case SO_RCVBUF: *out = SOCKBUF; break;
// An honest zero rather than the blanket one it replaces: connect() here
// is blocking and reports failure as its own return value, so there is no
// deferred error for SO_ERROR to carry. It becomes a real field the day a
// non-blocking connect exists.
case SO_ERROR: *out = 0; break;
default: r = -ENOPROTOOPT; break;
}
}
release(&stab.lock);
return r;
}
const char *
sockname(struct socket *s)
{
return s->name;
}
// ---------------------------------------------------------------------------
// Descriptor passing
//
// docs/plan/hybrid.md mechanism 1, and item 1 of foundations.md. The kernel
// holds a reference on every descriptor in flight, which is what makes the
// sender free to close its own copy the instant sendmsg returns -- and what
// makes dropping a socket with descriptors still queued a thing that has to be
// handled rather than a thing that happens to work.
//
// **What this does not have is a garbage collector.** A descriptor for an
// AF_UNIX socket, sent over that same socket and never received, holds a
// reference to the socket that holds the reference to it; neither side's close
// can reach zero and the pair leaks until reboot. Linux carries several hundred
// lines to break exactly these cycles. Nothing on either plan passes a socket
// over a socket -- Wayland passes memory and the driver work passes devices --
// so this is a known, bounded leak (thirty-two sockets) and not a silent one.
// ---------------------------------------------------------------------------
// Close every descriptor still queued on s.
//
// Called with the socket already unreachable but *not yet marked free*, and
// without stab.lock: fileclose can sleep. Popping one at a time keeps the
// kernel stack out of it -- a snapshot of every batch would be half a page.
static void
scm_drain(struct socket *s)
{
struct file *f;
int i, j;
for(;;){
f = 0;
acquire(&stab.lock);
for(i = 0; i < s->nscm && f == 0; i++)
for(j = 0; j < s->scm[i].n; j++)
if(s->scm[i].f[j]){
f = s->scm[i].f[j];
s->scm[i].f[j] = 0;
break;
}
if(f == 0){
s->nscm = 0;
release(&stab.lock);
return;
}
release(&stab.lock);
fileclose(f);
}
}
// ---------------------------------------------------------------------------
// Data
// ---------------------------------------------------------------------------
// Read bytes, and the descriptors that came with them.
//
// `fout` is an array of SCM_MAXFD slots, or 0. **Zero is not "ignore them"**:
// a plain read(2) of a message carrying descriptors *closes* them, because the
// alternative is holding kernel references for a receiver that has already
// proved it is not looking. That is what Linux does and it is the only choice
// that cannot leak.
int
sockread_fds(struct socket *s, char *addr, int n, struct file **fout, int *nfout)
{
struct file *taken[SCM_MAXFD];
int got = 0;
int i;
if(nfout)
*nfout = 0;
if(n < 0)
return -EINVAL;
if(s->domain == AF_INET)
return inet_read(s->inet, addr, n);
// Netlink never blocks: the answer was generated when the request was sent,
// so an empty buffer means the caller has already drained this dump rather
// than that more is coming. Blocking here would hang a getifaddrs() that
// asked one question too many.
if(s->domain == AF_NETLINK){
acquire(&stab.lock);
uint have = s->nwrite - s->nread;
if(have == 0){
release(&stab.lock);
return -EAGAIN;
}
if((uint)n > have)
n = (int)have;
memmove(addr, s->data + s->nread, (uint)n);
s->nread += (uint)n;
release(&stab.lock);
return n;
}
acquire(&stab.lock);
while(s->nread == s->nwrite && !s->peer_closed){
if(myproc()->killed || s->state == SS_FREE){
release(&stab.lock);
return -EINTR;
}
sleep(&s->nread, &stab.lock);
}
// A message carrying descriptors is a boundary. This read either starts
// exactly on one -- and takes them -- or is cut short so that it stops
// before it, leaving the next read to start there.
if(s->nscm > 0){
if(s->scm[0].pos == s->nread){
got = s->scm[0].n;
for(i = 0; i < got; i++)
taken[i] = s->scm[0].f[i];
for(i = 1; i < s->nscm; i++)
s->scm[i - 1] = s->scm[i];
s->nscm--;
}
// Positions strictly increase -- a message with descriptors carries at
// least one byte -- so after taking one the next is genuinely ahead.
if(s->nscm > 0 && (uint)n > s->scm[0].pos - s->nread)
n = (int)(s->scm[0].pos - s->nread);
}
for(i = 0; i < n; i++){
if(s->nread == s->nwrite)
break;
addr[i] = s->data[s->nread++ % SOCKBUF];
}
// Waking the writer matters even when nothing was read: it may be blocked
// on a full buffer that this call has just made room in.
wakeup(&s->nwrite);
release(&stab.lock);
// Outside the lock, because fileclose can sleep.
if(fout){
for(int j = 0; j < got; j++)
fout[j] = taken[j];
if(nfout)
*nfout = got;
} else {
for(int j = 0; j < got; j++)
fileclose(taken[j]);
}
return i;
}
int
sockread(struct socket *s, char *addr, int n)
{
return sockread_fds(s, addr, n, 0, 0);
}
// Write bytes, optionally handing `nf` descriptors over with them.
//
// The kernel takes its own reference on each before it gets here, so the
// sender may close its copies as soon as this returns. A return of *any*
// positive count means the descriptors were queued; a negative return means
// they were not, and are still the caller's to close. There is no third case,
// which is deliberate -- the attach happens under the same lock hold as the
// first byte, so no reader can slip between them.
int
sockwrite_fds(struct socket *s, char *addr, int n, struct file **f, int nf)
{
struct socket *dst;
int i;
if(n < 0)
return -EINVAL;
if(nf > 0 && (s->domain != AF_UNIX || n == 0))
return -EINVAL;
if(s->domain == AF_INET)
return inet_write(s->inet, addr, n);
// A netlink request is answered immediately, into this socket's own buffer
// rather than a peer's -- there is no peer. Doing the work here is what lets
// the read that follows never block, which matters because musl reads with
// MSG_DONTWAIT and treats a short read as the end of the dump.
if(s->domain == AF_NETLINK){
acquire(&stab.lock);
s->nread = s->nwrite = 0;
int got = netlink_request(addr, (uint)n, s->data, SOCKBUF);
if(got > 0)
s->nwrite = (uint)got;
release(&stab.lock);
return n; /* the request was consumed whole */
}
acquire(&stab.lock);
// Data goes into the peer's buffer, because that is where the peer will
// look for it. Same contract as a pipe with no reader: a signal, then an
// error. A process that writes to a dead connection and is not told about
// it spins forever believing it is making progress.
if(s->peer == 0){
release(&stab.lock);
sigsend(myproc()->pid, SIGPIPE);
return -EPIPE;
}
// Refuse early and cleanly rather than part-way through the bytes. A sender
// that is this far ahead of its receiver is not helped by a partial message.
if(nf > 0 && s->peer->nscm >= NSCM){
release(&stab.lock);
return -ENOBUFS;
}
for(i = 0; i < n; i++){
// Re-read every iteration: the peer can go away while we sleep below.
dst = s->peer;
while(dst && dst->nwrite == dst->nread + SOCKBUF){
if(myproc()->killed){
release(&stab.lock);
return i > 0 ? i : -EINTR;
}
wakeup(&dst->nread);
sleep(&dst->nwrite, &stab.lock);
dst = s->peer;
}
if(dst == 0){
release(&stab.lock);
sigsend(myproc()->pid, SIGPIPE);
return i > 0 ? i : -EPIPE; // a short write is still a write
}
// The descriptors ride at the position of the first byte, pinned here --
// between the room check above and the store below, with the lock held
// throughout, so the position cannot be read past before it exists.
if(i == 0 && nf > 0){
if(dst->nscm >= NSCM){
release(&stab.lock);
return -ENOBUFS;
}
struct scm_fds *b = &dst->scm[dst->nscm++];
b->pos = dst->nwrite;
b->n = nf;
for(int j = 0; j < nf; j++)
b->f[j] = f[j];
}
dst->data[dst->nwrite++ % SOCKBUF] = addr[i];
}
wakeup(&s->peer->nread);
release(&stab.lock);
return i;
}
int
sockwrite(struct socket *s, char *addr, int n)
{
return sockwrite_fds(s, addr, n, 0, 0);
}
// ---------------------------------------------------------------------------
// Connection setup
// ---------------------------------------------------------------------------
struct socket *
socknew(int domain, int type, int proto)
{
struct socket *s;
if(domain != AF_UNIX && domain != AF_INET && domain != AF_NETLINK)
return 0;
// AF_UNIX is stream-only here; AF_INET has datagrams too, and inet_new is
// what decides whether it can serve the type asked for.
if(domain == AF_UNIX && type != SOCK_STREAM)
return 0;
// AF_NETLINK carries whole messages and musl asks for SOCK_RAW; SOCK_DGRAM is
// accepted too because Linux does. NETLINK_ROUTE is the only protocol served
// -- see kernel/net/netlink.c, which answers RTM_GETLINK and RTM_GETADDR and
// nothing else.
if(domain == AF_NETLINK){
if(type != SOCK_RAW && type != SOCK_DGRAM)
return 0;
if(proto != NETLINK_ROUTE)
return 0;
}
acquire(&stab.lock);
s = alloc_locked(type);
if(s)
s->domain = domain;
release(&stab.lock);
if(s == 0)
return 0;
if(domain == AF_INET){
// Outside the table lock: inet_new takes the lwIP lock, which is a
// sleeplock, and sleeping while holding a spinlock is a panic.
s->inet = inet_new(type, proto);
if(s->inet == 0){
sockclose(s);
return 0;
}
}
return s;
}
// A connected pair with no name and no listener, for socketpair(2).
int
sockpair(struct socket **pa, struct socket **pb)
{
struct socket *a, *b;
acquire(&stab.lock);
if((a = alloc_locked(SOCK_STREAM)) == 0){
release(&stab.lock);
return -ENFILE;
}
if((b = alloc_locked(SOCK_STREAM)) == 0){
a->state = SS_FREE;
a->ref = 0;
release(&stab.lock);
return -ENFILE;
}
// The domain was never set here, and nothing noticed for as long as every
// data path asked only whether it was AF_INET or AF_NETLINK -- a socketpair
// socket answered "neither" by being zero. SCM_RIGHTS asks the other
// question, "is this AF_UNIX", and zero is the wrong answer to that one.
a->domain = AF_UNIX;
b->domain = AF_UNIX;
a->peer = b; a->state = SS_CONNECTED;
b->peer = a; b->state = SS_CONNECTED;
release(&stab.lock);
*pa = a;
*pb = b;
return 0;
}
int
sockbind(struct socket *s, const char *path)
{
struct socket *other;
if(path[0] == '\0')
return -EINVAL;
acquire(&stab.lock);
if(s->state != SS_UNBOUND){
release(&stab.lock);
return -EINVAL;
}
for(other = stab.sock; other < stab.sock + NSOCK; other++){
if(other != s && other->state >= SS_BOUND &&
strncmp(other->name, path, UNIX_PATH_MAX) == 0){
release(&stab.lock);
return -EADDRINUSE;
}
}
safestrcpy(s->name, path, UNIX_PATH_MAX);
s->state = SS_BOUND;
release(&stab.lock);
return 0;
}
int
socklisten(struct socket *s, int backlog)
{
if(s->domain == AF_INET)
return inet_listen(s->inet, backlog);
(void)backlog; // the queue is a fixed NBACKLOG either way
acquire(&stab.lock);
if(s->state != SS_BOUND && s->state != SS_LISTENING){
release(&stab.lock);
return -EINVAL;
}
s->state = SS_LISTENING;
release(&stab.lock);
return 0;
}
// Returns the server-side socket of a new connection with a reference already
// held, or 0 on error.
struct socket *
sockaccept(struct socket *s)
{
struct socket *c;
int i;
if(s->domain == AF_INET){
// The connection lwIP hands back needs a table entry of its own, because
// everything above this layer works in `struct socket` and knows nothing
// about lwIP.
void *h = inet_accept(s->inet);
if(h == 0)
return 0;
acquire(&stab.lock);
c = alloc_locked(s->type);
if(c){
c->domain = AF_INET;
c->state = SS_CONNECTED;
}
release(&stab.lock);
if(c == 0){
inet_close(h);
return 0;
}
c->inet = h;
return c;
}
acquire(&stab.lock);
if(s->state != SS_LISTENING){
release(&stab.lock);
return 0;
}
while(s->nqueue == 0){
if(myproc()->killed || s->state != SS_LISTENING){
release(&stab.lock);
return 0;
}
sleep(&s->nqueue, &stab.lock);
}
c = s->queue[0];
for(i = 0; i + 1 < s->nqueue; i++)
s->queue[i] = s->queue[i+1];
s->nqueue--;
release(&stab.lock);
return c;
}
int
sockconnect(struct socket *s, const char *path)
{
struct socket *srv, *peer;
acquire(&stab.lock);
if(s->state != SS_UNBOUND && s->state != SS_BOUND){
release(&stab.lock);
return -EINVAL;
}
srv = find_listener(path);
if(srv == 0){
release(&stab.lock);
return -ECONNREFUSED;
}
if(srv->nqueue >= NBACKLOG){
release(&stab.lock);
return -EAGAIN;
}
// Every connection needs a socket at each end. The listener is not one of
// them -- it stays listening, and accept() hands back this new one.
peer = alloc_locked(s->type);
if(peer == 0){
release(&stab.lock);
return -ENFILE;
}
peer->peer = s;
peer->state = SS_CONNECTED;
s->peer = peer;
s->state = SS_CONNECTED;
srv->queue[srv->nqueue++] = peer;
wakeup(&srv->nqueue); // an accept() may be blocked on this
release(&stab.lock);
// Deliberately does *not* wait for accept(). On Unix a queued connection is
// already established: the client may write, and the bytes sit in the
// server-side buffer until somebody accepts and reads them. Blocking here
// instead would deadlock any server that connects to itself, and would make
// a busy server's backlog useless.
return 0;
}
// shutdown(2). There is one buffer per direction and no per-direction flag,
// so a half-close is approximated: SHUT_RD stops this end reading anything
// further, and SHUT_WR/SHUT_RDWR give the peer end-of-file. What programs
// actually use shutdown for -- "I have finished sending, read until EOF and
// then reply" -- works; what is missing is the ability to keep reading after
// SHUT_WR, since the connection is torn down rather than half-closed.
int
sock_shutdown(struct socket *s, int how)
{
acquire(&stab.lock);
if(s->state != SS_CONNECTED){
release(&stab.lock);
return -ENOTCONN;
}
if(how == SHUT_RD || how == SHUT_RDWR){
s->peer_closed = 1;
wakeup(&s->nread);
}
if(how == SHUT_WR || how == SHUT_RDWR)
orphan_peer(s);
release(&stab.lock);
return 0;
}
// ---------------------------------------------------------------------------
// Readiness, for poll/select
// ---------------------------------------------------------------------------
int
sockreadable(struct socket *s)
{
if(s->domain == AF_INET)
return inet_readable(s->inet);
if(s->state == SS_LISTENING)
return s->nqueue > 0;
return s->nread != s->nwrite || s->peer_closed;
}
// The peer is gone and everything it sent has been read.
int
sock_hup(struct socket *s)
{
if(s->domain == AF_INET)
return inet_hup(s->inet);
if(s->state == SS_LISTENING)
return 0;
return s->peer == 0 && s->nread == s->nwrite;
}
/* ------------------------------------------------------------------ */
/* AF_INET addressing */
/* */
/* Separate entry points rather than an overloaded sockbind, because */
/* the two families do not name the same kind of thing: one takes a */
/* path, the other an address and a port. The syscall layer picks by */
/* the family in the sockaddr the caller passed. */
/* ------------------------------------------------------------------ */
int
sockbind_in(struct socket *s, uint addr, ushort port)
{
if(s->domain != AF_INET)
return -EAFNOSUPPORT;
int r = inet_bind(s->inet, addr, port);
if(r == 0){
acquire(&stab.lock);
s->state = SS_BOUND;
release(&stab.lock);
}
return r;
}
int
sockconnect_in(struct socket *s, uint addr, ushort port)
{
if(s->domain != AF_INET)
return -EAFNOSUPPORT;
int r = inet_connect(s->inet, addr, port);
if(r == 0){
acquire(&stab.lock);
s->state = SS_CONNECTED;
release(&stab.lock);
}
return r;
}
// Which family a socket belongs to. An accessor because kernel/sys/sys_net.c is
// the translation layer and deliberately does not see inside struct socket --
// it works in descriptors and user addresses, not in the socket's guts.
int
sock_domain(struct socket *s)
{
return s ? s->domain : 0;
}
// recvfrom and sendto, which differ from read and write only in carrying an
// address. Both live here rather than in sys_net.c for the same reason as
// sock_domain: the syscall layer must not know which side of the socket union
// is live.
//
// `port` comes back in host byte order, matching what sockbind_in and
// sockconnect_in take. The syscall layer converts on the way out, in the one
// place that also builds the sockaddr.
int
sockrecvfrom(struct socket *s, char *buf, int n, uint *addr, ushort *port)
{
if(n < 0)
return -EINVAL;
if(s->domain == AF_INET)
return inet_recvfrom(s->inet, buf, n, addr, port);
// A Unix socket has no address of this shape. Saying so by leaving the
// caller's address untouched, rather than inventing one, is what lets
// recvfrom report an address length of zero -- which is exactly what a
// program reading from an unnamed peer expects.
return sockread(s, buf, n);
}
int
socksendto(struct socket *s, char *buf, int n, uint addr, ushort port)
{
if(n < 0)
return -EINVAL;
if(s->domain == AF_INET)
return inet_sendto(s->inet, buf, n, addr, port);
if(addr)
return -EAFNOSUPPORT; // an AF_INET destination on an AF_UNIX socket
return sockwrite(s, buf, n);
}