SchOS/cmd/memfdtest/main.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:05:12 +02:00

248 lines
8.3 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// memfdtest -- two processes map the same memfd, one writes, the other sees it.
//
// That sentence is docs/plan/foundations.md's own acceptance criterion for
// item 2, and it is the check a permissive implementation cannot pass: an
// mmap that quietly returns private zeroed pages satisfies every return code
// here and fails the one assertion that matters, because the reader is a
// *different process* and the pages would not be the same pages.
//
// The lifecycle is the one a Wayland client performs, in order:
//
// fd = memfd_create("buf", 0);
// ftruncate(fd, size);
// p = mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
//
// and the reason it had to wait for SCM_RIGHTS is that the *other* process
// gets the same object by being handed the descriptor, not by naming it.
#include "core/types.h"
#include "uapi/user.h"
#include "uapi/fcntl.h"
#include "uapi/stat.h"
#include "uapi/posix.h"
#include "net/socket.h"
static int failures;
static void
puts_(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void *)s, n);
}
// mmap returns -errno in the pointer on this system's own ABI -- musl turns
// that into MAP_FAILED, but a first-party binary sees the raw value. Anything
// in the top page is an error, not an address.
static int
mmap_failed(void *p)
{
return (uint64)p >= (uint64)-4096;
}
static void
check(const char *what, int ok)
{
puts_(ok ? "[ok] " : "[FAIL] ");
puts_(what);
puts_("\n");
if (!ok) failures++;
}
#define SIZE 8192
static void memset_(void *d, int c, int n) { char *p = d; while (n-- > 0) *p++ = (char)c; }
static void memmove_(void *d, const void *s, int n)
{ char *a = d; const char *b = s; while (n-- > 0) *a++ = *b++; }
// Send one descriptor over a socket, the way a Wayland client hands its buffer
// to the compositor. Same shape as cmd/socktest's helper; kept local so this
// test stands on its own.
static int
sendfd(int sock, int fd)
{
struct msghdr m;
struct iovec iv;
char cbuf[CMSG_SPACE(sizeof(int))];
struct cmsghdr *c;
char tag = 'x';
memset_(&m, 0, sizeof(m));
memset_(cbuf, 0, sizeof(cbuf));
iv.iov_base = &tag; iv.iov_len = 1;
m.msg_iov = &iv; m.msg_iovlen = 1;
m.msg_control = cbuf; m.msg_controllen = sizeof(cbuf);
c = (struct cmsghdr *)cbuf;
c->cmsg_len = CMSG_LEN(sizeof(int));
c->cmsg_level = SOL_SOCKET;
c->cmsg_type = SCM_RIGHTS;
memmove_(CMSG_DATA(c), &fd, sizeof(int));
return sendmsg(sock, &m, 0);
}
static int
recvfd(int sock)
{
struct msghdr m;
struct iovec iv;
char cbuf[CMSG_SPACE(sizeof(int))];
struct cmsghdr *c;
char tag = 0;
int got = -1;
memset_(&m, 0, sizeof(m));
memset_(cbuf, 0, sizeof(cbuf));
iv.iov_base = &tag; iv.iov_len = 1;
m.msg_iov = &iv; m.msg_iovlen = 1;
m.msg_control = cbuf; m.msg_controllen = CMSG_LEN(sizeof(int));
if (recvmsg(sock, &m, 0) < 0)
return -1;
c = CMSG_FIRSTHDR(&m);
if (c && c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS)
memmove_(&got, CMSG_DATA(c), sizeof(int));
return got;
}
int
main(void)
{
int fd, pfd[2], st = 0;
char *p;
struct stat sb;
puts_("memfdtest: a shareable memory object\n\n");
fd = memfd_create("test-buffer", 0);
check("memfd_create returns a descriptor", fd >= 0);
if (fd < 0) { puts_("\nmemfdtest: FAILED\n"); return 1; }
// A fresh memfd is empty. Mapping it before it has a size must fail --
// there are no pages yet, and handing back anonymous ones would be the
// lie this whole object exists to avoid.
check("a new memfd has size 0",
fstat(fd, &sb) == 0 && sb.size == 0);
check("mapping a memfd that has no size yet is refused",
mmap_failed(mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)));
check("ftruncate gives it a size", ftruncate(fd, SIZE) == 0);
check("and fstat reports it back",
fstat(fd, &sb) == 0 && sb.size == SIZE);
p = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
check("mmap(MAP_SHARED) returns a mapping", !mmap_failed(p));
if (mmap_failed(p)) { puts_("\nmemfdtest: FAILED\n"); return 1; }
// Readable and writable by this process at all, before anything about
// sharing is claimed.
p[0] = 'A';
p[SIZE - 1] = 'Z';
check("the mapping is writable and reads back",
p[0] == 'A' && p[SIZE - 1] == 'Z');
// MAP_PRIVATE is refused rather than silently copied.
check("MAP_PRIVATE on a memfd is refused",
mmap_failed(mmap(0, SIZE, PROT_READ, MAP_PRIVATE, fd, 0)));
// --- the assertion this test exists for ------------------------------
//
// fork(), then have the child map the *same* descriptor independently and
// write through its own mapping. The parent must see it through its own.
// A private mapping passes everything above and fails here.
if (pipe(pfd) < 0) { puts_("[FAIL] pipe\n"); return 1; }
int pid = fork();
if (pid == 0) {
char *q = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
char ok = mmap_failed(q) ? 0 : 1;
if (ok) {
// Prove the child sees what the parent wrote before the fork...
if (q[0] != 'A' || q[SIZE - 1] != 'Z')
ok = 0;
// ...then write something of its own, in a different place.
q[100] = 'C';
q[SIZE / 2] = 'M';
}
write(pfd[1], &ok, 1);
close(pfd[1]);
exit();
}
close(pfd[1]);
{
char ok = 0;
read(pfd[0], &ok, 1);
close(pfd[0]);
check("a second process maps the same fd and sees the parent's bytes",
ok == 1);
}
wait();
(void)st;
check("and the parent sees what the child wrote",
p[100] == 'C' && p[SIZE / 2] == 'M');
check("without disturbing anything else in the mapping",
p[0] == 'A' && p[SIZE - 1] == 'Z' && p[101] == 0);
// --- read()/write() reach the same bytes -------------------------------
{
char buf[4] = { 0, 0, 0, 0 };
lseek(fd, 100, 0 /* SEEK_SET */);
check("read() sees what mmap wrote",
read(fd, buf, 1) == 1 && buf[0] == 'C');
lseek(fd, 200, 0 /* SEEK_SET */);
buf[0] = 'W';
check("write() is visible through the mapping",
write(fd, buf, 1) == 1 && p[200] == 'W');
}
// --- shrinking while mapped is refused, not faked ----------------------
check("shrinking a mapped memfd is refused", ftruncate(fd, 4096) < 0);
check("growing it is allowed", ftruncate(fd, SIZE * 2) == 0);
// --- the combination this was all for ---------------------------------
//
// A forked child inherits the descriptor, which proves the *pages* are
// shared but not that the *object* travels. The Wayland case is an
// unrelated process that is handed the descriptor over a socket and has
// no other way to name it. So: send the memfd through a socketpair, and
// have the receiver close every inherited copy before it maps.
{
int sv[2], cpid, rp[2];
char ok = 0;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0 || pipe(rp) < 0) {
check("socketpair for the descriptor-passing check", 0);
} else if ((cpid = fork()) == 0) {
int got;
char *q;
close(sv[0]); close(rp[0]);
got = recvfd(sv[1]);
// Close the inherited copy, so the only route to the object is
// the descriptor that arrived over the socket.
close(fd);
q = (got >= 0) ? mmap(0, SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, got, 0) : (char *)-1;
ok = (got >= 0 && !mmap_failed(q) && q[100] == 'C') ? 1 : 0;
if (ok) q[300] = 'S';
write(rp[1], &ok, 1);
close(rp[1]);
exit();
} else {
close(sv[1]); close(rp[1]);
sendfd(sv[0], fd);
read(rp[0], &ok, 1);
close(rp[0]); close(sv[0]);
wait();
check("a memfd passed over a socket maps in the receiver", ok == 1);
check("and the sender sees what the receiver wrote", p[300] == 'S');
}
}
close(fd);
puts_("\n");
puts_(failures ? "memfdtest: FAILED\n" : "memfdtest: all checks passed\n");
return failures ? 1 : 0;
}