fs

package
v2.5.1 Latest Latest
Warning

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

Go to latest
Published: Mar 25, 2024 License: BSD-3-Clause Imports: 17 Imported by: 182

README

Objective

A high-performance FUSE API that minimizes pitfalls with writing correct filesystems.

Decisions

  • Nodes contain references to their children. This is useful because most filesystems will need to construct tree-like structures.

  • Nodes contain references to their parents. As a result, we can derive the path for each Inode, and there is no need for a separate PathFS.

  • Nodes can be "persistent", meaning their lifetime is not under control of the kernel. This is useful for constructing FS trees in advance, rather than driven by LOOKUP.

  • The NodeID for FS tree node must be defined on creation and are immutable. By contrast, reusing NodeIds (eg. rsc/bazil FUSE, as well as old go-fuse/fuse/nodefs) needs extra synchronization to avoid races with notify and FORGET, and makes handling the inode Generation more complicated.

  • The mode of an Inode is defined on creation. Files cannot change type during their lifetime. This also prevents the common error of forgetting to return the filetype in Lookup/GetAttr.

  • The NodeID (used for communicating with kernel) is equal to Attr.Ino (value shown in Stat and Lstat return values.).

  • No global treelock, to ensure scalability.

  • Support for hard links. libfuse doesn't support this in the high-level API. Extra care for race conditions is needed when looking up the same file through different paths.

  • do not issue Notify{Entry,Delete} as part of AddChild/RmChild/MvChild: because NodeIDs are unique and immutable, there is no confusion about which nodes are invalidated, and the notification doesn't have to happen under lock.

  • Directory reading uses the DirStream. Semantics for rewinding directory reads, and adding files after opening (but before reading) are handled automatically. No support for directory seeks.

  • Method names are based on syscall names. Where there is no syscall (eg. "open directory"), we bias towards writing everything together (Opendir)

To do/To decide

  • Symlink []byte vs string.

Documentation

Overview

Package fs provides infrastructure to build tree-organized filesystems.

Structure of a file system implementation

To create a file system, you should first define types for the nodes of the file system tree.

	type myNode struct {
	   fs.Inode
	}

	// Node types must be InodeEmbedders
	var _ = (fs.InodeEmbedder)((*myNode)(nil))

	// Node types should implement some file system operations, eg. Lookup
	var _ = (fs.NodeLookuper)((*myNode)(nil))

	func (n *myNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno) {
	  ops := myNode{}
       out.Mode = 0755
       out.Size = 42
	  return n.NewInode(ctx, &ops, fs.StableAttr{Mode: syscall.S_IFREG}), 0
	}

The method names are inspired on the system call names, so we have Listxattr rather than ListXAttr.

the file system is mounted by calling mount on the root of the tree,

server, err := fs.Mount("/tmp/mnt", &myNode{}, &fs.Options{})
..
// start serving the file system
server.Wait()

Error handling

All error reporting must use the syscall.Errno type. This is an integer with predefined error codes, where the value 0 (`OK`) should be used to indicate success.

File system concepts

The FUSE API is very similar to Linux' internal VFS API for defining file systems in the kernel. It is therefore useful to understand some terminology.

File content: the raw bytes that we store inside regular files.

Path: a /-separated string path that describes location of a node in the file system tree. For example

dir1/file

describes path root → dir1 → file.

There can be several paths leading from tree root to a particular node, known as hard-linking, for example

  root
  /  \
dir1 dir2
  \  /
  file

Inode: ("index node") points to the file content, and stores metadata (size, timestamps) about a file or directory. Each inode has a type (directory, symlink, regular file, etc.) and an identity (a 64-bit number, unique to the file system). Directories can have children.

The inode in the kernel is represented in Go-FUSE as the Inode type.

While common OS APIs are phrased in terms of paths (strings), the precise semantics of a file system are better described in terms of Inodes. This allows us to specify what happens in corner cases, such as writing data to deleted files.

File descriptor: a handle returned to opening a file. File descriptors always refer to a single inode.

Dirent: a dirent maps (parent inode number, name string) tuple to child inode, thus representing a parent/child relation (or the absense thereof). Dirents do not have an equivalent type inside Go-FUSE, but the result of Lookup operation essentially is a dirent, which the kernel puts in a cache.

Kernel caching

The kernel caches several pieces of information from the FUSE process:

1. File contents: enabled with the fuse.FOPEN_KEEP_CACHE return flag in Open, manipulated with ReadCache and WriteCache, and invalidated with Inode.NotifyContent

2. File Attributes (size, mtime, etc.): controlled with the attribute timeout fields in fuse.AttrOut and fuse.EntryOut, which get be populated from Getattr and Lookup

3. Directory entries (parent/child relations in the FS tree): controlled with the timeout fields in fuse.EntryOut, and invalidated with Inode.NotifyEntry and Inode.NotifyDelete.

Without Directory Entry timeouts, every operation on file "a/b/c" must first do lookups for "a", "a/b" and "a/b/c", which is expensive because of context switches between the kernel and the FUSE process.

Unsuccessful entry lookups can also be cached by setting an entry timeout when Lookup returns ENOENT.

The libfuse C library specifies 1 second timeouts for both attribute and directory entries, but no timeout for negative entries. by default. This can be achieve in go-fuse by setting options on mount, eg.

sec := time.Second
opts := fs.Options{
  EntryTimeout: &sec,
  AttrTimeout: &sec,
}

Locking

Locks for networked filesystems are supported through the suite of Getlk, Setlk and Setlkw methods. They alllow locks on regions of regular files.

Parallelism

The VFS layer in the kernel is optimized to be highly parallel, and this parallelism also affects FUSE file systems: many FUSE operations can run in parallel, and this invites race conditions. It is strongly recommended to test your FUSE file system issuing file operations in parallel, and using the race detector to weed out data races.

Deadlocks

The Go runtime multiplexes Goroutines onto operating system threads, and makes assumptions that some system calls do not block. When accessing a file system from the same process that serves the file system (e.g. in unittests), this can lead to deadlocks, especially when GOMAXPROCS=1, when the Go runtime assumes a system call does not block, but actually is served by the Go-FUSE process.

The following deadlocks are known:

1. Spawning a subprocess uses a fork/exec sequence: the process forks itself into a parent and child. The parent waits for the child to signal that the exec failed or succeeded, while the child prepares for calling exec(). Any setup step in the child that triggers a FUSE request can cause a deadlock.

1a. If the subprocess has a directory specified, the child will chdir into that directory. This generates an ACCESS operation on the directory.

This deadlock can be avoided by disabling the ACCESS operation: return syscall.ENOSYS in the Access implementation, and ensure it is triggered called before initiating the subprocess.

1b. If the subprocess inherits files, the child process uses dup3() to remap file descriptors. If the destination fd happens to be backed by Go-FUSE, the dup3() call will implicitly close the fd, generating a FLUSH operation, eg.

