aboutsummaryrefslogtreecommitdiff
path: root/internal/fusefrontend
diff options
context:
space:
mode:
authorJakob Unterwurzacher2016-07-01 23:29:31 +0200
committerJakob Unterwurzacher2016-07-01 23:32:27 +0200
commitf2b4d57068d13b6dc3de2ccc6550675d11d34cfa (patch)
treeb90d31b0375e41318f2ce879df14f36bff4e3b5e /internal/fusefrontend
parentae77d1852793c4dd43c8ddd69de574223aecce5e (diff)
fusefrontend: coalesce grows in Truncate()
We were growing the file block-by-block which was pretty inefficient. We now coalesce all the grows into a single Ftruncate. Also simplifies the code! Simplistic benchmark: Before: $ time truncate -s 1000M foo real 0m0.568s After: $ time truncate -s 1000M foo real 0m0.205s
Diffstat (limited to 'internal/fusefrontend')
-rw-r--r--internal/fusefrontend/file.go48
-rw-r--r--internal/fusefrontend/file_holes.go4
2 files changed, 24 insertions, 28 deletions
diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go
index 513dbbf..8f13553 100644
--- a/internal/fusefrontend/file.go
+++ b/internal/fusefrontend/file.go
@@ -217,6 +217,8 @@ const FALLOC_FL_KEEP_SIZE = 0x01
//
// Called by Write() for normal writing,
// and by Truncate() to rewrite the last file block.
+//
+// Empty writes do nothing and are allowed.
func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
// Read header from disk, create a new one if the file is empty
@@ -282,6 +284,8 @@ func (f *file) doWrite(data []byte, off int64) (uint32, fuse.Status) {
}
// Write - FUSE call
+//
+// If the write creates a hole, pads the file to the next block boundary.
func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
f.fdLock.RLock()
defer f.fdLock.RUnlock()
@@ -362,7 +366,6 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
wlock.lock(f.ino)
defer wlock.unlock(f.ino)
var err error
-
// Common case first: Truncate to zero
if newSize == 0 {
err = syscall.Ftruncate(int(f.fd.Fd()), 0)
@@ -374,7 +377,6 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
f.header = nil
return fuse.OK
}
-
// We need the old file size to determine if we are growing or shrinking
// the file
fi, err := f.fd.Stat()
@@ -388,12 +390,10 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
newB := float32(newSize) / float32(f.contentEnc.PlainBS())
tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize)
}
-
// File size stays the same - nothing to do
if newSize == oldSize {
return fuse.OK
}
-
// File grows
if newSize > oldSize {
// File was empty, create new header
@@ -405,30 +405,22 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
}
// New blocks to add
addBlocks := f.contentEnc.ExplodePlainRange(oldSize, newSize-oldSize)
- for _, b := range addBlocks {
- // First and last block may be partial and must be actually written
- if b.IsPartial() {
- off, _ := b.PlaintextRange()
- off += b.Skip
- _, status := f.doWrite(make([]byte, b.Length), int64(off))
- if status != fuse.OK {
- return status
- }
- } else {
- // Complete all-zero blocks can stay all-zero because we do file
- // hole passthrough.
- // TODO We are growing the file block-by-block which is pretty
- // inefficient. We could coalesce all the grows into a single
- // Ftruncate.
- off, length := b.CiphertextRange()
- err = syscall.Ftruncate(int(f.fd.Fd()), int64(off+length))
- if err != nil {
- tlog.Warn.Printf("grow Ftruncate returned error: %v", err)
- return fuse.ToStatus(err)
- }
+ if len(addBlocks) >= 2 {
+ f.zeroPad(oldSize)
+ }
+ lastBlock := addBlocks[len(addBlocks)-1]
+ if lastBlock.IsPartial() {
+ off, _ := lastBlock.PlaintextRange()
+ _, status := f.doWrite(make([]byte, lastBlock.Length), int64(off+lastBlock.Skip))
+ return status
+ } else {
+ off, length := lastBlock.CiphertextRange()
+ err = syscall.Ftruncate(f.intFd(), int64(off+length))
+ if err != nil {
+ tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err)
}
+ return fuse.ToStatus(err)
}
- return fuse.OK
} else {
// File shrinks
blockNo := f.contentEnc.PlainOffToBlockNo(newSize)
@@ -440,14 +432,14 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
var status fuse.Status
data, status = f.doRead(plainOff, lastBlockLen)
if status != fuse.OK {
- tlog.Warn.Printf("shrink doRead returned error: %v", err)
+ tlog.Warn.Printf("Truncate: shrink doRead returned error: %v", err)
return status
}
}
// Truncate down to the last complete block
err = syscall.Ftruncate(int(f.fd.Fd()), int64(cipherOff))
if err != nil {
- tlog.Warn.Printf("shrink Ftruncate returned error: %v", err)
+ tlog.Warn.Printf("Truncate: shrink Ftruncate returned error: %v", err)
return fuse.ToStatus(err)
}
// Append partial block
diff --git a/internal/fusefrontend/file_holes.go b/internal/fusefrontend/file_holes.go
index 46a5865..1d9a5fb 100644
--- a/internal/fusefrontend/file_holes.go
+++ b/internal/fusefrontend/file_holes.go
@@ -22,6 +22,10 @@ func (f *file) createsHole(plainSize uint64, off int64) bool {
func (f *file) zeroPad(plainSize uint64) fuse.Status {
lastBlockLen := plainSize % f.contentEnc.PlainBS()
missing := f.contentEnc.PlainBS() - lastBlockLen
+ if missing == 0 {
+ // Already block-aligned
+ return fuse.OK
+ }
pad := make([]byte, missing)
tlog.Debug.Printf("zeroPad: Writing %d bytes\n", missing)
_, status := f.doWrite(pad, int64(plainSize))