fs

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 10, 2026 License: BSD-3-Clause Imports: 2 Imported by: 0

README

go-virtio/fs

Pure-Go virtio-fs (FUSE-over-virtio) guest driver targeting the go-virtio/common transport interfaces. Implements the modern-transport (Virtio 1.0+) init sequence, the request virtqueue, and the FUSE-over-virtio wire framing for the standard PCI-bound virtio-fs device (VID 0x1AF4, DID 0x105A — device type 26).

CGO=0, no architecture-specific assembly, 100% statement test coverage.

Scope

Like go-virtio/blk this package owns device bring-up, a request virtqueue, and the on-the-wire request format. For virtio-fs the request format is a FUSE message carried as a descriptor chain (Virtio 1.2 §5.11.6):

readable descriptors : [ struct fuse_in_header  | op-specific in-args  ]
writable descriptors : [ struct fuse_out_header | op-specific out-args ]

The driver exposes a read-only mount closure, each op cited from Linux include/uapi/linux/fuse.h:

Method FUSE opcode in-args / out-args
Init() FUSE_INIT (26) fuse_init_infuse_init_out (major 7 nego.)
Lookup(parent, name)Entry FUSE_LOOKUP (1) NUL-terminated name → fuse_entry_out
GetAttr(nodeid)Attr FUSE_GETATTR (3) fuse_getattr_infuse_attr_out
Open(nodeid)fh FUSE_OPEN (14) fuse_open_infuse_open_out
Read(nodeid, fh, off, size)[]byte FUSE_READ (15) fuse_read_in → raw bytes
Release(nodeid, fh) FUSE_RELEASE (18) fuse_release_in → (no out-args)
Forget(nodeid, nlookup) FUSE_FORGET (2) fuse_forget_in → (no reply at all)
Destroy() FUSE_DESTROY (38) (no in-args) → (no out-args)

Write-side FUSE ops (CREATE/WRITE/MKDIR/…) are out of scope. The FUSE protocol major is 7; the minor is negotiated down to whatever the device offers in FUSE_INIT.

Queues

Queue 0 is the hiprio queue (FUSE_FORGET / FUSE_INTERRUPT fast-path); queues 1..num_request_queues are the request queues (Virtio 1.2 §5.11.2). This driver submits every request on the first request queue (index 1) and does not use the hiprio queue.

Device config

virtio_fs_config (Linux virtio_fs.h) is char tag[36] followed by __le32 num_request_queues; both are read at OpenVirtioFS into VirtioFS.Tag (trailing NULs trimmed) and VirtioFS.NumRequestQueues.

Quick start

import virtiofs "github.com/go-virtio/fs"

// transport is any value that implements go-virtio/common.Transport.
fs, err := virtiofs.OpenVirtioFS(transport)
if err != nil { return err }
if err := fs.Init(); err != nil { return err } // FUSE_INIT handshake

// Read /hello.txt from the shared dir (root nodeid is always 1).
e, err := fs.Lookup(1, "hello.txt")
if err != nil { return err }
fh, err := fs.Open(e.NodeID)
if err != nil { return err }
data, err := fs.Read(e.NodeID, fh, 0, uint32(e.Attr.Size))
if err != nil { return err }
_ = fs.Release(e.NodeID, fh)
fmt.Printf("%s\n", data)

Device-ID centralization

Per the org convention, virtio device IDs live in go-virtio/common. This driver consumes them from common v0.1.5, which centralizes DeviceTypeFS (26), PCIDeviceIDModernFS (0x105A) and PCIDeviceIDIsFS alongside the other DeviceType* / PCIDeviceIDModern* constants.

Tests

GOWORK=off go test -cover ./...   # 100.0% of statements

White-box tests drive a fake common.Transport (fakeFSDevice) that serves canned FUSE replies, plus an injection harness (injectTransport) that fails each transport touch-point to exercise every error branch.