f1, err := os.Open("/fusemnt/file1")
// f1.Fd() == 3
f2, err := os.Open("/fusemnt/file1")
// f2.Fd() == 4

cmd := exec.Command("/bin/true")
cmd.ExtraFiles = []*os.File{f2}
// f2 (fd 4) is moved to fd 3. Deadlocks with GOMAXPROCS=1.
cmd.Start()

This deadlock can be avoided by ensuring that file descriptors pointing into FUSE mounts and file descriptors passed into subprocesses do not overlap, e.g. inserting the following before the above example:

for {
	f, _ := os.Open("/dev/null")
	defer f.Close()
	if f.Fd() > 3 {
		break
	}
}

2. The Go runtime uses the epoll system call to understand which goroutines can respond to I/O. The runtime assumes that epoll does not block, but if files are on a FUSE filesystem, the kernel will generate a POLL operation. To prevent this from happening, Go-FUSE disables the POLL opcode on mount. To ensure this has happened, call WaitMount.

Dynamically discovered file systems

File system data usually cannot fit all in RAM, so the kernel must discover the file system dynamically: as you are entering and list directory contents, the kernel asks the FUSE server about the files and directories you are busy reading/writing, and forgets parts of your file system when it is low on memory.

The two important operations for dynamic file systems are: 1. Lookup, part of the NodeLookuper interface for discovering individual children of directories, and 2. Readdir, part of the NodeReaddirer interface for listing the contents of a directory.

Static in-memory file systems

For small, read-only file systems, getting the locking mechanics of Lookup correct is tedious, so Go-FUSE provides a feature to simplify building such file systems.

Instead of discovering the FS tree on the fly, you can construct the entire tree from an OnAdd method. Then, that in-memory tree structure becomes the source of truth. This means you Go-FUSE must remember Inodes even if the kernel is no longer interested in them. This is done by instantiating "persistent" inodes from the OnAdd method of the root node. See the ZipFS example for a runnable example of how to do this.

Example

This demonstrates how to build a file system in memory. The read/write logic for the file is provided by the MemRegularFile type.

package main

import (
	"context"
	"io/ioutil"
	"log"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
)

// files contains the files we will expose as a file system
var files = map[string]string{
	"file":              "content",
	"subdir/other-file": "other-content",
}

// inMemoryFS is the root of the tree
type inMemoryFS struct {
	fs.Inode
}

// Ensure that we implement NodeOnAdder
var _ = (fs.NodeOnAdder)((*inMemoryFS)(nil))

// OnAdd is called on mounting the file system. Use it to populate
// the file system tree.
func (root *inMemoryFS) OnAdd(ctx context.Context) {
	for name, content := range files {
		dir, base := filepath.Split(name)

		p := &root.Inode

		// Add directories leading up to the file.
		for _, component := range strings.Split(dir, "/") {
			if len(component) == 0 {
				continue
			}
			ch := p.GetChild(component)
			if ch == nil {
				// Create a directory
				ch = p.NewPersistentInode(ctx, &fs.Inode{},
					fs.StableAttr{Mode: syscall.S_IFDIR})
				// Add it
				p.AddChild(component, ch, true)
			}

			p = ch
		}

		// Make a file out of the content bytes. This type
		// provides the open/read/flush methods.
		embedder := &fs.MemRegularFile{
			Data: []byte(content),
		}

		// Create the file. The Inode must be persistent,
		// because its life time is not under control of the
		// kernel.
		child := p.NewPersistentInode(ctx, embedder, fs.StableAttr{})

		// And add it
		p.AddChild(base, child, true)
	}
}

// This demonstrates how to build a file system in memory. The
// read/write logic for the file is provided by the MemRegularFile type.
func main() {
	// This is where we'll mount the FS
	mntDir, _ := ioutil.TempDir("", "")

	root := &inMemoryFS{}
	server, err := fs.Mount(mntDir, root, &fs.Options{
		MountOptions: fuse.MountOptions{Debug: true},
	})
	if err != nil {
		log.Panic(err)
	}

	log.Printf("Mounted on %s", mntDir)
	log.Printf("Unmount by calling 'fusermount -u %s'", mntDir)

	// Wait until unmount before exiting
	server.Wait()
}
Output:

Example (DirectIO)

ExampleDirectIO shows how to create a file whose contents change on every read.

package main

import (
	"context"
	"fmt"
	"log"
	"syscall"
	"time"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
)

// bytesFileHandle is a file handle that carries separate content for
// each Open call
type bytesFileHandle struct {
	content []byte
}

// bytesFileHandle allows reads
var _ = (fs.FileReader)((*bytesFileHandle)(nil))

func (fh *bytesFileHandle) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
	end := off + int64(len(dest))
	if end > int64(len(fh.content)) {
		end = int64(len(fh.content))
	}

	// We could copy to the `dest` buffer, but since we have a
	// []byte already, return that.
	return fuse.ReadResultData(fh.content[off:end]), 0
}

// timeFile is a file that contains the wall clock time as ASCII.
type timeFile struct {
	fs.Inode
}

// timeFile implements Open
var _ = (fs.NodeOpener)((*timeFile)(nil))

func (f *timeFile) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
	// disallow writes
	if fuseFlags&(syscall.O_RDWR|syscall.O_WRONLY) != 0 {
		return nil, 0, syscall.EROFS
	}

	// capture open time
	now := time.Now().Format(time.StampNano) + "\n"
	fh = &bytesFileHandle{
		content: []byte(now),
	}

	// Return FOPEN_DIRECT_IO so content is not cached.
	return fh, fuse.FOPEN_DIRECT_IO, 0
}

