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-write mount closure: Init, Lookup, GetAttr, Open/OpenRW, Read, Write, Create, Mkdir, Mknod, Symlink, Link, SetAttr, Unlink, Rmdir, Rename, Fsync, Flush, Release, Forget, Destroy. FUSE_WRITE carries its data in an extra readable descriptor (a three-region chain); every other op keeps the two-region read/write split.
- 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 here; FUSE_FORGET is submitted on the request queue).
- 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
- Variables
- func AcceptFeatures(deviceFeatures uint64) (uint64, error)
- type Attr
- type Entry
- type SetAttrIn
- type VirtioFS
- func (f *VirtioFS) Create(parent uint64, name string, mode, flags uint32) (Entry, uint64, error)
- func (f *VirtioFS) Destroy() error
- func (f *VirtioFS) Flush(nodeid, fh uint64) error
- func (f *VirtioFS) Forget(nodeid uint64, nlookup uint64) error
- func (f *VirtioFS) Fsync(nodeid, fh uint64, datasync bool) error
- func (f *VirtioFS) GetAttr(nodeid uint64) (Attr, error)
- func (f *VirtioFS) Init() error
- func (f *VirtioFS) Link(oldnodeid, newparent uint64, newname string) (Entry, error)
- func (f *VirtioFS) Lookup(parent uint64, name string) (Entry, error)
- func (f *VirtioFS) Mkdir(parent uint64, name string, mode uint32) (Entry, error)
- func (f *VirtioFS) Mknod(parent uint64, name string, mode, rdev uint32) (Entry, error)
- func (f *VirtioFS) Open(nodeid uint64) (uint64, error)
- func (f *VirtioFS) OpenRW(nodeid uint64, flags uint32) (uint64, error)
- func (f *VirtioFS) Read(nodeid, fh, off uint64, size uint32) ([]byte, error)
- func (f *VirtioFS) Release(nodeid, fh uint64) error
- func (f *VirtioFS) Rename(oldparent uint64, oldname string, newparent uint64, newname string) error
- func (f *VirtioFS) RequestQueue() *common.Virtqueue
- func (f *VirtioFS) Rmdir(parent uint64, name string) error
- func (f *VirtioFS) SetAttr(nodeid uint64, in SetAttrIn) (Attr, error)
- func (f *VirtioFS) Symlink(parent uint64, name, target string) (Entry, error)
- func (f *VirtioFS) Unlink(parent uint64, name string) error
- func (f *VirtioFS) Write(nodeid, fh, off uint64, data []byte) (int, error)
Constants ¶
const ( FuseOpLookup uint32 = 1 FuseOpForget uint32 = 2 FuseOpGetattr uint32 = 3 FuseOpSetattr uint32 = 4 FuseOpSymlink uint32 = 6 FuseOpMknod uint32 = 8 FuseOpMkdir uint32 = 9 FuseOpUnlink uint32 = 10 FuseOpRmdir uint32 = 11 FuseOpRename uint32 = 12 FuseOpLink uint32 = 13 FuseOpOpen uint32 = 14 FuseOpRead uint32 = 15 FuseOpWrite uint32 = 16 FuseOpRelease uint32 = 18 FuseOpFsync uint32 = 20 FuseOpFlush uint32 = 25 FuseOpInit uint32 = 26 FuseOpCreate uint32 = 35 FuseOpDestroy uint32 = 38 )
FUSE opcodes (Linux fuse.h: enum fuse_opcode). The read-only mount subset plus the read-write closure.
const ( FattrMode uint32 = 1 << 0 // FATTR_MODE (chmod) FattrUID uint32 = 1 << 1 // FATTR_UID (chown user) FattrGID uint32 = 1 << 2 // FATTR_GID (chown group) FattrSize uint32 = 1 << 3 // FATTR_SIZE (truncate) FattrAtime uint32 = 1 << 4 // FATTR_ATIME (utimes access) FattrMtime uint32 = 1 << 5 // FATTR_MTIME (utimes modify) FattrFh uint32 = 1 << 6 // FATTR_FH (fh field valid) FattrAtimeNow uint32 = 1 << 7 // FATTR_ATIME_NOW FattrMtimeNow uint32 = 1 << 8 // FATTR_MTIME_NOW FattrCtime uint32 = 1 << 10 // FATTR_CTIME )
FUSE_SETATTR valid-mask bits (Linux fuse.h: FATTR_*). The `valid` field of fuse_setattr_in selects which attributes the request changes.
const ( OpenReadOnly uint32 = 0 // O_RDONLY OpenWriteOnly uint32 = 1 // O_WRONLY OpenReadWrite uint32 = 2 // O_RDWR )
Open-flag values for Open/OpenRW/Create (the POSIX open(2) access modes, Linux asm-generic/fcntl.h: O_RDONLY/O_WRONLY/O_RDWR). FUSE passes them through verbatim in fuse_open_in.flags / fuse_create_in.flags.
const ( FuseBigWrites uint32 = 1 << 5 // FUSE_BIG_WRITES FuseAtomicOTrunc uint32 = 1 << 3 // FUSE_ATOMIC_O_TRUNC FuseWritebackCach uint32 = 1 << 16 // FUSE_WRITEBACK_CACHE (not negotiated) )
FUSE_INIT flag bits (Linux fuse.h: FUSE_*), the protocol-level feature handshake carried in fuse_init_in.flags / fuse_init_out.flags (independent of the virtio feature bits). The read-write closure proposes the two that affect write semantics:
- FUSE_BIG_WRITES (1<<5): the kernel/guest may send writes larger than one page. virtiofsd assumes it at FUSE 7.x; we set it so the Write op is permitted to carry multi-page data regions.
- FUSE_ATOMIC_O_TRUNC (1<<3): O_TRUNC is handled atomically by the OPEN/CREATE op itself (no separate SETATTR-size-0). Required for a correct OpenRW(...O_TRUNC)/Create truncating open.
FUSE_WRITEBACK_CACHE (1<<16) is deliberately NOT negotiated: it shifts page-cache writeback responsibility to the kernel and is a host/guest page-cache optimization this raw request driver does not implement.
const AcceptedFeatures uint64 = common.FeatureVersion1
AcceptedFeatures is the feature mask the driver negotiates ON — only the non-negotiable VIRTIO_F_VERSION_1.
const FuseInitFlags uint32 = FuseBigWrites | FuseAtomicOTrunc
FuseInitFlags is the fuse_init_in.flags mask the driver proposes (the device intersects it with its own). Only write-relevant bits are set.
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.
const FuseKernelVersion uint32 = 7
FuseKernelVersion is the FUSE major protocol version this driver speaks (Linux fuse.h: FUSE_KERNEL_VERSION = 7).
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.
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).
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.
const TxPollIterations = 200000
TxPollIterations is the default busy-poll budget for one request.
Variables ¶
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/Write size must be positive") ErrShortWrite = fsError("go-virtio/fs: device accepted fewer bytes than requested (short write)") )
Sentinel errors for the virtio-fs path.
Functions ¶
func AcceptFeatures ¶
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.
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 SetAttrIn ¶ added in v0.2.0
type SetAttrIn struct {
Valid uint32 // FATTR_* mask selecting which fields below are live
Fh uint64 // open handle (only if FattrFh set in Valid)
Size uint64 // new size (truncate; FattrSize)
Mode uint32 // new st_mode (chmod; FattrMode)
UID uint32 // new owner uid (FattrUID)
GID uint32 // new owner gid (FattrGID)
Atime uint64 // access time, seconds (FattrAtime)
Mtime uint64 // modify time, seconds (FattrMtime)
}
SetAttrIn carries the fuse_setattr_in fields the caller wants to change for SetAttr. Only fields flagged in Valid (FATTR_* bits) are applied by the device; the others are ignored.
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
// FuseFlags records the negotiated FUSE_INIT flags (the intersection
// of FuseInitFlags and the device-offered fuse_init_out.flags),
// populated by Init.
FuseFlags uint32
// contains filtered or unexported fields
}
VirtioFS wraps one initialised virtio-fs device.
func OpenVirtioFS ¶
OpenVirtioFS drives the full bring-up of one virtio-fs device:
- Verify the PCI device ID is 0x105A (modern virtio-fs).
- InitModernConfig walks PCI caps + populates the BAR locators.
- Reset → ACK → DRIVER status progression.
- Read DeviceFeature, require VERSION_1, mask, write DriverFeature.
- Set FEATURES_OK, verify it stuck.
- Read virtio_fs_config (tag + num_request_queues).
- Allocate + publish the first request queue (index 1).
- DRIVER_OK status.
The caller next calls Init() to perform the FUSE_INIT handshake.
func (*VirtioFS) Create ¶ added in v0.2.0
Create performs FUSE_CREATE (Linux fuse.h, opcode 35) in directory `parent`: an atomic create+open of `name` with `mode` (st_mode incl. type/perm bits) and access `flags` (OpenWriteOnly/OpenReadWrite, OR-ed with create-time flags such as O_EXCL). The in-args are fuse_create_in{flags, mode, umask, open_flags} followed by the NUL-terminated name; the reply is fuse_entry_out immediately followed by fuse_open_out. Returns the new Entry and the open file handle.
func (*VirtioFS) Destroy ¶
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) Flush ¶ added in v0.2.0
Flush performs FUSE_FLUSH (Linux fuse.h, opcode 25) on (nodeid, fh), issued on each close(2) of a duplicated handle (one per open). The in-args are fuse_flush_in{fh, unused, padding, lock_owner}; error-only reply.
func (*VirtioFS) Forget ¶
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) Fsync ¶ added in v0.2.0
Fsync performs FUSE_FSYNC (Linux fuse.h, opcode 20) on (nodeid, fh), flushing the file's data (and, unless datasync, metadata) to stable storage. `datasync` true sets fsync_flags bit 0 (FUSE_FSYNC_FDATASYNC). The in-args are fuse_fsync_in{fh, fsync_flags, padding}; error-only reply.
func (*VirtioFS) GetAttr ¶
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 ¶
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) Link ¶ added in v0.2.0
Link performs FUSE_LINK (Linux fuse.h, opcode 13): create a hard link `newname` in directory `newparent` pointing at the existing node `oldnodeid`. The in-args are fuse_link_in{oldnodeid} followed by the NUL-terminated new name; the reply is fuse_entry_out.
func (*VirtioFS) Lookup ¶
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) Mkdir ¶ added in v0.2.0
Mkdir performs FUSE_MKDIR (Linux fuse.h, opcode 9) in directory `parent`, creating subdirectory `name` with permission bits `mode`. The in-args are fuse_mkdir_in{mode, umask} followed by the NUL-terminated name; the reply is fuse_entry_out for the new directory.
func (*VirtioFS) Mknod ¶ added in v0.2.0
Mknod performs FUSE_MKNOD (Linux fuse.h, opcode 8) in directory `parent`, creating a non-directory node `name` with st_mode `mode` (type bits select regular file / fifo / socket / device) and device number `rdev` (0 for non-device nodes). The in-args are fuse_mknod_in{mode, rdev, umask, padding} followed by the NUL-terminated name; the reply is fuse_entry_out.
func (*VirtioFS) Open ¶
Open performs FUSE_OPEN (Linux fuse.h, opcode 14) on `nodeid`, read-only. It is unchanged from the read-only closure (flags = O_RDONLY); the returned fh is used in Read/Release.
func (*VirtioFS) OpenRW ¶ added in v0.2.0
OpenRW performs FUSE_OPEN (Linux fuse.h, opcode 14) on `nodeid` with an explicit access mode in `flags` (OpenReadOnly / OpenWriteOnly / OpenReadWrite, optionally OR-ed with other open(2) flags such as O_TRUNC). The in-args are fuse_open_in{flags, open_flags}; the reply is fuse_open_out{fh, open_flags, backing_id}; the returned fh is used in Read/Write/Fsync/Flush/Release.
func (*VirtioFS) Read ¶
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 ¶
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) Rename ¶ added in v0.2.0
Rename performs FUSE_RENAME (Linux fuse.h, opcode 12): move/rename `oldname` in directory `oldparent` to `newname` in directory `newparent`. The in-args are fuse_rename_in{newdir} followed by the NUL-terminated oldname and the NUL-terminated newname; the reply is error-only.
func (*VirtioFS) RequestQueue ¶
RequestQueue exposes the request virtqueue handle for diagnostics.
func (*VirtioFS) Rmdir ¶ added in v0.2.0
Rmdir performs FUSE_RMDIR (Linux fuse.h, opcode 11): remove the empty subdirectory `name` from directory `parent`. The in-args are the NUL-terminated name; the reply is error-only.
func (*VirtioFS) SetAttr ¶ added in v0.2.0
SetAttr performs FUSE_SETATTR (Linux fuse.h, opcode 4) on `nodeid`, changing the attributes selected by in.Valid (truncate via FattrSize, chmod via FattrMode, chown via FattrUID/FattrGID, utimes via FattrAtime/FattrMtime, etc.). The in-args are fuse_setattr_in; the reply is fuse_attr_out, returned as the updated Attr.
func (*VirtioFS) Symlink ¶ added in v0.2.0
Symlink performs FUSE_SYMLINK (Linux fuse.h, opcode 6) in directory `parent`, creating symlink `name` whose target is `target`. FUSE_SYMLINK has no fixed in-args struct: the in-args are the NUL-terminated link name followed by the NUL-terminated target. The reply is fuse_entry_out.
func (*VirtioFS) Unlink ¶ added in v0.2.0
Unlink performs FUSE_UNLINK (Linux fuse.h, opcode 10): remove the non-directory entry `name` from directory `parent`. The in-args are the NUL-terminated name; the reply is error-only (no out-args).
func (*VirtioFS) Write ¶ added in v0.2.0
Write performs FUSE_WRITE (Linux fuse.h, opcode 16) on (nodeid, fh), writing `data` at byte offset `off`. The request is a THREE-region chain (Virtio 1.2 §5.11.6): readable [fuse_in_header | fuse_write_in], readable [data], writable [fuse_out_header | fuse_write_out]. The reply fuse_write_out{size, padding} reports the bytes the device accepted; Write returns that count and ErrShortWrite if it is less than len(data).
fuse_write_in (Linux fuse.h): fh, offset, size, write_flags, lock_owner, flags, padding.