References

  • Virtio 1.2 §5.11 "File System Device".
  • Linux include/uapi/linux/virtio_fs.hstruct virtio_fs_config.
  • Linux include/uapi/linux/virtio_ids.hVIRTIO_ID_FS = 26.
  • Linux include/uapi/linux/fuse.h — every FUSE struct + opcode.

Documentation

Overview

Package fs is a pure-Go virtio-fs (FUSE-over-virtio) guest driver. It drives a modern (Virtio 1.0+) PCI virtio-fs device through the transport interfaces defined in github.com/go-virtio/common; the same code drives a UEFI-backed device, a bare-metal device, or a virtio-mmio device depending on which common.Transport implementation the caller supplies.

Scope — this package owns device bring-up, the request virtqueue, the FUSE-over-virtio descriptor-chain framing (readable [fuse_in_header | in-args], writable [fuse_out_header | out-args]; Virtio 1.2 §5.11), and a read-only mount closure: Init, Lookup, GetAttr, Open, Read, Release, Forget, Destroy. Write-side FUSE ops are out of scope.

  • Modern transport (VIRTIO_F_VERSION_1 mandatory). Legacy devices are rejected by the common init sequence.
  • Queue 0 is the hiprio queue; queues 1..num_request_queues are the request queues (Virtio 1.2 §5.11.2). This driver issues all FUSE requests on the first request queue (index 1) and does not use the hiprio queue (no FUSE_INTERRUPT / FUSE_FORGET fast-path needed for a read-only mount).
  • No virtio feature bit beyond VIRTIO_F_VERSION_1 is negotiated. (VIRTIO_FS_F_NOTIFICATION exists but is not required for the request path.)

The FUSE protocol itself is versioned independently of virtio: this driver speaks FUSE major 7 and negotiates the minor down to whatever the device offers (FUSE_INIT, Linux fuse.h).

References:

  • Virtio 1.2 §5.11 "File System Device" — device-type 26 binding, virtio_fs_config (tag[36] + le32 num_request_queues), queue layout (hiprio + request queues).
  • Linux include/uapi/linux/virtio_fs.h — struct virtio_fs_config.
  • Linux include/uapi/linux/virtio_ids.h — VIRTIO_ID_FS = 26.
  • Linux include/uapi/linux/fuse.h — every FUSE wire struct + opcode cited at its use site below (FUSE_KERNEL_VERSION = 7).
  • Virtio 1.1 §3.1.1 "Device Initialization" — the status-bit choreography (shared with blk/net/console via common).

Index

Constants

View Source
const (
	FuseOpLookup  uint32 = 1
	FuseOpForget  uint32 = 2
	FuseOpGetattr uint32 = 3
	FuseOpOpen    uint32 = 14
	FuseOpRead    uint32 = 15
	FuseOpRelease uint32 = 18
	FuseOpInit    uint32 = 26
	FuseOpDestroy uint32 = 38
)

FUSE opcodes (Linux fuse.h: enum fuse_opcode). Only the read-only mount subset is defined.

View Source
const AcceptedFeatures uint64 = common.FeatureVersion1

AcceptedFeatures is the feature mask the driver negotiates ON — only the non-negotiable VIRTIO_F_VERSION_1.

View Source
const FuseKernelMinorVersion uint32 = 31

FuseKernelMinorVersion is the FUSE minor version the driver proposes in FUSE_INIT; the negotiated minor is min(proposed, device-offered) (Linux fuse.h: FUSE_KERNEL_MINOR_VERSION). We propose a conservative 31 (the baseline virtiofsd requires) and accept whatever the device reports back.

View Source
const FuseKernelVersion uint32 = 7

FuseKernelVersion is the FUSE major protocol version this driver speaks (Linux fuse.h: FUSE_KERNEL_VERSION = 7).

View Source
const HiprioQueueIdx uint16 = 0

HiprioQueueIdx is the index of the hiprio queue (queue 0) — used for FUSE_FORGET / FUSE_INTERRUPT priority requests (Virtio 1.2 §5.11.2). This read-only driver does not submit on it.

