summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2015-10-04 14:21:07 +0200
committerJakob Unterwurzacher2015-10-04 14:21:07 +0200
commit775676ecb82f415874f3c6b83da1436b629166f4 (patch)
tree7aa81dc0bdc0669630036289c26beb8c36807f32
parent2003ca965d2905e240f2b2f1c596aa02f7786c77 (diff)
Utilize file hole passtrough capability in Truncate()
Cuts down the runtime of xfstests generic/014 from 1822 seconds to 36 seconds
-rw-r--r--pathfs_frontend/file.go124
-rw-r--r--pathfs_frontend/file_holes.go22
2 files changed, 59 insertions, 87 deletions
diff --git a/pathfs_frontend/file.go b/pathfs_frontend/file.go
index 2c2f1eb..733bfd0 100644
--- a/pathfs_frontend/file.go
+++ b/pathfs_frontend/file.go
@@ -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))
- 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)
+ err = syscall.Ftruncate(int(f.fd.Fd()), int64(lastBlockOff))
f.lock.Unlock()
if err != nil {
- cryptfs.Warn.Printf("Truncate: Intermediate Ftruncate failed: %v\n", err)
+ 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
}
- // Append truncated last block
- _, status = f.Write(blockData[0:newBlockLen], int64(blockOffset))
- return status
}
func (f *file) Chmod(mode uint32) fuse.Status {
diff --git a/pathfs_frontend/file_holes.go b/pathfs_frontend/file_holes.go
index 8c9c83f..db27805 100644
--- a/pathfs_frontend/file_holes.go
+++ b/pathfs_frontend/file_holes.go
@@ -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)