// ExampleDirectIO shows how to create a file whose contents change on
// every read.
func main() {
	mntDir := "/tmp/x"
	root := &fs.Inode{}

	// Mount the file system
	server, err := fs.Mount(mntDir, root, &fs.Options{
		MountOptions: fuse.MountOptions{Debug: false},

		// Setup the clock file.
		OnAdd: func(ctx context.Context) {
			ch := root.NewPersistentInode(
				ctx,
				&timeFile{},
				fs.StableAttr{Mode: syscall.S_IFREG})
			root.AddChild("clock", ch, true)
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("cat %s/clock to see the time\n", mntDir)
	fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir)

	// Serve the file system, until unmounted by calling fusermount -u
	server.Wait()
}
Output:

Example (Dynamic)

ExampleDynamic is a whimsical example of a dynamically discovered file system.

package main

import (
	"context"
	"log"
	"os"
	"strconv"
	"syscall"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
)

// numberNode is a filesystem node representing an integer. Prime
// numbers are regular files, while composite numbers are directories
// containing all smaller numbers, eg.
//
//	$ ls -F  /tmp/x/6
//	2  3  4/  5
//
// the file system nodes are deduplicated using inode numbers. The
// number 2 appears in many directories, but it is actually the represented
// by the same numberNode{} object, with inode number 2.
//
//	$ ls -i1  /tmp/x/2  /tmp/x/8/6/4/2
//	2 /tmp/x/2
//	2 /tmp/x/8/6/4/2
type numberNode struct {
	// Must embed an Inode for the struct to work as a node.
	fs.Inode

	// num is the integer represented in this file/directory
	num int
}

// isPrime returns whether n is prime
func isPrime(n int) bool {
	for i := 2; i*i <= n; i++ {
		if n%i == 0 {
			return false
		}
	}
	return true
}

func numberToMode(n int) uint32 {
	// prime numbers are files
	if isPrime(n) {
		return fuse.S_IFREG
	}
	// composite numbers are directories
	return fuse.S_IFDIR
}

// Ensure we are implementing the NodeReaddirer interface
var _ = (fs.NodeReaddirer)((*numberNode)(nil))

// Readdir is part of the NodeReaddirer interface
func (n *numberNode) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
	r := make([]fuse.DirEntry, 0, n.num)
	for i := 2; i < n.num; i++ {
		d := fuse.DirEntry{
			Name: strconv.Itoa(i),
			Ino:  uint64(i),
			Mode: numberToMode(i),
		}
		r = append(r, d)
	}
	return fs.NewListDirStream(r), 0
}

// Ensure we are implementing the NodeLookuper interface
var _ = (fs.NodeLookuper)((*numberNode)(nil))

// Lookup is part of the NodeLookuper interface
func (n *numberNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
	i, err := strconv.Atoi(name)
	if err != nil {
		return nil, syscall.ENOENT
	}

	if i >= n.num || i <= 1 {
		return nil, syscall.ENOENT
	}

	stable := fs.StableAttr{
		Mode: numberToMode(i),
		// The child inode is identified by its Inode number.
		// If multiple concurrent lookups try to find the same
		// inode, they are deduplicated on this key.
		Ino: uint64(i),
	}
	operations := &numberNode{num: i}

	// The NewInode call wraps the `operations` object into an Inode.
	child := n.NewInode(ctx, operations, stable)

	// In case of concurrent lookup requests, it can happen that operations !=
	// child.Operations().
	return child, 0
}

// ExampleDynamic is a whimsical example of a dynamically discovered
// file system.
func main() {
	// This is where we'll mount the FS
	mntDir := "/tmp/x"
	os.Mkdir(mntDir, 0755)
	root := &numberNode{num: 10}
	server, err := fs.Mount(mntDir, root, &fs.Options{
		MountOptions: fuse.MountOptions{
			// Set to true to see how the file system works.
			Debug: true,
		},
	})
	if err != nil {
		log.Panic(err)
	}

	log.Printf("Mounted on %s", mntDir)
	log.Printf("Unmount by calling 'fusermount -u %s'", mntDir)

	// Wait until unmount before exiting
	server.Wait()
}
Output:

Example (HandleLess)

ExampleHandleLess shows how to create a file that can be read or written by implementing Read/Write directly on the nodes.

package main

import (
	"context"
	"fmt"
	"log"
	"sync"
	"syscall"
	"time"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
)

// bytesNode is a file that can be read and written
type bytesNode struct {
	fs.Inode

	// When file systems are mutable, all access must use
	// synchronization.
	mu      sync.Mutex
	content []byte
	mtime   time.Time
}

// Implement GetAttr to provide size and mtime
var _ = (fs.NodeGetattrer)((*bytesNode)(nil))

func (bn *bytesNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
	bn.mu.Lock()
	defer bn.mu.Unlock()
	bn.getattr(out)
	return 0
}

func (bn *bytesNode) getattr(out *fuse.AttrOut) {
	out.Size = uint64(len(bn.content))
	out.SetTimes(nil, &bn.mtime, nil)
}

func (bn *bytesNode) resize(sz uint64) {
	if sz > uint64(cap(bn.content)) {
		n := make([]byte, sz)
		copy(n, bn.content)
		bn.content = n
	} else {
		bn.content = bn.content[:sz]
	}
	bn.mtime = time.Now()
}

// Implement Setattr to support truncation
var _ = (fs.NodeSetattrer)((*bytesNode)(nil))

func (bn *bytesNode) Setattr(ctx context.Context, fh fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno {
	bn.mu.Lock()
	defer bn.mu.Unlock()

	if sz, ok := in.GetSize(); ok {
		bn.resize(sz)
	}
	bn.getattr(out)
	return 0
}

// Implement handleless read.
var _ = (fs.NodeReader)((*bytesNode)(nil))

func (bn *bytesNode) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
	bn.mu.Lock()
	defer bn.mu.Unlock()

	end := off + int64(len(dest))
	if end > int64(len(bn.content)) {
		end = int64(len(bn.content))
	}

	// We could copy to the `dest` buffer, but since we have a
	// []byte already, return that.
	return fuse.ReadResultData(bn.content[off:end]), 0
}

// Implement handleless write.
var _ = (fs.NodeWriter)((*bytesNode)(nil))

func (bn *bytesNode) Write(ctx context.Context, fh fs.FileHandle, buf []byte, off int64) (uint32, syscall.Errno) {
	bn.mu.Lock()
	defer bn.mu.Unlock()

	sz := int64(len(buf))
	if off+sz > int64(len(bn.content)) {
		bn.resize(uint64(off + sz))
	}
	copy(bn.content[off:], buf)
	bn.mtime = time.Now()
	return uint32(sz), 0
}

// Implement (handleless) Open
var _ = (fs.NodeOpener)((*bytesNode)(nil))

func (f *bytesNode) Open(ctx context.Context, openFlags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
	return nil, 0, 0
}

// ExampleHandleLess shows how to create a file that can be read or written
// by implementing Read/Write directly on the nodes.
func main() {
	mntDir := "/tmp/x"
	root := &fs.Inode{}

	// Mount the file system
	server, err := fs.Mount(mntDir, root, &fs.Options{
		MountOptions: fuse.MountOptions{Debug: false},

		// Setup the file.
		OnAdd: func(ctx context.Context) {
			ch := root.NewPersistentInode(
				ctx,
				&bytesNode{},
				fs.StableAttr{
					Mode: syscall.S_IFREG,
					// Make debug output readable.
					Ino: 2,
				})
			root.AddChild("bytes", ch, true)
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf(`Try:

  cd %s
  ls -l bytes
  echo hello > bytes
  ls -l bytes
  cat bytes
  cd -

`, mntDir)
	fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir)

	// Serve the file system, until unmounted by calling fusermount -u
	server.Wait()
}
Output:

Example (LoopbackReuse)

ExampleLoopbackReuse shows how to build a file system on top of the loopback file system.

package main

import (
	"context"
	"fmt"
	"log"
	"sync"
	"syscall"
	"time"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
)

// WindowsNode emulates Windows FS semantics, which forbids deleting open files.
type WindowsNode struct {
	// WindowsNode inherits most functionality from LoopbackNode.
	fs.LoopbackNode

	mu        sync.Mutex
	openCount int
}

var _ = (fs.NodeOpener)((*WindowsNode)(nil))

func (n *WindowsNode) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
	fh, flags, errno := n.LoopbackNode.Open(ctx, flags)
	if errno == 0 {
		n.mu.Lock()
		defer n.mu.Unlock()

		n.openCount++
	}
	return fh, flags, errno
}

var _ = (fs.NodeCreater)((*WindowsNode)(nil))

func (n *WindowsNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (*fs.Inode, fs.FileHandle, uint32, syscall.Errno) {
	inode, fh, flags, errno := n.LoopbackNode.Create(ctx, name, flags, mode, out)
	if errno == 0 {
		wn := inode.Operations().(*WindowsNode)
		wn.openCount++
	}

	return inode, fh, flags, errno
}

var _ = (fs.NodeReleaser)((*WindowsNode)(nil))

// Release decreases the open count. The kernel doesn't wait with
// returning from close(), so if the caller is too quick to
// unlink/rename after calling close(), this may still trigger EBUSY.
func (n *WindowsNode) Release(ctx context.Context, f fs.FileHandle) syscall.Errno {
	n.mu.Lock()
	defer n.mu.Unlock()

	n.openCount--
	if fr, ok := f.(fs.FileReleaser); ok {
		return fr.Release(ctx)
	}
	return 0
}

func isBusy(parent *fs.Inode, name string) bool {
	if ch := parent.GetChild(name); ch != nil {
		if wn, ok := ch.Operations().(*WindowsNode); ok {
			wn.mu.Lock()
			defer wn.mu.Unlock()
			if wn.openCount > 0 {
				return true
			}
		}
	}
	return false
}

var _ = (fs.NodeUnlinker)((*WindowsNode)(nil))

func (n *WindowsNode) Unlink(ctx context.Context, name string) syscall.Errno {
	if isBusy(n.EmbeddedInode(), name) {
		return syscall.EBUSY
	}

	return n.LoopbackNode.Unlink(ctx, name)
}

func newWindowsNode(rootData *fs.LoopbackRoot, parent *fs.Inode, name string, st *syscall.Stat_t) fs.InodeEmbedder {
	n := &WindowsNode{
		LoopbackNode: fs.LoopbackNode{
			RootData: rootData,
		},
	}
	return n
}

// ExampleLoopbackReuse shows how to build a file system on top of the
// loopback file system.
func main() {
	mntDir := "/tmp/mnt"
	origDir := "/tmp/orig"

	rootData := &fs.LoopbackRoot{
		NewNode: newWindowsNode,
		Path:    origDir,
	}

	sec := time.Second
	opts := &fs.Options{
		AttrTimeout:  &sec,
		EntryTimeout: &sec,
	}

	server, err := fs.Mount(mntDir, newWindowsNode(rootData, nil, "", nil), opts)
	if err != nil {
		log.Fatalf("Mount fail: %v\n", err)
	}
	fmt.Printf("files under %s cannot be deleted if they are opened", mntDir)
	server.Wait()
}
Output:

Example (Mount)

ExampleMount shows how to create a loopback file system, and mounting it onto a directory

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
)

func main() {
	mntDir, _ := ioutil.TempDir("", "")
	home := os.Getenv("HOME")
	// Make $HOME available on a mount dir under /tmp/ . Caution:
	// write operations are also mirrored.
	root, err := fs.NewLoopbackRoot(home)
	if err != nil {
		log.Fatal(err)
	}

	// Mount the file system
	server, err := fs.Mount(mntDir, root, &fs.Options{
		MountOptions: fuse.MountOptions{Debug: true},
	})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Mounted %s as loopback on %s\n", home, mntDir)
	fmt.Printf("\n\nCAUTION:\nwrite operations on %s will also affect $HOME (%s)\n\n", mntDir, home)
	fmt.Printf("Unmount by calling 'fusermount -u %s'\n", mntDir)

	// Serve the file system, until unmounted by calling fusermount -u
	server.Wait()
}
Output:

Example (ZipFS)

ExampleZipFS shows an in-memory, static file system

package main

import (
	"archive/zip"
	"context"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"syscall"

	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
)

// zipFile is a file read from a zip archive.
type zipFile struct {
	fs.Inode
	file *zip.File

	mu   sync.Mutex
	data []byte
}

// We decompress the file on demand in Open
var _ = (fs.NodeOpener)((*zipFile)(nil))

// Getattr sets the minimum, which is the size. A more full-featured
// FS would also set timestamps and permissions.
var _ = (fs.NodeGetattrer)((*zipFile)(nil))

func (zf *zipFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
	out.Size = zf.file.UncompressedSize64
	return 0
}

// Open lazily unpacks zip data
func (zf *zipFile) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
	zf.mu.Lock()
	defer zf.mu.Unlock()
	if zf.data == nil {
		rc, err := zf.file.Open()
		if err != nil {
			return nil, 0, syscall.EIO
		}
		content, err := ioutil.ReadAll(rc)
		if err != nil {
			return nil, 0, syscall.EIO
		}

		zf.data = content
	}

	// We don't return a filehandle since we don't really need
	// one.  The file content is immutable, so hint the kernel to
	// cache the data.
	return nil, fuse.FOPEN_KEEP_CACHE, fs.OK
}

// Read simply returns the data that was already unpacked in the Open call
func (zf *zipFile) Read(ctx context.Context, f fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
	end := int(off) + len(dest)
	if end > len(zf.data) {
		end = len(zf.data)
	}
	return fuse.ReadResultData(zf.data[off:end]), fs.OK
}

// zipRoot is the root of the Zip filesystem. Its only functionality
// is populating the filesystem.
type zipRoot struct {
	fs.Inode

	zr *zip.Reader
}

// The root populates the tree in its OnAdd method
var _ = (fs.NodeOnAdder)((*zipRoot)(nil))

func (zr *zipRoot) OnAdd(ctx context.Context) {
	// OnAdd is called once we are attached to an Inode. We can
	// then construct a tree.  We construct the entire tree, and
	// we don't want parts of the tree to disappear when the
	// kernel is short on memory, so we use persistent inodes.
	for _, f := range zr.zr.File {
		dir, base := filepath.Split(f.Name)

		p := &zr.Inode
		for _, component := range strings.Split(dir, "/") {
			if len(component) == 0 {
				continue
			}
			ch := p.GetChild(component)
			if ch == nil {
				ch = p.NewPersistentInode(ctx, &fs.Inode{},
					fs.StableAttr{Mode: fuse.S_IFDIR})
				p.AddChild(component, ch, true)
			}

			p = ch
		}

		if f.FileInfo().IsDir() {
			continue
		}

		ch := p.NewPersistentInode(ctx, &zipFile{file: f}, fs.StableAttr{})
		p.AddChild(base, ch, true)
	}
}

// ExampleZipFS shows an in-memory, static file system
func main() {
	flag.Parse()
	if len(flag.Args()) != 1 {
		log.Fatal("usage: zipmount ZIP-FILE")
	}
	zfile, err := zip.OpenReader(flag.Arg(0))
	if err != nil {
		log.Fatal(err)
	}

	root := &zipRoot{zr: &zfile.Reader}
	mnt := "/tmp/x"
	os.Mkdir(mnt, 0755)
	server, err := fs.Mount(mnt, root, nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("zip file mounted")
	fmt.Printf("to unmount: fusermount -u %s\n", mnt)
	server.Wait()
}
Output:

Index

Examples

Constants

View Source
const RENAME_EXCHANGE = 0x2

RENAME_EXCHANGE is a flag argument for renameat2()

Variables

View Source
var ENOATTR = syscall.ENODATA

ENOATTR indicates that an extended attribute was not present.

View Source
var OK = syscall.Errno(0)

OK is the Errno return value to indicate absense of errors.

Functions

func Mount

func Mount(dir string, root InodeEmbedder, options *Options) (*fuse.Server, error)

Mount mounts the given NodeFS on the directory, and starts serving requests. This is a convenience wrapper around NewNodeFS and fuse.NewServer. If nil is given as options, default settings are applied, which are 1 second entry and attribute timeout.

func NewNodeFS

func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem

NewNodeFS creates a node based filesystem based on the InodeEmbedder instance for the root of the tree.

func ToErrno

func ToErrno(err error) syscall.Errno

ToErrno exhumes the syscall.Errno error from wrapped error values.

Types

type DirStream

type DirStream interface {
	// HasNext indicates if there are further entries. HasNext
	// might be called on already closed streams.
	HasNext() bool

	// Next retrieves the next entry. It is only called if HasNext
	// has previously returned true.  The Errno return may be used to
	// indicate I/O errors
	Next() (fuse.DirEntry, syscall.Errno)

	// Close releases resources related to this directory
	// stream.
	Close()
}

DirStream lists directory entries.

func NewListDirStream

func NewListDirStream(list []fuse.DirEntry) DirStream

NewListDirStream wraps a slice of DirEntry as a DirStream.

func NewLoopbackDirStream

func NewLoopbackDirStream(name string) (DirStream, syscall.Errno)

NewLoopbackDirStream open a directory for reading as a DirStream

type FileAllocater

type FileAllocater interface {
	Allocate(ctx context.Context, off uint64, size uint64, mode uint32) syscall.Errno
}

See NodeAllocater.

type FileFlusher

type FileFlusher interface {
	Flush(ctx context.Context) syscall.Errno
}

See NodeFlusher.

type FileFsyncer

type FileFsyncer interface {
	Fsync(ctx context.Context, flags uint32) syscall.Errno
}

See NodeFsync.

type FileGetattrer

type FileGetattrer interface {
	Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno
}

See NodeGetattrer.

type FileGetlker

type FileGetlker interface {
	Getlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno
}

See NodeGetlker.

type FileHandle

type FileHandle interface {
}

FileHandle is a resource identifier for opened files. Usually, a FileHandle should implement some of the FileXxxx interfaces.

All of the FileXxxx operations can also be implemented at the InodeEmbedder level, for example, one can implement NodeReader instead of FileReader.

FileHandles are useful in two cases: First, if the underlying storage systems needs a handle for reading/writing. This is the case with Unix system calls, which need a file descriptor (See also the function `NewLoopbackFile`). Second, it is useful for implementing files whose contents are not tied to an inode. For example, a file like `/proc/interrupts` has no fixed content, but changes on each open call. This means that each file handle must have its own view of the content; this view can be tied to a FileHandle. Files that have such dynamic content should return the FOPEN_DIRECT_IO flag from their `Open` method. See directio_test.go for an example.

func NewLoopbackFile

func NewLoopbackFile(fd int) FileHandle

NewLoopbackFile creates a FileHandle out of a file descriptor. All operations are implemented. When using the Fd from a *os.File, call syscall.Dup() on the fd, to avoid os.File's finalizer from closing the file descriptor.

type FileLseeker

type FileLseeker interface {
	Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno)
}

See NodeLseeker.

type FileReader

type FileReader interface {
	Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
}

See NodeReader.

type FileReleaser

type FileReleaser interface {
	Release(ctx context.Context) syscall.Errno
}

See NodeReleaser.

type FileSetattrer

type FileSetattrer interface {
	Setattr(ctx context.Context, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
}

See NodeFsync.

type FileSetlker

type FileSetlker interface {
	Setlk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
}

See NodeSetlker.

type FileSetlkwer

type FileSetlkwer interface {
	Setlkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
}

See NodeSetlkwer.

type FileWriter

type FileWriter interface {
	Write(ctx context.Context, data []byte, off int64) (written uint32, errno syscall.Errno)
}

See NodeWriter.

type Inode

type Inode struct {
	// contains filtered or unexported fields
}

Inode is a node in VFS tree. Inodes are one-to-one mapped to Operations instances, which is the extension interface for file systems. One can create fully-formed trees of Inodes ahead of time by creating "persistent" Inodes.

The Inode struct contains a lock, so it should not be copied. Inodes should be obtained by calling Inode.NewInode() or Inode.NewPersistentInode().

func (*Inode) AddChild

func (n *Inode) AddChild(name string, ch *Inode, overwrite bool) (success bool)

AddChild adds a child to this node. If overwrite is false, fail if the destination already exists.

func (*Inode) Children

func (n *Inode) Children() map[string]*Inode

Children returns the list of children of this directory Inode.

func (*Inode) EmbeddedInode

func (n *Inode) EmbeddedInode() *Inode

func (*Inode) ExchangeChild

func (n *Inode) ExchangeChild(oldName string, newParent *Inode, newName string)

ExchangeChild swaps the entries at (n, oldName) and (newParent, newName).

func (*Inode) ForgetPersistent

func (n *Inode) ForgetPersistent()

ForgetPersistent manually marks the node as no longer important. If it has no children, and if the kernel as no references, the nodes gets removed from the tree.

func (*Inode) Forgotten

func (n *Inode) Forgotten() bool

Forgotten returns true if the kernel holds no references to this inode. This can be used for background cleanup tasks, since the kernel has no way of reviving forgotten nodes by its own initiative.

func (*Inode) GetChild

func (n *Inode) GetChild(name string) *Inode

GetChild returns a child node with the given name, or nil if the directory has no child by that name.

func (*Inode) IsDir

func (n *Inode) IsDir() bool

func (*Inode) IsRoot

func (n *Inode) IsRoot() bool

Returns whether this is the root of the tree

func (*Inode) Mode

func (n *Inode) Mode() uint32

Mode returns the filetype

func (*Inode) MvChild

func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool

MvChild executes a rename. If overwrite is set, a child at the destination will be overwritten, should it exist. It returns false if 'overwrite' is false, and the destination exists.

func (*Inode) NewInode

func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode

NewInode returns an inode for the given InodeEmbedder. The mode should be standard mode argument (eg. S_IFDIR). The inode number in id.Ino argument is used to implement hard-links. If it is given, and another node with the same ID is known, the new inode may be ignored, and the old one used instead.

func (*Inode) NewPersistentInode

func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode

NewPersistentInode returns an Inode whose lifetime is not in control of the kernel.

When the kernel is short on memory, it will forget cached file system information (directory entries and inode metadata). This is announced with FORGET messages. There are no guarantees if or when this happens. When it happens, these are handled transparently by go-fuse: all Inodes created with NewInode are released automatically. NewPersistentInode creates inodes that go-fuse keeps in memory, even if the kernel is not interested in them. This is convenient for building static trees up-front.

func (*Inode) NotifyContent

func (n *Inode) NotifyContent(off, sz int64) syscall.Errno

NotifyContent notifies the kernel that content under the given inode should be flushed from buffers.

func (*Inode) NotifyDelete

func (n *Inode) NotifyDelete(name string, child *Inode) syscall.Errno

NotifyDelete notifies the kernel that the given inode was removed from this directory as entry under the given name. It is equivalent to NotifyEntry, but also sends an event to inotify watchers.

func (*Inode) NotifyEntry

func (n *Inode) NotifyEntry(name string) syscall.Errno

NotifyEntry notifies the kernel that data for a (directory, name) tuple should be invalidated. On next access, a LOOKUP operation will be started.

func (*Inode) Operations

func (n *Inode) Operations() InodeEmbedder

Operations returns the object implementing the file system operations.

func (*Inode) Parent

func (n *Inode) Parent() (string, *Inode)

Parents returns a parent of this Inode, or nil if this Inode is deleted or is the root

func (*Inode) Path

func (n *Inode) Path(root *Inode) string

Path returns a path string to the inode relative to `root`. Pass nil to walk the hierarchy as far up as possible.

If you set `root`, Path() warns if it finds an orphaned Inode, i.e. if it does not end up at `root` after walking the hierarchy.

func (*Inode) ReadCache

func (n *Inode) ReadCache(offset int64, dest []byte) (count int, errno syscall.Errno)

ReadCache reads data from the kernel cache.

func (*Inode) RmAllChildren

func (n *Inode) RmAllChildren()

RmAllChildren recursively drops a tree, forgetting all persistent nodes.

func (*Inode) RmChild

func (n *Inode) RmChild(names ...string) (success, live bool)

RmChild removes multiple children. Returns whether the removal succeeded and whether the node is still live afterward. The removal is transactional: it only succeeds if all names are children, and if they all were removed successfully. If the removal was successful, and there are no children left, the node may be removed from the FS tree. In that case, RmChild returns live==false.

func (*Inode) Root

func (n *Inode) Root() *Inode

Returns the root of the tree

func (*Inode) StableAttr

func (n *Inode) StableAttr() StableAttr

StableAttr returns the (Ino, Gen) tuple for this node.

func (*Inode) String

func (n *Inode) String() string

debugString is used for debugging. Racy.

func (*Inode) WriteCache

func (n *Inode) WriteCache(offset int64, data []byte) syscall.Errno

WriteCache stores data in the kernel cache.

type InodeEmbedder

type InodeEmbedder interface {

	// EmbeddedInode returns a pointer to the embedded inode.
	EmbeddedInode() *Inode
	// contains filtered or unexported methods
}

InodeEmbedder is an interface for structs that embed Inode.

InodeEmbedder objects usually should implement some of the NodeXxxx interfaces, to provide user-defined file system behaviors.

In general, if an InodeEmbedder does not implement specific filesystem methods, the filesystem will react as if it is a read-only filesystem with a predefined tree structure.

func NewLoopbackRoot

func NewLoopbackRoot(rootPath string) (InodeEmbedder, error)

NewLoopbackRoot returns a root node for a loopback file system whose root is at the given root. This node implements all NodeXxxxer operations available.

type LoopbackNode added in v2.1.0

type LoopbackNode struct {
	Inode

	// RootData points back to the root of the loopback filesystem.
	RootData *LoopbackRoot
}

LoopbackNode is a filesystem node in a loopback file system. It is public so it can be used as a basis for other loopback based filesystems. See NewLoopbackFile or LoopbackRoot for more information.

func (*LoopbackNode) CopyFileRange added in v2.1.0

func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
	offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
	len uint64, flags uint64) (uint32, syscall.Errno)

func (*LoopbackNode) Create added in v2.1.0

func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno)

