Development guide
Toolchain
- Rust 1.85+ (MSRV), edition 2024 —
rust-toolchain.tomlpinsstable+rustfmt+clippy. - Single crate, single dependency (
libc). Theloader/sub-crate is built bybuild.rswith a directrustc --edition=2024invocation (it isno_std/freestanding, not a Cargo workspace member). - Syscall-number tables are generated by
build.rsfromdata/sysnums-*.txtintoOUT_DIR/sysnums.rs.
$ cargo build # debug
$ cargo build --release # release → target/release/proot
$ cargo test # unit tests
Quality gates (CI-enforced)
$ cargo fmt --all -- --check
$ cargo clippy --all-targets --locked -- -D warnings
$ cargo test --locked
$ cargo deny check
$ cargo build --release --locked
$ cargo doc --no-deps --document-private-items
rustfmt.toml: edition 2024, 100 columns, Unix newlines.
Cargo.toml [lints]: unsafe_op_in_unsafe_fn = "deny" +
clippy::all warn (CI: -D warnings). deny.toml: license
allow-list, advisory DB, banned sources.
The parity rule
Every src/ file is a named port of a C unit (comments say so:
// port of tracee/event.c). When changing behavior:
- Find the C counterpart — the reference checkout is the spec.
- Keep C-identical errno semantics: innermost
-errnoreturns,sys::clear_errno()before calls judged byerrno == 0,process_vm_*fast-path success must clear a potentially stale errno (the C code does this inside the mem primitives — a subtle rule that already bit us once). - Keep C’s verdicts for weird cases — they are almost always
load-bearing (e.g.
deliver_sigtrap,INHERIT_PARENT → shared,reconf.tracee == NULLrunner resolution).
The unsafe policy
Where unsafe may live, in descending preference:
src/sys.rs— the only home for libc calls. New kernel boundary? Add a wrapper,// SAFETY:note, keep C status/errno return.- Documented islands —
execve/elf.rsunion accessors,tracee/event.rskernel-struct reads,syscall/netlink.rsCStr. loader/— freestanding, inherently unsafe.
Hard rules: no static mut (atomics/thread_local!), no
transmute (sys::as_bytes, offset_of!), no raw pointers in safe
signatures (use usize words), // SAFETY: on every block, and
unsafe_op_in_unsafe_fn is denied crate-wide.
Project layout
src/
cli/mod.rs option parsing, launch, run loop
tracee/ registry, event loop, regs, mem, seccomp, ABI
ptrace/ ptrace + wait *emulation* (guest ptrace API)
syscall/ enter/exit pipeline, sockets, netlink, seccomp
path/ canon, bindings, glue, proc_emul, temp, f2fs
execve/ ELF, ldso, shebang, auxv, aoxp, enter/exit
extension/ Event/AnyExtension + 8 extensions (sysvipc sub-mod)
fpath.rs FixedPath — PATH_MAX buffer with C semantics
sys.rs all libc wrappers (the unsafe home)
sysnum.rs per-ABI syscall tables (generated)
arch.rs note.rs util.rs
loader/ no_std execve-completion stub
data/ sysnums-*.txt tables
tests/ (empty — unit tests are #[cfg(test)] in-file;
integration = upstream C suite)
docs/ this book
doc/proot.1 man page
Adding a syscall
- Find its sysnum entry in
data/sysnums-<abi>.txt(regen happens at build time). - Implement
translate_xxx_enter/_exitinsyscall/enter.rsorexit.rs(or a focused module likesocket.rs). - Route args via
tracee::mem(peek/pokeprimitives); translate paths viapath::canon. - Add an integration test upstream and/or a
#[cfg(test)]unit test for pure logic.
Adding an extension
See Extension framework → Writing an extension.
Debugging
-v 9= full syscall trace on stderr;PROOT_VERBOSEequivalent.PROOT_NO_SECCOMP=1— rule out the accelerator when a stop is misbehaving.strace -f -e ptrace ./proot …— watch the tracer’s own ptrace.RUST_BACKTRACE=1— panics (should not happen — report them).
Commit & PR conventions
- Small focused commits, imperative subject; why in the body.
- CI must be green: fmt, clippy, tests, deny, docs.
- Behavioral changes need the upstream suite green (see Testing).