diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend/file.go | 94 | ||||
| -rw-r--r-- | internal/fusefrontend/file_allocate_truncate.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend/file_holes.go | 1 | ||||
| -rw-r--r-- | internal/nametransform/diriv.go | 5 | ||||
| -rw-r--r-- | internal/syscallcompat/helpers.go | 21 | 
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 +} | 
