func (fs *fsMutable) createNode(lk []byte, parentINode fuseops.InodeID, childName string,
	entry *fuseops.ChildInodeEntry, nodeType fuseutil.DirentType, isRoot bool) error {
	if lk == nil {
		lk = formLookupKey(parentINode, childName)
	}
	var iNodeID fuseops.InodeID
	if !isRoot {
		iNodeID = fs.iNodeGenerator.allocINode()
	} else {
		iNodeID = parentINode
	}
	fs.lookupTree, _, _ = fs.lookupTree.Insert(lk, lookupEntry{iNode: iNodeID})
	var linkCount = fileLinkCount
	var defaultMode os.FileMode = fileDefaultMode
	var defaultSize uint64
	if nodeType == fuseutil.DT_Directory {
		linkCount = dirLinkCount
		defaultMode = dirDefaultMode
		defaultSize = dirInitialSize
		fs.readDirMap[iNodeID] = make(map[fuseops.InodeID]*fuseutil.Dirent)
	} else {
		file, err := fs.localCache.Create(fmt.Sprint(iNodeID))
		if err == nil {
			fs.backingFiles[iNodeID] = &file
		} else {
			fs.l.Warn("failed to create backing file: open file will retry this",
				zap.Error(err),
				zap.String("child", childName),
				zap.String("localfs", fs.localCache.Name()),
				zap.Uint64("parent", uint64(parentINode)))
		}
	}
	d := &fuseutil.Dirent{
		Inode: iNodeID,
		Name:  childName,
		Type:  nodeType,
	}
	if !isRoot {
		fs.insertReadDirEntry(parentINode, d)
	}
	ts := time.Now()
	attr := fuseops.InodeAttributes{
		Size:   defaultSize,
		Nlink:  linkCount,
		Mode:   defaultMode,
		Atime:  ts,
		Mtime:  ts,
		Ctime:  ts,
		Crtime: ts,
		Uid:    defaultGID,
		Gid:    defaultUID,
	}
	fs.iNodeStore, _, _ = fs.iNodeStore.Insert(formKey(iNodeID), &nodeEntry{
		lock:              sync.Mutex{},
		refCount:          1,  
		pathToBackingFile: getPathToBackingFile(iNodeID),
		attr:              attr,
	})
	if nodeType == fuseutil.DT_Directory {
		p, _ := fs.iNodeStore.Get(formKey(parentINode))
		parentNodeEntry := p.(*nodeEntry)
		parentNodeEntry.attr.Nlink++
	}
	if entry != nil {
		entry.Attributes = attr
		entry.EntryExpiration = time.Now().Add(cacheYearLong)
		entry.AttributesExpiration = time.Now().Add(cacheYearLong)
		entry.Child = iNodeID
	}
	return nil
}
Create a node. Need to hold the locks before calling.
package fuse
import (
	"context"
	"fmt"
	"math"
	"os"
	"sync"
	"time"
	"go.uber.org/zap"
	iradix "github.com/hashicorp/go-immutable-radix"
	"github.com/spf13/afero"
	"github.com/jacobsa/fuse/fuseutil"
	jfuse "github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/oneconcern/datamon/pkg/cafs"
	"github.com/oneconcern/datamon/pkg/core"
	"github.com/oneconcern/datamon/pkg/dlogger"
	"github.com/oneconcern/datamon/pkg/model"
)
var _ fuseutil.FileSystem = &fsMutable{}
type fsMutable struct {
	fsCommon
	iNodeStore *iradix.Tree
	readDirMap map[fuseops.InodeID]map[fuseops.InodeID]*fuseutil.Dirent
	backingFiles map[fuseops.InodeID]*afero.File
	lock             sync.Mutex
	lockBackingFiles sync.RWMutex
	iNodeGenerator iNodeGenerator
	localCache afero.Fs
}
func defaultMutableFS(bundle *core.Bundle, pathToStaging string) *fsMutable {
	return &fsMutable{
		fsCommon: fsCommon{
			bundle:     bundle,
			lookupTree: iradix.New(),
			l:          dlogger.MustGetLogger("info"),
		},
		readDirMap:   make(map[fuseops.InodeID]map[fuseops.InodeID]*fuseutil.Dirent),
		iNodeStore:   iradix.New(),
		backingFiles: make(map[fuseops.InodeID]*afero.File),
		lock:         sync.Mutex{},
		iNodeGenerator: iNodeGenerator{
			lock:         sync.Mutex{},
			highestInode: firstINode,
			freeInodes:   make([]fuseops.InodeID, 0, 65536),
		},
		localCache: afero.NewBasePathFs(afero.NewOsFs(), pathToStaging),
	}
}
func (fs *fsMutable) atomicGetReferences() (nodeStore *iradix.Tree, lookupTree *iradix.Tree) {
	fs.lock.Lock()
	lookupTree = fs.lookupTree
	nodeStore = fs.iNodeStore
	fs.lock.Unlock()
	return
}
func lookup(p fuseops.InodeID, c string, lookupTree *iradix.Tree) (le lookupEntry, found bool, lk []byte) {
	lk = formLookupKey(p, c)
	val, found := lookupTree.Get(lk)
	if found {
		le = val.(lookupEntry)
		return le, found, lk
	}
	return lookupEntry{}, found, lk
}
func (fs *fsMutable) lookup(p fuseops.InodeID, c string) (le lookupEntry, found bool, lk []byte) {
	return lookup(p, c, fs.lookupTree)
}
func (fs *fsMutable) deleteNSEntry(p fuseops.InodeID, c string) error {
	pn, found := fs.iNodeStore.Get(formKey(p))
	if !found {
		return jfuse.ENOENT
	}
	pNode := pn.(*nodeEntry)
	cLE, found, lk := fs.lookup(p, c)
	if !found {
		return jfuse.ENOENT
	}
	cn, found := fs.iNodeStore.Get(formKey(cLE.iNode))
	if !found {
		fs.l.Error("Did not find node after lookup", zap.Uint64("childInode", uint64(cLE.iNode)), zap.String("name", c))
		panic("did not find node after lookup")
	}
	cNode := cn.(*nodeEntry)
	if cNode.attr.Mode.IsDir() {
		children := fs.readDirMap[cLE.iNode]
		if len(children) > 0 {
			return jfuse.ENOTEMPTY
		}
		delete(fs.readDirMap, cLE.iNode)
		pNode.attr.Nlink--
	}
	fs.lookupTree, _, _ = fs.lookupTree.Delete(lk)
	children := fs.readDirMap[p]
	delete(children, cLE.iNode)
	return nil
}
func (fs *fsMutable) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	nodeStore, lookupTree := fs.atomicGetReferences()
	childEntry, found, _ := lookup(op.Parent, op.Name, lookupTree)
	if found {
		v, _ := nodeStore.Get(formKey(childEntry.iNode))
		n := v.(*nodeEntry)
		n.refCount++  
		op.Entry.Attributes = n.attr
		op.Entry.Generation = 1
		op.Entry.Child = childEntry.iNode
		op.Entry.AttributesExpiration = time.Now().Add(cacheYearLong)
		op.Entry.EntryExpiration = op.Entry.AttributesExpiration
	} else {
		return jfuse.ENOENT
	}
	return nil
}
func (fs *fsMutable) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	nodeStore, _ := fs.atomicGetReferences()
	key := formKey(op.Inode)
	e, found := nodeStore.Get(key)
	if !found {
		err := jfuse.ENOENT
		return err
	}
	n := e.(*nodeEntry)
	op.AttributesExpiration = time.Now().Add(cacheYearLong)
	n.lock.Lock()
	op.Attributes = n.attr
	n.lock.Unlock()
	return
}
func (fs *fsMutable) SetInodeAttributes(ctx context.Context, op *fuseops.SetInodeAttributesOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	if op.Mode != nil {  
		fs.l.Debug("setting permissions mode is not supported", zap.Uint32("mode", uint32(*op.Mode)))
		return jfuse.ENOSYS
	}
	nodeStore, _ := fs.atomicGetReferences()
	key := formKey(op.Inode)
	e, found := nodeStore.Get(key)
	if !found {
		return jfuse.ENOENT
	}
	n := e.(*nodeEntry)
	n.lock.Lock()
	defer n.lock.Unlock()
	if op.Size != nil {
		file, err := fs.localCache.OpenFile(fmt.Sprint(op.Inode), os.O_WRONLY|os.O_SYNC, fileDefaultMode)
		if err != nil {
			return jfuse.EIO
		}
		if *op.Size > math.MaxInt64 {
			fs.l.Error("Received size greater than MaxInt64", zap.Uint64("size", *op.Size), zap.Uint64("inode", uint64(op.Inode)))
			return jfuse.EINVAL
		}
		err = file.Truncate(int64(*op.Size))
		if err != nil {
			fs.l.Error("truncate error", zap.Error(err))
			return jfuse.EIO
		}
		n.attr.Size = *op.Size
	}
	if op.Atime != nil {
		n.attr.Atime = *op.Atime
	}
	if op.Mtime != nil {
		n.attr.Mtime = *op.Mtime
	}
	op.AttributesExpiration = time.Now().Add(cacheYearLong)
	op.Attributes = n.attr
	return nil
}
func (fs *fsMutable) ForgetInode(
	ctx context.Context,
	op *fuseops.ForgetInodeOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	nodeStore, _ := fs.atomicGetReferences()
	key := formKey(op.Inode)
	e, found := nodeStore.Get(key)
	if !found {
		fs.l.Error("ForgetInode inode not found", zap.Uint64("inode", uint64(op.Inode)))
		panic(fmt.Sprintf("not found iNode:%d", op.Inode))
	}
	var del bool
	n := e.(*nodeEntry)
	n.lock.Lock()
	n.refCount--
	if n.refCount < 0 {
		panic(fmt.Sprintf("RefCount below zero %d", op.Inode))
	}
	del = shouldDelete(n)
	n.lock.Unlock()
	if del {
		fs.lock.Lock()
		defer fs.lock.Unlock()
		e, found := fs.iNodeStore.Get(key)
		if !found {
			return
		}
		n := e.(*nodeEntry)
		if shouldDelete(n) {
			fs.iNodeStore, _, _ = fs.iNodeStore.Delete(key)
			fs.l.Debug("NodeStore", zap.Int("Size", fs.iNodeStore.Len()))
		}
		fs.iNodeGenerator.freeINode(op.Inode)
	}
	return nil
}
func (fs *fsMutable) MkDir(
	ctx context.Context,
	op *fuseops.MkDirOp) (err error) {
	fs.l.Info("mkdir", zap.Uint64("id", uint64(op.Parent)), zap.String("name", op.Name))
	fs.lock.Lock()
	defer fs.lock.Unlock()
	lk := formLookupKey(op.Parent, op.Name)
	err = fs.preCreateCheck(op.Parent, lk)
	if err != nil {
		fs.lock.Unlock()
		return
	}
	err = fs.createNode(lk, op.Parent, op.Name, &op.Entry, fuseutil.DT_Directory, false)
	return
}
func (fs *fsMutable) CreateFile(
	ctx context.Context,
	op *fuseops.CreateFileOp) (err error) {
	fs.l.Info("createFile", zap.Uint64("id", uint64(op.Parent)), zap.String("name", op.Name))
	fs.lock.Lock()
	defer fs.lock.Unlock()
	lk := formLookupKey(op.Parent, op.Name)
	err = fs.preCreateCheck(op.Parent, lk)
	if err != nil {
		return
	}
	err = fs.createNode(lk, op.Parent, op.Name, &op.Entry, fuseutil.DT_File, false)
	return
}
func (fs *fsMutable) Rename(ctx context.Context, op *fuseops.RenameOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	fs.lock.Lock()
	defer fs.lock.Unlock()
	oldChild, found, _ := fs.lookup(op.OldParent, op.OldName)
	if !found {
		return jfuse.ENOENT
	}
	newChild, found, _ := fs.lookup(op.NewParent, op.NewName)
	if found {
		if newChild.mode.IsDir() {
			return jfuse.ENOSYS
		}
		_ = fs.deleteNSEntry(op.NewParent, op.NewName)
	}
	rC := fs.readDirMap[op.OldParent][oldChild.iNode]
	newRC := fuseutil.Dirent{
		Inode: rC.Inode,
		Name:  op.NewName,
		Type:  rC.Type,
	}
	delete(fs.readDirMap[op.OldParent], rC.Inode)
	var l interface{}
	fs.lookupTree, l, _ = fs.lookupTree.Delete(formLookupKey(op.OldParent, op.OldName))  
	fs.insertReadDirEntry(op.NewParent, &newRC)
	fs.insertLookupEntry(op.NewParent, op.NewName, l.(lookupEntry))
	return nil
}
func (fs *fsMutable) RmDir(
	ctx context.Context,
	op *fuseops.RmDirOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	fs.lock.Lock()
	defer fs.lock.Unlock()
	return fs.deleteNSEntry(op.Parent, op.Name)
}
func (fs *fsMutable) Unlink(
	ctx context.Context,
	op *fuseops.UnlinkOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	fs.lock.Lock()
	defer fs.lock.Unlock()
	return fs.deleteNSEntry(op.Parent, op.Name)
}
func (fs *fsMutable) OpenDir(
	ctx context.Context,
	op *fuseops.OpenDirOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	return
}
func (fs *fsMutable) ReadDir(
	ctx context.Context,
	op *fuseops.ReadDirOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	offset := int(op.Offset)
	iNode := op.Inode
	fs.lock.Lock()
	defer fs.lock.Unlock()
	children, found := fs.readDirMap[iNode]
	if !found {
		return jfuse.ENOENT
	}
	if offset > len(children) {
		return
	}
	var i uint64 = 1
	for _, c := range children {
		i++
		if i < uint64(offset) {
			continue
		}
		child := *c
		child.Offset = fuseops.DirOffset(i)  
		n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], child)
		if n == 0 {
			break
		}
		op.BytesRead += n
	}
	return nil
}
func (fs *fsMutable) ReleaseDirHandle(
	ctx context.Context,
	op *fuseops.ReleaseDirHandleOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	return
}
func (fs *fsMutable) OpenFile(
	ctx context.Context,
	op *fuseops.OpenFileOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	return
}
func (fs *fsMutable) ReadFile(
	ctx context.Context,
	op *fuseops.ReadFileOp) (err error) {
	t0 := fs.opStart(op)
	defer func() {
		fs.opEnd(t0, op, err)
		if fs.MetricsEnabled() {
			fs.m.Volume.Files.Inc("read")
			fs.m.Volume.Files.Size(int64(op.BytesRead), "read")
			fs.m.Volume.IO.IORecord(t0, "read")(int64(op.BytesRead), err)
		}
	}()
	file, err := fs.localCache.OpenFile(getPathToBackingFile(op.Inode), os.O_RDONLY|os.O_SYNC, fileDefaultMode)
	if err != nil {
		return jfuse.EIO
	}
	fs.lockBackingFiles.Lock()
	defer fs.lockBackingFiles.Unlock()
	fs.backingFiles[op.Inode] = &file
	op.BytesRead, err = file.ReadAt(op.Dst, op.Offset)
	if err != nil {
		return jfuse.EIO
	}
	return
}
func (fs *fsMutable) WriteFile(
	ctx context.Context,
	op *fuseops.WriteFileOp) (err error) {
	var n int
	t0 := fs.opStart(op)
	defer func() {
		fs.opEnd(t0, op, err)
		if fs.MetricsEnabled() {
			fs.m.Volume.Files.Inc("write")
			fs.m.Volume.Files.Size(int64(n), "write")
			fs.m.Volume.IO.IORecord(t0, "write")(int64(n), err)
		}
	}()
	file, err := fs.localCache.OpenFile(getPathToBackingFile(op.Inode), os.O_WRONLY|os.O_SYNC, fileDefaultMode)
	if err != nil {
		return jfuse.EIO
	}
	fs.lockBackingFiles.Lock()
	defer fs.lockBackingFiles.Unlock()
	fs.backingFiles[op.Inode] = &file
	n, err = file.WriteAt(op.Data, op.Offset)
	if err != nil {
		return jfuse.EIO
	}
	ne, found := fs.iNodeStore.Get(formKey(op.Inode))
	if !found {
		panic("Invalid state inode: not found" + fmt.Sprint(uint64(op.Inode)))
	}
	nodeEntry := ne.(*nodeEntry)
	nodeEntry.lock.Lock()
	s, _ := file.Stat()
	nodeEntry.attr.Size = uint64(s.Size())
	nodeEntry.lock.Unlock()
	return err
}
func (fs *fsMutable) SyncFile(
	ctx context.Context,
	op *fuseops.SyncFileOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	fs.lockBackingFiles.RLock()
	defer fs.lockBackingFiles.RUnlock()
	file := *fs.backingFiles[op.Inode]
	if file != nil {
		err := file.Sync()
		if err != nil {
			return jfuse.EIO
		}
	}
	return
}
func (fs *fsMutable) FlushFile(
	ctx context.Context,
	op *fuseops.FlushFileOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	fs.lockBackingFiles.RLock()
	defer fs.lockBackingFiles.RUnlock()
	f := fs.backingFiles[op.Inode]
	if f != nil {
		file := *f
		err := file.Sync()
		if err != nil {
			return jfuse.EIO
		}
	}
	return
}
func (fs *fsMutable) ReleaseFileHandle(
	ctx context.Context,
	op *fuseops.ReleaseFileHandleOp) (err error) {
	t0 := fs.opStart(op)
	defer fs.opEnd(t0, op, err)
	return
}
func (dfs *MutableFS) Commit() error {
	return dfs.fsInternal.Commit()
}
func (fs *fsMutable) initRoot() (err error) {
	_, found := fs.lookupTree.Get(formKey(fuseops.RootInodeID))
	if found {
		return
	}
	err = fs.createNode(
		formLookupKey(fuseops.RootInodeID, rootPath),
		fuseops.RootInodeID,
		rootPath,
		nil,
		fuseutil.DT_Directory,
		true)
	return
}
func (fs *fsMutable) preCreateCheck(parentInode fuseops.InodeID, lk []byte) error {
	key := formKey(parentInode)
	e, found := fs.iNodeStore.Get(key)
	if !found {
		return jfuse.ENOENT
	}
	n := e.(*nodeEntry)
	if !n.attr.Mode.IsDir() {
		return jfuse.ENOTDIR
	}
	_, found = fs.lookupTree.Get(lk)
	if found {
		return jfuse.EEXIST
	}
	return nil
}
func (fs *fsMutable) insertReadDirEntry(id fuseops.InodeID, dirEnt *fuseutil.Dirent) {
	if fs.readDirMap[id] == nil {
		fs.readDirMap[id] = make(map[fuseops.InodeID]*fuseutil.Dirent)
	}
	fs.readDirMap[id][dirEnt.Inode] = dirEnt
}
func (fs *fsMutable) insertLookupEntry(id fuseops.InodeID, child string, entry lookupEntry) {
	fs.lookupTree, _, _ = fs.lookupTree.Insert(formLookupKey(id, child), entry)
}