wasi_file_nif (wasm v0.1.0)
View SourceOptional native file access that closes the WASI path race.
Changing the C means running it under a sanitizer; the sanitizer section of
docs/wasi.md says how, and why AddressSanitizer alone is not enough for it.
You never call this; wasi_fs picks it when it built. Call wasi_fs:backend/0
to find out whether you have it. wasi_path resolves a path and then opens it,
and a component can be swapped for a symlink in between. filelib:safe_relative_path/2 closes every lexical
and static symlink escape, but Erlang exposes neither openat nor
O_NOFOLLOW, so the race itself cannot be closed from Erlang.
This module walks a path one component at a time with openat(..., O_NOFOLLOW)
relative to the previously opened directory, so each name is resolved exactly
once, by the kernel, and no symlink is followed at any depth.
Optional by construction
If the shared object is missing or fails to load, available/0 returns false
and wasi_path falls back to the pure-Erlang resolver with the race documented.
Building this project never requires a C compiler.
Scope
Six functions, all file I/O. No WebAssembly semantics cross the boundary: capability decisions, rights masking and errno mapping stay in Erlang. Every call is O(1) or bounded by an explicit length, and all of them run on dirty I/O schedulers because they block.
Summary
Functions
Whether the native backend loaded. false means the fallback is in use.
Flush this file to disk.
Set an open descriptor's times, as two 64-bit second/nanosecond pairs.
Open RelPath beneath Dir, one component at a time.
Open a preopen root by name, once.
The two-place operations, rename and link.
Act on a name inside a directory, without resolving that name twice.
Functions
-spec available() -> boolean().
Whether the native backend loaded. false means the fallback is in use.
-spec close(term()) -> ok.
Flush this file to disk.
Answers ok for a descriptor that cannot be synced, such as a pipe. A guest
that calls fd_sync defensively should not be failed for holding something
that has nothing to flush.
-spec ftruncate(term(), non_neg_integer()) -> ok | {error, integer()}.
Set an open descriptor's times, as two 64-bit second/nanosecond pairs.
futimens, because an open file has no name to walk: fd_filestat_set_times
cannot go through the path operations the way its path_* counterpart does.
Open RelPath beneath Dir, one component at a time.
Dir is either a handle from a previous call or, for a preopen, the host root
as a string. Flags are raw open(2) flags.
Follow is 1 when the final component may be a symlink that gets followed
and 0 when it may not, which is what LOOKUPFLAGS_SYMLINK_FOLLOW decides.
Components before the last are always followed: that is what a path means.
Open a preopen root by name, once.
The only place a path is resolved by name rather than component by component, and it happens once per preopen rather than once per open, which is what anchoring means.
The two-place operations, rename and link.
Each end is resolved against its own directory. Resolving only one would let a module move a file out of the sandbox by naming the destination carelessly.
Act on a name inside a directory, without resolving that name twice.
The walk stops one component short and the operation is a single *at call
against a descriptor nobody can substitute, which is what closes the window
between checking a path and acting on it. Op is a small integer because
choosing it is wasi_fs's business and no WebAssembly meaning crosses this
boundary.
-spec pread(term(), integer(), non_neg_integer()) -> {ok, binary()} | eof | {error, integer()}.
-spec pwrite(term(), integer(), binary()) -> {ok, non_neg_integer()} | {error, integer()}.