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) (*fs.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 that 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.
Copyright 2024 the Go-FUSE Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
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" "log" "os" "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, _ := os.MkdirTemp("", "") 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" "log" "os" "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" ) func main() { mntDir, _ := os.MkdirTemp("", "") 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" "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 := io.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 ¶
- Constants
- Variables
- func Mount(dir string, root InodeEmbedder, options *Options) (*fuse.Server, error)
- func NewNodeFS(root InodeEmbedder, opts *Options) fuse.RawFileSystem
- func ToErrno(err error) syscall.Errno
- type DirStream
- type FileAllocater
- type FileFlusher
- type FileFsyncdirer
- type FileFsyncer
- type FileGetattrer
- type FileGetlker
- type FileHandle
- type FileLseeker
- type FilePassthroughFder
- type FileReaddirenter
- type FileReader
- type FileReleasedirer
- type FileReleaser
- type FileSeekdirer
- type FileSetattrer
- type FileSetlker
- type FileSetlkwer
- type FileStatxer
- type FileWriter
- type Inode
- func (n *Inode) AddChild(name string, ch *Inode, overwrite bool) (success bool)
- func (n *Inode) Children() map[string]*Inode
- func (n *Inode) EmbeddedInode() *Inode
- func (n *Inode) ExchangeChild(oldName string, newParent *Inode, newName string)
- func (n *Inode) ForgetPersistent()
- func (n *Inode) Forgotten() booldeprecated
- func (n *Inode) GetChild(name string) *Inode
- func (n *Inode) IsDir() bool
- func (n *Inode) IsRoot() bool
- func (n *Inode) Mode() uint32
- func (n *Inode) MvChild(old string, newParent *Inode, newName string, overwrite bool) bool
- func (n *Inode) NewInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode
- func (n *Inode) NewPersistentInode(ctx context.Context, node InodeEmbedder, id StableAttr) *Inode
- func (n *Inode) NotifyContent(off, sz int64) syscall.Errno
- func (n *Inode) NotifyDelete(name string, child *Inode) syscall.Errno
- func (n *Inode) NotifyEntry(name string) syscall.Errno
- func (n *Inode) Operations() InodeEmbedder
- func (n *Inode) Parent() (string, *Inode)
- func (n *Inode) Path(root *Inode) string
- func (n *Inode) ReadCache(offset int64, dest []byte) (count int, errno syscall.Errno)
- func (n *Inode) RmAllChildren()
- func (n *Inode) RmChild(names ...string) (success, live bool)
- func (n *Inode) Root() *Inode
- func (n *Inode) StableAttr() StableAttr
- func (n *Inode) String() string
- func (n *Inode) WriteCache(offset int64, data []byte) syscall.Errno
- type InodeEmbedder
- type LoopbackNode
- func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle, offIn uint64, out *Inode, ...) (uint32, syscall.Errno)
- func (n *LoopbackNode) Create(ctx context.Context, name string, flags uint32, mode uint32, ...) (inode *Inode, fh FileHandle, fuseFlags uint32, errno syscall.Errno)
- func (n *LoopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
- 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 (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno)
- func (n *LoopbackNode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*Inode, syscall.Errno)
- func (n *LoopbackNode) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
- func (n *LoopbackNode) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (*Inode, syscall.Errno)
- func (n *LoopbackNode) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
- func (n *LoopbackNode) OpendirHandle(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno)
- func (n *LoopbackNode) Readdir(ctx context.Context) (DirStream, syscall.Errno)
- func (n *LoopbackNode) Readlink(ctx context.Context) ([]byte, syscall.Errno)
- func (n *LoopbackNode) Removexattr(ctx context.Context, attr string) syscall.Errno
- func (n *LoopbackNode) Rename(ctx context.Context, name string, newParent InodeEmbedder, newName string, ...) syscall.Errno
- func (n *LoopbackNode) Rmdir(ctx context.Context, name string) syscall.Errno
- func (n *LoopbackNode) Setattr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
- func (n *LoopbackNode) Setxattr(ctx context.Context, attr string, data []byte, flags uint32) syscall.Errno
- func (n *LoopbackNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno
- func (n *LoopbackNode) Statx(ctx context.Context, f FileHandle, flags uint32, mask uint32, ...) 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
- type MemRegularFile
- func (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno
- func (f *MemRegularFile) Getattr(ctx context.Context, fh FileHandle, out *fuse.AttrOut) syscall.Errno
- func (f *MemRegularFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
- func (f *MemRegularFile) Read(ctx context.Context, fh FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
- func (f *MemRegularFile) Setattr(ctx context.Context, fh FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) syscall.Errno
- func (f *MemRegularFile) Write(ctx context.Context, fh FileHandle, data []byte, off int64) (uint32, syscall.Errno)
- type MemSymlink
- type NodeAccesser
- type NodeAllocater
- type NodeCopyFileRanger
- type NodeCreater
- type NodeFlusher
- type NodeFsyncer
- type NodeGetattrer
- type NodeGetlker
- type NodeGetxattrer
- type NodeLinker
- type NodeListxattrer
- type NodeLookuper
- type NodeLseeker
- type NodeMkdirer
- type NodeMknoder
- type NodeOnAdder
- type NodeOnForgetter
- type NodeOpendirHandler
- type NodeOpendirer
- type NodeOpener
- type NodeReaddirer
- type NodeReader
- type NodeReadlinker
- type NodeReleaser
- type NodeRemovexattrer
- type NodeRenamer
- type NodeRmdirer
- type NodeSetattrer
- type NodeSetlker
- type NodeSetlkwer
- type NodeSetxattrer
- type NodeStatfser
- type NodeStatxer
- type NodeSymlinker
- type NodeUnlinker
- type NodeWriter
- type Options
- type ServerCallbacks
- type StableAttr
Examples ¶
Constants ¶
const ENOATTR = xattr.ENOATTR
ENOATTR indicates that an extended attribute was not present.
const RENAME_EXCHANGE = 0x2
RENAME_EXCHANGE is a flag argument for renameat2()
Variables ¶
var OK = syscall.Errno(0)
OK is the Errno return value to indicate absense of errors.
Functions ¶
func Mount ¶
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.
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 ¶
NewListDirStream wraps a slice of DirEntry as a DirStream.
type FileAllocater ¶
type FileAllocater interface {
Allocate(ctx context.Context, off uint64, size uint64, mode uint32) syscall.Errno
}
See NodeAllocater.
type FileFlusher ¶
See NodeFlusher.
type FileFsyncdirer ¶ added in v2.6.0
FileFsyncer is a directory that supports fsyncdir.
type FileFsyncer ¶
See NodeFsync.
type FileGetattrer ¶
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 FilePassthroughFder ¶ added in v2.6.0
FilePassthroughFder is a file backed by a physical file. PassthroughFd should return an open file descriptor (and true), and the kernel will execute read/write operations directly on the backing file, bypassing the FUSE process. This function will be called once when processing the Create or Open operation, so there is no concern about concurrent access to the Fd. If the function returns false, passthrough will not be used for this file.
type FileReaddirenter ¶ added in v2.6.0
type FileReaddirenter interface { // Read a single directory entry. Readdirent(ctx context.Context) (*fuse.DirEntry, syscall.Errno) }
FileReaddirenter is a directory that supports reading.
type FileReader ¶
type FileReader interface {
Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno)
}
See NodeReader.
type FileReleasedirer ¶ added in v2.6.0
FileReleasedirer is a directory that supports a cleanup operation.
type FileReleaser ¶
See NodeReleaser.
type FileSeekdirer ¶ added in v2.6.0
FileSeekdirer is directory that supports seeking. `off` is an opaque uint64 value, where only the value 0 is reserved for the start of the stream. (See https://lwn.net/Articles/544520/ for background).
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 FileStatxer ¶ added in v2.7.0
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 ¶
AddChild adds a child to this node. If overwrite is false, fail if the destination already exists.
func (*Inode) EmbeddedInode ¶
func (*Inode) ExchangeChild ¶
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
deprecated
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.
Bugs: Forgotten() may momentarily return true in the window between creation (NewInode) and adding the node into the tree, which happens after Lookup/Mkdir/etc. return.
Deprecated: use NodeOnForgetter instead.
func (*Inode) GetChild ¶
GetChild returns a child node with the given name, or nil if the directory has no child by that name.
func (*Inode) MvChild ¶
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 ¶
NotifyContent notifies the kernel that content under the given inode should be flushed from buffers.
func (*Inode) NotifyDelete ¶
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 ¶
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 ¶
Parents returns a parent of this Inode, or nil if this Inode is deleted or is the root
func (*Inode) Path ¶
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) RmAllChildren ¶
func (n *Inode) RmAllChildren()
RmAllChildren recursively drops a tree, forgetting all persistent nodes.
func (*Inode) RmChild ¶
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) StableAttr ¶
func (n *Inode) StableAttr() StableAttr
StableAttr returns the (Ino, Gen) tuple for this node.
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) Getattr ¶ added in v2.1.0
func (n *LoopbackNode) Getattr(ctx context.Context, f FileHandle, out *fuse.AttrOut) syscall.Errno
func (*LoopbackNode) Link ¶ added in v2.1.0
func (n *LoopbackNode) Link(ctx context.Context, target InodeEmbedder, name string, 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) OpendirHandle ¶ added in v2.6.0
func (n *LoopbackNode) OpendirHandle(ctx context.Context, flags uint32) (FileHandle, uint32, syscall.Errno)
func (*LoopbackNode) Removexattr ¶ added in v2.1.0
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) 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) Statx ¶ added in v2.7.0
func (n *LoopbackNode) Statx(ctx context.Context, f FileHandle, flags uint32, mask uint32, out *fuse.StatxOut) 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 // RootNode is the root of the Loopback. This must be set if // the Loopback file system is not the root of the FUSE // mount. It is set automatically by NewLoopbackRoot. RootNode 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 (f *MemRegularFile) Flush(ctx context.Context, fh FileHandle) syscall.Errno
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)
type MemSymlink ¶
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
type NodeAccesser ¶
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 ¶
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 ¶
OnAdd is called when this InodeEmbedder is initialized.
type NodeOnForgetter ¶ added in v2.6.0
type NodeOnForgetter interface {
OnForget()
}
OnForget is called when the node becomes unreachable. This can happen because the kernel issues a FORGET request, ForgetPersistent() is called on the inode, or the last child of the directory disappears. Implementers must make sure that the inode cannot be revived concurrently by a LOOKUP call. Modifying the tree using RmChild and AddChild can also trigger a spurious OnForget; use MvChild instead.
type NodeOpendirHandler ¶ added in v2.6.0
type NodeOpendirHandler interface {
OpendirHandle(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, errno syscall.Errno)
}
Opens a directory. This supersedes NodeOpendirer, allowing to pass back flags (eg. FOPEN_CACHE_DIR).
type NodeOpendirer ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 NodeStatxer ¶ added in v2.7.0
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 ¶
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. // // This field shadows (and thus, is distinct) from // MountOptions.Logger. 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.
func (StableAttr) String ¶ added in v2.6.0
func (a StableAttr) String() string