This forces all syscall functions, notably filesystem, to return numeric codes as opposed to mapping in two different areas. The result of this change is better consolidation in call sites of `sysfs.FS`, while further refactoring is needed to address consolidation of file errors. Signed-off-by: Adrian Cole <adrian@tetrate.io>
246 lines
5.8 KiB
Go
246 lines
5.8 KiB
Go
package platform
|
|
|
|
import (
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"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, syscall.ENOENT, err)
|
|
|
|
err = Utimens("nope", nil, false)
|
|
if SupportsSymlinkNoFollow {
|
|
require.EqualErrno(t, syscall.ENOENT, err)
|
|
} else {
|
|
require.EqualErrno(t, syscall.ENOSYS, err)
|
|
}
|
|
})
|
|
testFutimens(t, true)
|
|
}
|
|
|
|
func testFutimens(t *testing.T, usePath 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 !usePath && 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.Zero(t, errno)
|
|
|
|
if usePath {
|
|
err = Utimens(path, tc.times, !symlinkNoFollow)
|
|
if symlinkNoFollow && !SupportsSymlinkNoFollow {
|
|
require.EqualErrno(t, syscall.ENOSYS, err)
|
|
return
|
|
}
|
|
require.Zero(t, err)
|
|
} 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, errno := OpenFile(path, flag, 0)
|
|
require.Zero(t, errno)
|
|
|
|
errno = UtimensFile(f, tc.times)
|
|
require.NoError(t, f.Close())
|
|
require.Zero(t, errno)
|
|
}
|
|
|
|
newSt, errno := Lstat(statPath)
|
|
require.Zero(t, errno)
|
|
|
|
if 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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUtimensFile(t *testing.T) {
|
|
switch runtime.GOOS {
|
|
case "linux", "darwin": // supported
|
|
case "freebsd": // TODO: support freebsd w/o CGO
|
|
case "windows":
|
|
if !IsGo120 {
|
|
t.Skip("windows only works after Go 1.20") // TODO: possibly 1.19 ;)
|
|
}
|
|
default: // expect ENOSYS and callers need to fall back to Utimens
|
|
t.Skip("unsupported GOOS", runtime.GOOS)
|
|
}
|
|
|
|
testFutimens(t, false)
|
|
|
|
t.Run("closed file", func(t *testing.T) {
|
|
file := path.Join(t.TempDir(), "file")
|
|
err := os.WriteFile(file, []byte{}, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
fileF, errno := OpenFile(file, syscall.O_RDWR, 0)
|
|
require.Zero(t, errno)
|
|
require.NoError(t, fileF.Close())
|
|
|
|
errno = UtimensFile(fileF, nil)
|
|
require.EqualErrno(t, syscall.EBADF, errno)
|
|
})
|
|
|
|
t.Run("closed dir", func(t *testing.T) {
|
|
dir := path.Join(t.TempDir(), "dir")
|
|
err := os.Mkdir(dir, 0o700)
|
|
require.NoError(t, err)
|
|
|
|
dirF, errno := OpenFile(dir, syscall.O_RDONLY, 0)
|
|
require.Zero(t, errno)
|
|
require.NoError(t, dirF.Close())
|
|
|
|
err = UtimensFile(dirF, nil)
|
|
require.EqualErrno(t, syscall.EBADF, err)
|
|
})
|
|
}
|