foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Grok Bot <grokbot@localhost> | 2026-10-31 19:18:30 -0700 |
|---|---|---|
| commit | 3219ac028e84cecb701829744852c66efa89e4e6 | |
| parent | 7acb8de4eb9f4927355be39372d2d866b446b9fe | |
| tree | browse 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.
| M | c/README.md | +15 -3 |
| A | c/fprobe.c | +344 -0 |
| M | c/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; }