// test gocryptfs cipherdir mounted multiple times at the same time
package sharedstorage

import (
	"fmt"
	"os"
	"testing"
	"time"

	"golang.org/x/sys/unix"

	"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)

var flagSharestorage bool

// EntryTimeout is 1 second, give the kernel 1.1 second to actually
// expire an entry. The tests fail sometime with 1.0 second!
const waitForExpire = time.Second + 100*time.Millisecond

func TestMain(m *testing.M) {
	ret := 0
	flagSharestorage = false
	ret += m.Run()
	flagSharestorage = true
	ret += m.Run()
	os.Exit(ret)
}

type testCase struct {
	t *testing.T

	cipherdir string
	mnt1      string
	mnt2      string
}

func newTestCase(t *testing.T) *testCase {
	tc := testCase{}
	tc.cipherdir = test_helpers.InitFS(t)
	tc.mnt1 = tc.cipherdir + ".mnt1"
	tc.mnt2 = tc.cipherdir + ".mnt2"
	mountSharedstorage(t, tc.cipherdir, tc.mnt1)
	mountSharedstorage(t, tc.cipherdir, tc.mnt2)
	t.Logf("newTestCase: sharedstorage=%v cipherdir=%q", flagSharestorage, tc.cipherdir)
	return &tc
}

func (tc *testCase) cleanup() {
	for _, mnt := range []string{tc.mnt1, tc.mnt2} {
		err := test_helpers.UnmountErr(mnt)
		if err != nil {
			tc.t.Error(err)
		}
	}
}

// mountSharedstorage mounts `cipherdir` on `mnt` with or without the
// `-sharedstorage` flag, depending on the global var `flagSharestorage`.
func mountSharedstorage(t *testing.T, cipherdir string, mnt string) {
	args := []string{"-extpass=echo test"}
	if flagSharestorage {
		args = append(args, "-sharedstorage")
	}
	test_helpers.MountOrFatal(t, cipherdir, mnt, args...)
}

func TestDirUnlink(t *testing.T) {
	tc := newTestCase(t)
	defer tc.cleanup()

	// Create dir via mnt1
	if err := unix.Mkdir(tc.mnt1+"/foo", 0700); err != nil {
		t.Fatal(err)
	}
	// Replace dir with file via mnt2
	if err := unix.Rmdir(tc.mnt2 + "/foo"); err != nil {
		t.Fatal(err)
	}
	if fd, err := unix.Open(tc.mnt2+"/foo", unix.O_CREAT|unix.O_WRONLY|unix.O_TRUNC, 0600); err != nil {
		t.Fatal(err)
	} else {
		unix.Close(fd)
	}
	// Try to unlink via mnt1
	if err := unix.Unlink(tc.mnt1 + "/foo"); err != nil {
		// Must work with -sharedstorage
		if flagSharestorage {
			t.Fatal(err)
		} else {
			// Must always work after cache timeout
			time.Sleep(waitForExpire)
			if err := unix.Unlink(tc.mnt1 + "/foo"); err != nil {
				t.Fatal(err)
			}
		}
	}
}

// TestStaleHardlinks always failed before
// https://review.gerrithub.io/c/hanwen/go-fuse/+/513646/2
func TestStaleHardlinks(t *testing.T) {
	tc := newTestCase(t)
	defer tc.cleanup()

	link0 := tc.mnt1 + "/link0"
	if fd, err := unix.Open(link0, unix.O_CREAT|unix.O_WRONLY|unix.O_TRUNC, 0600); err != nil {
		t.Fatal(err)
	} else {
		unix.Close(fd)
	}
	// Create hardlinks via mnt1
	for i := 1; i < 20; i++ {
		linki := fmt.Sprintf(tc.mnt1+"/link%d", i)
		if err := unix.Link(link0, linki); err != nil {
			t.Fatal(err)
		}
	}
	// Delete hardlinks via mnt2
	for i := 1; i < 20; i++ {
		linki := fmt.Sprintf(tc.mnt2+"/link%d", i)
		if err := unix.Unlink(linki); err != nil {
			t.Fatal(err)
		}
	}
	// Open link0 via mnt1
	fd, err := unix.Open(link0, unix.O_RDONLY, 0)
	if err != nil {
		// Must work with -sharedstorage
		if flagSharestorage {
			t.Fatal(err)
		} else {
			// Must always work after cache timeout
			time.Sleep(waitForExpire)
			fd, err = unix.Open(link0, unix.O_RDONLY, 0)
			if err != nil {
				t.Fatal(err)
			}
		}
	}
	unix.Close(fd)
}
