libgocryptfs/tests/matrix/matrix_test.go
Jakob Unterwurzacher c19baa10f8 tests: use t.Name()
We did not use t.Name() as it was not available
before Go 1.8. Now the oldest Go version we support is
Go 1.11, so we can use it.
2020-05-09 15:42:57 +02:00

875 lines
22 KiB
Go

// Tests run for (almost all) combinations of openssl, aessiv, plaintextnames.
package matrix
// File reading, writing, modification, truncate
//
// Runs everything four times, for all combinations of
// "-plaintextnames" and "-openssl".
//
// Test Matrix:
// openssl=true openssl=false
// plaintextnames=false X X
// plaintextnames=true X X
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"runtime"
"sync"
"syscall"
"testing"
"golang.org/x/sys/unix"
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
"github.com/rfjakob/gocryptfs/tests/test_helpers"
)
// Several tests need to be aware if plaintextnames is active or not, so make this
// a global variable
var testcase testcaseMatrix
type testcaseMatrix struct {
plaintextnames bool
openssl string
aessiv bool
raw64 bool
extraArgs []string
}
var matrix = []testcaseMatrix{
// Normal
{false, "auto", false, false, nil},
{false, "true", false, false, nil},
{false, "false", false, false, nil},
// Plaintextnames
{true, "true", false, false, nil},
{true, "false", false, false, nil},
// AES-SIV (does not use openssl, no need to test permutations)
{false, "auto", true, false, nil},
{true, "auto", true, false, nil},
// Raw64
{false, "auto", false, true, nil},
// -serialize_reads
{false, "auto", false, false, []string{"-serialize_reads"}},
}
// This is the entry point for the tests
func TestMain(m *testing.M) {
// Make "testing.Verbose()" return the correct value
flag.Parse()
for _, testcase = range matrix {
if testcase.openssl == "true" && stupidgcm.BuiltWithoutOpenssl {
continue
}
if testing.Verbose() {
fmt.Printf("matrix: testcase = %#v\n", testcase)
}
test_helpers.ResetTmpDir(!testcase.plaintextnames)
opts := []string{"-zerokey"}
opts = append(opts, fmt.Sprintf("-openssl=%v", testcase.openssl))
opts = append(opts, fmt.Sprintf("-plaintextnames=%v", testcase.plaintextnames))
opts = append(opts, fmt.Sprintf("-aessiv=%v", testcase.aessiv))
opts = append(opts, fmt.Sprintf("-raw64=%v", testcase.raw64))
opts = append(opts, testcase.extraArgs...)
test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, opts...)
before := test_helpers.ListFds(0, test_helpers.TmpDir)
r := m.Run()
// Catch fd leaks in the tests. NOTE: this does NOT catch leaks in
// the gocryptfs FUSE process, but only in the tests that access it!
// All fds that point outside TmpDir are not interesting (the Go test
// infrastucture creates temporary log files we don't care about).
after := test_helpers.ListFds(0, test_helpers.TmpDir)
if len(before) != len(after) {
fmt.Printf("fd leak in test process? before, after:\n%v\n%v\n", before, after)
os.Exit(1)
}
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
if r != 0 {
os.Exit(r)
}
}
os.Exit(0)
}
// Write `n` random bytes to filename `fn`, read again, compare hash
func testWriteN(t *testing.T, fn string, n int) string {
file, err := os.Create(test_helpers.DefaultPlainDir + "/" + fn)
if err != nil {
t.Fatal(err)
}
d := make([]byte, n)
for i := range d {
// Fill with pattern
d[i] = byte(rand.Int())
}
_, err = file.Write(d)
if err != nil {
t.Fatal(err)
}
err = file.Close()
if err != nil {
t.Fatal(err)
}
test_helpers.VerifySize(t, test_helpers.DefaultPlainDir+"/"+fn, n)
hashWant := test_helpers.Md5hex(d)
hashActual := test_helpers.Md5fn(test_helpers.DefaultPlainDir + "/" + fn)
if hashActual != hashWant {
t.Errorf("Wrong content, hashWant=%s hashActual=%s", hashWant, hashActual)
}
return hashActual
}
func TestWrite10(t *testing.T) {
testWriteN(t, "10", 10)
}
func TestWrite100(t *testing.T) {
testWriteN(t, "100", 100)
}
func TestWrite1M(t *testing.T) {
testWriteN(t, "1M", 1024*1024)
}
func TestWrite100x100(t *testing.T) {
hashWant := testWriteN(t, "100x100", 100)
// Read and check 100 times to catch race conditions
var i int
for i = 0; i < 100; i++ {
hashActual := test_helpers.Md5fn(test_helpers.DefaultPlainDir + "/100x100")
if hashActual != hashWant {
fmt.Printf("Read corruption in loop #%d: have=%s want=%s\n", i, hashActual, hashWant)
t.FailNow()
} else {
//fmt.Print(".")
}
}
}
func TestWrite10Tight(t *testing.T) {
path := test_helpers.DefaultPlainDir + "/TestWrite10Tight"
content := make([]byte, 10)
buf := make([]byte, 100)
for i := 0; i < 100; i++ {
file, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
_, err = file.Write(content)
if err != nil {
t.Fatal(err)
}
err = file.Close()
if err != nil {
t.Fatal(err)
}
file, err = os.Open(path)
if err != nil {
t.Fatal(err)
}
n, err := file.Read(buf)
if err != nil {
t.Fatal(err)
}
if n != 10 {
t.Fatalf("want 10 bytes, got %d", n)
}
err = file.Close()
if err != nil {
t.Fatal(err)
}
err = os.Remove(path)
if err != nil {
t.Fatal()
}
}
}
// Hint for calculating reference md5sums:
// dd if=/dev/zero count=1 bs=XYZ | md5sum
func TestTruncate(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/truncate"
file, err := os.Create(fn)
if err != nil {
t.FailNow()
}
defer file.Close()
// Grow to two blocks
file.Truncate(7000)
test_helpers.VerifySize(t, fn, 7000)
if md5 := test_helpers.Md5fn(fn); md5 != "95d4ec7038e3e4fdbd5f15c34c3f0b34" {
t.Errorf("Wrong md5 %s", md5)
}
// Shrink - needs RMW
file.Truncate(6999)
test_helpers.VerifySize(t, fn, 6999)
if md5 := test_helpers.Md5fn(fn); md5 != "35fd15873ec6c35380064a41b9b9683b" {
t.Errorf("Wrong md5 %s", md5)
}
// Shrink to one partial block
file.Truncate(465)
test_helpers.VerifySize(t, fn, 465)
if md5 := test_helpers.Md5fn(fn); md5 != "a1534d6e98a6b21386456a8f66c55260" {
t.Errorf("Wrong md5 %s", md5)
}
// Grow to exactly one block
file.Truncate(4096)
test_helpers.VerifySize(t, fn, 4096)
if md5 := test_helpers.Md5fn(fn); md5 != "620f0b67a91f7f74151bc5be745b7110" {
t.Errorf("Wrong md5 %s", md5)
}
// Truncate to zero
file.Truncate(0)
test_helpers.VerifySize(t, fn, 0)
// Grow to 10MB (creates file holes)
var sz int
sz = 10 * 1024 * 1024
file.Truncate(int64(sz))
test_helpers.VerifySize(t, fn, sz)
if md5 := test_helpers.Md5fn(fn); md5 != "f1c9645dbc14efddc7d8a322685f26eb" {
t.Errorf("Wrong md5 %s", md5)
}
// Grow to 10MB + 100B (partial block on the end)
sz = 10*1024*1024 + 100
file.Truncate(int64(sz))
test_helpers.VerifySize(t, fn, sz)
if md5 := test_helpers.Md5fn(fn); md5 != "c23ea79b857b91a7ff07c6ecf185f1ca" {
t.Errorf("Wrong md5 %s", md5)
}
// Grow to 20MB (creates file holes, partial block on the front)
sz = 20 * 1024 * 1024
file.Truncate(int64(sz))
test_helpers.VerifySize(t, fn, sz)
if md5 := test_helpers.Md5fn(fn); md5 != "8f4e33f3dc3e414ff94e5fb6905cba8c" {
t.Errorf("Wrong md5 %s", md5)
}
}
func TestAppend(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/append"
file, err := os.Create(fn)
if err != nil {
t.FailNow()
}
defer file.Close()
data := []byte("testdata123456789") // length 17
var buf bytes.Buffer
var hashWant string
for i := 0; i <= 500; i++ {
file.Write(data)
buf.Write(data)
hashWant = test_helpers.Md5hex(buf.Bytes())
hashActual := test_helpers.Md5fn(fn)
if hashWant != hashActual {
t.FailNow()
}
}
// Overwrite with the same data
// Hash must stay the same
file.Seek(0, 0)
for i := 0; i <= 500; i++ {
file.Write(data)
hashActual := test_helpers.Md5fn(fn)
if hashWant != hashActual {
t.FailNow()
}
}
}
// Create a file with holes by writing to offset 0 (block #0) and
// offset 4096 (block #1).
func TestFileHoles(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/fileholes"
file, err := os.Create(fn)
if err != nil {
t.Errorf("file create failed")
}
defer file.Close()
foo := []byte("foo")
file.Write(foo)
file.WriteAt(foo, 4096)
_, err = ioutil.ReadFile(fn)
if err != nil {
t.Error(err)
}
}
// sContains - does the slice of strings "haystack" contain "needle"?
func sContains(haystack []string, needle string) bool {
for _, element := range haystack {
if element == needle {
return true
}
}
return false
}
func TestRmwRace(t *testing.T) {
runtime.GOMAXPROCS(10)
fn := test_helpers.DefaultPlainDir + "/rmwrace"
f1, err := os.Create(fn)
if err != nil {
t.Fatalf("file create failed")
}
defer f1.Close()
f2, err := os.Create(fn)
if err != nil {
t.Fatalf("file create failed")
}
defer f2.Close()
oldBlock := bytes.Repeat([]byte("o"), 4096)
newBlock := bytes.Repeat([]byte("n"), 4096)
shortBlock := bytes.Repeat([]byte("s"), 16)
mergedBlock := make([]byte, 4096)
copy(mergedBlock, newBlock)
copy(mergedBlock[4080:], shortBlock)
goodMd5 := make(map[string]int)
for i := 0; i < 1000; i++ {
// Reset to [ooooooooo]
_, err = f1.WriteAt(oldBlock, 0)
if err != nil {
t.Fatalf("Write failed")
}
var wg sync.WaitGroup
wg.Add(2)
// Write to the end of the file, [....ssss]
go func() {
f1.WriteAt(shortBlock, 4080)
wg.Done()
}()
// Overwrite to [nnnnnnn]
go func() {
f2.WriteAt(newBlock, 0)
wg.Done()
}()
wg.Wait()
// The file should be either:
// [nnnnnnnnnn] (md5: 6c1660fdabccd448d1359f27b3db3c99) or
// [nnnnnnssss] (md5: da885006a6a284530a427c73ce1e5c32)
// but it must not be
// [oooooossss]
buf, _ := ioutil.ReadFile(fn)
m := test_helpers.Md5hex(buf)
goodMd5[m] = goodMd5[m] + 1
/*
if m == "6c1660fdabccd448d1359f27b3db3c99" {
fmt.Println(hex.Dump(buf))
t.FailNow()
}
*/
}
}
// With "--plaintextnames", the name "/gocryptfs.conf" is reserved.
// Otherwise there should be no restrictions.
func TestFiltered(t *testing.T) {
filteredFile := test_helpers.DefaultPlainDir + "/gocryptfs.conf"
file, err := os.Create(filteredFile)
if testcase.plaintextnames && err == nil {
t.Errorf("should have failed but didn't")
} else if !testcase.plaintextnames && err != nil {
t.Error(err)
}
file.Close()
err = os.Remove(filteredFile)
if testcase.plaintextnames && err == nil {
t.Errorf("should have failed but didn't")
} else if !testcase.plaintextnames && err != nil {
t.Error(err)
}
}
func TestFilenameEncryption(t *testing.T) {
file, err := os.Create(test_helpers.DefaultPlainDir + "/TestFilenameEncryption.txt")
file.Close()
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(test_helpers.DefaultCipherDir + "/TestFilenameEncryption.txt")
if testcase.plaintextnames && err != nil {
t.Errorf("plaintextnames not working: %v", err)
} else if !testcase.plaintextnames && err == nil {
t.Errorf("file name encryption not working")
}
}
// Test Rename
func TestRename(t *testing.T) {
test_helpers.TestRename(t, test_helpers.DefaultPlainDir)
}
// Test that names of all lengths work
func TestNameLengths(t *testing.T) {
f, err := os.Open(test_helpers.DefaultPlainDir)
if err != nil {
t.Fatal(err)
}
entries, err := f.Readdirnames(0)
if err != nil {
t.Fatal(err)
}
f.Close()
cnt1 := len(entries)
wd := test_helpers.DefaultPlainDir + "/"
name := "x"
for len(name) < 2000 {
f, err := os.Create(wd + name + "x")
if err != nil {
break
}
name = name + "x"
f.Close()
f, err = os.Open(test_helpers.DefaultPlainDir)
if err != nil {
t.Fatal(err)
}
// In v1.7-rc2, we had a bug that allowed creation of too-long names.
// This threw errors in like this in READDIR:
//
// OpenDir ".": invalid entry "gocryptfs.longname.wrE-izsR9ciEkP7JSCFDrk_d_Nj4mQo1dGY6hjuixAU=":
// Could not read .name: ReadLongName: size=345 > limit=344
//
entries, err = f.Readdirnames(0)
if err != nil {
t.Fatal(err)
}
f.Close()
cnt2 := len(entries)
if cnt2 != cnt1+1 {
t.Fatalf("len=%d: expected %d dir entries, have %d: %v", len(name), cnt1+1, cnt2, entries)
}
err = syscall.Unlink(wd + name)
if err != nil {
t.Fatal(err)
}
}
if len(name) != 255 {
t.Errorf("maxlen=%d", len(name))
}
}
func TestLongNames(t *testing.T) {
fi, err := ioutil.ReadDir(test_helpers.DefaultCipherDir)
if err != nil {
t.Fatal(err)
}
cnt1 := len(fi)
wd := test_helpers.DefaultPlainDir + "/"
// Create file with long name
n255x := string(bytes.Repeat([]byte("x"), 255))
f, err := os.Create(wd + n255x)
if err != nil {
t.Fatalf("Could not create n255x: %v", err)
}
f.Close()
if !test_helpers.VerifyExistence(wd + n255x) {
t.Errorf("n255x is not in directory listing")
}
// Rename long to long (target does not exist)
n255y := string(bytes.Repeat([]byte("y"), 255))
err = os.Rename(wd+n255x, wd+n255y)
if err != nil {
t.Fatalf("Could not rename n255x to n255y: %v", err)
}
if !test_helpers.VerifyExistence(wd + n255y) {
t.Errorf("n255y is not in directory listing")
}
// Rename long to long (target exists)
f, err = os.Create(wd + n255x)
if err != nil {
t.Fatalf("Could not create n255x: %v", err)
}
f.Close()
err = os.Rename(wd+n255x, wd+n255y)
if err != nil {
t.Fatalf("Could not rename n255x to n255y: %v", err)
}
if !test_helpers.VerifyExistence(wd + n255y) {
t.Errorf("n255y is not in directory listing")
}
// Rename long to short (target does not exist)
err = os.Rename(wd+n255y, wd+"short")
if err != nil {
t.Fatalf("Could not rename n255y to short: %v", err)
}
if !test_helpers.VerifyExistence(wd + "short") {
t.Errorf("short is not in directory listing")
}
// Rename long to short (target exists)
f, err = os.Create(wd + n255y)
if err != nil {
t.Fatalf("Could not create n255y: %v", err)
}
f.Close()
err = os.Rename(wd+n255y, wd+"short")
if err != nil {
t.Fatalf("Could not rename n255y to short: %v", err)
}
if !test_helpers.VerifyExistence(wd + "short") {
t.Errorf("short is not in directory listing")
}
// Rename short to long (target does not exist)
err = os.Rename(wd+"short", wd+n255x)
if err != nil {
t.Fatalf("Could not rename short to n255x: %v", err)
}
if !test_helpers.VerifyExistence(wd + n255x) {
t.Errorf("255x is not in directory listing II")
}
// Rename short to long (target exists)
f, err = os.Create(wd + "short")
if err != nil {
t.Fatalf("Could not create short: %v", err)
}
f.Close()
err = os.Rename(wd+"short", wd+n255x)
if err != nil {
t.Fatalf("Could not rename short to n255x: %v", err)
}
if !test_helpers.VerifyExistence(wd + n255x) {
t.Errorf("n255x is not in directory listing")
}
// Unlink
err = syscall.Unlink(wd + n255x)
if err != nil {
t.Fatalf("Could not unlink n255x: %v", err)
}
if test_helpers.VerifyExistence(wd + n255x) {
t.Errorf("n255x still there after unlink")
}
// Long symlink
n255s := string(bytes.Repeat([]byte("s"), 255))
err = os.Symlink("/", wd+n255s)
if err != nil {
t.Fatal(err)
}
if !test_helpers.VerifyExistence(wd + n255s) {
t.Errorf("n255s is not in directory listing")
}
err = syscall.Unlink(wd + n255s)
if err != nil {
t.Error(err)
}
// Long dir
n255d := string(bytes.Repeat([]byte("d"), 255))
err = os.Mkdir(wd+n255d, 0777)
if err != nil {
t.Fatal(err)
}
err = syscall.Rmdir(wd + n255d)
if err != nil {
t.Error(err)
}
// Check for orphaned files
fi, err = ioutil.ReadDir(test_helpers.DefaultCipherDir)
if err != nil {
t.Fatal(err)
}
cnt2 := len(fi)
if cnt1 != cnt2 {
t.Errorf("Leftover files, cnt1=%d cnt2=%d", cnt1, cnt2)
}
}
// Create hard link with long name.
// This was broken up to v1.2.
func TestLongLink(t *testing.T) {
wd := test_helpers.DefaultPlainDir + "/"
target := wd + "TestLongLink.target"
f, err := os.Create(target)
if err != nil {
t.Fatalf("%v", err)
}
f.Close()
l255 := string(bytes.Repeat([]byte("l"), 255))
err = os.Link(target, wd+l255)
if err != nil {
t.Error(err)
}
}
func TestLchown(t *testing.T) {
name := test_helpers.DefaultPlainDir + "/symlink"
err := os.Symlink("/target/does/not/exist", name)
if err != nil {
t.Fatal(err)
}
err = os.Chown(name, os.Getuid(), os.Getgid())
if err == nil {
t.Error("Chown on dangling symlink should fail")
}
err = os.Lchown(name, os.Getuid(), os.Getgid())
if err != nil {
t.Error(err)
}
}
// Set nanoseconds by path, symlink
func TestUtimesNanoSymlink(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skipf("MacOS \"touch\" does not support \"--no-dereference\"")
}
path := test_helpers.DefaultPlainDir + "/utimesnano_symlink"
err := os.Symlink("/some/nonexisting/file", path)
if err != nil {
t.Fatal(err)
}
// syscall.UtimesNano does not provide a way to pass AT_SYMLINK_NOFOLLOW,
// so we call the external utility "touch", which does.
cmd := exec.Command("touch", "--no-dereference", path)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
err = cmd.Run()
if err != nil {
t.Error(err)
}
}
type utimesTestcaseStruct struct {
// Input atime and mtime
in [2]syscall.Timespec
// Expected output atime and mtime
out [2]syscall.Timespec
}
// compareTimespec return true if the two passed Timespec are identical.
func compareTimespec(want syscall.Timespec, actual syscall.Timespec) bool {
if want.Sec != actual.Sec {
return false
}
if want.Nsec != actual.Nsec {
return false
}
return true
}
const _UTIME_OMIT = ((1 << 30) - 2)
// doTestUtimesNano verifies that setting nanosecond-precision times on "path"
// works correctly. Pass "/proc/self/fd/N" to test a file descriptor.
func doTestUtimesNano(t *testing.T, path string) {
utimeTestcases := []utimesTestcaseStruct{
{
in: [2]syscall.Timespec{{Sec: 50, Nsec: 0}, {Sec: 51, Nsec: 0}},
out: [2]syscall.Timespec{{Sec: 50, Nsec: 0}, {Sec: 51, Nsec: 0}},
},
{
in: [2]syscall.Timespec{{Sec: 1, Nsec: 2}, {Sec: 3, Nsec: 4}},
out: [2]syscall.Timespec{{Sec: 1, Nsec: 2}, {Sec: 3, Nsec: 4}},
},
{
in: [2]syscall.Timespec{{Sec: 7, Nsec: 8}, {Sec: 99, Nsec: _UTIME_OMIT}},
out: [2]syscall.Timespec{{Sec: 7, Nsec: 8}, {Sec: 3, Nsec: 4}},
},
{
in: [2]syscall.Timespec{{Sec: 99, Nsec: _UTIME_OMIT}, {Sec: 5, Nsec: 6}},
out: [2]syscall.Timespec{{Sec: 7, Nsec: 8}, {Sec: 5, Nsec: 6}},
},
}
if runtime.GOOS == "darwin" {
// darwin neither supports UTIME_OMIT nor nanoseconds (!?)
utimeTestcases = utimeTestcases[:1]
}
for i, tc := range utimeTestcases {
err := syscall.UtimesNano(path, tc.in[:])
if err != nil {
t.Fatalf("%q: %v", path, err)
}
var st syscall.Stat_t
err = syscall.Stat(path, &st)
if err != nil {
t.Fatal(err)
}
want := tc.out
have := extractAtimeMtime(st)
if !compareTimespec(want[0], have[0]) {
t.Errorf("Testcase %d: atime: want=%+v, have=%+v", i, want[0], have[0])
}
if !compareTimespec(want[1], have[1]) {
t.Errorf("Testcase %d: mtime: want=%+v, have=%+v", i, want[1], have[1])
}
}
}
// Set nanoseconds by path, normal file
func TestUtimesNano(t *testing.T) {
path := test_helpers.DefaultPlainDir + "/utimesnano"
err := ioutil.WriteFile(path, []byte("foobar"), 0600)
if err != nil {
t.Fatal(err)
}
doTestUtimesNano(t, path)
}
// Set nanoseconds by fd
func TestUtimesNanoFd(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skipf("MacOS does not have /proc")
}
path := test_helpers.DefaultPlainDir + "/utimesnanofd"
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
defer f.Close()
procPath := fmt.Sprintf("/proc/self/fd/%d", f.Fd())
doTestUtimesNano(t, procPath)
}
// Make sure the Mknod call works by creating a fifo (named pipe)
func TestMkfifo(t *testing.T) {
path := test_helpers.DefaultPlainDir + "/fifo1"
err := syscall.Mkfifo(path, 0700)
if err != nil {
t.Fatal(err)
}
path = test_helpers.DefaultPlainDir + "/gocryptfs.longname.XXX"
err = syscall.Mkfifo(path, 0700)
if err != nil {
t.Fatal(err)
}
err = os.Remove(path)
if err != nil {
t.Fatal(err)
}
}
// TestMagicNames verifies that "magic" names are handled correctly
// https://github.com/rfjakob/gocryptfs/issues/174
func TestMagicNames(t *testing.T) {
names := []string{"gocryptfs.longname.QhUr5d9FHerwEs--muUs6_80cy6JRp89c1otLwp92Cs", "gocryptfs.diriv"}
for _, n := range names {
t.Logf("Testing n=%q", n)
p := test_helpers.DefaultPlainDir + "/" + n
// Create file
err := ioutil.WriteFile(p, []byte("xxxxxxx"), 0200)
if err != nil {
t.Fatalf("creating file %q failed: %v", n, err)
}
// Rename magic to normal
err = os.Rename(p, test_helpers.DefaultPlainDir+"/x")
if err != nil {
t.Fatalf("rename 1 failed: %v", err)
}
// Rename normal to magic
err = os.Rename(test_helpers.DefaultPlainDir+"/x", p)
if err != nil {
t.Fatalf("rename 2 failed: %v", err)
}
// Unlink
err = syscall.Unlink(p)
if err != nil {
t.Fatal(err)
}
// Mkdir
err = os.Mkdir(p, 0700)
if err != nil {
t.Fatal(err)
}
// Rmdir
err = syscall.Rmdir(p)
if err != nil {
t.Fatal(err)
}
// Symlink
err = syscall.Symlink("xxxyyyyzzz", p)
if err != nil {
t.Fatal(err)
}
syscall.Unlink(p)
// Link
target := test_helpers.DefaultPlainDir + "/linktarget"
err = ioutil.WriteFile(target, []byte("yyyyy"), 0200)
if err != nil {
t.Fatal(err)
}
err = syscall.Link(target, p)
if err != nil {
t.Fatal(err)
}
}
}
// Test that chmod works correctly
func TestChmod(t *testing.T) {
path := test_helpers.DefaultPlainDir + "/" + t.Name()
file, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
file.Close()
modes := []os.FileMode{0777, 0707, 0606, 0666, 0444, 0000, 0111, 0123, 0321}
for _, modeWant := range modes {
fi, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
err = syscall.Chmod(path, uint32(modeWant))
if err != nil {
t.Errorf("chmod %03o -> %03o failed: %v", fi.Mode(), modeWant, err)
continue
}
fi, err = os.Stat(path)
if err != nil {
t.Fatal(err)
}
modeHave := fi.Mode()
if modeHave != modeWant {
t.Errorf("modeHave %#o != modeWant %#o", modeHave, modeWant)
}
}
}
// Test that access(2) works correctly
func TestAccess(t *testing.T) {
path := test_helpers.DefaultPlainDir + "/" + t.Name()
file, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
defer file.Close()
err = unix.Access(path, unix.F_OK)
if err != nil {
t.Error(err)
}
err = unix.Access(path, unix.R_OK)
if err != nil {
t.Error(err)
}
err = unix.Access(path, unix.X_OK)
if err == nil {
t.Error("X_OK should have failed")
}
}