Add per-inode write mutex
At the moment, FUSE writes to a single file are serialized by the kernel. However, it is unclear if this is guaranteed behaviour or may change in the future. This patch adds our own per-inode write lock to rule out races regardless of kernel behavoir.
This commit is contained in:
parent
dac9f71089
commit
2f32114bd3
|
@ -43,6 +43,7 @@ type file struct {
|
||||||
func NewFile(fd *os.File, writeOnly bool, cfs *cryptfs.CryptFS) nodefs.File {
|
func NewFile(fd *os.File, writeOnly bool, cfs *cryptfs.CryptFS) nodefs.File {
|
||||||
var st syscall.Stat_t
|
var st syscall.Stat_t
|
||||||
syscall.Fstat(int(fd.Fd()), &st)
|
syscall.Fstat(int(fd.Fd()), &st)
|
||||||
|
wlock.register(st.Ino)
|
||||||
|
|
||||||
return &file{
|
return &file{
|
||||||
fd: fd,
|
fd: fd,
|
||||||
|
@ -59,20 +60,6 @@ func (f *file) InnerFile() nodefs.File {
|
||||||
func (f *file) SetInode(n *nodefs.Inode) {
|
func (f *file) SetInode(n *nodefs.Inode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that all modifications to the file contents are serialized and no
|
|
||||||
// reads happen concurrently.
|
|
||||||
//
|
|
||||||
// This prevents several races:
|
|
||||||
// * getFileId vs Truncate
|
|
||||||
// * zeroPad vs Read
|
|
||||||
// * RMW vs Write
|
|
||||||
func (f *file) wlock() {
|
|
||||||
}
|
|
||||||
func (f *file) rlock() {
|
|
||||||
}
|
|
||||||
func (f *file) unlock() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// readHeader - load the file header from disk
|
// readHeader - load the file header from disk
|
||||||
//
|
//
|
||||||
// Returns io.EOF if the file is empty
|
// Returns io.EOF if the file is empty
|
||||||
|
@ -252,6 +239,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||||
cryptfs.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData))
|
cryptfs.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
blockOffset, blockLen := b.CiphertextRange()
|
blockOffset, blockLen := b.CiphertextRange()
|
||||||
blockData = f.cfs.EncryptBlock(blockData, b.BlockNo, f.header.Id)
|
blockData = f.cfs.EncryptBlock(blockData, b.BlockNo, f.header.Id)
|
||||||
cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d",
|
cryptfs.Debug.Printf("ino%d: Writing %d bytes to block #%d",
|
||||||
|
@ -271,6 +259,7 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||||
f.fdLock.Lock()
|
f.fdLock.Lock()
|
||||||
_, err = f.fd.WriteAt(blockData, int64(blockOffset))
|
_, err = f.fd.WriteAt(blockData, int64(blockOffset))
|
||||||
f.fdLock.Unlock()
|
f.fdLock.Unlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cryptfs.Warn.Printf("doWrite: Write failed: %s", err.Error())
|
cryptfs.Warn.Printf("doWrite: Write failed: %s", err.Error())
|
||||||
status = fuse.ToStatus(err)
|
status = fuse.ToStatus(err)
|
||||||
|
@ -284,6 +273,8 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
||||||
// Write - FUSE call
|
// Write - FUSE call
|
||||||
func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
|
func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
|
||||||
cryptfs.Debug.Printf("ino%d: FUSE Write: offset=%d length=%d", f.ino, off, len(data))
|
cryptfs.Debug.Printf("ino%d: FUSE Write: offset=%d length=%d", f.ino, off, len(data))
|
||||||
|
wlock.lock(f.ino)
|
||||||
|
defer wlock.unlock(f.ino)
|
||||||
|
|
||||||
fi, err := f.fd.Stat()
|
fi, err := f.fd.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -306,6 +297,7 @@ func (f *file) Release() {
|
||||||
f.fdLock.Lock()
|
f.fdLock.Lock()
|
||||||
f.fd.Close()
|
f.fd.Close()
|
||||||
f.fdLock.Unlock()
|
f.fdLock.Unlock()
|
||||||
|
wlock.unregister(f.ino)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush - FUSE call
|
// Flush - FUSE call
|
||||||
|
@ -333,7 +325,11 @@ func (f *file) Fsync(flags int) (code fuse.Status) {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate - FUSE call
|
||||||
func (f *file) Truncate(newSize uint64) fuse.Status {
|
func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||||
|
wlock.lock(f.ino)
|
||||||
|
defer wlock.unlock(f.ino)
|
||||||
|
|
||||||
// Common case first: Truncate to zero
|
// Common case first: Truncate to zero
|
||||||
if newSize == 0 {
|
if newSize == 0 {
|
||||||
f.fdLock.Lock()
|
f.fdLock.Lock()
|
||||||
|
@ -343,7 +339,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||||
cryptfs.Warn.Printf("Ftruncate(fd, 0) returned error: %v", err)
|
cryptfs.Warn.Printf("Ftruncate(fd, 0) returned error: %v", err)
|
||||||
return fuse.ToStatus(err)
|
return fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
// A truncate to zero kills the file header
|
// Truncate to zero kills the file header
|
||||||
f.header = nil
|
f.header = nil
|
||||||
return fuse.OK
|
return fuse.OK
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package pathfs_frontend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
wlock.m = make(map[uint64]*refCntMutex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wlock - serializes write accesses to each file (identified by inode number)
|
||||||
|
// Writing partial blocks means we have to do read-modify-write cycles. We
|
||||||
|
// really don't want concurrent writes there.
|
||||||
|
// Concurrent full-block writes could actually be allowed, but are not to
|
||||||
|
// keep the locking simple.
|
||||||
|
var wlock wlockMap
|
||||||
|
|
||||||
|
// wlockMap - usage:
|
||||||
|
// 1) register
|
||||||
|
// 2) lock ... unlock ...
|
||||||
|
// 3) unregister
|
||||||
|
type wlockMap struct {
|
||||||
|
mapMutex sync.RWMutex
|
||||||
|
m map[uint64]*refCntMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wlockMap) register(ino uint64) {
|
||||||
|
w.mapMutex.Lock()
|
||||||
|
r := w.m[ino]
|
||||||
|
if r == nil {
|
||||||
|
r = &refCntMutex{}
|
||||||
|
w.m[ino] = r
|
||||||
|
}
|
||||||
|
r.refCnt++ // this must happen inside the mapMutex lock
|
||||||
|
w.mapMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wlockMap) unregister(ino uint64) {
|
||||||
|
w.mapMutex.Lock()
|
||||||
|
r := w.m[ino]
|
||||||
|
r.refCnt--
|
||||||
|
if r.refCnt == 0 {
|
||||||
|
delete(w.m, ino)
|
||||||
|
}
|
||||||
|
w.mapMutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wlockMap) lock(ino uint64) {
|
||||||
|
w.mapMutex.RLock()
|
||||||
|
r := w.m[ino]
|
||||||
|
w.mapMutex.RUnlock()
|
||||||
|
r.Lock() // this can take a long time - execute outside the mapMutex lock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wlockMap) unlock(ino uint64) {
|
||||||
|
w.mapMutex.RLock()
|
||||||
|
r := w.m[ino]
|
||||||
|
w.mapMutex.RUnlock()
|
||||||
|
r.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// refCntMutex - mutex with reference count
|
||||||
|
type refCntMutex struct {
|
||||||
|
sync.Mutex
|
||||||
|
refCnt int
|
||||||
|
}
|
Loading…
Reference in New Issue