Wick.Protocol (wick v0.1.0)

Copy Markdown View Source

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)
  # ...
end

Wire 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* / dummy fields are wire-significant — encode them as zero; ignore them on decode.
  • fuse_in_header.len / fuse_out_header.len is the total message length, header inclusive.
  • Error replies carry fuse_out_header only (no body). error is 0 for 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

opcode()

@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.

request()

@type request() :: Wick.Protocol.Request.t()

Decoded request tagged with its opcode atom.

response()

@type response() :: Wick.Protocol.Response.t()

Response struct to be encoded into an outgoing frame.

Functions

atom_to_opcode(atom)

@spec atom_to_opcode(opcode()) :: non_neg_integer()

Translate an opcode atom back into its numeric code.

decode_request(frame)

@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}opcode is the atom, header is a Wick.Protocol.InHeader, request is the opcode-specific struct from Wick.Protocol.Request.
  • {:error, reason} where reason is one of:
    • :short_header — fewer than 40 bytes delivered.
    • {:length_mismatch, declared, actual}in_header.len disagrees with the delivered frame size.
    • {:unknown_opcode, n} — opcode not supported by this codec.
    • :malformed_body — body doesn't match the opcode's layout.

encode_error(unique, errno)

@spec encode_error(non_neg_integer(), integer()) :: binary()

Shorthand for an error-only response. Equivalent to encode_response(unique, nil, errno).

encode_response(unique, reply, error)

@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.

opcode_to_atom(n)

@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.).