summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2015-09-30 22:36:53 +0200
committerJakob Unterwurzacher2015-09-30 22:36:53 +0200
commitb835f83fd54944de0aa87a4e53073c1204450fda (patch)
tree475f3930eda5a15700648927a617f8278a08916e
parentaea8d8d6e7e0f852643804641b27d8038991eff9 (diff)
Implement Truncate() + Test
-rw-r--r--cryptfs/cryptfs_content.go9
-rw-r--r--main_test.go59
-rw-r--r--pathfs_frontend/file.go101
3 files changed, 147 insertions, 22 deletions
diff --git a/cryptfs/cryptfs_content.go b/cryptfs/cryptfs_content.go
index 5350ec7..0494b69 100644
--- a/cryptfs/cryptfs_content.go
+++ b/cryptfs/cryptfs_content.go
@@ -115,6 +115,15 @@ func (be *CryptFS) PlainSize(size uint64) uint64 {
return size
}
+// CipherSize - calculate ciphertext size from plaintext size
+func (be *CryptFS) CipherSize(size uint64) uint64 {
+ overhead := be.cipherBS - be.plainBS
+ nBlocks := (size + be.plainBS - 1) / be.plainBS
+ size += nBlocks * overhead
+
+ return size
+}
+
func (be *CryptFS) minu64(x uint64, y uint64) uint64 {
if x < y {
return x
diff --git a/main_test.go b/main_test.go
index 93aa6ff..8d216ac 100644
--- a/main_test.go
+++ b/main_test.go
@@ -17,12 +17,23 @@ const plainDir = tmpDir + "plain/"
const cipherDir = tmpDir + "cipher/"
func unmount() error {
- fu := exec.Command("fusermount", "-u", plainDir)
+ fu := exec.Command("fusermount", "-z", "-u", plainDir)
fu.Stdout = os.Stdout
fu.Stderr = os.Stderr
return fu.Run()
}
+func md5fn(filename string) string {
+ buf, err := ioutil.ReadFile(filename)
+ if err != nil {
+ fmt.Printf("ReadFile: %v\n", err)
+ return ""
+ }
+ rawHash := md5.Sum(buf)
+ hash := hex.EncodeToString(rawHash[:])
+ return hash
+}
+
func TestMain(m *testing.M) {
unmount()
@@ -66,16 +77,11 @@ func testWriteN(t *testing.T, fn string, n int) string {
}
file.Close()
- buf, err := ioutil.ReadFile(plainDir + fn)
- if err != nil {
- t.Fail()
- }
+ bin := md5.Sum(d)
+ hashWant := hex.EncodeToString(bin[:])
- raw := md5.Sum(d)
- hashWant := hex.EncodeToString(raw[:])
+ hashActual := md5fn(plainDir + fn)
- raw = md5.Sum(buf)
- hashActual := hex.EncodeToString(raw[:])
if hashActual != hashWant {
fmt.Printf("hashWant=%s hashActual=%s\n", hashWant, hashActual)
t.Fail()
@@ -101,12 +107,7 @@ func TestWrite1Mx100(t *testing.T) {
// Read and check 100 times to catch race conditions
var i int
for i = 0; i < 100; i++ {
- buf, err := ioutil.ReadFile(plainDir + "1M")
- if err != nil {
- t.Fail()
- }
- rawHash := md5.Sum(buf)
- hashActual := hex.EncodeToString(rawHash[:])
+ hashActual := md5fn(plainDir + "1M")
if hashActual != hashWant {
fmt.Printf("Read corruption in loop # %d\n", i)
t.FailNow()
@@ -116,6 +117,34 @@ func TestWrite1Mx100(t *testing.T) {
}
}
+func TestTruncate(t *testing.T) {
+ fn := plainDir + "truncate"
+ file, err := os.Create(fn)
+ if err != nil {
+ t.FailNow()
+ }
+ // Grow to two blocks
+ file.Truncate(7000)
+ if md5fn(fn) != "95d4ec7038e3e4fdbd5f15c34c3f0b34" {
+ t.Fail()
+ }
+ // Shrink - needs RMW
+ file.Truncate(6999)
+ if md5fn(fn) != "35fd15873ec6c35380064a41b9b9683b" {
+ t.Fail()
+ }
+ // Shrink to one partial block
+ file.Truncate(465)
+ if md5fn(fn) != "a1534d6e98a6b21386456a8f66c55260" {
+ t.Fail()
+ }
+ // Grow to exactly one block
+ file.Truncate(4096)
+ if md5fn(fn) != "620f0b67a91f7f74151bc5be745b7110" {
+ t.Fail()
+ }
+}
+
func BenchmarkStreamWrite(t *testing.B) {
buf := make([]byte, 1024*1024)
t.SetBytes(int64(len(buf)))
diff --git a/pathfs_frontend/file.go b/pathfs_frontend/file.go
index e3be7ff..fe346b6 100644
--- a/pathfs_frontend/file.go
+++ b/pathfs_frontend/file.go
@@ -51,7 +51,11 @@ func (f *file) String() string {
return fmt.Sprintf("cryptFile(%s)", f.fd.Name())
}
-// Called by Read() and for RMW in Write()
+// doRead - returns "length" plaintext bytes from plaintext offset "offset".
+// Reads the corresponding ciphertext from disk, decryptfs it, returns the relevant
+// part.
+//
+// Called by Read(), for RMW in Write() and Truncate()
func (f *file) doRead(off uint64, length uint64) ([]byte, fuse.Status) {
// Read the backing ciphertext in one go
@@ -117,7 +121,7 @@ func (f *file) Write(data []byte, off int64) (uint32, fuse.Status) {
cryptfs.Debug.Printf("Write %s: offset=%d length=%d\n", f.fd.Name(), off, len(data))
var written uint32
- var status fuse.Status
+ status := fuse.OK
dataBuf := bytes.NewBuffer(data)
blocks := f.cfs.SplitRange(uint64(off), uint64(len(data)))
for _, b := range(blocks) {
@@ -189,12 +193,95 @@ func (f *file) Fsync(flags int) (code fuse.Status) {
return r
}
-func (f *file) Truncate(size uint64) fuse.Status {
- f.lock.Lock()
- r := fuse.ToStatus(syscall.Ftruncate(int(f.fd.Fd()), int64(size)))
- f.lock.Unlock()
+func (f *file) Truncate(newSize uint64) fuse.Status {
- return r
+ // 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)
+ return fuse.ToStatus(err)
+ }
+ oldSize := uint64(fi.Size())
+
+ // Grow file by appending zeros
+ 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)
+ }
+ for remaining >= uint64(len(zeros)) {
+ written, status := f.Write(zeros, int64(offset))
+ if status != fuse.OK {
+ 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
+ }
+
+ // 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
+ 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 {