From 1418565a6b28a91f2f15d5b957d7fd31392a47a1 Mon Sep 17 00:00:00 2001
From: Jakob Unterwurzacher
Date: Thu, 8 Jun 2023 15:50:05 +0200
Subject: tests/cluster: add TestPoCTornWrite

Scary. But explains why TestConcurrentCreate fails.

gocryptfs/tests/cluster$ go test -run TestPoCTornWrite
 --- FAIL: TestPoCTornWrite (0.00s)
    poc_test.go:210: iteration 214: inconsistent block: d6d6d6d6d6d6d6d6d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1
FAIL
---
 tests/cluster/poc_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/tests/cluster/poc_test.go b/tests/cluster/poc_test.go
index 6f74ba2..6719663 100644
--- a/tests/cluster/poc_test.go
+++ b/tests/cluster/poc_test.go
@@ -139,3 +139,84 @@ func TestPoCHeaderCreation(t *testing.T) {
 
 	t.Logf("readEmpty=%d readOk=%d writes=%d", stats.readEmpty, stats.readOk, stats.writes)
 }
+
+// TestPoCTornWrite simulates what TestConcurrentCreate does.
+func TestPoCTornWrite(t *testing.T) {
+	path := test_helpers.TmpDir + "/" + t.Name()
+	var wg sync.WaitGroup
+	const loops = 10000
+
+	writerThread := func() {
+		defer wg.Done()
+		for i := 0; i < loops; i++ {
+			if t.Failed() {
+				return
+			}
+			f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600)
+			if err != nil {
+				t.Errorf("BUG: this should not happen: open err=%v", err)
+				return
+			}
+
+			// Write dummy header unless it is already there
+			lk := unix.Flock_t{
+				Type:   unix.F_WRLCK,
+				Whence: unix.SEEK_SET,
+				Start:  0,
+				Len:    0,
+			}
+			if err := unix.FcntlFlock(uintptr(f.Fd()), syscallcompat.F_OFD_SETLKW, &lk); err != nil {
+				t.Error(err)
+				return
+			}
+
+			hdr := make([]byte, contentenc.HeaderLen)
+			if _, err := f.ReadAt(hdr, 0); err == io.EOF {
+				hdr = bytes.Repeat([]byte{byte(i)}, contentenc.HeaderLen)
+				if _, err := f.WriteAt(hdr, 0); err != nil {
+					t.Errorf("header write failed: %v", err)
+					return
+				}
+			} else if err != nil {
+				t.Error(err)
+				return
+			}
+			lk.Type = unix.F_UNLCK
+			if err := unix.FcntlFlock(uintptr(f.Fd()), syscallcompat.F_OFD_SETLKW, &lk); err != nil {
+				t.Error(err)
+				return
+			}
+
+			// Write dummy data
+			blockData := bytes.Repeat([]byte{byte(i)}, 42)
+			if _, err = f.WriteAt(blockData, contentenc.HeaderLen); err != nil {
+				t.Errorf("iteration %d: WriteAt: %v", i, err)
+				return
+			}
+
+			readBuf := make([]byte, 100)
+			if n, err := f.ReadAt(readBuf, contentenc.HeaderLen); err == io.EOF {
+				readBuf = readBuf[:n]
+			} else if err != nil {
+				t.Error(err)
+				return
+			}
+			if len(readBuf) != len(blockData) {
+				t.Error("wrong length")
+				return
+			}
+			for _, v := range readBuf {
+				if v != readBuf[0] {
+					t.Errorf("iteration %d: inconsistent block: %x", i, readBuf)
+					return
+				}
+			}
+			f.Close()
+		}
+	}
+
+	wg.Add(2)
+	go writerThread()
+	go writerThread()
+	wg.Wait()
+}
-- 
cgit v1.2.3