D371 - Files and sockets share one descriptor table, because a guest has one
decided - 2026-08-29
Sockets are the milestone rather than another subsystem, and the
reason is arithmetic: pros check - the independent tool
this project wants as its grader - does exactly one thing per service,
TcpStream::connect_timeout(...).is_ok(). No handshake, no
protocol. A service reads as up the moment the guest has a
listening socket on its port, so for klogsrv that
is reaching its listen call and nothing more.
And the deeper commands need no protocol work either.
ftpsrv implements FTP; klogsrv writes the log.
orbistoun owes them sockets and file calls, and the guest brings its own
protocol - which is the property that makes the grader worth having,
because every byte Prosperous sees was produced by guest code
executing.
One table
A guest closes a socket with close, reads one with
read, writes one with write. Two tables would
mean two numbering spaces and a descriptor that means different things
depending on which call is holding it - so Target grew a
Socket variant beside its File one, and those
three calls take either without being told which.
That also makes a server that reads a request with read
and one that reads it with recv the same program to
everything below the dispatch layer, which is what a guest expects and
what a second table would have made accidentally different.
A socket exists before it has anything to do
socket() answers a descriptor that is not yet a host
object: the host makes a listener by binding and listening in
one step, and a stream by connecting. So a descriptor starts pending,
remembers what bind was told, and becomes a host object at
listen or connect.
Bookkeeping rather than a claim. The guest sees the sequence it
wrote; the host sees the sequence it accepts. Binding at
bind and rebuilding at listen would hold the
port twice.
The byte that catches people
struct sockaddr_in { sys/netinet/in.h
uint8_t sin_len; offset 0
sa_family_t sin_family; offset 1
in_port_t sin_port; offset 2 network byte order
struct in_addr sin_addr; offset 4
char sin_zero[8]; offset 8
};
sin_len is not on most platforms. It is on this one, and
a shim written from memory of Linux reads offset zero and gets a length
where it wanted a family. It is in the checkout, so it was read rather
than recalled - and there is a test that asserts the family is taken
from offset one.
setsockopt
is accepted and applied to nothing
A server's first act after socket is
setsockopt(SO_REUSEADDR), and failing it stops the
server: a correct program checks, reports and exits. Refusing
outright would end every payload measured before it reached
bind.
Applying it is a different question. SO_REUSEADDR is
what the host's listener does by default on the platforms this runs on,
so honouring it changes nothing; the rest - timeouts, buffer sizes,
keepalive - would need a per-option mapping nothing here can verify, and
a wrong one is a socket behaving differently from what the guest asked
for with nothing saying so.
Accepted, recorded as not applied, and the knowledge file says which. That is the honest shape of the call succeeded and the option did nothing.
Where they are declared
Eight of the eleven were measured being imported from
libScePosix by a title. accept,
listen and getpeername were not - a title
connects rather than listens - so they are declared there by
inference from their siblings, and each says so in its own
knowledge entry rather than being passed off as measured (D367).