Utilize file hole passtrough capability in Truncate()

Cuts down the runtime of xfstests generic/014
from 1822 seconds to 36 seconds
This commit is contained in:
Jakob Unterwurzacher 2015-10-04 14:21:07 +02:00
parent 2003ca965d
commit 775676ecb8
2 changed files with 61 additions and 89 deletions

View File

@ -181,7 +181,20 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
// Write - FUSE call
func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
cryptfs.Debug.Printf("ino%d: FUSE Write %s: offset=%d length=%d\n", f.ino, off, len(data))
f.conditionalZeroPad(off)
fi, err := f.fd.Stat()
if err != nil {
cryptfs.Warn.Printf("Write: Fstat failed: %v\n", err)
return 0, fuse.ToStatus(err)
}
plainSize := f.cfs.PlainSize(uint64(fi.Size()))
if f.createsHole(plainSize, off) {
status := f.zeroPad(plainSize)
if status != fuse.OK {
cryptfs.Warn.Printf("zeroPad returned error %v\n", status)
return 0, status
}
}
return f.doWrite(data, off)
}
@ -219,19 +232,11 @@ func (f *file) Fsync(flags int) (code fuse.Status) {
func (f *file) Truncate(newSize uint64) fuse.Status {
// Common case: Truncate to zero
if newSize == 0 {
f.lock.Lock()
err := syscall.Ftruncate(int(f.fd.Fd()), 0)
f.lock.Unlock()
return fuse.ToStatus(err)
}
// We need the old file size to determine if we are growing or shrinking
// the file
fi, err := f.fd.Stat()
if err != nil {
cryptfs.Warn.Printf("Truncate: fstat failed: %v\n", err)
cryptfs.Warn.Printf("Truncate: Fstat failed: %v\n", err)
return fuse.ToStatus(err)
}
oldSize := f.cfs.PlainSize(uint64(fi.Size()))
@ -240,77 +245,58 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
newB := (newSize + f.cfs.PlainBS() - 1) / f.cfs.PlainBS()
cryptfs.Debug.Printf("ino%d: truncate from %d to %d blocks (%d to %d bytes)\n", f.ino, oldB, newB, oldSize, newSize)
}
// Grow file by appending zeros
// File grows
if newSize > oldSize {
remaining := newSize - oldSize
offset := oldSize
var zeros []byte
// Append a maximum of 1MB in each iteration
if remaining > 1048576 {
zeros = make([]byte, 1048576)
} else {
zeros = make([]byte, remaining)
blocks := f.cfs.SplitRange(oldSize, newSize - oldSize)
for _, b := range(blocks) {
// First and last block may be partial
if b.IsPartial() {
off, _ := b.PlaintextRange()
off += b.Offset
_, status := f.doWrite(make([]byte, b.Length), int64(off))
if status != fuse.OK {
return status
}
} else {
off, length := b.CiphertextRange()
f.lock.Lock()
err := syscall.Ftruncate(int(f.fd.Fd()), int64(off + length))
f.lock.Unlock()
if err != nil {
cryptfs.Warn.Printf("grow Ftruncate returned error: %v", err)
return fuse.ToStatus(err)
}
}
}
for remaining >= uint64(len(zeros)) {
written, status := f.Write(zeros, int64(offset))
return fuse.OK
// File shrinks
} else {
blockNo := f.cfs.BlockNoPlainOff(newSize)
lastBlockOff := blockNo * f.cfs.PlainBS()
lastBlockLen := newSize - lastBlockOff
var data []byte
if lastBlockLen > 0 {
var status fuse.Status
data, status = f.doRead(lastBlockOff, lastBlockLen)
if status != fuse.OK {
cryptfs.Warn.Printf("shrink doRead returned error: %v", err)
return status
}
remaining -= uint64(written)
offset += uint64(written)
cryptfs.Debug.Printf("Truncate: written=%d remaining=%d offset=%d\n",
written, remaining, offset)
}
if remaining > 0 {
_, status := f.Write(zeros[0:remaining], int64(offset))
f.lock.Lock()
err = syscall.Ftruncate(int(f.fd.Fd()), int64(lastBlockOff))
f.lock.Unlock()
if err != nil {
cryptfs.Warn.Printf("shrink Ftruncate returned error: %v", err)
return fuse.ToStatus(err)
}
if lastBlockLen > 0 {
_, status := f.doWrite(data, int64(lastBlockOff))
return status
}
return fuse.OK
}
// else:
// Shrink file by truncating
newBlockLen := int(newSize % f.cfs.PlainBS())
// New file size is aligned to block size - just truncate
if newBlockLen == 0 {
cSize := int64(f.cfs.CipherSize(newSize))
f.lock.Lock()
err := syscall.Ftruncate(int(f.fd.Fd()), cSize)
f.lock.Unlock()
return fuse.ToStatus(err)
}
// New file size is not aligned - need to do RMW on the last block
cryptfs.Debug.Printf("Truncate: Shrink RMW\n")
var blockOffset, blockLen uint64
{
// Get the block the last byte belongs to.
// This is, by definition, the last block.
blockList := f.cfs.SplitRange(newSize - 1, 1)
lastBlock := blockList[0]
blockOffset, blockLen = lastBlock.PlaintextRange()
}
blockData, status := f.doRead(blockOffset, blockLen)
if status != fuse.OK {
cryptfs.Warn.Printf("Truncate: doRead failed: %v\n", err)
return status
}
if len(blockData) < newBlockLen {
cryptfs.Warn.Printf("Truncate: file has shrunk under our feet\n")
return fuse.OK
}
// Truncate the file down to the next block
{
nextBlockSz := int64(f.cfs.CipherSize(newSize - uint64(newBlockLen)))
f.lock.Lock()
err = syscall.Ftruncate(int(f.fd.Fd()), nextBlockSz)
f.lock.Unlock()
if err != nil {
cryptfs.Warn.Printf("Truncate: Intermediate Ftruncate failed: %v\n", err)
return fuse.ToStatus(err)
}
}
// Append truncated last block
_, status = f.Write(blockData[0:newBlockLen], int64(blockOffset))
return status
}
func (f *file) Chmod(mode uint32) fuse.Status {

View File

@ -1,14 +1,12 @@
package pathfs_frontend
import (
"fmt"
"github.com/hanwen/go-fuse/fuse"
"github.com/rfjakob/gocryptfs/cryptfs"
)
// Will a write to offset "off" create a file hole?
func (f *file) createsHole(cipherSize uint64, off int64) bool {
nextBlock := f.cfs.BlockNoCipherOff(cipherSize)
func (f *file) createsHole(plainSize uint64, off int64) bool {
nextBlock := f.cfs.BlockNoPlainOff(plainSize)
targetBlock := f.cfs.BlockNoPlainOff(uint64(off))
if targetBlock > nextBlock {
return true
@ -16,20 +14,8 @@ func (f *file) createsHole(cipherSize uint64, off int64) bool {
return false
}
// Zero-pad the file if a write to "off" creates a file hole
func (f *file) conditionalZeroPad(off int64) fuse.Status {
fi, err := f.fd.Stat()
if err != nil {
cryptfs.Warn.Printf("conditionalZeroPad: Stat: %v\n", err)
return fuse.ToStatus(err)
}
cipherSize := uint64(fi.Size())
if f.createsHole(cipherSize, off) == false {
return fuse.OK
}
plainSize := f.cfs.PlainSize(cipherSize)
// Zero-pad the file of size plainSize to the next block boundary
func (f *file) zeroPad(plainSize uint64) fuse.Status {
lastBlockLen := plainSize % f.cfs.PlainBS()
missing := f.cfs.PlainBS() - lastBlockLen
pad := make([]byte, missing)