func (*LoopbackNode) Getattr added in v2.1.0

func (n *LoopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno

func (*LoopbackNode) Getxattr added in v2.1.0

func (n *LoopbackNode) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno)
func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)

func (*LoopbackNode) Listxattr added in v2.1.0

func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno)

func (*LoopbackNode) Lookup added in v2.1.0

func (n *LoopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)

func (*LoopbackNode) Mkdir added in v2.1.0

func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)

func (*LoopbackNode) Mknod added in v2.1.0

func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)

func (*LoopbackNode) Open added in v2.1.0

func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)

func (*LoopbackNode) Opendir added in v2.1.0

func (n *LoopbackNode) Opendir(ctx context.Context) syscall.Errno

func (*LoopbackNode) Readdir added in v2.1.0

func (n *LoopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno)
func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno)

func (*LoopbackNode) Removexattr added in v2.1.0

func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno

func (*LoopbackNode) Rename added in v2.1.0

func (n *LoopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno

func (*LoopbackNode) Rmdir added in v2.1.0

func (n *LoopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno

func (*LoopbackNode) Setattr added in v2.1.0

func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno

func (*LoopbackNode) Setxattr added in v2.1.0

func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno

func (*LoopbackNode) Statfs added in v2.1.0

func (n *LoopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno
func (n *LoopbackNode) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)
func (n *LoopbackNode) Unlink(ctx context.Context, name string) syscall.Errno

type LoopbackRoot added in v2.1.0

type LoopbackRoot struct {
	// The path to the root of the underlying file system.
	Path string

	// The device on which the Path resides. This must be set if
	// the underlying filesystem crosses file systems.
	Dev uint64

	// NewNode returns a new InodeEmbedder to be used to respond
	// to a LOOKUP/CREATE/MKDIR/MKNOD opcode. If not set, use a
	// LoopbackNode.
	NewNode func(rootData *LoopbackRoot, parent *Inode, name string, st *syscall.Stat_t) InodeEmbedder
}

LoopbackRoot holds the parameters for creating a new loopback filesystem. Loopback filesystem delegate their operations to an underlying POSIX file system.

type MemRegularFile

type MemRegularFile struct {
	Inode

	Data []byte
	Attr fuse.Attr
	// contains filtered or unexported fields
}

MemRegularFile is a filesystem node that holds a read-only data slice in memory.

func (*MemRegularFile) Flush

func (*MemRegularFile) Getattr

func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno

func (*MemRegularFile) Open

func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)

func (*MemRegularFile) Read

func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)

