What is a FUSE filesystem? | Jan 2023
Filesystem in USErspace (FUSE) is a software interface for Unix and Unix-like computer operating systems that lets non-privileged users create their file systems without editing kernel code. This is achieved by running file system code in user space while the FUSE module provides only a bridge to the actual kernel interfaces.
FUSE is available for Linux, FreeBSD, OpenBSD, NetBSD, OpenSolaris, Minix 3, macOS, and Windows.
This article is part of a series exploring/implementing a FUSE filesystem. A full list of articles can be found at the bottom of the article.
How does it work?
To implement a new file system, a handler program should use the libfuse library. This handler program should implement the required methods.
When the filesystem is mounted, the handler is registered with the kernel. Now, when a user calls an operation on this filesystem, the kernel will proxy these requests to the handler.
FUSE is particularly useful for writing virtual filesystems. Unlike traditional filesystems that essentially work with data on mass storage, virtual filesystems don’t actually store data themselves. They act as a view or translation of an existing filesystem or storage device.
In principle, any resource available to a FUSE implementation can be exported as a file system.
Where is this used?
Check these pages for great examples of where FUSE is used.
https://en.wikipedia.org/wiki/Filesystem_in_Userspace#Applications
https://wiki.archlinux.org/title/FUSE
Basic implementation
https://github.com/Goamaral/fuse-filesystem
For this first implementation, I used Go. After reviewing some solutions I decided to use https://github.com/bazil/fuse. It seemed to be the easiest way to prototype.
This library implements the communication with the kernel from scratch in Go (without using libfuse) and enables an incremental implementation of a custom filesystem. It takes advantage of interfaces and if the implementation does not implement an interface (does not have a method), it has a fallback.
My goal for this implementation was to be able to list directory contents, create files and create directories.
I encourage you to check the code, it always seems harder before implementing.
Implemented interfaces
Get the file/directory attributes (permissions, ownership, size, …)
type Node interface {
Attr(ctx context.Context, attr *fuse.Attr) error
}
Lookup file/directory by name inside a file/directory (of course, looking up anything inside a file should return an error)
type NodeStringLookuper interface {
Lookup(ctx context.Context, name string) (Node, error)
}
Creates a file (not sure if it can createdirectories)
type NodeCreater interface {
Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (Node, Handle, error)
}
List files and directories inside a directory
type HandleReadDirAller interface {
ReadDirAll(ctx context.Context) ([]fuse.Dirent, error)
}
Create directory
type NodeMkdirer interface {
Mkdir(ctx context.Context, req *fuse.MkdirRequest) (Node, error)
}
How do I unmount the FUSE filesystem?
$ fusermount3 -u MOUNTPOINT
What’s next?
I will continue implementing more interfaces like writing/reading to a file, getting the file size and exploring the POSIX syscalls to find new features.
After that, I will probably implement the same but in C (with libfuse probably) and register the handler in the kernel.
References
https://en.wikipedia.org/wiki/Filesystem_in_Userspace
https://wiki.archlinux.org/title/FUSE
https://man7.org/linux/man-pages/man3/errno.3.html