permfs is an embeddable Linux filesystem policy driver used by Permbox.
It mounts a private kernel OverlayFS and exposes that merged tree through a
small low-level FUSE filesystem.
OverlayFS handles copy-up, whiteouts, opaque directories, sparse files, mmap, links, rename, and ordinary filesystem behavior. The FUSE layer decides whether each path is hidden, read-only, writable, or requires a synchronous decision.
Every explicit trie rule is one of:
| Rule | Meaning |
|---|---|
whiteout | The path appears not to exist |
r | Metadata and read-only access |
rw | Reads and mutations, subject to Unix permissions |
ask | Resolve to one of the other three before the operation |
Rules use longest-component-prefix inheritance. There is no separate execute rule. Owner, group, mode, POSIX ACL, sticky-directory, and execute checks are normal kernel permission checks.
The text form is:
fs {
"/":rw {
"usr":r
"home/me":rw
"home/me/.ssh":ask
"secret":whiteout
}
}
The installed CLI uses LLVM by default:
zig buildRun both required test configurations:
zig build test
zig build test -Doptimize=ReleaseSafeLinux, glibc, and libfuse 3.18 are required. Kernel OverlayFS mounting and FUSE passthrough require the corresponding kernel features and privileges. When passthrough is unavailable, file I/O uses the normal FUSE path.
conststd=@import("std");
constpermfs=@import("permfs");
fnask(
context: ?*anyopaque,
io: std.Io,
request: permfs.AskRequest,
) !permfs.Access {
_=context;
_=io;
std.log.info("policy request: {s} {t}", .{
request.path,
request.operation,
});
return.r;
}
vardriver: permfs.Driver=undefined;
trydriver.init(io, .{
.lower_path="/srv/root",
.policy_path="/run/permbox/policy.trie",
.session_path="/run/permbox/session",
.ask_fn=ask,
});
deferdriver.deinit();
trydriver.setRule("/", .rw);
trydriver.setRule("/etc", .r);
trydriver.mount(allocator, "/run/permbox/mount", .{});mount blocks until unmounted. Run it with std.Io.concurrent or another
thread when the caller needs to continue. requestUnmount is thread-safe.
The driver must remain at a stable address while mounted.
The private OverlayFS is mounted only for the duration of Driver.mount.
After it returns, changes can be applied or discarded:
constresult=trydriver.apply(.{ .max_entries=100 });
if (!result.complete()) {
// A later call resumes from the remaining upper entries.
}
trydriver.discard("tmp/generated");Apply publishes through destination-side temporary names, syncs the result and destination directory, then removes the corresponding upper entry. The upper tree is therefore the restart checkpoint.
zig-out/bin/permfs \
--lower=/absolute/lower \
--policy=/absolute/policy.trie \
--session=/absolute/session \
/absolute/mountpointInteractive commands:
show
get /path
set /path whiteout|r|rw|ask
del /path
load policy.fs
save policy.fs
unmount
apply 100
discard /path
quit
Trie conversion does not mount a filesystem:
permfs trie to-text policy.trie policy.fs
permfs trie from-text policy.fs policy.trieSee docs/API.md for the exported API and PLAN.md for the implementation design and remaining acceptance work.