func (*MemRegularFile) Setattr added in v2.0.3

func (*MemRegularFile) Write added in v2.0.3

func (f *MemRegularFile) Write(ctx context.Context, fh FileHandle, data []byte, off int64) (uint32, syscall.Errno)
type MemSymlink struct {
	Inode
	Attr fuse.Attr
	Data []byte
}

MemSymlink is an inode holding a symlink in memory.

func (*MemSymlink) Getattr

func (l *MemSymlink) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno
func (l *MemSymlink) Readlink(ctx context.Context) ([]byte, syscall.Errno)

type NodeAccesser

type NodeAccesser interface {
	Access(ctx context.Context, mask uint32) syscall.Errno
}

Access should return if the caller can access the file with the given mode. This is used for two purposes: to determine if a user may enter a directory, and to answer to implement the access system call. In the latter case, the context has data about the real UID. For example, a root-SUID binary called by user susan gets the UID and GID for susan here.

If not defined, a default implementation will check traditional unix permissions of the Getattr result agains the caller. If so, it is necessary to either return permissions from GetAttr/Lookup or set Options.DefaultPermissions in order to allow chdir into the FUSE mount.

type NodeAllocater

type NodeAllocater interface {
	Allocate(ctx context.Context, f FileHandle, off uint64, size uint64, mode uint32) syscall.Errno
}

Allocate preallocates space for future writes, so they will never encounter ESPACE.

type NodeCopyFileRanger

type NodeCopyFileRanger interface {
	CopyFileRange(ctx context.Context, fhIn FileHandle,
		offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
		len uint64, flags uint64) (uint32, syscall.Errno)
}

CopyFileRange copies data between sections of two files, without the data having to pass through the calling process.

type NodeCreater

type NodeCreater interface {
	Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno)
}

Create is similar to Lookup, but should create a new child. It typically also returns a FileHandle as a reference for future reads/writes. Default is to return EROFS.

type NodeFlusher

type NodeFlusher interface {
	Flush(ctx context.Context, f FileHandle) syscall.Errno
}

Flush is called for the close(2) call on a file descriptor. In case of a descriptor that was duplicated using dup(2), it may be called more than once for the same FileHandle. The default implementation forwards to the FileHandle, or if the handle does not support FileFlusher, returns OK.

type NodeFsyncer

type NodeFsyncer interface {
	Fsync(ctx context.Context, f FileHandle, flags uint32) syscall.Errno
}

Fsync is a signal to ensure writes to the Inode are flushed to stable storage.

type NodeGetattrer

type NodeGetattrer interface {
	Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
}

GetAttr reads attributes for an Inode. The library will ensure that Mode and Ino are set correctly. For files that are not opened with FOPEN_DIRECTIO, Size should be set so it can be read correctly. If returning zeroed permissions, the default behavior is to change the mode of 0755 (directory) or 0644 (files). This can be switched off with the Options.NullPermissions setting. If blksize is unset, 4096 is assumed, and the 'blocks' field is set accordingly. The 'f' argument is provided for consistency, however, in practice the kernel never sends a file handle, even if the Getattr call originated from a fstat system call.

