CUBELinux.0.6: cube(2) — the coordinate interface
The write path was proven but unreachable: its operations lived behind a device node. This is the interface the decision chose (DESIGN-cube-interface.md) — one syscall number, an opcode, and a versioned argument block, with operations that are the verbs the command language already defines: put, get, del, sync. long cube(unsigned int op, struct cube_args __user *args) `size` comes first and is checked, because syscall numbers are permanent and an interface that cannot grow would have to be replaced. A coordinate is the space and its three axes; nothing here resolves a name and nothing enumerates. Split deliberately: the entry point, the user copies and the argument validation are in C (cube_syscall.c) because `SYSCALL_DEFINE*` is a C macro this kernel has no Rust equivalent for; everything that touches the store's bytes is in Rust, which passes the coordinate to the format code as its parts so that Morton encoding stays in the one module that must get it exactly right. A read that does not fit returns the size it needs rather than truncating — a short read would be worse than an error. Number 548: the x86_64 table says numbers 548 and above are available for non-x32 use. Gate (kernel/verify-syscall.sh): a static client in the initramfs does four writes (including an empty value and a second space), reads one back *through the same interface*, and folds with `sync`. Three different failures are separated — the calls failing (a broken ABI), a read not returning what a write stored (a wrong key encoding or index), and the folded image differing (a wrong format, order or merge). put 7,0,0 ok (21 bytes) put 8,0,0 ok (0 bytes) put 9,0,0 ok (28 bytes) put 1,2,3 ok (13 bytes) get 7,0,0 21 bytes: the kernel wrote this sync ok and the image left on the device is byte-identical to the one userspace writes from the same mutations, with the log empty. The interface's store is the same store. All nine gates pass on 0.6.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* CUBELinux: the `cube(2)` syscall — the kernel's coordinate interface.
|
||||
*
|
||||
* The store's operations live in Rust (drivers/cube/cubelinux_store.rs) and are already
|
||||
* proven: they append to a write-ahead log durably, replay it, fold it into the image, and
|
||||
* survive a torn tail. What was missing was a way for a program to *call* them. This file is
|
||||
* that way and nothing else.
|
||||
*
|
||||
* Why C for the entry point: syscalls are defined by `SYSCALL_DEFINE*`, which is a C macro
|
||||
* that registers the function in the syscall table with the right calling convention. Rust in
|
||||
* this kernel cannot define one, so the entry point, the user copies and the argument
|
||||
* validation are here, and the Rust side sees only kernel memory and validated numbers. That
|
||||
* split is deliberate: everything that touches a userspace pointer is in one place, and
|
||||
* everything that touches the store's bytes is in the other.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/cube.h>
|
||||
|
||||
/*
|
||||
* Implemented in Rust. The coordinate is passed as its parts rather than as a struct, so the
|
||||
* format knowledge on the Rust side stays in the module that owns it — including the Morton
|
||||
* encoding, which must produce exactly the key a userspace reader decodes.
|
||||
*/
|
||||
int cubelinux_kernel_put(const __u8 *space, __u64 x, __u64 y, __u64 z,
|
||||
const void *value, size_t len);
|
||||
ssize_t cubelinux_kernel_get(const __u8 *space, __u64 x, __u64 y, __u64 z,
|
||||
void *buf, size_t len);
|
||||
int cubelinux_kernel_del(const __u8 *space, __u64 x, __u64 y, __u64 z);
|
||||
int cubelinux_kernel_sync(void);
|
||||
|
||||
/* No value may be larger than this in one call. A coordinate store is not a bulk-file path;
|
||||
* a caller with more than this to store has more than one record to store. */
|
||||
#define CUBE_MAX_VALUE (16u * 1024u * 1024u)
|
||||
|
||||
SYSCALL_DEFINE2(cube, unsigned int, op, void __user *, uargs)
|
||||
{
|
||||
struct cube_args args;
|
||||
void *buf = NULL;
|
||||
long ret = 0;
|
||||
|
||||
if (copy_from_user(&args, uargs, sizeof(args)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* The size is the caller's, and it must be the one this kernel implements: a caller
|
||||
* built against a later block would otherwise have fields silently ignored.
|
||||
*/
|
||||
if (args.size != sizeof(struct cube_args))
|
||||
return -EINVAL;
|
||||
|
||||
switch (op) {
|
||||
case CUBE_OP_PUT:
|
||||
case CUBE_OP_GET:
|
||||
break;
|
||||
case CUBE_OP_DEL:
|
||||
case CUBE_OP_SYNC:
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (op == CUBE_OP_PUT || op == CUBE_OP_GET) {
|
||||
if (args.len > CUBE_MAX_VALUE)
|
||||
return -E2BIG;
|
||||
if (args.len > 0) {
|
||||
buf = kvmalloc(args.len, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case CUBE_OP_PUT:
|
||||
if (args.len > 0 &&
|
||||
copy_from_user(buf, (void __user *)args.value, args.len)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
ret = cubelinux_kernel_put(args.coord.space, args.coord.x,
|
||||
args.coord.y, args.coord.z, buf, args.len);
|
||||
break;
|
||||
|
||||
case CUBE_OP_GET: {
|
||||
ssize_t got;
|
||||
|
||||
got = cubelinux_kernel_get(args.coord.space, args.coord.x,
|
||||
args.coord.y, args.coord.z, buf, args.len);
|
||||
if (got < 0) {
|
||||
ret = got;
|
||||
break;
|
||||
}
|
||||
if ((u64)got > args.len) {
|
||||
/*
|
||||
* Too small. Tell the caller how much it needs, so a read is two
|
||||
* calls at worst and never a guess.
|
||||
*/
|
||||
args.len = (u64)got;
|
||||
if (copy_to_user(uargs, &args, sizeof(args)))
|
||||
ret = -EFAULT;
|
||||
else
|
||||
ret = -ERANGE;
|
||||
break;
|
||||
}
|
||||
if (got > 0 && copy_to_user((void __user *)args.value, buf, got)) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
args.len = (u64)got;
|
||||
if (copy_to_user(uargs, &args, sizeof(args)))
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
case CUBE_OP_DEL:
|
||||
ret = cubelinux_kernel_del(args.coord.space, args.coord.x,
|
||||
args.coord.y, args.coord.z);
|
||||
break;
|
||||
|
||||
case CUBE_OP_SYNC:
|
||||
ret = cubelinux_kernel_sync();
|
||||
break;
|
||||
}
|
||||
|
||||
kvfree(buf);
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user