Commit 75dc7589 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

Move open file administration into mountData.

* simplifies code

* deal with GetPath() on deleted files
parent 8379ce53
...@@ -37,6 +37,8 @@ type testCase struct { ...@@ -37,6 +37,8 @@ type testCase struct {
connector *FileSystemConnector connector *FileSystemConnector
} }
const testTtl = 0.1
// Create and mount filesystem. // Create and mount filesystem.
func (me *testCase) Setup(t *testing.T) { func (me *testCase) Setup(t *testing.T) {
me.tester = t me.tester = t
...@@ -61,7 +63,12 @@ func (me *testCase) Setup(t *testing.T) { ...@@ -61,7 +63,12 @@ func (me *testCase) Setup(t *testing.T) {
pfs = NewLockingFileSystem(pfs) pfs = NewLockingFileSystem(pfs)
var rfs RawFileSystem var rfs RawFileSystem
me.connector = NewFileSystemConnector(pfs, nil) me.connector = NewFileSystemConnector(pfs,
&FileSystemOptions{
EntryTimeout: testTtl,
AttrTimeout: testTtl,
NegativeTimeout: 0.0,
})
rfs = me.connector rfs = me.connector
rfs = NewTimingRawFileSystem(rfs) rfs = NewTimingRawFileSystem(rfs)
rfs = NewLockingRawFileSystem(rfs) rfs = NewLockingRawFileSystem(rfs)
...@@ -676,7 +683,7 @@ func TestRecursiveMount(t *testing.T) { ...@@ -676,7 +683,7 @@ func TestRecursiveMount(t *testing.T) {
f.Close() f.Close()
log.Println("Waiting for kernel to flush file-close to fuse...") log.Println("Waiting for kernel to flush file-close to fuse...")
time.Sleep(1e9) time.Sleep(1.5e9 * testTtl)
code = ts.connector.Unmount("/mnt") code = ts.connector.Unmount("/mnt")
if code != OK { if code != OK {
...@@ -684,3 +691,40 @@ func TestRecursiveMount(t *testing.T) { ...@@ -684,3 +691,40 @@ func TestRecursiveMount(t *testing.T) {
} }
} }
func TestDeletedUnmount(t *testing.T) {
ts := new(testCase)
ts.Setup(t)
defer ts.Cleanup()
submnt := filepath.Join(ts.mountPoint, "mnt")
err := os.Mkdir(submnt, 0777)
CheckSuccess(err)
pfs2 := NewLoopbackFileSystem(ts.origDir)
code := ts.connector.Mount("/mnt", pfs2, nil)
if !code.Ok() {
t.Fatal("err")
}
f, err := os.Create(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
log.Println("Removing")
err = os.Remove(filepath.Join(submnt, "hello.txt"))
CheckSuccess(err)
log.Println("Removing")
_, err = f.Write([]byte("bla"))
CheckSuccess(err)
code = ts.connector.Unmount("/mnt")
if code != EBUSY {
t.Error("expect EBUSY", code)
}
f.Close()
time.Sleep(1.5e9 * testTtl)
code = ts.connector.Unmount("/mnt")
if !code.Ok() {
t.Error("should succeed", code)
}
}
...@@ -28,6 +28,14 @@ import ( ...@@ -28,6 +28,14 @@ import (
"unsafe" "unsafe"
) )
// fileBridge stores either an open dir or an open file.
type fileBridge struct {
*mountData
*inode
Flags uint32
Iface interface{}
}
type mountData struct { type mountData struct {
// If non-nil the file system mounted here. // If non-nil the file system mounted here.
fs FileSystem fs FileSystem
...@@ -46,12 +54,20 @@ type mountData struct { ...@@ -46,12 +54,20 @@ type mountData struct {
// We could have separate treeLocks per mount; something to // We could have separate treeLocks per mount; something to
// consider if we can measure significant contention for // consider if we can measure significant contention for
// multi-mount filesystems. // multi-mount filesystems.
options *FileSystemOptions options *FileSystemOptions
// Protects openFiles
openFilesLock sync.RWMutex
// Open files/directories.
openFiles map[uint64]*fileBridge
} }
func newMount(fs FileSystem) *mountData { func newMount(fs FileSystem) *mountData {
return &mountData{fs: fs} return &mountData{
fs: fs,
openFiles: make(map[uint64]*fileBridge),
}
} }
func (me *mountData) setOwner(attr *Attr) { func (me *mountData) setOwner(attr *Attr) {
...@@ -59,7 +75,40 @@ func (me *mountData) setOwner(attr *Attr) { ...@@ -59,7 +75,40 @@ func (me *mountData) setOwner(attr *Attr) {
attr.Owner = *me.options.Owner attr.Owner = *me.options.Owner
} }
} }
func (me *mountData) unregisterFile(node *inode, handle uint64) interface{} {
me.openFilesLock.Lock()
defer me.openFilesLock.Unlock()
b, ok := me.openFiles[handle]
if !ok {
panic("invalid handle")
}
node.OpenCount--
me.openFiles[handle] = nil, false
return b.Iface
}
func (me *mountData) registerFile(node *inode, f interface{}, flags uint32) uint64 {
me.openFilesLock.Lock()
defer me.openFilesLock.Unlock()
b := &fileBridge{
Iface: f,
inode: node,
mountData: me,
Flags: flags,
}
h := uint64(uintptr(unsafe.Pointer(b)))
_, ok := me.openFiles[h]
if ok {
panic("handle counter wrapped")
}
node.OpenCount++
me.openFiles[h] = b
return h
}
////////////////
// Tests should set to true. // Tests should set to true.
var paranoia = false var paranoia = false
...@@ -71,18 +120,29 @@ type inode struct { ...@@ -71,18 +120,29 @@ type inode struct {
NodeId uint64 NodeId uint64
Name string Name string
LookupCount int LookupCount int
// Protected by openFilesLock.
// TODO - verify() this variable too.
OpenCount int OpenCount int
// Non-nil if this is a mountpoint. // Non-nil if this is a mountpoint.
mountPoint *mountData mountPoint *mountData
// The point under which this node is. // The point under which this node is. Should be non-nil for
// all nodes.
mount *mountData mount *mountData
} }
// Should be called with treeLock and fileLock held. // Should be called with treeLock and fileLock held.
func (me *inode) totalOpenCount() int { func (me *inode) totalOpenCount() int {
o := me.OpenCount o := 0
if me.mountPoint != nil {
me.mountPoint.openFilesLock.RLock()
defer me.mountPoint.openFilesLock.RUnlock()
o += len(me.mountPoint.openFiles)
}
for _, v := range me.Children { for _, v := range me.Children {
o += v.totalOpenCount() o += v.totalOpenCount()
} }
...@@ -108,17 +168,19 @@ func (me *inode) IsDir() bool { ...@@ -108,17 +168,19 @@ func (me *inode) IsDir() bool {
const initDirSize = 20 const initDirSize = 20
func (me *inode) verify(cur *mountData) { func (me *inode) verify(cur *mountData) {
if !(me.NodeId == FUSE_ROOT_ID || me.LookupCount > 0 || len(me.Children) > 0) { if !(me.NodeId == FUSE_ROOT_ID || me.LookupCount > 0 || len(me.Children) > 0 || me.mountPoint != nil) {
p, _ := me.GetPath() p, _ := me.GetPath()
panic(fmt.Sprintf("node %v should be dead: %v %v", p, len(me.Children), me.LookupCount)) panic(fmt.Sprintf("node %v %d should be dead: %v %v", p, me.NodeId, len(me.Children), me.LookupCount))
} }
if me.mountPoint == nil { if me.mountPoint != nil {
if me.mount != cur { if me.mountPoint.unmountPending && len(me.mountPoint.openFiles) > 0 {
// panic("me.mount not set correctly", me.mount, cur) panic(fmt.Sprintf("cannot have open files for pending unmount"))
} }
} else {
cur = me.mountPoint cur = me.mountPoint
} }
if me.mount != cur {
panic(fmt.Sprintf("me.mount not set correctly %v %v", me.mount, cur))
}
for n, ch := range me.Children { for n, ch := range me.Children {
if ch == nil { if ch == nil {
...@@ -136,7 +198,29 @@ func (me *inode) verify(cur *mountData) { ...@@ -136,7 +198,29 @@ func (me *inode) verify(cur *mountData) {
} }
} }
func (me *inode) GetFullPath() (path string) {
rev_components := make([]string, 0, 10)
inode := me
for ; inode != nil; inode = inode.Parent {
rev_components = append(rev_components, inode.Name)
}
return ReverseJoin(rev_components, "/")
}
func ReverseJoin(rev_components []string, sep string) string {
components := make([]string, len(rev_components))
for i, v := range rev_components {
components[len(rev_components)-i-1] = v
}
return strings.Join(components, sep)
}
func (me *inode) GetPath() (path string, mount *mountData) { func (me *inode) GetPath() (path string, mount *mountData) {
if me.NodeId != FUSE_ROOT_ID && me.Parent == nil {
// Deleted node. Treat as if the filesystem was unmounted.
return ".deleted", nil
}
rev_components := make([]string, 0, 10) rev_components := make([]string, 0, 10)
inode := me inode := me
...@@ -151,27 +235,32 @@ func (me *inode) GetPath() (path string, mount *mountData) { ...@@ -151,27 +235,32 @@ func (me *inode) GetPath() (path string, mount *mountData) {
if mount.unmountPending { if mount.unmountPending {
return "", nil return "", nil
} }
components := make([]string, len(rev_components)) return ReverseJoin(rev_components, "/"), mount
for i, v := range rev_components {
components[len(rev_components)-i-1] = v
}
fullPath := strings.Join(components, "/")
return fullPath, mount
} }
// Must be called with treeLock held. // Must be called with treeLock held.
func (me *inode) setParent(newParent *inode) { func (me *inode) setParent(newParent *inode) {
if me.Parent == newParent { oldParent := me.Parent
if oldParent == newParent {
return return
} }
if me.Parent != nil { if oldParent != nil {
if paranoia { if paranoia {
ch := me.Parent.Children[me.Name] ch := oldParent.Children[me.Name]
if ch == nil { if ch == nil {
panic(fmt.Sprintf("parent has no child named %v", me.Name)) panic(fmt.Sprintf("parent has no child named %v", me.Name))
} }
} }
me.Parent.Children[me.Name] = nil, false oldParent.Children[me.Name] = nil, false
if oldParent.mountPoint != nil && oldParent.mountPoint.unmountPending &&
len(oldParent.Children) == 0 {
oldParent.mountPoint = nil
if oldParent.Parent != nil {
oldParent.mount = oldParent.Parent.mount
}
}
me.Parent = nil me.Parent = nil
} }
if newParent != nil { if newParent != nil {
...@@ -218,65 +307,16 @@ type FileSystemConnector struct { ...@@ -218,65 +307,16 @@ type FileSystemConnector struct {
// Invariants: see the verify() method. // Invariants: see the verify() method.
inodeMap map[uint64]*inode inodeMap map[uint64]*inode
rootNode *inode rootNode *inode
// Open files/directories.
openFiles map[uint64]*fileBridge
// Protects openFiles and OpenCount in all of the nodes.
fileLock sync.RWMutex
}
type fileBridge struct {
*mountData
*inode
Flags uint32
Iface interface{}
} }
func (me *FileSystemConnector) Statistics() string { func (me *FileSystemConnector) Statistics() string {
me.treeLock.RLock() me.treeLock.RLock()
defer me.treeLock.RUnlock() defer me.treeLock.RUnlock()
me.fileLock.RLock()
defer me.fileLock.RUnlock()
root := me.rootNode root := me.rootNode
return fmt.Sprintf("Mounts %20d\nFiles %20d\nInodes %20d\n", return fmt.Sprintf("Mounts %20d\nFiles %20d\nInodes %20d\n",
root.totalMountCount(), root.totalMountCount(),
len(me.openFiles), len(me.inodeMap)) root.totalOpenCount(), len(me.inodeMap))
}
func (me *FileSystemConnector) unregisterFile(node *inode, handle uint64) interface{} {
me.fileLock.Lock()
defer me.fileLock.Unlock()
b, ok := me.openFiles[handle]
if !ok {
panic("invalid handle")
}
me.openFiles[handle] = nil, false
node.OpenCount--
return b.Iface
}
func (me *FileSystemConnector) registerFile(node *inode, mount *mountData, f interface{}, flags uint32) uint64 {
me.fileLock.Lock()
defer me.fileLock.Unlock()
b := &fileBridge{
Iface: f,
inode: node,
mountData: mount,
Flags: flags,
}
h := uint64(uintptr(unsafe.Pointer(b)))
_, ok := me.openFiles[h]
if ok {
panic("handle counter wrapped")
}
node.OpenCount++
me.openFiles[h] = b
return h
} }
func (me *FileSystemConnector) decodeFileHandle(h uint64) *fileBridge { func (me *FileSystemConnector) decodeFileHandle(h uint64) *fileBridge {
...@@ -305,27 +345,15 @@ func (me *FileSystemConnector) verify() { ...@@ -305,27 +345,15 @@ func (me *FileSystemConnector) verify() {
} }
me.treeLock.Lock() me.treeLock.Lock()
defer me.treeLock.Unlock() defer me.treeLock.Unlock()
me.fileLock.Lock()
defer me.fileLock.Unlock()
hiddenOpen := 0
for k, v := range me.inodeMap { for k, v := range me.inodeMap {
if v.NodeId != k { if v.NodeId != k {
panic(fmt.Sprintf("nodeid mismatch %v %v", v, k)) panic(fmt.Sprintf("nodeid mismatch %v %v", v, k))
} }
if v.Parent == nil && v != me.rootNode {
hiddenOpen += v.OpenCount
}
} }
root := me.rootNode root := me.rootNode
root.verify(me.rootNode.mountPoint) root.verify(me.rootNode.mountPoint)
open := root.totalOpenCount()
openFiles := len(me.openFiles)
if open+hiddenOpen != openFiles {
panic(fmt.Sprintf("opencount mismatch totalOpen=%v openFiles=%v hiddenOpen=%v", open, openFiles, hiddenOpen))
}
} }
func (me *FileSystemConnector) newInode(root bool, isDir bool) *inode { func (me *FileSystemConnector) newInode(root bool, isDir bool) *inode {
...@@ -355,6 +383,7 @@ func (me *FileSystemConnector) lookupUpdate(parent *inode, name string, isDir bo ...@@ -355,6 +383,7 @@ func (me *FileSystemConnector) lookupUpdate(parent *inode, name string, isDir bo
data = me.newInode(false, isDir) data = me.newInode(false, isDir)
data.Name = name data.Name = name
data.setParent(parent) data.setParent(parent)
data.mount = parent.mount
} }
data.LookupCount += lookupCount data.LookupCount += lookupCount
return data return data
...@@ -385,7 +414,7 @@ func (me *FileSystemConnector) considerDropInode(n *inode) { ...@@ -385,7 +414,7 @@ func (me *FileSystemConnector) considerDropInode(n *inode) {
n.OpenCount <= 0 { n.OpenCount <= 0 {
n.setParent(nil) n.setParent(nil)
me.inodeMap[n.NodeId] = nil, false me.inodeMap[n.NodeId] = nil, false
} }
} }
func (me *FileSystemConnector) renameUpdate(oldParent *inode, oldName string, newParent *inode, newName string) { func (me *FileSystemConnector) renameUpdate(oldParent *inode, oldName string, newParent *inode, newName string) {
...@@ -444,7 +473,6 @@ func (me *FileSystemConnector) findInode(fullPath string) *inode { ...@@ -444,7 +473,6 @@ func (me *FileSystemConnector) findInode(fullPath string) *inode {
func EmptyFileSystemConnector() (out *FileSystemConnector) { func EmptyFileSystemConnector() (out *FileSystemConnector) {
out = new(FileSystemConnector) out = new(FileSystemConnector)
out.inodeMap = make(map[uint64]*inode) out.inodeMap = make(map[uint64]*inode)
out.openFiles = make(map[uint64]*fileBridge)
rootData := out.newInode(true, true) rootData := out.newInode(true, true)
rootData.Children = make(map[string]*inode, initDirSize) rootData.Children = make(map[string]*inode, initDirSize)
...@@ -495,6 +523,7 @@ func (me *FileSystemConnector) Mount(mountPoint string, fs FileSystem, opts *Fil ...@@ -495,6 +523,7 @@ func (me *FileSystemConnector) Mount(mountPoint string, fs FileSystem, opts *Fil
} }
node.mountPoint = newMount(fs) node.mountPoint = newMount(fs)
node.mount = node.mountPoint
if opts == nil { if opts == nil {
opts = NewFileSystemOptions() opts = NewFileSystemOptions()
} }
...@@ -510,7 +539,6 @@ func (me *FileSystemConnector) Unmount(path string) Status { ...@@ -510,7 +539,6 @@ func (me *FileSystemConnector) Unmount(path string) Status {
// Need to lock to look at node.Children // Need to lock to look at node.Children
me.treeLock.RLock() me.treeLock.RLock()
me.fileLock.Lock()
unmountError := OK unmountError := OK
...@@ -529,7 +557,6 @@ func (me *FileSystemConnector) Unmount(path string) Status { ...@@ -529,7 +557,6 @@ func (me *FileSystemConnector) Unmount(path string) Status {
// We settle for eventual consistency. // We settle for eventual consistency.
mount.unmountPending = true mount.unmountPending = true
} }
me.fileLock.Unlock()
me.treeLock.RUnlock() me.treeLock.RUnlock()
if unmountError.Ok() { if unmountError.Ok() {
...@@ -551,7 +578,7 @@ func (me *FileSystemConnector) GetPath(nodeid uint64) (path string, mount *mount ...@@ -551,7 +578,7 @@ func (me *FileSystemConnector) GetPath(nodeid uint64) (path string, mount *mount
p, m := n.GetPath() p, m := n.GetPath()
if me.Debug { if me.Debug {
log.Printf("Node %v = '%s'", nodeid, p) log.Printf("Node %v = '%s'", nodeid, n.GetFullPath())
} }
return p, m, n return p, m, n
......
...@@ -130,7 +130,7 @@ func (me *FileSystemConnector) OpenDir(header *InHeader, input *OpenIn) (flags u ...@@ -130,7 +130,7 @@ func (me *FileSystemConnector) OpenDir(header *InHeader, input *OpenIn) (flags u
de := &Dir{ de := &Dir{
stream: stream, stream: stream,
} }
h := me.registerFile(node, mount, de, input.Flags) h := mount.registerFile(node, de, input.Flags)
return 0, h, OK return 0, h, OK
} }
...@@ -155,7 +155,7 @@ func (me *FileSystemConnector) Open(header *InHeader, input *OpenIn) (flags uint ...@@ -155,7 +155,7 @@ func (me *FileSystemConnector) Open(header *InHeader, input *OpenIn) (flags uint
if err != OK { if err != OK {
return 0, 0, err return 0, 0, err
} }
h := me.registerFile(node, mount, f, input.Flags) h := mount.registerFile(node, f, input.Flags)
return 0, h, OK return 0, h, OK
} }
...@@ -373,12 +373,12 @@ func (me *FileSystemConnector) Create(header *InHeader, input *CreateIn, name st ...@@ -373,12 +373,12 @@ func (me *FileSystemConnector) Create(header *InHeader, input *CreateIn, name st
} }
out, code, inode := me.internalLookupWithNode(parent, name, 1) out, code, inode := me.internalLookupWithNode(parent, name, 1)
return 0, me.registerFile(inode, mount, f, input.Flags), out, code return 0, mount.registerFile(inode, f, input.Flags), out, code
} }
func (me *FileSystemConnector) Release(header *InHeader, input *ReleaseIn) { func (me *FileSystemConnector) Release(header *InHeader, input *ReleaseIn) {
node := me.getInodeData(header.NodeId) node := me.getInodeData(header.NodeId)
f := me.unregisterFile(node, input.Fh).(File) f := node.mount.unregisterFile(node, input.Fh).(File)
f.Release() f.Release()
} }
...@@ -406,7 +406,7 @@ func (me *FileSystemConnector) Flush(input *FlushIn) Status { ...@@ -406,7 +406,7 @@ func (me *FileSystemConnector) Flush(input *FlushIn) Status {
func (me *FileSystemConnector) ReleaseDir(header *InHeader, input *ReleaseIn) { func (me *FileSystemConnector) ReleaseDir(header *InHeader, input *ReleaseIn) {
node := me.getInodeData(header.NodeId) node := me.getInodeData(header.NodeId)
d := me.unregisterFile(node, input.Fh).(rawDir) d := node.mount.unregisterFile(node, input.Fh).(rawDir)
d.Release() d.Release()
me.considerDropInode(node) me.considerDropInode(node)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment