aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend/file.go94
-rw-r--r--internal/fusefrontend/file_allocate_truncate.go2
-rw-r--r--internal/fusefrontend/file_holes.go1
-rw-r--r--internal/nametransform/diriv.go5
-rw-r--r--internal/syscallcompat/helpers.go21
5 files changed, 87 insertions, 36 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)
}
}
diff --git a/internal/fusefrontend/file_allocate_truncate.go b/internal/fusefrontend/file_allocate_truncate.go
index 74c51a7..81ac8d3 100644
--- a/internal/fusefrontend/file_allocate_truncate.go
+++ b/internal/fusefrontend/file_allocate_truncate.go
@@ -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
diff --git a/internal/fusefrontend/file_holes.go b/internal/fusefrontend/file_holes.go
index 04a00ec..3725f56 100644
--- a/internal/fusefrontend/file_holes.go
+++ b/internal/fusefrontend/file_holes.go
@@ -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
diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go
index d67a5fa..1a72d2a 100644
--- a/internal/nametransform/diriv.go
+++ b/internal/nametransform/diriv.go
@@ -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
diff --git a/internal/syscallcompat/helpers.go b/internal/syscallcompat/helpers.go
new file mode 100644
index 0000000..e2a2215
--- /dev/null
+++ b/internal/syscallcompat/helpers.go
@@ -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
+}