Pure-Elixir codec for the Linux FUSE kernel protocol, targeting FUSE_KERNEL_VERSION 7.31 (the version exposed by libfuse 3.10+ and the Linux 5.4+ kernel).
The codec operates on binaries only — no I/O, no dependency on the
/dev/fuse NIF. A typical read path looks like:
{:ok, request_bytes} = Wick.Native.read_frame(fd)
case Wick.Protocol.decode_request(request_bytes) do
{:ok, :lookup, header, %Wick.Protocol.LookupRequest{name: name}} ->
reply = %Wick.Protocol.EntryReply{nodeid: ..., attr: ...}
bytes = Wick.Protocol.encode_response(header.unique, reply, 0)
:ok = Wick.Native.write_frame(fd, bytes)
# ...
endWire rules
- All multi-byte integers are little-endian. (The kernel UAPI is documented as native-endian, but every Linux platform Wick targets is little-endian.)
- Every struct is padded to an 8-byte boundary. All
padding/unused*/dummyfields are wire-significant — encode them as zero; ignore them on decode. fuse_in_header.len/fuse_out_header.lenis the total message length, header inclusive.- Error replies carry
fuse_out_headeronly (no body).erroris0for success or a negative POSIX errno. - Filenames in request bodies are NUL-terminated.
Supported opcodes
See opcode_to_atom/1 and atom_to_opcode/1 for the supported
opcode set (the ~20 opcodes required for the read-path + write-path +
metadata operations — INIT, LOOKUP, GETATTR, SETATTR, READDIR, READ,
WRITE, OPEN, RELEASE, CREATE, MKDIR, UNLINK, RMDIR, RENAME, STATFS,
FLUSH, FSYNC, FORGET, BATCH_FORGET, DESTROY).
Extended opcodes for xattrs (SETXATTR / GETXATTR / LISTXATTR /
REMOVEXATTR) are supported as of #671. The lock opcodes
(GETLK / SETLK / SETLKW) are decoded as of #672 — handlers route
them by lk_flags & FUSE_LK_FLOCK to either FLOCK whole-file
logic (#672, #677) or byte-range fcntl (#674, #681). INTERRUPT
(cancellation of a queued SETLKW) is supported as of #675. The
remaining extended opcodes (IOCTL, POLL) are out of scope.
Summary
Types
Wire opcode atom — see opcode_to_atom/1.
Decoded request tagged with its opcode atom.
Response struct to be encoded into an outgoing frame.
Functions
Translate an opcode atom back into its numeric code.
Decode a complete request frame (header + body) read from
/dev/fuse.
Shorthand for an error-only response. Equivalent to
encode_response(unique, nil, errno).
Encode a response frame. unique is the fuse_in_header.unique
value from the matching request. error is 0 on success or a
negative POSIX errno.
Translate a numeric opcode into its atom. Returns :unknown for
opcodes outside this codec's scope (xattrs, locks, ioctl, etc.).
Types
@type opcode() ::
:lookup
| :forget
| :getattr
| :setattr
| :mkdir
| :unlink
| :rmdir
| :rename
| :open
| :read
| :write
| :statfs
| :release
| :fsync
| :setxattr
| :getxattr
| :listxattr
| :removexattr
| :getlk
| :setlk
| :setlkw
| :interrupt
| :flush
| :init
| :opendir
| :readdir
| :releasedir
| :create
| :destroy
| :batch_forget
| :readdirplus
| :rename2
Wire opcode atom — see opcode_to_atom/1.
@type request() :: Wick.Protocol.Request.t()
Decoded request tagged with its opcode atom.
@type response() :: Wick.Protocol.Response.t()
Response struct to be encoded into an outgoing frame.
Functions
@spec atom_to_opcode(opcode()) :: non_neg_integer()
Translate an opcode atom back into its numeric code.
@spec decode_request(binary()) :: {:ok, opcode(), Wick.Protocol.InHeader.t(), request()} | {:error, term()}
Decode a complete request frame (header + body) read from
/dev/fuse.
The kernel guarantees one complete request per read(2) so the
caller should pass exactly one frame in. Returns:
{:ok, opcode, header, request}—opcodeis the atom,headeris aWick.Protocol.InHeader,requestis the opcode-specific struct fromWick.Protocol.Request.{:error, reason}wherereasonis one of::short_header— fewer than 40 bytes delivered.{:length_mismatch, declared, actual}—in_header.lendisagrees with the delivered frame size.{:unknown_opcode, n}— opcode not supported by this codec.:malformed_body— body doesn't match the opcode's layout.
@spec encode_error(non_neg_integer(), integer()) :: binary()
Shorthand for an error-only response. Equivalent to
encode_response(unique, nil, errno).
@spec encode_response(non_neg_integer(), response() | nil, integer()) :: iodata()
Encode a response frame. unique is the fuse_in_header.unique
value from the matching request. error is 0 on success or a
negative POSIX errno.
When error != 0 the response body is omitted — the kernel expects
a header-only frame for error replies. In that case reply can be
nil and is ignored.
@spec opcode_to_atom(non_neg_integer()) :: opcode() | :unknown
Translate a numeric opcode into its atom. Returns :unknown for
opcodes outside this codec's scope (xattrs, locks, ioctl, etc.).