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:
parent
2003ca965d
commit
775676ecb8
@ -181,7 +181,20 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
|
|||||||
// Write - FUSE call
|
// Write - FUSE call
|
||||||
func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
|
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))
|
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)
|
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 {
|
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
|
// We need the old file size to determine if we are growing or shrinking
|
||||||
// the file
|
// the file
|
||||||
fi, err := f.fd.Stat()
|
fi, err := f.fd.Stat()
|
||||||
if err != nil {
|
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)
|
return fuse.ToStatus(err)
|
||||||
}
|
}
|
||||||
oldSize := f.cfs.PlainSize(uint64(fi.Size()))
|
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()
|
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)
|
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 {
|
if newSize > oldSize {
|
||||||
remaining := newSize - oldSize
|
blocks := f.cfs.SplitRange(oldSize, newSize - oldSize)
|
||||||
offset := oldSize
|
for _, b := range(blocks) {
|
||||||
var zeros []byte
|
// First and last block may be partial
|
||||||
// Append a maximum of 1MB in each iteration
|
if b.IsPartial() {
|
||||||
if remaining > 1048576 {
|
off, _ := b.PlaintextRange()
|
||||||
zeros = make([]byte, 1048576)
|
off += b.Offset
|
||||||
} else {
|
_, status := f.doWrite(make([]byte, b.Length), int64(off))
|
||||||
zeros = make([]byte, remaining)
|
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)) {
|
return fuse.OK
|
||||||
written, status := f.Write(zeros, int64(offset))
|
// 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 {
|
if status != fuse.OK {
|
||||||
|
cryptfs.Warn.Printf("shrink doRead returned error: %v", err)
|
||||||
return status
|
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 {
|
f.lock.Lock()
|
||||||
_, status := f.Write(zeros[0:remaining], int64(offset))
|
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 status
|
||||||
}
|
}
|
||||||
return fuse.OK
|
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 {
|
func (f *file) Chmod(mode uint32) fuse.Status {
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
package pathfs_frontend
|
package pathfs_frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/hanwen/go-fuse/fuse"
|
"github.com/hanwen/go-fuse/fuse"
|
||||||
"github.com/rfjakob/gocryptfs/cryptfs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Will a write to offset "off" create a file hole?
|
// Will a write to offset "off" create a file hole?
|
||||||
func (f *file) createsHole(cipherSize uint64, off int64) bool {
|
func (f *file) createsHole(plainSize uint64, off int64) bool {
|
||||||
nextBlock := f.cfs.BlockNoCipherOff(cipherSize)
|
nextBlock := f.cfs.BlockNoPlainOff(plainSize)
|
||||||
targetBlock := f.cfs.BlockNoPlainOff(uint64(off))
|
targetBlock := f.cfs.BlockNoPlainOff(uint64(off))
|
||||||
if targetBlock > nextBlock {
|
if targetBlock > nextBlock {
|
||||||
return true
|
return true
|
||||||
@ -16,20 +14,8 @@ func (f *file) createsHole(cipherSize uint64, off int64) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zero-pad the file if a write to "off" creates a file hole
|
// Zero-pad the file of size plainSize to the next block boundary
|
||||||
func (f *file) conditionalZeroPad(off int64) fuse.Status {
|
func (f *file) zeroPad(plainSize uint64) 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)
|
|
||||||
lastBlockLen := plainSize % f.cfs.PlainBS()
|
lastBlockLen := plainSize % f.cfs.PlainBS()
|
||||||
missing := f.cfs.PlainBS() - lastBlockLen
|
missing := f.cfs.PlainBS() - lastBlockLen
|
||||||
pad := make([]byte, missing)
|
pad := make([]byte, missing)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user