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.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
305
cmd/socktest/main.c
Normal file
305
cmd/socktest/main.c
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
// 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();
|
||||
}
|
||||
Loading…
Reference in a new issue