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:
Nexoniarz 2026-08-09 13:47:01 +02:00
commit d0f7acf9eb
275 changed files with 51417 additions and 0 deletions

219
cmd/fbinfo/main.c Normal file
View file

@ -0,0 +1,219 @@
// fbinfo -- ask /dev/fb0 what it is, and optionally draw on it.
//
// This exists because the framebuffer is the one device whose correctness you
// cannot check by reading kernel source: the numbers have to come back from
// the hardware the bootloader actually programmed. Three places in the kernel
// used to assume the framebuffer lived at 0xfd000000 and was 800x600x32; they
// agreed with QEMU's stdvga by luck, and nothing would have told us when the
// luck ran out. Now `fbinfo` would.
//
// fbinfo print the geometry
// fbinfo fill draw a gradient over the whole screen through mmap
// fbinfo bars draw colour bars through write(), no mmap involved
//
// The two drawing modes take different paths on purpose. If `fill` works and
// `bars` does not, the mapping is right and the device's read/write is wrong;
// if it is the other way round, the reverse. Having both makes a failure say
// which half broke.
#include "types.h"
#include "user.h"
#include "fcntl.h"
#include "fb.h"
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define MAP_SHARED 0x01
static void
puts(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void*)s, n);
}
// Unsigned decimal. No printf in lib/, and pulling one in for six numbers
// would be a worse trade than fourteen lines.
static void
putu(unsigned long v)
{
char buf[24];
int i = 0;
if (v == 0) {
write(1, "0", 1);
return;
}
while (v > 0 && i < (int)sizeof(buf)) {
buf[i++] = (char)('0' + (v % 10));
v /= 10;
}
while (i > 0) {
i--;
write(1, &buf[i], 1);
}
}
static void
puthex(unsigned long v)
{
static const char d[] = "0123456789abcdef";
char buf[16];
int i = 0;
write(1, "0x", 2);
if (v == 0) {
write(1, "0", 1);
return;
}
while (v > 0 && i < (int)sizeof(buf)) {
buf[i++] = d[v & 0xf];
v >>= 4;
}
while (i > 0) {
i--;
write(1, &buf[i], 1);
}
}
// Name, then spaces out to a fixed column. Padding inside each string
// literal is what produced "bits_per_pixel32": the longest name happened to
// be exactly as wide as the column, so it got no separator at all and the
// value ran straight into it.
static void
label(const char *name)
{
int n = 0;
while (name[n]) n++;
write(1, (void*)name, n);
while (n < 16) { write(1, " ", 1); n++; }
}
static void
field(const char *name, unsigned long v)
{
label(name);
putu(v);
puts("\n");
}
static void
chanline(const char *name, struct fb_bitfield *b)
{
label(name);
puts("offset ");
putu(b->offset);
puts(", length ");
putu(b->length);
if (b->length == 0)
puts(" (absent)");
puts("\n");
}
int
main(int argc, char *argv[])
{
struct fb_var_screeninfo var;
struct fb_fix_screeninfo fix;
int fd;
fd = open("/dev/fb0", O_RDWR);
if (fd < 0) {
puts("fbinfo: cannot open /dev/fb0\n");
return 1;
}
if (ioctl(fd, FBIOGET_VSCREENINFO, &var) < 0) {
puts("fbinfo: FBIOGET_VSCREENINFO failed\n");
return 1;
}
if (ioctl(fd, FBIOGET_FSCREENINFO, &fix) < 0) {
puts("fbinfo: FBIOGET_FSCREENINFO failed\n");
return 1;
}
if (argc < 2) {
label("id");
puts(fix.id);
puts("\n");
field("xres", var.xres);
field("yres", var.yres);
field("bits_per_pixel", var.bits_per_pixel);
label("smem_start");
puthex(fix.smem_start);
puts("\n");
field("smem_len", fix.smem_len);
field("line_length", fix.line_length);
// The stride is the number worth staring at. A card is free to pad
// each row, and a program that computes row starts as
// y * xres * bytes_per_pixel instead of y * line_length draws a
// picture that shears progressively down the screen.
if (fix.line_length != var.xres * (var.bits_per_pixel / 8)) {
label("note");
puts("stride is padded; rows are not xres*bpp/8 apart\n");
}
chanline("red", &var.red);
chanline("green", &var.green);
chanline("blue", &var.blue);
chanline("transp", &var.transp);
return 0;
}
if (argv[1][0] == 'f') { // fill
unsigned char *fb = mmap(0, fix.smem_len,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (fb == 0 || (long)fb < 0) {
puts("fbinfo: mmap failed\n");
return 1;
}
unsigned bpp = var.bits_per_pixel / 8;
for (unsigned y = 0; y < var.yres; y++) {
unsigned char *row = fb + (unsigned long)y * fix.line_length;
for (unsigned x = 0; x < var.xres; x++) {
unsigned r = (x * 255) / (var.xres ? var.xres : 1);
unsigned g = (y * 255) / (var.yres ? var.yres : 1);
unsigned long px = ((unsigned long)r << var.red.offset) |
((unsigned long)g << var.green.offset) |
((unsigned long)0x80 << var.blue.offset);
unsigned char *p = row + (unsigned long)x * bpp;
for (unsigned b = 0; b < bpp; b++)
p[b] = (unsigned char)(px >> (b * 8));
}
}
puts("fbinfo: gradient drawn through mmap\n");
return 0;
}
if (argv[1][0] == 'b') { // bars
// One scanline at a time through write(). No mapping: this is the
// path that used to be devsw's nullwrite, i.e. a silent no-op.
static unsigned char row[8192];
unsigned bpp = var.bits_per_pixel / 8;
unsigned len = fix.line_length;
if (len > sizeof(row))
len = sizeof(row);
for (unsigned y = 0; y < var.yres; y++) {
unsigned band = (y * 8) / (var.yres ? var.yres : 1);
unsigned long px =
((unsigned long)((band & 1) ? 255 : 0) << var.red.offset) |
((unsigned long)((band & 2) ? 255 : 0) << var.green.offset) |
((unsigned long)((band & 4) ? 255 : 0) << var.blue.offset);
for (unsigned x = 0; x * bpp + bpp <= len; x++)
for (unsigned b = 0; b < bpp; b++)
row[x * bpp + b] = (unsigned char)(px >> (b * 8));
if (write(fd, row, len) != (int)len) {
puts("fbinfo: short write to /dev/fb0\n");
return 1;
}
}
puts("fbinfo: colour bars drawn through write()\n");
return 0;
}
puts("usage: fbinfo [fill|bars]\n");
return 1;
}

124
cmd/init/main.c Normal file
View file

@ -0,0 +1,124 @@
// SPDX-License-Identifier: Apache-2.0
// Schunix — minimal init (PID 1)
#include "types.h"
#include "user.h"
#include "fcntl.h"
static void
xputs(int fd, const char *s)
{
int n = 0;
while (s[n]) n++;
write(fd, (void *)s, n);
}
int
main(void)
{
if (getpid() != 1)
exit();
// Open console as fd 0; dup to fd 1 (stdout) and fd 2 (stderr).
if (open("console", O_RDWR) < 0) {
mknod("console", 1, 1);
open("console", O_RDWR);
}
dup(0);
dup(0);
// --- /dev ------------------------------------------------------------
// Minor numbers under major 1 follow Linux's mem-major layout where it
// costs nothing (null=2, zero=5, full=7, random=8, urandom=9).
// /dev/hole (was minor 4) is gone: it backed an abandoned experiment and
// nothing has referenced it since.
mkdir("/dev");
mknod("/dev/console", 1, 1);
mknod("/dev/null", 1, 2);
mknod("/dev/keyboard", 1, 3);
mknod("/dev/zero", 1, 5);
mknod("/dev/full", 1, 7);
mknod("/dev/random", 1, 8);
mknod("/dev/urandom", 1, 9);
mknod("/dev/fb0", 2, 0);
mknod("/dev/mouse", 3, 1);
mknod("/dev/tty", 1, 1); // alias for console — must pass TCGETS ioctl
mknod("/dev/ttyS0", 4, 64);
mknod("/dev/stdin", 1, 1);
mknod("/dev/stdout", 1, 1);
mknod("/dev/stderr", 1, 1);
// Raw input events, Linux evdev layout (kernel/dev/input.c). /dev/keyboard
// and /dev/mouse above are the old character-and-position interfaces; these
// are the ones with key releases and button transitions, which is what a
// game or a compositor needs and what neither of the others can express.
mkdir("/dev/input");
mknod("/dev/input/event0", 7, 0); // keyboard
mknod("/dev/input/event1", 7, 1); // mouse
// --- /proc -----------------------------------------------------------
// Synthesised status files, major 6 (kernel/dev/procfs.c). These are the
// global ones; per-process /proc/<pid>/ directories need synthetic
// directory support in the filesystem, which does not exist yet.
mkdir("/proc");
// /proc is a real filesystem now (kernel/fs/procfs.c), not a handful of
// mknod'd nodes: it has to be, because one directory per process cannot be
// created ahead of time. This is what makes `ps` and `top` work.
//
// The mknod'd global files below still happen, and are simply covered by
// the mount. They stay for now so that a failure here degrades to the old
// behaviour instead of losing /proc entirely.
mount("proc", "/proc", "proc", 0, 0);
mknod("/proc/meminfo", 6, 1);
mknod("/proc/uptime", 6, 2);
mknod("/proc/cpuinfo", 6, 3);
mknod("/proc/version", 6, 4);
mknod("/proc/stat", 6, 5);
mknod("/proc/loadavg", 6, 6);
mknod("/proc/mounts", 6, 7);
mknod("/proc/filesystems", 6, 8);
// SchFS volume summary: label, generation, free space, and the root
// directory. Reachable whether or not the volume is mounted into the
// namespace, which is what makes it useful when a mount has gone wrong.
mknod("/proc/schfs", 6, 9);
// Write 'c' here to crash on purpose. The crash screen is otherwise the
// one part of the system that can only be seen by having a bug.
mknod("/proc/sysrq", 6, 10);
mknod("/proc/pci", 6, 11);
mknod("/proc/interrupts", 6, 12);
mkdir("/sys");
xputs(1, "\nSchunix\n");
xputs(1, "init: ready (pid 1)\n");
for (;;) {
int pid = fork();
if (pid < 0) {
xputs(2, "init: fork failed\n");
sleep(100);
continue;
}
if (pid == 0) {
// exec.c auto-enables Linux ABI for musl binaries (entry >= 0x100000).
// Do NOT call enable_linux_abi() here — it corrupts Schunix syscall
// numbers for the exec/exit calls that follow in this child.
// Standardized naming: the shell is always /bin/sh, regardless of
// which implementation backs it (dash, mksh, etc.) — no ksh/mksh/
// other aliases are looked up here.
static char *argv[] = { "sh", 0 };
exec("/bin/sh", argv);
xputs(2, "init: no shell found; place sh in /bin\n");
exit();
}
// Reap zombies; the loop restarts the shell when it exits.
int wpid;
while ((wpid = wait()) >= 0 && wpid != pid)
;
// Brief pause before restarting so we don't spin on a missing shell.
sleep(100);
}
return 0;
}

124
cmd/inputtest/main.c Normal file
View file

@ -0,0 +1,124 @@
// inputtest -- read raw events from /dev/input/eventN.
//
// The point of the raw devices is everything the console cannot say: which
// key was released, whether one is still held, and where the mouse went. None
// of that shows up in a character stream, so none of it can be tested by
// typing into a shell.
//
// inputtest read the keyboard until Esc is released
// inputtest mouse read the mouse until interrupted
// inputtest -n N stop after N events (used by the boot check)
//
// Prints one line per event: type, code and value, decoded where there is a
// name for it.
#include "types.h"
#include "user.h"
#include "fcntl.h"
#include "input.h"
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 const char *
typename_(int t)
{
switch (t) {
case EV_SYN: return "SYN";
case EV_KEY: return "KEY";
case EV_REL: return "REL";
default: return "?";
}
}
static int
streq(const char *a, const char *b)
{
while (*a && *a == *b) { a++; b++; }
return *a == *b;
}
int
main(int argc, char **argv)
{
const char *path = "/dev/input/event0";
int limit = 0, count = 0, i;
int fd;
struct input_event ev;
for (i = 1; i < argc; i++) {
if (streq(argv[i], "mouse"))
path = "/dev/input/event1";
else if (streq(argv[i], "-n") && i + 1 < argc) {
const char *p = argv[++i];
limit = 0;
while (*p >= '0' && *p <= '9') limit = limit * 10 + (*p++ - '0');
}
}
fd = open((char*)path, O_RDONLY);
if (fd < 0) {
puts_("inputtest: cannot open ");
puts_(path);
puts_("\n");
exit();
}
puts_("inputtest: reading ");
puts_(path);
puts_("\n");
for (;;) {
int n = read(fd, &ev, sizeof(ev));
// A short read would mean the device handed back part of a struct,
// which would desynchronise every event after it. Saying so is more
// use than silently carrying on with garbage.
if (n == 0) { puts_("inputtest: end of stream\n"); break; }
if (n < 0) { puts_("inputtest: read error\n"); break; }
if (n != sizeof(ev)) {
puts_("inputtest: SHORT READ -- events are not whole\n");
break;
}
puts_(" ");
puts_(typename_(ev.type));
puts_(" code ");
putn_(ev.code);
puts_(" value ");
putn_(ev.value);
if (ev.type == EV_KEY)
puts_(ev.value ? " (press)" : " (release)");
puts_("\n");
// Esc released ends the keyboard run, so a person can stop it without
// a signal. The boot check uses -n instead.
if (ev.type == EV_KEY && ev.code == KEY_ESC && ev.value == 0)
break;
if (limit && ++count >= limit) {
puts_("inputtest: event limit reached\n");
break;
}
}
close(fd);
puts_("inputtest: done\n");
exit();
}

112
cmd/shmtest/main.c Normal file
View file

@ -0,0 +1,112 @@
// shmtest -- prove that shared memory is actually shared, and that a
// file-backed mmap returns the file.
//
// Both of these are things you cannot check by reading a return value. shmget
// and shmat returned -ENOSYS until now, and mmap of a file "succeeded" while
// handing back zeroed anonymous pages -- a lie that looks exactly like
// success. The only convincing test is for one process to write and a
// different one to read.
//
// shmtest run every check
//
// Prints one line per check, and a final verdict a script can grep for.
#include "types.h"
#include "user.h"
#include "fcntl.h"
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define MAP_PRIVATE 0x02
#define MAP_SHARED 0x01
#define IPC_PRIVATE 0
#define IPC_CREAT 01000
static int failures;
static void
puts_(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void*)s, n);
}
static void
check(int ok, const char *what)
{
puts_(ok ? " [ok] " : " FAIL: ");
puts_(what);
puts_("\n");
if (!ok) failures++;
}
int
main(void)
{
// --- shared memory ----------------------------------------------------
int id = shmget(IPC_PRIVATE, 8192, IPC_CREAT | 0600);
check(id >= 0, "shmget created a segment");
if (id < 0) { puts_("shmtest: FAILED\n"); return 1; }
char *p = shmat(id, 0, 0);
check((long)p > 0, "shmat mapped it");
if ((long)p <= 0) { puts_("shmtest: FAILED\n"); return 1; }
// A fresh segment must be zeroed, or it leaks whatever the previous
// tenant left in those pages to whoever gets them next.
int zeroed = 1;
for (int i = 0; i < 8192; i++)
if (p[i] != 0) { zeroed = 0; break; }
check(zeroed, "a new segment reads as zeros");
p[0] = 'P';
p[1] = 0;
int pid = fork();
if (pid == 0) {
// The child inherited the attachment through fork. If the pages had
// been copied instead of shared, the parent would never see this.
p[0] = 'C';
p[100] = 'X';
exit();
}
wait();
check(p[0] == 'C', "a write by the child is visible in the parent");
check(p[100] == 'X', "and so is one further into the segment");
// --- file-backed mmap --------------------------------------------------
int fd = open("/tmp/shmtest.dat", O_CREATE | O_RDWR);
check(fd >= 0, "created a file to map");
if (fd >= 0) {
write(fd, "SCHUNIX-MMAP-OK", 15);
close(fd);
}
fd = open("/tmp/shmtest.dat", O_RDONLY);
if (fd >= 0) {
char *m = mmap(0, 4096, PROT_READ, MAP_PRIVATE, fd, 0);
int ok = ((long)m > 0) &&
m[0]=='S' && m[1]=='C' && m[2]=='H' && m[3]=='U' &&
m[8]=='M' && m[13]=='O' && m[14]=='K';
check(ok, "mmap of a file returns the file, not zeros");
close(fd);
} else {
check(0, "mmap of a file returns the file, not zeros");
}
// MAP_SHARED + PROT_WRITE on a file cannot be honoured without a page
// cache, and is refused rather than quietly downgraded to a private copy.
fd = open("/tmp/shmtest.dat", O_RDWR);
if (fd >= 0) {
void *m = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
check((long)m < 0, "writable MAP_SHARED on a file is refused, not faked");
close(fd);
}
check(shmdt(p) == 0, "shmdt released the attachment");
puts_(failures ? "shmtest: FAILED\n" : "shmtest: all checks passed\n");
return failures ? 1 : 0;
}

305
cmd/socktest/main.c Normal file
View 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();
}