Added fuse to vendor, with arm support Using genversion from repository Added filecontainer, and inode based caching
@@ -134,7 +134,7 @@ func (c *Core) parseFlags() (err error) {
func (c *Core) Mount() {
- // Start driveFS somehow
+ // Start Selected driveFS
c.CurrentFS.Start()
//////////////
// Server
@@ -1,59 +0,0 @@
-package main
-
-import (
- "flag"
- "fmt"
- "log"
- "os"
- "os/exec"
- "regexp"
- "strings"
- "time"
-)
-func main() {
- var pkg string
- var dst string
- flag.StringVar(&pkg, "package", "", "Set package name")
- flag.StringVar(&dst, "out", "", "Output file")
- flag.Parse()
- if pkg == "" {
- log.Println("Missing argument -package")
- flag.Usage()
- return
- }
- if dst == "" {
- log.Println("Missing argument -out")
- tag, err := exec.Command("git", "describe", "--tags").Output()
- if err != nil {
- log.Fatal("Error Getting tag", err)
- re := regexp.MustCompile(`\r?\n`)
- vtag := re.ReplaceAll(tag, []byte(" "))
- version := fmt.Sprintf("%s - built: %s", strings.TrimSpace(string(vtag)), time.Now().UTC().Format("2006-01-02 15:04:05 UTC"))
- fmt.Printf("Version: %s\n", version)
- f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
- fmt.Println("Error opening file", err)
- fmt.Fprintf(f, "package %s\n", pkg)
- fmt.Fprintln(f, "\nconst (")
- fmt.Fprintf(f, " //Version contains version of the package\n")
- fmt.Fprintf(f, " Version = \"%s\"\n", version)
- fmt.Fprintln(f, ")")
- f.Sync()
- f.Close()
-}
@@ -8,16 +8,88 @@ import (
"net/http"
"os"
"strings"
+ "sync"
"time"
"github.com/jacobsa/fuse/fuseops"
drive "google.golang.org/api/drive/v3"
)
+type FileContainer struct {
+ fileEntries map[fuseops.InodeID]*FileEntry
+ tree *FileEntry
+ fs *GDriveFS
+ uid uint32
+ gid uint32
+
+ inodeMU *sync.Mutex
+}
+func NewFileContainer(fs *GDriveFS) *FileContainer {
+ fc := &FileContainer{
+ fileEntries: map[fuseops.InodeID]*FileEntry{},
+ fs: fs,
+ inodeMU: &sync.Mutex{},
+ }
+ fc.tree = fc.FileEntry(fuseops.RootInodeID)
+ return fc
+func (fc *FileContainer) FindByInode(inode fuseops.InodeID) *FileEntry {
+ return fc.fileEntries[inode]
+//Return or create inode
+func (fc *FileContainer) FileEntry(inodeOps ...fuseops.InodeID) *FileEntry {
+ fc.inodeMU.Lock()
+ defer fc.inodeMU.Unlock()
+ var inode fuseops.InodeID
+ if len(inodeOps) > 0 {
+ inode = inodeOps[0]
+ if fe, ok := fc.fileEntries[inode]; ok {
+ return fe
+ } else { // generate new inode
+ // Max Inode Number
+ for inode = 2; inode < 99999; inode++ {
+ _, ok := fc.fileEntries[inode]
+ if !ok {
+ break
+ fe := &FileEntry{
+ Inode: inode,
+ container: fc,
+ //fs: fc.fs,
+ children: []*FileEntry{},
+ Attr: fuseops.InodeAttributes{},
+ fc.fileEntries[inode] = fe
+// RemoveEntry remove file entry
+func (fc *FileContainer) RemoveEntry(entry *FileEntry) {
+ for k, e := range fc.fileEntries {
+ if e == entry {
+ inode = k
+ delete(fc.fileEntries, inode)
//FileEntry entry to handle files
type FileEntry struct {
//parent *FileEntry
- fs *GDriveFS
+ container *FileContainer
+ //fs *GDriveFS
GFile *drive.File // GDrive file
isDir bool // Is dir
Name string // local name
@@ -70,8 +142,8 @@ func (fe *FileEntry) SetGFile(f *drive.File) {
attr.Size = uint64(f.Size)
//attr.Size = uint64(f.QuotaBytesUsed)
// Temp
- attr.Uid = fe.fs.core.Config.UID
- attr.Gid = fe.fs.core.Config.GID
+ attr.Uid = fe.container.uid
+ attr.Gid = fe.container.gid
attr.Crtime, _ = time.Parse(time.RFC3339, f.CreatedTime)
attr.Ctime = attr.Crtime // Set CTime to created, although it is change inode metadata
attr.Mtime, _ = time.Parse(time.RFC3339, f.ModifiedTime)
@@ -88,16 +160,16 @@ func (fe *FileEntry) SetGFile(f *drive.File) {
}
// Sync cached , upload to gdrive
func (fe *FileEntry) Sync() (err error) {
if fe.tempFile == nil {
return
fe.tempFile.Sync()
fe.tempFile.Seek(0, io.SeekStart)
ngFile := &drive.File{}
- up := fe.fs.client.Files.Update(fe.GFile.Id, ngFile)
+ up := fe.container.fs.client.Files.Update(fe.GFile.Id, ngFile)
upFile, err := up.Media(fe.tempFile).Do()
fe.SetGFile(upFile) // update local GFile entry
@@ -127,12 +199,12 @@ func (fe *FileEntry) Cache() *os.File {
switch fe.GFile.MimeType { // Make this somewhat optional
case "application/vnd.google-apps.document":
log.Println("Exporting as: text/markdown")
- res, err = fe.fs.client.Files.Export(fe.GFile.Id, "text/plain").Download()
+ res, err = fe.container.fs.client.Files.Export(fe.GFile.Id, "text/plain").Download()
case "application/vnd.google-apps.spreadsheet":
log.Println("Exporting as: text/csv")
- res, err = fe.fs.client.Files.Export(fe.GFile.Id, "text/csv").Download()
+ res, err = fe.container.fs.client.Files.Export(fe.GFile.Id, "text/csv").Download()
default:
- res, err = fe.fs.client.Files.Get(fe.GFile.Id).Download()
+ res, err = fe.container.fs.client.Files.Get(fe.GFile.Id).Download()
if err != nil {
@@ -202,10 +274,9 @@ func (fe *FileEntry) AppendGFile(f *drive.File, inode fuseops.InodeID) *FileEntr
//log.Println("Creating new file entry for name:", name, "for GFile:", f.Name)
// lock from find inode to fileList append
- entry := fe.fs.NewFileEntry()
+ entry := fe.container.FileEntry(inode)
entry.Name = name
entry.SetGFile(f)
- entry.Inode = inode
fe.AddChild(entry)
@@ -271,15 +342,3 @@ func (fe *FileEntry) FindByGID(gdriveID string, recurse bool) *FileEntry {
// For each child we findByInode
return nil
-func (fe *FileEntry) FindUnusedInode() fuseops.InodeID {
- var inode fuseops.InodeID
- for inode = 2; inode < 99999; inode++ {
- f := fe.FindByInode(inode, true)
- if f == nil {
- return inode
- log.Println("0 Inode ODD")
- return 0
@@ -4,6 +4,7 @@ package gdrivefs
import (
"io"
"syscall"
@@ -24,29 +25,29 @@ var (
log = prettylog.New("gdrivemount")
-type fileHandle struct {
- handleID fuseops.HandleID
+type Handle struct {
+ ID fuseops.HandleID
entry *FileEntry
uploadOnDone bool
// Handling for dir
entries []fuseutil.Dirent
-/*type DirEntry struct {
- file *FileEntry
-}*/
-// FuseHndler handler
+// GDriveFS
type GDriveFS struct {
fuseutil.NotImplementedFileSystem // Defaults
- core *cloudfs.Core // Core Config instead?
- client *drive.Service
- root *FileEntry // hiearchy reference
- fileHandles map[fuseops.HandleID]*fileHandle
+ core *cloudfs.Core // Core Config instead?
+ client *drive.Service
+ //root *FileEntry // hiearchy reference
+ root *FileContainer
+ fileHandles map[fuseops.HandleID]*Handle
nextRefresh time.Time
+ handleMU *sync.Mutex
//fileMap map[string]
// Map IDS with FileEntries
@@ -55,60 +56,58 @@ func New(core *cloudfs.Core) cloudfs.Driver {
fs := &GDriveFS{
core: core,
- fileHandles: map[fuseops.HandleID]*fileHandle{},
+ fileHandles: map[fuseops.HandleID]*Handle{},
+ handleMU: &sync.Mutex{},
fs.initClient() // Init Oauth2 client
+ fs.root = NewFileContainer(fs)
+ fs.root.uid = core.Config.UID
+ fs.root.gid = core.Config.GID
+ rootEntry := fs.root.FileEntry(fuseops.RootInodeID)
- rootEntry := fs.NewFileEntry()
rootEntry.Attr = fuseops.InodeAttributes{
Mode: os.FileMode(0755) | os.ModeDir,
Uid: core.Config.UID,
Gid: core.Config.GID,
- rootEntry.Inode = fuseops.RootInodeID
rootEntry.isDir = true
- fs.root = rootEntry
+ //fs.root = rootEntry
// Temporary entry
- entry := fs.root.AppendGFile(&drive.File{Id: "0", Name: "Loading..."}, 999999)
+ entry := fs.root.tree.AppendGFile(&drive.File{Id: "0", Name: "Loading..."}, 999999)
entry.Attr.Mode = os.FileMode(0)
return fs
func (fs *GDriveFS) Start() {
- fs.timedRefresh()
-func (fs *GDriveFS) NewFileEntry() *FileEntry {
- return &FileEntry{
- fs: fs,
- children: []*FileEntry{},
- Attr: fuseops.InodeAttributes{},
+ fs.timedRefresh() // start synching
////////////////////////////////////////////////////////
// TOOLS & HELPERS
-func (fs *GDriveFS) createHandle() *fileHandle {
+func (fs *GDriveFS) createHandle() *Handle {
// Lock here instead
+ fs.handleMU.Lock()
+ defer fs.handleMU.Unlock()
- var handle fuseops.HandleID
+ var handleID fuseops.HandleID
- for handle = 1; handle < 99999; handle++ {
- _, ok := fs.fileHandles[handle]
+ for handleID = 1; handleID < 99999; handleID++ {
+ _, ok := fs.fileHandles[handleID]
if !ok {
break
- fh := &fileHandle{handleID: handle}
- fs.fileHandles[handle] = fh
+ handle := &Handle{ID: handleID}
+ fs.fileHandles[handleID] = handle
- return fh
+ return handle
// Cache somewhere?
@@ -193,7 +192,7 @@ func (fs *GDriveFS) Refresh() {
// Create clean fileList
- root := fs.NewFileEntry()
+ root := NewFileContainer(fs)
// Helper func to recurse
// Everything loaded we add to our entries
@@ -211,17 +210,18 @@ func (fs *GDriveFS) Refresh() {
appendFile(parentFile) // Recurse
// Find existing entry
var inode fuseops.InodeID
- entry := fs.root.FindByGID(df.Id, true)
+ entry := fs.root.tree.FindByGID(df.Id, true)
if entry == nil {
- inode = root.FindUnusedInode()
+ inode = root.FileEntry().Inode // This can be a problem if next time a existing inode comes? Allocate new file entry with new Inode
} else {
- inode = entry.Inode
+ inode = entry.Inode //
- newEntry := root.solveAppendGFile(df, inode) // Find right parent
- if entry != nil && entry.GFile.Name == df.Name { // Copy name from old entry
+ newEntry := fs.root.tree.solveAppendGFile(df, inode) // Find right parent
+ if entry != nil && entry.GFile.Name == df.Name { // Copy name from old entry
newEntry.Name = entry.Name
@@ -233,22 +233,26 @@ func (fs *GDriveFS) Refresh() {
log.Println("Refresh done, update root")
- fs.root.children = root.children
+ //fs.root.children = root.children
- log.Println("File count:", fs.root.Count())
+ log.Println("File count:", fs.root.tree.Count())
+///////////////////////////////
+// Fuse operations
+////////////
// OpenDir return nil error allows open dir
func (fs *GDriveFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) (err error) {
- entry := fs.root.FindByInode(op.Inode, true)
+ entry := fs.root.FindByInode(op.Inode)
return fuse.ENOENT
- fh := fs.createHandle()
- fh.entry = entry
- op.Handle = fh.handleID
+ handle := fs.createHandle()
+ handle.entry = entry
+ op.Handle = handle.ID
return // No error allow, dir open
@@ -303,7 +307,7 @@ func (fs *GDriveFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInode
// Hack to truncate file?
if op.Size != nil {
- f := fs.root.FindByInode(op.Inode, true)
+ f := fs.root.FindByInode(op.Inode)
if *op.Size != 0 { // We only allow truncate to 0
return fuse.ENOSYS
@@ -323,7 +327,7 @@ func (fs *GDriveFS) SetInodeAttributes(ctx context.Context, op *fuseops.SetInode
//GetInodeAttributes return attributes
func (fs *GDriveFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
if f == nil {
@@ -342,7 +346,7 @@ func (fs *GDriveFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDir
// LookUpInode based on Parent and Name we return a self cached inode
func (fs *GDriveFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
- parentFile := fs.root.FindByInode(op.Parent, true) // true means transverse all
+ parentFile := fs.root.FindByInode(op.Parent) // true means transverse all
if parentFile == nil {
@@ -384,13 +388,13 @@ func (fs *GDriveFS) GetXAttr(ctx context.Context, op *fuseops.GetXattrOp) (err e
// OpenFile creates a temporary handle to be handled on read or write
func (fs *GDriveFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err error) {
- f := fs.root.FindByInode(op.Inode, true) // might not exists
+ f := fs.root.FindByInode(op.Inode) // might not exists
// Generate new handle
- fh.entry = f
+ handle.entry = f
op.UseDirectIO = true
@@ -398,9 +402,9 @@ func (fs *GDriveFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) (err e
// ReadFile if the first time we download the google drive file into a local temporary file
func (fs *GDriveFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err error) {
- lf := fs.fileHandles[op.Handle]
+ handle := fs.fileHandles[op.Handle]
- localFile := lf.entry.Cache()
+ localFile := handle.entry.Cache()
op.BytesRead, err = localFile.ReadAt(op.Dst, op.Offset)
if err == io.EOF { // fuse does not expect a EOF
err = nil
@@ -412,7 +416,7 @@ func (fs *GDriveFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) (err e
// CreateFile creates empty file in google Drive and returns its ID and attributes, only allows file creation on 'My Drive'
func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (err error) {
- parentFile := fs.root.FindByInode(op.Parent, true)
+ parentFile := fs.root.FindByInode(op.Parent)
@@ -426,38 +430,34 @@ func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (e
return fuse.EEXIST
- // Generate ID
- //genId, err := fs.client.Files.GenerateIds().Count(1).Do()
- //id := genId.Ids[0]
- parents := []string{parentFile.GFile.Id}
newFile := &drive.File{
- Parents: parents,
+ Parents: []string{parentFile.GFile.Id},
Name: op.Name,
createdFile, err := fs.client.Files.Create(newFile).Do()
err = fuse.EINVAL
- entry := parentFile.AppendGFile(createdFile, fs.root.FindUnusedInode()) // Add new created file
+ // Temp
+ entry := fs.root.FileEntry()
+ entry = parentFile.AppendGFile(createdFile, entry.Inode) // Add new created file
- localFile := entry.Cache()
- if localFile == nil {
- return fuse.EINVAL
// Associate a temp file to a new handle
// Local copy
// Lock
- fh.uploadOnDone = true
+ handle.uploadOnDone = true
//
op.Entry = fuseops.ChildInodeEntry{
Attributes: entry.Attr,
Child: entry.Inode,
@@ -472,12 +472,12 @@ func (fs *GDriveFS) CreateFile(ctx context.Context, op *fuseops.CreateFileOp) (e
// WriteFile as ReadFile it creates a temporary file on first read
// Maybe the ReadFile should be called here aswell to cache current contents since we are using writeAt
func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err error) {
- lf, ok := fs.fileHandles[op.Handle]
+ handle, ok := fs.fileHandles[op.Handle]
return fuse.EIO
if localFile == nil {
return fuse.EINVAL
@@ -487,22 +487,22 @@ func (fs *GDriveFS) WriteFile(ctx context.Context, op *fuseops.WriteFileOp) (err
err = fuse.EIO
- lf.uploadOnDone = true
// FlushFile just returns no error, maybe upload should be handled here
func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err error) {
- if lf.entry.tempFile == nil {
+ if handle.entry.tempFile == nil {
- if lf.uploadOnDone { // or if content changed basically
- err = lf.entry.Sync()
+ if handle.uploadOnDone { // or if content changed basically
+ err = handle.entry.Sync()
@@ -513,15 +513,10 @@ func (fs *GDriveFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) (err
// ReleaseFileHandle closes and deletes any temporary files, upload in case if changed locally
func (fs *GDriveFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) {
+ handle.entry.ClearCache()
- /*if lf.uploadOnDone {
- }*/
- lf.entry.ClearCache()
delete(fs.fileHandles, op.Handle)
@@ -529,7 +524,7 @@ func (fs *GDriveFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFi
// Unlink remove file and remove from local cache entry
func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error) {
- parentEntry := fs.root.FindByInode(op.Parent, true)
+ parentEntry := fs.root.FindByInode(op.Parent)
if parentEntry == nil {
@@ -546,6 +541,7 @@ func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error
+ fs.root.RemoveEntry(fileEntry)
parentEntry.RemoveChild(fileEntry)
@@ -554,7 +550,7 @@ func (fs *GDriveFS) Unlink(ctx context.Context, op *fuseops.UnlinkOp) (err error
// MkDir creates a directory on a parent dir
func (fs *GDriveFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error) {
@@ -571,7 +567,8 @@ func (fs *GDriveFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error)
return fuse.ENOATTR
- entry := parentFile.AppendGFile(fi, fs.root.FindUnusedInode())
+ entry = parentFile.AppendGFile(fi, entry.Inode)
@@ -589,7 +586,7 @@ func (fs *GDriveFS) MkDir(ctx context.Context, op *fuseops.MkDirOp) (err error)
// RmDir fuse implementation
func (fs *GDriveFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error) {
@@ -613,11 +610,11 @@ func (fs *GDriveFS) RmDir(ctx context.Context, op *fuseops.RmDirOp) (err error)
// Rename fuse implementation
func (fs *GDriveFS) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
- oldParentFile := fs.root.FindByInode(op.OldParent, true)
+ oldParentFile := fs.root.FindByInode(op.OldParent)
if oldParentFile == nil {
- newParentFile := fs.root.FindByInode(op.NewParent, true)
+ newParentFile := fs.root.FindByInode(op.NewParent)
if newParentFile == nil {
@@ -2,7 +2,8 @@
package main
-//go:generate go run cmd/genversion/main.go -package main -out version.go
+//go:generate go get dev.hexasoftware.com/hxs/genversion
+//go:generate genversion -package main -out version.go
"fmt"
@@ -14,7 +15,6 @@ import (
"dev.hexasoftware.com/hxs/cloudmount/cloudfs"
"dev.hexasoftware.com/hxs/cloudmount/fs/gdrivefs"
- //_ "github.com/icattlecoder/godaemon" // No reason
var (
@@ -25,6 +25,7 @@ var (
func main() {
prettylog.Global()
// getClient
fmt.Printf("%s-%s\n", Name, Version)
@@ -50,6 +51,8 @@ func main() {
cmd := exec.Command(os.Args[0], subArgs...)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
cmd.Start()
fmt.Println("[PID]", cmd.Process.Pid)
os.Exit(0)
@@ -0,0 +1,27 @@
+# Vim detritus
+.*.swp
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+# Folders
+_obj
+_test
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+_testmain.go
+*.exe
+*.test
+*.prof
@@ -0,0 +1,28 @@
+# Cf. http://docs.travis-ci.com/user/getting-started/
+# Cf. http://docs.travis-ci.com/user/languages/go/
+matrix:
+ include:
+ - os: linux
+ language: go
+ go: 1.8.1
+ # Use the virtualized Trusty beta Travis is running in order to get
+ # support for installing fuse.
+ #
+ # Cf. Personal communication from support@travis-ci.com.
+ dist: trusty
+ sudo: required
+ - os: osx
+# Install fuse before installing our code.
+before_install:
+ # For linux: install fuse.
+ - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
+ sudo apt-get install -qq fuse;
+ fi
+ # For macOS: update homebrew and then install osxfuse.
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew cask install osxfuse; fi
@@ -0,0 +1,300 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+ 1. Definitions.
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+ END OF TERMS AND CONDITIONS
+ APPENDIX: How to apply the Apache License to your work.
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+ Copyright {yyyy} {name of copyright owner}
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==========================================================================
+Portions of this package were adopted from bazil.org/fuse, which contains the
+following license notice.
+Copyright (c) 2013-2015 Tommi Virtanen.
+Copyright (c) 2009, 2011, 2012 The Go Authors.
+All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+The following included software components have additional copyright
+notices and license terms that may differ from the above.
+File fuse.go:
+// Adapted from Plan 9 from User Space's src/cmd/9pfuse/fuse.c,
+// which carries this notice:
+//
+// The files in this directory are subject to the following license.
+// The author of this software is Russ Cox.
+// Copyright (c) 2006 Russ Cox
+// Permission to use, copy, modify, and distribute this software for any
+// purpose without fee is hereby granted, provided that this entire notice
+// is included in all copies of any software which is or includes a copy
+// or modification of this software and in all copies of the supporting
+// documentation for such software.
+// THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+// WARRANTY. IN PARTICULAR, THE AUTHOR MAKES NO REPRESENTATION OR WARRANTY
+// OF ANY KIND CONCERNING THE MERCHANTABILITY OF THIS SOFTWARE OR ITS
+// FITNESS FOR ANY PARTICULAR PURPOSE.
+File fuse_kernel.go:
+// Derived from FUSE's fuse_kernel.h
+/*
+ This file defines the kernel interface of FUSE
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+ This -- and only this -- header file may also be distributed under
+ the terms of the BSD Licence as follows:
+ Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+*/
@@ -0,0 +1,30 @@
+[](https://godoc.org/github.com/jacobsa/fuse)
+This package allows for writing and mounting user-space file systems from Go.
+Install it as follows:
+ go get -u github.com/jacobsa/fuse
+Afterward, see the documentation for the following three packages:
+ * Package [fuse][] provides support for mounting a new file system and
+ reading requests from the kernel.
+ * Package [fuseops][] enumerates the supported requests from the kernel, and
+ provides documentation on their semantics.
+ * Package [fuseutil][], in particular the `FileSystem` interface, provides a
+ convenient way to create a file system type and export it to the kernel via
+ `fuse.Mount`.
+Make sure to also see the sub-packages of the [samples][] package for examples
+and tests.
+This package owes its inspiration and most of its kernel-related code to
+[bazil.org/fuse][bazil].
+[fuse]: http://godoc.org/github.com/jacobsa/fuse
+[fuseops]: http://godoc.org/github.com/jacobsa/fuse/fuseops
+[fuseutil]: http://godoc.org/github.com/jacobsa/fuse/fuseutil
+[samples]: http://godoc.org/github.com/jacobsa/fuse/samples
+[bazil]: http://godoc.org/bazil.org/fuse
@@ -0,0 +1,509 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package fuse
+import (
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path"
+ "runtime"
+ "syscall"
+ "golang.org/x/net/context"
+ "github.com/jacobsa/fuse/fuseops"
+ "github.com/jacobsa/fuse/internal/buffer"
+ "github.com/jacobsa/fuse/internal/freelist"
+ "github.com/jacobsa/fuse/internal/fusekernel"
+)
+type contextKeyType uint64
+var contextKey interface{} = contextKeyType(0)
+// Ask the Linux kernel for larger read requests.
+// As of 2015-03-26, the behavior in the kernel is:
+// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
+// ra_pages to be init_response->max_readahead divided by the page size.
+// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
+// backing_dev_info::ra_pages to the min of that value and what was sent
+// in the request's max_readahead field.
+// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
+// how much to read ahead.
+// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
+// Reading a page at a time is a drag. Ask for a larger size.
+const maxReadahead = 1 << 20
+// Connection represents a connection to the fuse kernel process. It is used to
+// receive and reply to requests from the kernel.
+type Connection struct {
+ cfg MountConfig
+ debugLogger *log.Logger
+ errorLogger *log.Logger
+ // The device through which we're talking to the kernel, and the protocol
+ // version that we're using to talk to it.
+ dev *os.File
+ protocol fusekernel.Protocol
+ mu sync.Mutex
+ // A map from fuse "unique" request ID (*not* the op ID for logging used
+ // above) to a function that cancel's its associated context.
+ //
+ // GUARDED_BY(mu)
+ cancelFuncs map[uint64]func()
+ // Freelists, serviced by freelists.go.
+ inMessages freelist.Freelist // GUARDED_BY(mu)
+ outMessages freelist.Freelist // GUARDED_BY(mu)
+// State that is maintained for each in-flight op. This is stuffed into the
+// context that the user uses to reply to the op.
+type opState struct {
+ inMsg *buffer.InMessage
+ outMsg *buffer.OutMessage
+ op interface{}
+// Create a connection wrapping the supplied file descriptor connected to the
+// kernel. You must eventually call c.close().
+// The loggers may be nil.
+func newConnection(
+ cfg MountConfig,
+ debugLogger *log.Logger,
+ errorLogger *log.Logger,
+ dev *os.File) (c *Connection, err error) {
+ c = &Connection{
+ cfg: cfg,
+ debugLogger: debugLogger,
+ errorLogger: errorLogger,
+ dev: dev,
+ cancelFuncs: make(map[uint64]func()),
+ // Initialize.
+ err = c.Init()
+ if err != nil {
+ c.close()
+ err = fmt.Errorf("Init: %v", err)
+ return
+// Init performs the work necessary to cause the mount process to complete.
+func (c *Connection) Init() (err error) {
+ // Read the init op.
+ ctx, op, err := c.ReadOp()
+ err = fmt.Errorf("Reading init op: %v", err)
+ initOp, ok := op.(*initOp)
+ c.Reply(ctx, syscall.EPROTO)
+ err = fmt.Errorf("Expected *initOp, got %T", op)
+ // Make sure the protocol version spoken by the kernel is new enough.
+ min := fusekernel.Protocol{
+ fusekernel.ProtoVersionMinMajor,
+ fusekernel.ProtoVersionMinMinor,
+ if initOp.Kernel.LT(min) {
+ err = fmt.Errorf("Version too old: %v", initOp.Kernel)
+ // Downgrade our protocol if necessary.
+ c.protocol = fusekernel.Protocol{
+ fusekernel.ProtoVersionMaxMajor,
+ fusekernel.ProtoVersionMaxMinor,
+ if initOp.Kernel.LT(c.protocol) {
+ c.protocol = initOp.Kernel
+ // Respond to the init op.
+ initOp.Library = c.protocol
+ initOp.MaxReadahead = maxReadahead
+ initOp.MaxWrite = buffer.MaxWriteSize
+ initOp.Flags = 0
+ // Tell the kernel not to use pitifully small 4 KiB writes.
+ initOp.Flags |= fusekernel.InitBigWrites
+ // Enable writeback caching if the user hasn't asked us not to.
+ if !c.cfg.DisableWritebackCaching {
+ initOp.Flags |= fusekernel.InitWritebackCache
+ c.Reply(ctx, nil)
+// Log information for an operation with the given ID. calldepth is the depth
+// to use when recovering file:line information with runtime.Caller.
+func (c *Connection) debugLog(
+ fuseID uint64,
+ calldepth int,
+ format string,
+ v ...interface{}) {
+ if c.debugLogger == nil {
+ // Get file:line info.
+ var file string
+ var line int
+ var ok bool
+ _, file, line, ok = runtime.Caller(calldepth)
+ file = "???"
+ fileLine := fmt.Sprintf("%v:%v", path.Base(file), line)
+ // Format the actual message to be printed.
+ msg := fmt.Sprintf(
+ "Op 0x%08x %24s] %v",
+ fuseID,
+ fileLine,
+ fmt.Sprintf(format, v...))
+ // Print it.
+ c.debugLogger.Println(msg)
+// LOCKS_EXCLUDED(c.mu)
+func (c *Connection) recordCancelFunc(
+ f func()) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if _, ok := c.cancelFuncs[fuseID]; ok {
+ panic(fmt.Sprintf("Already have cancel func for request %v", fuseID))
+ c.cancelFuncs[fuseID] = f
+// Set up state for an op that is about to be returned to the user, given its
+// underlying fuse opcode and request ID.
+// Return a context that should be used for the op.
+func (c *Connection) beginOp(
+ opCode uint32,
+ fuseID uint64) (ctx context.Context) {
+ // Start with the parent context.
+ ctx = c.cfg.OpContext
+ // Set up a cancellation function.
+ // Special case: On Darwin, osxfuse aggressively reuses "unique" request IDs.
+ // This matters for Forget requests, which have no reply associated and
+ // therefore have IDs that are immediately eligible for reuse. For these, we
+ // should not record any state keyed on their ID.
+ // Cf. https://github.com/osxfuse/osxfuse/issues/208
+ if opCode != fusekernel.OpForget {
+ var cancel func()
+ ctx, cancel = context.WithCancel(ctx)
+ c.recordCancelFunc(fuseID, cancel)
+// Clean up all state associated with an op to which the user has responded,
+// given its underlying fuse opcode and request ID. This must be called before
+// a response is sent to the kernel, to avoid a race where the request's ID
+// might be reused by osxfuse.
+func (c *Connection) finishOp(
+ fuseID uint64) {
+ // Even though the op is finished, context.WithCancel requires us to arrange
+ // for the cancellation function to be invoked. We also must remove it from
+ // our map.
+ // Special case: we don't do this for Forget requests. See the note in
+ // beginOp above.
+ cancel, ok := c.cancelFuncs[fuseID]
+ panic(fmt.Sprintf("Unknown request ID in finishOp: %v", fuseID))
+ cancel()
+ delete(c.cancelFuncs, fuseID)
+func (c *Connection) handleInterrupt(fuseID uint64) {
+ // NOTE(jacobsa): fuse.txt in the Linux kernel documentation
+ // (https://goo.gl/H55Dnr) defines the kernel <-> userspace protocol for
+ // interrupts.
+ // In particular, my reading of it is that an interrupt request cannot be
+ // delivered to userspace before the original request. The part about the
+ // race and EAGAIN appears to be aimed at userspace programs that
+ // concurrently process requests (cf. http://goo.gl/BES2rs).
+ // So in this method if we can't find the ID to be interrupted, it means that
+ // the request has already been replied to.
+ // Cf. http://comments.gmane.org/gmane.comp.file-systems.fuse.devel/14675
+// Read the next message from the kernel. The message must later be destroyed
+// using destroyInMessage.
+func (c *Connection) readMessage() (m *buffer.InMessage, err error) {
+ // Allocate a message.
+ m = c.getInMessage()
+ // Loop past transient errors.
+ for {
+ // Attempt a reaed.
+ err = m.Init(c.dev)
+ // Special cases:
+ // * ENODEV means fuse has hung up.
+ // * EINTR means we should try again. (This seems to happen often on
+ // OS X, cf. http://golang.org/issue/11180)
+ if pe, ok := err.(*os.PathError); ok {
+ switch pe.Err {
+ case syscall.ENODEV:
+ err = io.EOF
+ case syscall.EINTR:
+ err = nil
+ continue
+ c.putInMessage(m)
+ m = nil
+// Write the supplied message to the kernel.
+func (c *Connection) writeMessage(msg []byte) (err error) {
+ // Avoid the retry loop in os.File.Write.
+ n, err := syscall.Write(int(c.dev.Fd()), msg)
+ if n != len(msg) {
+ err = fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg))
+// ReadOp consumes the next op from the kernel process, returning the op and a
+// context that should be used for work related to the op. It returns io.EOF if
+// the kernel has closed the connection.
+// If err != nil, the user is responsible for later calling c.Reply with the
+// returned context.
+// This function delivers ops in exactly the order they are received from
+// /dev/fuse. It must not be called multiple times concurrently.
+func (c *Connection) ReadOp() (ctx context.Context, op interface{}, err error) {
+ // Keep going until we find a request we know how to convert.
+ // Read the next message from the kernel.
+ var inMsg *buffer.InMessage
+ inMsg, err = c.readMessage()
+ // Convert the message to an op.
+ outMsg := c.getOutMessage()
+ op, err = convertInMessage(inMsg, outMsg, c.protocol)
+ c.putOutMessage(outMsg)
+ err = fmt.Errorf("convertInMessage: %v", err)
+ // Choose an ID for this operation for the purposes of logging, and log it.
+ if c.debugLogger != nil {
+ c.debugLog(inMsg.Header().Unique, 1, "<- %s", describeRequest(op))
+ // Special case: handle interrupt requests inline.
+ if interruptOp, ok := op.(*interruptOp); ok {
+ c.handleInterrupt(interruptOp.FuseID)
+ // Set up a context that remembers information about this op.
+ ctx = c.beginOp(inMsg.Header().Opcode, inMsg.Header().Unique)
+ ctx = context.WithValue(ctx, contextKey, opState{inMsg, outMsg, op})
+ // Return the op to the user.
+// Skip errors that happen as a matter of course, since they spook users.
+func (c *Connection) shouldLogError(
+ op interface{},
+ err error) bool {
+ // We don't log non-errors.
+ if err == nil {
+ return false
+ // We can't log if there's nothing to log to.
+ if c.errorLogger == nil {
+ switch op.(type) {
+ case *fuseops.LookUpInodeOp:
+ // It is totally normal for the kernel to ask to look up an inode by name
+ // and find the name doesn't exist. For example, this happens when linking
+ // a new file.
+ if err == syscall.ENOENT {
+ case *fuseops.GetXattrOp:
+ if err == syscall.ENODATA || err == syscall.ERANGE {
+ case *unknownOp:
+ // Don't bother the user with methods we intentionally don't support.
+ if err == syscall.ENOSYS {
+ return true
+// Reply replies to an op previously read using ReadOp, with the supplied error
+// (or nil if successful). The context must be the context returned by ReadOp.
+func (c *Connection) Reply(ctx context.Context, opErr error) {
+ // Extract the state we stuffed in earlier.
+ var key interface{} = contextKey
+ foo := ctx.Value(key)
+ state, ok := foo.(opState)
+ panic(fmt.Sprintf("Reply called with invalid context: %#v", ctx))
+ op := state.op
+ inMsg := state.inMsg
+ outMsg := state.outMsg
+ fuseID := inMsg.Header().Unique
+ // Make sure we destroy the messages when we're done.
+ defer c.putInMessage(inMsg)
+ defer c.putOutMessage(outMsg)
+ // Clean up state for this op.
+ c.finishOp(inMsg.Header().Opcode, inMsg.Header().Unique)
+ // Debug logging
+ if opErr == nil {
+ c.debugLog(fuseID, 1, "-> OK (%s)", describeResponse(op))
+ } else {
+ c.debugLog(fuseID, 1, "-> Error: %q", opErr.Error())
+ // Error logging
+ if c.shouldLogError(op, opErr) {
+ c.errorLogger.Printf("%T error: %v", op, opErr)
+ // Send the reply to the kernel, if one is required.
+ noResponse := c.kernelResponse(outMsg, inMsg.Header().Unique, op, opErr)
+ if !noResponse {
+ err := c.writeMessage(outMsg.Bytes())
+ if err != nil && c.errorLogger != nil {
+ c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.Bytes())
+// Close the connection. Must not be called until operations that were read
+// from the connection have been responded to.
+func (c *Connection) close() (err error) {
+ // Posix doesn't say that close can be called concurrently with read or
+ // write, but luckily we exclude the possibility of a race by requiring the
+ // user to respond to all ops first.
+ err = c.dev.Close()
@@ -0,0 +1,890 @@
+ "bytes"
+ "errors"
+ "reflect"
+ "time"
+ "unsafe"
+////////////////////////////////////////////////////////////////////////
+// Incoming messages
+// Convert a kernel message to an appropriate op. If the op is unknown, a
+// special unexported type will be used.
+// The caller is responsible for arranging for the message to be destroyed.
+func convertInMessage(
+ inMsg *buffer.InMessage,
+ outMsg *buffer.OutMessage,
+ protocol fusekernel.Protocol) (o interface{}, err error) {
+ switch inMsg.Header().Opcode {
+ case fusekernel.OpLookup:
+ buf := inMsg.ConsumeBytes(inMsg.Len())
+ n := len(buf)
+ if n == 0 || buf[n-1] != '\x00' {
+ err = errors.New("Corrupt OpLookup")
+ o = &fuseops.LookUpInodeOp{
+ Parent: fuseops.InodeID(inMsg.Header().Nodeid),
+ Name: string(buf[:n-1]),
+ case fusekernel.OpGetattr:
+ o = &fuseops.GetInodeAttributesOp{
+ Inode: fuseops.InodeID(inMsg.Header().Nodeid),
+ case fusekernel.OpSetattr:
+ type input fusekernel.SetattrIn
+ in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
+ if in == nil {
+ err = errors.New("Corrupt OpSetattr")
+ to := &fuseops.SetInodeAttributesOp{
+ o = to
+ valid := fusekernel.SetattrValid(in.Valid)
+ if valid&fusekernel.SetattrSize != 0 {
+ to.Size = &in.Size
+ if valid&fusekernel.SetattrMode != 0 {
+ mode := convertFileMode(in.Mode)
+ to.Mode = &mode
+ if valid&fusekernel.SetattrAtime != 0 {
+ t := time.Unix(int64(in.Atime), int64(in.AtimeNsec))
+ to.Atime = &t
+ if valid&fusekernel.SetattrMtime != 0 {
+ t := time.Unix(int64(in.Mtime), int64(in.MtimeNsec))
+ to.Mtime = &t
+ case fusekernel.OpForget:
+ type input fusekernel.ForgetIn
+ err = errors.New("Corrupt OpForget")
+ o = &fuseops.ForgetInodeOp{
+ N: in.Nlookup,
+ case fusekernel.OpMkdir:
+ in := (*fusekernel.MkdirIn)(inMsg.Consume(fusekernel.MkdirInSize(protocol)))
+ err = errors.New("Corrupt OpMkdir")
+ name := inMsg.ConsumeBytes(inMsg.Len())
+ i := bytes.IndexByte(name, '\x00')
+ if i < 0 {
+ name = name[:i]
+ o = &fuseops.MkDirOp{
+ Name: string(name),
+ // On Linux, vfs_mkdir calls through to the inode with at most
+ // permissions and sticky bits set (cf. https://goo.gl/WxgQXk), and fuse
+ // passes that on directly (cf. https://goo.gl/f31aMo). In other words,
+ // the fact that this is a directory is implicit in the fact that the
+ // opcode is mkdir. But we want the correct mode to go through, so ensure
+ // that os.ModeDir is set.
+ Mode: convertFileMode(in.Mode) | os.ModeDir,
+ case fusekernel.OpMknod:
+ in := (*fusekernel.MknodIn)(inMsg.Consume(fusekernel.MknodInSize(protocol)))
+ err = errors.New("Corrupt OpMknod")
+ o = &fuseops.MkNodeOp{
+ Mode: convertFileMode(in.Mode),
+ case fusekernel.OpCreate:
+ in := (*fusekernel.CreateIn)(inMsg.Consume(fusekernel.CreateInSize(protocol)))
+ err = errors.New("Corrupt OpCreate")
+ o = &fuseops.CreateFileOp{
+ case fusekernel.OpSymlink:
+ // The message is "newName\0target\0".
+ names := inMsg.ConsumeBytes(inMsg.Len())
+ if len(names) == 0 || names[len(names)-1] != 0 {
+ err = errors.New("Corrupt OpSymlink")
+ i := bytes.IndexByte(names, '\x00')
+ newName, target := names[0:i], names[i+1:len(names)-1]
+ o = &fuseops.CreateSymlinkOp{
+ Name: string(newName),
+ Target: string(target),
+ case fusekernel.OpRename:
+ type input fusekernel.RenameIn
+ err = errors.New("Corrupt OpRename")
+ // names should be "old\x00new\x00"
+ if len(names) < 4 {
+ if names[len(names)-1] != '\x00' {
+ oldName, newName := names[:i], names[i+1:len(names)-1]
+ o = &fuseops.RenameOp{
+ OldParent: fuseops.InodeID(inMsg.Header().Nodeid),
+ OldName: string(oldName),
+ NewParent: fuseops.InodeID(in.Newdir),
+ NewName: string(newName),
+ case fusekernel.OpUnlink:
+ err = errors.New("Corrupt OpUnlink")
+ o = &fuseops.UnlinkOp{
+ case fusekernel.OpRmdir:
+ err = errors.New("Corrupt OpRmdir")
+ o = &fuseops.RmDirOp{
+ case fusekernel.OpOpen:
+ o = &fuseops.OpenFileOp{
+ case fusekernel.OpOpendir:
+ o = &fuseops.OpenDirOp{
+ case fusekernel.OpRead:
+ in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol)))
+ err = errors.New("Corrupt OpRead")
+ to := &fuseops.ReadFileOp{
+ Handle: fuseops.HandleID(in.Fh),
+ Offset: int64(in.Offset),
+ readSize := int(in.Size)
+ p := outMsg.GrowNoZero(readSize)
+ if p == nil {
+ err = fmt.Errorf("Can't grow for %d-byte read", readSize)
+ sh := (*reflect.SliceHeader)(unsafe.Pointer(&to.Dst))
+ sh.Data = uintptr(p)
+ sh.Len = readSize
+ sh.Cap = readSize
+ case fusekernel.OpReaddir:
+ err = errors.New("Corrupt OpReaddir")
+ to := &fuseops.ReadDirOp{
+ Offset: fuseops.DirOffset(in.Offset),
+ case fusekernel.OpRelease:
+ type input fusekernel.ReleaseIn
+ err = errors.New("Corrupt OpRelease")
+ o = &fuseops.ReleaseFileHandleOp{
+ case fusekernel.OpReleasedir:
+ err = errors.New("Corrupt OpReleasedir")
+ o = &fuseops.ReleaseDirHandleOp{
+ case fusekernel.OpWrite:
+ in := (*fusekernel.WriteIn)(inMsg.Consume(fusekernel.WriteInSize(protocol)))
+ err = errors.New("Corrupt OpWrite")
+ if len(buf) < int(in.Size) {
+ o = &fuseops.WriteFileOp{
+ Data: buf,
+ case fusekernel.OpFsync:
+ type input fusekernel.FsyncIn
+ err = errors.New("Corrupt OpFsync")
+ o = &fuseops.SyncFileOp{
+ case fusekernel.OpFlush:
+ type input fusekernel.FlushIn
+ err = errors.New("Corrupt OpFlush")
+ o = &fuseops.FlushFileOp{
+ case fusekernel.OpReadlink:
+ o = &fuseops.ReadSymlinkOp{
+ case fusekernel.OpStatfs:
+ o = &fuseops.StatFSOp{}
+ case fusekernel.OpInterrupt:
+ type input fusekernel.InterruptIn
+ err = errors.New("Corrupt OpInterrupt")
+ o = &interruptOp{
+ FuseID: in.Unique,
+ case fusekernel.OpInit:
+ type input fusekernel.InitIn
+ err = errors.New("Corrupt OpInit")
+ o = &initOp{
+ Kernel: fusekernel.Protocol{in.Major, in.Minor},
+ MaxReadahead: in.MaxReadahead,
+ Flags: fusekernel.InitFlags(in.Flags),
+ case fusekernel.OpRemovexattr:
+ err = errors.New("Corrupt OpRemovexattr")
+ o = &fuseops.RemoveXattrOp{
+ case fusekernel.OpGetxattr:
+ type input fusekernel.GetxattrIn
+ err = errors.New("Corrupt OpGetxattr")
+ to := &fuseops.GetXattrOp{
+ case fusekernel.OpListxattr:
+ type input fusekernel.ListxattrIn
+ err = errors.New("Corrupt OpListxattr")
+ to := &fuseops.ListXattrOp{
+ if readSize != 0 {
+ case fusekernel.OpSetxattr:
+ type input fusekernel.SetxattrIn
+ err = errors.New("Corrupt OpSetxattr")
+ payload := inMsg.ConsumeBytes(inMsg.Len())
+ // payload should be "name\x00value"
+ if len(payload) < 3 {
+ i := bytes.IndexByte(payload, '\x00')
+ name, value := payload[:i], payload[i+1:len(payload)]
+ o = &fuseops.SetXattrOp{
+ Value: value,
+ Flags: in.Flags,
+ default:
+ o = &unknownOp{
+ OpCode: inMsg.Header().Opcode,
+// Outgoing messages
+// Fill in the response that should be sent to the kernel, or set noResponse if
+// the op requires no response.
+func (c *Connection) kernelResponse(
+ m *buffer.OutMessage,
+ opErr error) (noResponse bool) {
+ h := m.OutHeader()
+ h.Unique = fuseID
+ // Special case: handle the ops for which the kernel expects no response.
+ // interruptOp .
+ case *fuseops.ForgetInodeOp:
+ noResponse = true
+ case *interruptOp:
+ // If the user returned the error, fill in the error field of the outgoing
+ // message header.
+ if opErr != nil {
+ handled := false
+ if opErr == syscall.ERANGE {
+ switch o := op.(type) {
+ writeXattrSize(m, uint32(o.BytesRead))
+ handled = true
+ case *fuseops.ListXattrOp:
+ if !handled {
+ m.OutHeader().Error = -int32(syscall.EIO)
+ if errno, ok := opErr.(syscall.Errno); ok {
+ m.OutHeader().Error = -int32(errno)
+ // Special case: for some types, convertInMessage grew the message in order
+ // to obtain a destination buffer. Make sure that we shrink back to just
+ // the header, because on OS X the kernel otherwise returns EINVAL when we
+ // attempt to write an error response with a length that extends beyond the
+ // header.
+ m.ShrinkTo(buffer.OutMessageHeaderSize)
+ // Otherwise, fill in the rest of the response.
+ c.kernelResponseForOp(m, op)
+ h.Len = uint32(m.Len())
+// Like kernelResponse, but assumes the user replied with a nil error to the
+// op.
+func (c *Connection) kernelResponseForOp(
+ op interface{}) {
+ // Create the appropriate output message
+ size := int(fusekernel.EntryOutSize(c.protocol))
+ out := (*fusekernel.EntryOut)(m.Grow(size))
+ convertChildInodeEntry(&o.Entry, out)
+ case *fuseops.GetInodeAttributesOp:
+ size := int(fusekernel.AttrOutSize(c.protocol))
+ out := (*fusekernel.AttrOut)(m.Grow(size))
+ out.AttrValid, out.AttrValidNsec = convertExpirationTime(
+ o.AttributesExpiration)
+ convertAttributes(o.Inode, &o.Attributes, &out.Attr)
+ case *fuseops.SetInodeAttributesOp:
+ case *fuseops.MkDirOp:
+ case *fuseops.MkNodeOp:
+ case *fuseops.CreateFileOp:
+ eSize := int(fusekernel.EntryOutSize(c.protocol))
+ e := (*fusekernel.EntryOut)(m.Grow(eSize))
+ convertChildInodeEntry(&o.Entry, e)
+ oo := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{}))))
+ oo.Fh = uint64(o.Handle)
+ case *fuseops.CreateSymlinkOp:
+ case *fuseops.RenameOp:
+ // Empty response
+ case *fuseops.RmDirOp:
+ case *fuseops.UnlinkOp:
+ case *fuseops.OpenDirOp:
+ out := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{}))))
+ out.Fh = uint64(o.Handle)
+ case *fuseops.ReadDirOp:
+ // convertInMessage already set up the destination buffer to be at the end
+ // of the out message. We need only shrink to the right size based on how
+ // much the user read.
+ m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
+ case *fuseops.ReleaseDirHandleOp:
+ case *fuseops.OpenFileOp:
+ if o.KeepPageCache {
+ out.OpenFlags |= uint32(fusekernel.OpenKeepCache)
+ if o.UseDirectIO {
+ out.OpenFlags |= uint32(fusekernel.OpenDirectIO)
+ case *fuseops.ReadFileOp:
+ case *fuseops.WriteFileOp:
+ out := (*fusekernel.WriteOut)(m.Grow(int(unsafe.Sizeof(fusekernel.WriteOut{}))))
+ out.Size = uint32(len(o.Data))
+ case *fuseops.SyncFileOp:
+ case *fuseops.FlushFileOp:
+ case *fuseops.ReleaseFileHandleOp:
+ case *fuseops.ReadSymlinkOp:
+ m.AppendString(o.Target)
+ case *fuseops.StatFSOp:
+ out := (*fusekernel.StatfsOut)(m.Grow(int(unsafe.Sizeof(fusekernel.StatfsOut{}))))
+ out.St.Blocks = o.Blocks
+ out.St.Bfree = o.BlocksFree
+ out.St.Bavail = o.BlocksAvailable
+ out.St.Files = o.Inodes
+ out.St.Ffree = o.InodesFree
+ // The posix spec for sys/statvfs.h (http://goo.gl/LktgrF) defines the
+ // following fields of statvfs, among others:
+ // f_bsize File system block size.
+ // f_frsize Fundamental file system block size.
+ // f_blocks Total number of blocks on file system in units of f_frsize.
+ // It appears as though f_bsize was the only thing supported by most unixes
+ // originally, but then f_frsize was added when new sorts of file systems
+ // came about. Quoth The Linux Programming Interface by Michael Kerrisk
+ // (https://goo.gl/5LZMxQ):
+ // For most Linux file systems, the values of f_bsize and f_frsize are
+ // the same. However, some file systems support the notion of block
+ // fragments, which can be used to allocate a smaller unit of storage
+ // at the end of the file if if a full block is not required. This
+ // avoids the waste of space that would otherwise occur if a full block
+ // was allocated. On such file systems, f_frsize is the size of a
+ // fragment, and f_bsize is the size of a whole block. (The notion of
+ // fragments in UNIX file systems first appeared in the early 1980s
+ // with the 4.2BSD Fast File System.)
+ // Confusingly, it appears as though osxfuse surfaces fuse_kstatfs::bsize
+ // as statfs::f_iosize (of advisory use only), and fuse_kstatfs::frsize as
+ // statfs::f_bsize (which affects free space display in the Finder).
+ out.St.Bsize = o.IoSize
+ out.St.Frsize = o.BlockSize
+ case *fuseops.RemoveXattrOp:
+ if o.BytesRead == 0 {
+ case *fuseops.SetXattrOp:
+ case *initOp:
+ out := (*fusekernel.InitOut)(m.Grow(int(unsafe.Sizeof(fusekernel.InitOut{}))))
+ out.Major = o.Library.Major
+ out.Minor = o.Library.Minor
+ out.MaxReadahead = o.MaxReadahead
+ out.Flags = uint32(o.Flags)
+ out.MaxWrite = o.MaxWrite
+ panic(fmt.Sprintf("Unexpected op: %#v", op))
+// General conversions
+func convertTime(t time.Time) (secs uint64, nsec uint32) {
+ totalNano := t.UnixNano()
+ secs = uint64(totalNano / 1e9)
+ nsec = uint32(totalNano % 1e9)
+func convertAttributes(
+ inodeID fuseops.InodeID,
+ in *fuseops.InodeAttributes,
+ out *fusekernel.Attr) {
+ out.Ino = uint64(inodeID)
+ out.Size = in.Size
+ out.Atime, out.AtimeNsec = convertTime(in.Atime)
+ out.Mtime, out.MtimeNsec = convertTime(in.Mtime)
+ out.Ctime, out.CtimeNsec = convertTime(in.Ctime)
+ out.SetCrtime(convertTime(in.Crtime))
+ out.Nlink = in.Nlink
+ out.Uid = in.Uid
+ out.Gid = in.Gid
+ // round up to the nearest 512 boundary
+ out.Blocks = (in.Size + 512 - 1) / 512
+ // Set the mode.
+ out.Mode = uint32(in.Mode) & 0777
+ switch {
+ out.Mode |= syscall.S_IFREG
+ case in.Mode&os.ModeDir != 0:
+ out.Mode |= syscall.S_IFDIR
+ case in.Mode&os.ModeDevice != 0:
+ if in.Mode&os.ModeCharDevice != 0 {
+ out.Mode |= syscall.S_IFCHR
+ out.Mode |= syscall.S_IFBLK
+ case in.Mode&os.ModeNamedPipe != 0:
+ out.Mode |= syscall.S_IFIFO
+ case in.Mode&os.ModeSymlink != 0:
+ out.Mode |= syscall.S_IFLNK
+ case in.Mode&os.ModeSocket != 0:
+ out.Mode |= syscall.S_IFSOCK
+// Convert an absolute cache expiration time to a relative time from now for
+// consumption by the fuse kernel module.
+func convertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
+ // Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
+ // counts of nanoseconds (cf. http://goo.gl/EJupJV). So negative durations
+ // are right out. There is no need to cap the positive magnitude, because
+ // 2^64 seconds is well longer than the 2^63 ns range of time.Duration.
+ d := t.Sub(time.Now())
+ if d > 0 {
+ secs = uint64(d / time.Second)
+ nsecs = uint32((d % time.Second) / time.Nanosecond)
+func convertChildInodeEntry(
+ in *fuseops.ChildInodeEntry,
+ out *fusekernel.EntryOut) {
+ out.Nodeid = uint64(in.Child)
+ out.Generation = uint64(in.Generation)
+ out.EntryValid, out.EntryValidNsec = convertExpirationTime(in.EntryExpiration)
+ out.AttrValid, out.AttrValidNsec = convertExpirationTime(in.AttributesExpiration)
+ convertAttributes(in.Child, &in.Attributes, &out.Attr)
+func convertFileMode(unixMode uint32) os.FileMode {
+ mode := os.FileMode(unixMode & 0777)
+ switch unixMode & syscall.S_IFMT {
+ case syscall.S_IFREG:
+ // nothing
+ case syscall.S_IFDIR:
+ mode |= os.ModeDir
+ case syscall.S_IFCHR:
+ mode |= os.ModeCharDevice | os.ModeDevice
+ case syscall.S_IFBLK:
+ mode |= os.ModeDevice
+ case syscall.S_IFIFO:
+ mode |= os.ModeNamedPipe
+ case syscall.S_IFLNK:
+ mode |= os.ModeSymlink
+ case syscall.S_IFSOCK:
+ mode |= os.ModeSocket
+ // no idea
+ if unixMode&syscall.S_ISUID != 0 {
+ mode |= os.ModeSetuid
+ if unixMode&syscall.S_ISGID != 0 {
+ mode |= os.ModeSetgid
+ return mode
+func writeXattrSize(m *buffer.OutMessage, size uint32) {
+ out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{}))))
+ out.Size = size
@@ -0,0 +1,129 @@
+ "strings"
+// Decide on the name of the given op.
+func opName(op interface{}) string {
+ // We expect all ops to be pointers.
+ t := reflect.TypeOf(op).Elem()
+ // Strip the "Op" from "FooOp".
+ return strings.TrimSuffix(t.Name(), "Op")
+func describeRequest(op interface{}) (s string) {
+ v := reflect.ValueOf(op).Elem()
+ // We will set up a comma-separated list of components.
+ var components []string
+ addComponent := func(format string, v ...interface{}) {
+ components = append(components, fmt.Sprintf(format, v...))
+ // Include an inode number, if available.
+ if f := v.FieldByName("Inode"); f.IsValid() {
+ addComponent("inode %v", f.Interface())
+ // Include a parent inode number, if available.
+ if f := v.FieldByName("Parent"); f.IsValid() {
+ addComponent("parent %v", f.Interface())
+ // Include a name, if available.
+ if f := v.FieldByName("Name"); f.IsValid() {
+ addComponent("name %q", f.Interface())
+ // Handle special cases.
+ switch typed := op.(type) {
+ addComponent("fuseid 0x%08x", typed.FuseID)
+ addComponent("opcode %d", typed.OpCode)
+ if typed.Size != nil {
+ addComponent("size %d", *typed.Size)
+ if typed.Mode != nil {
+ addComponent("mode %v", *typed.Mode)
+ if typed.Atime != nil {
+ addComponent("atime %v", *typed.Atime)
+ if typed.Mtime != nil {
+ addComponent("mtime %v", *typed.Mtime)
+ addComponent("handle %d", typed.Handle)
+ addComponent("offset %d", typed.Offset)
+ addComponent("%d bytes", len(typed.Dst))
+ addComponent("%d bytes", len(typed.Data))
+ addComponent("name %s", typed.Name)
+ // Use just the name if there is no extra info.
+ if len(components) == 0 {
+ return opName(op)
+ // Otherwise, include the extra info.
+ return fmt.Sprintf("%s (%s)", opName(op), strings.Join(components, ", "))
+func describeResponse(op interface{}) string {
+ // Include a resulting inode number, if available.
+ if f := v.FieldByName("Entry"); f.IsValid() {
+ if entry, ok := f.Interface().(fuseops.ChildInodeEntry); ok {
+ addComponent("inode %v", entry.Child)
+ return fmt.Sprintf("%s", strings.Join(components, ", "))
@@ -0,0 +1,36 @@
+// Package fuse enables writing and mounting user-space file systems.
+// The primary elements of interest are:
+// * The fuseops package, which defines the operations that fuse might send
+// to your userspace daemon.
+// * The Server interface, which your daemon must implement.
+// * fuseutil.NewFileSystemServer, which offers a convenient way to implement
+// the Server interface.
+// * Mount, a function that allows for mounting a Server as a file system.
+// Make sure to see the examples in the sub-packages of samples/, which double
+// as tests for this package: http://godoc.org/github.com/jacobsa/fuse/samples
+// In order to use this package to mount file systems on OS X, the system must
+// have FUSE for OS X installed (see http://osxfuse.github.io/). Do note that
+// there are several OS X-specific oddities; grep through the documentation for
+// more info.
+import "syscall"
+const (
+ // Errors corresponding to kernel error numbers. These may be treated
+ // specially by Connection.Reply.
+ EEXIST = syscall.EEXIST
+ EINVAL = syscall.EINVAL
+ EIO = syscall.EIO
+ ENOATTR = syscall.ENODATA
+ ENOENT = syscall.ENOENT
+ ENOSYS = syscall.ENOSYS
+ ENOTDIR = syscall.ENOTDIR
+ ENOTEMPTY = syscall.ENOTEMPTY
@@ -0,0 +1,70 @@
+// buffer.InMessage
+func (c *Connection) getInMessage() (x *buffer.InMessage) {
+ x = (*buffer.InMessage)(c.inMessages.Get())
+ c.mu.Unlock()
+ if x == nil {
+ x = new(buffer.InMessage)
+func (c *Connection) putInMessage(x *buffer.InMessage) {
+ c.inMessages.Put(unsafe.Pointer(x))
+// buffer.OutMessage
+func (c *Connection) getOutMessage() (x *buffer.OutMessage) {
+ x = (*buffer.OutMessage)(c.outMessages.Get())
+ x = new(buffer.OutMessage)
+ x.Reset()
+func (c *Connection) putOutMessage(x *buffer.OutMessage) {
+ c.outMessages.Put(unsafe.Pointer(x))
@@ -0,0 +1,23 @@
+package fsutil
+import "os"
+const FdatasyncSupported = false
+func fdatasync(f *os.File) error {
+ panic("We require FdatasyncSupported be true.")
@@ -0,0 +1,26 @@
+const FdatasyncSupported = true
+ return syscall.Fdatasync(int(f.Fd()))
@@ -0,0 +1,57 @@
+ "io/ioutil"
+// Create a temporary file with the same semantics as ioutil.TempFile, but
+// ensure that it is unlinked before returning so that it does not persist
+// after the process exits.
+// Warning: this is not production-quality code, and should only be used for
+// testing purposes. In particular, there is a race between creating and
+// unlinking by name.
+func AnonymousFile(dir string) (f *os.File, err error) {
+ // Choose a prefix based on the binary name.
+ prefix := path.Base(os.Args[0])
+ // Create the file.
+ f, err = ioutil.TempFile(dir, prefix)
+ err = fmt.Errorf("TempFile: %v", err)
+ // Unlink it.
+ err = os.Remove(f.Name())
+ err = fmt.Errorf("Remove: %v", err)
+// Call fdatasync on the supplied file.
+// REQUIRES: FdatasyncSupported is true.
+func Fdatasync(f *os.File) error {
+ return fdatasync(f)
@@ -0,0 +1,17 @@
+// Package fuseops contains ops that may be returned by fuse.Connection.ReadOp.
+// See documentation in that package for more.
+package fuseops
@@ -0,0 +1,847 @@
+// File system
+// Return statistics about the file system's capacity and available resources.
+// Called by statfs(2) and friends:
+// * (https://goo.gl/Xi1lDr) sys_statfs called user_statfs, which calls
+// vfs_statfs, which calls statfs_by_dentry.
+// * (https://goo.gl/VAIOwU) statfs_by_dentry calls the superblock
+// operation statfs, which in our case points at
+// fuse_statfs (cf. https://goo.gl/L7BTM3)
+// * (https://goo.gl/Zn7Sgl) fuse_statfs sends a statfs op, then uses
+// convert_fuse_statfs to convert the response in a straightforward
+// manner.
+// This op is particularly important on OS X: if you don't implement it, the
+// file system will not successfully mount. If you don't model a sane amount of
+// free space, the Finder will refuse to copy files into the file system.
+type StatFSOp struct {
+ // The size of the file system's blocks. This may be used, in combination
+ // with the block counts below, by callers of statfs(2) to infer the file
+ // system's capacity and space availability.
+ // On Linux this is surfaced as statfs::f_frsize, matching the posix standard
+ // (http://goo.gl/LktgrF), which says that f_blocks and friends are in units
+ // of f_frsize. On OS X this is surfaced as statfs::f_bsize, which plays the
+ // same roll.
+ // It appears as though the original intent of statvfs::f_frsize in the posix
+ // standard was to support a smaller addressable unit than statvfs::f_bsize
+ // (cf. The Linux Programming Interface by Michael Kerrisk,
+ // https://goo.gl/5LZMxQ). Therefore users should probably arrange for this
+ // to be no larger than IoSize.
+ // On Linux this can be any value, and will be faithfully returned to the
+ // caller of statfs(2) (see the code walk above). On OS X it appears that
+ // only powers of 2 in the range [2^7, 2^20] are preserved, and a value of
+ // zero is treated as 4096.
+ // This interface does not distinguish between blocks and block fragments.
+ BlockSize uint32
+ // The total number of blocks in the file system, the number of unused
+ // blocks, and the count of the latter that are available for use by non-root
+ // users.
+ // For each category, the corresponding number of bytes is derived by
+ // multiplying by BlockSize.
+ Blocks uint64
+ BlocksFree uint64
+ BlocksAvailable uint64
+ // The preferred size of writes to and reads from the file system, in bytes.
+ // This may affect clients that use statfs(2) to size buffers correctly. It
+ // does not appear to influence the size of writes sent from the kernel to
+ // the file system daemon.
+ // On Linux this is surfaced as statfs::f_bsize, and on OS X as
+ // statfs::f_iosize. Both are documented in `man 2 statfs` as "optimal
+ // transfer block size".
+ // On Linux this can be any value. On OS X it appears that only powers of 2
+ // in the range [2^12, 2^25] are faithfully preserved, and a value of zero is
+ // treated as 65536.
+ IoSize uint32
+ // The total number of inodes in the file system, and how many remain free.
+ Inodes uint64
+ InodesFree uint64
+// Inodes
+// Look up a child by name within a parent directory. The kernel sends this
+// when resolving user paths to dentry structs, which are then cached.
+type LookUpInodeOp struct {
+ // The ID of the directory inode to which the child belongs.
+ Parent InodeID
+ // The name of the child of interest, relative to the parent. For example, in
+ // this directory structure:
+ // foo/
+ // bar/
+ // baz
+ // the file system may receive a request to look up the child named "bar" for
+ // the parent foo/.
+ Name string
+ // The resulting entry. Must be filled out by the file system.
+ // The lookup count for the inode is implicitly incremented. See notes on
+ // ForgetInodeOp for more information.
+ Entry ChildInodeEntry
+// Refresh the attributes for an inode whose ID was previously returned in a
+// LookUpInodeOp. The kernel sends this when the FUSE VFS layer's cache of
+// inode attributes is stale. This is controlled by the AttributesExpiration
+// field of ChildInodeEntry, etc.
+type GetInodeAttributesOp struct {
+ // The inode of interest.
+ Inode InodeID
+ // Set by the file system: attributes for the inode, and the time at which
+ // they should expire. See notes on ChildInodeEntry.AttributesExpiration for
+ // more.
+ Attributes InodeAttributes
+ AttributesExpiration time.Time
+// Change attributes for an inode.
+// The kernel sends this for obvious cases like chmod(2), and for less obvious
+// cases like ftrunctate(2).
+type SetInodeAttributesOp struct {
+ // The attributes to modify, or nil for attributes that don't need a change.
+ Size *uint64
+ Mode *os.FileMode
+ Atime *time.Time
+ Mtime *time.Time
+ // Set by the file system: the new attributes for the inode, and the time at
+ // which they should expire. See notes on
+ // ChildInodeEntry.AttributesExpiration for more.
+// Decrement the reference count for an inode ID previously issued by the file
+// system.
+// The comments for the ops that implicitly increment the reference count
+// contain a note of this (but see also the note about the root inode below).
+// For example, LookUpInodeOp and MkDirOp. The authoritative source is the
+// libfuse documentation, which states that any op that returns
+// fuse_reply_entry fuse_reply_create implicitly increments (cf.
+// http://goo.gl/o5C7Dx).
+// If the reference count hits zero, the file system can forget about that ID
+// entirely, and even re-use it in future responses. The kernel guarantees that
+// it will not otherwise use it again.
+// The reference count corresponds to fuse_inode::nlookup
+// (http://goo.gl/ut48S4). Some examples of where the kernel manipulates it:
+// * (http://goo.gl/vPD9Oh) Any caller to fuse_iget increases the count.
+// * (http://goo.gl/B6tTTC) fuse_lookup_name calls fuse_iget.
+// * (http://goo.gl/IlcxWv) fuse_create_open calls fuse_iget.
+// * (http://goo.gl/VQMQul) fuse_dentry_revalidate increments after
+// revalidating.
+// In contrast to all other inodes, RootInodeID begins with an implicit
+// lookup count of one, without a corresponding op to increase it. (There
+// could be no such op, because the root cannot be referred to by name.) Code
+// walk:
+// * (http://goo.gl/gWAheU) fuse_fill_super calls fuse_get_root_inode.
+// * (http://goo.gl/AoLsbb) fuse_get_root_inode calls fuse_iget without
+// sending any particular request.
+// * (http://goo.gl/vPD9Oh) fuse_iget increments nlookup.
+// File systems should tolerate but not rely on receiving forget ops for
+// remaining inodes when the file system unmounts, including the root inode.
+// Rather they should take fuse.Connection.ReadOp returning io.EOF as
+// implicitly decrementing all lookup counts to zero.
+type ForgetInodeOp struct {
+ // The inode whose reference count should be decremented.
+ // The amount to decrement the reference count.
+ N uint64
+// Inode creation
+// Create a directory inode as a child of an existing directory inode. The
+// kernel sends this in response to a mkdir(2) call.
+// The Linux kernel appears to verify the name doesn't already exist (mkdir
+// calls mkdirat calls user_path_create calls filename_create, which verifies:
+// http://goo.gl/FZpLu5). Indeed, the tests in samples/memfs that call in
+// parallel appear to bear this out. But osxfuse does not appear to guarantee
+// this (cf. https://goo.gl/PqzZDv). And if names may be created outside of the
+// kernel's control, it doesn't matter what the kernel does anyway.
+// Therefore the file system should return EEXIST if the name already exists.
+type MkDirOp struct {
+ // The ID of parent directory inode within which to create the child.
+ // The name of the child to create, and the mode with which to create it.
+ Mode os.FileMode
+ // Set by the file system: information about the inode that was created.
+// Create a file inode as a child of an existing directory inode. The kernel
+// sends this in response to a mknod(2) call. It may also send it in special
+// cases such as an NFS export (cf. https://goo.gl/HiLfnK). It is more typical
+// to see CreateFileOp, which is received for an open(2) that creates a file.
+// The Linux kernel appears to verify the name doesn't already exist (mknod
+// calls sys_mknodat calls user_path_create calls filename_create, which
+// verifies: http://goo.gl/FZpLu5). But osxfuse may not guarantee this, as with
+// mkdir(2). And if names may be created outside of the kernel's control, it
+// doesn't matter what the kernel does anyway.
+type MkNodeOp struct {
+// Create a file inode and open it.
+// The kernel sends this when the user asks to open a file with the O_CREAT
+// flag and the kernel has observed that the file doesn't exist. (See for
+// example lookup_open, http://goo.gl/PlqE9d). However, osxfuse doesn't appear
+// to make this check atomically (cf. https://goo.gl/PqzZDv). And if names may
+// be created outside of the kernel's control, it doesn't matter what the
+// kernel does anyway.
+type CreateFileOp struct {
+ // The ID of parent directory inode within which to create the child file.
+ // Set by the file system: an opaque ID that will be echoed in follow-up
+ // calls for this file using the same struct file in the kernel. In practice
+ // this usually means follow-up calls using the file descriptor returned by
+ // open(2).
+ // The handle may be supplied in future ops like ReadFileOp that contain a
+ // file handle. The file system must ensure this ID remains valid until a
+ // later call to ReleaseFileHandle.
+ Handle HandleID
+// Create a symlink inode. If the name already exists, the file system should
+// return EEXIST (cf. the notes on CreateFileOp and MkDirOp).
+type CreateSymlinkOp struct {
+ // The ID of parent directory inode within which to create the child symlink.
+ // The name of the symlink to create.
+ // The target of the symlink.
+ Target string
+ // Set by the file system: information about the symlink inode that was
+ // created.
+// Unlinking
+// Rename a file or directory, given the IDs of the original parent directory
+// and the new one (which may be the same).
+// In Linux, this is called by vfs_rename (https://goo.gl/eERItT), which is
+// called by sys_renameat2 (https://goo.gl/fCC9qC).
+// The kernel takes care of ensuring that the source and destination are not
+// identical (in which case it does nothing), that the rename is not across
+// file system boundaries, and that the destination doesn't already exist with
+// the wrong type. Some subtleties that the file system must care about:
+// * If the new name is an existing directory, the file system must ensure it
+// is empty before replacing it, returning ENOTEMPTY otherwise. (This is
+// per the posix spec: http://goo.gl/4XtT79)
+// * The rename must be atomic from the point of view of an observer of the
+// new name. That is, if the new name already exists, there must be no
+// point at which it doesn't exist.
+// * It is okay for the new name to be modified before the old name is
+// removed; these need not be atomic. In fact, the Linux man page
+// explicitly says this is likely (cf. https://goo.gl/Y1wVZc).
+// * Linux bends over backwards (https://goo.gl/pLDn3r) to ensure that
+// neither the old nor the new parent can be concurrently modified. But
+// it's not clear whether OS X does this, and in any case it doesn't matter
+// for file systems that may be modified remotely. Therefore a careful file
+// system implementor should probably ensure if possible that the unlink
+// step in the "link new name, unlink old name" process doesn't unlink a
+// different inode than the one that was linked to the new name. Still,
+// posix and the man pages are imprecise about the actual semantics of a
+// rename if it's not atomic, so it is probably not disastrous to be loose
+// about this.
+type RenameOp struct {
+ // The old parent directory, and the name of the entry within it to be
+ // relocated.
+ OldParent InodeID
+ OldName string
+ // The new parent directory, and the name of the entry to be created or
+ // overwritten within it.
+ NewParent InodeID
+ NewName string
+// Unlink a directory from its parent. Because directories cannot have a link
+// count above one, this means the directory inode should be deleted as well
+// once the kernel sends ForgetInodeOp.
+// The file system is responsible for checking that the directory is empty.
+// Sample implementation in ext2: ext2_rmdir (http://goo.gl/B9QmFf)
+type RmDirOp struct {
+ // The ID of parent directory inode, and the name of the directory being
+ // removed within it.
+// Unlink a file or symlink from its parent. If this brings the inode's link
+// count to zero, the inode should be deleted once the kernel sends
+// ForgetInodeOp. It may still be referenced before then if a user still has
+// the file open.
+// Sample implementation in ext2: ext2_unlink (http://goo.gl/hY6r6C)
+type UnlinkOp struct {
+ // The ID of parent directory inode, and the name of the entry being removed
+ // within it.
+// Directory handles
+// Open a directory inode.
+// On Linux the sends this when setting up a struct file for a particular inode
+// with type directory, usually in response to an open(2) call from a
+// user-space process. On OS X it may not be sent for every open(2) (cf.
+// https://github.com/osxfuse/osxfuse/issues/199).
+type OpenDirOp struct {
+ // The ID of the inode to be opened.
+ // calls for this directory using the same struct file in the kernel. In
+ // practice this usually means follow-up calls using the file descriptor
+ // returned by open(2).
+ // The handle may be supplied in future ops like ReadDirOp that contain a
+ // directory handle. The file system must ensure this ID remains valid until
+ // a later call to ReleaseDirHandle.
+// Read entries from a directory previously opened with OpenDir.
+type ReadDirOp struct {
+ // The directory inode that we are reading, and the handle previously
+ // returned by OpenDir when opening that inode.
+ // The offset within the directory at which to read.
+ // Warning: this field is not necessarily a count of bytes. Its legal values
+ // are defined by the results returned in ReadDirResponse. See the notes
+ // below and the notes on that struct.
+ // In the Linux kernel this ultimately comes from file::f_pos, which starts
+ // at zero and is set by llseek and by the final consumed result returned by
+ // each call to ReadDir:
+ // * (http://goo.gl/2nWJPL) iterate_dir, which is called by getdents(2) and
+ // readdir(2), sets dir_context::pos to file::f_pos before calling
+ // f_op->iterate, and then does the opposite assignment afterward.
+ // * (http://goo.gl/rTQVSL) fuse_readdir, which implements iterate for fuse
+ // directories, passes dir_context::pos as the offset to fuse_read_fill,
+ // which passes it on to user-space. fuse_readdir later calls
+ // parse_dirfile with the same context.
+ // * (http://goo.gl/vU5ukv) For each returned result (except perhaps the
+ // last, which may be truncated by the page boundary), parse_dirfile
+ // updates dir_context::pos with fuse_dirent::off.
+ // It is affected by the Posix directory stream interfaces in the following
+ // manner:
+ // * (http://goo.gl/fQhbyn, http://goo.gl/ns1kDF) opendir initially causes
+ // filepos to be set to zero.
+ // * (http://goo.gl/ezNKyR, http://goo.gl/xOmDv0) readdir allows the user
+ // to iterate through the directory one entry at a time. As each entry is
+ // consumed, its d_off field is stored in __dirstream::filepos.
+ // * (http://goo.gl/WEOXG8, http://goo.gl/rjSXl3) telldir allows the user
+ // to obtain the d_off field from the most recently returned entry.
+ // * (http://goo.gl/WG3nDZ, http://goo.gl/Lp0U6W) seekdir allows the user
+ // to seek backward to an offset previously returned by telldir. It
+ // stores the new offset in filepos, and calls llseek to update the
+ // kernel's struct file.
+ // * (http://goo.gl/gONQhz, http://goo.gl/VlrQkc) rewinddir allows the user
+ // to go back to the beginning of the directory, obtaining a fresh view.
+ // It updates filepos and calls llseek to update the kernel's struct
+ // file.
+ // Unfortunately, FUSE offers no way to intercept seeks
+ // (http://goo.gl/H6gEXa), so there is no way to cause seekdir or rewinddir
+ // to fail. Additionally, there is no way to distinguish an explicit
+ // rewinddir followed by readdir from the initial readdir, or a rewinddir
+ // from a seekdir to the value returned by telldir just after opendir.
+ // Luckily, Posix is vague about what the user will see if they seek
+ // backwards, and requires the user not to seek to an old offset after a
+ // rewind. The only requirement on freshness is that rewinddir results in
+ // something that looks like a newly-opened directory. So FUSE file systems
+ // may e.g. cache an entire fresh listing for each ReadDir with a zero
+ // offset, and return array offsets into that cached listing.
+ Offset DirOffset
+ // The destination buffer, whose length gives the size of the read.
+ // The output data should consist of a sequence of FUSE directory entries in
+ // the format generated by fuse_add_direntry (http://goo.gl/qCcHCV), which is
+ // consumed by parse_dirfile (http://goo.gl/2WUmD2). Use fuseutil.WriteDirent
+ // to generate this data.
+ // Each entry returned exposes a directory offset to the user that may later
+ // show up in ReadDirRequest.Offset. See notes on that field for more
+ // information.
+ Dst []byte
+ // Set by the file system: the number of bytes read into Dst.
+ // It is okay for this to be less than len(Dst) if there are not enough
+ // entries available or the final entry would not fit.
+ // Zero means that the end of the directory has been reached. This is
+ // unambiguous because NAME_MAX (https://goo.gl/ZxzKaE) plus the size of
+ // fuse_dirent (https://goo.gl/WO8s3F) plus the 8-byte alignment of
+ // FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH) is less than the read size of
+ // PAGE_SIZE used by fuse_readdir (cf. https://goo.gl/VajtS2).
+ BytesRead int
+// Release a previously-minted directory handle. The kernel sends this when
+// there are no more references to an open directory: all file descriptors are
+// closed and all memory mappings are unmapped.
+// The kernel guarantees that the handle ID will not be used in further ops
+// sent to the file system (unless it is reissued by the file system).
+// Errors from this op are ignored by the kernel (cf. http://goo.gl/RL38Do).
+type ReleaseDirHandleOp struct {
+ // The handle ID to be released. The kernel guarantees that this ID will not
+ // be used in further calls to the file system (unless it is reissued by the
+ // file system).
+// File handles
+// Open a file inode.
+// with type file, usually in response to an open(2) call from a user-space
+// process. On OS X it may not be sent for every open(2)
+// (cf.https://github.com/osxfuse/osxfuse/issues/199).
+type OpenFileOp struct {
+ // An opaque ID that will be echoed in follow-up calls for this file using
+ // the same struct file in the kernel. In practice this usually means
+ // follow-up calls using the file descriptor returned by open(2).
+ // By default, fuse invalidates the kernel's page cache for an inode when a
+ // new file handle is opened for that inode (cf. https://goo.gl/2rZ9uk). The
+ // intent appears to be to allow users to "see" content that has changed
+ // remotely on a networked file system by re-opening the file.
+ // For file systems where this is not a concern because all modifications for
+ // a particular inode go through the kernel, set this field to true to
+ // disable this behavior.
+ // (More discussion: http://goo.gl/cafzWF)
+ // Note that on OS X it appears that the behavior is always as if this field
+ // is set to true, regardless of its value, at least for files opened in the
+ // same mode. (Cf. https://github.com/osxfuse/osxfuse/issues/223)
+ KeepPageCache bool
+ // Whether to use direct IO for this file handle. By default, the kernel
+ // suppresses what it sees as redundant operations (including reads beyond
+ // the precomputed EOF).
+ // Enabling direct IO ensures that all client operations reach the fuse
+ // layer. This allows for filesystems whose file sizes are not known in
+ // advance, for example, because contents are generated on the fly.
+ UseDirectIO bool
+// Read data from a file previously opened with CreateFile or OpenFile.
+// Note that this op is not sent for every call to read(2) by the end user;
+// some reads may be served by the page cache. See notes on WriteFileOp for
+// more.
+type ReadFileOp struct {
+ // The file inode that we are reading, and the handle previously returned by
+ // CreateFile or OpenFile when opening that inode.
+ // The offset within the file at which to read.
+ Offset int64
+ // Set by the file system: the number of bytes read.
+ // The FUSE documentation requires that exactly the requested number of bytes
+ // be returned, except in the case of EOF or error (http://goo.gl/ZgfBkF).
+ // This appears to be because it uses file mmapping machinery
+ // (http://goo.gl/SGxnaN) to read a page at a time. It appears to understand
+ // where EOF is by checking the inode size (http://goo.gl/0BkqKD), returned
+ // by a previous call to LookUpInode, GetInodeAttributes, etc.
+ // If direct IO is enabled, semantics should match those of read(2).
+// Write data to a file previously opened with CreateFile or OpenFile.
+// When the user writes data using write(2), the write goes into the page
+// cache and the page is marked dirty. Later the kernel may write back the
+// page via the FUSE VFS layer, causing this op to be sent:
+// * The kernel calls address_space_operations::writepage when a dirty page
+// needs to be written to backing store (cf. http://goo.gl/Ezbewg). Fuse
+// sets this to fuse_writepage (cf. http://goo.gl/IeNvLT).
+// * (http://goo.gl/Eestuy) fuse_writepage calls fuse_writepage_locked.
+// * (http://goo.gl/RqYIxY) fuse_writepage_locked makes a write request to
+// the userspace server.
+// Note that the kernel *will* ensure that writes are received and acknowledged
+// by the file system before sending a FlushFileOp when closing the file
+// descriptor to which they were written. Cf. the notes on
+// fuse.MountConfig.DisableWritebackCaching.
+// (See also http://goo.gl/ocdTdM, fuse-devel thread "Fuse guarantees on
+// concurrent requests".)
+type WriteFileOp struct {
+ // The file inode that we are modifying, and the handle previously returned
+ // by CreateFile or OpenFile when opening that inode.
+ // The offset at which to write the data below.
+ // The man page for pwrite(2) implies that aside from changing the file
+ // handle's offset, using pwrite is equivalent to using lseek(2) and then
+ // write(2). The man page for lseek(2) says the following:
+ // "The lseek() function allows the file offset to be set beyond the end of
+ // the file (but this does not change the size of the file). If data is later
+ // written at this point, subsequent reads of the data in the gap (a "hole")
+ // return null bytes (aq\0aq) until data is actually written into the gap."
+ // It is therefore reasonable to assume that the kernel is looking for
+ // the following semantics:
+ // * If the offset is less than or equal to the current size, extend the
+ // file as necessary to fit any data that goes past the end of the file.
+ // * If the offset is greater than the current size, extend the file
+ // with null bytes until it is not, then do the above.
+ // The data to write.
+ // The FUSE documentation requires that exactly the number of bytes supplied
+ // be written, except on error (http://goo.gl/KUpwwn). This appears to be
+ // because it uses file mmapping machinery (http://goo.gl/SGxnaN) to write a
+ // page at a time.
+ Data []byte
+// Synchronize the current contents of an open file to storage.
+// vfs.txt documents this as being called for by the fsync(2) system call
+// (cf. http://goo.gl/j9X8nB). Code walk for that case:
+// * (http://goo.gl/IQkWZa) sys_fsync calls do_fsync, calls vfs_fsync, calls
+// vfs_fsync_range.
+// * (http://goo.gl/5L2SMy) vfs_fsync_range calls f_op->fsync.
+// Note that this is also sent by fdatasync(2) (cf. http://goo.gl/01R7rF), and
+// may be sent for msync(2) with the MS_SYNC flag (see the notes on
+// FlushFileOp).
+// See also: FlushFileOp, which may perform a similar function when closing a
+// file (but which is not used in "real" file systems).
+type SyncFileOp struct {
+ // The file and handle being sync'd.
+// Flush the current state of an open file to storage upon closing a file
+// descriptor.
+// vfs.txt documents this as being sent for each close(2) system call (cf.
+// http://goo.gl/FSkbrq). Code walk for that case:
+// * (http://goo.gl/e3lv0e) sys_close calls __close_fd, calls filp_close.
+// * (http://goo.gl/nI8fxD) filp_close calls f_op->flush (fuse_flush).
+// But note that this is also sent in other contexts where a file descriptor is
+// closed, such as dup2(2) (cf. http://goo.gl/NQDvFS). In the case of close(2),
+// a flush error is returned to the user. For dup2(2), it is not.
+// One potentially significant case where this may not be sent is mmap'd files,
+// where the behavior is complicated:
+// * munmap(2) does not cause flushes (cf. http://goo.gl/j8B9g0).
+// * On OS X, if a user modifies a mapped file via the mapping before
+// closing the file with close(2), the WriteFileOps for the modifications
+// may not be received before the FlushFileOp for the close(2) (cf.
+// https://github.com/osxfuse/osxfuse/issues/202). It appears that this may
+// be fixed in osxfuse 3 (cf. https://goo.gl/rtvbko).
+// * However, you safely can arrange for writes via a mapping to be
+// flushed by calling msync(2) followed by close(2). On OS X msync(2)
+// will cause a WriteFileOps to go through and close(2) will cause a
+// FlushFile as usual (cf. http://goo.gl/kVmNcx). On Linux, msync(2) does
+// nothing unless you set the MS_SYNC flag, in which case it causes a
+// SyncFileOp to be sent (cf. http://goo.gl/P3mErk).
+// In summary: if you make data durable in both FlushFile and SyncFile, then
+// your users can get safe behavior from mapped files on both operating systems
+// by calling msync(2) with MS_SYNC, followed by munmap(2), followed by
+// close(2). On Linux, the msync(2) is optional (cf. http://goo.gl/EIhAxv and
+// the notes on WriteFileOp).
+// Because of cases like dup2(2), FlushFileOps are not necessarily one to one
+// with OpenFileOps. They should not be used for reference counting, and the
+// handle must remain valid even after the flush op is received (use
+// ReleaseFileHandleOp for disposing of it).
+// Typical "real" file systems do not implement this, presumably relying on
+// the kernel to write out the page cache to the block device eventually.
+// They can get away with this because a later open(2) will see the same
+// data. A file system that writes to remote storage however probably wants
+// to at least schedule a real flush, and maybe do it immediately in order to
+// return any errors that occur.
+type FlushFileOp struct {
+ // The file and handle being flushed.
+// Release a previously-minted file handle. The kernel calls this when there
+// are no more references to an open file: all file descriptors are closed
+// and all memory mappings are unmapped.
+// The kernel guarantees that the handle ID will not be used in further calls
+// to the file system (unless it is reissued by the file system).
+type ReleaseFileHandleOp struct {
+// Reading symlinks
+// Read the target of a symlink inode.
+type ReadSymlinkOp struct {
+ // The symlink inode that we are reading.
+ // Set by the file system: the target of the symlink.
+// eXtended attributes
+// Remove an extended attribute.
+// This is sent in response to removexattr(2). Return ENOATTR if the
+// extended attribute does not exist.
+type RemoveXattrOp struct {
+ // The inode that we are removing an extended attribute from.
+ // The name of the extended attribute.
+// Get an extended attribute.
+// This is sent in response to getxattr(2). Return ENOATTR if the
+type GetXattrOp struct {
+ // The inode whose extended attribute we are reading.
+ // The destination buffer. If the size is too small for the
+ // value, the ERANGE error should be sent.
+ // Set by the file system: the number of bytes read into Dst, or
+ // the number of bytes that would have been read into Dst if Dst was
+ // big enough (return ERANGE in this case).
+// List all the extended attributes for a file.
+// This is sent in response to listxattr(2).
+type ListXattrOp struct {
+ // The inode whose extended attributes we are listing.
+ // The output data should consist of a sequence of NUL-terminated strings,
+ // one for each xattr.
+// Set an extended attribute.
+// This is sent in response to setxattr(2). Return ENOSPC if there is
+// insufficient space remaining to store the extended attribute.
+type SetXattrOp struct {
+ // The inode whose extended attribute we are setting.
+ // The name of the extended attribute
+ // The value to for the extened attribute.
+ Value []byte
+ // If Flags is 0x1, and the attribute exists already, EEXIST should be returned.
+ // If Flags is 0x2, and the attribute does not exist, ENOATTR should be returned.
+ // If Flags is 0x0, the extended attribute will be created if need be, or will
+ // simply replace the value if the attribute exists.
+ Flags uint32
@@ -0,0 +1,222 @@
+// InodeID is a 64-bit number used to uniquely identify a file or directory in
+// the file system. File systems may mint inode IDs with any value except for
+// RootInodeID.
+// This corresponds to struct inode::i_no in the VFS layer.
+// (Cf. http://goo.gl/tvYyQt)
+type InodeID uint64
+// RootInodeID is a distinguished inode ID that identifies the root of the file
+// system, e.g. in an OpenDirOp or LookUpInodeOp. Unlike all other inode IDs,
+// which are minted by the file system, the FUSE VFS layer may send a request
+// for this ID without the file system ever having referenced it in a previous
+// response.
+const RootInodeID = 1
+func init() {
+ // Make sure the constant above is correct. We do this at runtime rather than
+ // defining the constant in terms of fusekernel.RootID for two reasons:
+ // 1. Users can more clearly see that the root ID is low and can therefore
+ // be used as e.g. an array index, with space reserved up to the root.
+ // 2. The constant can be untyped and can therefore more easily be used as
+ // an array index.
+ if RootInodeID != fusekernel.RootID {
+ panic(
+ fmt.Sprintf(
+ "Oops, RootInodeID is wrong: %v vs. %v",
+ RootInodeID,
+ fusekernel.RootID))
+// InodeAttributes contains attributes for a file or directory inode. It
+// corresponds to struct inode (cf. http://goo.gl/tvYyQt).
+type InodeAttributes struct {
+ Size uint64
+ // The number of incoming hard links to this inode.
+ Nlink uint32
+ // The mode of the inode. This is exposed to the user in e.g. the result of
+ // fstat(2).
+ // Note that in contrast to the defaults for FUSE, this package mounts file
+ // systems in a manner such that the kernel checks inode permissions in the
+ // standard posix way. This is implemented by setting the default_permissions
+ // mount option (cf. http://goo.gl/1LxOop and http://goo.gl/1pTjuk).
+ // For example, in the case of mkdir:
+ // * (http://goo.gl/JkdxDI) sys_mkdirat calls inode_permission.
+ // * (...) inode_permission eventually calls do_inode_permission.
+ // * (http://goo.gl/aGCsmZ) calls i_op->permission, which is
+ // fuse_permission (cf. http://goo.gl/VZ9beH).
+ // * (http://goo.gl/5kqUKO) fuse_permission doesn't do anything at all for
+ // several code paths if FUSE_DEFAULT_PERMISSIONS is unset. In contrast,
+ // if that flag *is* set, then it calls generic_permission.
+ // Time information. See `man 2 stat` for full details.
+ Atime time.Time // Time of last access
+ Mtime time.Time // Time of last modification
+ Ctime time.Time // Time of last modification to inode
+ Crtime time.Time // Time of creation (OS X only)
+ // Ownership information
+ Uid uint32
+ Gid uint32
+func (a *InodeAttributes) DebugString() string {
+ return fmt.Sprintf(
+ "%d %d %v %d %d",
+ a.Size,
+ a.Nlink,
+ a.Mode,
+ a.Uid,
+ a.Gid)
+// GenerationNumber represents a generation of an inode. It is irrelevant for
+// file systems that won't be exported over NFS. For those that will and that
+// reuse inode IDs when they become free, the generation number must change
+// when an ID is reused.
+// This corresponds to struct inode::i_generation in the VFS layer.
+// Some related reading:
+// http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html
+// http://stackoverflow.com/q/11071996/1505451
+// http://goo.gl/CqvwyX
+// http://julipedia.meroh.net/2005/09/nfs-file-handles.html
+// http://goo.gl/wvo3MB
+type GenerationNumber uint64
+// HandleID is an opaque 64-bit number used to identify a particular open
+// handle to a file or directory.
+// This corresponds to fuse_file_info::fh.
+type HandleID uint64
+// DirOffset is an offset into an open directory handle. This is opaque to
+// FUSE, and can be used for whatever purpose the file system desires. See
+// notes on ReadDirOp.Offset for details.
+type DirOffset uint64
+// ChildInodeEntry contains information about a child inode within its parent
+// directory. It is shared by LookUpInodeOp, MkDirOp, CreateFileOp, etc, and is
+// consumed by the kernel in order to set up a dcache entry.
+type ChildInodeEntry struct {
+ // The ID of the child inode. The file system must ensure that the returned
+ // inode ID remains valid until a later ForgetInodeOp.
+ Child InodeID
+ // A generation number for this incarnation of the inode with the given ID.
+ // See comments on type GenerationNumber for more.
+ Generation GenerationNumber
+ // Current attributes for the child inode.
+ // When creating a new inode, the file system is responsible for initializing
+ // and recording (where supported) attributes like time information,
+ // ownership information, etc.
+ // Ownership information in particular must be set to something reasonable or
+ // by default root will own everything and unprivileged users won't be able
+ // to do anything useful. In traditional file systems in the kernel, the
+ // function inode_init_owner (http://goo.gl/5qavg8) contains the
+ // standards-compliant logic for this.
+ // The FUSE VFS layer in the kernel maintains a cache of file attributes,
+ // used whenever up to date information about size, mode, etc. is needed.
+ // For example, this is the abridged call chain for fstat(2):
+ // * (http://goo.gl/tKBH1p) fstat calls vfs_fstat.
+ // * (http://goo.gl/3HeITq) vfs_fstat eventuall calls vfs_getattr_nosec.
+ // * (http://goo.gl/DccFQr) vfs_getattr_nosec calls i_op->getattr.
+ // * (http://goo.gl/dpKkst) fuse_getattr calls fuse_update_attributes.
+ // * (http://goo.gl/yNlqPw) fuse_update_attributes uses the values in the
+ // struct inode if allowed, otherwise calling out to the user-space code.
+ // In addition to obvious cases like fstat, this is also used in more subtle
+ // cases like updating size information before seeking (http://goo.gl/2nnMFa)
+ // or reading (http://goo.gl/FQSWs8).
+ // Most 'real' file systems do not set inode_operations::getattr, and
+ // therefore vfs_getattr_nosec calls generic_fillattr which simply grabs the
+ // information from the inode struct. This makes sense because these file
+ // systems cannot spontaneously change; all modifications go through the
+ // kernel which can update the inode struct as appropriate.
+ // In contrast, a FUSE file system may have spontaneous changes, so it calls
+ // out to user space to fetch attributes. However this is expensive, so the
+ // FUSE layer in the kernel caches the attributes if requested.
+ // This field controls when the attributes returned in this response and
+ // stashed in the struct inode should be re-queried. Leave at the zero value
+ // to disable caching.
+ // More reading:
+ // http://stackoverflow.com/q/21540315/1505451
+ // The time until which the kernel may maintain an entry for this name to
+ // inode mapping in its dentry cache. After this time, it will revalidate the
+ // dentry.
+ // As in the discussion of attribute caching above, unlike real file systems,
+ // FUSE file systems may spontaneously change their name -> inode mapping.
+ // Therefore the FUSE VFS layer uses dentry_operations::d_revalidate
+ // (http://goo.gl/dVea0h) to intercept lookups and revalidate by calling the
+ // user-space LookUpInode method. However the latter may be slow, so it
+ // caches the entries until the time defined by this field.
+ // Example code walk:
+ // * (http://goo.gl/M2G3tO) lookup_dcache calls d_revalidate if enabled.
+ // * (http://goo.gl/ef0Elu) fuse_dentry_revalidate just uses the dentry's
+ // inode if fuse_dentry_time(entry) hasn't passed. Otherwise it sends a
+ // lookup request.
+ // Leave at the zero value to disable caching.
+ // Beware: this value is ignored on OS X, where entry caching is disabled by
+ // default. See notes on MountConfig.EnableVnodeCaching for more.
+ EntryExpiration time.Time
@@ -0,0 +1,367 @@
+package fusetesting
+ "sync/atomic"
+ . "github.com/jacobsa/ogletest"
+ "github.com/jacobsa/syncutil"
+// Run an ogletest test that checks expectations for parallel calls to open(2)
+// with O_CREAT.
+func RunCreateInParallelTest_NoTruncate(
+ ctx context.Context,
+ dir string) {
+ // Ensure that we get parallelism for this test.
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
+ // Try for awhile to see if anything breaks.
+ const duration = 500 * time.Millisecond
+ startTime := time.Now()
+ for time.Since(startTime) < duration {
+ filename := path.Join(dir, "foo")
+ // Set up a function that opens the file with O_CREATE and then appends a
+ // byte to it.
+ worker := func(id byte) (err error) {
+ f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
+ err = fmt.Errorf("Worker %d: Open: %v", id, err)
+ defer f.Close()
+ _, err = f.Write([]byte{id})
+ err = fmt.Errorf("Worker %d: Write: %v", id, err)
+ // Run several workers in parallel.
+ const numWorkers = 16
+ b := syncutil.NewBundle(ctx)
+ for i := 0; i < numWorkers; i++ {
+ id := byte(i)
+ b.Add(func(ctx context.Context) (err error) {
+ err = worker(id)
+ })
+ err := b.Join()
+ AssertEq(nil, err)
+ // Read the contents of the file. We should see each worker's ID once.
+ contents, err := ioutil.ReadFile(filename)
+ idsSeen := make(map[byte]struct{})
+ for i, _ := range contents {
+ id := contents[i]
+ AssertLt(id, numWorkers)
+ if _, ok := idsSeen[id]; ok {
+ AddFailure("Duplicate ID: %d", id)
+ idsSeen[id] = struct{}{}
+ AssertEq(numWorkers, len(idsSeen))
+ // Delete the file.
+ err = os.Remove(filename)
+// with O_CREAT|O_TRUNC.
+func RunCreateInParallelTest_Truncate(
+ // Set up a function that opens the file with O_CREATE and O_TRUNC and then
+ // appends a byte to it.
+ f, err := os.OpenFile(
+ filename,
+ os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_TRUNC,
+ 0600)
+ // Read the contents of the file. We should see at least one ID (the last
+ // one that truncated), and at most all of them.
+ AssertGe(len(idsSeen), 1)
+ AssertLe(len(idsSeen), numWorkers)
+// with O_CREAT|O_EXCL.
+func RunCreateInParallelTest_Exclusive(
+ // Set up a function that opens the file with O_CREATE and O_EXCL, and then
+ // appends a byte to it if it was successfully opened.
+ var openCount uint64
+ os.O_CREATE|os.O_EXCL|os.O_WRONLY|os.O_APPEND,
+ // If we failed to open due to the file already existing, just leave.
+ if os.IsExist(err) {
+ // Propgate other errors.
+ atomic.AddUint64(&openCount, 1)
+ // Exactly one worker should have opened successfully.
+ AssertEq(1, openCount)
+ // Read the contents of the file. It should contain that one worker's ID.
+ AssertEq(1, len(contents))
+ AssertLt(contents[0], numWorkers)
+// Run an ogletest test that checks expectations for parallel calls to mkdir(2).
+func RunMkdirInParallelTest(
+ // Set up a function that creates the directory, ignoring EEXIST errors.
+ err = os.Mkdir(filename, 0700)
+ err = fmt.Errorf("Worker %d: Mkdir: %v", id, err)
+ // The directory should have been created, once.
+ entries, err := ReadDirPicky(dir)
+ AssertEq(1, len(entries))
+ AssertEq("foo", entries[0].Name())
+ // Delete the directory.
+// Run an ogletest test that checks expectations for parallel calls to
+// symlink(2).
+func RunSymlinkInParallelTest(
+ // Set up a function that creates the symlink, ignoring EEXIST errors.
+ err = os.Symlink("blah", filename)
+ err = fmt.Errorf("Worker %d: Symlink: %v", id, err)
+ // The symlink should have been created, once.
@@ -0,0 +1,76 @@
+ "sort"
+type sortedEntries []os.FileInfo
+func (f sortedEntries) Len() int { return len(f) }
+func (f sortedEntries) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
+func (f sortedEntries) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
+// Read the directory with the given name and return a list of directory
+// entries, sorted by name.
+// Unlike ioutil.ReadDir (cf. http://goo.gl/i0nNP4), this function does not
+// silently ignore "file not found" errors when stat'ing the names read from
+// the directory.
+func ReadDirPicky(dirname string) (entries []os.FileInfo, err error) {
+ // Open the directory.
+ f, err := os.Open(dirname)
+ err = fmt.Errorf("Open: %v", err)
+ // Don't forget to close it later.
+ defer func() {
+ closeErr := f.Close()
+ if closeErr != nil && err == nil {
+ err = fmt.Errorf("Close: %v", closeErr)
+ }()
+ // Read all of the names from the directory.
+ names, err := f.Readdirnames(-1)
+ err = fmt.Errorf("Readdirnames: %v", err)
+ // Stat each one.
+ for _, name := range names {
+ var fi os.FileInfo
+ fi, err = os.Lstat(path.Join(dirname, name))
+ err = fmt.Errorf("Lstat(%s): %v", name, err)
+ entries = append(entries, fi)
+ // Sort the entries by name.
+ sort.Sort(sortedEntries(entries))
@@ -0,0 +1,125 @@
+ "github.com/jacobsa/oglematchers"
+// Match os.FileInfo values that specify an mtime equal to the given time.
+func MtimeIs(expected time.Time) oglematchers.Matcher {
+ return oglematchers.NewMatcher(
+ func(c interface{}) error { return mtimeIsWithin(c, expected, 0) },
+ fmt.Sprintf("mtime is %v", expected))
+// Like MtimeIs, but allows for a tolerance.
+func MtimeIsWithin(expected time.Time, d time.Duration) oglematchers.Matcher {
+ func(c interface{}) error { return mtimeIsWithin(c, expected, d) },
+ fmt.Sprintf("mtime is within %v of %v", d, expected))
+func mtimeIsWithin(c interface{}, expected time.Time, d time.Duration) error {
+ fi, ok := c.(os.FileInfo)
+ return fmt.Errorf("which is of type %v", reflect.TypeOf(c))
+ // Check ModTime().
+ diff := fi.ModTime().Sub(expected)
+ absDiff := diff
+ if absDiff < 0 {
+ absDiff = -absDiff
+ if !(absDiff < d) {
+ return fmt.Errorf("which has mtime %v, off by %v", fi.ModTime(), diff)
+ return nil
+// Match os.FileInfo values that specify a file birth time within the supplied
+// radius of the given time. On platforms where there is no birth time
+// available, match all os.FileInfo values.
+func BirthtimeIsWithin(
+ expected time.Time,
+ d time.Duration) oglematchers.Matcher {
+ func(c interface{}) error { return birthtimeIsWithin(c, expected, d) },
+ fmt.Sprintf("birthtime is within %v of %v", d, expected))
+func birthtimeIsWithin(
+ c interface{},
+ d time.Duration) error {
+ t, ok := extractBirthtime(fi.Sys())
+ diff := t.Sub(expected)
+ return fmt.Errorf("which has birth time %v, off by %v", t, diff)
+// Extract time information from the supplied file info. Panic on platforms
+// where this is not possible.
+func GetTimes(fi os.FileInfo) (atime, ctime, mtime time.Time) {
+ return getTimes(fi.Sys().(*syscall.Stat_t))
+// Match os.FileInfo values that specify a number of links equal to the given
+// number. On platforms where there is no nlink field available, match all
+// os.FileInfo values.
+func NlinkIs(expected uint64) oglematchers.Matcher {
+ func(c interface{}) error { return nlinkIs(c, expected) },
+ fmt.Sprintf("nlink is %v", expected))
+func nlinkIs(c interface{}, expected uint64) error {
+ if actual, ok := extractNlink(fi.Sys()); ok && actual != expected {
+ return fmt.Errorf("which has nlink == %v", actual)
@@ -0,0 +1,45 @@
+func extractMtime(sys interface{}) (mtime time.Time, ok bool) {
+ mtime = time.Unix(sys.(*syscall.Stat_t).Mtimespec.Unix())
+ ok = true
+func extractBirthtime(sys interface{}) (birthtime time.Time, ok bool) {
+ birthtime = time.Unix(sys.(*syscall.Stat_t).Birthtimespec.Unix())
+func extractNlink(sys interface{}) (nlink uint64, ok bool) {
+ nlink = uint64(sys.(*syscall.Stat_t).Nlink)
+func getTimes(stat *syscall.Stat_t) (atime, ctime, mtime time.Time) {
+ atime = time.Unix(stat.Atimespec.Unix())
+ ctime = time.Unix(stat.Ctimespec.Unix())
+ mtime = time.Unix(stat.Mtimespec.Unix())
@@ -0,0 +1,43 @@
+ mtime = time.Unix(sys.(*syscall.Stat_t).Mtim.Unix())
+ nlink = sys.(*syscall.Stat_t).Nlink
+ atime = time.Unix(stat.Atim.Unix())
+ ctime = time.Unix(stat.Ctim.Unix())
+ mtime = time.Unix(stat.Mtim.Unix())
@@ -0,0 +1,105 @@
+package fuseutil
+type DirentType uint32
+ DT_Unknown DirentType = 0
+ DT_Socket DirentType = syscall.DT_SOCK
+ DT_Link DirentType = syscall.DT_LNK
+ DT_File DirentType = syscall.DT_REG
+ DT_Block DirentType = syscall.DT_BLK
+ DT_Directory DirentType = syscall.DT_DIR
+ DT_Char DirentType = syscall.DT_CHR
+ DT_FIFO DirentType = syscall.DT_FIFO
+// A struct representing an entry within a directory file, describing a child.
+// See notes on fuseops.ReadDirOp and on WriteDirent for details.
+type Dirent struct {
+ // The (opaque) offset within the directory file of the entry following this
+ // one. See notes on fuseops.ReadDirOp.Offset for details.
+ Offset fuseops.DirOffset
+ // The inode of the child file or directory, and its name within the parent.
+ Inode fuseops.InodeID
+ // The type of the child. The zero value (DT_Unknown) is legal, but means
+ // that the kernel will need to call GetAttr when the type is needed.
+ Type DirentType
+// Write the supplied directory entry intto the given buffer in the format
+// expected in fuseops.ReadFileOp.Data, returning the number of bytes written.
+// Return zero if the entry would not fit.
+func WriteDirent(buf []byte, d Dirent) (n int) {
+ // We want to write bytes with the layout of fuse_dirent
+ // (http://goo.gl/BmFxob) in host order. The struct must be aligned according
+ // to FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH), which dictates 8-byte
+ // alignment.
+ type fuse_dirent struct {
+ ino uint64
+ off uint64
+ namelen uint32
+ type_ uint32
+ name [0]byte
+ const direntAlignment = 8
+ const direntSize = 8 + 8 + 4 + 4
+ // Compute the number of bytes of padding we'll need to maintain alignment
+ // for the next entry.
+ var padLen int
+ if len(d.Name)%direntAlignment != 0 {
+ padLen = direntAlignment - (len(d.Name) % direntAlignment)
+ // Do we have enough room?
+ totalLen := direntSize + len(d.Name) + padLen
+ if totalLen > len(buf) {
+ // Write the header.
+ de := fuse_dirent{
+ ino: uint64(d.Inode),
+ off: uint64(d.Offset),
+ namelen: uint32(len(d.Name)),
+ type_: uint32(d.Type),
+ n += copy(buf[n:], (*[direntSize]byte)(unsafe.Pointer(&de))[:])
+ // Write the name afterward.
+ n += copy(buf[n:], d.Name)
+ // Add any necessary padding.
+ if padLen != 0 {
+ var padding [direntAlignment]byte
+ n += copy(buf[n:], padding[:padLen])
@@ -0,0 +1,16 @@
+// Types and functions that make it easier to work with package fuse.
@@ -0,0 +1,208 @@
+ "github.com/jacobsa/fuse"
+// An interface with a method for each op type in the fuseops package. This can
+// be used in conjunction with NewFileSystemServer to avoid writing a "dispatch
+// loop" that switches on op types, instead receiving typed method calls
+// directly.
+// The FileSystem implementation should not call Connection.Reply, instead
+// returning the error with which the caller should respond.
+// See NotImplementedFileSystem for a convenient way to embed default
+// implementations for methods you don't care about.
+type FileSystem interface {
+ StatFS(context.Context, *fuseops.StatFSOp) error
+ LookUpInode(context.Context, *fuseops.LookUpInodeOp) error
+ GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error
+ SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
+ ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
+ MkDir(context.Context, *fuseops.MkDirOp) error
+ MkNode(context.Context, *fuseops.MkNodeOp) error
+ CreateFile(context.Context, *fuseops.CreateFileOp) error
+ CreateSymlink(context.Context, *fuseops.CreateSymlinkOp) error
+ Rename(context.Context, *fuseops.RenameOp) error
+ RmDir(context.Context, *fuseops.RmDirOp) error
+ Unlink(context.Context, *fuseops.UnlinkOp) error
+ OpenDir(context.Context, *fuseops.OpenDirOp) error
+ ReadDir(context.Context, *fuseops.ReadDirOp) error
+ ReleaseDirHandle(context.Context, *fuseops.ReleaseDirHandleOp) error
+ OpenFile(context.Context, *fuseops.OpenFileOp) error
+ ReadFile(context.Context, *fuseops.ReadFileOp) error
+ WriteFile(context.Context, *fuseops.WriteFileOp) error
+ SyncFile(context.Context, *fuseops.SyncFileOp) error
+ FlushFile(context.Context, *fuseops.FlushFileOp) error
+ ReleaseFileHandle(context.Context, *fuseops.ReleaseFileHandleOp) error
+ ReadSymlink(context.Context, *fuseops.ReadSymlinkOp) error
+ RemoveXattr(context.Context, *fuseops.RemoveXattrOp) error
+ GetXattr(context.Context, *fuseops.GetXattrOp) error
+ ListXattr(context.Context, *fuseops.ListXattrOp) error
+ SetXattr(context.Context, *fuseops.SetXattrOp) error
+ // Regard all inodes (including the root inode) as having their lookup counts
+ // decremented to zero, and clean up any resources associated with the file
+ // system. No further calls to the file system will be made.
+ Destroy()
+// Create a fuse.Server that handles ops by calling the associated FileSystem
+// method.Respond with the resulting error. Unsupported ops are responded to
+// directly with ENOSYS.
+// Each call to a FileSystem method is made on its own goroutine, and is free
+// to block.
+// (It is safe to naively process ops concurrently because the kernel
+// guarantees to serialize operations that the user expects to happen in order,
+// cf. http://goo.gl/jnkHPO, fuse-devel thread "Fuse guarantees on concurrent
+// requests").
+func NewFileSystemServer(fs FileSystem) fuse.Server {
+ return &fileSystemServer{
+type fileSystemServer struct {
+ fs FileSystem
+ opsInFlight sync.WaitGroup
+func (s *fileSystemServer) ServeOps(c *fuse.Connection) {
+ // When we are done, we clean up by waiting for all in-flight ops then
+ // destroying the file system.
+ s.opsInFlight.Wait()
+ s.fs.Destroy()
+ if err == io.EOF {
+ panic(err)
+ s.opsInFlight.Add(1)
+ go s.handleOp(c, ctx, op)
+func (s *fileSystemServer) handleOp(
+ c *fuse.Connection,
+ defer s.opsInFlight.Done()
+ // Dispatch to the appropriate method.
+ var err error
+ err = fuse.ENOSYS
+ err = s.fs.StatFS(ctx, typed)
+ err = s.fs.LookUpInode(ctx, typed)
+ err = s.fs.GetInodeAttributes(ctx, typed)
+ err = s.fs.SetInodeAttributes(ctx, typed)
+ err = s.fs.ForgetInode(ctx, typed)
+ err = s.fs.MkDir(ctx, typed)
+ err = s.fs.MkNode(ctx, typed)
+ err = s.fs.CreateFile(ctx, typed)
+ err = s.fs.CreateSymlink(ctx, typed)
+ err = s.fs.Rename(ctx, typed)
+ err = s.fs.RmDir(ctx, typed)
+ err = s.fs.Unlink(ctx, typed)
+ err = s.fs.OpenDir(ctx, typed)
+ err = s.fs.ReadDir(ctx, typed)
+ err = s.fs.ReleaseDirHandle(ctx, typed)
+ err = s.fs.OpenFile(ctx, typed)
+ err = s.fs.ReadFile(ctx, typed)
+ err = s.fs.WriteFile(ctx, typed)
+ err = s.fs.SyncFile(ctx, typed)
+ err = s.fs.FlushFile(ctx, typed)
+ err = s.fs.ReleaseFileHandle(ctx, typed)
+ err = s.fs.ReadSymlink(ctx, typed)
+ err = s.fs.RemoveXattr(ctx, typed)
+ err = s.fs.GetXattr(ctx, typed)
+ err = s.fs.ListXattr(ctx, typed)
+ err = s.fs.SetXattr(ctx, typed)
+ c.Reply(ctx, err)
@@ -0,0 +1,215 @@
+// A FileSystem that responds to all ops with fuse.ENOSYS. Embed this in your
+// struct to inherit default implementations for the methods you don't care
+// about, ensuring your struct will continue to implement FileSystem even as
+// new methods are added.
+type NotImplementedFileSystem struct {
+var _ FileSystem = &NotImplementedFileSystem{}
+func (fs *NotImplementedFileSystem) StatFS(
+ op *fuseops.StatFSOp) (err error) {
+func (fs *NotImplementedFileSystem) LookUpInode(
+ op *fuseops.LookUpInodeOp) (err error) {
+func (fs *NotImplementedFileSystem) GetInodeAttributes(
+ op *fuseops.GetInodeAttributesOp) (err error) {
+func (fs *NotImplementedFileSystem) SetInodeAttributes(
+ op *fuseops.SetInodeAttributesOp) (err error) {
+func (fs *NotImplementedFileSystem) ForgetInode(
+ op *fuseops.ForgetInodeOp) (err error) {
+func (fs *NotImplementedFileSystem) MkDir(
+ op *fuseops.MkDirOp) (err error) {
+func (fs *NotImplementedFileSystem) MkNode(
+ op *fuseops.MkNodeOp) (err error) {
+func (fs *NotImplementedFileSystem) CreateFile(
+ op *fuseops.CreateFileOp) (err error) {
+func (fs *NotImplementedFileSystem) CreateSymlink(
+ op *fuseops.CreateSymlinkOp) (err error) {
+func (fs *NotImplementedFileSystem) Rename(
+ op *fuseops.RenameOp) (err error) {
+func (fs *NotImplementedFileSystem) RmDir(
+ op *fuseops.RmDirOp) (err error) {
+func (fs *NotImplementedFileSystem) Unlink(
+ op *fuseops.UnlinkOp) (err error) {
+func (fs *NotImplementedFileSystem) OpenDir(
+ op *fuseops.OpenDirOp) (err error) {
+func (fs *NotImplementedFileSystem) ReadDir(
+ op *fuseops.ReadDirOp) (err error) {
+func (fs *NotImplementedFileSystem) ReleaseDirHandle(
+ op *fuseops.ReleaseDirHandleOp) (err error) {
+func (fs *NotImplementedFileSystem) OpenFile(
+ op *fuseops.OpenFileOp) (err error) {
+func (fs *NotImplementedFileSystem) ReadFile(
+ op *fuseops.ReadFileOp) (err error) {
+func (fs *NotImplementedFileSystem) WriteFile(
+ op *fuseops.WriteFileOp) (err error) {
+func (fs *NotImplementedFileSystem) SyncFile(
+ op *fuseops.SyncFileOp) (err error) {
+func (fs *NotImplementedFileSystem) FlushFile(
+ op *fuseops.FlushFileOp) (err error) {
+func (fs *NotImplementedFileSystem) ReleaseFileHandle(
+ op *fuseops.ReleaseFileHandleOp) (err error) {
+func (fs *NotImplementedFileSystem) ReadSymlink(
+ op *fuseops.ReadSymlinkOp) (err error) {
+func (fs *NotImplementedFileSystem) RemoveXattr(
+ op *fuseops.RemoveXattrOp) (err error) {
+func (fs *NotImplementedFileSystem) GetXattr(
+ op *fuseops.GetXattrOp) (err error) {
+func (fs *NotImplementedFileSystem) ListXattr(
+ op *fuseops.ListXattrOp) (err error) {
+func (fs *NotImplementedFileSystem) SetXattr(
+ op *fuseops.SetXattrOp) (err error) {
+func (fs *NotImplementedFileSystem) Destroy() {
@@ -0,0 +1,115 @@
+package buffer
+// All requests read from the kernel, without data, are shorter than
+// this.
+const pageSize = 4096
+ // Confirm the page size.
+ if syscall.Getpagesize() != pageSize {
+ panic(fmt.Sprintf("Page size is unexpectedly %d", syscall.Getpagesize()))
+// We size the buffer to have enough room for a fuse request plus data
+// associated with a write request.
+const bufSize = pageSize + MaxWriteSize
+// An incoming message from the kernel, including leading fusekernel.InHeader
+// struct. Provides storage for messages and convenient access to their
+// contents.
+type InMessage struct {
+ remaining []byte
+ storage [bufSize]byte
+// Initialize with the data read by a single call to r.Read. The first call to
+// Consume will consume the bytes directly after the fusekernel.InHeader
+// struct.
+func (m *InMessage) Init(r io.Reader) (err error) {
+ n, err := r.Read(m.storage[:])
+ // Make sure the message is long enough.
+ const headerSize = unsafe.Sizeof(fusekernel.InHeader{})
+ if uintptr(n) < headerSize {
+ err = fmt.Errorf("Unexpectedly read only %d bytes.", n)
+ m.remaining = m.storage[headerSize:n]
+ // Check the header's length.
+ if int(m.Header().Len) != n {
+ err = fmt.Errorf(
+ "Header says %d bytes, but we read %d",
+ m.Header().Len,
+ n)
+// Return a reference to the header read in the most recent call to Init.
+func (m *InMessage) Header() (h *fusekernel.InHeader) {
+ h = (*fusekernel.InHeader)(unsafe.Pointer(&m.storage[0]))
+// Return the number of bytes left to consume.
+func (m *InMessage) Len() uintptr {
+ return uintptr(len(m.remaining))
+// Consume the next n bytes from the message, returning a nil pointer if there
+// are fewer than n bytes available.
+func (m *InMessage) Consume(n uintptr) (p unsafe.Pointer) {
+ if m.Len() == 0 || n > m.Len() {
+ p = unsafe.Pointer(&m.remaining[0])
+ m.remaining = m.remaining[n:]
+// Equivalent to Consume, except returns a slice of bytes. The result will be
+// nil if Consume would fail.
+func (m *InMessage) ConsumeBytes(n uintptr) (b []byte) {
+ if n > m.Len() {
+ b = m.remaining[:n]
@@ -0,0 +1,21 @@
+// The maximum fuse write request size that InMessage can acommodate.
+// Experimentally, OS X appears to cap the size of writes to 1 MiB, regardless
+// of whether a larger size is specified in the mount options.
+const MaxWriteSize = 1 << 20
+// Experimentally, Linux appears to refuse to honor a MaxWrite setting in an
+// INIT response of more than 128 KiB.
+const MaxWriteSize = 1 << 17
@@ -0,0 +1,167 @@
+// OutMessageHeaderSize is the size of the leading header in every
+// properly-constructed OutMessage. Reset brings the message back to this size.
+const OutMessageHeaderSize = int(unsafe.Sizeof(fusekernel.OutHeader{}))
+// OutMessage provides a mechanism for constructing a single contiguous fuse
+// message from multiple segments, where the first segment is always a
+// fusekernel.OutHeader message.
+// Must be initialized with Reset.
+type OutMessage struct {
+ // The offset into payload to which we're currently writing.
+ payloadOffset int
+ header fusekernel.OutHeader
+ payload [MaxReadSize]byte
+// Make sure that the header and payload are contiguous.
+ a := unsafe.Offsetof(OutMessage{}.header) + uintptr(OutMessageHeaderSize)
+ b := unsafe.Offsetof(OutMessage{}.payload)
+ if a != b {
+ log.Panicf(
+ "header ends at offset %d, but payload starts at offset %d",
+ a, b)
+// Reset resets m so that it's ready to be used again. Afterward, the contents
+// are solely a zeroed fusekernel.OutHeader struct.
+func (m *OutMessage) Reset() {
+ // Ideally we'd like to write:
+ // m.payloadOffset = 0
+ // m.header = fusekernel.OutHeader{}
+ // But Go 1.8 beta 2 generates bad code for this
+ // (https://golang.org/issue/18370). Encourage it to generate the same code
+ // as Go 1.7.4 did.
+ //if unsafe.Offsetof(m.payload) != 24 {
+ // panic("unexpected OutMessage layout")
+ //}
+ //a := (*[3]uint64)(unsafe.Pointer(m))
+ //a[0] = 0
+ //a[1] = 0
+ //a[2] = 0
+ m.payloadOffset = 0
+ m.header = fusekernel.OutHeader{}
+// OutHeader returns a pointer to the header at the start of the message.
+func (m *OutMessage) OutHeader() *fusekernel.OutHeader {
+ return &m.header
+// Grow grows m's buffer by the given number of bytes, returning a pointer to
+// the start of the new segment, which is guaranteed to be zeroed. If there is
+// insufficient space, it returns nil.
+func (m *OutMessage) Grow(n int) (p unsafe.Pointer) {
+ p = m.GrowNoZero(n)
+ if p != nil {
+ memclr(p, uintptr(n))
+// GrowNoZero is equivalent to Grow, except the new segment is not zeroed. Use
+// with caution!
+func (m *OutMessage) GrowNoZero(n int) (p unsafe.Pointer) {
+ // Will we overflow the buffer?
+ o := m.payloadOffset
+ if len(m.payload)-o < n {
+ p = unsafe.Pointer(uintptr(unsafe.Pointer(&m.payload)) + uintptr(o))
+ m.payloadOffset = o + n
+// ShrinkTo shrinks m to the given size. It panics if the size is greater than
+// Len() or less than OutMessageHeaderSize.
+func (m *OutMessage) ShrinkTo(n int) {
+ if n < OutMessageHeaderSize || n > m.Len() {
+ panic(fmt.Sprintf(
+ "ShrinkTo(%d) out of range (current Len: %d)",
+ n,
+ m.Len()))
+ m.payloadOffset = n - OutMessageHeaderSize
+// Append is equivalent to growing by len(src), then copying src over the new
+// segment. Int panics if there is not enough room available.
+func (m *OutMessage) Append(src []byte) {
+ p := m.GrowNoZero(len(src))
+ panic(fmt.Sprintf("Can't grow %d bytes", len(src)))
+ sh := (*reflect.SliceHeader)(unsafe.Pointer(&src))
+ memmove(p, unsafe.Pointer(sh.Data), uintptr(sh.Len))
+// AppendString is like Append, but accepts string input.
+func (m *OutMessage) AppendString(src string) {
+ sh := (*reflect.StringHeader)(unsafe.Pointer(&src))
+// Len returns the current size of the message, including the leading header.
+func (m *OutMessage) Len() int {
+ return OutMessageHeaderSize + m.payloadOffset
+// Bytes returns a reference to the current contents of the buffer, including
+// the leading header.
+func (m *OutMessage) Bytes() []byte {
+ l := m.Len()
+ sh := reflect.SliceHeader{
+ Data: uintptr(unsafe.Pointer(&m.header)),
+ Len: l,
+ Cap: l,
+ return *(*[]byte)(unsafe.Pointer(&sh))
+// The maximum read size that we expect to ever see from the kernel, used for
+// calculating the size of out messages.
+// Experimentally determined on OS X.
+const MaxReadSize = 1 << 20
+// For 4 KiB pages, this is 128 KiB (cf. https://goo.gl/HOiEYo)
+const MaxReadSize = 1 << 17
@@ -0,0 +1,357 @@
+ "crypto/rand"
+ "testing"
+ "github.com/kylelemons/godebug/pretty"
+func toByteSlice(p unsafe.Pointer, n int) []byte {
+ Data: uintptr(p),
+ Len: n,
+ Cap: n,
+// fillWithGarbage writes random data to [p, p+n).
+func fillWithGarbage(p unsafe.Pointer, n int) (err error) {
+ b := toByteSlice(p, n)
+ _, err = io.ReadFull(rand.Reader, b)
+func randBytes(n int) (b []byte, err error) {
+ b = make([]byte, n)
+// findNonZero finds the offset of the first non-zero byte in [p, p+n). If
+// none, it returns n.
+func findNonZero(p unsafe.Pointer, n int) int {
+ for i, x := range b {
+ if x != 0 {
+ return i
+ return n
+func TestMemclr(t *testing.T) {
+ // All sizes up to 32 bytes.
+ var sizes []int
+ for i := 0; i <= 32; i++ {
+ sizes = append(sizes, i)
+ // And a few hand-chosen sizes.
+ sizes = append(sizes, []int{
+ 39, 41, 64, 127, 128, 129,
+ 1<<20 - 1,
+ 1 << 20,
+ 1<<20 + 1,
+ }...)
+ // For each size, fill a buffer with random bytes and then zero it.
+ for _, size := range sizes {
+ size := size
+ t.Run(fmt.Sprintf("size=%d", size), func(t *testing.T) {
+ // Generate
+ b, err := randBytes(size)
+ t.Fatalf("randBytes: %v", err)
+ // Clear
+ var p unsafe.Pointer
+ if len(b) != 0 {
+ p = unsafe.Pointer(&b[0])
+ memclr(p, uintptr(len(b)))
+ // Check
+ if i := findNonZero(p, len(b)); i != len(b) {
+ t.Fatalf("non-zero byte at offset %d", i)
+func TestOutMessageAppend(t *testing.T) {
+ var om OutMessage
+ om.Reset()
+ // Append some payload.
+ const wantPayloadStr = "tacoburrito"
+ wantPayload := []byte(wantPayloadStr)
+ om.Append(wantPayload[:4])
+ om.Append(wantPayload[4:])
+ // The result should be a zeroed header followed by the desired payload.
+ const wantLen = OutMessageHeaderSize + len(wantPayloadStr)
+ if got, want := om.Len(), wantLen; got != want {
+ t.Errorf("om.Len() = %d, want %d", got, want)
+ b := om.Bytes()
+ if got, want := len(b), wantLen; got != want {
+ t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
+ want := append(
+ make([]byte, OutMessageHeaderSize),
+ wantPayload...)
+ if !bytes.Equal(b, want) {
+ t.Error("messages differ")
+func TestOutMessageAppendString(t *testing.T) {
+ const wantPayload = "tacoburrito"
+ om.AppendString(wantPayload[:4])
+ om.AppendString(wantPayload[4:])
+ const wantLen = OutMessageHeaderSize + len(wantPayload)
+func TestOutMessageShrinkTo(t *testing.T) {
+ // Set up a buffer with some payload.
+ om.AppendString("taco")
+ om.AppendString("burrito")
+ // Shrink it.
+ om.ShrinkTo(OutMessageHeaderSize + len("taco"))
+ // The result should be a zeroed header followed by "taco".
+ const wantLen = OutMessageHeaderSize + len("taco")
+ "taco"...)
+func TestOutMessageHeader(t *testing.T) {
+ // Fill in the header.
+ want := fusekernel.OutHeader{
+ Len: 0xdeadbeef,
+ Error: -31231917,
+ Unique: 0xcafebabeba5eba11,
+ h := om.OutHeader()
+ if h == nil {
+ t.Fatal("OutHeader returned nil")
+ *h = want
+ // Check that the result is as expected.
+ if len(b) != int(unsafe.Sizeof(want)) {
+ t.Fatalf("unexpected length %d; want %d", len(b), unsafe.Sizeof(want))
+ got := *(*fusekernel.OutHeader)(unsafe.Pointer(&b[0]))
+ if diff := pretty.Compare(got, want); diff != "" {
+ t.Errorf("diff -got +want:\n%s", diff)
+func TestOutMessageReset(t *testing.T) {
+ const trials = 10
+ for i := 0; i < trials; i++ {
+ // Fill the header with garbage.
+ err := fillWithGarbage(unsafe.Pointer(h), int(unsafe.Sizeof(*h)))
+ t.Fatalf("fillWithGarbage: %v", err)
+ // Ensure a non-zero payload length.
+ if p := om.GrowNoZero(128); p == nil {
+ t.Fatal("GrowNoZero failed")
+ // Reset.
+ // Check that the length was updated.
+ if got, want := om.Len(), OutMessageHeaderSize; got != want {
+ t.Fatalf("om.Len() = %d, want %d", got, want)
+ // Check that the header was zeroed.
+ if h.Len != 0 {
+ t.Fatalf("non-zero Len %v", h.Len)
+ if h.Error != 0 {
+ t.Fatalf("non-zero Error %v", h.Error)
+ if h.Unique != 0 {
+ t.Fatalf("non-zero Unique %v", h.Unique)
+func TestOutMessageGrow(t *testing.T) {
+ // Set up garbage where the payload will soon be.
+ const payloadSize = 1234
+ {
+ p := om.GrowNoZero(payloadSize)
+ err := fillWithGarbage(p, payloadSize)
+ om.ShrinkTo(OutMessageHeaderSize)
+ // Call Grow.
+ if p := om.Grow(payloadSize); p == nil {
+ t.Fatal("Grow failed")
+ // Check the resulting length in two ways.
+ const wantLen = payloadSize + OutMessageHeaderSize
+ t.Errorf("om.Len() = %d, want %d", got)
+ t.Fatalf("len(om.Len()) = %d, want %d", got)
+ // Check that the payload was zeroed.
+ for i, x := range b[OutMessageHeaderSize:] {
+ t.Fatalf("non-zero byte 0x%02x at payload offset %d", x, i)
+func BenchmarkOutMessageReset(b *testing.B) {
+ // A single buffer, which should fit in some level of CPU cache.
+ b.Run("Single buffer", func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ b.SetBytes(int64(unsafe.Offsetof(om.payload)))
+ // Many megabytes worth of buffers, which should defeat the CPU cache.
+ b.Run("Many buffers", func(b *testing.B) {
+ // The number of messages; intentionally a power of two.
+ const numMessages = 128
+ var oms [numMessages]OutMessage
+ if s := unsafe.Sizeof(oms); s < 128<<20 {
+ panic(fmt.Sprintf("Array is too small; total size: %d", s))
+ oms[i%numMessages].Reset()
+ b.SetBytes(int64(unsafe.Offsetof(oms[0].payload)))
+func BenchmarkOutMessageGrowShrink(b *testing.B) {
+ om.Grow(MaxReadSize)
+ b.SetBytes(int64(MaxReadSize))
+ oms[i%numMessages].Grow(MaxReadSize)
+ oms[i%numMessages].ShrinkTo(OutMessageHeaderSize)
@@ -0,0 +1,29 @@
+import "unsafe"
+//go:noescape
+// Zero the n bytes starting at p.
+// REQUIRES: the region does not contain any Go pointers.
+func memclr(p unsafe.Pointer, n uintptr)
+// Copy from src to dst, allowing overlap.
+func memmove(dst unsafe.Pointer, src unsafe.Pointer, n uintptr)
@@ -0,0 +1,40 @@
+// +build amd64 arm64 ppc64 ppc64le arm
+// +build go1.8
+// Assembly code isn't subject to visibility restrictions, so we can jump
+// directly into package runtime.
+// Technique copied from here:
+// https://github.com/golang/go/blob/d8c6dac/src/os/signal/sig.s
+#include "textflag.h"
+#ifdef GOARCH_arm
+#define JMP B
+#endif
+#ifdef GOARCH_ppc64
+#define JMP BR
+#ifdef GOARCH_ppc64le
+TEXT ·memclr(SB),NOSPLIT,$0-16
+ JMP runtime·memclrNoHeapPointers(SB)
+TEXT ·memmove(SB),NOSPLIT,$0-24
+ JMP runtime·memmove(SB)
+// +build !go1.8
+ JMP runtime·memclr(SB)
+package freelist
+// A freelist for arbitrary pointers. Not safe for concurrent access.
+type Freelist struct {
+ list []unsafe.Pointer
+// Get an element from the freelist, returning nil if empty.
+func (fl *Freelist) Get() (p unsafe.Pointer) {
+ l := len(fl.list)
+ if l == 0 {
+ p = fl.list[l-1]
+ fl.list = fl.list[:l-1]
+// Contribute an element back to the freelist.
+func (fl *Freelist) Put(p unsafe.Pointer) {
+ fl.list = append(fl.list, p)
@@ -0,0 +1,771 @@
+// See the file LICENSE for copyright and licensing information.
+// Derived from FUSE's fuse_kernel.h, which carries this notice:
+package fusekernel
+// The FUSE version implemented by the package.
+ ProtoVersionMinMajor = 7
+ ProtoVersionMinMinor = 8
+ ProtoVersionMaxMajor = 7
+ ProtoVersionMaxMinor = 12
+ RootID = 1
+type Kstatfs struct {
+ Bfree uint64
+ Bavail uint64
+ Files uint64
+ Ffree uint64
+ Bsize uint32
+ Namelen uint32
+ Frsize uint32
+ Padding uint32
+ Spare [6]uint32
+type fileLock struct {
+ Start uint64
+ End uint64
+ Type uint32
+ Pid uint32
+// GetattrFlags are bit flags that can be seen in GetattrRequest.
+type GetattrFlags uint32
+ // Indicates the handle is valid.
+ GetattrFh GetattrFlags = 1 << 0
+var getattrFlagsNames = []flagName{
+ {uint32(GetattrFh), "GetattrFh"},
+func (fl GetattrFlags) String() string {
+ return flagString(uint32(fl), getattrFlagsNames)
+// The SetattrValid are bit flags describing which fields in the SetattrRequest
+// are included in the change.
+type SetattrValid uint32
+ SetattrMode SetattrValid = 1 << 0
+ SetattrUid SetattrValid = 1 << 1
+ SetattrGid SetattrValid = 1 << 2
+ SetattrSize SetattrValid = 1 << 3
+ SetattrAtime SetattrValid = 1 << 4
+ SetattrMtime SetattrValid = 1 << 5
+ SetattrHandle SetattrValid = 1 << 6
+ // Linux only(?)
+ SetattrAtimeNow SetattrValid = 1 << 7
+ SetattrMtimeNow SetattrValid = 1 << 8
+ SetattrLockOwner SetattrValid = 1 << 9 // http://www.mail-archive.com/git-commits-head@vger.kernel.org/msg27852.html
+ // OS X only
+ SetattrCrtime SetattrValid = 1 << 28
+ SetattrChgtime SetattrValid = 1 << 29
+ SetattrBkuptime SetattrValid = 1 << 30
+ SetattrFlags SetattrValid = 1 << 31
+func (fl SetattrValid) Mode() bool { return fl&SetattrMode != 0 }
+func (fl SetattrValid) Uid() bool { return fl&SetattrUid != 0 }
+func (fl SetattrValid) Gid() bool { return fl&SetattrGid != 0 }
+func (fl SetattrValid) Size() bool { return fl&SetattrSize != 0 }
+func (fl SetattrValid) Atime() bool { return fl&SetattrAtime != 0 }
+func (fl SetattrValid) Mtime() bool { return fl&SetattrMtime != 0 }
+func (fl SetattrValid) Handle() bool { return fl&SetattrHandle != 0 }
+func (fl SetattrValid) AtimeNow() bool { return fl&SetattrAtimeNow != 0 }
+func (fl SetattrValid) MtimeNow() bool { return fl&SetattrMtimeNow != 0 }
+func (fl SetattrValid) LockOwner() bool { return fl&SetattrLockOwner != 0 }
+func (fl SetattrValid) Crtime() bool { return fl&SetattrCrtime != 0 }
+func (fl SetattrValid) Chgtime() bool { return fl&SetattrChgtime != 0 }
+func (fl SetattrValid) Bkuptime() bool { return fl&SetattrBkuptime != 0 }
+func (fl SetattrValid) Flags() bool { return fl&SetattrFlags != 0 }
+func (fl SetattrValid) String() string {
+ return flagString(uint32(fl), setattrValidNames)
+var setattrValidNames = []flagName{
+ {uint32(SetattrMode), "SetattrMode"},
+ {uint32(SetattrUid), "SetattrUid"},
+ {uint32(SetattrGid), "SetattrGid"},
+ {uint32(SetattrSize), "SetattrSize"},
+ {uint32(SetattrAtime), "SetattrAtime"},
+ {uint32(SetattrMtime), "SetattrMtime"},
+ {uint32(SetattrHandle), "SetattrHandle"},
+ {uint32(SetattrAtimeNow), "SetattrAtimeNow"},
+ {uint32(SetattrMtimeNow), "SetattrMtimeNow"},
+ {uint32(SetattrLockOwner), "SetattrLockOwner"},
+ {uint32(SetattrCrtime), "SetattrCrtime"},
+ {uint32(SetattrChgtime), "SetattrChgtime"},
+ {uint32(SetattrBkuptime), "SetattrBkuptime"},
+ {uint32(SetattrFlags), "SetattrFlags"},
+// Flags that can be seen in OpenRequest.Flags.
+ // Access modes. These are not 1-bit flags, but alternatives where
+ // only one can be chosen. See the IsReadOnly etc convenience
+ // methods.
+ OpenReadOnly OpenFlags = syscall.O_RDONLY
+ OpenWriteOnly OpenFlags = syscall.O_WRONLY
+ OpenReadWrite OpenFlags = syscall.O_RDWR
+ OpenAppend OpenFlags = syscall.O_APPEND
+ OpenCreate OpenFlags = syscall.O_CREAT
+ OpenExclusive OpenFlags = syscall.O_EXCL
+ OpenSync OpenFlags = syscall.O_SYNC
+ OpenTruncate OpenFlags = syscall.O_TRUNC
+// OpenAccessModeMask is a bitmask that separates the access mode
+// from the other flags in OpenFlags.
+const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE
+// OpenFlags are the O_FOO flags passed to open/create/etc calls. For
+// example, os.O_WRONLY | os.O_APPEND.
+type OpenFlags uint32
+func (fl OpenFlags) String() string {
+ // O_RDONLY, O_RWONLY, O_RDWR are not flags
+ s := accModeName(fl & OpenAccessModeMask)
+ flags := uint32(fl &^ OpenAccessModeMask)
+ if flags != 0 {
+ s = s + "+" + flagString(flags, openFlagNames)
+ return s
+// Return true if OpenReadOnly is set.
+func (fl OpenFlags) IsReadOnly() bool {
+ return fl&OpenAccessModeMask == OpenReadOnly
+// Return true if OpenWriteOnly is set.
+func (fl OpenFlags) IsWriteOnly() bool {
+ return fl&OpenAccessModeMask == OpenWriteOnly
+// Return true if OpenReadWrite is set.
+func (fl OpenFlags) IsReadWrite() bool {
+ return fl&OpenAccessModeMask == OpenReadWrite
+func accModeName(flags OpenFlags) string {
+ switch flags {
+ case OpenReadOnly:
+ return "OpenReadOnly"
+ case OpenWriteOnly:
+ return "OpenWriteOnly"
+ case OpenReadWrite:
+ return "OpenReadWrite"
+ return ""
+var openFlagNames = []flagName{
+ {uint32(OpenCreate), "OpenCreate"},
+ {uint32(OpenExclusive), "OpenExclusive"},
+ {uint32(OpenTruncate), "OpenTruncate"},
+ {uint32(OpenAppend), "OpenAppend"},
+ {uint32(OpenSync), "OpenSync"},
+// The OpenResponseFlags are returned in the OpenResponse.
+type OpenResponseFlags uint32
+ OpenDirectIO OpenResponseFlags = 1 << 0 // bypass page cache for this open file
+ OpenKeepCache OpenResponseFlags = 1 << 1 // don't invalidate the data cache on open
+ OpenNonSeekable OpenResponseFlags = 1 << 2 // mark the file as non-seekable (not supported on OS X)
+ OpenPurgeAttr OpenResponseFlags = 1 << 30 // OS X
+ OpenPurgeUBC OpenResponseFlags = 1 << 31 // OS X
+func (fl OpenResponseFlags) String() string {
+ return flagString(uint32(fl), openResponseFlagNames)
+var openResponseFlagNames = []flagName{
+ {uint32(OpenDirectIO), "OpenDirectIO"},
+ {uint32(OpenKeepCache), "OpenKeepCache"},
+ {uint32(OpenNonSeekable), "OpenNonSeekable"},
+ {uint32(OpenPurgeAttr), "OpenPurgeAttr"},
+ {uint32(OpenPurgeUBC), "OpenPurgeUBC"},
+// The InitFlags are used in the Init exchange.
+type InitFlags uint32
+ InitAsyncRead InitFlags = 1 << 0
+ InitPosixLocks InitFlags = 1 << 1
+ InitFileOps InitFlags = 1 << 2
+ InitAtomicTrunc InitFlags = 1 << 3
+ InitExportSupport InitFlags = 1 << 4
+ InitBigWrites InitFlags = 1 << 5
+ InitDontMask InitFlags = 1 << 6
+ InitSpliceWrite InitFlags = 1 << 7
+ InitSpliceMove InitFlags = 1 << 8
+ InitSpliceRead InitFlags = 1 << 9
+ InitFlockLocks InitFlags = 1 << 10
+ InitHasIoctlDir InitFlags = 1 << 11
+ InitAutoInvalData InitFlags = 1 << 12
+ InitDoReaddirplus InitFlags = 1 << 13
+ InitReaddirplusAuto InitFlags = 1 << 14
+ InitAsyncDIO InitFlags = 1 << 15
+ InitWritebackCache InitFlags = 1 << 16
+ InitNoOpenSupport InitFlags = 1 << 17
+ InitCaseSensitive InitFlags = 1 << 29 // OS X only
+ InitVolRename InitFlags = 1 << 30 // OS X only
+ InitXtimes InitFlags = 1 << 31 // OS X only
+type flagName struct {
+ bit uint32
+ name string
+var initFlagNames = []flagName{
+ {uint32(InitAsyncRead), "InitAsyncRead"},
+ {uint32(InitPosixLocks), "InitPosixLocks"},
+ {uint32(InitFileOps), "InitFileOps"},
+ {uint32(InitAtomicTrunc), "InitAtomicTrunc"},
+ {uint32(InitExportSupport), "InitExportSupport"},
+ {uint32(InitBigWrites), "InitBigWrites"},
+ {uint32(InitDontMask), "InitDontMask"},
+ {uint32(InitSpliceWrite), "InitSpliceWrite"},
+ {uint32(InitSpliceMove), "InitSpliceMove"},
+ {uint32(InitSpliceRead), "InitSpliceRead"},
+ {uint32(InitFlockLocks), "InitFlockLocks"},
+ {uint32(InitHasIoctlDir), "InitHasIoctlDir"},
+ {uint32(InitAutoInvalData), "InitAutoInvalData"},
+ {uint32(InitDoReaddirplus), "InitDoReaddirplus"},
+ {uint32(InitReaddirplusAuto), "InitReaddirplusAuto"},
+ {uint32(InitAsyncDIO), "InitAsyncDIO"},
+ {uint32(InitWritebackCache), "InitWritebackCache"},
+ {uint32(InitNoOpenSupport), "InitNoOpenSupport"},
+ {uint32(InitCaseSensitive), "InitCaseSensitive"},
+ {uint32(InitVolRename), "InitVolRename"},
+ {uint32(InitXtimes), "InitXtimes"},
+func (fl InitFlags) String() string {
+ return flagString(uint32(fl), initFlagNames)
+func flagString(f uint32, names []flagName) string {
+ var s string
+ if f == 0 {
+ return "0"
+ for _, n := range names {
+ if f&n.bit != 0 {
+ s += "+" + n.name
+ f &^= n.bit
+ if f != 0 {
+ s += fmt.Sprintf("%+#x", f)
+ return s[1:]
+// The ReleaseFlags are used in the Release exchange.
+type ReleaseFlags uint32
+ ReleaseFlush ReleaseFlags = 1 << 0
+func (fl ReleaseFlags) String() string {
+ return flagString(uint32(fl), releaseFlagNames)
+var releaseFlagNames = []flagName{
+ {uint32(ReleaseFlush), "ReleaseFlush"},
+// Opcodes
+ OpLookup = 1
+ OpForget = 2 // no reply
+ OpGetattr = 3
+ OpSetattr = 4
+ OpReadlink = 5
+ OpSymlink = 6
+ OpMknod = 8
+ OpMkdir = 9
+ OpUnlink = 10
+ OpRmdir = 11
+ OpRename = 12
+ OpLink = 13
+ OpOpen = 14
+ OpRead = 15
+ OpWrite = 16
+ OpStatfs = 17
+ OpRelease = 18
+ OpFsync = 20
+ OpSetxattr = 21
+ OpGetxattr = 22
+ OpListxattr = 23
+ OpRemovexattr = 24
+ OpFlush = 25
+ OpInit = 26
+ OpOpendir = 27
+ OpReaddir = 28
+ OpReleasedir = 29
+ OpFsyncdir = 30
+ OpGetlk = 31
+ OpSetlk = 32
+ OpSetlkw = 33
+ OpAccess = 34
+ OpCreate = 35
+ OpInterrupt = 36
+ OpBmap = 37
+ OpDestroy = 38
+ OpIoctl = 39 // Linux?
+ OpPoll = 40 // Linux?
+ // OS X
+ OpSetvolname = 61
+ OpGetxtimes = 62
+ OpExchange = 63
+type EntryOut struct {
+ Nodeid uint64 // Inode ID
+ Generation uint64 // Inode generation
+ EntryValid uint64 // Cache timeout for the name
+ AttrValid uint64 // Cache timeout for the attributes
+ EntryValidNsec uint32
+ AttrValidNsec uint32
+ Attr Attr
+func EntryOutSize(p Protocol) uintptr {
+ case p.LT(Protocol{7, 9}):
+ return unsafe.Offsetof(EntryOut{}.Attr) + unsafe.Offsetof(EntryOut{}.Attr.Blksize)
+ return unsafe.Sizeof(EntryOut{})
+type ForgetIn struct {
+ Nlookup uint64
+type GetattrIn struct {
+ GetattrFlags uint32
+ dummy uint32
+ Fh uint64
+type AttrOut struct {
+ Dummy uint32
+func AttrOutSize(p Protocol) uintptr {
+ return unsafe.Offsetof(AttrOut{}.Attr) + unsafe.Offsetof(AttrOut{}.Attr.Blksize)
+ return unsafe.Sizeof(AttrOut{})
+// OS X
+type GetxtimesOut struct {
+ Bkuptime uint64
+ Crtime uint64
+ BkuptimeNsec uint32
+ CrtimeNsec uint32
+type MknodIn struct {
+ Mode uint32
+ Rdev uint32
+ Umask uint32
+ padding uint32
+ // "filename\x00" follows.
+func MknodInSize(p Protocol) uintptr {
+ case p.LT(Protocol{7, 12}):
+ return unsafe.Offsetof(MknodIn{}.Umask)
+ return unsafe.Sizeof(MknodIn{})
+type MkdirIn struct {
+ // filename follows
+func MkdirInSize(p Protocol) uintptr {
+ return unsafe.Offsetof(MkdirIn{}.Umask) + 4
+ return unsafe.Sizeof(MkdirIn{})
+type RenameIn struct {
+ Newdir uint64
+ // "oldname\x00newname\x00" follows
+type ExchangeIn struct {
+ Olddir uint64
+ Options uint64
+type LinkIn struct {
+ Oldnodeid uint64
+type setattrInCommon struct {
+ Valid uint32
+ LockOwner uint64 // unused on OS X?
+ Atime uint64
+ Mtime uint64
+ Unused2 uint64
+ AtimeNsec uint32
+ MtimeNsec uint32
+ Unused3 uint32
+ Unused4 uint32
+ Unused5 uint32
+type OpenIn struct {
+ Unused uint32
+type OpenOut struct {
+ OpenFlags uint32
+type CreateIn struct {
+func CreateInSize(p Protocol) uintptr {
+ return unsafe.Offsetof(CreateIn{}.Umask)
+ return unsafe.Sizeof(CreateIn{})
+type ReleaseIn struct {
+ ReleaseFlags uint32
+ LockOwner uint32
+type FlushIn struct {
+ FlushFlags uint32
+ LockOwner uint64
+type ReadIn struct {
+ Offset uint64
+ Size uint32
+ ReadFlags uint32
+func ReadInSize(p Protocol) uintptr {
+ return unsafe.Offsetof(ReadIn{}.ReadFlags) + 4
+ return unsafe.Sizeof(ReadIn{})
+// The ReadFlags are passed in ReadRequest.
+type ReadFlags uint32
+ // LockOwner field is valid.
+ ReadLockOwner ReadFlags = 1 << 1
+var readFlagNames = []flagName{
+ {uint32(ReadLockOwner), "ReadLockOwner"},
+func (fl ReadFlags) String() string {
+ return flagString(uint32(fl), readFlagNames)
+type WriteIn struct {
+ WriteFlags uint32
+func WriteInSize(p Protocol) uintptr {
+ return unsafe.Offsetof(WriteIn{}.LockOwner)
+ return unsafe.Sizeof(WriteIn{})
+type WriteOut struct {
+// The WriteFlags are passed in WriteRequest.
+type WriteFlags uint32
+ WriteCache WriteFlags = 1 << 0
+ WriteLockOwner WriteFlags = 1 << 1
+var writeFlagNames = []flagName{
+ {uint32(WriteCache), "WriteCache"},
+ {uint32(WriteLockOwner), "WriteLockOwner"},
+func (fl WriteFlags) String() string {
+ return flagString(uint32(fl), writeFlagNames)
+const compatStatfsSize = 48
+type StatfsOut struct {
+ St Kstatfs
+type FsyncIn struct {
+ FsyncFlags uint32
+type setxattrInCommon struct {
+func (setxattrInCommon) GetPosition() uint32 {
+ return 0
+type getxattrInCommon struct {
+func (getxattrInCommon) GetPosition() uint32 {
+type GetxattrOut struct {
+type ListxattrIn struct {
+type LkIn struct {
+ Owner uint64
+ Lk fileLock
+ LkFlags uint32
+func LkInSize(p Protocol) uintptr {
+ return unsafe.Offsetof(LkIn{}.LkFlags)
+ return unsafe.Sizeof(LkIn{})
+type LkOut struct {
+type AccessIn struct {
+ Mask uint32
+type InitIn struct {
+ Major uint32
+ Minor uint32
+ MaxReadahead uint32
+const InitInSize = int(unsafe.Sizeof(InitIn{}))
+type InitOut struct {
+ MaxWrite uint32
+type InterruptIn struct {
+ Unique uint64
+type BmapIn struct {
+ Block uint64
+type BmapOut struct {
+type InHeader struct {
+ Len uint32
+ Opcode uint32
+ Nodeid uint64
+const InHeaderSize = int(unsafe.Sizeof(InHeader{}))
+type OutHeader struct {
+ Error int32
+ Ino uint64
+ Off uint64
+ Name [0]byte
+const DirentSize = 8 + 8 + 4 + 4
+ NotifyCodePoll int32 = 1
+ NotifyCodeInvalInode int32 = 2
+ NotifyCodeInvalEntry int32 = 3
+type NotifyInvalInodeOut struct {
+ Off int64
+ Len int64
+type NotifyInvalEntryOut struct {
+ Parent uint64
@@ -0,0 +1,88 @@
+type Attr struct {
+ Ctime uint64
+ Crtime_ uint64 // OS X only
+ CtimeNsec uint32
+ CrtimeNsec uint32 // OS X only
+ Flags_ uint32 // OS X only; see chflags(2)
+ Blksize uint32
+func (a *Attr) SetCrtime(s uint64, ns uint32) {
+ a.Crtime_, a.CrtimeNsec = s, ns
+func (a *Attr) SetFlags(f uint32) {
+ a.Flags_ = f
+type SetattrIn struct {
+ setattrInCommon
+ Bkuptime_ uint64
+ Chgtime_ uint64
+ ChgtimeNsec uint32
+ Flags_ uint32 // see chflags(2)
+func (in *SetattrIn) BkupTime() time.Time {
+ return time.Unix(int64(in.Bkuptime_), int64(in.BkuptimeNsec))
+func (in *SetattrIn) Chgtime() time.Time {
+ return time.Unix(int64(in.Chgtime_), int64(in.ChgtimeNsec))
+func (in *SetattrIn) Flags() uint32 {
+ return in.Flags_
+func openFlags(flags uint32) OpenFlags {
+ return OpenFlags(flags)
+type GetxattrIn struct {
+ getxattrInCommon
+ Position uint32
+func (g *GetxattrIn) GetPosition() uint32 {
+ return g.Position
+type SetxattrIn struct {
+ setxattrInCommon
+func (s *SetxattrIn) GetPosition() uint32 {
+ return s.Position
+import "time"
+func (a *Attr) Crtime() time.Time {
+ return time.Time{}
+ // Ignored on Linux.
+ // on amd64, the 32-bit O_LARGEFILE flag is always seen;
+ // on i386, the flag probably depends on the app
+ // requesting, but in any case should be utterly
+ // uninteresting to us here; our kernel protocol messages
+ // are not directly related to the client app's kernel
+ // API/ABI
+ flags &^= 0x8000
@@ -0,0 +1 @@
@@ -0,0 +1,75 @@
+// Protocol is a FUSE protocol version number.
+type Protocol struct {
+func (p Protocol) String() string {
+ return fmt.Sprintf("%d.%d", p.Major, p.Minor)
+// LT returns whether a is less than b.
+func (a Protocol) LT(b Protocol) bool {
+ return a.Major < b.Major ||
+ (a.Major == b.Major && a.Minor < b.Minor)
+// GE returns whether a is greater than or equal to b.
+func (a Protocol) GE(b Protocol) bool {
+ return a.Major > b.Major ||
+ (a.Major == b.Major && a.Minor >= b.Minor)
+func (a Protocol) is79() bool {
+ return a.GE(Protocol{7, 9})
+// HasAttrBlockSize returns whether Attr.BlockSize is respected by the
+// kernel.
+func (a Protocol) HasAttrBlockSize() bool {
+ return a.is79()
+// HasReadWriteFlags returns whether ReadRequest/WriteRequest
+// fields Flags and FileFlags are valid.
+func (a Protocol) HasReadWriteFlags() bool {
+// HasGetattrFlags returns whether GetattrRequest field Flags is
+// valid.
+func (a Protocol) HasGetattrFlags() bool {
+func (a Protocol) is710() bool {
+ return a.GE(Protocol{7, 10})
+// HasOpenNonSeekable returns whether OpenResponse field Flags flag
+// OpenNonSeekable is supported.
+func (a Protocol) HasOpenNonSeekable() bool {
+ return a.is710()
+func (a Protocol) is712() bool {
+ return a.GE(Protocol{7, 12})
+// HasUmask returns whether CreateRequest/MkdirRequest/MknodRequest
+// field Umask is valid.
+func (a Protocol) HasUmask() bool {
+ return a.is712()
+// HasInvalidate returns whether InvalidateNode/InvalidateEntry are
+// supported.
+func (a Protocol) HasInvalidate() bool {
@@ -0,0 +1,102 @@
+// Server is an interface for any type that knows how to serve ops read from a
+// connection.
+type Server interface {
+ // Read and serve ops from the supplied connection until EOF. Do not return
+ // until all operations have been responded to. Must not be called more than
+ // once.
+ ServeOps(*Connection)
+// Mount attempts to mount a file system on the given directory, using the
+// supplied Server to serve connection requests. It blocks until the file
+// system is successfully mounted.
+func Mount(
+ dir string,
+ server Server,
+ config *MountConfig) (mfs *MountedFileSystem, err error) {
+ // Sanity check: make sure the mount point exists and is a directory. This
+ // saves us from some confusing errors later on OS X.
+ fi, err := os.Stat(dir)
+ case os.IsNotExist(err):
+ case err != nil:
+ err = fmt.Errorf("Statting mount point: %v", err)
+ case !fi.IsDir():
+ err = fmt.Errorf("Mount point %s is not a directory", dir)
+ // Initialize the struct.
+ mfs = &MountedFileSystem{
+ dir: dir,
+ joinStatusAvailable: make(chan struct{}),
+ // Begin the mounting process, which will continue in the background.
+ ready := make(chan error, 1)
+ dev, err := mount(dir, config, ready)
+ err = fmt.Errorf("mount: %v", err)
+ // Choose a parent context for ops.
+ cfgCopy := *config
+ if cfgCopy.OpContext == nil {
+ cfgCopy.OpContext = context.Background()
+ // Create a Connection object wrapping the device.
+ connection, err := newConnection(
+ cfgCopy,
+ config.DebugLogger,
+ config.ErrorLogger,
+ dev)
+ err = fmt.Errorf("newConnection: %v", err)
+ // Serve the connection in the background. When done, set the join status.
+ go func() {
+ server.ServeOps(connection)
+ mfs.joinStatus = connection.close()
+ close(mfs.joinStatusAvailable)
+ // Wait for the mount process to complete.
+ if err = <-ready; err != nil {
+ err = fmt.Errorf("mount (background): %v", err)
@@ -0,0 +1,232 @@
+// Optional configuration accepted by Mount.
+type MountConfig struct {
+ // The context from which every op read from the connetion by the sever
+ // should inherit. If nil, context.Background() will be used.
+ OpContext context.Context
+ // If non-empty, the name of the file system as displayed by e.g. `mount`.
+ // This is important because the `umount` command requires root privileges if
+ // it doesn't agree with /etc/fstab.
+ FSName string
+ // Mount the file system in read-only mode. File modes will appear as normal,
+ // but opening a file for writing and metadata operations like chmod,
+ // chtimes, etc. will fail.
+ ReadOnly bool
+ // A logger to use for logging errors. All errors are logged, with the
+ // exception of a few blacklisted errors that are expected. If nil, no error
+ // logging is performed.
+ ErrorLogger *log.Logger
+ // A logger to use for logging debug information. If nil, no debug logging is
+ // performed.
+ DebugLogger *log.Logger
+ // Linux only. OS X always behaves as if writeback caching is disabled.
+ // By default on Linux we allow the kernel to perform writeback caching
+ // (cf. http://goo.gl/LdZzo1):
+ // * When the user calls write(2), the kernel sticks the user's data into
+ // its page cache. Only later does it call through to the file system,
+ // potentially after coalescing multiple small user writes.
+ // * The file system may receive multiple write ops from the kernel
+ // concurrently if there is a lot of page cache data to flush.
+ // * Write performance may be significantly improved due to the user and
+ // the kernel not waiting for serial round trips to the file system. This
+ // is especially true if the user makes tiny writes.
+ // * close(2) (and anything else calling f_op->flush) causes all dirty
+ // pages to be written out before it proceeds to send a FlushFileOp
+ // (cf. https://goo.gl/TMrY6X).
+ // * Similarly, close(2) causes the kernel to send a setattr request
+ // filling in the mtime if any dirty pages were flushed, since the time
+ // at which the pages were written to the file system can't be trusted.
+ // * close(2) (and anything else calling f_op->flush) writes out all dirty
+ // pages, then sends a setattr request with an appropriate mtime for
+ // those writes if there were any, and only then proceeds to send a
+ // flush.
+ // Code walk:
+ // * (https://goo.gl/zTIZQ9) fuse_flush calls write_inode_now before
+ // calling the file system. The latter eventually calls into
+ // __writeback_single_inode.
+ // * (https://goo.gl/L7Z2w5) __writeback_single_inode calls
+ // do_writepages, which writes out any dirty pages.
+ // * (https://goo.gl/DOPgla) __writeback_single_inode later calls
+ // write_inode, which calls into the superblock op struct's write_inode
+ // member. For fuse, this is fuse_write_inode
+ // (cf. https://goo.gl/eDSKOX).
+ // * (https://goo.gl/PbkGA1) fuse_write_inode calls fuse_flush_times.
+ // * (https://goo.gl/ig8x9V) fuse_flush_times sends a setttr request
+ // for setting the inode's mtime.
+ // However, this brings along some caveats:
+ // * The file system must handle SetInodeAttributesOp or close(2) will fail,
+ // due to the call chain into fuse_flush_times listed above.
+ // * The kernel caches mtime and ctime regardless of whether the file
+ // system tells it to do so, disregarding the result of further getattr
+ // requests (cf. https://goo.gl/3ZZMUw, https://goo.gl/7WtQUp). It
+ // appears this may be true of the file size, too. Writeback caching may
+ // therefore not be suitable for file systems where these attributes can
+ // spontaneously change for reasons the kernel doesn't observe. See
+ // http://goo.gl/V5WQCN for more discussion.
+ // Setting DisableWritebackCaching disables this behavior. Instead the file
+ // system is called one or more times for each write(2), and the user's
+ // syscall doesn't return until the file system returns.
+ DisableWritebackCaching bool
+ // OS X only.
+ // Normally on OS X we mount with the novncache option
+ // (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel.
+ // This is because osxfuse does not honor the entry expiration values we
+ // return to it, instead caching potentially forever (cf.
+ // http://goo.gl/8yR0Ie), and it is probably better to fail to cache than to
+ // cache for too long, since the latter is more likely to hide consistency
+ // bugs that are difficult to detect and diagnose.
+ // This field disables the use of novncache, restoring entry caching. Beware:
+ // the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and
+ // entries will be cached for an arbitrarily long time.
+ EnableVnodeCaching bool
+ // The name of the mounted volume, as displayed in the Finder. If empty, a
+ // default name involving the string 'osxfuse' is used.
+ VolumeName string
+ // Additional key=value options to pass unadulterated to the underlying mount
+ // command. See `man 8 mount`, the fuse documentation, etc. for
+ // system-specific information.
+ // For expert use only! May invalidate other guarantees made in the
+ // documentation for this package.
+ Options map[string]string
+// Create a map containing all of the key=value mount options to be given to
+// the mount helper.
+func (c *MountConfig) toMap() (opts map[string]string) {
+ isDarwin := runtime.GOOS == "darwin"
+ opts = make(map[string]string)
+ // Enable permissions checking in the kernel. See the comments on
+ // InodeAttributes.Mode.
+ opts["default_permissions"] = ""
+ // HACK(jacobsa): Work around what appears to be a bug in systemd v219, as
+ // shipped in Ubuntu 15.04, where it automatically unmounts any file system
+ // that doesn't set an explicit name.
+ // When Ubuntu contains systemd v220, this workaround should be removed and
+ // the systemd bug reopened if the problem persists.
+ // Cf. https://github.com/bazil/fuse/issues/89
+ // Cf. https://bugs.freedesktop.org/show_bug.cgi?id=90907
+ fsname := c.FSName
+ if runtime.GOOS == "linux" && fsname == "" {
+ fsname = "some_fuse_file_system"
+ // Special file system name?
+ if fsname != "" {
+ opts["fsname"] = fsname
+ // Read only?
+ if c.ReadOnly {
+ opts["ro"] = ""
+ // Handle OS X options.
+ if isDarwin {
+ if !c.EnableVnodeCaching {
+ opts["novncache"] = ""
+ if c.VolumeName != "" {
+ // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options#volname
+ opts["volname"] = c.VolumeName
+ // OS X: disable the use of "Apple Double" (._foo and .DS_Store) files, which
+ // just add noise to debug output and can have significant cost on
+ // network-based file systems.
+ // Cf. https://github.com/osxfuse/osxfuse/wiki/Mount-options
+ opts["noappledouble"] = ""
+ // Last but not least: other user-supplied options.
+ for k, v := range c.Options {
+ opts[k] = v
+func escapeOptionsKey(s string) (res string) {
+ res = s
+ res = strings.Replace(res, `\`, `\\`, -1)
+ res = strings.Replace(res, `,`, `\,`, -1)
+// Create an options string suitable for passing to the mount helper.
+func (c *MountConfig) toOptionsString() string {
+ for k, v := range c.toMap() {
+ k = escapeOptionsKey(k)
+ component := k
+ if v != "" {
+ component = fmt.Sprintf("%s=%s", k, v)
+ components = append(components, component)
+ return strings.Join(components, ",")
@@ -0,0 +1,217 @@
+ "os/exec"
+ "strconv"
+var errNoAvail = errors.New("no available fuse devices")
+var errNotLoaded = errors.New("osxfuse is not loaded")
+// errOSXFUSENotFound is returned from Mount when the OSXFUSE installation is
+// not detected. Make sure OSXFUSE is installed.
+var errOSXFUSENotFound = errors.New("cannot locate OSXFUSE")
+// osxfuseInstallation describes the paths used by an installed OSXFUSE
+// version.
+type osxfuseInstallation struct {
+ // Prefix for the device file. At mount time, an incrementing number is
+ // suffixed until a free FUSE device is found.
+ DevicePrefix string
+ // Path of the load helper, used to load the kernel extension if no device
+ // files are found.
+ Load string
+ // Path of the mount helper, used for the actual mount operation.
+ Mount string
+ // Environment variable used to pass the path to the executable calling the
+ // mount helper.
+ DaemonVar string
+var (
+ osxfuseInstallations = []osxfuseInstallation{
+ // v3
+ DevicePrefix: "/dev/osxfuse",
+ Load: "/Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse",
+ Mount: "/Library/Filesystems/osxfuse.fs/Contents/Resources/mount_osxfuse",
+ DaemonVar: "MOUNT_OSXFUSE_DAEMON_PATH",
+ },
+ // v2
+ Load: "/Library/Filesystems/osxfusefs.fs/Support/load_osxfusefs",
+ Mount: "/Library/Filesystems/osxfusefs.fs/Support/mount_osxfusefs",
+ DaemonVar: "MOUNT_FUSEFS_DAEMON_PATH",
+func loadOSXFUSE(bin string) error {
+ cmd := exec.Command(bin)
+ cmd.Dir = "/"
+ err := cmd.Run()
+ return err
+func openOSXFUSEDev(devPrefix string) (dev *os.File, err error) {
+ // Try each device name.
+ for i := uint64(0); ; i++ {
+ path := devPrefix + strconv.FormatUint(i, 10)
+ dev, err = os.OpenFile(path, os.O_RDWR, 0000)
+ if os.IsNotExist(err) {
+ if i == 0 {
+ // Not even the first device was found. Fuse must not be loaded.
+ err = errNotLoaded
+ // Otherwise we've run out of kernel-provided devices
+ err = errNoAvail
+ if err2, ok := err.(*os.PathError); ok && err2.Err == syscall.EBUSY {
+ // This device is in use; try the next one.
+func callMount(
+ bin string,
+ daemonVar string,
+ cfg *MountConfig,
+ dev *os.File,
+ ready chan<- error) (err error) {
+ // The mount helper doesn't understand any escaping.
+ for k, v := range cfg.toMap() {
+ if strings.Contains(k, ",") || strings.Contains(v, ",") {
+ return fmt.Errorf(
+ "mount options cannot contain commas on darwin: %q=%q",
+ k,
+ v)
+ // Call the mount helper, passing in the device file and saving output into a
+ // buffer.
+ cmd := exec.Command(
+ bin,
+ "-o", cfg.toOptionsString(),
+ // Tell osxfuse-kext how large our buffer is. It must split
+ // writes larger than this into multiple writes.
+ // OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
+ // this instead.
+ "-o", "iosize="+strconv.FormatUint(buffer.MaxWriteSize, 10),
+ // refers to fd passed in cmd.ExtraFiles
+ "3",
+ dir,
+ )
+ cmd.ExtraFiles = []*os.File{dev}
+ cmd.Env = os.Environ()
+ // OSXFUSE <3.3.0
+ cmd.Env = append(cmd.Env, "MOUNT_FUSEFS_CALL_BY_LIB=")
+ // OSXFUSE >=3.3.0
+ cmd.Env = append(cmd.Env, "MOUNT_OSXFUSE_CALL_BY_LIB=")
+ daemon := os.Args[0]
+ if daemonVar != "" {
+ cmd.Env = append(cmd.Env, daemonVar+"="+daemon)
+ var buf bytes.Buffer
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ err = cmd.Start()
+ // In the background, wait for the command to complete.
+ err := cmd.Wait()
+ if buf.Len() > 0 {
+ output := buf.Bytes()
+ output = bytes.TrimRight(output, "\n")
+ err = fmt.Errorf("%v: %s", err, output)
+ ready <- err
+// Begin the process of mounting at the given directory, returning a connection
+// to the kernel. Mounting continues in the background, and is complete when an
+// error is written to the supplied channel. The file system may need to
+// service the connection in order for mounting to complete.
+func mount(
+ ready chan<- error) (dev *os.File, err error) {
+ // Find the version of osxfuse installed on this machine.
+ for _, loc := range osxfuseInstallations {
+ if _, err := os.Stat(loc.Mount); os.IsNotExist(err) {
+ // try the other locations
+ // Open the device.
+ dev, err = openOSXFUSEDev(loc.DevicePrefix)
+ // Special case: we may need to explicitly load osxfuse. Load it, then
+ // try again.
+ if err == errNotLoaded {
+ err = loadOSXFUSE(loc.Load)
+ err = fmt.Errorf("loadOSXFUSE: %v", err)
+ // Propagate errors.
+ err = fmt.Errorf("openOSXFUSEDev: %v", err)
+ // Call the mount binary with the device.
+ err = callMount(loc.Mount, loc.DaemonVar, dir, cfg, dev, ready)
+ dev.Close()
+ err = fmt.Errorf("callMount: %v", err)
+ err = errOSXFUSENotFound
@@ -0,0 +1,113 @@
+ "net"
+ // On linux, mounting is never delayed.
+ ready <- nil
+ // Create a socket pair.
+ fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
+ err = fmt.Errorf("Socketpair: %v", err)
+ // Wrap the sockets into os.File objects that we will pass off to fusermount.
+ writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
+ defer writeFile.Close()
+ readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
+ defer readFile.Close()
+ // Start fusermount, passing it a buffer in which to write stderr.
+ var stderr bytes.Buffer
+ "fusermount",
+ "--",
+ cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
+ cmd.ExtraFiles = []*os.File{writeFile}
+ cmd.Stderr = &stderr
+ // Run the command.
+ err = cmd.Run()
+ err = fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes())
+ // Wrap the socket file in a connection.
+ c, err := net.FileConn(readFile)
+ err = fmt.Errorf("FileConn: %v", err)
+ defer c.Close()
+ // We expect to have a Unix domain socket.
+ uc, ok := c.(*net.UnixConn)
+ err = fmt.Errorf("Expected UnixConn, got %T", c)
+ // Read a message.
+ buf := make([]byte, 32) // expect 1 byte
+ oob := make([]byte, 32) // expect 24 bytes
+ _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
+ err = fmt.Errorf("ReadMsgUnix: %v", err)
+ // Parse the message.
+ scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
+ err = fmt.Errorf("ParseSocketControlMessage: %v", err)
+ // We expect one message.
+ if len(scms) != 1 {
+ err = fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
+ scm := scms[0]
+ // Pull out the FD returned by fusermount
+ gotFds, err := syscall.ParseUnixRights(&scm)
+ err = fmt.Errorf("syscall.ParseUnixRights: %v", err)
+ if len(gotFds) != 1 {
+ err = fmt.Errorf("wanted 1 fd; got %#v", gotFds)
+ // Turn the FD into an os.File.
+ dev = os.NewFile(uintptr(gotFds[0]), "/dev/fuse")
@@ -0,0 +1,142 @@
+package fuse_test
+ "github.com/jacobsa/fuse/fuseutil"
+// minimalFS
+// A minimal fuseutil.FileSystem that can successfully mount but do nothing
+// else.
+type minimalFS struct {
+ fuseutil.NotImplementedFileSystem
+func (fs *minimalFS) StatFS(
+// Tests
+func TestSuccessfulMount(t *testing.T) {
+ ctx := context.Background()
+ // Set up a temporary directory.
+ dir, err := ioutil.TempDir("", "mount_test")
+ t.Fatal("ioutil.TempDir: %v", err)
+ defer os.RemoveAll(dir)
+ // Mount.
+ fs := &minimalFS{}
+ mfs, err := fuse.Mount(
+ fuseutil.NewFileSystemServer(fs),
+ &fuse.MountConfig{})
+ t.Fatalf("fuse.Mount: %v", err)
+ if err := mfs.Join(ctx); err != nil {
+ t.Errorf("Joining: %v", err)
+ defer fuse.Unmount(mfs.Dir())
+func TestNonEmptyMountPoint(t *testing.T) {
+ // osxfuse appears to be happy to mount over a non-empty mount point.
+ // We leave this test in for Linux, because it tickles the behavior of
+ // fusermount writing to stderr and exiting with an error code. We want to
+ // make sure that a descriptive error makes it back to the user.
+ if runtime.GOOS == "darwin" {
+ // Add a file within it.
+ err = ioutil.WriteFile(path.Join(dir, "foo"), []byte{}, 0600)
+ t.Fatalf("ioutil.WriteFile: %v", err)
+ // Attempt to mount.
+ fuse.Unmount(mfs.Dir())
+ mfs.Join(ctx)
+ t.Fatal("fuse.Mount returned nil")
+ const want = "not empty"
+ if got := err.Error(); !strings.Contains(got, want) {
+ t.Errorf("Unexpected error: %v", got)
+func TestNonexistentMountPoint(t *testing.T) {
+ // Attempt to mount into a sub-directory that doesn't exist.
+ path.Join(dir, "foo"),
+ const want = "no such file"
@@ -0,0 +1,49 @@
+import "golang.org/x/net/context"
+// MountedFileSystem represents the status of a mount operation, with a method
+// that waits for unmounting.
+type MountedFileSystem struct {
+ dir string
+ // The result to return from Join. Not valid until the channel is closed.
+ joinStatus error
+ joinStatusAvailable chan struct{}
+// Dir returns the directory on which the file system is mounted (or where we
+// attempted to mount it.)
+func (mfs *MountedFileSystem) Dir() string {
+ return mfs.dir
+// Join blocks until a mounted file system has been unmounted. It does not
+// return successfully until all ops read from the connection have been
+// responded to (i.e. the file system server has finished processing all
+// in-flight ops).
+// The return value will be non-nil if anything unexpected happened while
+// serving. May be called multiple times.
+func (mfs *MountedFileSystem) Join(ctx context.Context) error {
+ select {
+ case <-mfs.joinStatusAvailable:
+ return mfs.joinStatus
+ case <-ctx.Done():
+ return ctx.Err()
@@ -0,0 +1,46 @@
+// A sentinel used for unknown ops. The user is expected to respond with a
+// non-nil error.
+type unknownOp struct {
+ OpCode uint32
+// Causes us to cancel the associated context.
+type interruptOp struct {
+ FuseID uint64
+// Required in order to mount on Linux and OS X.
+type initOp struct {
+ // In
+ Kernel fusekernel.Protocol
+ // In/out
+ Flags fusekernel.InitFlags
+ // Out
+ Library fusekernel.Protocol
@@ -0,0 +1,378 @@
+package cachingfs
+ // Sizes of the files according to the file system.
+ FooSize = 123
+ BarSize = 456
+// A file system with a fixed structure that looks like this:
+// foo
+// dir/
+// bar
+// The file system is configured with durations that specify how long to allow
+// inode entries and attributes to be cached, used when responding to fuse
+// requests. It also exposes methods for renumbering inodes and updating mtimes
+// that are useful in testing that these durations are honored.
+// Each file responds to reads with random contents. SetKeepCache can be used
+// to control whether the response to OpenFileOp tells the kernel to keep the
+// file's data in the page cache or not.
+type CachingFS interface {
+ fuseutil.FileSystem
+ // Return the current inode ID of the file/directory with the given name.
+ FooID() fuseops.InodeID
+ DirID() fuseops.InodeID
+ BarID() fuseops.InodeID
+ // Cause the inode IDs to change to values that have never before been used.
+ RenumberInodes()
+ // Cause further queries for the attributes of inodes to use the supplied
+ // time as the inode's mtime.
+ SetMtime(mtime time.Time)
+ // Instruct the file system whether or not to reply to OpenFileOp with
+ // FOPEN_KEEP_CACHE set.
+ SetKeepCache(keep bool)
+// Create a file system that issues cacheable responses according to the
+// following rules:
+// * LookUpInodeResponse.Entry.EntryExpiration is set according to
+// lookupEntryTimeout.
+// * GetInodeAttributesResponse.AttributesExpiration is set according to
+// getattrTimeout.
+// * Nothing else is marked cacheable. (In particular, the attributes
+// returned by LookUpInode are not cacheable.)
+func NewCachingFS(
+ lookupEntryTimeout time.Duration,
+ getattrTimeout time.Duration) (fs CachingFS, err error) {
+ roundUp := func(n fuseops.InodeID) fuseops.InodeID {
+ return numInodes * ((n + numInodes - 1) / numInodes)
+ cfs := &cachingFS{
+ lookupEntryTimeout: lookupEntryTimeout,
+ getattrTimeout: getattrTimeout,
+ baseID: roundUp(fuseops.RootInodeID + 1),
+ mtime: time.Now(),
+ cfs.mu = syncutil.NewInvariantMutex(cfs.checkInvariants)
+ fs = cfs
+ // Inode IDs are issued such that "foo" always receives an ID that is
+ // congruent to fooOffset modulo numInodes, etc.
+ fooOffset = iota
+ dirOffset
+ barOffset
+ numInodes
+type cachingFS struct {
+ /////////////////////////
+ // Constant data
+ lookupEntryTimeout time.Duration
+ getattrTimeout time.Duration
+ // Mutable state
+ mu syncutil.InvariantMutex
+ keepPageCache bool
+ // The current ID of the lowest numbered non-root inode.
+ // INVARIANT: baseID > fuseops.RootInodeID
+ // INVARIANT: baseID % numInodes == 0
+ baseID fuseops.InodeID
+ mtime time.Time
+// Helpers
+func (fs *cachingFS) checkInvariants() {
+ if fs.baseID <= fuseops.RootInodeID || fs.baseID%numInodes != 0 {
+ panic(fmt.Sprintf("Bad baseID: %v", fs.baseID))
+// LOCKS_REQUIRED(fs.mu)
+func (fs *cachingFS) fooID() fuseops.InodeID {
+ return fs.baseID + fooOffset
+func (fs *cachingFS) dirID() fuseops.InodeID {
+ return fs.baseID + dirOffset
+func (fs *cachingFS) barID() fuseops.InodeID {
+ return fs.baseID + barOffset
+func (fs *cachingFS) rootAttrs() fuseops.InodeAttributes {
+ return fuseops.InodeAttributes{
+ Mode: os.ModeDir | 0777,
+ Mtime: fs.mtime,
+func (fs *cachingFS) fooAttrs() fuseops.InodeAttributes {
+ Nlink: 1,
+ Size: FooSize,
+ Mode: 0777,
+func (fs *cachingFS) dirAttrs() fuseops.InodeAttributes {
+func (fs *cachingFS) barAttrs() fuseops.InodeAttributes {
+ Size: BarSize,
+// Public interface
+// LOCKS_EXCLUDED(fs.mu)
+func (fs *cachingFS) FooID() fuseops.InodeID {
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+ return fs.fooID()
+func (fs *cachingFS) DirID() fuseops.InodeID {
+ return fs.dirID()
+func (fs *cachingFS) BarID() fuseops.InodeID {
+ return fs.barID()
+func (fs *cachingFS) RenumberInodes() {
+ fs.baseID += numInodes
+func (fs *cachingFS) SetMtime(mtime time.Time) {
+ fs.mtime = mtime
+func (fs *cachingFS) SetKeepCache(keep bool) {
+ fs.keepPageCache = keep
+// FileSystem methods
+func (fs *cachingFS) StatFS(
+func (fs *cachingFS) LookUpInode(
+ // Find the ID and attributes.
+ var id fuseops.InodeID
+ var attrs fuseops.InodeAttributes
+ switch op.Name {
+ case "foo":
+ // Parent must be the root.
+ if op.Parent != fuseops.RootInodeID {
+ err = fuse.ENOENT
+ id = fs.fooID()
+ attrs = fs.fooAttrs()
+ case "dir":
+ id = fs.dirID()
+ attrs = fs.dirAttrs()
+ case "bar":
+ // Parent must be dir.
+ if op.Parent == fuseops.RootInodeID || op.Parent%numInodes != dirOffset {
+ id = fs.barID()
+ attrs = fs.barAttrs()
+ // Fill in the response.
+ op.Entry.Child = id
+ op.Entry.Attributes = attrs
+ op.Entry.EntryExpiration = time.Now().Add(fs.lookupEntryTimeout)
+func (fs *cachingFS) GetInodeAttributes(
+ // Figure out which inode the request is for.
+ case op.Inode == fuseops.RootInodeID:
+ attrs = fs.rootAttrs()
+ case op.Inode%numInodes == fooOffset:
+ case op.Inode%numInodes == dirOffset:
+ case op.Inode%numInodes == barOffset:
+ op.Attributes = attrs
+ op.AttributesExpiration = time.Now().Add(fs.getattrTimeout)
+func (fs *cachingFS) OpenDir(
+func (fs *cachingFS) OpenFile(
+ op.KeepPageCache = fs.keepPageCache
+func (fs *cachingFS) ReadFile(
+ op.BytesRead, err = io.ReadFull(rand.Reader, op.Dst)
@@ -0,0 +1,725 @@
+package cachingfs_test
+ "github.com/jacobsa/fuse/samples"
+ "github.com/jacobsa/fuse/samples/cachingfs"
+ . "github.com/jacobsa/oglematchers"
+ "github.com/jacobsa/timeutil"
+func TestCachingFS(t *testing.T) { RunTests(t) }
+// Boilerplate
+type cachingFSTest struct {
+ samples.SampleTest
+ fs cachingfs.CachingFS
+ initialMtime time.Time
+var _ TearDownInterface = &cachingFSTest{}
+func (t *cachingFSTest) setUp(
+ ti *TestInfo,
+ getattrTimeout time.Duration) {
+ // We assert things about whether or not mtimes are cached, but writeback
+ // caching causes them to always be cached. Turn it off.
+ t.MountConfig.DisableWritebackCaching = true
+ // Create the file system.
+ t.fs, err = cachingfs.NewCachingFS(lookupEntryTimeout, getattrTimeout)
+ t.Server = fuseutil.NewFileSystemServer(t.fs)
+ // Mount it.
+ t.SampleTest.SetUp(ti)
+ // Set up the mtime.
+ t.initialMtime = time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)
+ t.fs.SetMtime(t.initialMtime)
+func (t *cachingFSTest) statAll() (foo, dir, bar os.FileInfo) {
+ foo, err = os.Stat(path.Join(t.Dir, "foo"))
+ dir, err = os.Stat(path.Join(t.Dir, "dir"))
+ bar, err = os.Stat(path.Join(t.Dir, "dir/bar"))
+func (t *cachingFSTest) openFiles() (foo, dir, bar *os.File) {
+ foo, err = os.Open(path.Join(t.Dir, "foo"))
+ dir, err = os.Open(path.Join(t.Dir, "dir"))
+ bar, err = os.Open(path.Join(t.Dir, "dir/bar"))
+func (t *cachingFSTest) statFiles(
+ f, g, h *os.File) (foo, dir, bar os.FileInfo) {
+ foo, err = f.Stat()
+ dir, err = g.Stat()
+ bar, err = h.Stat()
+func getInodeID(fi os.FileInfo) uint64 {
+ return fi.Sys().(*syscall.Stat_t).Ino
+// Basics
+type BasicsTest struct {
+ cachingFSTest
+var _ SetUpInterface = &BasicsTest{}
+func init() { RegisterTestSuite(&BasicsTest{}) }
+func (t *BasicsTest) SetUp(ti *TestInfo) {
+ const (
+ lookupEntryTimeout = 0
+ getattrTimeout = 0
+ t.cachingFSTest.setUp(ti, lookupEntryTimeout, getattrTimeout)
+func (t *BasicsTest) StatNonexistent() {
+ names := []string{
+ "blah",
+ "bar",
+ "dir/blah",
+ "dir/dir",
+ "dir/foo",
+ _, err := os.Stat(path.Join(t.Dir, n))
+ AssertNe(nil, err)
+ ExpectTrue(os.IsNotExist(err), "n: %s, err: %v", n, err)
+func (t *BasicsTest) StatFoo() {
+ fi, err := os.Stat(path.Join(t.Dir, "foo"))
+ ExpectEq("foo", fi.Name())
+ ExpectEq(cachingfs.FooSize, fi.Size())
+ ExpectEq(0777, fi.Mode())
+ ExpectThat(fi.ModTime(), timeutil.TimeEq(t.initialMtime))
+ ExpectFalse(fi.IsDir())
+ ExpectEq(t.fs.FooID(), getInodeID(fi))
+ ExpectEq(1, fi.Sys().(*syscall.Stat_t).Nlink)
+func (t *BasicsTest) StatDir() {
+ fi, err := os.Stat(path.Join(t.Dir, "dir"))
+ ExpectEq("dir", fi.Name())
+ ExpectEq(os.ModeDir|0777, fi.Mode())
+ ExpectTrue(fi.IsDir())
+ ExpectEq(t.fs.DirID(), getInodeID(fi))
+func (t *BasicsTest) StatBar() {
+ fi, err := os.Stat(path.Join(t.Dir, "dir/bar"))
+ ExpectEq("bar", fi.Name())
+ ExpectEq(cachingfs.BarSize, fi.Size())
+ ExpectEq(t.fs.BarID(), getInodeID(fi))
+// No caching
+type NoCachingTest struct {
+var _ SetUpInterface = &NoCachingTest{}
+func init() { RegisterTestSuite(&NoCachingTest{}) }
+func (t *NoCachingTest) SetUp(ti *TestInfo) {
+func (t *NoCachingTest) StatStat() {
+ fooBefore, dirBefore, barBefore := t.statAll()
+ fooAfter, dirAfter, barAfter := t.statAll()
+ // Make sure everything matches.
+ ExpectThat(fooAfter.ModTime(), timeutil.TimeEq(fooBefore.ModTime()))
+ ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(dirBefore.ModTime()))
+ ExpectThat(barAfter.ModTime(), timeutil.TimeEq(barBefore.ModTime()))
+ ExpectEq(getInodeID(fooBefore), getInodeID(fooAfter))
+ ExpectEq(getInodeID(dirBefore), getInodeID(dirAfter))
+ ExpectEq(getInodeID(barBefore), getInodeID(barAfter))
+func (t *NoCachingTest) StatRenumberStat() {
+ t.statAll()
+ t.fs.RenumberInodes()
+ // We should see the new inode IDs, because the entries should not have been
+ // cached.
+ ExpectEq(t.fs.FooID(), getInodeID(fooAfter))
+ ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
+ ExpectEq(t.fs.BarID(), getInodeID(barAfter))
+func (t *NoCachingTest) StatMtimeStat() {
+ newMtime := t.initialMtime.Add(time.Second)
+ t.fs.SetMtime(newMtime)
+ // We should see the new mtimes, because the attributes should not have been
+ ExpectThat(fooAfter.ModTime(), timeutil.TimeEq(newMtime))
+ ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime))
+ ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
+func (t *NoCachingTest) StatRenumberMtimeStat() {
+ // We should see the new inode IDs and mtimes, because nothing should have
+ // been cached.
+// Entry caching
+type EntryCachingTest struct {
+var _ SetUpInterface = &EntryCachingTest{}
+func init() { RegisterTestSuite(&EntryCachingTest{}) }
+func (t *EntryCachingTest) SetUp(ti *TestInfo) {
+ t.lookupEntryTimeout = 250 * time.Millisecond
+ t.SampleTest.MountConfig.EnableVnodeCaching = true
+ t.cachingFSTest.setUp(ti, t.lookupEntryTimeout, 0)
+func (t *EntryCachingTest) StatStat() {
+func (t *EntryCachingTest) StatRenumberStat() {
+ // We should still see the old inode IDs, because the inode entries should
+ // have been cached.
+ // But after waiting for the entry cache to expire, we should see the new
+ // IDs.
+ // Note that the cache is not guaranteed to expire on darwin. See notes on
+ // fuse.MountConfig.EnableVnodeCaching.
+ if runtime.GOOS != "darwin" {
+ time.Sleep(2 * t.lookupEntryTimeout)
+ fooAfter, dirAfter, barAfter = t.statAll()
+func (t *EntryCachingTest) StatMtimeStat() {
+func (t *EntryCachingTest) StatRenumberMtimeStat() {
+ // have been cached. But the attributes should not have been.
+ // After waiting for the entry cache to expire, we should see fresh
+ // everything.
+// Attribute caching
+type AttributeCachingTest struct {
+var _ SetUpInterface = &AttributeCachingTest{}
+func init() { RegisterTestSuite(&AttributeCachingTest{}) }
+func (t *AttributeCachingTest) SetUp(ti *TestInfo) {
+ t.getattrTimeout = 250 * time.Millisecond
+ t.cachingFSTest.setUp(ti, 0, t.getattrTimeout)
+func (t *AttributeCachingTest) StatStat() {
+func (t *AttributeCachingTest) StatRenumberStat() {
+func (t *AttributeCachingTest) StatMtimeStat_ViaPath() {
+ // Since we don't have entry caching enabled, the call above had to look up
+ // the entry again. With the lookup we returned new attributes, so it's
+ // possible that the mtime will be fresh. On Linux it appears to be, and on
+ // OS X it appears to not be.
+ m := AnyOf(timeutil.TimeEq(newMtime), timeutil.TimeEq(t.initialMtime))
+ ExpectThat(fooAfter.ModTime(), m)
+ ExpectThat(dirAfter.ModTime(), m)
+ ExpectThat(barAfter.ModTime(), m)
+func (t *AttributeCachingTest) StatMtimeStat_ViaFileDescriptor() {
+ // Open everything, fixing a particular inode number for each.
+ foo, dir, bar := t.openFiles()
+ foo.Close()
+ dir.Close()
+ bar.Close()
+ fooBefore, dirBefore, barBefore := t.statFiles(foo, dir, bar)
+ fooAfter, dirAfter, barAfter := t.statFiles(foo, dir, bar)
+ // We should still see the old cached mtime.
+ // After waiting for the attribute cache to expire, we should see the fresh
+ // mtime.
+ time.Sleep(2 * t.getattrTimeout)
+ fooAfter, dirAfter, barAfter = t.statFiles(foo, dir, bar)
+func (t *AttributeCachingTest) StatRenumberMtimeStat_ViaPath() {
+ // We should see new everything, because this is the first time the new
+ // inodes have been encountered. Entries for the old ones should not have
+ // been cached, because we have entry caching disabled.
+func (t *AttributeCachingTest) StatRenumberMtimeStat_ViaFileDescriptor() {
+ // We should still see the old cached mtime with the old inode ID.
+ // mtime, still with the old inode ID.
+// Page cache
+type PageCacheTest struct {
+var _ SetUpInterface = &PageCacheTest{}
+func init() { RegisterTestSuite(&PageCacheTest{}) }
+func (t *PageCacheTest) SetUp(ti *TestInfo) {
+func (t *PageCacheTest) SingleFileHandle_NoKeepCache() {
+ t.fs.SetKeepCache(false)
+ // Open the file.
+ f, err := os.Open(path.Join(t.Dir, "foo"))
+ // Read its contents once.
+ f.Seek(0, 0)
+ c1, err := ioutil.ReadAll(f)
+ AssertEq(cachingfs.FooSize, len(c1))
+ // And again.
+ c2, err := ioutil.ReadAll(f)
+ AssertEq(cachingfs.FooSize, len(c2))
+ // We should have seen the same contents each time.
+ ExpectTrue(bytes.Equal(c1, c2))
+func (t *PageCacheTest) SingleFileHandle_KeepCache() {
+ t.fs.SetKeepCache(true)
+func (t *PageCacheTest) TwoFileHandles_NoKeepCache() {
+ // SetKeepCache(false) doesn't work on OS X. See the notes on
+ // OpenFileOp.KeepPageCache.
+ f1, err := os.Open(path.Join(t.Dir, "foo"))
+ defer f1.Close()
+ f1.Seek(0, 0)
+ c1, err := ioutil.ReadAll(f1)
+ // Open a second handle.
+ f2, err := os.Open(path.Join(t.Dir, "foo"))
+ defer f2.Close()
+ // We should see different contents if we read from that handle, due to the
+ // cache being invalidated at the time of opening.
+ f2.Seek(0, 0)
+ c2, err := ioutil.ReadAll(f2)
+ ExpectFalse(bytes.Equal(c1, c2))
+ // Another read from the second handle should give the same result as the
+ // first one from that handle.
+ c3, err := ioutil.ReadAll(f2)
+ AssertEq(cachingfs.FooSize, len(c3))
+ ExpectTrue(bytes.Equal(c2, c3))
+ // And another read from the first handle should give the same result yet
+ // again.
+ c4, err := ioutil.ReadAll(f1)
+ AssertEq(cachingfs.FooSize, len(c4))
+ ExpectTrue(bytes.Equal(c2, c4))
+func (t *PageCacheTest) TwoFileHandles_KeepCache() {
+ // We should see the same contents when we read via the second handle.
+ // Ditto if we read again from the first.
+ c3, err := ioutil.ReadAll(f1)
+ ExpectTrue(bytes.Equal(c1, c3))
@@ -0,0 +1,284 @@
+package dynamicfs
+// Create a file system that contains 2 files (`age` and `weekday`) and no
+// directories. Every time the `age` file is opened, its contents are refreshed
+// to show the number of seconds elapsed since the file system was created (as
+// opposed to mounted). Every time the `weekday` file is opened, its contents
+// are refreshed to reflect the current weekday.
+// The contents of both of these files is updated within the filesystem itself,
+// i.e., these changes do not go through the kernel. Additionally, file access
+// times are not updated and file size is not known in advance and is set to 0.
+// This simulates a filesystem that is backed by a dynamic data source where
+// file metadata is not necessarily known before the file is read. For example,
+// a filesystem backed by an expensive RPC or by a stream that's generated on
+// the fly might not know data size ahead of time.
+// This implementation depends on direct IO in fuse. Without it, all read
+// operations are suppressed because the kernel detects that they read beyond
+// the end of the files.
+func NewDynamicFS(clock timeutil.Clock) (server fuse.Server, err error) {
+ createTime := clock.Now()
+ fs := &dynamicFS{
+ clock: clock,
+ createTime: createTime,
+ fileHandles: make(map[fuseops.HandleID]string),
+ server = fuseutil.NewFileSystemServer(fs)
+type dynamicFS struct {
+ clock timeutil.Clock
+ createTime time.Time
+ nextHandle fuseops.HandleID
+ fileHandles map[fuseops.HandleID]string
+ rootInode fuseops.InodeID = fuseops.RootInodeID + iota
+ ageInode
+ weekdayInode
+type inodeInfo struct {
+ attributes fuseops.InodeAttributes
+ // File or directory?
+ dir bool
+ // For directories, children.
+ children []fuseutil.Dirent
+// We have a fixed directory structure.
+var gInodeInfo = map[fuseops.InodeID]inodeInfo{
+ // root
+ rootInode: {
+ attributes: fuseops.InodeAttributes{
+ Mode: 0555 | os.ModeDir,
+ dir: true,
+ children: []fuseutil.Dirent{
+ Offset: 1,
+ Inode: ageInode,
+ Name: "age",
+ Type: fuseutil.DT_File,
+ Offset: 2,
+ Inode: weekdayInode,
+ Name: "weekday",
+ // age
+ ageInode: {
+ Mode: 0444,
+ // weekday
+ weekdayInode: {
+ // Size left at 0.
+func findChildInode(
+ name string,
+ children []fuseutil.Dirent) (inode fuseops.InodeID, err error) {
+ for _, e := range children {
+ if e.Name == name {
+ inode = e.Inode
+func (fs *dynamicFS) findUnusedHandle() fuseops.HandleID {
+ // TODO: Mutex annotation?
+ handle := fs.nextHandle
+ for _, exists := fs.fileHandles[handle]; exists; _, exists = fs.fileHandles[handle] {
+ handle++
+ fs.nextHandle = handle + 1
+func (fs *dynamicFS) GetInodeAttributes(
+ // Find the info for this inode.
+ info, ok := gInodeInfo[op.Inode]
+ // Copy over its attributes.
+ op.Attributes = info.attributes
+func (fs *dynamicFS) LookUpInode(
+ // Find the info for the parent.
+ parentInfo, ok := gInodeInfo[op.Parent]
+ // Find the child within the parent.
+ childInode, err := findChildInode(op.Name, parentInfo.children)
+ // Copy over information.
+ op.Entry.Child = childInode
+ op.Entry.Attributes = gInodeInfo[childInode].attributes
+func (fs *dynamicFS) OpenDir(
+ // Allow opening directory.
+func (fs *dynamicFS) ReadDir(
+ if !info.dir {
+ err = fuse.EIO
+ entries := info.children
+ // Grab the range of interest.
+ if op.Offset > fuseops.DirOffset(len(entries)) {
+ entries = entries[op.Offset:]
+ // Resume at the specified offset into the array.
+ for _, e := range entries {
+ n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], e)
+ if n == 0 {
+ op.BytesRead += n
+func (fs *dynamicFS) OpenFile(
+ var contents string
+ // Update file contents on (and only on) open.
+ switch op.Inode {
+ case ageInode:
+ now := fs.clock.Now()
+ ageInSeconds := int(now.Sub(fs.createTime).Seconds())
+ contents = fmt.Sprintf("This filesystem is %d seconds old.", ageInSeconds)
+ case weekdayInode:
+ contents = fmt.Sprintf("Today is %s.", fs.clock.Now().Weekday())
+ err = fuse.EINVAL
+ handle := fs.findUnusedHandle()
+ fs.fileHandles[handle] = contents
+ op.UseDirectIO = true
+ op.Handle = handle
+func (fs *dynamicFS) ReadFile(
+ contents, ok := fs.fileHandles[op.Handle]
+ log.Printf("ReadFile: no open file handle: %d", op.Handle)
+ reader := strings.NewReader(contents)
+ op.BytesRead, err = reader.ReadAt(op.Dst, op.Offset)
+func (fs *dynamicFS) ReleaseFileHandle(
+ _, ok := fs.fileHandles[op.Handle]
+ log.Printf("ReleaseFileHandle: bad handle: %d", op.Handle)
+ delete(fs.fileHandles, op.Handle)
+func (fs *dynamicFS) StatFS(ctx context.Context,
@@ -0,0 +1,181 @@
+package dynamicfs_test
+ "github.com/jacobsa/fuse/fusetesting"
+ "github.com/jacobsa/fuse/samples/dynamicfs"
+func TestDynamicFS(t *testing.T) { RunTests(t) }
+type DynamicFSTest struct {
+ RegisterTestSuite(&DynamicFSTest{})
+var gCreateTime = time.Date(2017, 5, 4, 14, 53, 10, 0, time.UTC)
+func (t *DynamicFSTest) SetUp(ti *TestInfo) {
+ t.Clock.SetTime(gCreateTime)
+ t.Server, err = dynamicfs.NewDynamicFS(&t.Clock)
+func (t *DynamicFSTest) ReadDir_Root() {
+ entries, err := fusetesting.ReadDirPicky(t.Dir)
+ AssertEq(2, len(entries))
+ fi = entries[0]
+ ExpectEq("age", fi.Name())
+ ExpectEq(0, fi.Size())
+ ExpectEq(0444, fi.Mode())
+ fi = entries[1]
+ ExpectEq("weekday", fi.Name())
+func (t *DynamicFSTest) ReadDir_NonExistent() {
+ _, err := fusetesting.ReadDirPicky(path.Join(t.Dir, "nosuchfile"))
+ ExpectThat(err, Error(HasSubstr("no such file")))
+func (t *DynamicFSTest) Stat_Age() {
+ fi, err := os.Stat(path.Join(t.Dir, "age"))
+func (t *DynamicFSTest) Stat_Weekday() {
+ fi, err := os.Stat(path.Join(t.Dir, "weekday"))
+func (t *DynamicFSTest) Stat_NonExistent() {
+ _, err := os.Stat(path.Join(t.Dir, "nosuchfile"))
+func (t *DynamicFSTest) ReadFile_AgeZero() {
+ slice, err := ioutil.ReadFile(path.Join(t.Dir, "age"))
+ ExpectEq("This filesystem is 0 seconds old.", string(slice))
+func (t *DynamicFSTest) ReadFile_Age1000() {
+ t.Clock.SetTime(gCreateTime.Add(1000 * time.Second))
+ ExpectEq("This filesystem is 1000 seconds old.", string(slice))
+func (t *DynamicFSTest) ReadFile_WeekdayNow() {
+ now := t.Clock.Now()
+ // Does simulated clock advance itself by default?
+ // Manually set time to ensure it's frozen.
+ t.Clock.SetTime(now)
+ slice, err := ioutil.ReadFile(path.Join(t.Dir, "weekday"))
+ ExpectEq(fmt.Sprintf("Today is %s.", now.Weekday().String()), string(slice))
+func (t *DynamicFSTest) ReadFile_WeekdayCreateTime() {
+ ExpectEq(fmt.Sprintf("Today is %s.", gCreateTime.Weekday().String()), string(slice))
+func (t *DynamicFSTest) ReadFile_AgeUnchangedForHandle() {
+ t.Clock.SetTime(gCreateTime.Add(100 * time.Second))
+ var file *os.File
+ file, err = os.Open(path.Join(t.Dir, "age"))
+ // Ensure that all reads from the same handle return the contents created at
+ // file open time.
+ func(file *os.File) {
+ defer file.Close()
+ var expectedContents string
+ var buffer bytes.Buffer
+ var bytesRead int64
+ expectedContents = "This filesystem is 100 seconds old."
+ bytesRead, err = buffer.ReadFrom(file)
+ ExpectEq(len(expectedContents), bytesRead)
+ ExpectEq(expectedContents, buffer.String())
+ // Seek back to the beginning of the file. The contents should be unchanged
+ // for the life of the file handle.
+ _, err = file.Seek(0, 0)
+ buffer.Reset()
+ }(file)
+ // The clock was advanced while the old handle was open. The content change
+ // should be reflected by the new handle.
+ expectedContents := "This filesystem is 1000 seconds old."
+ buffer := bytes.Buffer{}
+ bytesRead, err := buffer.ReadFrom(file)
@@ -0,0 +1,221 @@
+package errorfs
+const FooContents = "xxxx"
+const fooInodeID = fuseops.RootInodeID + 1
+var fooAttrs = fuseops.InodeAttributes{
+ Size: uint64(len(FooContents)),
+// A file system whose sole contents are a file named "foo" containing the
+// string defined by FooContents.
+// The file system can be configured to returned canned errors for particular
+// operations using the method SetError.
+type FS interface {
+ // Cause the file system to return the supplied error for all future
+ // operations matching the supplied type.
+ SetError(t reflect.Type, err syscall.Errno)
+func New() (fs FS, err error) {
+ fs = &errorFS{
+ errors: make(map[reflect.Type]syscall.Errno),
+type errorFS struct {
+ errors map[reflect.Type]syscall.Errno
+func (fs *errorFS) SetError(t reflect.Type, err syscall.Errno) {
+ fs.errors[t] = err
+func (fs *errorFS) transformError(op interface{}, err *error) bool {
+ cannedErr, ok := fs.errors[reflect.TypeOf(op)]
+ if ok {
+ *err = cannedErr
+// File system methods
+func (fs *errorFS) GetInodeAttributes(
+ if fs.transformError(op, &err) {
+ op.Attributes = fuseops.InodeAttributes{
+ case op.Inode == fooInodeID:
+ op.Attributes = fooAttrs
+ err = fmt.Errorf("Unknown inode: %d", op.Inode)
+func (fs *errorFS) StatFS(
+func (fs *errorFS) LookUpInode(
+ // Is this a known inode?
+ if !(op.Parent == fuseops.RootInodeID && op.Name == "foo") {
+ err = syscall.ENOENT
+ op.Entry.Child = fooInodeID
+ op.Entry.Attributes = fooAttrs
+func (fs *errorFS) OpenFile(
+ if op.Inode != fooInodeID {
+ err = fmt.Errorf("Unsupported inode ID: %d", op.Inode)
+func (fs *errorFS) ReadFile(
+ if op.Inode != fooInodeID || op.Offset != 0 {
+ err = fmt.Errorf("Unexpected request: %#v", op)
+ op.BytesRead = copy(op.Dst, FooContents)
+func (fs *errorFS) OpenDir(
+ if op.Inode != fuseops.RootInodeID {
+func (fs *errorFS) ReadDir(
+ if op.Inode != fuseops.RootInodeID || op.Offset != 0 {
+ op.BytesRead = fuseutil.WriteDirent(
+ op.Dst,
+ fuseutil.Dirent{
+ Offset: 0,
+ Inode: fooInodeID,
+ Name: "foo",
@@ -0,0 +1,106 @@
+package errorfs_test
+ "github.com/jacobsa/fuse/samples/errorfs"
+func TestErrorFS(t *testing.T) { RunTests(t) }
+type ErrorFSTest struct {
+ fs errorfs.FS
+func init() { RegisterTestSuite(&ErrorFSTest{}) }
+var _ SetUpInterface = &ErrorFSTest{}
+var _ TearDownInterface = &ErrorFSTest{}
+func (t *ErrorFSTest) SetUp(ti *TestInfo) {
+ t.fs, err = errorfs.New()
+func (t *ErrorFSTest) OpenFile() {
+ t.fs.SetError(reflect.TypeOf(&fuseops.OpenFileOp{}), syscall.EOWNERDEAD)
+ ExpectThat(err, Error(MatchesRegexp("open.*: .*owner died")))
+func (t *ErrorFSTest) ReadFile() {
+ t.fs.SetError(reflect.TypeOf(&fuseops.ReadFileOp{}), syscall.EOWNERDEAD)
+ // Open
+ // Read
+ _, err = ioutil.ReadAll(f)
+ ExpectThat(err, Error(MatchesRegexp("read.*: .*owner died")))
+func (t *ErrorFSTest) OpenDir() {
+ t.fs.SetError(reflect.TypeOf(&fuseops.OpenDirOp{}), syscall.EOWNERDEAD)
+ f, err := os.Open(t.Dir)
+func (t *ErrorFSTest) ReadDir() {
+ t.fs.SetError(reflect.TypeOf(&fuseops.ReadDirOp{}), syscall.EOWNERDEAD)
+ _, err = f.Readdirnames(1)
@@ -0,0 +1,338 @@
+package flushfs
+// Create a file system whose sole contents are a file named "foo" and a
+// directory named "bar".
+// The file may be opened for reading and/or writing. Its initial contents are
+// empty. Whenever a flush or fsync is received, the supplied function will be
+// called with the current contents of the file and its status returned.
+// The directory cannot be modified.
+func NewFileSystem(
+ reportFlush func(string) error,
+ reportFsync func(string) error) (server fuse.Server, err error) {
+ fs := &flushFS{
+ reportFlush: reportFlush,
+ reportFsync: reportFsync,
+ fooID = fuseops.RootInodeID + 1 + iota
+ barID
+type flushFS struct {
+ reportFlush func(string) error
+ reportFsync func(string) error
+ fooContents []byte // GUARDED_BY(mu)
+func (fs *flushFS) rootAttributes() fuseops.InodeAttributes {
+ Mode: 0777 | os.ModeDir,
+func (fs *flushFS) fooAttributes() fuseops.InodeAttributes {
+ Size: uint64(len(fs.fooContents)),
+func (fs *flushFS) barAttributes() fuseops.InodeAttributes {
+func (fs *flushFS) getAttributes(id fuseops.InodeID) (
+ attrs fuseops.InodeAttributes,
+ err error) {
+ switch id {
+ case fuseops.RootInodeID:
+ attrs = fs.rootAttributes()
+ case fooID:
+ attrs = fs.fooAttributes()
+ case barID:
+ attrs = fs.barAttributes()
+func (fs *flushFS) StatFS(
+func (fs *flushFS) LookUpInode(
+ // Sanity check.
+ // Set up the entry.
+ op.Entry = fuseops.ChildInodeEntry{
+ Child: fooID,
+ Attributes: fs.fooAttributes(),
+ Child: barID,
+ Attributes: fs.barAttributes(),
+func (fs *flushFS) GetInodeAttributes(
+ op.Attributes, err = fs.getAttributes(op.Inode)
+func (fs *flushFS) SetInodeAttributes(
+ // Ignore any changes and simply return existing attributes.
+func (fs *flushFS) OpenFile(
+ if op.Inode != fooID {
+func (fs *flushFS) ReadFile(
+ // Ensure the offset is in range.
+ if op.Offset > int64(len(fs.fooContents)) {
+ // Read what we can.
+ op.BytesRead = copy(op.Dst, fs.fooContents[op.Offset:])
+func (fs *flushFS) WriteFile(
+ // Ensure that the contents slice is long enough.
+ newLen := int(op.Offset) + len(op.Data)
+ if len(fs.fooContents) < newLen {
+ padding := make([]byte, newLen-len(fs.fooContents))
+ fs.fooContents = append(fs.fooContents, padding...)
+ // Copy in the data.
+ n := copy(fs.fooContents[op.Offset:], op.Data)
+ if n != len(op.Data) {
+ panic(fmt.Sprintf("Unexpected short copy: %v", n))
+func (fs *flushFS) SyncFile(
+ err = fs.reportFsync(string(fs.fooContents))
+func (fs *flushFS) FlushFile(
+ err = fs.reportFlush(string(fs.fooContents))
+func (fs *flushFS) OpenDir(
+func (fs *flushFS) ReadDir(
+ // Create the appropriate listing.
+ var dirents []fuseutil.Dirent
+ dirents = []fuseutil.Dirent{
+ Inode: fooID,
+ Inode: barID,
+ Name: "bar",
+ Type: fuseutil.DT_Directory,
+ err = fmt.Errorf("Unexpected inode: %v", op.Inode)
+ // If the offset is for the end of the listing, we're done. Otherwise we
+ // expect it to be for the start.
+ switch op.Offset {
+ case fuseops.DirOffset(len(dirents)):
+ case 0:
+ err = fmt.Errorf("Unexpected offset: %v", op.Offset)
+ // Fill in the listing.
+ for _, de := range dirents {
+ n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], de)
+ // We don't support doing this in anything more than one shot.
+ err = fmt.Errorf("Couldn't fit listing in %v bytes", len(op.Dst))
@@ -0,0 +1,389 @@
+package forgetfs
+// The file "foo" may be opened for reading and/or writing, but reads and
+// writes aren't supported. Additionally, any non-existent file or directory
+// name may be created within any directory, but the resulting inode will
+// appear to have been unlinked immediately.
+// The file system maintains reference counts for the inodes involved. It will
+// panic if a reference count becomes negative or if an inode ID is re-used
+// after we expect it to be dead. Its Check method may be used to check that
+// there are no inodes with unexpected reference counts remaining, after
+// unmounting.
+func NewFileSystem() (fs *ForgetFS) {
+ // Set up the actual file system.
+ impl := &fsImpl{
+ inodes: map[fuseops.InodeID]*inode{
+ cannedID_Root: &inode{
+ cannedID_Foo: &inode{
+ cannedID_Bar: &inode{
+ nextInodeID: cannedID_Next,
+ // The root inode starts with a lookup count of one.
+ impl.inodes[cannedID_Root].IncrementLookupCount()
+ // The canned inodes are supposed to be stable from the user's point of view,
+ // so we should allow them to be looked up at any point even if the kernel
+ // has balanced its lookups with its forgets. Ensure that they never go to
+ // zero until the file system is destroyed.
+ impl.inodes[cannedID_Foo].IncrementLookupCount()
+ impl.inodes[cannedID_Bar].IncrementLookupCount()
+ // Set up the mutex.
+ impl.mu = syncutil.NewInvariantMutex(impl.checkInvariants)
+ // Set up a wrapper that exposes only certain methods.
+ fs = &ForgetFS{
+ impl: impl,
+ server: fuseutil.NewFileSystemServer(impl),
+// ForgetFS
+type ForgetFS struct {
+ impl *fsImpl
+ server fuse.Server
+func (fs *ForgetFS) ServeOps(c *fuse.Connection) {
+ fs.server.ServeOps(c)
+// Panic if there are any inodes that have a non-zero reference count. For use
+// after unmounting.
+func (fs *ForgetFS) Check() {
+ fs.impl.Check()
+// Actual implementation
+ cannedID_Root = fuseops.RootInodeID + iota
+ cannedID_Foo
+ cannedID_Bar
+ cannedID_Next
+type fsImpl struct {
+ // An index of inode by ID, for all IDs we have issued.
+ inodes map[fuseops.InodeID]*inode
+ // The next ID to issue.
+ // INVARIANT: For each k in inodes, k < nextInodeID
+ nextInodeID fuseops.InodeID
+// inode
+type inode struct {
+ // The current lookup count.
+ lookupCount uint64
+ // true if lookupCount has ever been positive.
+ lookedUp bool
+func (in *inode) Forgotten() bool {
+ return in.lookedUp && in.lookupCount == 0
+func (in *inode) IncrementLookupCount() {
+ in.lookupCount++
+ in.lookedUp = true
+func (in *inode) DecrementLookupCount(n uint64) {
+ if in.lookupCount < n {
+ "Overly large decrement: %v, %v",
+ in.lookupCount,
+ n))
+ in.lookupCount -= n
+// Decrement the lookup count to zero.
+func (in *inode) Destroy() {
+ in.DecrementLookupCount(in.lookupCount)
+func (fs *fsImpl) checkInvariants() {
+ for k, _ := range fs.inodes {
+ if !(k < fs.nextInodeID) {
+ panic("Unexpectedly large inode ID")
+func (fs *fsImpl) Check() {
+ for k, v := range fs.inodes {
+ if v.lookupCount != 0 {
+ panic(fmt.Sprintf("Inode %v has lookup count %v", k, v.lookupCount))
+// Look up the inode and verify it hasn't been forgotten.
+func (fs *fsImpl) findInodeByID(id fuseops.InodeID) (in *inode) {
+ in = fs.inodes[id]
+ panic(fmt.Sprintf("Unknown inode: %v", id))
+ if in.Forgotten() {
+ panic(fmt.Sprintf("Forgotten inode: %v", id))
+func (fs *fsImpl) StatFS(
+func (fs *fsImpl) LookUpInode(
+ // Make sure the parent exists and has not been forgotten.
+ _ = fs.findInodeByID(op.Parent)
+ // Handle the names we support.
+ var childID fuseops.InodeID
+ case op.Parent == cannedID_Root && op.Name == "foo":
+ childID = cannedID_Foo
+ case op.Parent == cannedID_Root && op.Name == "bar":
+ childID = cannedID_Bar
+ // Look up the child.
+ child := fs.findInodeByID(childID)
+ child.IncrementLookupCount()
+ // Return an appropriate entry.
+ Child: childID,
+ Attributes: child.attributes,
+func (fs *fsImpl) GetInodeAttributes(
+ // Find the inode, verifying that it has not been forgotten.
+ in := fs.findInodeByID(op.Inode)
+ // Return appropriate attributes.
+ op.Attributes = in.attributes
+func (fs *fsImpl) ForgetInode(
+ // Find the inode and decrement its count.
+ in.DecrementLookupCount(op.N)
+func (fs *fsImpl) MkDir(
+ // Mint a child inode.
+ childID := fs.nextInodeID
+ fs.nextInodeID++
+ child := &inode{
+ Nlink: 0,
+ fs.inodes[childID] = child
+func (fs *fsImpl) CreateFile(
+func (fs *fsImpl) OpenFile(
+ // Verify that the inode has not been forgotten.
+ _ = fs.findInodeByID(op.Inode)
+func (fs *fsImpl) OpenDir(
+func (fs *fsImpl) Destroy() {
+ for _, in := range fs.inodes {
+ in.Destroy()
+package forgetfs_test
+ "github.com/jacobsa/fuse/samples/forgetfs"
+func TestForgetFS(t *testing.T) { RunTests(t) }
+type ForgetFSTest struct {
+ fs *forgetfs.ForgetFS
+func init() { RegisterTestSuite(&ForgetFSTest{}) }
+func (t *ForgetFSTest) SetUp(ti *TestInfo) {
+ t.fs = forgetfs.NewFileSystem()
+ t.Server = t.fs
+func (t *ForgetFSTest) TearDown() {
+ // Unmount.
+ t.SampleTest.TearDown()
+ // Crash if anything is left.
+ t.fs.Check()
+func (t *ForgetFSTest) Open_Foo() {
+ err = f.Close()
+func (t *ForgetFSTest) Open_Bar() {
+ f, err := os.Open(path.Join(t.Dir, "bar"))
+func (t *ForgetFSTest) Open_ManyTimes() {
+ // Set up a slice of files that will be closed when we're done.
+ var toClose []io.Closer
+ for _, c := range toClose {
+ ExpectEq(nil, c.Close())
+ // Open foo many times.
+ for i := 0; i < 100; i++ {
+ toClose = append(toClose, f)
+ // Open bar many times.
+func (t *ForgetFSTest) Stat_Foo() {
+ fi, err = os.Stat(path.Join(t.Dir, "foo"))
+ AssertEq("foo", fi.Name())
+ AssertEq(os.FileMode(0777), fi.Mode())
+func (t *ForgetFSTest) Stat_Bar() {
+ fi, err = os.Stat(path.Join(t.Dir, "bar"))
+ AssertEq("bar", fi.Name())
+ AssertEq(0777|os.ModeDir, fi.Mode())
+func (t *ForgetFSTest) Stat_ManyTimes() {
+ // Stat foo many times.
+ _, err = os.Stat(path.Join(t.Dir, "foo"))
+ // Stat bar many times.
+ _, err = os.Stat(path.Join(t.Dir, "bar"))
+func (t *ForgetFSTest) CreateFile() {
+ // Create and close many files within the root.
+ f, err := os.Create(path.Join(t.Dir, "blah"))
+ AssertEq(nil, f.Close())
+ // Create and close many files within the sub-directory.
+ f, err := os.Create(path.Join(t.Dir, "bar", "blah"))
+func (t *ForgetFSTest) MkDir() {
+ // Create many directories within the root.
+ err := os.Mkdir(path.Join(t.Dir, "blah"), 0777)
+ // Create many directories within the sub-directory.
+ err := os.Mkdir(path.Join(t.Dir, "bar", "blah"), 0777)
@@ -0,0 +1,269 @@
+package hellofs
+// Create a file system with a fixed structure that looks like this:
+// hello
+// world
+// Each file contains the string "Hello, world!".
+func NewHelloFS(clock timeutil.Clock) (server fuse.Server, err error) {
+ fs := &helloFS{
+ Clock: clock,
+type helloFS struct {
+ Clock timeutil.Clock
+ helloInode
+ dirInode
+ worldInode
+ rootInode: inodeInfo{
+ Inode: helloInode,
+ Name: "hello",
+ Inode: dirInode,
+ Name: "dir",
+ // hello
+ helloInode: inodeInfo{
+ Size: uint64(len("Hello, world!")),
+ // dir
+ dirInode: inodeInfo{
+ Inode: worldInode,
+ Name: "world",
+ // world
+ worldInode: inodeInfo{
+func (fs *helloFS) patchAttributes(
+ attr *fuseops.InodeAttributes) {
+ now := fs.Clock.Now()
+ attr.Atime = now
+ attr.Mtime = now
+ attr.Crtime = now
+func (fs *helloFS) StatFS(
+func (fs *helloFS) LookUpInode(
+ // Patch attributes.
+ fs.patchAttributes(&op.Entry.Attributes)
+func (fs *helloFS) GetInodeAttributes(
+ fs.patchAttributes(&op.Attributes)
+func (fs *helloFS) OpenDir(
+ // Allow opening any directory.
+func (fs *helloFS) ReadDir(
+func (fs *helloFS) OpenFile(
+ // Allow opening any file.
+func (fs *helloFS) ReadFile(
+ // Let io.ReaderAt deal with the semantics.
+ reader := strings.NewReader("Hello, world!")
+ // Special case: FUSE doesn't expect us to return io.EOF.
@@ -0,0 +1,226 @@
+package hellofs_test
+ "github.com/jacobsa/fuse/samples/hellofs"
+func TestHelloFS(t *testing.T) { RunTests(t) }
+type HelloFSTest struct {
+func init() { RegisterTestSuite(&HelloFSTest{}) }
+func (t *HelloFSTest) SetUp(ti *TestInfo) {
+ t.Server, err = hellofs.NewHelloFS(&t.Clock)
+// Test functions
+func (t *HelloFSTest) ReadDir_Root() {
+ ExpectEq(os.ModeDir|0555, fi.Mode())
+ ExpectEq(0, t.Clock.Now().Sub(fi.ModTime()), "ModTime: %v", fi.ModTime())
+ ExpectEq("hello", fi.Name())
+ ExpectEq(len("Hello, world!"), fi.Size())
+func (t *HelloFSTest) ReadDir_Dir() {
+ entries, err := fusetesting.ReadDirPicky(path.Join(t.Dir, "dir"))
+ ExpectEq("world", fi.Name())
+func (t *HelloFSTest) ReadDir_NonExistent() {
+ _, err := fusetesting.ReadDirPicky(path.Join(t.Dir, "foobar"))
+func (t *HelloFSTest) Stat_Hello() {
+ fi, err := os.Stat(path.Join(t.Dir, "hello"))
+func (t *HelloFSTest) Stat_Dir() {
+ ExpectEq(0555|os.ModeDir, fi.Mode())
+func (t *HelloFSTest) Stat_World() {
+ fi, err := os.Stat(path.Join(t.Dir, "dir/world"))
+func (t *HelloFSTest) Stat_NonExistent() {
+ _, err := os.Stat(path.Join(t.Dir, "foobar"))
+func (t *HelloFSTest) ReadFile_Hello() {
+ slice, err := ioutil.ReadFile(path.Join(t.Dir, "hello"))
+ ExpectEq("Hello, world!", string(slice))
+func (t *HelloFSTest) ReadFile_Dir() {
+ _, err := ioutil.ReadFile(path.Join(t.Dir, "dir"))
+ ExpectThat(err, Error(HasSubstr("is a directory")))
+func (t *HelloFSTest) ReadFile_World() {
+ slice, err := ioutil.ReadFile(path.Join(t.Dir, "dir/world"))
+func (t *HelloFSTest) OpenAndRead() {
+ var buf []byte = make([]byte, 1024)
+ var n int
+ var off int64
+ f, err := os.Open(path.Join(t.Dir, "hello"))
+ if f != nil {
+ ExpectEq(nil, f.Close())
+ // Seeking shouldn't affect the random access reads below.
+ _, err = f.Seek(7, 0)
+ // Random access reads
+ n, err = f.ReadAt(buf[:2], 0)
+ ExpectEq(2, n)
+ ExpectEq("He", string(buf[:n]))
+ n, err = f.ReadAt(buf[:2], int64(len("Hel")))
+ ExpectEq("lo", string(buf[:n]))
+ n, err = f.ReadAt(buf[:3], int64(len("Hello, wo")))
+ ExpectEq(3, n)
+ ExpectEq("rld", string(buf[:n]))
+ // Read beyond end.
+ n, err = f.ReadAt(buf[:3], int64(len("Hello, world")))
+ AssertEq(io.EOF, err)
+ ExpectEq(1, n)
+ ExpectEq("!", string(buf[:n]))
+ // Seek then read the rest.
+ off, err = f.Seek(int64(len("Hel")), 0)
+ AssertEq(len("Hel"), off)
+ n, err = io.ReadFull(f, buf[:len("lo, world!")])
+ ExpectEq(len("lo, world!"), n)
+ ExpectEq("lo, world!", string(buf[:n]))
+func (t *HelloFSTest) Open_NonExistent() {
+ _, err := os.Open(path.Join(t.Dir, "foobar"))
@@ -0,0 +1,153 @@
+package samples
+ "github.com/jacobsa/ogletest"
+// A struct that implements common behavior needed by tests in the samples/
+// directory. Use it as an embedded field in your test fixture, calling its
+// SetUp method from your SetUp method after setting the Server field.
+type SampleTest struct {
+ // The server under test and the configuration with which it should be
+ // mounted. These must be set by the user of this type before calling SetUp;
+ // all the other fields below are set by SetUp itself.
+ Server fuse.Server
+ MountConfig fuse.MountConfig
+ // A context object that can be used for long-running operations.
+ Ctx context.Context
+ // A clock with a fixed initial time. The test's set up method may use this
+ // to wire the server with a clock, if desired.
+ Clock timeutil.SimulatedClock
+ // The directory at which the file system is mounted.
+ Dir string
+ // Anothing non-nil in this slice will be closed by TearDown. The test will
+ // fail if closing fails.
+ ToClose []io.Closer
+ mfs *fuse.MountedFileSystem
+// Mount t.Server and initialize the other exported fields of the struct.
+// Panics on error.
+// REQUIRES: t.Server has been set.
+func (t *SampleTest) SetUp(ti *ogletest.TestInfo) {
+ cfg := t.MountConfig
+ if *fDebug {
+ cfg.DebugLogger = log.New(os.Stderr, "fuse: ", 0)
+ err := t.initialize(ti.Ctx, t.Server, &cfg)
+// Like SetUp, but doens't panic.
+func (t *SampleTest) initialize(
+ server fuse.Server,
+ config *fuse.MountConfig) (err error) {
+ // Initialize the context used by the test.
+ t.Ctx = ctx
+ // Make the server share that context, if the test hasn't already set some
+ // other one.
+ if config.OpContext == nil {
+ config.OpContext = ctx
+ // Initialize the clock.
+ t.Clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local))
+ t.Dir, err = ioutil.TempDir("", "sample_test")
+ err = fmt.Errorf("TempDir: %v", err)
+ // Mount the file system.
+ t.mfs, err = fuse.Mount(t.Dir, server, config)
+ err = fmt.Errorf("Mount: %v", err)
+// Unmount the file system and clean up. Panics on error.
+func (t *SampleTest) TearDown() {
+ err := t.destroy()
+// Like TearDown, but doesn't panic.
+func (t *SampleTest) destroy() (err error) {
+ // Close what is necessary.
+ for _, c := range t.ToClose {
+ if c == nil {
+ ogletest.ExpectEq(nil, c.Close())
+ // Was the file system mounted?
+ if t.mfs == nil {
+ // Unmount the file system.
+ err = unmount(t.Dir)
+ err = fmt.Errorf("unmount: %v", err)
+ // Unlink the mount point.
+ if err = os.Remove(t.Dir); err != nil {
+ err = fmt.Errorf("Unlinking mount point: %v", err)
+ // Join the file system.
+ err = t.mfs.Join(t.Ctx)
+ err = fmt.Errorf("mfs.Join: %v", err)
@@ -0,0 +1,209 @@
+package interruptfs
+var rootAttrs = fuseops.InodeAttributes{
+const fooID = fuseops.RootInodeID + 1
+ Size: 1234,
+// A file system containing exactly one file, named "foo". ReadFile and
+// FlushFile ops can be made to hang until interrupted. Exposes a method for
+// synchronizing with the arrival of a read or a flush.
+// Must be created with New.
+type InterruptFS struct {
+ blockForReads bool // GUARDED_BY(mu)
+ blockForFlushes bool // GUARDED_BY(mu)
+ // Must hold the mutex when closing these.
+ readReceived chan struct{}
+ flushReceived chan struct{}
+func New() (fs *InterruptFS) {
+ fs = &InterruptFS{
+ readReceived: make(chan struct{}),
+ flushReceived: make(chan struct{}),
+// Block until the first read is received.
+func (fs *InterruptFS) WaitForFirstRead() {
+ <-fs.readReceived
+// Block until the first flush is received.
+func (fs *InterruptFS) WaitForFirstFlush() {
+ <-fs.flushReceived
+// Enable blocking until interrupted for the next (and subsequent) read ops.
+func (fs *InterruptFS) EnableReadBlocking() {
+ fs.blockForReads = true
+// Enable blocking until interrupted for the next (and subsequent) flush ops.
+func (fs *InterruptFS) EnableFlushBlocking() {
+ fs.blockForFlushes = true
+func (fs *InterruptFS) StatFS(
+func (fs *InterruptFS) LookUpInode(
+ // We support only one parent.
+ err = fmt.Errorf("Unexpected parent: %v", op.Parent)
+ // We support only one name.
+ if op.Name != "foo" {
+ op.Entry.Child = fooID
+func (fs *InterruptFS) GetInodeAttributes(
+ op.Attributes = rootAttrs
+ err = fmt.Errorf("Unexpected inode ID: %v", op.Inode)
+func (fs *InterruptFS) OpenFile(
+func (fs *InterruptFS) ReadFile(
+ shouldBlock := fs.blockForReads
+ // Signal that a read has been received, if this is the first.
+ case <-fs.readReceived:
+ close(fs.readReceived)
+ fs.mu.Unlock()
+ // Wait for cancellation if enabled.
+ if shouldBlock {
+ done := ctx.Done()
+ if done == nil {
+ panic("Expected non-nil channel.")
+ <-done
+ err = ctx.Err()
+func (fs *InterruptFS) FlushFile(
+ shouldBlock := fs.blockForFlushes
+ // Signal that a flush has been received, if this is the first.
+ case <-fs.flushReceived:
+ close(fs.flushReceived)
@@ -0,0 +1,156 @@
+package interruptfs_test
+ "github.com/jacobsa/fuse/samples/interruptfs"
+func TestInterruptFS(t *testing.T) { RunTests(t) }
+type InterruptFSTest struct {
+ fs *interruptfs.InterruptFS
+func init() { RegisterTestSuite(&InterruptFSTest{}) }
+var _ SetUpInterface = &InterruptFSTest{}
+var _ TearDownInterface = &InterruptFSTest{}
+func (t *InterruptFSTest) SetUp(ti *TestInfo) {
+ t.fs = interruptfs.New()
+func (t *InterruptFSTest) StatFoo() {
+func (t *InterruptFSTest) InterruptedDuringRead() {
+ t.fs.EnableReadBlocking()
+ // Start a sub-process that attempts to read the file.
+ cmd := exec.Command("cat", path.Join(t.Dir, "foo"))
+ var cmdOutput bytes.Buffer
+ cmd.Stdout = &cmdOutput
+ cmd.Stderr = &cmdOutput
+ // Wait for the command in the background, writing to a channel when it is
+ // finished.
+ cmdErr := make(chan error)
+ cmdErr <- cmd.Wait()
+ // Wait for the read to make it to the file system.
+ t.fs.WaitForFirstRead()
+ // The command should be hanging on the read, and not yet have returned.
+ case err = <-cmdErr:
+ AddFailure("Command returned early with error: %v", err)
+ AbortTest()
+ case <-time.After(10 * time.Millisecond):
+ // Send SIGINT.
+ cmd.Process.Signal(os.Interrupt)
+ // Now the command should return, with an appropriate error.
+ err = <-cmdErr
+ ExpectThat(err, Error(HasSubstr("signal")))
+ ExpectThat(err, Error(HasSubstr("interrupt")))
+func (t *InterruptFSTest) InterruptedDuringFlush() {
+ t.fs.EnableFlushBlocking()
+ // Wait for the flush to make it to the file system.
+ t.fs.WaitForFirstFlush()
+ // The command should be hanging on the flush, and not yet have returned.
@@ -0,0 +1,384 @@
+package memfs
+// Common attributes for files and directories.
+// External synchronization is required.
+ // The current attributes of this inode.
+ // INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
+ // INVARIANT: !(isDir() && isSymlink())
+ // INVARIANT: attrs.Size == len(contents)
+ attrs fuseops.InodeAttributes
+ // For directories, entries describing the children of the directory. Unused
+ // entries are of type DT_Unknown.
+ // This array can never be shortened, nor can its elements be moved, because
+ // we use its indices for Dirent.Offset, which is exposed to the user who
+ // might be calling readdir in a loop while concurrently modifying the
+ // directory. Unused entries can, however, be reused.
+ // INVARIANT: If !isDir(), len(entries) == 0
+ // INVARIANT: For each i, entries[i].Offset == i+1
+ // INVARIANT: Contains no duplicate names in used entries.
+ entries []fuseutil.Dirent
+ // For files, the current contents of the file.
+ // INVARIANT: If !isFile(), len(contents) == 0
+ contents []byte
+ // For symlinks, the target of the symlink.
+ // INVARIANT: If !isSymlink(), len(target) == 0
+ target string
+ // extended attributes and values
+ xattrs map[string][]byte
+// Create a new inode with the supplied attributes, which need not contain
+// time-related information (the inode object will take care of that).
+func newInode(
+ attrs fuseops.InodeAttributes) (in *inode) {
+ // Update time info.
+ now := time.Now()
+ attrs.Mtime = now
+ attrs.Crtime = now
+ // Create the object.
+ in = &inode{
+ attrs: attrs,
+ xattrs: make(map[string][]byte),
+func (in *inode) CheckInvariants() {
+ if !(in.attrs.Mode&^(os.ModePerm|os.ModeDir|os.ModeSymlink) == 0) {
+ panic(fmt.Sprintf("Unexpected mode: %v", in.attrs.Mode))
+ if in.isDir() && in.isSymlink() {
+ if in.attrs.Size != uint64(len(in.contents)) {
+ "Size mismatch: %d vs. %d",
+ in.attrs.Size,
+ len(in.contents)))
+ if !in.isDir() && len(in.entries) != 0 {
+ panic(fmt.Sprintf("Unexpected entries length: %d", len(in.entries)))
+ for i, e := range in.entries {
+ if !(e.Offset == fuseops.DirOffset(i+1)) {
+ panic(fmt.Sprintf("Unexpected offset for index %d: %d", i, e.Offset))
+ childNames := make(map[string]struct{})
+ for _, e := range in.entries {
+ if e.Type != fuseutil.DT_Unknown {
+ if _, ok := childNames[e.Name]; ok {
+ panic(fmt.Sprintf("Duplicate name: %s", e.Name))
+ childNames[e.Name] = struct{}{}
+ if !in.isFile() && len(in.contents) != 0 {
+ panic(fmt.Sprintf("Unexpected length: %d", len(in.contents)))
+ if !in.isSymlink() && len(in.target) != 0 {
+ panic(fmt.Sprintf("Unexpected target length: %d", len(in.target)))
+func (in *inode) isDir() bool {
+ return in.attrs.Mode&os.ModeDir != 0
+func (in *inode) isSymlink() bool {
+ return in.attrs.Mode&os.ModeSymlink != 0
+func (in *inode) isFile() bool {
+ return !(in.isDir() || in.isSymlink())
+// Return the index of the child within in.entries, if it exists.
+// REQUIRES: in.isDir()
+func (in *inode) findChild(name string) (i int, ok bool) {
+ if !in.isDir() {
+ panic("findChild called on non-directory.")
+ var e fuseutil.Dirent
+ for i, e = range in.entries {
+// Public methods
+// Return the number of children of the directory.
+func (in *inode) Len() (n int) {
+ n++
+// Find an entry for the given child name and return its inode ID.
+func (in *inode) LookUpChild(name string) (
+ id fuseops.InodeID,
+ typ fuseutil.DirentType,
+ ok bool) {
+ index, ok := in.findChild(name)
+ id = in.entries[index].Inode
+ typ = in.entries[index].Type
+// Add an entry for a child.
+// REQUIRES: dt != fuseutil.DT_Unknown
+func (in *inode) AddChild(
+ dt fuseutil.DirentType) {
+ var index int
+ // Update the modification time.
+ in.attrs.Mtime = time.Now()
+ // No matter where we place the entry, make sure it has the correct Offset
+ // field.
+ in.entries[index].Offset = fuseops.DirOffset(index + 1)
+ e := fuseutil.Dirent{
+ Inode: id,
+ Name: name,
+ Type: dt,
+ // Look for a gap in which we can insert it.
+ for index = range in.entries {
+ if in.entries[index].Type == fuseutil.DT_Unknown {
+ in.entries[index] = e
+ // Append it to the end.
+ index = len(in.entries)
+ in.entries = append(in.entries, e)
+// Remove an entry for a child.
+// REQUIRES: An entry for the given name exists.
+func (in *inode) RemoveChild(name string) {
+ // Find the entry.
+ i, ok := in.findChild(name)
+ panic(fmt.Sprintf("Unknown child: %s", name))
+ // Mark it as unused.
+ in.entries[i] = fuseutil.Dirent{
+ Type: fuseutil.DT_Unknown,
+ Offset: fuseops.DirOffset(i + 1),
+// Serve a ReadDir request.
+func (in *inode) ReadDir(p []byte, offset int) (n int) {
+ panic("ReadDir called on non-directory.")
+ for i := offset; i < len(in.entries); i++ {
+ e := in.entries[i]
+ // Skip unused entries.
+ if e.Type == fuseutil.DT_Unknown {
+ tmp := fuseutil.WriteDirent(p[n:], in.entries[i])
+ if tmp == 0 {
+ n += tmp
+// Read from the file's contents. See documentation for ioutil.ReaderAt.
+// REQUIRES: in.isFile()
+func (in *inode) ReadAt(p []byte, off int64) (n int, err error) {
+ if !in.isFile() {
+ panic("ReadAt called on non-file.")
+ if off > int64(len(in.contents)) {
+ n = copy(p, in.contents[off:])
+ if n < len(p) {
+// Write to the file's contents. See documentation for ioutil.WriterAt.
+func (in *inode) WriteAt(p []byte, off int64) (n int, err error) {
+ panic("WriteAt called on non-file.")
+ newLen := int(off) + len(p)
+ if len(in.contents) < newLen {
+ padding := make([]byte, newLen-len(in.contents))
+ in.contents = append(in.contents, padding...)
+ in.attrs.Size = uint64(newLen)
+ n = copy(in.contents[off:], p)
+ if n != len(p) {
+// Update attributes from non-nil parameters.
+func (in *inode) SetAttributes(
+ size *uint64,
+ mode *os.FileMode,
+ mtime *time.Time) {
+ // Truncate?
+ if size != nil {
+ intSize := int(*size)
+ // Update contents.
+ if intSize <= len(in.contents) {
+ in.contents = in.contents[:intSize]
+ padding := make([]byte, intSize-len(in.contents))
+ // Update attributes.
+ in.attrs.Size = *size
+ // Change mode?
+ if mode != nil {
+ in.attrs.Mode = *mode
+ // Change mtime?
+ if mtime != nil {
+ in.attrs.Mtime = *mtime
@@ -0,0 +1,716 @@
+type memFS struct {
+ // The UID and GID that every inode receives.
+ // The collection of live inodes, indexed by ID. IDs of free inodes that may
+ // be re-used have nil entries. No ID less than fuseops.RootInodeID is ever
+ // used.
+ // All inodes are protected by the file system mutex.
+ // INVARIANT: For each inode in, in.CheckInvariants() does not panic.
+ // INVARIANT: len(inodes) > fuseops.RootInodeID
+ // INVARIANT: For all i < fuseops.RootInodeID, inodes[i] == nil
+ // INVARIANT: inodes[fuseops.RootInodeID] != nil
+ // INVARIANT: inodes[fuseops.RootInodeID].isDir()
+ inodes []*inode // GUARDED_BY(mu)
+ // A list of inode IDs within inodes available for reuse, not including the
+ // reserved IDs less than fuseops.RootInodeID.
+ // INVARIANT: This is all and only indices i of 'inodes' such that i >
+ // fuseops.RootInodeID and inodes[i] == nil
+ freeInodes []fuseops.InodeID // GUARDED_BY(mu)
+// Create a file system that stores data and metadata in memory.
+// The supplied UID/GID pair will own the root inode. This file system does no
+// permissions checking, and should therefore be mounted with the
+// default_permissions option.
+func NewMemFS(
+ uid uint32,
+ gid uint32) fuse.Server {
+ // Set up the basic struct.
+ fs := &memFS{
+ inodes: make([]*inode, fuseops.RootInodeID+1),
+ uid: uid,
+ gid: gid,
+ // Set up the root inode.
+ rootAttrs := fuseops.InodeAttributes{
+ Mode: 0700 | os.ModeDir,
+ Uid: uid,
+ Gid: gid,
+ fs.inodes[fuseops.RootInodeID] = newInode(rootAttrs)
+ // Set up invariant checking.
+ fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
+ return fuseutil.NewFileSystemServer(fs)
+func (fs *memFS) checkInvariants() {
+ // Check reserved inodes.
+ for i := 0; i < fuseops.RootInodeID; i++ {
+ if fs.inodes[i] != nil {
+ panic(fmt.Sprintf("Non-nil inode for ID: %v", i))
+ // Check the root inode.
+ if !fs.inodes[fuseops.RootInodeID].isDir() {
+ panic("Expected root to be a directory.")
+ // Build our own list of free IDs.
+ freeIDsEncountered := make(map[fuseops.InodeID]struct{})
+ for i := fuseops.RootInodeID + 1; i < len(fs.inodes); i++ {
+ inode := fs.inodes[i]
+ if inode == nil {
+ freeIDsEncountered[fuseops.InodeID(i)] = struct{}{}
+ // Check fs.freeInodes.
+ if len(fs.freeInodes) != len(freeIDsEncountered) {
+ "Length mismatch: %v vs. %v",
+ len(fs.freeInodes),
+ len(freeIDsEncountered)))
+ for _, id := range fs.freeInodes {
+ if _, ok := freeIDsEncountered[id]; !ok {
+ panic(fmt.Sprintf("Unexected free inode ID: %v", id))
+ in.CheckInvariants()
+// Find the given inode. Panic if it doesn't exist.
+func (fs *memFS) getInodeOrDie(id fuseops.InodeID) (inode *inode) {
+ inode = fs.inodes[id]
+// Allocate a new inode, assigning it an ID that is not in use.
+func (fs *memFS) allocateInode(
+ attrs fuseops.InodeAttributes) (id fuseops.InodeID, inode *inode) {
+ // Create the inode.
+ inode = newInode(attrs)
+ // Re-use a free ID if possible. Otherwise mint a new one.
+ numFree := len(fs.freeInodes)
+ if numFree != 0 {
+ id = fs.freeInodes[numFree-1]
+ fs.freeInodes = fs.freeInodes[:numFree-1]
+ fs.inodes[id] = inode
+ id = fuseops.InodeID(len(fs.inodes))
+ fs.inodes = append(fs.inodes, inode)
+func (fs *memFS) deallocateInode(id fuseops.InodeID) {
+ fs.freeInodes = append(fs.freeInodes, id)
+ fs.inodes[id] = nil
+func (fs *memFS) StatFS(
+func (fs *memFS) LookUpInode(
+ // Grab the parent directory.
+ inode := fs.getInodeOrDie(op.Parent)
+ // Does the directory have an entry with the given name?
+ childID, _, ok := inode.LookUpChild(op.Name)
+ // Grab the child.
+ child := fs.getInodeOrDie(childID)
+ op.Entry.Child = childID
+ op.Entry.Attributes = child.attrs
+ // We don't spontaneously mutate, so the kernel can cache as long as it wants
+ // (since it also handles invalidation).
+ op.Entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
+ op.Entry.EntryExpiration = op.Entry.EntryExpiration
+func (fs *memFS) GetInodeAttributes(
+ // Grab the inode.
+ inode := fs.getInodeOrDie(op.Inode)
+ op.Attributes = inode.attrs
+ op.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
+func (fs *memFS) SetInodeAttributes(
+ // Handle the request.
+ inode.SetAttributes(op.Size, op.Mode, op.Mtime)
+func (fs *memFS) MkDir(
+ // Grab the parent, which we will update shortly.
+ parent := fs.getInodeOrDie(op.Parent)
+ // Ensure that the name doesn't already exist, so we don't wind up with a
+ // duplicate.
+ _, _, exists := parent.LookUpChild(op.Name)
+ if exists {
+ err = fuse.EEXIST
+ // Set up attributes from the child.
+ childAttrs := fuseops.InodeAttributes{
+ Mode: op.Mode,
+ Uid: fs.uid,
+ Gid: fs.gid,
+ // Allocate a child.
+ childID, child := fs.allocateInode(childAttrs)
+ // Add an entry in the parent.
+ parent.AddChild(childID, op.Name, fuseutil.DT_Directory)
+func (fs *memFS) MkNode(
+ op.Entry, err = fs.createFile(op.Parent, op.Name, op.Mode)
+func (fs *memFS) createFile(
+ parentID fuseops.InodeID,
+ mode os.FileMode) (entry fuseops.ChildInodeEntry, err error) {
+ parent := fs.getInodeOrDie(parentID)
+ _, _, exists := parent.LookUpChild(name)
+ // Set up attributes for the child.
+ Mode: mode,
+ Atime: now,
+ Mtime: now,
+ Ctime: now,
+ Crtime: now,
+ parent.AddChild(childID, name, fuseutil.DT_File)
+ // Fill in the response entry.
+ entry.Child = childID
+ entry.Attributes = child.attrs
+ entry.AttributesExpiration = time.Now().Add(365 * 24 * time.Hour)
+ entry.EntryExpiration = entry.AttributesExpiration
+func (fs *memFS) CreateFile(
+func (fs *memFS) CreateSymlink(
+ Mode: 0444 | os.ModeSymlink,
+ // Set up its target.
+ child.target = op.Target
+ parent.AddChild(childID, op.Name, fuseutil.DT_Link)
+func (fs *memFS) Rename(
+ // Ask the old parent for the child's inode ID and type.
+ oldParent := fs.getInodeOrDie(op.OldParent)
+ childID, childType, ok := oldParent.LookUpChild(op.OldName)
+ // If the new name exists already in the new parent, make sure it's not a
+ // non-empty directory, then delete it.
+ newParent := fs.getInodeOrDie(op.NewParent)
+ existingID, _, ok := newParent.LookUpChild(op.NewName)
+ existing := fs.getInodeOrDie(existingID)
+ var buf [4096]byte
+ if existing.isDir() && existing.ReadDir(buf[:], 0) > 0 {
+ err = fuse.ENOTEMPTY
+ newParent.RemoveChild(op.NewName)
+ // Link the new name.
+ newParent.AddChild(
+ childID,
+ op.NewName,
+ childType)
+ // Finally, remove the old name from the old parent.
+ oldParent.RemoveChild(op.OldName)
+func (fs *memFS) RmDir(
+ childID, _, ok := parent.LookUpChild(op.Name)
+ // Make sure the child is empty.
+ if child.Len() != 0 {
+ // Remove the entry within the parent.
+ parent.RemoveChild(op.Name)
+ // Mark the child as unlinked.
+ child.attrs.Nlink--
+func (fs *memFS) Unlink(
+func (fs *memFS) OpenDir(
+ // We don't mutate spontaneosuly, so if the VFS layer has asked for an
+ // inode that doesn't exist, something screwed up earlier (a lookup, a
+ // cache invalidation, etc.).
+ if !inode.isDir() {
+ panic("Found non-dir.")
+func (fs *memFS) ReadDir(
+ // Grab the directory.
+ // Serve the request.
+ op.BytesRead = inode.ReadDir(op.Dst, int(op.Offset))
+func (fs *memFS) OpenFile(
+ if !inode.isFile() {
+ panic("Found non-file.")
+func (fs *memFS) ReadFile(
+ // Find the inode in question.
+ op.BytesRead, err = inode.ReadAt(op.Dst, op.Offset)
+ // Don't return EOF errors; we just indicate EOF to fuse using a short read.
+func (fs *memFS) WriteFile(
+ _, err = inode.WriteAt(op.Data, op.Offset)
+func (fs *memFS) ReadSymlink(
+ op.Target = inode.target
+func (fs *memFS) GetXattr(ctx context.Context,
+ if value, ok := inode.xattrs[op.Name]; ok {
+ op.BytesRead = len(value)
+ if len(op.Dst) >= len(value) {
+ copy(op.Dst, value)
+ err = syscall.ERANGE
+ err = fuse.ENOATTR
+func (fs *memFS) ListXattr(ctx context.Context,
+ dst := op.Dst[:]
+ for key := range inode.xattrs {
+ keyLen := len(key) + 1
+ if err == nil && len(dst) >= keyLen {
+ copy(dst, key)
+ dst = dst[keyLen:]
+ op.BytesRead += keyLen
+func (fs *memFS) RemoveXattr(ctx context.Context,
+ if _, ok := inode.xattrs[op.Name]; ok {
+ delete(inode.xattrs, op.Name)
+func (fs *memFS) SetXattr(ctx context.Context,
+ _, ok := inode.xattrs[op.Name]
+ switch op.Flags {
+ case 0x1:
+ case 0x2:
+ value := make([]byte, len(op.Value))
+ copy(value, op.Value)
+ inode.xattrs[op.Name] = value
@@ -0,0 +1,5 @@
+package memfs_test
+const atLeastGo18 = true
+const atLeastGo18 = false
@@ -0,0 +1,447 @@
+// Tests for the behavior of os.File objects on plain old posix file systems,
+// for use in verifying the intended behavior of memfs.
+func TestPosix(t *testing.T) { RunTests(t) }
+func getFileOffset(f *os.File) (offset int64, err error) {
+ const relativeToCurrent = 1
+ offset, err = f.Seek(0, relativeToCurrent)
+type PosixTest struct {
+ ctx context.Context
+ // A temporary directory.
+ // Files to close when tearing down. Nil entries are skipped.
+ toClose []io.Closer
+var _ SetUpInterface = &PosixTest{}
+var _ TearDownInterface = &PosixTest{}
+func init() { RegisterTestSuite(&PosixTest{}) }
+func (t *PosixTest) SetUp(ti *TestInfo) {
+ t.ctx = ti.Ctx
+ // Create a temporary directory.
+ t.dir, err = ioutil.TempDir("", "posix_test")
+func (t *PosixTest) TearDown() {
+ // Close any files we opened.
+ for _, c := range t.toClose {
+ err := c.Close()
+ // Remove the temporary directory.
+ err := os.RemoveAll(t.dir)
+func (t *PosixTest) WriteOverlapsEndOfFile() {
+ // Create a file.
+ f, err := os.Create(path.Join(t.dir, "foo"))
+ t.toClose = append(t.toClose, f)
+ // Make it 4 bytes long.
+ err = f.Truncate(4)
+ // Write the range [2, 6).
+ n, err = f.WriteAt([]byte("taco"), 2)
+ AssertEq(4, n)
+ // Read the full contents of the file.
+ contents, err := ioutil.ReadAll(f)
+ ExpectEq("\x00\x00taco", string(contents))
+func (t *PosixTest) WriteStartsAtEndOfFile() {
+ // Make it 2 bytes long.
+ err = f.Truncate(2)
+func (t *PosixTest) WriteStartsPastEndOfFile() {
+func (t *PosixTest) WriteStartsPastEndOfFile_AppendMode() {
+ path.Join(t.dir, "foo"),
+ os.O_RDWR|os.O_APPEND|os.O_CREATE,
+ // Write three bytes.
+ n, err = f.Write([]byte("111"))
+ AssertEq(3, n)
+ // Write at offset six.
+ n, err = f.WriteAt([]byte("222"), 6)
+ // Linux's support for pwrite is buggy; the pwrite(2) man page says this:
+ // POSIX requires that opening a file with the O_APPEND flag should have
+ // no affect on the location at which pwrite() writes data. However, on
+ // Linux, if a file is opened with O_APPEND, pwrite() appends data to
+ // the end of the file, regardless of the value of offset.
+ contents, err := ioutil.ReadFile(f.Name())
+ if runtime.GOOS == "linux" {
+ ExpectEq("111222", string(contents))
+ ExpectEq("111\x00\x00\x00222", string(contents))
+func (t *PosixTest) WriteAtDoesntChangeOffset_NotAppendMode() {
+ // Make it 16 bytes long.
+ err = f.Truncate(16)
+ // Seek to offset 4.
+ _, err = f.Seek(4, 0)
+ // Write the range [10, 14).
+ // We should still be at offset 4.
+ offset, err := getFileOffset(f)
+ ExpectEq(4, offset)
+func (t *PosixTest) WriteAtDoesntChangeOffset_AppendMode() {
+ // Create a file in append mode.
+func (t *PosixTest) AppendMode() {
+ buf := make([]byte, 1024)
+ // Create a file with some contents.
+ fileName := path.Join(t.dir, "foo")
+ err = ioutil.WriteFile(fileName, []byte("Jello, "), 0600)
+ // Open the file in append mode.
+ f, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0600)
+ // Seek to somewhere silly and then write.
+ off, err = f.Seek(2, 0)
+ AssertEq(2, off)
+ n, err = f.Write([]byte("world!"))
+ AssertEq(6, n)
+ // The offset should have been updated to point at the end of the file.
+ off, err = getFileOffset(f)
+ ExpectEq(13, off)
+ // A random write should still work, without updating the offset.
+ n, err = f.WriteAt([]byte("H"), 0)
+ AssertEq(1, n)
+ // Read back the contents of the file, which should be correct even though we
+ // seeked to a silly place before writing the world part.
+ // So we allow either the POSIX result or the Linux result.
+ n, err = f.ReadAt(buf, 0)
+ ExpectEq("Jello, world!H", string(buf[:n]))
+ ExpectEq("Hello, world!", string(buf[:n]))
+func (t *PosixTest) ReadsPastEndOfFile() {
+ // Give it some contents.
+ n, err = f.Write([]byte("taco"))
+ // Read a range overlapping EOF.
+ n, err = f.ReadAt(buf[:4], 2)
+ ExpectEq("co", string(buf[:n]))
+ // Read a range starting at EOF.
+ n, err = f.ReadAt(buf[:4], 4)
+ ExpectEq(0, n)
+ ExpectEq("", string(buf[:n]))
+ // Read a range starting past EOF.
+ n, err = f.ReadAt(buf[:4], 100)
+func (t *PosixTest) HardLinkDirectory() {
+ dirName := path.Join(t.dir, "dir")
+ // Create a directory.
+ err := os.Mkdir(dirName, 0700)
+ // Attempt to hard-link it to a new name.
+ err = os.Link(dirName, path.Join(t.dir, "other"))
+ ExpectThat(err, Error(HasSubstr("link")))
+ ExpectThat(err, Error(HasSubstr("not permitted")))
+func (t *PosixTest) RmdirWhileOpenedForReading() {
+ err = os.Mkdir(path.Join(t.dir, "dir"), 0700)
+ // Open the directory for reading.
+ f, err := os.Open(path.Join(t.dir, "dir"))
+ // Remove the directory.
+ err = os.Remove(path.Join(t.dir, "dir"))
+ // Create a new directory, with the same name even, and add some contents
+ err = os.MkdirAll(path.Join(t.dir, "dir/foo"), 0700)
+ err = os.MkdirAll(path.Join(t.dir, "dir/bar"), 0700)
+ err = os.MkdirAll(path.Join(t.dir, "dir/baz"), 0700)
+ // We should still be able to stat the open file handle.
+ fi, err := f.Stat()
+ // Attempt to read from the directory. This shouldn't see any junk from the
+ // new directory. It should either succeed with an empty result or should
+ // return ENOENT.
+ names, err := f.Readdirnames(0)
+ ExpectThat(names, ElementsAre())
+func (t *PosixTest) CreateInParallel_NoTruncate() {
+ fusetesting.RunCreateInParallelTest_NoTruncate(t.ctx, t.dir)
+func (t *PosixTest) CreateInParallel_Truncate() {
+ fusetesting.RunCreateInParallelTest_Truncate(t.ctx, t.dir)
+func (t *PosixTest) CreateInParallel_Exclusive() {
+ fusetesting.RunCreateInParallelTest_Exclusive(t.ctx, t.dir)
+func (t *PosixTest) MkdirInParallel() {
+ fusetesting.RunMkdirInParallelTest(t.ctx, t.dir)
+func (t *PosixTest) SymlinkInParallel() {
+ fusetesting.RunSymlinkInParallelTest(t.ctx, t.dir)
@@ -0,0 +1,163 @@
+// A simple tool for mounting sample file systems, used by the tests in
+// samples/.
+package main
+ "flag"
+ "github.com/jacobsa/fuse/samples/flushfs"
+var fType = flag.String("type", "", "The name of the samples/ sub-dir.")
+var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
+var fReadyFile = flag.Uint64("ready_file", 0, "FD to signal when ready.")
+var fFlushesFile = flag.Uint64("flushfs.flushes_file", 0, "")
+var fFsyncsFile = flag.Uint64("flushfs.fsyncs_file", 0, "")
+var fFlushError = flag.Int("flushfs.flush_error", 0, "")
+var fFsyncError = flag.Int("flushfs.fsync_error", 0, "")
+var fReadOnly = flag.Bool("read_only", false, "Mount in read-only mode.")
+var fDebug = flag.Bool("debug", false, "Enable debug logging.")
+func makeFlushFS() (server fuse.Server, err error) {
+ // Check the flags.
+ if *fFlushesFile == 0 || *fFsyncsFile == 0 {
+ err = fmt.Errorf("You must set the flushfs flags.")
+ // Set up the files.
+ flushes := os.NewFile(uintptr(*fFlushesFile), "(flushes file)")
+ fsyncs := os.NewFile(uintptr(*fFsyncsFile), "(fsyncs file)")
+ // Set up errors.
+ var flushErr error
+ var fsyncErr error
+ if *fFlushError != 0 {
+ flushErr = syscall.Errno(*fFlushError)
+ if *fFsyncError != 0 {
+ fsyncErr = syscall.Errno(*fFsyncError)
+ // Report flushes and fsyncs by writing the contents followed by a newline.
+ report := func(f *os.File, outErr error) func(string) error {
+ return func(s string) (err error) {
+ buf := []byte(s)
+ buf = append(buf, '\n')
+ _, err = f.Write(buf)
+ err = fmt.Errorf("Write: %v", err)
+ err = outErr
+ reportFlush := report(flushes, flushErr)
+ reportFsync := report(fsyncs, fsyncErr)
+ server, err = flushfs.NewFileSystem(reportFlush, reportFsync)
+func makeFS() (server fuse.Server, err error) {
+ switch *fType {
+ err = fmt.Errorf("Unknown FS type: %v", *fType)
+ case "flushfs":
+ server, err = makeFlushFS()
+func getReadyFile() (f *os.File, err error) {
+ if *fReadyFile == 0 {
+ err = errors.New("You must set --ready_file.")
+ f = os.NewFile(uintptr(*fReadyFile), "(ready file)")
+func main() {
+ flag.Parse()
+ // Allow parallelism in the file system implementation, to help flush out
+ // bugs like https://github.com/jacobsa/fuse/issues/4.
+ runtime.GOMAXPROCS(2)
+ // Grab the file to signal when ready.
+ readyFile, err := getReadyFile()
+ log.Fatalf("getReadyFile:", err)
+ // Create an appropriate file system.
+ server, err := makeFS()
+ log.Fatalf("makeFS: %v", err)
+ if *fMountPoint == "" {
+ log.Fatalf("You must set --mount_point.")
+ cfg := &fuse.MountConfig{
+ ReadOnly: *fReadOnly,
+ mfs, err := fuse.Mount(*fMountPoint, server, cfg)
+ log.Fatalf("Mount: %v", err)
+ // Signal that it is ready.
+ _, err = readyFile.Write([]byte("x"))
+ log.Fatalf("readyFile.Write: %v", err)
+ // Wait for it to be unmounted.
+ if err = mfs.Join(context.Background()); err != nil {
+ log.Fatalf("Join: %v", err)
@@ -0,0 +1,184 @@
+package statfs
+// A file system that allows orchestrating canned responses to statfs ops, for
+// testng out OS-specific statfs behavior.
+// The file system allows opening and writing to any name that is a child of
+// the root inode, and keeps track of the most recent write size delivered by
+// the kernel (in order to test statfs response block size effects on write
+// size, if any).
+// Safe for concurrent access.
+ // Set the canned response to be used for future statfs ops.
+ SetStatFSResponse(r fuseops.StatFSOp)
+ // Set the canned response to be used for future stat ops.
+ SetStatResponse(r fuseops.InodeAttributes)
+ // Return the size of the most recent write delivered by the kernel, or -1 if
+ // none.
+ MostRecentWriteSize() int
+func New() (fs FS) {
+ fs = &statFS{
+ cannedStatResponse: fuseops.InodeAttributes{
+ Mode: 0666,
+ mostRecentWriteSize: -1,
+const childInodeID = fuseops.RootInodeID + 1
+type statFS struct {
+ cannedResponse fuseops.StatFSOp // GUARDED_BY(mu)
+ cannedStatResponse fuseops.InodeAttributes // GUARDED_BY(mu)
+ mostRecentWriteSize int // GUARDED_BY(mu)
+func dirAttrs() fuseops.InodeAttributes {
+func (fs *statFS) fileAttrs() fuseops.InodeAttributes {
+ return fs.cannedStatResponse
+func (fs *statFS) SetStatFSResponse(r fuseops.StatFSOp) {
+ fs.cannedResponse = r
+func (fs *statFS) SetStatResponse(r fuseops.InodeAttributes) {
+ fs.cannedStatResponse = r
+func (fs *statFS) MostRecentWriteSize() int {
+ return fs.mostRecentWriteSize
+func (fs *statFS) StatFS(
+ *op = fs.cannedResponse
+func (fs *statFS) LookUpInode(
+ // Only the root has children.
+ op.Entry.Child = childInodeID
+ op.Entry.Attributes = fs.fileAttrs()
+func (fs *statFS) GetInodeAttributes(
+ op.Attributes = dirAttrs()
+ case childInodeID:
+ op.Attributes = fs.fileAttrs()
+func (fs *statFS) SetInodeAttributes(
+ // Ignore calls to truncate existing files when opening.
+func (fs *statFS) OpenFile(
+func (fs *statFS) WriteFile(
+ fs.mostRecentWriteSize = len(op.Data)
+package statfs_test
+ "math"
+ "regexp"
+// Sample output:
+// Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
+// fake@bucket 32 16 16 50% 0 0 100% /Users/jacobsa/tmp/mp
+var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+\d+\s+\d+\s+\d+%.*$`)
+func convertName(in []int8) (s string) {
+ var tmp []byte
+ for _, v := range in {
+ if v == 0 {
+ tmp = append(tmp, byte(v))
+ s = string(tmp)
+func (t *StatFSTest) Syscall_ZeroValues() {
+ var stat syscall.Statfs_t
+ // Call without configuring a canned response, meaning the OS will see the
+ // zero value for each field. The assertions below act as documentation for
+ // the OS's behavior in this case.
+ err = syscall.Statfs(t.Dir, &stat)
+ ExpectEq(4096, stat.Bsize)
+ ExpectEq(65536, stat.Iosize)
+ ExpectEq(0, stat.Blocks)
+ ExpectEq(0, stat.Bfree)
+ ExpectEq(0, stat.Bavail)
+ ExpectEq(0, stat.Files)
+ ExpectEq(0, stat.Ffree)
+ ExpectEq("osxfuse", convertName(stat.Fstypename[:]))
+ ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
+ ExpectEq(fsName, convertName(stat.Mntfromname[:]))
+func (t *StatFSTest) Syscall_NonZeroValues() {
+ // Set up the canned response.
+ canned := fuseops.StatFSOp{
+ BlockSize: 1 << 15,
+ IoSize: 1 << 16,
+ Blocks: 1<<51 + 3,
+ BlocksFree: 1<<43 + 5,
+ BlocksAvailable: 1<<41 + 7,
+ Inodes: 1<<59 + 11,
+ InodesFree: 1<<58 + 13,
+ t.fs.SetStatFSResponse(canned)
+ // Stat.
+ ExpectEq(canned.BlockSize, stat.Bsize)
+ ExpectEq(canned.IoSize, stat.Iosize)
+ ExpectEq(canned.Blocks, stat.Blocks)
+ ExpectEq(canned.BlocksFree, stat.Bfree)
+ ExpectEq(canned.BlocksAvailable, stat.Bavail)
+ ExpectEq(canned.Inodes, stat.Files)
+ ExpectEq(canned.InodesFree, stat.Ffree)
+func (t *StatFSTest) BlockSizes() {
+ // Test a bunch of block sizes that the OS does or doesn't support
+ // faithfully, checking what it transforms them too.
+ testCases := []struct {
+ fsBlockSize uint32
+ expectedBsize uint32
+ }{
+ 0: {0, 4096},
+ 1: {1, 128},
+ 2: {3, 128},
+ 3: {511, 512},
+ 4: {512, 512},
+ 5: {513, 1024},
+ 6: {1023, 1024},
+ 7: {1024, 1024},
+ 8: {4095, 4096},
+ 9: {1 << 16, 1 << 16},
+ 10: {1 << 17, 1 << 17},
+ 11: {1 << 18, 1 << 18},
+ 12: {1 << 19, 1 << 19},
+ 13: {1<<20 - 1, 1 << 20},
+ 14: {1 << 20, 1 << 20},
+ 15: {1<<20 + 1, 1 << 20},
+ 16: {1 << 21, 1 << 20},
+ 17: {1 << 22, 1 << 20},
+ 18: {math.MaxInt32 - 1, 1 << 20},
+ 19: {math.MaxInt32, 1 << 20},
+ 20: {math.MaxInt32 + 1, 128},
+ 21: {math.MaxInt32 + 1<<15, 1 << 15},
+ 22: {math.MaxUint32, 1 << 20},
+ for i, tc := range testCases {
+ desc := fmt.Sprintf("Case %d: block size %d", i, tc.fsBlockSize)
+ // Set up.
+ BlockSize: tc.fsBlockSize,
+ Blocks: 10,
+ // Check.
+ ExpectEq(tc.expectedBsize, stat.Bsize, "%s", desc)
+func (t *StatFSTest) IoSizes() {
+ // Test a bunch of io sizes that the OS does or doesn't support faithfully,
+ // checking what it transforms them too.
+ fsIoSize uint32
+ expectedIosize uint32
+ 0: {0, 65536},
+ 1: {1, 4096},
+ 2: {3, 4096},
+ 3: {4095, 4096},
+ 4: {4096, 4096},
+ 5: {4097, 8192},
+ 6: {8191, 8192},
+ 7: {8192, 8192},
+ 8: {8193, 16384},
+ 9: {1 << 18, 1 << 18},
+ 10: {1 << 20, 1 << 20},
+ 11: {1 << 23, 1 << 23},
+ 12: {1<<25 - 1, 1 << 25},
+ 13: {1 << 25, 1 << 25},
+ 14: {1<<25 + 1, 1 << 25},
+ 15: {math.MaxInt32 - 1, 1 << 25},
+ 16: {math.MaxInt32, 1 << 25},
+ 17: {math.MaxInt32 + 1, 4096},
+ 18: {math.MaxInt32 + 1<<15, 1 << 15},
+ 19: {math.MaxUint32, 1 << 25},
+ desc := fmt.Sprintf("Case %d: IO size %d", i, tc.fsIoSize)
+ IoSize: tc.fsIoSize,
+ ExpectEq(tc.expectedIosize, stat.Iosize, "%s", desc)
@@ -0,0 +1,161 @@
+// Filesystem 1K-blocks Used Available Use% Mounted on
+// some_fuse_file_system 512 64 384 15% /tmp/sample_test001288095
+var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%.*$`)
+ ExpectEq(0, stat.Bsize)
+ ExpectEq(0, stat.Frsize)
+ ExpectEq(canned.BlockSize, stat.Frsize)
+ ExpectEq(canned.IoSize, stat.Bsize)
+ // Test a bunch of weird block sizes that OS X would be cranky about.
+ blockSizes := []uint32{
+ 0,
+ 1,
+ 3,
+ 17,
+ 1<<20 + 0,
+ math.MaxInt32,
+ math.MaxInt32 + 1,
+ math.MaxUint32,
+ for _, bs := range blockSizes {
+ desc := fmt.Sprintf("block size %d", bs)
+ BlockSize: bs,
+ ExpectEq(bs, stat.Frsize, "%s", desc)
+ // Test a bunch of weird IO sizes that OS X would be cranky about.
+ ioSizes := []uint32{
+ for _, bs := range ioSizes {
+ desc := fmt.Sprintf("IO size %d", bs)
+ IoSize: bs,
+ ExpectEq(bs, stat.Bsize, "%s", desc)
@@ -0,0 +1,228 @@
+ "path/filepath"
+ "github.com/jacobsa/fuse/samples/statfs"
+func TestStatFS(t *testing.T) { RunTests(t) }
+const fsName = "some_fs_name"
+const volumeName = "Some volume"
+// Ask `df` for statistics about the file system's capacity and free space,
+// useful for checking that our reading of statfs(2) output matches the
+// system's. The output is not guaranteed to have resolution greater than 2^10
+// (1 KiB).
+func df(dir string) (capacity, used, available uint64, err error) {
+ // Call df with a block size of 1024 and capture its output.
+ cmd := exec.Command("df", dir)
+ cmd.Env = []string{"BLOCKSIZE=1024"}
+ output, err := cmd.CombinedOutput()
+ // Scrape it.
+ for _, line := range bytes.Split(output, []byte{'\n'}) {
+ // Is this the line we're interested in?
+ if !bytes.Contains(line, []byte(dir)) {
+ submatches := gDfOutputRegexp.FindSubmatch(line)
+ if submatches == nil {
+ err = fmt.Errorf("Unable to parse line: %q", line)
+ capacity, err = strconv.ParseUint(string(submatches[1]), 10, 64)
+ used, err = strconv.ParseUint(string(submatches[2]), 10, 64)
+ available, err = strconv.ParseUint(string(submatches[3]), 10, 64)
+ // Scale appropriately based on the BLOCKSIZE set above.
+ capacity *= 1024
+ used *= 1024
+ available *= 1024
+ err = fmt.Errorf("Unable to parse df output:\n%s", output)
+type StatFSTest struct {
+ fs statfs.FS
+ // t.Dir, with symlinks resolved and redundant path components removed.
+ canonicalDir string
+var _ SetUpInterface = &StatFSTest{}
+var _ TearDownInterface = &StatFSTest{}
+func init() { RegisterTestSuite(&StatFSTest{}) }
+func (t *StatFSTest) SetUp(ti *TestInfo) {
+ // Writeback caching can ruin our measurement of the write sizes the kernel
+ // decides to give us, since it causes write acking to race against writes
+ // being issued from the client.
+ // Configure names.
+ t.MountConfig.FSName = fsName
+ t.MountConfig.VolumeName = volumeName
+ t.fs = statfs.New()
+ // Canonicalize the mount point.
+ t.canonicalDir, err = filepath.EvalSymlinks(t.Dir)
+ t.canonicalDir = path.Clean(t.canonicalDir)
+func (t *StatFSTest) CapacityAndFreeSpace() {
+ Blocks: 1024,
+ BlocksFree: 896,
+ BlocksAvailable: 768,
+ IoSize: 1024, // Shouldn't matter.
+ // Check that df agrees with us about a range of block sizes.
+ for log2BlockSize := uint(9); log2BlockSize <= 17; log2BlockSize++ {
+ bs := uint64(1) << log2BlockSize
+ desc := fmt.Sprintf("block size: %d (2^%d)", bs, log2BlockSize)
+ canned.BlockSize = uint32(bs)
+ // Call df.
+ capacity, used, available, err := df(t.canonicalDir)
+ ExpectEq(bs*canned.Blocks, capacity, "%s", desc)
+ ExpectEq(bs*(canned.Blocks-canned.BlocksFree), used, "%s", desc)
+ ExpectEq(bs*canned.BlocksAvailable, available, "%s", desc)
+func (t *StatFSTest) WriteSize() {
+ // Set up a smallish block size.
+ BlockSize: 8192,
+ IoSize: 16384,
+ Blocks: 1234,
+ BlocksFree: 1234,
+ BlocksAvailable: 1234,
+ // Cause a large amount of date to be written.
+ err = ioutil.WriteFile(
+ path.Join(t.Dir, "foo"),
+ bytes.Repeat([]byte{'x'}, 1<<22),
+ 0400)
+ // Despite the small block size, the OS shouldn't have given us pitifully
+ // small chunks of data.
+ switch runtime.GOOS {
+ case "linux":
+ ExpectEq(1<<17, t.fs.MostRecentWriteSize())
+ case "darwin":
+ ExpectEq(1<<20, t.fs.MostRecentWriteSize())
+ AddFailure("Unhandled OS: %s", runtime.GOOS)
+func (t *StatFSTest) StatBlocks() {
+ var stat syscall.Stat_t
+ const fileName = "foo"
+ const size = 1 << 22
+ path.Join(t.Dir, fileName),
+ bytes.Repeat([]byte{'x'}, size),
+ t.fs.SetStatResponse(fuseops.InodeAttributes{
+ Size: size,
+ err = syscall.Stat(path.Join(t.Dir, fileName), &stat)
+ ExpectEq(size/512, stat.Blocks)
@@ -0,0 +1,366 @@
+var fToolPath = flag.String(
+ "mount_sample",
+ "",
+ "Path to the mount_sample tool. If unset, we will compile it.")
+var fDebug = flag.Bool("debug", false, "If true, print fuse debug info.")
+// directory where the file system is mounted by a subprocess. Use it as an
+// embedded field in your test fixture, calling its SetUp method from your
+// SetUp method after setting the MountType and MountFlags fields.
+type SubprocessTest struct {
+ // The type of the file system to mount. Must be recognized by mount_sample.
+ MountType string
+ // Additional flags to be passed to the mount_sample tool.
+ MountFlags []string
+ // A list of files to pass to mount_sample. The given string flag will be
+ // used to pass the file descriptor number.
+ MountFiles map[string]*os.File
+ mountSampleErr <-chan error
+// Mount the file system and initialize the other exported fields of the
+// struct. Panics on error.
+func (t *SubprocessTest) SetUp(ti *ogletest.TestInfo) {
+ err := t.initialize(ti.Ctx)
+// Private state for getToolPath.
+var getToolContents_Contents []byte
+var getToolContents_Err error
+var getToolContents_Once sync.Once
+// Implementation detail of getToolPath.
+func getToolContentsImpl() (contents []byte, err error) {
+ // Fast path: has the user set the flag?
+ if *fToolPath != "" {
+ contents, err = ioutil.ReadFile(*fToolPath)
+ err = fmt.Errorf("Reading mount_sample contents: %v", err)
+ // Create a temporary directory into which we will compile the tool.
+ tempDir, err := ioutil.TempDir("", "sample_test")
+ toolPath := path.Join(tempDir, "mount_sample")
+ // Ensure that we kill the temporary directory when we're finished here.
+ defer os.RemoveAll(tempDir)
+ // Run "go build".
+ "go",
+ "build",
+ "-o",
+ toolPath,
+ "github.com/jacobsa/fuse/samples/mount_sample")
+ "mount_sample exited with %v, output:\n%s",
+ err,
+ string(output))
+ // Slurp the tool contents.
+ contents, err = ioutil.ReadFile(toolPath)
+ err = fmt.Errorf("ReadFile: %v", err)
+// Build the mount_sample tool if it has not yet been built for this process.
+// Return its contents.
+func getToolContents() (contents []byte, err error) {
+ // Get hold of the binary contents, if we haven't yet.
+ getToolContents_Once.Do(func() {
+ getToolContents_Contents, getToolContents_Err = getToolContentsImpl()
+ contents, err = getToolContents_Contents, getToolContents_Err
+func waitForMountSample(
+ cmd *exec.Cmd,
+ errChan chan<- error,
+ stderr *bytes.Buffer) {
+ // However we exit, write the error to the channel.
+ errChan <- err
+ // Wait for the command.
+ err = cmd.Wait()
+ // Make exit errors nicer.
+ if exitErr, ok := err.(*exec.ExitError); ok {
+ "mount_sample exited with %v. Stderr:\n%s",
+ exitErr,
+ stderr.String())
+ err = fmt.Errorf("Waiting for mount_sample: %v", err)
+func waitForReady(readyReader *os.File, c chan<- struct{}) {
+ _, err := readyReader.Read(make([]byte, 1))
+ log.Printf("Readying from ready pipe: %v", err)
+ c <- struct{}{}
+func (t *SubprocessTest) initialize(ctx context.Context) (err error) {
+ // Initialize the context.
+ // Build/read the mount_sample tool.
+ toolContents, err := getToolContents()
+ err = fmt.Errorf("getTooltoolContents: %v", err)
+ // Create a temporary file to hold the contents of the tool.
+ toolFile, err := ioutil.TempFile("", "sample_test")
+ defer toolFile.Close()
+ // Ensure that it is deleted when we leave.
+ toolPath := toolFile.Name()
+ defer os.Remove(toolPath)
+ // Write out the tool contents and make them executable.
+ if _, err = toolFile.Write(toolContents); err != nil {
+ err = fmt.Errorf("toolFile.Write: %v", err)
+ if err = toolFile.Chmod(0500); err != nil {
+ err = fmt.Errorf("toolFile.Chmod: %v", err)
+ // Close the tool file to prevent "text file busy" errors below.
+ err = toolFile.Close()
+ toolFile = nil
+ err = fmt.Errorf("toolFile.Close: %v", err)
+ // Set up basic args for the subprocess.
+ args := []string{
+ "--type",
+ t.MountType,
+ "--mount_point",
+ t.Dir,
+ args = append(args, t.MountFlags...)
+ // Set up a pipe for the "ready" status.
+ readyReader, readyWriter, err := os.Pipe()
+ err = fmt.Errorf("Pipe: %v", err)
+ defer readyReader.Close()
+ defer readyWriter.Close()
+ t.MountFiles["ready_file"] = readyWriter
+ // Set up inherited files and appropriate flags.
+ var extraFiles []*os.File
+ for flag, file := range t.MountFiles {
+ // Cf. os/exec.Cmd.ExtraFiles
+ fd := 3 + len(extraFiles)
+ extraFiles = append(extraFiles, file)
+ args = append(args, "--"+flag)
+ args = append(args, fmt.Sprintf("%d", fd))
+ // Set up a command.
+ mountCmd := exec.Command(toolPath, args...)
+ mountCmd.Stderr = &stderr
+ mountCmd.ExtraFiles = extraFiles
+ // Handle debug mode.
+ mountCmd.Stderr = os.Stderr
+ mountCmd.Args = append(mountCmd.Args, "--debug")
+ // Start the command.
+ if err = mountCmd.Start(); err != nil {
+ err = fmt.Errorf("mountCmd.Start: %v", err)
+ // Launch a goroutine that waits for it and returns its status.
+ mountSampleErr := make(chan error, 1)
+ go waitForMountSample(mountCmd, mountSampleErr, &stderr)
+ // Wait for the tool to say the file system is ready. In parallel, watch for
+ // the tool to fail.
+ readyChan := make(chan struct{}, 1)
+ go waitForReady(readyReader, readyChan)
+ case <-readyChan:
+ case err = <-mountSampleErr:
+ // TearDown is no responsible for joining.
+ t.mountSampleErr = mountSampleErr
+func (t *SubprocessTest) TearDown() {
+func (t *SubprocessTest) destroy() (err error) {
+ // Make sure we clean up after ourselves after everything else below.
+ // If we didn't try to mount the file system, there's nothing further to do.
+ if t.mountSampleErr == nil {
+ // In the background, initiate an unmount.
+ unmountErrChan := make(chan error)
+ unmountErrChan <- unmount(t.Dir)
+ // Make sure we wait for the unmount, even if we've already returned early in
+ // error. Return its error if we haven't seen any other error.
+ // Wait.
+ unmountErr := <-unmountErrChan
+ if unmountErr != nil {
+ log.Println("unmount:", unmountErr)
+ err = fmt.Errorf("unmount: %v", unmountErr)
+ // Clean up.
+ ogletest.ExpectEq(nil, os.Remove(t.Dir))
+ // Wait for the subprocess.
+ if err = <-t.mountSampleErr; err != nil {
@@ -0,0 +1,48 @@
+// Unmount the file system mounted at the supplied directory. Try again on
+// "resource busy" errors, which happen from time to time on OS X (due to weird
+// requests from the Finder) and when tests don't or can't synchronize all
+// events.
+func unmount(dir string) (err error) {
+ delay := 10 * time.Millisecond
+ err = fuse.Unmount(dir)
+ if strings.Contains(err.Error(), "resource busy") {
+ log.Println("Resource busy error while unmounting; trying again")
+ time.Sleep(delay)
+ delay = time.Duration(1.3 * float64(delay))
+ err = fmt.Errorf("Unmount: %v", err)
+// Unmount attempts to unmount the file system whose mount point is the
+// supplied directory.
+func Unmount(dir string) error {
+ return unmount(dir)
+ // Call fusermount.
+ cmd := exec.Command("fusermount", "-u", dir)
+ if len(output) > 0 {
@@ -0,0 +1,18 @@
+// +build !linux
+ err = syscall.Unmount(dir, 0)
+ err = &os.PathError{Op: "unmount", Path: dir, Err: err}
@@ -2,5 +2,5 @@ package main
const (
//Version contains version of the package
- Version = "0.1 - built: 2017-07-09 20:47:15 UTC"
+ Version = "0.1-1-gad1a9d3 - built: 2017-07-10 16:18:52 UTC"