Build FUSE userspace filesystems on the BEAM.

Wick has two layers and deliberately stops there — it does not impose a filesystem abstraction. You drive the protocol loop yourself; the Writing a filesystem guide walks through a complete read-only example.

Transport

  • Wick.Native — opens /dev/fuse, arms enif_select readiness notifications, and does bounded read_frame/1 / write_frame/2 of protocol frames.
  • Wick.Fusermount — mounts and unmounts via the setuid fusermount3 helper.

Codec

  • Wick.Protocol — a pure-Elixir codec for the Linux FUSE kernel protocol (FUSE_KERNEL_VERSION 7.31). decode_request/1 turns a kernel frame into an opcode, a Wick.Protocol.InHeader, and a request struct; encode_response/3 builds the reply frame.

The request/response loop

A FUSE server is an event loop over a single mounted fd:

  1. Wick.Fusermount.mount/2 returns a handle.
  2. Wick.Native.select_read/1 arms one read-readiness notification; the owning process then receives {:select, handle, :undefined, :ready_input} when a request is waiting.
  3. Wick.Native.read_frame/1 reads one request frame.
  4. Wick.Protocol.decode_request/1 decodes it.
  5. You build a reply and write it with Wick.Protocol.encode_response/3 and Wick.Native.write_frame/2 (or Wick.Protocol.encode_error/2 for an errno).
  6. Re-arm with select_read/1 and repeat — the notification is one-shot.

The INIT handshake

The kernel's first request after a mount is :init, and nothing else works until you answer it with a Wick.Protocol.Response.Init carrying a compatible version (clamp the minor to 31) and your negotiated max_write. See the guide for the full handshake.

Linux only

The transport binds the Linux FUSE ABI, so Wick.Native and Wick.Fusermount only run on Linux. Wick.Protocol is pure Elixir and runs anywhere.