fusefrontend: doWrite: delete file header if first write fails
xfstests generic/083 fills the filesystem almost completely while running fsstress in parallel. In fsck, these would show up: readFileID 2580: incomplete file, got 18 instead of 19 bytes This could happen when writing the file header works, but writing the actual data fails. Now we kill the header again by truncating the file to zero.
This commit is contained in:
parent
55bb22bad6
commit
c70df522d2
@ -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 {
|
||||
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()
|
||||
// The file ID may change in here. This does no harm because we
|
||||
// re-read it after the RLock().
|
||||
// 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()
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ func (f *File) Truncate(newSize uint64) fuse.Status {
|
||||
var data []byte
|
||||
if lastBlockLen > 0 {
|
||||
var status fuse.Status
|
||||
data, status = f.doRead(nil, plainOff, lastBlockLen)
|
||||
data, status = f.doRead(nil, plainOff, lastBlockLen, false)
|
||||
if status != fuse.OK {
|
||||
tlog.Warn.Printf("Truncate: shrink doRead returned error: %v", err)
|
||||
return status
|
||||
|
@ -36,7 +36,6 @@ func (f *File) writePadHole(targetOff int64) fuse.Status {
|
||||
// will contain a file hole in the ciphertext.
|
||||
status := f.zeroPad(plainSize)
|
||||
if status != fuse.OK {
|
||||
tlog.Warn.Printf("zeroPad returned error %v", status)
|
||||
return status
|
||||
}
|
||||
return fuse.OK
|
||||
|
@ -100,7 +100,10 @@ func WriteDirIV(dirfd *os.File, dir string) error {
|
||||
_, err = fd.Write(iv)
|
||||
if err != nil {
|
||||
fd.Close()
|
||||
tlog.Warn.Printf("WriteDirIV: Write: %v", err)
|
||||
// It is normal to get ENOSPC here
|
||||
if !syscallcompat.IsENOSPC(err) {
|
||||
tlog.Warn.Printf("WriteDirIV: Write: %v", err)
|
||||
}
|
||||
// Delete incomplete gocryptfs.diriv file
|
||||
syscallcompat.Unlinkat(int(dirfd.Fd()), file, 0)
|
||||
return err
|
||||
|
21
internal/syscallcompat/helpers.go
Normal file
21
internal/syscallcompat/helpers.go
Normal file
@ -0,0 +1,21 @@
|
||||
package syscallcompat
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsENOSPC tries to find out if "err" is a (potentially wrapped) ENOSPC error.
|
||||
func IsENOSPC(err error) bool {
|
||||
// syscallcompat.EnospcPrealloc returns the naked syscall error
|
||||
if err == syscall.ENOSPC {
|
||||
return true
|
||||
}
|
||||
// os.File.WriteAt returns &PathError
|
||||
if err2, ok := err.(*os.PathError); ok {
|
||||
if err2.Err == syscall.ENOSPC {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Reference in New Issue
Block a user