RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

authorGrok Bot <grokbot@localhost>2026-10-31 19:18:30 -0700
commit3219ac028e84cecb701829744852c66efa89e4e6
parent7acb8de4eb9f4927355be39372d2d866b446b9fe
treebrowse files at this commit
c/: hello-musl names the kernel and ELF type; fprobe, a 40-check libc self-check

fprobe is a small, deterministic cousin of Clarke's mprobe2 (read from
clarke-scratch/exp16..exp19, not modified): ELF/auxv, files in /tmp,
fork/exec/wait, pipes, signals, threads + TLS, mmap/mremap/malloc, clocks
and timerfd (Rustbelt 0.20.0), incl. CANCEL_ON_SET -> EINVAL. Timing
checks leave room for the ~55 ms tick; mprobe2's benchmarks are left out.
Mc/README.md+15 -3
Ac/fprobe.c+344 -0
Mc/hello-musl.c+20 -10

3 files changed, 379 insertions, 13 deletions

@@ -1,6 +1,18 @@ # c/: small C programs of our own -libc smoke tests for Foundry OS, built against musl. Not in the image yet-(milestone M2 adds them to /bin and to the self-test).+Built by `make` (target `cprogs`) with `toolchain/foundry-cc`: gcc + our+musl (`make musl` builds it into `out/sysroot`), as **static-PIEs**. They+are installed in `/bin` and the self-test runs them (the "Foundry OS" block+at the end of `rootfs/scripts/selftest.sh`; expected output in+`scripts/expect-foundry.txt`). -- `hello-musl.c`: prints one line; build with `toolchain/foundry-cc` (static-PIE; `make musl` first).+- `hello-musl.c`: prints `hello from musl on Rustbelt 0.20.0 (Foundry OS, static-PIE)`.+- `fprobe.c`: the libc self-check, a small, deterministic cousin of+  Clarke's `mprobe2` probe (clarke-scratch/exp16..exp19). 40 checks: ELF+  and auxv, files in /tmp, fork/exec/wait, pipes, signals, threads and+  TLS, mmap/mremap/malloc, clocks, and timerfd (new in Rustbelt 0.20.0).+  Each prints `ok` or `FAIL`; the last line is `fprobe: all 40 checks passed`.++Both also run on a Linux PC: `toolchain/foundry-cc -O2 -pthread c/fprobe.c -o out/fprobe && out/fprobe`.++Adding a program: see docs/porting.md, "Our own C programs".
@@ -0,0 +1,344 @@+/* fprobe: Foundry OS's libc self-check (milestone M2).+ *+ * A small, deterministic cousin of Clarke's mprobe2 probe+ * (clarke-scratch/exp16..exp19/mprobe2.c): it checks what the C library+ * gets back from the Rustbelt kernel -- the VALUES, not just "returned 0".+ * Every check prints one line ending in "ok" or "FAIL"; the last line is+ *   fprobe: all N checks passed        (exit code 0), or+ *   fprobe: F of N checks FAILED       (exit code F).+ * Timing checks leave room for Rustbelt's ~55 ms timer tick. The+ * benchmark-style checks of mprobe2 (which can fail on a busy host) are+ * left out on purpose: `make test` must not be flaky.+ *+ * Built with toolchain/foundry-cc (static-PIE). Runs on Linux too:+ *   toolchain/foundry-cc -O2 -pthread c/fprobe.c -o out/fprobe && out/fprobe+ * (a few checks are Rustbelt-specific and accept Linux's answer as well).+ */+#define _GNU_SOURCE+#include <dirent.h>+#include <elf.h>+#include <errno.h>+#include <fcntl.h>+#include <ftw.h>+#include <poll.h>+#include <pthread.h>+#include <signal.h>+#include <stdint.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>+#include <sys/auxv.h>+#include <sys/epoll.h>+#include <sys/mman.h>+#include <sys/stat.h>+#include <sys/timerfd.h>+#include <sys/utsname.h>+#include <sys/wait.h>+#include <time.h>+#include <unistd.h>++static int fails, checks, on_rustbelt;++/* One check: print its name, then ok or FAIL (with errno when it failed). */+static void chk(const char *what, int ok)+{+	int e = errno;+	checks++;+	if (!ok)+		fails++;+	printf("  %-46s %s", what, ok ? "ok" : "FAIL");+	if (!ok)+		printf(" (errno %d %s)", e, strerror(e));+	printf("\n");+	errno = 0;+}++static long long now_ms(void)+{+	struct timespec t;+	clock_gettime(CLOCK_MONOTONIC, &t);+	return t.tv_sec * 1000LL + t.tv_nsec / 1000000;+}++static void ms_sleep(long ms)+{+	struct timespec t = { ms / 1000, ms % 1000 * 1000000L };+	nanosleep(&t, NULL);+}++/* ---- the program itself: ELF, auxv ------------------------------------- */+extern const char __ehdr_start[];++static void elf_and_auxv(void)+{+	printf("fprobe: -- ELF and auxv\n");+	const Elf64_Ehdr *me = (const void *)__ehdr_start;+	const Elf64_Phdr *ph = (const void *)(__ehdr_start + me->e_phoff);+	int interp = 0;+	for (int i = 0; i < me->e_phnum; i++)+		interp |= ph[i].p_type == PT_INTERP;+	chk("static-PIE: ET_DYN without PT_INTERP", me->e_type == ET_DYN && !interp);+	chk("AT_BASE == 0 (no dynamic loader)", getauxval(AT_BASE) == 0);+	chk("AT_PHDR == our header + e_phoff",+	    getauxval(AT_PHDR) == (unsigned long)__ehdr_start + me->e_phoff);+	chk("AT_PAGESZ 4096, AT_RANDOM present",+	    getauxval(AT_PAGESZ) == 4096 && getauxval(AT_RANDOM) != 0);+	chk("vDSO mapped (AT_SYSINFO_EHDR)", getauxval(AT_SYSINFO_EHDR) != 0);+}++/* ---- files in /tmp ------------------------------------------------------ */+static int rm_cb(const char *p, const struct stat *s, int f, struct FTW *w)+{+	(void)s, (void)f, (void)w;+	return remove(p);+}++static int count_dir(const char *d)+{+	DIR *dd = opendir(d);+	struct dirent *e;+	int n = 0;+	if (!dd)+		return -1;+	while ((e = readdir(dd)))+		if (strcmp(e->d_name, ".") && strcmp(e->d_name, ".."))+			n++;+	closedir(dd);+	return n;+}++static void files(void)+{+	printf("fprobe: -- files (/tmp)\n");+	const char *D = "/tmp/fprobe", *F = "/tmp/fprobe/a.txt";+	struct stat st, st2;+	char buf[64] = { 0 };+	nftw(D, rm_cb, 8, FTW_DEPTH | FTW_PHYS); /* leftovers from an earlier run */+	umask(022);+	chk("mkdir", mkdir(D, 0755) == 0);+	int fd = open(F, O_CREAT | O_TRUNC | O_RDWR, 0666);+	chk("open O_CREAT 0666 -> mode 0644 (umask 022)",+	    fd >= 0 && fstat(fd, &st) == 0 && (st.st_mode & 0777) == 0644);+	chk("write + lseek + read back",+	    write(fd, "hello world\n", 12) == 12 && lseek(fd, 6, SEEK_SET) == 6 &&+		    read(fd, buf, sizeof buf - 1) == 6 && strcmp(buf, "world\n") == 0);+	chk("ftruncate -> st_size 5", ftruncate(fd, 5) == 0 && fstat(fd, &st) == 0 && st.st_size == 5);+	chk("mtime is a real date (after 2020)", st.st_mtime > 1600000000);+	close(fd);+	chk("rename", rename(F, "/tmp/fprobe/b.txt") == 0 && stat(F, &st) == -1 && errno == ENOENT);+	errno = 0;+	chk("link: same inode, st_nlink 2",+	    link("/tmp/fprobe/b.txt", "/tmp/fprobe/c.txt") == 0 && stat("/tmp/fprobe/b.txt", &st) == 0 &&+		    stat("/tmp/fprobe/c.txt", &st2) == 0 && st.st_ino == st2.st_ino && st.st_nlink == 2);+	memset(buf, 0, sizeof buf);+	chk("symlink + readlink",+	    symlink("b.txt", "/tmp/fprobe/l") == 0 && readlink("/tmp/fprobe/l", buf, sizeof buf) == 5 &&+		    strcmp(buf, "b.txt") == 0);+	chk("lstat sees the link, stat follows it",+	    lstat("/tmp/fprobe/l", &st) == 0 && S_ISLNK(st.st_mode) && stat("/tmp/fprobe/l", &st2) == 0 &&+		    S_ISREG(st2.st_mode) && st2.st_size == 5);+	for (int i = 0; i < 20; i++) {+		snprintf(buf, sizeof buf, "/tmp/fprobe/f%02d", i);+		close(open(buf, O_CREAT | O_WRONLY, 0644));+	}+	chk("readdir lists all 23 entries", count_dir(D) == 23);+	chk("nftw(FTW_DEPTH) removes the tree (rm -r)",+	    nftw(D, rm_cb, 8, FTW_DEPTH | FTW_PHYS) == 0 && stat(D, &st) == -1);+	errno = 0;+	chk("open of a missing file -> ENOENT", open("/tmp/no/such", O_RDONLY) == -1 && errno == ENOENT);+}++/* ---- processes, pipes, signals ------------------------------------------ */+static volatile sig_atomic_t got;+static void on_sig(int s) { got = s; }++static void processes(void)+{+	printf("fprobe: -- processes, pipes, signals\n");+	int p[2], st = 0;+	chk("pipe", pipe(p) == 0);+	pid_t c = fork();+	if (c == 0) {+		close(p[0]);+		dprintf(p[1], "child %d", (int)getpid());+		_exit(7);+	}+	close(p[1]);+	char buf[32] = { 0 };+	int n = read(p[0], buf, sizeof buf - 1);+	close(p[0]);+	chk("fork: child writes into the pipe", n > 6 && strncmp(buf, "child ", 6) == 0 && atoi(buf + 6) == c);+	chk("waitpid: exit status 7", waitpid(c, &st, 0) == c && WIFEXITED(st) && WEXITSTATUS(st) == 7);+	c = fork();+	if (c == 0) {+		execl("/no/such/program", "x", (char *)NULL);+		_exit(errno == ENOENT ? 3 : 4);+	}+	chk("execve of a missing file -> ENOENT", waitpid(c, &st, 0) == c && WEXITSTATUS(st) == 3);+	c = fork();+	if (c == 0) {+		pause();+		_exit(0);+	}+	kill(c, SIGTERM);+	chk("kill(SIGTERM) -> WIFSIGNALED",+	    waitpid(c, &st, 0) == c && WIFSIGNALED(st) && WTERMSIG(st) == SIGTERM);+	struct sigaction sa = { .sa_handler = on_sig };+	sigemptyset(&sa.sa_mask);+	sigaction(SIGUSR1, &sa, NULL);+	sigset_t s, old, pend;+	sigemptyset(&s);+	sigaddset(&s, SIGUSR1);+	sigprocmask(SIG_BLOCK, &s, &old);+	got = 0;+	raise(SIGUSR1);+	sigemptyset(&pend);+	chk("blocked signal stays pending", got == 0 && sigpending(&pend) == 0 && sigismember(&pend, SIGUSR1));+	sigprocmask(SIG_SETMASK, &old, NULL);+	chk("unblocking runs the handler", got == SIGUSR1);+}++/* ---- threads, memory ----------------------------------------------------- */+static __thread int tls = 7;+static long counter;+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;++static void *worker(void *arg)+{+	int ok = tls == 7;+	tls = 100 + (int)(long)arg;+	for (int i = 0; i < 20000; i++) {+		pthread_mutex_lock(&lock);+		counter++;+		pthread_mutex_unlock(&lock);+	}+	return (void *)(long)(ok && tls == 100 + (int)(long)arg);+}++static void threads_and_memory(void)+{+	printf("fprobe: -- threads and memory\n");+	pthread_t t[4];+	long all = 1;+	for (long i = 0; i < 4; i++)+		pthread_create(&t[i], NULL, worker, (void *)i);+	for (int i = 0; i < 4; i++) {+		void *r;+		pthread_join(t[i], &r);+		all &= (long)r;+	}+	chk("4 threads: own copy of __thread data", all && tls == 7);+	chk("4 threads x 20000 mutex increments = 80000", counter == 80000);+	char *m = mmap(NULL, 1 << 20, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);+	int zero = m != MAP_FAILED;+	for (int i = 0; zero && i < 1 << 20; i += 4096)+		zero = m[i] == 0;+	chk("mmap 1 MiB anonymous, zero-filled", zero);+	if (m != MAP_FAILED) {+		memset(m, 0x5a, 1 << 20);+		char *g = mremap(m, 1 << 20, 4 << 20, MREMAP_MAYMOVE);+		chk("mremap grow 1 -> 4 MiB keeps the data",+		    g != MAP_FAILED && g[0] == 0x5a && g[(1 << 20) - 1] == 0x5a && g[(4 << 20) - 1] == 0);+		munmap(g != MAP_FAILED ? g : m, g != MAP_FAILED ? 4 << 20 : 1 << 20);+	}+	char *big = malloc(8 << 20), *small = malloc(100);+	chk("malloc 8 MiB + 100 bytes", big && small);+	if (big)+		big[(8 << 20) - 1] = 1;+	free(big);+	free(small);+	char out[64];+	snprintf(out, sizeof out, "%.3f %s %d", strtod("2.5e1", NULL) / 8, "x", -42);+	chk("printf %f / strtod", strcmp(out, "3.125 x -42") == 0);+}++/* ---- clocks and timerfd (Rustbelt 0.20.0) -------------------------------- */+static int arm(int fd, int flags, long ms, long every_ms)+{+	struct itimerspec its = { { every_ms / 1000, every_ms % 1000 * 1000000L },+				  { ms / 1000, ms % 1000 * 1000000L } };+	return timerfd_settime(fd, flags, &its, NULL);+}++static void clocks_and_timerfd(void)+{+	printf("fprobe: -- clocks and timerfd\n");+	struct timespec a, b, rt;+	int back = 0;+	clock_gettime(CLOCK_MONOTONIC, &a);+	for (int i = 0; i < 100000; i++) {+		clock_gettime(CLOCK_MONOTONIC, &b);+		back += b.tv_sec < a.tv_sec || (b.tv_sec == a.tv_sec && b.tv_nsec < a.tv_nsec);+		a = b;+	}+	chk("100000 MONOTONIC reads never go backwards", back == 0);+	clock_gettime(CLOCK_REALTIME, &rt);+	chk("REALTIME is 2026 or later", rt.tv_sec > 1767225600L);+	long long t0 = now_ms();+	ms_sleep(200);+	long long slept = now_ms() - t0;+	chk("nanosleep 200 ms takes 200-1000 ms", slept >= 195 && slept < 1000);++	/* One-shot timer, blocking read: waits ~150 ms, then the count is 1. */+	int fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);+	chk("timerfd_create(MONOTONIC)", fd >= 0);+	uint64_t n = 0;+	t0 = now_ms();+	arm(fd, 0, 150, 0);+	int r = read(fd, &n, sizeof n);+	long long took = now_ms() - t0;+	chk("timerfd one-shot: read blocks ~150 ms, count 1", r == 8 && n == 1 && took >= 140 && took < 1000);+	struct itimerspec cur;+	chk("timerfd disarmed after firing",+	    timerfd_gettime(fd, &cur) == 0 && cur.it_value.tv_sec == 0 && cur.it_value.tv_nsec == 0);+	close(fd);++	/* Periodic timer: expirations add up while nobody reads. */+	fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);+	errno = 0;+	chk("timerfd nonblocking + disarmed: EAGAIN", read(fd, &n, sizeof n) == -1 && errno == EAGAIN);+	arm(fd, 0, 60, 60);+	ms_sleep(700);+	n = 0;+	chk("timerfd 60 ms interval: 5-14 expirations in 700 ms",+	    read(fd, &n, sizeof n) == 8 && n >= 5 && n <= 14);+	printf("  (%llu expirations)\n", (unsigned long long)n);++	/* epoll wakes up when the timer fires. */+	int ep = epoll_create1(EPOLL_CLOEXEC);+	struct epoll_event ev = { .events = EPOLLIN, .data.u64 = 42 }, out;+	arm(fd, 0, 100, 0);+	read(fd, &n, sizeof n); /* drop anything left from the interval timer */+	epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev);+	t0 = now_ms();+	r = epoll_wait(ep, &out, 1, 3000);+	took = now_ms() - t0;+	chk("timerfd wakes epoll_wait", r == 1 && out.data.u64 == 42 && took < 1500);+	close(ep);++	/* Rustbelt doesn't do TFD_TIMER_CANCEL_ON_SET: it must say EINVAL. */+	struct itimerspec abs = { { 0, 0 }, { rt.tv_sec + 60, 0 } };+	errno = 0;+	r = timerfd_settime(fd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &abs, NULL);+	chk(on_rustbelt ? "TFD_TIMER_CANCEL_ON_SET -> EINVAL (Rustbelt)" : "TFD_TIMER_CANCEL_ON_SET (Linux: works)",+	    on_rustbelt ? r == -1 && errno == EINVAL : r == 0);+	close(fd);+}++int main(void)+{+	struct utsname u;+	setvbuf(stdout, NULL, _IOLBF, 0); /* lines appear in order with the kernel's */+	on_rustbelt = uname(&u) == 0 && strcmp(u.sysname, "Rustbelt") == 0;+	printf("fprobe: Foundry OS libc self-check on %s %s\n", u.sysname, u.release);+	elf_and_auxv();+	files();+	processes();+	threads_and_memory();+	clocks_and_timerfd();+	if (fails)+		printf("fprobe: %d of %d checks FAILED\n", fails, checks);+	else+		printf("fprobe: all %d checks passed\n", checks);+	return fails;+}
@@ -1,14 +1,24 @@-/* The first Foundry OS C program (milestone M2): plain C against musl.- * Build:  toolchain/foundry-cc -O2 c/hello-musl.c -o out/hello-musl  (after `make musl`)- * (Rustbelt 0.19.0 loads static-PIE programs, so no special link flags.) */+/* hello-musl: the first Foundry OS C program (milestone M2), plain C on musl.+ *+ * Built by `make` with toolchain/foundry-cc, i.e. gcc + our musl as a+ * static-PIE (see docs/porting.md), and installed as /bin/hello-musl.+ * On Rustbelt it prints "hello from musl on Rustbelt 0.20.0 (...)"; the+ * same binary also runs on a Linux PC ("hello from musl on Linux 6.x").+ */+#include <elf.h> #include <stdio.h> #include <sys/utsname.h> -int main(void) {-    struct utsname u;-    if (uname(&u) == 0)-        printf("hello from musl on Foundry OS (%s %s)\n", u.sysname, u.release);-    else-        printf("hello from musl on Foundry OS\n");-    return 0;+extern const char __ehdr_start[]; /* our own ELF header, wherever we were loaded */++int main(void)+{+	const Elf64_Ehdr *me = (const void *)__ehdr_start;+	const char *kind = me->e_type == ET_DYN ? "static-PIE" : "ET_EXEC";+	struct utsname u;+	if (uname(&u) == 0)+		printf("hello from musl on %s %s (Foundry OS, %s)\n", u.sysname, u.release, kind);+	else+		printf("hello from musl (Foundry OS, %s)\n", kind);+	return 0; }