View Source
const RequestQueueIdx uint16 = 1

RequestQueueIdx is the index of the first request queue. Queue 0 is hiprio; request queues start at index 1 (Virtio 1.2 §5.11.2).

View Source
const RequestQueueSize uint16 = 16

RequestQueueSize is the desired ring size (clamped + rounded during setup). A FUSE request consumes 2–4 descriptors; the driver issues them one at a time.

View Source
const TxPollIterations = 200000

TxPollIterations is the default busy-poll budget for one request.

Variables

View Source
var (
	ErrNotModernDevice   = fsError("go-virtio/fs: device doesn't offer VIRTIO_F_VERSION_1 (legacy-only)")
	ErrFeaturesNotOK     = fsError("go-virtio/fs: FEATURES_OK status bit didn't stick after DriverFeature write")
	ErrInitWrongDeviceID = fsError("go-virtio/fs: PCI device ID is not 0x105A (modern virtio-fs device)")
	ErrQueueNotAvailable = fsError("go-virtio/fs: device reports QueueSize=0 for the request queue")
	ErrRequestTimeout    = fsError("go-virtio/fs: request poll timeout (device did not complete the request)")
	ErrFuseError         = fsError("go-virtio/fs: device returned a non-zero fuse_out_header.error")
	ErrFuseVersion       = fsError("go-virtio/fs: device FUSE major version is not 7")
	ErrShortReply        = fsError("go-virtio/fs: device wrote fewer reply bytes than the op requires")
	ErrNoEntry           = fsError("go-virtio/fs: FUSE_LOOKUP returned nodeid 0 (no such entry)")
	ErrZeroSize          = fsError("go-virtio/fs: Read size must be positive")
)

Sentinel errors for the virtio-fs path.

Functions

func AcceptFeatures

func AcceptFeatures(deviceFeatures uint64) (uint64, error)

AcceptFeatures returns the negotiated mask (requires VERSION_1).

Types

type Attr

type Attr struct {
	Ino   uint64 // inode number
	Size  uint64 // file size in bytes
	Mode  uint32 // st_mode (type + permission bits)
	Nlink uint32 // link count
}

Attr is the decoded subset of FUSE fuse_attr (Linux fuse.h) the read-only mount surface exposes. Only the fields a caller needs to stat + read a file are decoded.

func ParseAttr

func ParseAttr(b []byte) Attr

ParseAttr decodes the subset of a 92-byte FUSE fuse_attr (Linux fuse.h) the read-only surface exposes. `b` MUST be at least fuseAttrSize bytes (callers slice exactly that).

type Entry

type Entry struct {
	NodeID uint64 // nodeid to use in subsequent ops (0 = negative lookup)
	Attr   Attr
}

Entry is the decoded result of a FUSE_LOOKUP (fuse_entry_out): the node id assigned to the looked-up name plus its attributes.

type VirtioFS

type VirtioFS struct {
	// Cfg is the modern-transport handle.
	Cfg *common.ModernConfig

	// Tag is the virtio_fs_config mount tag (Virtio 1.2 §5.11.5), the
	// name the guest mounts ("-o tag"). Trailing NULs are trimmed.
	Tag string

	// NumRequestQueues is the device-advertised request-queue count
	// (virtio_fs_config.num_request_queues).
	NumRequestQueues uint32

	// NegotiatedFeatures records the virtio feature handshake result.
	NegotiatedFeatures uint64

	// FuseMajor / FuseMinor record the negotiated FUSE protocol version
	// (populated by Init).
	FuseMajor uint32
	FuseMinor uint32
	// contains filtered or unexported fields
}

VirtioFS wraps one initialised virtio-fs device.

func OpenVirtioFS

func OpenVirtioFS(t common.Transport) (*VirtioFS, error)

