SchOS/cmd/socktest/main.c
Nexoniarz d0f7acf9eb Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
2026-08-09 13:47:01 +02:00

305 lines
8 KiB
C

// socktest -- prove that Unix domain sockets carry data between processes.
//
// Every part of a socket is invisible from a return value. bind() returning 0
// proves nothing; a socket layer that accepts every call and drops every byte
// would pass a test built on return codes alone. So each check here has one
// process write and a *different* process read, and compares the bytes.
//
// socktest run every check
//
// Prints one line per check and a final verdict a script can grep for.
#include "types.h"
#include "user.h"
#include "socket.h"
static int failures;
static void
puts_(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void*)s, n);
}
static void
putn_(int v)
{
char buf[12];
int i = 0, neg = 0;
if (v < 0) { neg = 1; v = -v; }
if (v == 0) buf[i++] = '0';
while (v) { buf[i++] = '0' + (v % 10); v /= 10; }
if (neg) buf[i++] = '-';
while (i--) write(1, &buf[i], 1);
}
static void
check(int ok, const char *what)
{
puts_(ok ? " [ok] " : " FAIL: ");
puts_(what);
puts_("\n");
if (!ok) failures++;
}
static int
streq(const char *a, const char *b)
{
while (*a && *a == *b) { a++; b++; }
return *a == *b;
}
// Fill a sockaddr_un and return the length to pass to bind/connect: the
// family plus the path plus its NUL, which is what musl computes and what the
// kernel side is written to expect.
static int
mkaddr(struct sockaddr_un *sa, const char *path)
{
int i = 0;
for (i = 0; i < UNIX_PATH_MAX - 1 && path[i]; i++)
sa->sun_path[i] = path[i];
sa->sun_path[i] = '\0';
sa->sun_family = AF_UNIX;
return (int)sizeof(sa->sun_family) + i + 1;
}
// ---------------------------------------------------------------------------
// 1. A round trip over a named socket, between two processes.
// ---------------------------------------------------------------------------
static void
test_roundtrip(void)
{
struct sockaddr_un sa;
int len = mkaddr(&sa, "/tmp/socktest");
int srv, pid, cli, n;
char buf[64];
puts_("named socket, two processes:\n");
srv = socket(AF_UNIX, SOCK_STREAM, 0);
check(srv >= 0, "socket() returns a descriptor");
if (srv < 0) return;
check(bind(srv, (struct sockaddr *)&sa, len) == 0, "bind() to /tmp/socktest");
check(listen(srv, 4) == 0, "listen()");
// A second bind to the same name must be refused, or two servers can
// silently steal each other's clients.
{
int dup_ = socket(AF_UNIX, SOCK_STREAM, 0);
check(bind(dup_, (struct sockaddr *)&sa, len) < 0,
"a second bind to the same name is refused");
close(dup_);
}
pid = fork();
if (pid == 0) {
// Child: the client. Connects, sends a question, reads the answer.
int c = socket(AF_UNIX, SOCK_STREAM, 0);
char reply[64];
int r;
close(srv);
if (connect(c, (struct sockaddr *)&sa, len) < 0) {
puts_(" FAIL: child connect()\n");
exit();
}
write(c, "ping", 4);
r = read(c, reply, sizeof(reply) - 1);
if (r < 0) r = 0;
reply[r] = '\0';
if (!streq(reply, "pong"))
puts_(" FAIL: child did not receive pong\n");
close(c);
exit();
}
cli = accept(srv, 0, 0);
check(cli >= 0, "accept() returns a connection");
if (cli < 0) { close(srv); wait(); return; }
n = read(cli, buf, sizeof(buf) - 1);
if (n < 0) n = 0;
buf[n] = '\0';
check(streq(buf, "ping"), "server read the client's bytes");
check(write(cli, "pong", 4) == 4, "server replied on the same socket");
close(cli);
close(srv);
wait();
}
// ---------------------------------------------------------------------------
// 2. Errors that must actually be errors.
// ---------------------------------------------------------------------------
static void
test_errors(void)
{
struct sockaddr_un sa;
int len = mkaddr(&sa, "/tmp/nobody-listening");
int s;
puts_("error paths:\n");
s = socket(AF_UNIX, SOCK_STREAM, 0);
check(connect(s, (struct sockaddr *)&sa, len) < 0,
"connect() to a name nobody bound fails");
close(s);
check(socket(AF_INET, SOCK_STREAM, 0) < 0, "AF_INET is refused, not faked");
check(socket(AF_UNIX, SOCK_DGRAM, 0) < 0, "SOCK_DGRAM is refused, not faked");
// read/write on a plain file descriptor must not be mistaken for a socket,
// and socket calls on one must be refused.
check(listen(1, 4) < 0, "listen() on a non-socket fd fails");
}
// ---------------------------------------------------------------------------
// 3. End-of-file. A reader whose peer closes must be told, not left hanging.
// ---------------------------------------------------------------------------
static void
test_eof(void)
{
struct sockaddr_un sa;
int len = mkaddr(&sa, "/tmp/socktest-eof");
int srv, cli, pid, n;
char buf[16];
puts_("end of file:\n");
srv = socket(AF_UNIX, SOCK_STREAM, 0);
bind(srv, (struct sockaddr *)&sa, len);
listen(srv, 4);
pid = fork();
if (pid == 0) {
int c = socket(AF_UNIX, SOCK_STREAM, 0);
close(srv);
connect(c, (struct sockaddr *)&sa, len);
write(c, "x", 1);
close(c); // the peer goes away with the byte still unread
exit();
}
cli = accept(srv, 0, 0);
n = read(cli, buf, sizeof(buf));
check(n == 1, "the byte sent before close() is still delivered");
n = read(cli, buf, sizeof(buf));
check(n == 0, "the next read returns 0 rather than blocking");
close(cli);
close(srv);
wait();
}
// ---------------------------------------------------------------------------
// 4. socketpair: bidirectional, no name, no listener.
// ---------------------------------------------------------------------------
static void
test_socketpair(void)
{
int sv[2], n;
char buf[16];
puts_("socketpair:\n");
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
check(0, "socketpair() succeeds");
return;
}
check(1, "socketpair() succeeds");
write(sv[0], "a", 1);
n = read(sv[1], buf, sizeof(buf));
check(n == 1 && buf[0] == 'a', "0 -> 1 carries a byte");
write(sv[1], "b", 1);
n = read(sv[0], buf, sizeof(buf));
check(n == 1 && buf[0] == 'b', "1 -> 0 carries a byte, on the same pair");
close(sv[0]);
close(sv[1]);
}
// ---------------------------------------------------------------------------
// 5. A message longer than the socket buffer, so the writer has to block and
// the reader has to drain in more than one go.
// ---------------------------------------------------------------------------
#define BIG 12000
static void
test_big(void)
{
int sv[2], pid, got = 0, ok = 1, i;
static char out[BIG], in[BIG];
puts_("a transfer larger than the buffer:\n");
for (i = 0; i < BIG; i++)
out[i] = (char)('A' + (i % 26));
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
check(0, "socketpair() for the big transfer");
return;
}
pid = fork();
if (pid == 0) {
int sent = 0, r;
close(sv[1]);
while (sent < BIG) {
r = write(sv[0], out + sent, BIG - sent);
if (r <= 0) break;
sent += r;
}
close(sv[0]);
exit();
}
close(sv[0]);
while (got < BIG) {
int r = read(sv[1], in + got, BIG - got);
if (r <= 0) break;
got += r;
}
close(sv[1]);
wait();
check(got == BIG, "every byte arrived");
if (got != BIG) {
puts_(" got ");
putn_(got);
puts_(" of ");
putn_(BIG);
puts_("\n");
}
for (i = 0; i < got; i++)
if (in[i] != out[i]) { ok = 0; break; }
check(ok, "and in the right order");
}
int
main(void)
{
puts_("socktest: unix domain sockets\n");
test_roundtrip();
test_errors();
test_eof();
test_socketpair();
test_big();
if (failures == 0)
puts_("socktest: ALL CHECKS PASSED\n");
else {
puts_("socktest: ");
putn_(failures);
puts_(" CHECK(S) FAILED\n");
}
exit();
}