206 lines
4.8 KiB
Go
206 lines
4.8 KiB
Go
package sysfs
|
|
|
|
import (
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tetratelabs/wazero/experimental/sys"
|
|
"github.com/tetratelabs/wazero/internal/platform"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
)
|
|
|
|
func TestUtimens(t *testing.T) {
|
|
t.Run("doesn't exist", func(t *testing.T) {
|
|
err := Utimens("nope", nil, true)
|
|
require.EqualErrno(t, sys.ENOENT, err)
|
|
|
|
err = Utimens("nope", nil, false)
|
|
if SupportsSymlinkNoFollow {
|
|
require.EqualErrno(t, sys.ENOENT, err)
|
|
} else {
|
|
require.EqualErrno(t, sys.ENOSYS, err)
|
|
}
|
|
})
|
|
testUtimens(t, false)
|
|
}
|
|
|
|
func testUtimens(t *testing.T, futimes bool) {
|
|
// Note: This sets microsecond granularity because Windows doesn't support
|
|
// nanosecond.
|
|
//
|
|
// Negative isn't tested as most platforms don't return consistent results.
|
|
tests := []struct {
|
|
name string
|
|
times *[2]syscall.Timespec
|
|
}{
|
|
{
|
|
name: "nil",
|
|
},
|
|
{
|
|
name: "a=omit,m=omit",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: UTIME_OMIT},
|
|
{Sec: 123, Nsec: UTIME_OMIT},
|
|
},
|
|
},
|
|
{
|
|
name: "a=now,m=omit",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: UTIME_NOW},
|
|
{Sec: 123, Nsec: UTIME_OMIT},
|
|
},
|
|
},
|
|
{
|
|
name: "a=omit,m=now",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: UTIME_OMIT},
|
|
{Sec: 123, Nsec: UTIME_NOW},
|
|
},
|
|
},
|
|
{
|
|
name: "a=now,m=now",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: UTIME_NOW},
|
|
{Sec: 123, Nsec: UTIME_NOW},
|
|
},
|
|
},
|
|
{
|
|
name: "a=now,m=set",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: UTIME_NOW},
|
|
{Sec: 123, Nsec: 4 * 1e3},
|
|
},
|
|
},
|
|
{
|
|
name: "a=set,m=now",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: 4 * 1e3},
|
|
{Sec: 123, Nsec: UTIME_NOW},
|
|
},
|
|
},
|
|
{
|
|
name: "a=set,m=omit",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: 4 * 1e3},
|
|
{Sec: 123, Nsec: UTIME_OMIT},
|
|
},
|
|
},
|
|
{
|
|
name: "a=omit,m=set",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: UTIME_OMIT},
|
|
{Sec: 123, Nsec: 4 * 1e3},
|
|
},
|
|
},
|
|
{
|
|
name: "a=set,m=set",
|
|
times: &[2]syscall.Timespec{
|
|
{Sec: 123, Nsec: 4 * 1e3},
|
|
{Sec: 223, Nsec: 5 * 1e3},
|
|
},
|
|
},
|
|
}
|
|
for _, fileType := range []string{"dir", "file", "link", "link-follow"} {
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
fileType := fileType
|
|
name := fileType + " " + tc.name
|
|
symlinkNoFollow := fileType == "link"
|
|
|
|
// symlinkNoFollow is invalid for file descriptor based operations,
|
|
// because the default for open is to follow links. You can't avoid
|
|
// this. O_NOFOLLOW is used only to return ELOOP on a link.
|
|
if futimes && symlinkNoFollow {
|
|
continue
|
|
}
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
file := path.Join(tmpDir, "file")
|
|
err := os.WriteFile(file, []byte{}, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
link := file + "-link"
|
|
require.NoError(t, os.Symlink(file, link))
|
|
|
|
dir := path.Join(tmpDir, "dir")
|
|
err = os.Mkdir(dir, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
var path, statPath string
|
|
switch fileType {
|
|
case "dir":
|
|
path = dir
|
|
statPath = dir
|
|
case "file":
|
|
path = file
|
|
statPath = file
|
|
case "link":
|
|
path = link
|
|
statPath = link
|
|
case "link-follow":
|
|
path = link
|
|
statPath = file
|
|
default:
|
|
panic(tc)
|
|
}
|
|
|
|
oldSt, errno := lstat(statPath)
|
|
require.EqualErrno(t, 0, errno)
|
|
|
|
if !futimes {
|
|
err = Utimens(path, tc.times, !symlinkNoFollow)
|
|
if symlinkNoFollow && !SupportsSymlinkNoFollow {
|
|
require.EqualErrno(t, sys.ENOSYS, err)
|
|
return
|
|
}
|
|
require.EqualErrno(t, 0, errno)
|
|
} else {
|
|
flag := syscall.O_RDWR
|
|
if path == dir {
|
|
flag = syscall.O_RDONLY
|
|
if runtime.GOOS == "windows" {
|
|
// windows requires O_RDWR, which is invalid for directories
|
|
t.Skip("windows cannot update timestamps on a dir")
|
|
}
|
|
}
|
|
|
|
f := requireOpenFile(t, path, flag, 0)
|
|
|
|
errno = f.Utimens(tc.times)
|
|
require.EqualErrno(t, 0, f.Close())
|
|
require.EqualErrno(t, 0, errno)
|
|
}
|
|
|
|
newSt, errno := lstat(statPath)
|
|
require.EqualErrno(t, 0, errno)
|
|
|
|
if platform.CompilerSupported() {
|
|
if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT {
|
|
require.Equal(t, oldSt.Atim, newSt.Atim)
|
|
} else if tc.times == nil || tc.times[0].Nsec == UTIME_NOW {
|
|
now := time.Now().UnixNano()
|
|
require.True(t, newSt.Atim <= now, "expected atim %d <= now %d", newSt.Atim, now)
|
|
} else {
|
|
require.Equal(t, tc.times[0].Nano(), newSt.Atim)
|
|
}
|
|
}
|
|
|
|
// When compiler isn't supported, we can still check mtim.
|
|
if tc.times != nil && tc.times[1].Nsec == UTIME_OMIT {
|
|
require.Equal(t, oldSt.Mtim, newSt.Mtim)
|
|
} else if tc.times == nil || tc.times[1].Nsec == UTIME_NOW {
|
|
now := time.Now().UnixNano()
|
|
require.True(t, newSt.Mtim <= now, "expected mtim %d <= now %d", newSt.Mtim, now)
|
|
} else {
|
|
require.Equal(t, tc.times[1].Nano(), newSt.Mtim)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|