diff options
Diffstat (limited to 'internal/fusefrontend/file.go')
-rw-r--r-- | internal/fusefrontend/file.go | 94 |
1 files changed, 61 insertions, 33 deletions
diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go index 1ae3386..ffc41c6 100644 --- a/internal/fusefrontend/file.go +++ b/internal/fusefrontend/file.go @@ -122,9 +122,9 @@ func (f *File) createHeader() (fileID []byte, err error) { if !f.fs.args.NoPrealloc { err = syscallcompat.EnospcPrealloc(int(f.fd.Fd()), 0, contentenc.HeaderLen) if err != nil { - // If the underlying filesystem is full, it is normal get ENOSPC here. - // Log at Info level instead of Warning. - tlog.Info.Printf("ino%d: createHeader: prealloc failed: %s\n", f.qIno.Ino, err.Error()) + if !syscallcompat.IsENOSPC(err) { + tlog.Warn.Printf("ino%d: createHeader: prealloc failed: %s\n", f.qIno.Ino, err.Error()) + } return nil, err } } @@ -144,33 +144,41 @@ func (f *File) createHeader() (fileID []byte, err error) { // returns the requested part of the plaintext. // // Called by Read() for normal reading, -// by Write() and Truncate() for Read-Modify-Write -func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Status) { - // Make sure we have the file ID. - f.fileTableEntry.HeaderLock.RLock() - if f.fileTableEntry.ID == nil { - f.fileTableEntry.HeaderLock.RUnlock() - // Yes, somebody else may take the lock before we can. This will get - // the header read twice, but causes no harm otherwise. - f.fileTableEntry.HeaderLock.Lock() - tmpID, err := f.readFileID() - if err == io.EOF { - f.fileTableEntry.HeaderLock.Unlock() - return nil, fuse.OK - } - if err != nil { +// by Write() and Truncate() via doWrite() for Read-Modify-Write. +// +// doWrite() uses nolock=true because it makes sure the ID is in the cache and +// HeaderLock is locked before calling doRead. +func (f *File) doRead(dst []byte, off uint64, length uint64, nolock bool) ([]byte, fuse.Status) { + if !nolock { + // Make sure we have the file ID. + f.fileTableEntry.HeaderLock.RLock() + if f.fileTableEntry.ID == nil { + f.fileTableEntry.HeaderLock.RUnlock() + // Yes, somebody else may take the lock before we can. This will get + // the header read twice, but causes no harm otherwise. + f.fileTableEntry.HeaderLock.Lock() + tmpID, err := f.readFileID() + if err == io.EOF { + f.fileTableEntry.HeaderLock.Unlock() + return nil, fuse.OK + } + if err != nil { + f.fileTableEntry.HeaderLock.Unlock() + tlog.Warn.Printf("doRead %d: corrupt header: %v", f.qIno.Ino, err) + return nil, fuse.EIO + } + f.fileTableEntry.ID = tmpID + // Downgrade the lock. f.fileTableEntry.HeaderLock.Unlock() - tlog.Warn.Printf("doRead %d: corrupt header: %v", f.qIno.Ino, err) - return nil, fuse.EIO + // The file ID may change in here. This does no harm because we + // re-read it after the RLock(). + f.fileTableEntry.HeaderLock.RLock() } - f.fileTableEntry.ID = tmpID - // Downgrade the lock. - f.fileTableEntry.HeaderLock.Unlock() - // The file ID may change in here. This does no harm because we - // re-read it after the RLock(). - f.fileTableEntry.HeaderLock.RLock() } fileID := f.fileTableEntry.ID + if fileID == nil { + log.Panicf("filedID=%v, nolock=%v", fileID, nolock) + } // Read the backing ciphertext in one go blocks := f.contentEnc.ExplodePlainRange(off, length) alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks) @@ -182,7 +190,9 @@ func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, fuse.Statu ciphertext = ciphertext[:int(alignedLength)] n, err := f.fd.ReadAt(ciphertext, int64(alignedOffset)) // We don't care if the file ID changes after we have read the data. Drop the lock. - f.fileTableEntry.HeaderLock.RUnlock() + if !nolock { + f.fileTableEntry.HeaderLock.RUnlock() + } if err != nil && err != io.EOF { tlog.Warn.Printf("read: ReadAt: %s", err.Error()) return nil, fuse.ToStatus(err) @@ -245,7 +255,7 @@ func (f *File) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus if f.fs.args.SerializeReads { serialize_reads.Wait(off, len(buf)) } - out, status := f.doRead(buf[:0], uint64(off), uint64(len(buf))) + out, status := f.doRead(buf[:0], uint64(off), uint64(len(buf)), false) if f.fs.args.SerializeReads { serialize_reads.Done() } @@ -266,6 +276,7 @@ func (f *File) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus // // Empty writes do nothing and are allowed. func (f *File) doWrite(data []byte, off int64) (uint32, fuse.Status) { + fileWasEmpty := false // If the file ID is not cached, read it from disk if f.fileTableEntry.ID == nil { // Block other readers while we mess with the file header. Other writers @@ -275,13 +286,22 @@ func (f *File) doWrite(data []byte, off int64) (uint32, fuse.Status) { // Write a new file header if the file is empty if err == io.EOF { tmpID, err = f.createHeader() + fileWasEmpty = true } if err != nil { f.fileTableEntry.HeaderLock.Unlock() return 0, fuse.ToStatus(err) } f.fileTableEntry.ID = tmpID - f.fileTableEntry.HeaderLock.Unlock() + if fileWasEmpty { + // The file was empty and we wrote a new file header. Keep the lock + // as we might have to kill the file header again if the data write + // fails. + defer f.fileTableEntry.HeaderLock.Unlock() + } else { + // We won't touch the header again, drop the lock immediately. + f.fileTableEntry.HeaderLock.Unlock() + } } // Handle payload data dataBuf := bytes.NewBuffer(data) @@ -292,7 +312,7 @@ func (f *File) doWrite(data []byte, off int64) (uint32, fuse.Status) { // Incomplete block -> Read-Modify-Write if b.IsPartial() { // Read - oldData, status := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS()) + oldData, status := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS(), true) if status != fuse.OK { tlog.Warn.Printf("ino%d fh%d: RMW read failed: %s", f.qIno.Ino, f.intFd(), status.String()) return 0, status @@ -315,9 +335,17 @@ func (f *File) doWrite(data []byte, off int64) (uint32, fuse.Status) { if !f.fs.args.NoPrealloc { err = syscallcompat.EnospcPrealloc(int(f.fd.Fd()), cOff, int64(len(ciphertext))) if err != nil { - // If the underlying filesystem is full, it is normal get ENOSPC here. - // Log at Info level instead of Warning. - tlog.Info.Printf("ino%d fh%d: doWrite: prealloc failed: %s", f.qIno.Ino, f.intFd(), err.Error()) + if !syscallcompat.IsENOSPC(err) { + tlog.Warn.Printf("ino%d fh%d: doWrite: prealloc failed: %v", f.qIno.Ino, f.intFd(), err) + } + if fileWasEmpty { + // Kill the file header again + f.fileTableEntry.ID = nil + err2 := syscall.Ftruncate(int(f.fd.Fd()), 0) + if err2 != nil { + tlog.Warn.Printf("ino%d fh%d: doWrite: rollback failed: %v", f.qIno.Ino, f.intFd(), err2) + } + } return 0, fuse.ToStatus(err) } } |