libgocryptfs/tests/matrix/matrix_test.go
Sebastian Lackner 9f56b33e0c fusefrontend: Fix longname handling for renames with existing target
Fixes https://github.com/rfjakob/gocryptfs/issues/170

Steps to reproduce the problem:

* Create a regular forward mount point
* Create a file with a shortname and one with a long filename
* Try to run 'mv <shortname> <longname>'

This should actually work and replace the existing file, but instead it
fails with:

    mv: cannot move '<shortname>' to '<longname>': File exists

The problem is the creation of the .name file. If the target already exists
we can safely ignore the EEXIST error and just keep the existing .name file.
2017-11-25 16:19:09 +01:00

784 lines
21 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"
"os"
"os/exec"
"runtime"
"sync"
"syscall"
"testing"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"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
}
var matrix = []testcaseMatrix{
// Normal
{false, "auto", false, false},
{false, "true", false, false},
{false, "false", false, false},
// Plaintextnames
{true, "true", false, false},
{true, "false", false, false},
// AES-SIV (does not use openssl, no need to test permutations)
{false, "auto", true, false},
{true, "auto", true, false},
// Raw64
{false, "auto", false, true},
}
// 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 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))
test_helpers.MountOrExit(test_helpers.DefaultCipherDir, test_helpers.DefaultPlainDir, opts...)
r := m.Run()
test_helpers.UnmountPanic(test_helpers.DefaultPlainDir)
if r != 0 {
os.Exit(r)
}
}
os.Exit(0)
}
// Write "n" zero 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)
written, err := file.Write(d)
if err != nil || written != len(d) {
t.Errorf("err=\"%s\", written=%d", err, written)
}
err = file.Close()
if err != nil {
t.Error(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 + "/100")
if hashActual != hashWant {
fmt.Printf("Read corruption in loop #%d\n", i)
t.FailNow()
} else {
//fmt.Print(".")
}
}
}
// 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()
}
// 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)
}
}
const (
// From man statfs
TMPFS_MAGIC = 0x01021994
EXT4_SUPER_MAGIC = 0xef53
)
// isWellKnownFS decides if the backing filesystem is well-known.
// The expected allocated sizes are only valid on tmpfs and ext4. btrfs
// gives different results, but that's not an error.
func isWellKnownFS(fn string) bool {
var fs syscall.Statfs_t
err := syscall.Statfs(fn, &fs)
if err != nil {
panic(err)
}
if fs.Type == EXT4_SUPER_MAGIC || fs.Type == TMPFS_MAGIC {
return true
}
return false
}
const FALLOC_DEFAULT = 0x00
const FALLOC_FL_KEEP_SIZE = 0x01
func TestFallocate(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skipf("OSX does not support fallocate")
}
fn := test_helpers.DefaultPlainDir + "/fallocate"
file, err := os.Create(fn)
if err != nil {
t.FailNow()
}
wellKnown := isWellKnownFS(test_helpers.DefaultCipherDir)
fd := int(file.Fd())
nBytes := test_helpers.Du(t, fd)
if nBytes != 0 {
t.Fatalf("Empty file has %d bytes", nBytes)
}
// Allocate 30 bytes, keep size
// gocryptfs || (0 blocks)
// ext4 | d | (1 block)
err = syscallcompat.Fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 30)
if err != nil {
t.Error(err)
}
var want int64
nBytes = test_helpers.Du(t, fd)
want = 4096
if nBytes != want {
t.Errorf("Expected %d allocated bytes, have %d", want, nBytes)
}
test_helpers.VerifySize(t, fn, 0)
// Three ciphertext blocks. The middle one should be a file hole.
// gocryptfs | h | h | d| (1 block)
// ext4 | d | h | d | (2 blocks)
// (Note that gocryptfs blocks are slightly bigger than the ext4 blocks,
// but the last one is partial)
err = file.Truncate(9000)
if err != nil {
t.Fatal(err)
}
nBytes = test_helpers.Du(t, fd)
want = 2 * 4096
if wellKnown && nBytes != want {
t.Errorf("Expected %d allocated bytes, have %d", want, nBytes)
}
if md5 := test_helpers.Md5fn(fn); md5 != "5420afa22f6423a9f59e669540656bb4" {
t.Errorf("Wrong md5 %s", md5)
}
// Allocate the whole file space
// gocryptfs | h | h | d| (1 block)
// ext4 | d | d | d | (3 blocks
err = syscallcompat.Fallocate(fd, FALLOC_DEFAULT, 0, 9000)
if err != nil {
t.Fatal(err)
}
nBytes = test_helpers.Du(t, fd)
want = 3 * 4096
if nBytes != want {
t.Errorf("Expected %d allocated bytes, have %d", want, nBytes)
}
// Neither apparent size nor content should have changed
test_helpers.VerifySize(t, fn, 9000)
if md5 := test_helpers.Md5fn(fn); md5 != "5420afa22f6423a9f59e669540656bb4" {
t.Errorf("Wrong md5 %s", md5)
}
// Partial block on the end. The first ext4 block is dirtied by the header.
// gocryptfs | h | h | d| (1 block)
// ext4 | d | h | d | (2 blocks)
file.Truncate(0)
file.Truncate(9000)
nBytes = test_helpers.Du(t, fd)
want = 2 * 4096
if wellKnown && nBytes != want {
t.Errorf("Expected %d allocated bytes, have %d", want, nBytes)
}
// Allocate 10 bytes in the second block
// gocryptfs | h | h | d| (1 block)
// ext4 | d | d | d | (3 blocks)
syscallcompat.Fallocate(fd, FALLOC_DEFAULT, 5000, 10)
nBytes = test_helpers.Du(t, fd)
want = 3 * 4096
if wellKnown && nBytes != want {
t.Errorf("Expected %d allocated bytes, have %d", want, nBytes)
}
// Neither apparent size nor content should have changed
test_helpers.VerifySize(t, fn, 9000)
if md5 := test_helpers.Md5fn(fn); md5 != "5420afa22f6423a9f59e669540656bb4" {
t.Errorf("Wrong md5 %s", md5)
}
// Grow the file to 4 blocks
// gocryptfs | h | h | d |d| (2 blocks)
// ext4 | d | d | d | d | (4 blocks)
syscallcompat.Fallocate(fd, FALLOC_DEFAULT, 15000, 10)
nBytes = test_helpers.Du(t, fd)
want = 4 * 4096
if wellKnown && nBytes != want {
t.Errorf("Expected %d allocated bytes, have %d", want, nBytes)
}
test_helpers.VerifySize(t, fn, 15010)
if md5 := test_helpers.Md5fn(fn); md5 != "c4c44c7a41ab7798a79d093eb44f99fc" {
t.Errorf("Wrong md5 %s", md5)
}
// Shrinking a file using fallocate should have no effect
for _, off := range []int64{0, 10, 2000, 5000} {
for _, sz := range []int64{0, 1, 42, 6000} {
syscallcompat.Fallocate(fd, FALLOC_DEFAULT, off, sz)
test_helpers.VerifySize(t, fn, 15010)
if md5 := test_helpers.Md5fn(fn); md5 != "c4c44c7a41ab7798a79d093eb44f99fc" {
t.Errorf("Wrong md5 %s", md5)
}
}
}
// Cleanup
file.Close()
syscall.Unlink(fn)
if !wellKnown {
// Even though most tests have been executed still, inform the user
// that some were disabled
t.Skipf("backing fs is not ext4 or tmpfs, skipped some disk-usage checks\n")
}
}
func TestAppend(t *testing.T) {
fn := test_helpers.DefaultPlainDir + "/append"
file, err := os.Create(fn)
if err != nil {
t.FailNow()
}
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")
}
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")
}
f2, err := os.Create(fn)
if err != nil {
t.Fatalf("file create failed")
}
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 Mkdir and Rmdir
func testMkdirRmdir(t *testing.T) {
test_helpers.TestMkdirRmdir(t, test_helpers.DefaultPlainDir)
}
// Test Rename
func testRename(t *testing.T) {
test_helpers.TestRename(t, test_helpers.DefaultPlainDir)
}
// Overwrite an empty directory with another directory
func TestDirOverwrite(t *testing.T) {
dir1 := test_helpers.DefaultPlainDir + "/DirOverwrite1"
dir2 := test_helpers.DefaultPlainDir + "/DirOverwrite2"
err := os.Mkdir(dir1, 0777)
if err != nil {
t.Fatal(err)
}
err = os.Mkdir(dir2, 0777)
if err != nil {
t.Fatal(err)
}
err = syscall.Rename(dir1, dir2)
if err != nil {
t.Fatal(err)
}
}
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) {
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
}
func compareUtimes(want [2]syscall.Timespec, actual [2]syscall.Timespec) error {
tsNames := []string{"atime", "mtime"}
for i := range want {
if want[i].Sec != actual[i].Sec {
return fmt.Errorf("Wrong %s seconds: want=%d actual=%d", tsNames[i], want[i].Sec, actual[i].Sec)
}
if want[i].Nsec != actual[i].Nsec {
return fmt.Errorf("Wrong %s nanoseconds: want=%d actual=%d", tsNames[i], want[i].Nsec, actual[i].Nsec)
}
}
return nil
}
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: 50, Nsec: 0}},
out: [2]syscall.Timespec{{Sec: 50, Nsec: 0}, {Sec: 50, 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}},
},
}
for i, tc := range utimeTestcases {
err := syscall.UtimesNano(path, tc.in[:])
if err != nil {
t.Fatal(err)
}
var st syscall.Stat_t
err = syscall.Stat(path, &st)
if err != nil {
t.Fatal(err)
}
err = compareUtimes(tc.out, extractAtimeMtime(st))
if err != nil {
t.Errorf("Testcase %d: %v", i, err)
}
}
}
// 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) {
path := test_helpers.DefaultPlainDir + "/utimesnanofd"
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
}
procPath := fmt.Sprintf("/proc/self/fd/%d", f.Fd())
doTestUtimesNano(t, procPath)
}