2021-05-24 14:28:36 +02:00
|
|
|
package plaintextnames
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2021-05-29 16:05:36 +02:00
|
|
|
"math/rand"
|
2021-05-24 14:28:36 +02:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
2021-05-29 16:05:36 +02:00
|
|
|
"syscall"
|
2021-05-24 14:28:36 +02:00
|
|
|
"testing"
|
2021-05-29 16:05:36 +02:00
|
|
|
"time"
|
2021-05-24 14:28:36 +02:00
|
|
|
|
2021-08-23 15:05:15 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
|
2021-05-24 14:28:36 +02:00
|
|
|
|
2021-08-23 15:05:15 +02:00
|
|
|
"github.com/rfjakob/gocryptfs/v2/contrib/findholes/holes"
|
2021-05-24 14:28:36 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2021-05-29 16:05:36 +02:00
|
|
|
func doTestFileHoleCopy(t *testing.T, name string, writeOffsets []int64) {
|
|
|
|
n := "TestFileHoleCopy." + name
|
2021-05-24 14:28:36 +02:00
|
|
|
pPath := []string{pDir + "/" + n}
|
|
|
|
cPath := []string{cDir + "/" + n}
|
|
|
|
|
2021-05-29 16:05:36 +02:00
|
|
|
os.Remove(pPath[0])
|
|
|
|
holes.Create(pPath[0])
|
|
|
|
|
|
|
|
// expected md6
|
2021-05-24 14:28:36 +02:00
|
|
|
md5 := test_helpers.Md5fn(pPath[0])
|
|
|
|
|
|
|
|
pSegments := []string{findHolesPretty(t, pPath[0])}
|
|
|
|
cSegments := []string{findHolesPretty(t, cPath[0])}
|
|
|
|
|
2021-05-29 16:05:36 +02:00
|
|
|
// create 5 more copies
|
2021-05-24 14:28:36 +02:00
|
|
|
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]))
|
|
|
|
}
|
|
|
|
|
2021-05-29 16:05:36 +02:00
|
|
|
// "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)
|
|
|
|
}
|
2021-05-24 14:28:36 +02:00
|
|
|
for i := range pSegments {
|
2021-05-29 16:05:36 +02:00
|
|
|
var st syscall.Stat_t
|
|
|
|
if err := syscall.Stat(pPath[i], &st); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-05-29 16:56:20 +02:00
|
|
|
// 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 {
|
2021-05-29 16:05:36 +02:00
|
|
|
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])
|
2021-05-24 14:28:36 +02:00
|
|
|
if i < len(pSegments)-1 {
|
|
|
|
if pSegments[i+1] != pSegments[i] {
|
2021-05-29 16:05:36 +02:00
|
|
|
same = false
|
|
|
|
t.Errorf("error: pSegments[%d] is different than pSegments[%d]!", i, i+1)
|
2021-05-24 14:28:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-29 16:05:36 +02:00
|
|
|
out += "------------------------------------\n"
|
2021-05-24 14:28:36 +02:00
|
|
|
for i := range cSegments {
|
2021-05-29 16:05:36 +02:00
|
|
|
out += fmt.Sprintf("cSegments[%d]:\n%s\n", i, cSegments[i])
|
2021-05-24 14:28:36 +02:00
|
|
|
if i < len(pSegments)-1 {
|
|
|
|
if cSegments[i+1] != cSegments[i] {
|
2021-05-29 16:05:36 +02:00
|
|
|
same = false
|
|
|
|
t.Errorf("error: cSegments[%d] is different than cSegments[%d]!", i, i+1)
|
2021-05-24 14:28:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-29 16:05:36 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2021-05-24 14:28:36 +02:00
|
|
|
}
|