type NodeGetlker

type NodeGetlker interface {
	Getlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) syscall.Errno
}

Getlk returns locks that would conflict with the given input lock. If no locks conflict, the output has type L_UNLCK. See fcntl(2) for more information. If not defined, returns ENOTSUP

type NodeGetxattrer

type NodeGetxattrer interface {
	Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno)
}

Getxattr should read data for the given attribute into `dest` and return the number of bytes. If `dest` is too small, it should return ERANGE and the size of the attribute. If not defined, Getxattr will return ENOATTR.

type NodeLinker

type NodeLinker interface {
	Link(ctx context.Context, target InodeEmbedder, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
}

Link is similar to Lookup, but must create a new link to an existing Inode. Default is to return ENOTSUP.

type NodeListxattrer

type NodeListxattrer interface {
	Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno)
}

Listxattr should read all attributes (null terminated) into `dest`. If the `dest` buffer is too small, it should return ERANGE and the correct size. If not defined, return an empty list and success.

type NodeLookuper

type NodeLookuper interface {
	Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)
}

Lookup should find a direct child of a directory by the child's name. If the entry does not exist, it should return ENOENT and optionally set a NegativeTimeout in `out`. If it does exist, it should return attribute data in `out` and return the Inode for the child. A new inode can be created using `Inode.NewInode`. The new Inode will be added to the FS tree automatically if the return status is OK.

If a directory does not implement NodeLookuper, the library looks for an existing child with the given name.

The input to a Lookup is {parent directory, name string}.

Lookup, if successful, must return an *Inode. Once the Inode is returned to the kernel, the kernel can issue further operations, such as Open or Getxattr on that node.

A successful Lookup also returns an EntryOut. Among others, this contains file attributes (mode, size, mtime, etc.).

FUSE supports other operations that modify the namespace. For example, the Symlink, Create, Mknod, Link methods all create new children in directories. Hence, they also return *Inode and must populate their fuse.EntryOut arguments.

type NodeLseeker

type NodeLseeker interface {
	Lseek(ctx context.Context, f FileHandle, Off uint64, whence uint32) (uint64, syscall.Errno)
}

Lseek is used to implement holes: it should return the first offset beyond `off` where there is data (SEEK_DATA) or where there is a hole (SEEK_HOLE).

type NodeMkdirer

type NodeMkdirer interface {
	Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
}

Mkdir is similar to Lookup, but must create a directory entry and Inode. Default is to return ENOTSUP.

type NodeMknoder