OpenVirtioFS drives the full bring-up of one virtio-fs device:

  1. Verify the PCI device ID is 0x105A (modern virtio-fs).
  2. InitModernConfig walks PCI caps + populates the BAR locators.
  3. Reset → ACK → DRIVER status progression.
  4. Read DeviceFeature, require VERSION_1, mask, write DriverFeature.
  5. Set FEATURES_OK, verify it stuck.
  6. Read virtio_fs_config (tag + num_request_queues).
  7. Allocate + publish the first request queue (index 1).
  8. DRIVER_OK status.

The caller next calls Init() to perform the FUSE_INIT handshake.

func (*VirtioFS) Destroy

func (f *VirtioFS) Destroy() error

Destroy performs FUSE_DESTROY (Linux fuse.h, opcode 38), tearing down the FUSE session. No in-args; the device writes a fuse_out_header with no out-args.

func (*VirtioFS) Forget

func (f *VirtioFS) Forget(nodeid uint64, nlookup uint64) error

Forget performs FUSE_FORGET (Linux fuse.h, opcode 2) on `nodeid`, telling the device to drop `nlookup` references to the node. in-args are fuse_forget_in{nlookup}. FUSE_FORGET has no reply at all (the device does not write a fuse_out_header), so this submits the request and reclaims without awaiting out-args.

func (*VirtioFS) GetAttr

func (f *VirtioFS) GetAttr(nodeid uint64) (Attr, error)

GetAttr performs FUSE_GETATTR (Linux fuse.h, opcode 3) on `nodeid`. in-args are fuse_getattr_in{getattr_flags=0, dummy=0, fh=0} (a path-based getattr, no open handle). The reply is fuse_attr_out{attr_valid, attr_valid_nsec, dummy, attr}.

func (*VirtioFS) Init

func (f *VirtioFS) Init() error

Init performs the FUSE_INIT handshake (Linux fuse.h, opcode 26). It sends fuse_init_in{major=7, minor, max_readahead, flags} on nodeid 0 and parses fuse_init_out{major, minor, ...}, recording the negotiated version. Returns ErrFuseVersion if the device reports a major other than 7 (the only major this driver speaks).

func (*VirtioFS) Lookup

func (f *VirtioFS) Lookup(parent uint64, name string) (Entry, error)

Lookup performs FUSE_LOOKUP (Linux fuse.h, opcode 1): resolve `name` within directory node `parent`. The in-args are the NUL-terminated name; the reply is fuse_entry_out{nodeid, ..., attr}. A nodeid of 0 means a negative lookup (name does not exist) — returned as ErrNoEntry.

func (*VirtioFS) Open

func (f *VirtioFS) Open(nodeid uint64) (uint64, error)

Open performs FUSE_OPEN (Linux fuse.h, opcode 14) on `nodeid`. The in-args are fuse_open_in{flags, open_flags}; the driver opens read-only (flags = O_RDONLY = 0). The reply is fuse_open_out{fh, open_flags, backing_id}; the returned fh is used in Read/Release.

func (*VirtioFS) Read

func (f *VirtioFS) Read(nodeid, fh, off uint64, size uint32) ([]byte, error)

Read performs FUSE_READ (Linux fuse.h, opcode 15) on (nodeid, fh), reading up to `size` bytes at byte offset `off`. The in-args are fuse_read_in{fh, offset, size, read_flags, lock_owner, flags, padding}; the reply is raw file bytes (no out-args struct — the device writes the data directly after fuse_out_header). The returned slice length is min(size, bytes-available) — a short read at EOF.

func (*VirtioFS) Release

func (f *VirtioFS) Release(nodeid, fh uint64) error

Release performs FUSE_RELEASE (Linux fuse.h, opcode 18) on (nodeid, fh), closing the open handle. in-args are fuse_release_in{fh, flags, release_flags, lock_owner}; there are no out-args.

func (*VirtioFS) RequestQueue

func (f *VirtioFS) RequestQueue() *common.Virtqueue

RequestQueue exposes the request virtqueue handle for diagnostics.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL