Implement Truncate() + Test
This commit is contained in:
parent
aea8d8d6e7
commit
b835f83fd5
@ -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
|
||||
|
59
main_test.go
59
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)))
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user