type NodeMknoder interface {
	Mknod(ctx context.Context, name string, mode uint32, dev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
}

Mknod is similar to Lookup, but must create a device entry and Inode. Default is to return ENOTSUP.

type NodeOnAdder

type NodeOnAdder interface {
	OnAdd(ctx context.Context)
}

OnAdd is called when this InodeEmbedder is initialized.

type NodeOpendirer

type NodeOpendirer interface {
	Opendir(ctx context.Context) syscall.Errno
}

OpenDir opens a directory Inode for reading its contents. The actual reading is driven from Readdir, so this method is just for performing sanity/permission checks. The default is to return success.

type NodeOpener

type NodeOpener interface {
	Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
}

Open opens an Inode (of regular file type) for reading. It is optional but recommended to return a FileHandle.

type NodeReaddirer

type NodeReaddirer interface {
	Readdir(ctx context.Context) (DirStream, syscall.Errno)
}

Readdir opens a stream of directory entries.

Readdir essentiallly returns a list of strings, and it is allowed for Readdir to return different results from Lookup. For example, you can return nothing for Readdir ("ls my-fuse-mount" is empty), while still implementing Lookup ("ls my-fuse-mount/a-specific-file" shows a single file). The DirStream returned must be deterministic; a randomized result (e.g. due to map iteration) can lead to entries disappearing if multiple processes read the same directory concurrently.

If a directory does not implement NodeReaddirer, a list of currently known children from the tree is returned. This means that static in-memory file systems need not implement NodeReaddirer.

type NodeReader

type NodeReader interface {
	Read(ctx context.Context, f FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
}

Reads data from a file. The data should be returned as ReadResult, which may be constructed from the incoming `dest` buffer. If the file was opened without FileHandle, the FileHandle argument here is nil. The default implementation forwards to the FileHandle.

type NodeReadlinker

type NodeReadlinker interface {
	Readlink(ctx context.Context) ([]byte, syscall.Errno)
}

Readlink reads the content of a symlink.

type NodeReleaser

type NodeReleaser interface {
	Release(ctx context.Context, f FileHandle) syscall.Errno
}

This is called to before a FileHandle is forgotten. The kernel ignores the return value of this method, so any cleanup that requires specific synchronization or could fail with I/O errors should happen in Flush instead. The default implementation forwards to the FileHandle.

type NodeRemovexattrer

type NodeRemovexattrer interface {
	Removexattr(ctx context.Context, attr string) syscall.Errno
}

Removexattr should delete the given attribute. If not defined, Removexattr will return ENOATTR.

type NodeRenamer

type NodeRenamer interface {
	Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, flags uint32) syscall.Errno
}

Rename should move a child from one directory to a different one. The change is effected in the FS tree if the return status is OK. Default is to return ENOTSUP.

type NodeRmdirer

type NodeRmdirer interface {
	Rmdir(ctx context.Context, name string) syscall.Errno
}

Rmdir is like Unlink but for directories. Default is to return success.

type NodeSetattrer

type NodeSetattrer interface {
	Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
}

SetAttr sets attributes for an Inode. Default is to return ENOTSUP.

type NodeSetlker

type NodeSetlker interface {
	Setlk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
}

Setlk obtains a lock on a file, or fail if the lock could not obtained. See fcntl(2) for more information. If not defined, returns ENOTSUP

type NodeSetlkwer

type NodeSetlkwer interface {
	Setlkw(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32) syscall.Errno
}

Setlkw obtains a lock on a file, waiting if necessary. See fcntl(2) for more information. If not defined, returns ENOTSUP

type NodeSetxattrer

type NodeSetxattrer interface {
	Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno
}

Setxattr should store data for the given attribute. See setxattr(2) for information about flags. If not defined, Setxattr will return ENOATTR.

type NodeStatfser

type NodeStatfser interface {
	Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno
}

Statfs implements statistics for the filesystem that holds this Inode. If not defined, the `out` argument will zeroed with an OK result. This is because OSX filesystems must Statfs, or the mount will not work.

type NodeSymlinker

type NodeSymlinker interface {
	Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (node *Inode, errno syscall.Errno)
}

Symlink is similar to Lookup, but must create a new symbolic link. Default is to return ENOTSUP.

type NodeUnlinker

type NodeUnlinker interface {
	Unlink(ctx context.Context, name string) syscall.Errno
}

Unlink should remove a child from this directory. If the return status is OK, the Inode is removed as child in the FS tree automatically. Default is to return success.

type NodeWriter

type NodeWriter interface {
	Write(ctx context.Context, f FileHandle, data []byte, off int64) (written uint32, errno syscall.Errno)
}

Writes the data into the file handle at given offset. After returning, the data will be reused and may not referenced. The default implementation forwards to the FileHandle.

type Options

type Options struct {
	// MountOptions contain the options for mounting the fuse server
	fuse.MountOptions

	// If set to nonnil, this defines the overall entry timeout
	// for the file system. See fuse.EntryOut for more information.
	EntryTimeout *time.Duration

	// If set to nonnil, this defines the overall attribute
	// timeout for the file system. See fuse.EntryOut for more
	// information.
	AttrTimeout *time.Duration

	// If set to nonnil, this defines the overall entry timeout
	// for failed lookups (fuse.ENOENT). See fuse.EntryOut for
	// more information.
	NegativeTimeout *time.Duration

	// Automatic inode numbers are handed out sequentially
	// starting from this number. If unset, use 2^63.
	FirstAutomaticIno uint64

	// OnAdd is an alternative way to specify the OnAdd
	// functionality of the root node.
	OnAdd func(ctx context.Context)

	// NullPermissions if set, leaves null file permissions
	// alone. Otherwise, they are set to 755 (dirs) or 644 (other
	// files.), which is necessary for doing a chdir into the FUSE
	// directories.
	NullPermissions bool

	// If nonzero, replace default (zero) UID with the given UID
	UID uint32

	// If nonzero, replace default (zero) GID with the given GID
	GID uint32

	// ServerCallbacks can be provided to stub out notification
	// functions for testing a filesystem without mounting it.
	ServerCallbacks ServerCallbacks

	// Logger is a sink for diagnostic messages. Diagnostic
	// messages are printed under conditions where we cannot
	// return error, but want to signal something seems off
	// anyway. If unset, no messages are printed.
	Logger *log.Logger

	// RootStableAttr is an optional way to set e.g. Ino and/or Gen for
	// the root directory when calling fs.Mount(), Mode is ignored.
	RootStableAttr *StableAttr
}

Options sets options for the entire filesystem

type ServerCallbacks added in v2.0.3

type ServerCallbacks interface {
	DeleteNotify(parent uint64, child uint64, name string) fuse.Status
	EntryNotify(parent uint64, name string) fuse.Status
	InodeNotify(node uint64, off int64, length int64) fuse.Status
	InodeRetrieveCache(node uint64, offset int64, dest []byte) (n int, st fuse.Status)
	InodeNotifyStoreCache(node uint64, offset int64, data []byte) fuse.Status
}

ServerCallbacks are calls into the kernel to manipulate the inode, entry and page cache. They are stubbed so filesystems can be unittested without mounting them.

type StableAttr

type StableAttr struct {
	// Each Inode has a type, which does not change over the
	// lifetime of the inode, for example fuse.S_IFDIR. The default (0)
	// is interpreted as S_IFREG (regular file).
	Mode uint32

	// The inode number must be unique among the currently live
	// objects in the file system. It is used to communicate to
	// the kernel about this file object. The value uint64(-1)
	// is reserved. When using Ino==0, a unique, sequential
	// number is assigned (starting at 2^63 by default) on Inode creation.
	Ino uint64

	// When reusing a previously used inode number for a new
	// object, the new object must have a different Gen
	// number. This is irrelevant if the FS is not exported over
	// NFS
	Gen uint64
}

StableAttr holds immutable attributes of a object in the filesystem.

func (*StableAttr) Reserved

func (i *StableAttr) Reserved() bool

Reserved returns if the StableAttr is using reserved Inode numbers.

Jump to

Keyboard shortcuts

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