Adds Utimens to platform.File (#1440)
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -457,7 +457,7 @@ func fdFilestatSetTimesFn(_ context.Context, mod api.Module, params []uint64) sy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to update the file timestamps by file-descriptor.
|
// Try to update the file timestamps by file-descriptor.
|
||||||
errno = platform.UtimensFile(f.File.File(), ×)
|
errno = f.File.Utimens(×)
|
||||||
|
|
||||||
// Fall back to path based, despite it being less precise.
|
// Fall back to path based, despite it being less precise.
|
||||||
switch errno {
|
switch errno {
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Benchmark_UtimensFile(b *testing.B) {
|
func BenchmarkFsFileUtimesNs(b *testing.B) {
|
||||||
tmpDir := b.TempDir()
|
tmpDir := b.TempDir()
|
||||||
f, err := os.Create(path.Join(tmpDir, "file"))
|
path := path.Join(tmpDir, "file")
|
||||||
|
f, err := os.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
fs := NewFsFile(path, syscall.O_RDONLY, f)
|
||||||
|
|
||||||
times := &[2]syscall.Timespec{
|
times := &[2]syscall.Timespec{
|
||||||
{Sec: 123, Nsec: 4 * 1e3},
|
{Sec: 123, Nsec: 4 * 1e3},
|
||||||
@@ -24,8 +26,8 @@ func Benchmark_UtimensFile(b *testing.B) {
|
|||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
if err := UtimensFile(f, times); err != 0 {
|
if errno := fs.Utimens(times); errno != 0 {
|
||||||
b.Fatal(err)
|
b.Fatal(errno)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,6 +207,30 @@ type File interface {
|
|||||||
// - This always returns syscall.ENOSYS on windows.
|
// - This always returns syscall.ENOSYS on windows.
|
||||||
Chown(uid, gid int) syscall.Errno
|
Chown(uid, gid int) syscall.Errno
|
||||||
|
|
||||||
|
// Utimens set file access and modification times of this file, 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.
|
||||||
|
//
|
||||||
|
// # Errors
|
||||||
|
//
|
||||||
|
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||||
|
// - syscall.ENOSYS: the implementation does not support this function.
|
||||||
|
// - syscall.EBADF: the file or directory was closed.
|
||||||
|
//
|
||||||
|
// # Notes
|
||||||
|
//
|
||||||
|
// - This is like syscall.UtimesNano and `futimens` in POSIX. See
|
||||||
|
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||||
|
// - Windows requires files to be open with syscall.O_RDWR, which means you
|
||||||
|
// cannot use this to update timestamps on a directory (syscall.EPERM).
|
||||||
|
Utimens(times *[2]syscall.Timespec) syscall.Errno
|
||||||
|
|
||||||
// Close closes the underlying file.
|
// Close closes the underlying file.
|
||||||
//
|
//
|
||||||
// A zero syscall.Errno is success. The below are expected otherwise:
|
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||||
@@ -276,6 +300,11 @@ func (UnimplementedFile) Chown(int, int) syscall.Errno {
|
|||||||
return syscall.ENOSYS
|
return syscall.ENOSYS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utimens implements File.Utimens
|
||||||
|
func (UnimplementedFile) Utimens(*[2]syscall.Timespec) syscall.Errno {
|
||||||
|
return syscall.ENOSYS
|
||||||
|
}
|
||||||
|
|
||||||
func NewFsFile(openPath string, openFlag int, f fs.File) File {
|
func NewFsFile(openPath string, openFlag int, f fs.File) File {
|
||||||
return &fsFile{
|
return &fsFile{
|
||||||
path: openPath,
|
path: openPath,
|
||||||
@@ -442,6 +471,15 @@ func (f *fsFile) Chown(uid, gid int) syscall.Errno {
|
|||||||
return syscall.ENOSYS
|
return syscall.ENOSYS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utimens implements File.Utimens
|
||||||
|
func (f *fsFile) Utimens(times *[2]syscall.Timespec) syscall.Errno {
|
||||||
|
if f, ok := f.file.(fdFile); ok {
|
||||||
|
err := futimens(f.Fd(), times)
|
||||||
|
return UnwrapOSError(err)
|
||||||
|
}
|
||||||
|
return syscall.ENOSYS
|
||||||
|
}
|
||||||
|
|
||||||
// Close implements File.Close
|
// Close implements File.Close
|
||||||
func (f *fsFile) Close() syscall.Errno {
|
func (f *fsFile) Close() syscall.Errno {
|
||||||
return UnwrapOSError(f.file.Close())
|
return UnwrapOSError(f.file.Close())
|
||||||
|
|||||||
@@ -456,6 +456,7 @@ func testSync(t *testing.T, sync func(File) syscall.Errno) {
|
|||||||
// Windows allows you to sync a closed file
|
// Windows allows you to sync a closed file
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
testEBADFIfFileClosed(t, sync)
|
testEBADFIfFileClosed(t, sync)
|
||||||
|
testEBADFIfDirClosed(t, sync)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,6 +531,39 @@ func TestFsFileTruncate(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFsFileUtimens(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
testUtimens(t, true)
|
||||||
|
|
||||||
|
testEBADFIfFileClosed(t, func(f File) syscall.Errno {
|
||||||
|
return f.Utimens(nil)
|
||||||
|
})
|
||||||
|
testEBADFIfDirClosed(t, func(d File) syscall.Errno {
|
||||||
|
return d.Utimens(nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEBADFIfDirClosed(t *testing.T, fn func(File) syscall.Errno) bool {
|
||||||
|
return t.Run("EBADF if dir closed", func(t *testing.T) {
|
||||||
|
d := openFsFile(t, t.TempDir(), syscall.O_RDONLY, 0o755)
|
||||||
|
|
||||||
|
// close the directory underneath
|
||||||
|
require.Zero(t, d.Close())
|
||||||
|
|
||||||
|
require.EqualErrno(t, syscall.EBADF, fn(d))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func testEBADFIfFileClosed(t *testing.T, fn func(File) syscall.Errno) bool {
|
func testEBADFIfFileClosed(t *testing.T, fn func(File) syscall.Errno) bool {
|
||||||
return t.Run("EBADF if file closed", func(t *testing.T) {
|
return t.Run("EBADF if file closed", func(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package platform
|
package platform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -33,39 +32,21 @@ const (
|
|||||||
//
|
//
|
||||||
// # Errors
|
// # Errors
|
||||||
//
|
//
|
||||||
// The following errors are expected:
|
// A zero syscall.Errno is success. The below are expected otherwise:
|
||||||
|
// - syscall.ENOSYS: the implementation does not support this function.
|
||||||
// - syscall.EINVAL: `path` is invalid.
|
// - syscall.EINVAL: `path` is invalid.
|
||||||
// - syscall.EEXIST: `path` exists and is a directory.
|
// - syscall.EEXIST: `path` exists and is a directory.
|
||||||
// - syscall.ENOTDIR: `path` exists and is a file.
|
// - syscall.ENOTDIR: `path` exists and is a file.
|
||||||
//
|
//
|
||||||
// # Notes
|
// # Notes
|
||||||
//
|
//
|
||||||
// - This is similar to syscall.UtimesNano, except that doesn't have flags to
|
// - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in
|
||||||
// control expansion of symbolic links. It also doesn't support special
|
// POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||||
// values UTIME_NOW or UTIME_NOW.
|
|
||||||
// - This is like `utimensat` with `AT_FDCWD` in POSIX. See
|
|
||||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
|
||||||
func Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
|
func Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno {
|
||||||
err := utimens(path, times, symlinkFollow)
|
err := utimens(path, times, symlinkFollow)
|
||||||
return UnwrapOSError(err)
|
return UnwrapOSError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UtimensFile is like Utimens, except it works on a file, not a path.
|
|
||||||
//
|
|
||||||
// # Notes
|
|
||||||
//
|
|
||||||
// - Windows requires files to be open with syscall.O_RDWR, which means you
|
|
||||||
// cannot use this to update timestamps on a directory (syscall.EPERM).
|
|
||||||
// - This is like the function `futimens` in POSIX. See
|
|
||||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
|
||||||
func UtimensFile(f fs.File, times *[2]syscall.Timespec) syscall.Errno {
|
|
||||||
if f, ok := f.(fdFile); ok {
|
|
||||||
err := futimens(f.Fd(), times)
|
|
||||||
return UnwrapOSError(err)
|
|
||||||
}
|
|
||||||
return syscall.ENOSYS
|
|
||||||
}
|
|
||||||
|
|
||||||
func timesToPtr(times *[2]syscall.Timespec) unsafe.Pointer { //nolint:unused
|
func timesToPtr(times *[2]syscall.Timespec) unsafe.Pointer { //nolint:unused
|
||||||
var _p0 unsafe.Pointer
|
var _p0 unsafe.Pointer
|
||||||
if times != nil {
|
if times != nil {
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ func TestUtimens(t *testing.T) {
|
|||||||
require.EqualErrno(t, syscall.ENOSYS, err)
|
require.EqualErrno(t, syscall.ENOSYS, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
testFutimens(t, true)
|
testUtimens(t, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFutimens(t *testing.T, usePath bool) {
|
func testUtimens(t *testing.T, futimes bool) {
|
||||||
// Note: This sets microsecond granularity because Windows doesn't support
|
// Note: This sets microsecond granularity because Windows doesn't support
|
||||||
// nanosecond.
|
// nanosecond.
|
||||||
//
|
//
|
||||||
@@ -112,7 +112,7 @@ func testFutimens(t *testing.T, usePath bool) {
|
|||||||
// symlinkNoFollow is invalid for file descriptor based operations,
|
// symlinkNoFollow is invalid for file descriptor based operations,
|
||||||
// because the default for open is to follow links. You can't avoid
|
// 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.
|
// this. O_NOFOLLOW is used only to return ELOOP on a link.
|
||||||
if !usePath && symlinkNoFollow {
|
if futimes && symlinkNoFollow {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func testFutimens(t *testing.T, usePath bool) {
|
|||||||
oldSt, errno := Lstat(statPath)
|
oldSt, errno := Lstat(statPath)
|
||||||
require.EqualErrno(t, 0, errno)
|
require.EqualErrno(t, 0, errno)
|
||||||
|
|
||||||
if usePath {
|
if !futimes {
|
||||||
err = Utimens(path, tc.times, !symlinkNoFollow)
|
err = Utimens(path, tc.times, !symlinkNoFollow)
|
||||||
if symlinkNoFollow && !SupportsSymlinkNoFollow {
|
if symlinkNoFollow && !SupportsSymlinkNoFollow {
|
||||||
require.EqualErrno(t, syscall.ENOSYS, err)
|
require.EqualErrno(t, syscall.ENOSYS, err)
|
||||||
@@ -169,7 +169,7 @@ func testFutimens(t *testing.T, usePath bool) {
|
|||||||
|
|
||||||
f := openFsFile(t, path, flag, 0)
|
f := openFsFile(t, path, flag, 0)
|
||||||
|
|
||||||
errno = UtimensFile(f.File(), tc.times)
|
errno = f.Utimens(tc.times)
|
||||||
require.Zero(t, f.Close())
|
require.Zero(t, f.Close())
|
||||||
require.EqualErrno(t, 0, errno)
|
require.EqualErrno(t, 0, errno)
|
||||||
}
|
}
|
||||||
@@ -201,42 +201,3 @@ func testFutimens(t *testing.T, usePath bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 := openFsFile(t, file, syscall.O_RDWR, 0)
|
|
||||||
require.Zero(t, fileF.Close())
|
|
||||||
|
|
||||||
errno := UtimensFile(fileF.File(), 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 := openFsFile(t, dir, syscall.O_RDONLY, 0)
|
|
||||||
require.Zero(t, dirF.Close())
|
|
||||||
|
|
||||||
err = UtimensFile(dirF.File(), nil)
|
|
||||||
require.EqualErrno(t, syscall.EBADF, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -240,6 +240,15 @@ func (r *lazyDir) Chown(uid, gid int) syscall.Errno {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utimens implements the same method as documented on platform.File
|
||||||
|
func (r *lazyDir) Utimens(times *[2]syscall.Timespec) syscall.Errno {
|
||||||
|
if f, ok := r.file(); !ok {
|
||||||
|
return syscall.EBADF
|
||||||
|
} else {
|
||||||
|
return f.Utimens(times)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// File implements the same method as documented on platform.File
|
// File implements the same method as documented on platform.File
|
||||||
func (r *lazyDir) File() fs.File {
|
func (r *lazyDir) File() fs.File {
|
||||||
if f, ok := r.file(); !ok {
|
if f, ok := r.file(); !ok {
|
||||||
|
|||||||
@@ -346,9 +346,10 @@ type FS interface {
|
|||||||
// # Parameters
|
// # Parameters
|
||||||
//
|
//
|
||||||
// The `times` parameter includes the access and modification timestamps to
|
// The `times` parameter includes the access and modification timestamps to
|
||||||
// assign. Special syscall.Timespec NSec values UTIME_NOW and UTIME_OMIT may be
|
// assign. Special syscall.Timespec NSec values platform.UTIME_NOW and
|
||||||
// specified instead of real timestamps. A nil `times` parameter behaves the
|
// platform.UTIME_OMIT may be specified instead of real timestamps. A nil
|
||||||
// same as if both were set to UTIME_NOW.
|
// `times` parameter behaves the same as if both were set to
|
||||||
|
// platform.UTIME_NOW.
|
||||||
//
|
//
|
||||||
// When the `symlinkFollow` parameter is true and the path is a symbolic link,
|
// When the `symlinkFollow` parameter is true and the path is a symbolic link,
|
||||||
// the target of expanding that link is updated.
|
// the target of expanding that link is updated.
|
||||||
@@ -363,11 +364,7 @@ type FS interface {
|
|||||||
//
|
//
|
||||||
// # Notes
|
// # Notes
|
||||||
//
|
//
|
||||||
// - This is like syscall.Utimes, except the path is relative to this
|
// - This is like syscall.UtimesNano and `utimensat` with `AT_FDCWD` in
|
||||||
// filesystem. It also doesn't have flags to control expansion of
|
// POSIX. See https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
||||||
// symbolic links. Neither does this support the support special values
|
|
||||||
// UTIME_NOW or UTIME_NOW.
|
|
||||||
// - This is like `utimensat` with `AT_FDCWD` in POSIX. See
|
|
||||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html
|
|
||||||
Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno
|
Utimens(path string, times *[2]syscall.Timespec, symlinkFollow bool) syscall.Errno
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user