libgocryptfs/tests/plaintextnames/file_holes_test.go

154 lines
4.1 KiB
Go

package plaintextnames
import (
"fmt"
"math/rand"
"os"
"os/exec"
"syscall"
"testing"
"time"
"github.com/rfjakob/gocryptfs/tests/test_helpers"
"github.com/rfjakob/gocryptfs/contrib/findholes/holes"
)
func findHolesPretty(t *testing.T, path string) string {
f, err := os.Open(path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
segments, err := holes.Find(int(f.Fd()))
if err != nil {
t.Fatal(err)
}
return holes.PrettyPrint(segments)
}
func doTestFileHoleCopy(t *testing.T, name string, writeOffsets []int64) {
n := "TestFileHoleCopy." + name
pPath := []string{pDir + "/" + n}
cPath := []string{cDir + "/" + n}
os.Remove(pPath[0])
holes.Create(pPath[0])
// expected md6
md5 := test_helpers.Md5fn(pPath[0])
pSegments := []string{findHolesPretty(t, pPath[0])}
cSegments := []string{findHolesPretty(t, cPath[0])}
// create 5 more copies
for i := 1; i < 5; i++ {
pPath = append(pPath, fmt.Sprintf("%s.%d", pPath[0], i))
cPath = append(cPath, fmt.Sprintf("%s.%d", cPath[0], i))
out, err := exec.Command("cp", "--sparse=auto", pPath[i-1], pPath[i]).CombinedOutput()
if err != nil {
t.Fatal(string(out))
}
tmp := test_helpers.Md5fn(pPath[0])
if tmp != md5 {
t.Errorf("pPath[%d]: wrong md5, have %s, want %s", i, tmp, md5)
}
pSegments = append(pSegments, findHolesPretty(t, pPath[i]))
cSegments = append(cSegments, findHolesPretty(t, cPath[i]))
}
// "cp --sparse=auto" checks of the file has fewer blocks on disk than it
// should have for its size. Only then it will try to create a sparse copy.
var st syscall.Stat_t
err := syscall.Stat(pPath[0], &st)
if err != nil {
t.Fatal(err)
}
// convert 512 byte blocks to 4k blocks
blocks4k := st.Blocks / 8
// For more than a few fragments, ext4 allocates one extra block
blocks4k++
if blocks4k >= (st.Size+4095)/4096 {
t.Logf("file will look non-sparse to cp, skipping segment check")
return
}
// Check that size on disk stays the same across copies
var st0 syscall.Stat_t
if err := syscall.Stat(pPath[0], &st0); err != nil {
t.Fatal(err)
}
for i := range pSegments {
var st syscall.Stat_t
if err := syscall.Stat(pPath[i], &st); err != nil {
t.Fatal(err)
}
// Size on disk fluctuates by +-4kB due to different number of extents
// (looking at "filefrag -v", it seems like ext4 needs 4kB extra once
// you have >=4 extents)
if st.Blocks != st0.Blocks && st.Blocks != st0.Blocks-8 && st.Blocks != st0.Blocks+8 {
t.Errorf("size changed: st0.Blocks=%d st%d.Blocks=%d", st0.Blocks, i, st.Blocks)
}
}
// Check that hole/data segments stays the same across copies
out := ""
same := true
for i := range pSegments {
out += fmt.Sprintf("pSegments[%d]:\n%s\n", i, pSegments[i])
if i < len(pSegments)-1 {
if pSegments[i+1] != pSegments[i] {
same = false
t.Errorf("error: pSegments[%d] is different than pSegments[%d]!", i, i+1)
}
}
}
out += "------------------------------------\n"
for i := range cSegments {
out += fmt.Sprintf("cSegments[%d]:\n%s\n", i, cSegments[i])
if i < len(pSegments)-1 {
if cSegments[i+1] != cSegments[i] {
same = false
t.Errorf("error: cSegments[%d] is different than cSegments[%d]!", i, i+1)
}
}
}
if !same {
t.Log(out)
}
}
// TestFileHoleCopy creates a sparse times, copies it a few times, and check if
// the copies are the same (including the location of holes and data sections).
//
// The test runs with -plaintextnames because that makes it easier to manipulate
// cipherdir directly.
func TestFileHoleCopy(t *testing.T) {
// | hole | x | hole | x | hole |
// truncate -s 50000 foo && dd if=/dev/zero of=foo bs=1 seek=10000 count=1 conv=notrunc && dd if=/dev/zero of=foo bs=1 seek=30000 count=1 conv=notrunc
name := "c0"
c0 := []int64{10000, 30000}
if !t.Run("c0", func(t *testing.T) { doTestFileHoleCopy(t, name, c0) }) {
t.Log("aborting further subtests")
return
}
rand.Seed(time.Now().UnixNano())
for k := 0; k < 100; k++ {
c1 := make([]int64, 10)
for i := range c1 {
c1[i] = int64(rand.Int31n(60000))
}
name := fmt.Sprintf("k%d", k)
if !t.Run(name, func(t *testing.T) { doTestFileHoleCopy(t, name, c1) }) {
t.Log("aborting further subtests")
return
}
}
}