Writing a file system in Go bazil.org/fuse: a pure-Go FUSE library 10 June 2013 Tommi Virtanen

FUSE File systems in User SpacE

fuse.sourceforge.net/

People tend to think it's a C library Nope

It's a Linux kernel API With a request-response protocol spoken over a fd Userspace is the server

The C library is the reference implementation ... named exactly the same as the kernel API :( All the things you'd expect from a project hosted at SourceForge in 2013. We'll call it "C FUSE" from here on.

Others provide similar libraries C libraries more or less API compatible with the C FUSE API.

May even be forks of it.

Lots of platform-specific extensions. OS X:

osxfuse.github.com/

(originally code.google.com/p/macfuse/

or github.com/macfuse/macfuse)

-- MacFUSE is dead upstream, forks ahoy Windows: dokan-dev.net/en/

hanwen/go-fuse Han-Wen Nienhuys wrote a go-fuse library. The structure of its API leaves a lot to be desired. Use it if you want. Good luck. Personally, I think writing a library from scratch would be less painful.

rsc to the rescue Russ Cox's fuse library can be found at code.google.com/p/rsc/fuse

code.google.com/p/rsc/source/browse/#hg%2Ffuse Bazil is a fork of it. With a name that doesn't match /fuse/ .

(Strictly, Bazil is an umbrella project, bazil.org/fuse is the FUSE library.

So that was a lie. But call it "Bazil FUSE" if you need to.) Hopefully our contributions help improve the state of the ecosystem. Russ seems to develop mostly on OS X, Tv is a Linux person.

Both should work. OS X contributions are most welcome.

Bazil is an independent implementation Pure-Go implementation of userspace server for the Linux and OS X kernel protocols. Linux kernel support is upstream,

OS X needs the fusefs kernel module from OSXFUSE. On Linux: uses setuid fusermount userspace mount helper from the C FUSE package. Many things you read about "FUSE" are about C FUSE, and don't apply to Bazil.

For example: automatic inode numbering, multithreading.

Understanding FUSE Kernel interface

Documentation/filesystems/fuse.txt

include/uapi/linux/fuse.h

numbered message types, wire structures, various constants C FUSE API docs, "experimental":

fuse.sourceforge.net/doxygen/structfuse__lowlevel__ops.html

fuse.sourceforge.net/doxygen/structfuse__operations.html

Kernel API mount(2) is given a file descriptor. Bazil uses /bin/fusermount as subprocess (to handle /etc/mtab and

such), passes a socketpair fd to it.

source From there on, it's just reads and writes on the fd.

Kernel protocol RequestID:

to match response to request

lifetime ends with response NodeID:

directory entry kernels knows about

kernel tells when to forget HandleID:

open file

kernel tells when to destroy

Low-level C FUSE C FUSE's low-level API exposes kernel requests and responses quite directly.

Dispatches in inode numbers, explicit response. Typical to see helper wrappers. static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { struct stat stbuf; (void) fi; memset(&stbuf, 0, sizeof(stbuf)); if (hello_stat(ino, &stbuf) == -1) fuse_reply_err(req, ENOENT); else fuse_reply_attr(req, &stbuf, 1.0); } source

Low-level C FUSE, 2 static int hello_stat(fuse_ino_t ino, struct stat *stbuf) { stbuf->st_ino = ino; switch (ino) { case 1: stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; break; case 2: stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = strlen(hello_str); break; default: return -1; } return 0; } source

High-level C FUSE Dispatches on path names instead of inode numbers.

Response is implicit. Less need for helper wrappers. static int hello_getattr(const char *path, struct stat *stbuf) { int res = 0; memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (strcmp(path, hello_path) == 0) { stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = strlen(hello_str); } else res = -ENOENT; return res; } source

Bazil API Bazil doesn't do either inode number or path dispatching. Instead, it mirrors kernel data structures, with uint64 handles to objects. It's also significantly closer to the wire protocol. Requests are served by methods on the node itself, not a global dispatch function. Feel free to construct an in-memory tree, for simple filesystems.

Interfaces A Node must implement Attr() . Everything else is optional. For example, if a node does not implement Remove() , unlink(2) on it will always fail. Documentation of possible interfaces is severely lacking right now.

Need to explore how to maintain it in sync with the code.

Directory lookup in Bazil Kernel struct dentry maps to fuse.Node , identified on wire with fuse.NodeID . Lookup() returns a Node , a reference is kept in a map[NodeID]Node until a Forget() call

Open files in Bazil Open file: kernel struct file maps to fuse.Handle , identified on wire with fuse.HandleID . Open() returns a Handle (maybe self), which is kept in a map[HandleID]Handle until a Destroy() call. close(2) has two parts:

per fd method Release() that returns error (for delayed writes and such),

and a final Destroy() that always succeeds.

Let's Go! type File struct{} func (File) Attr() fuse.Attr { return fuse.Attr{Mode: 0444} }

File content func (File) ReadAll(intr fuse.Intr) ([]byte, fuse.Error) { return []byte("hello, world

"), nil } ReadAll() caches the whole content in memory and serves smaller reads from that. It's convenient for pseudofiles. There's also Read() for more realistic use.

Looking up the file by name type Dir struct{} func (Dir) Attr() fuse.Attr { return fuse.Attr{Inode: 1, Mode: os.ModeDir | 0555} } func (Dir) Lookup(name string, intr fuse.Intr) (fuse.Node, fuse.Error) { if name == "hello" { return File{}, nil } return nil, fuse.ENOENT }

Starting point type FS struct{} func (FS) Root() (fuse.Node, fuse.Error) { return Dir{}, nil }

Listing files var dirDirs = []fuse.Dirent{ {Inode: 2, Name: "hello", Type: fuse.DT_File}, } func (Dir) ReadDir(intr fuse.Intr) ([]fuse.Dirent, fuse.Error) { return dirDirs, nil }

Boilerplate // Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Hellofs implements a simple "hello world" file system. package main import ( "flag" "fmt" "log" "os" "bazil.org/fuse" ) var Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:

", os.Args[0]) fmt.Fprintf(os.Stderr, " %s MOUNTPOINT

", os.Args[0]) flag.PrintDefaults() }

...and Go! func main() { flag.Usage = Usage flag.Parse() if flag.NArg() != 1 { Usage() os.Exit(2) } mountpoint := flag.Arg(0) c, err := fuse.Mount(mountpoint) if err != nil { log.Fatal(err) } c.Serve(FS{}) }

Bazil internals API decisions in bazil.org/fuse

Special Error type Bazil methods return fuse.Error and not Go's usual error . It has hidden knowledge of what POSIX errno to return to kernel. Use one of fuse.EIO , fuse.EPERM , fuse.ENOENT , fuse.ENOSYS , fuse.ESTALE , etc. Worst case, use fuse.Errno(syscall.ENOTDIR) etc.

Ideally, Bazil will provide ready errors for all those.

Inode numbers If you don't fill in inode numbers in your Attr() etc calls, Bazil will hash the full path to create a pseudorandom inode number. These may collide, causing confusion in low-level tools like find . This also currently forces Bazil to remember full pathname to every Node.

Details are likely to change, if a cheaper replacement scheme can be figured out. If you care, manage inode numbers explicitly.

Concurrency for { req, err := c.ReadRequest() if err != nil { if err == io.EOF { break } return err } go c.serve(fs, req) } Each request is served in a separate goroutine. Sort of like net/http . If you mutate shared data, use mutexes.