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
219
cmd/fbinfo/main.c
Normal file
219
cmd/fbinfo/main.c
Normal 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
124
cmd/init/main.c
Normal 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
124
cmd/inputtest/main.c
Normal 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
112
cmd/shmtest/main.c
Normal 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
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