sysfs: decouples FS and File from the syscall package (#1602)
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
socketapi "github.com/tetratelabs/wazero/internal/sock"
|
||||
"github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/sysfs"
|
||||
"github.com/tetratelabs/wazero/internal/wasip1"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
sysapi "github.com/tetratelabs/wazero/sys"
|
||||
@@ -503,50 +502,55 @@ func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) ex
|
||||
return experimentalsys.EBADF
|
||||
}
|
||||
|
||||
times, errno := toTimes(atim, mtim, fstFlags)
|
||||
atim, mtim, errno := toTimes(sys.WalltimeNanos, atim, mtim, fstFlags)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
|
||||
// Try to update the file timestamps by file-descriptor.
|
||||
errno = f.File.Utimens(×)
|
||||
errno = f.File.Utimens(atim, mtim)
|
||||
|
||||
// Fall back to path based, despite it being less precise.
|
||||
switch errno {
|
||||
case experimentalsys.EPERM, experimentalsys.ENOSYS:
|
||||
errno = f.FS.Utimens(f.Name, ×)
|
||||
errno = f.FS.Utimens(f.Name, atim, mtim)
|
||||
}
|
||||
|
||||
return errno
|
||||
}
|
||||
|
||||
func toTimes(atim, mtime int64, fstFlags uint16) (times [2]syscall.Timespec, errno experimentalsys.Errno) {
|
||||
func toTimes(walltime func() int64, atim, mtim int64, fstFlags uint16) (int64, int64, experimentalsys.Errno) {
|
||||
// times[0] == atim, times[1] == mtim
|
||||
|
||||
var nowTim int64
|
||||
|
||||
// coerce atim into a timespec
|
||||
if set, now := fstFlags&wasip1.FstflagsAtim != 0, fstFlags&wasip1.FstflagsAtimNow != 0; set && now {
|
||||
errno = experimentalsys.EINVAL
|
||||
return
|
||||
return 0, 0, experimentalsys.EINVAL
|
||||
} else if set {
|
||||
times[0] = syscall.NsecToTimespec(atim)
|
||||
// atim is already correct
|
||||
} else if now {
|
||||
times[0].Nsec = sysfs.UTIME_NOW
|
||||
nowTim = walltime()
|
||||
atim = nowTim
|
||||
} else {
|
||||
times[0].Nsec = sysfs.UTIME_OMIT
|
||||
atim = fsapi.UTIME_OMIT
|
||||
}
|
||||
|
||||
// coerce mtim into a timespec
|
||||
if set, now := fstFlags&wasip1.FstflagsMtim != 0, fstFlags&wasip1.FstflagsMtimNow != 0; set && now {
|
||||
errno = experimentalsys.EINVAL
|
||||
return
|
||||
return 0, 0, experimentalsys.EINVAL
|
||||
} else if set {
|
||||
times[1] = syscall.NsecToTimespec(mtime)
|
||||
// mtim is already correct
|
||||
} else if now {
|
||||
times[1].Nsec = sysfs.UTIME_NOW
|
||||
if nowTim != 0 {
|
||||
mtim = nowTim
|
||||
} else {
|
||||
mtim = walltime()
|
||||
}
|
||||
} else {
|
||||
times[1].Nsec = sysfs.UTIME_OMIT
|
||||
mtim = fsapi.UTIME_OMIT
|
||||
}
|
||||
return
|
||||
return atim, mtim, 0
|
||||
}
|
||||
|
||||
// fdPread is the WASI function named FdPreadName which reads from a file
|
||||
@@ -1445,7 +1449,7 @@ func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64)
|
||||
sys := mod.(*wasm.ModuleInstance).Sys
|
||||
fsc := sys.FS()
|
||||
|
||||
times, errno := toTimes(atim, mtim, fstFlags)
|
||||
atim, mtim, errno := toTimes(sys.WalltimeNanos, atim, mtim, fstFlags)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
@@ -1457,14 +1461,14 @@ func pathFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64)
|
||||
|
||||
symlinkFollow := flags&wasip1.LOOKUP_SYMLINK_FOLLOW != 0
|
||||
if symlinkFollow {
|
||||
return preopen.Utimens(pathName, ×)
|
||||
return preopen.Utimens(pathName, atim, mtim)
|
||||
}
|
||||
// Otherwise, we need to emulate don't follow by opening the file by path.
|
||||
if f, errno := preopen.OpenFile(pathName, syscall.O_WRONLY, 0); errno != 0 {
|
||||
return errno
|
||||
} else {
|
||||
defer f.Close()
|
||||
return f.Utimens(×)
|
||||
return f.Utimens(atim, mtim)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package fsapi
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
@@ -346,10 +344,9 @@ type File interface {
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// The `times` parameter includes the access and modification timestamps to
|
||||
// assign. Special syscall.Timespec NSec values UTIME_NOW and UTIME_OMIT may be
|
||||
// specified instead of real timestamps. A nil `times` parameter behaves the
|
||||
// same as if both were set to UTIME_NOW.
|
||||
// The `atim` and `mtim` parameters refer to access and modification time
|
||||
// stamps as defined in sys.Stat_t. To retain one or the other, substitute
|
||||
// it with the pseudo-timestamp UTIME_OMIT.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
@@ -363,7 +360,7 @@ type File interface {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||
// - Windows requires files to be open with fsapi.O_RDWR, which means you
|
||||
// cannot use this to update timestamps on a directory (EPERM).
|
||||
Utimens(times *[2]syscall.Timespec) experimentalsys.Errno
|
||||
Utimens(atim, mtim int64) experimentalsys.Errno
|
||||
|
||||
// Close closes the underlying file.
|
||||
//
|
||||
|
||||
@@ -2,7 +2,6 @@ package fsapi
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
@@ -272,11 +271,12 @@ type FS interface {
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// The `times` parameter includes the access and modification timestamps to
|
||||
// assign. Special syscall.Timespec NSec values UTIME_NOW and UTIME_OMIT
|
||||
// may be specified instead of real timestamps. A nil `times` parameter
|
||||
// behaves the same as if both were set to UTIME_NOW. If the path is a
|
||||
// symbolic link, the target of expanding that link is updated.
|
||||
// If the path is a symbolic link, the target of expanding that link is
|
||||
// updated.
|
||||
//
|
||||
// The `atim` and `mtim` parameters refer to access and modification time
|
||||
// stamps as defined in sys.Stat_t. To retain one or the other, substitute
|
||||
// it with the pseudo-timestamp UTIME_OMIT.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
@@ -290,7 +290,5 @@ type FS interface {
|
||||
//
|
||||
// - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in
|
||||
// POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||
Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno
|
||||
// TODO: change impl to not use syscall package,
|
||||
// possibly by being just a pair of int64s..
|
||||
Utimens(path string, atim, mtim int64) experimentalsys.Errno
|
||||
}
|
||||
|
||||
10
internal/fsapi/time.go
Normal file
10
internal/fsapi/time.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package fsapi
|
||||
|
||||
import "math"
|
||||
|
||||
// UTIME_OMIT is a special constant for use in updating times via FS.Utimens
|
||||
// or File.Utimens. When used for atim or mtim, the value is retained.
|
||||
//
|
||||
// Note: This may be implemented via a stat when the underlying filesystem
|
||||
// does not support this value.
|
||||
const UTIME_OMIT int64 = math.MinInt64
|
||||
@@ -2,7 +2,6 @@ package fsapi
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
@@ -78,7 +77,7 @@ func (UnimplementedFS) Unlink(path string) experimentalsys.Errno {
|
||||
}
|
||||
|
||||
// Utimens implements FS.Utimens
|
||||
func (UnimplementedFS) Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
func (UnimplementedFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
@@ -184,7 +183,7 @@ func (UnimplementedFile) Datasync() experimentalsys.Errno {
|
||||
}
|
||||
|
||||
// Utimens implements File.Utimens
|
||||
func (UnimplementedFile) Utimens(*[2]syscall.Timespec) experimentalsys.Errno {
|
||||
func (UnimplementedFile) Utimens(int64, int64) experimentalsys.Errno {
|
||||
return experimentalsys.ENOSYS
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package gojs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
@@ -432,10 +431,7 @@ func (u *jsfsUtimes) invoke(ctx context.Context, mod api.Module, args ...interfa
|
||||
callback := args[3].(funcWrapper)
|
||||
|
||||
fsc := mod.(*wasm.ModuleInstance).Sys.FS()
|
||||
times := [2]syscall.Timespec{
|
||||
syscall.NsecToTimespec(atimeSec * 1e9), syscall.NsecToTimespec(mtimeSec * 1e9),
|
||||
}
|
||||
errno := fsc.RootFS().Utimens(path, ×)
|
||||
errno := fsc.RootFS().Utimens(path, atimeSec*1e9, mtimeSec*1e9)
|
||||
|
||||
return jsfsInvoke(ctx, mod, callback, errno)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
@@ -103,11 +101,11 @@ func (r *lazyDir) Datasync() experimentalsys.Errno {
|
||||
}
|
||||
|
||||
// Utimens implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Utimens(times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
func (r *lazyDir) Utimens(atim, mtim int64) experimentalsys.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return experimentalsys.EBADF
|
||||
} else {
|
||||
return f.Utimens(times)
|
||||
return f.Utimens(atim, mtim)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ func TestAdapt_UtimesNano(t *testing.T) {
|
||||
realPath := joinPath(tmpDir, path)
|
||||
require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
|
||||
|
||||
err := testFS.Utimens(path, nil)
|
||||
err := testFS.Utimens(path, fsapi.UTIME_OMIT, fsapi.UTIME_OMIT)
|
||||
require.EqualErrno(t, experimentalsys.ENOSYS, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
@@ -19,14 +19,12 @@ func BenchmarkFsFileUtimesNs(b *testing.B) {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
times := &[2]syscall.Timespec{
|
||||
{Sec: 123, Nsec: 4 * 1e3},
|
||||
{Sec: 123, Nsec: 4 * 1e3},
|
||||
}
|
||||
atim := int64(123*time.Second + 4*time.Microsecond)
|
||||
mtim := atim
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if errno := f.Utimens(times); errno != 0 {
|
||||
if errno := f.Utimens(atim, mtim); errno != 0 {
|
||||
b.Fatal(errno)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,8 +116,8 @@ func (d *dirFS) Symlink(oldName, link string) experimentalsys.Errno {
|
||||
}
|
||||
|
||||
// Utimens implements the same method as documented on fsapi.FS
|
||||
func (d *dirFS) Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
return Utimens(d.join(path), times)
|
||||
func (d *dirFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno {
|
||||
return utimens(d.join(path), atim, mtim)
|
||||
}
|
||||
|
||||
func (d *dirFS) join(path string) string {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -531,7 +530,7 @@ func TestDirFS_Utimesns(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("doesn't exist", func(t *testing.T) {
|
||||
err := testFS.Utimens("nope", nil)
|
||||
err := testFS.Utimens("nope", 0, 0)
|
||||
require.EqualErrno(t, sys.ENOENT, err)
|
||||
})
|
||||
|
||||
@@ -540,60 +539,31 @@ func TestDirFS_Utimesns(t *testing.T) {
|
||||
//
|
||||
// Negative isn't tested as most platforms don't return consistent results.
|
||||
tests := []struct {
|
||||
name string
|
||||
times *[2]syscall.Timespec
|
||||
name string
|
||||
atim, mtim int64
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
},
|
||||
{
|
||||
name: "a=omit,m=omit",
|
||||
times: &[2]syscall.Timespec{
|
||||
{Sec: 123, Nsec: UTIME_OMIT},
|
||||
{Sec: 123, Nsec: UTIME_OMIT},
|
||||
},
|
||||
atim: fsapi.UTIME_OMIT,
|
||||
mtim: fsapi.UTIME_OMIT,
|
||||
},
|
||||
{
|
||||
name: "a=now,m=omit",
|
||||
times: &[2]syscall.Timespec{
|
||||
{Sec: 123, Nsec: UTIME_NOW},
|
||||
{Sec: 123, Nsec: UTIME_OMIT},
|
||||
},
|
||||
name: "a=set,m=omit",
|
||||
atim: int64(123*time.Second + 4*time.Microsecond),
|
||||
mtim: fsapi.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=omit,m=set",
|
||||
atim: fsapi.UTIME_OMIT,
|
||||
mtim: int64(123*time.Second + 4*time.Microsecond),
|
||||
},
|
||||
{
|
||||
name: "a=set,m=set",
|
||||
times: &[2]syscall.Timespec{
|
||||
{Sec: 123, Nsec: 4 * 1e3},
|
||||
{Sec: 223, Nsec: 5 * 1e3},
|
||||
},
|
||||
atim: int64(123*time.Second + 4*time.Microsecond),
|
||||
mtim: int64(223*time.Second + 5*time.Microsecond),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -636,31 +606,25 @@ func TestDirFS_Utimesns(t *testing.T) {
|
||||
oldSt, errno := testFS.Lstat(statPath)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
|
||||
errno = testFS.Utimens(path, tc.times)
|
||||
errno = testFS.Utimens(path, tc.atim, tc.mtim)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
|
||||
newSt, errno := testFS.Lstat(statPath)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
|
||||
if platform.CompilerSupported() {
|
||||
if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT {
|
||||
if tc.atim == fsapi.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)
|
||||
require.Equal(t, tc.atim, newSt.Atim)
|
||||
}
|
||||
}
|
||||
|
||||
// When compiler isn't supported, we can still check mtim.
|
||||
if tc.times != nil && tc.times[1].Nsec == UTIME_OMIT {
|
||||
if tc.mtim == fsapi.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)
|
||||
require.Equal(t, tc.mtim, newSt.Mtim)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
@@ -465,3 +466,40 @@ func pwrite(w io.WriterAt, buf []byte, off int64) (n int, errno experimentalsys.
|
||||
n, err := w.WriteAt(buf, off)
|
||||
return n, experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func chtimes(path string, atim, mtim int64) (errno experimentalsys.Errno) { //nolint:unused
|
||||
// When both inputs are omitted, there is nothing to change.
|
||||
if atim == fsapi.UTIME_OMIT && mtim == fsapi.UTIME_OMIT {
|
||||
return
|
||||
}
|
||||
|
||||
// UTIME_OMIT is expensive until progress is made in Go, as it requires a
|
||||
// stat to read-back the value to re-apply.
|
||||
// - https://github.com/golang/go/issues/32558.
|
||||
// - https://go-review.googlesource.com/c/go/+/219638 (unmerged)
|
||||
var st sys.Stat_t
|
||||
if atim == fsapi.UTIME_OMIT || mtim == fsapi.UTIME_OMIT {
|
||||
if st, errno = stat(path); errno != 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var atime, mtime time.Time
|
||||
if atim == fsapi.UTIME_OMIT {
|
||||
atime = epochNanosToTime(st.Atim)
|
||||
mtime = epochNanosToTime(mtim)
|
||||
} else if mtim == fsapi.UTIME_OMIT {
|
||||
atime = epochNanosToTime(atim)
|
||||
mtime = epochNanosToTime(st.Mtim)
|
||||
} else {
|
||||
atime = epochNanosToTime(atim)
|
||||
mtime = epochNanosToTime(mtim)
|
||||
}
|
||||
return experimentalsys.UnwrapOSError(os.Chtimes(path, atime, mtime))
|
||||
}
|
||||
|
||||
func epochNanosToTime(epochNanos int64) time.Time { //nolint:unused
|
||||
seconds := epochNanos / 1e9
|
||||
nanos := epochNanos % 1e9
|
||||
return time.Unix(seconds, nanos)
|
||||
}
|
||||
|
||||
@@ -986,10 +986,10 @@ func TestFileUtimens(t *testing.T) {
|
||||
testUtimens(t, true)
|
||||
|
||||
testEBADFIfFileClosed(t, func(f fsapi.File) experimentalsys.Errno {
|
||||
return f.Utimens(nil)
|
||||
return f.Utimens(fsapi.UTIME_OMIT, fsapi.UTIME_OMIT)
|
||||
})
|
||||
testEBADFIfDirClosed(t, func(d fsapi.File) experimentalsys.Errno {
|
||||
return d.Utimens(nil)
|
||||
return d.Utimens(fsapi.UTIME_OMIT, fsapi.UTIME_OMIT)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,53 +1,14 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package sysfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
)
|
||||
|
||||
const (
|
||||
// UTIME_NOW is a special syscall.Timespec NSec value used to set the
|
||||
// file's timestamp to a value close to, but not greater than the current
|
||||
// system time.
|
||||
UTIME_NOW = _UTIME_NOW
|
||||
|
||||
// UTIME_OMIT is a special syscall.Timespec NSec value used to avoid
|
||||
// setting the file's timestamp.
|
||||
UTIME_OMIT = _UTIME_OMIT
|
||||
)
|
||||
|
||||
// Utimens set file access and modification times on a path resolved to the
|
||||
// current working directory, at nanosecond precision.
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// The `times` parameter includes the access and modification timestamps to
|
||||
// assign. Special syscall.Timespec NSec values UTIME_NOW and UTIME_OMIT may be
|
||||
// specified instead of real timestamps. A nil `times` parameter behaves the
|
||||
// same as if both were set to UTIME_NOW. If the path is a symbolic link, the
|
||||
// target of expanding that link is updated.
|
||||
//
|
||||
// # Errors
|
||||
//
|
||||
// A zero sys.Errno is success. The below are expected otherwise:
|
||||
// - sys.ENOSYS: the implementation does not support this function.
|
||||
// - sys.EINVAL: `path` is invalid.
|
||||
// - sys.EEXIST: `path` exists and is a directory.
|
||||
// - sys.ENOTDIR: `path` exists and is a file.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in
|
||||
// POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||
func Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
err := utimens(path, times)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
func timesToPtr(times *[2]syscall.Timespec) unsafe.Pointer { //nolint:unused
|
||||
if times != nil {
|
||||
return unsafe.Pointer(×[0])
|
||||
@@ -55,67 +16,22 @@ func timesToPtr(times *[2]syscall.Timespec) unsafe.Pointer { //nolint:unused
|
||||
return unsafe.Pointer(nil)
|
||||
}
|
||||
|
||||
func utimensPortable(path string, times *[2]syscall.Timespec) error { //nolint:unused
|
||||
// Handle when both inputs are current system time.
|
||||
if times == nil || times[0].Nsec == UTIME_NOW && times[1].Nsec == UTIME_NOW {
|
||||
ts := nowTimespec()
|
||||
return syscall.UtimesNano(path, []syscall.Timespec{ts, ts})
|
||||
}
|
||||
|
||||
func timesToTimespecs(atim int64, mtim int64) (times *[2]syscall.Timespec) {
|
||||
// When both inputs are omitted, there is nothing to change.
|
||||
if times[0].Nsec == UTIME_OMIT && times[1].Nsec == UTIME_OMIT {
|
||||
return nil
|
||||
if atim == fsapi.UTIME_OMIT && mtim == fsapi.UTIME_OMIT {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle when neither input are special values
|
||||
if times[0].Nsec != UTIME_NOW && times[1].Nsec != UTIME_NOW &&
|
||||
times[0].Nsec != UTIME_OMIT && times[1].Nsec != UTIME_OMIT {
|
||||
return syscall.UtimesNano(path, times[:])
|
||||
}
|
||||
|
||||
// Now, either atim or mtim is a special value, but not both.
|
||||
|
||||
// Now, either one of the inputs is a special value, or neither. This means
|
||||
// we don't have a risk of re-reading the clock or re-doing stat.
|
||||
if atim, err := normalizeTimespec(path, times, 0); err != 0 {
|
||||
return err
|
||||
} else if mtim, err := normalizeTimespec(path, times, 1); err != 0 {
|
||||
return err
|
||||
times = &[2]syscall.Timespec{}
|
||||
if atim == fsapi.UTIME_OMIT {
|
||||
times[0] = syscall.Timespec{Nsec: _UTIME_OMIT}
|
||||
times[1] = syscall.NsecToTimespec(mtim)
|
||||
} else if mtim == fsapi.UTIME_OMIT {
|
||||
times[0] = syscall.NsecToTimespec(atim)
|
||||
times[1] = syscall.Timespec{Nsec: _UTIME_OMIT}
|
||||
} else {
|
||||
return syscall.UtimesNano(path, []syscall.Timespec{atim, mtim})
|
||||
times[0] = syscall.NsecToTimespec(atim)
|
||||
times[1] = syscall.NsecToTimespec(mtim)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeTimespec(path string, times *[2]syscall.Timespec, i int) (ts syscall.Timespec, err experimentalsys.Errno) { //nolint:unused
|
||||
switch times[i].Nsec {
|
||||
case UTIME_NOW: // declined in Go per golang/go#31880.
|
||||
ts = nowTimespec()
|
||||
return
|
||||
case UTIME_OMIT:
|
||||
// UTIME_OMIT is expensive until progress is made in Go, as it requires a
|
||||
// stat to read-back the value to re-apply.
|
||||
// - https://github.com/golang/go/issues/32558.
|
||||
// - https://go-review.googlesource.com/c/go/+/219638 (unmerged)
|
||||
var st sys.Stat_t
|
||||
if st, err = stat(path); err != 0 {
|
||||
return
|
||||
}
|
||||
switch i {
|
||||
case 0:
|
||||
ts = syscall.NsecToTimespec(st.Atim)
|
||||
case 1:
|
||||
ts = syscall.NsecToTimespec(st.Mtim)
|
||||
default:
|
||||
panic("BUG")
|
||||
}
|
||||
return
|
||||
default: // not special
|
||||
ts = times[i]
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func nowTimespec() syscall.Timespec { //nolint:unused
|
||||
now := time.Now().UnixNano()
|
||||
return syscall.NsecToTimespec(now)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package sysfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
_ "unsafe" // for go:linkname
|
||||
_ "unsafe"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
)
|
||||
|
||||
const (
|
||||
_AT_FDCWD = -0x2
|
||||
_AT_SYMLINK_NOFOLLOW = 0x0020
|
||||
_UTIME_NOW = -1
|
||||
_UTIME_OMIT = -2
|
||||
)
|
||||
|
||||
@@ -16,20 +17,25 @@ const (
|
||||
//go:linkname utimensat syscall.utimensat
|
||||
func utimensat(dirfd int, path string, times *[2]syscall.Timespec, flags int) error
|
||||
|
||||
func utimens(path string, times *[2]syscall.Timespec) error {
|
||||
func utimens(path string, atim, mtim int64) experimentalsys.Errno {
|
||||
times := timesToTimespecs(atim, mtim)
|
||||
if times == nil {
|
||||
return 0
|
||||
}
|
||||
var flags int
|
||||
return utimensat(_AT_FDCWD, path, times, flags)
|
||||
return experimentalsys.UnwrapOSError(utimensat(_AT_FDCWD, path, times, flags))
|
||||
}
|
||||
|
||||
func futimens(fd uintptr, times *[2]syscall.Timespec) error {
|
||||
func futimens(fd uintptr, atim, mtim int64) experimentalsys.Errno {
|
||||
times := timesToTimespecs(atim, mtim)
|
||||
if times == nil {
|
||||
return 0
|
||||
}
|
||||
_p0 := timesToPtr(times)
|
||||
|
||||
// Warning: futimens only exists since High Sierra (10.13).
|
||||
_, _, e1 := syscall_syscall6(libc_futimens_trampoline_addr, fd, uintptr(_p0), 0, 0, 0, 0)
|
||||
if e1 != 0 {
|
||||
return e1
|
||||
}
|
||||
return nil
|
||||
return experimentalsys.UnwrapOSError(e1)
|
||||
}
|
||||
|
||||
// libc_futimens_trampoline_addr is the address of the
|
||||
|
||||
@@ -3,28 +3,38 @@ package sysfs
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
_ "unsafe" // for go:linkname
|
||||
_ "unsafe"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
)
|
||||
|
||||
const (
|
||||
_AT_FDCWD = -0x64
|
||||
_UTIME_NOW = (1 << 30) - 1
|
||||
_UTIME_OMIT = (1 << 30) - 2
|
||||
)
|
||||
|
||||
func utimens(path string, times *[2]syscall.Timespec) (err error) {
|
||||
func utimens(path string, atim, mtim int64) experimentalsys.Errno {
|
||||
times := timesToTimespecs(atim, mtim)
|
||||
if times == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var flags int
|
||||
var _p0 *byte
|
||||
_p0, err = syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return
|
||||
_p0, err := syscall.BytePtrFromString(path)
|
||||
if err == nil {
|
||||
err = utimensat(_AT_FDCWD, uintptr(unsafe.Pointer(_p0)), times, flags)
|
||||
}
|
||||
return utimensat(_AT_FDCWD, uintptr(unsafe.Pointer(_p0)), times, flags)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
// On linux, implement futimens via utimensat with the NUL path.
|
||||
func futimens(fd uintptr, times *[2]syscall.Timespec) error {
|
||||
return utimensat(int(fd), 0 /* NUL */, times, 0)
|
||||
func futimens(fd uintptr, atim, mtim int64) experimentalsys.Errno {
|
||||
times := timesToTimespecs(atim, mtim)
|
||||
if times == nil {
|
||||
return 0
|
||||
}
|
||||
return experimentalsys.UnwrapOSError(utimensat(int(fd), 0 /* NUL */, times, 0))
|
||||
}
|
||||
|
||||
// utimensat is like syscall.utimensat special-cased to accept a NUL string for the path value.
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
//go:build windows || linux || darwin
|
||||
|
||||
package sysfs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
|
||||
func TestUtimens(t *testing.T) {
|
||||
t.Run("doesn't exist", func(t *testing.T) {
|
||||
err := Utimens("nope", nil)
|
||||
err := utimens("nope", 0, 0)
|
||||
require.EqualErrno(t, sys.ENOENT, err)
|
||||
})
|
||||
testUtimens(t, false)
|
||||
@@ -28,74 +29,31 @@ func testUtimens(t *testing.T, futimes bool) {
|
||||
//
|
||||
// Negative isn't tested as most platforms don't return consistent results.
|
||||
tests := []struct {
|
||||
name string
|
||||
times *[2]syscall.Timespec
|
||||
name string
|
||||
atim, mtim int64
|
||||
}{
|
||||
{
|
||||
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},
|
||||
},
|
||||
atim: fsapi.UTIME_OMIT,
|
||||
mtim: fsapi.UTIME_OMIT,
|
||||
},
|
||||
{
|
||||
name: "a=set,m=omit",
|
||||
times: &[2]syscall.Timespec{
|
||||
{Sec: 123, Nsec: 4 * 1e3},
|
||||
{Sec: 123, Nsec: UTIME_OMIT},
|
||||
},
|
||||
atim: int64(123*time.Second + 4*time.Microsecond),
|
||||
mtim: fsapi.UTIME_OMIT,
|
||||
},
|
||||
{
|
||||
name: "a=omit,m=set",
|
||||
times: &[2]syscall.Timespec{
|
||||
{Sec: 123, Nsec: UTIME_OMIT},
|
||||
{Sec: 123, Nsec: 4 * 1e3},
|
||||
},
|
||||
atim: fsapi.UTIME_OMIT,
|
||||
mtim: int64(123*time.Second + 4*time.Microsecond),
|
||||
},
|
||||
{
|
||||
name: "a=set,m=set",
|
||||
times: &[2]syscall.Timespec{
|
||||
{Sec: 123, Nsec: 4 * 1e3},
|
||||
{Sec: 223, Nsec: 5 * 1e3},
|
||||
},
|
||||
atim: int64(123*time.Second + 4*time.Microsecond),
|
||||
mtim: int64(223*time.Second + 5*time.Microsecond),
|
||||
},
|
||||
}
|
||||
for _, fileType := range []string{"dir", "file", "link"} {
|
||||
@@ -136,7 +94,7 @@ func testUtimens(t *testing.T, futimes bool) {
|
||||
require.EqualErrno(t, 0, errno)
|
||||
|
||||
if !futimes {
|
||||
errno = Utimens(path, tc.times)
|
||||
errno = utimens(path, tc.atim, tc.mtim)
|
||||
require.EqualErrno(t, 0, errno)
|
||||
} else {
|
||||
flag := fsapi.O_RDWR
|
||||
@@ -150,7 +108,7 @@ func testUtimens(t *testing.T, futimes bool) {
|
||||
|
||||
f := requireOpenFile(t, path, flag, 0)
|
||||
|
||||
errno = f.Utimens(tc.times)
|
||||
errno = f.Utimens(tc.atim, tc.mtim)
|
||||
require.EqualErrno(t, 0, f.Close())
|
||||
require.EqualErrno(t, 0, errno)
|
||||
}
|
||||
@@ -159,24 +117,18 @@ func testUtimens(t *testing.T, futimes bool) {
|
||||
require.EqualErrno(t, 0, errno)
|
||||
|
||||
if platform.CompilerSupported() {
|
||||
if tc.times != nil && tc.times[0].Nsec == UTIME_OMIT {
|
||||
if tc.atim == fsapi.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)
|
||||
require.Equal(t, tc.atim, newSt.Atim)
|
||||
}
|
||||
}
|
||||
|
||||
// When compiler isn't supported, we can still check mtim.
|
||||
if tc.times != nil && tc.times[1].Nsec == UTIME_OMIT {
|
||||
if tc.mtim == fsapi.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)
|
||||
require.Equal(t, tc.mtim, newSt.Mtim)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,22 +3,14 @@
|
||||
package sysfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
)
|
||||
|
||||
// Define values even if not used except as sentinels.
|
||||
const (
|
||||
_UTIME_NOW = -1
|
||||
_UTIME_OMIT = -2
|
||||
)
|
||||
|
||||
func utimens(path string, times *[2]syscall.Timespec) error {
|
||||
return utimensPortable(path, times)
|
||||
func utimens(path string, atim, mtim int64) sys.Errno {
|
||||
return chtimes(path, atim, mtim)
|
||||
}
|
||||
|
||||
func futimens(fd uintptr, times *[2]syscall.Timespec) error {
|
||||
func futimens(fd uintptr, atim, mtim int64) error {
|
||||
// Go exports syscall.Futimes, which is microsecond granularity, and
|
||||
// WASI tests expect nanosecond. We don't yet have a way to invoke the
|
||||
// futimens syscall portably.
|
||||
|
||||
@@ -2,24 +2,17 @@ package sysfs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
)
|
||||
|
||||
// Define values even if not used except as sentinels.
|
||||
const (
|
||||
_UTIME_NOW = -1
|
||||
_UTIME_OMIT = -2
|
||||
SupportsSymlinkNoFollow = false
|
||||
)
|
||||
|
||||
func utimens(path string, times *[2]syscall.Timespec) error {
|
||||
return utimensPortable(path, times)
|
||||
func utimens(path string, atim, mtim int64) sys.Errno {
|
||||
return chtimes(path, atim, mtim)
|
||||
}
|
||||
|
||||
func futimens(fd uintptr, times *[2]syscall.Timespec) error {
|
||||
func futimens(fd uintptr, atim, mtim int64) error {
|
||||
// Before Go 1.20, ERROR_INVALID_HANDLE was returned for too many reasons.
|
||||
// Kick out so that callers can use path-based operations instead.
|
||||
if !platform.IsAtLeastGo120 {
|
||||
@@ -27,9 +20,9 @@ func futimens(fd uintptr, times *[2]syscall.Timespec) error {
|
||||
}
|
||||
|
||||
// Per docs, zero isn't a valid timestamp as it cannot be differentiated
|
||||
// from nil. In both cases, it is a marker like syscall.UTIME_OMIT.
|
||||
// from nil. In both cases, it is a marker like fsapi.UTIME_OMIT.
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfiletime
|
||||
a, w := timespecToFiletime(times)
|
||||
a, w := timespecToFiletime(atim, mtim)
|
||||
|
||||
if a == nil && w == nil {
|
||||
return nil // both omitted, so nothing to change
|
||||
@@ -42,32 +35,16 @@ func futimens(fd uintptr, times *[2]syscall.Timespec) error {
|
||||
return syscall.SetFileTime(h, nil, a, w)
|
||||
}
|
||||
|
||||
func timespecToFiletime(times *[2]syscall.Timespec) (a, w *syscall.Filetime) {
|
||||
// Handle when both inputs are current system time.
|
||||
if times == nil || times[0].Nsec == UTIME_NOW && times[1].Nsec == UTIME_NOW {
|
||||
now := time.Now().UnixNano()
|
||||
ft := syscall.NsecToFiletime(now)
|
||||
return &ft, &ft
|
||||
}
|
||||
|
||||
// Now, either one of the inputs is current time, or neither. This
|
||||
// means we don't have a risk of re-reading the clock.
|
||||
a = timespecToFileTime(times, 0)
|
||||
w = timespecToFileTime(times, 1)
|
||||
func timespecToFiletime(atim, mtim int64) (a, w *syscall.Filetime) {
|
||||
a = timespecToFileTime(atim)
|
||||
w = timespecToFileTime(mtim)
|
||||
return
|
||||
}
|
||||
|
||||
func timespecToFileTime(times *[2]syscall.Timespec, i int) *syscall.Filetime {
|
||||
if times[i].Nsec == UTIME_OMIT {
|
||||
func timespecToFileTime(tim int64) *syscall.Filetime {
|
||||
if tim == fsapi.UTIME_OMIT {
|
||||
return nil
|
||||
}
|
||||
|
||||
var nsec int64
|
||||
if times[i].Nsec == UTIME_NOW {
|
||||
nsec = time.Now().UnixNano()
|
||||
} else {
|
||||
nsec = syscall.TimespecToNsec(times[i])
|
||||
}
|
||||
ft := syscall.NsecToFiletime(nsec)
|
||||
ft := syscall.NsecToFiletime(tim)
|
||||
return &ft
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
@@ -244,12 +243,12 @@ func (f *osFile) Datasync() experimentalsys.Errno {
|
||||
}
|
||||
|
||||
// Utimens implements the same method as documented on fsapi.File
|
||||
func (f *osFile) Utimens(times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
func (f *osFile) Utimens(atim, mtim int64) experimentalsys.Errno {
|
||||
if f.closed {
|
||||
return experimentalsys.EBADF
|
||||
}
|
||||
|
||||
err := futimens(f.fd, times)
|
||||
err := futimens(f.fd, atim, mtim)
|
||||
return experimentalsys.UnwrapOSError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -42,9 +42,6 @@ const pollInterval = 100 * time.Millisecond
|
||||
|
||||
// _poll implements poll on Windows, for a subset of cases.
|
||||
//
|
||||
// pollWithContext emulates the behavior of POSIX poll(2) on Windows, for a subset of cases,
|
||||
// and it supports context cancellation.
|
||||
//
|
||||
// fds may contain any number of file handles, but regular files and pipes are only processed for _POLLIN.
|
||||
// Stdin is a pipe, thus it is checked for readiness when present. Pipes are checked using PeekNamedPipe.
|
||||
// Regular files always immediately reported as ready, regardless their actual state and timeouts.
|
||||
|
||||
@@ -2,7 +2,6 @@ package sysfs
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"syscall"
|
||||
|
||||
experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
@@ -98,7 +97,7 @@ func (r *readFS) Unlink(path string) experimentalsys.Errno {
|
||||
}
|
||||
|
||||
// Utimens implements the same method as documented on fsapi.FS
|
||||
func (r *readFS) Utimens(path string, times *[2]syscall.Timespec) experimentalsys.Errno {
|
||||
func (r *readFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno {
|
||||
return experimentalsys.EROFS
|
||||
}
|
||||
|
||||
@@ -135,7 +134,7 @@ func (r *readFile) Datasync() experimentalsys.Errno {
|
||||
}
|
||||
|
||||
// Utimens implements the same method as documented on fsapi.File.
|
||||
func (r *readFile) Utimens(*[2]syscall.Timespec) experimentalsys.Errno {
|
||||
func (r *readFile) Utimens(int64, int64) experimentalsys.Errno {
|
||||
return experimentalsys.EBADF
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestReadFS_UtimesNano(t *testing.T) {
|
||||
realPath := joinPath(tmpDir, path)
|
||||
require.NoError(t, os.WriteFile(realPath, []byte{}, 0o600))
|
||||
|
||||
err := testFS.Utimens(path, nil)
|
||||
err := testFS.Utimens(path, fsapi.UTIME_OMIT, fsapi.UTIME_OMIT)
|
||||
require.EqualErrno(t, sys.EROFS, err)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user