mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
os: add Root.Chtimes
For #67002 Change-Id: I9b10ac30f852052c85d6d21eb1752a9de5474346 Reviewed-on: https://go-review.googlesource.com/c/go/+/649515 Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Kirill Kolyshkin <kolyshkin@gmail.com> Reviewed-by: Ian Lance Taylor <iant@google.com>
This commit is contained in:
parent
3309658d39
commit
2148309963
@ -1,2 +1,3 @@
|
||||
pkg os, method (*Root) Chmod(string, fs.FileMode) error #67002
|
||||
pkg os, method (*Root) Chown(string, int, int) error #67002
|
||||
pkg os, method (*Root) Chtimes(string, time.Time, time.Time) error #67002
|
||||
|
@ -2,3 +2,4 @@ The [os.Root] type supports the following additional methods:
|
||||
|
||||
* [os.Root.Chmod]
|
||||
* [os.Root.Chown]
|
||||
* [os.Root.Chtimes]
|
||||
|
15
src/internal/syscall/unix/utimes.go
Normal file
15
src/internal/syscall/unix/utimes.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build unix && !wasip1
|
||||
|
||||
package unix
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
_ "unsafe" // for //go:linkname
|
||||
)
|
||||
|
||||
//go:linkname Utimensat syscall.utimensat
|
||||
func Utimensat(dirfd int, path string, times *[2]syscall.Timespec, flag int) error
|
42
src/internal/syscall/unix/utimes_wasip1.go
Normal file
42
src/internal/syscall/unix/utimes_wasip1.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build wasip1
|
||||
|
||||
package unix
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 path_filestat_set_times
|
||||
//go:noescape
|
||||
func path_filestat_set_times(fd int32, flags uint32, path *byte, pathLen size, atim uint64, mtim uint64, fstflags uint32) syscall.Errno
|
||||
|
||||
func Utimensat(dirfd int, path string, times *[2]syscall.Timespec, flag int) error {
|
||||
if path == "" {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
atime := syscall.TimespecToNsec(times[0])
|
||||
mtime := syscall.TimespecToNsec(times[1])
|
||||
|
||||
var fflag uint32
|
||||
if times[0].Nsec != UTIME_OMIT {
|
||||
fflag |= syscall.FILESTAT_SET_ATIM
|
||||
}
|
||||
if times[1].Nsec != UTIME_OMIT {
|
||||
fflag |= syscall.FILESTAT_SET_MTIM
|
||||
}
|
||||
errno := path_filestat_set_times(
|
||||
int32(dirfd),
|
||||
syscall.LOOKUP_SYMLINK_FOLLOW,
|
||||
unsafe.StringData(path),
|
||||
size(len(path)),
|
||||
uint64(atime),
|
||||
uint64(mtime),
|
||||
fflag,
|
||||
)
|
||||
return errnoErr(errno)
|
||||
}
|
@ -177,6 +177,14 @@ func (f *File) Sync() error {
|
||||
// less precise time unit.
|
||||
// If there is an error, it will be of type [*PathError].
|
||||
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
utimes := chtimesUtimes(atime, mtime)
|
||||
if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil {
|
||||
return &PathError{Op: "chtimes", Path: name, Err: e}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func chtimesUtimes(atime, mtime time.Time) [2]syscall.Timespec {
|
||||
var utimes [2]syscall.Timespec
|
||||
set := func(i int, t time.Time) {
|
||||
if t.IsZero() {
|
||||
@ -187,10 +195,7 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
}
|
||||
set(0, atime)
|
||||
set(1, mtime)
|
||||
if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil {
|
||||
return &PathError{Op: "chtimes", Path: name, Err: e}
|
||||
}
|
||||
return nil
|
||||
return utimes
|
||||
}
|
||||
|
||||
// Chdir changes the current working directory to the file,
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"io/fs"
|
||||
"runtime"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OpenInRoot opens the file name in the directory dir.
|
||||
@ -54,7 +55,7 @@ func OpenInRoot(dir, name string) (*File, error) {
|
||||
//
|
||||
// - When GOOS=windows, file names may not reference Windows reserved device names
|
||||
// such as NUL and COM1.
|
||||
// - On Unix, [Root.Chmod] and [Root.Chown] are vulnerable to a race condition.
|
||||
// - On Unix, [Root.Chmod], [Root.Chown], and [Root.Chtimes] are vulnerable to a race condition.
|
||||
// If the target of the operation is changed from a regular file to a symlink
|
||||
// while the operation is in progress, the operation may be peformed on the link
|
||||
// rather than the link target.
|
||||
@ -158,6 +159,12 @@ func (r *Root) Chown(name string, uid, gid int) error {
|
||||
return rootChown(r, name, uid, gid)
|
||||
}
|
||||
|
||||
// Chtimes changes the access and modification times of the named file in the root.
|
||||
// See [Chtimes] for more details.
|
||||
func (r *Root) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
return rootChtimes(r, name, atime, mtime)
|
||||
}
|
||||
|
||||
// Remove removes the named file or (empty) directory in the root.
|
||||
// See [Remove] for more details.
|
||||
func (r *Root) Remove(name string) error {
|
||||
|
@ -9,6 +9,7 @@ package os
|
||||
import (
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// root implementation for platforms with no openat.
|
||||
@ -115,6 +116,16 @@ func rootChown(r *Root, name string, uid, gid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
|
||||
if err := checkPathEscapes(r, name); err != nil {
|
||||
return &PathError{Op: "chtimesat", Path: name, Err: err}
|
||||
}
|
||||
if err := Chtimes(joinPath(r.root.name, name), atime, mtime); err != nil {
|
||||
return &PathError{Op: "chtimesat", Path: name, Err: underlyingError(err)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rootMkdir(r *Root, name string, perm FileMode) error {
|
||||
if err := checkPathEscapes(r, name); err != nil {
|
||||
return &PathError{Op: "mkdirat", Path: name, Err: err}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"slices"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// root implementation for platforms with a function to open a file
|
||||
@ -87,6 +88,16 @@ func rootChown(r *Root, name string, uid, gid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func rootChtimes(r *Root, name string, atime time.Time, mtime time.Time) error {
|
||||
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
|
||||
return struct{}{}, chtimesat(parent, name, atime, mtime)
|
||||
})
|
||||
if err != nil {
|
||||
return &PathError{Op: "chtimesat", Path: name, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func rootMkdir(r *Root, name string, perm FileMode) error {
|
||||
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
|
||||
return struct{}{}, mkdirat(parent, name, perm)
|
||||
|
@ -426,6 +426,53 @@ func TestRootChmod(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootChtimes(t *testing.T) {
|
||||
for _, test := range rootTestCases {
|
||||
test.run(t, func(t *testing.T, target string, root *os.Root) {
|
||||
if target != "" {
|
||||
if err := os.WriteFile(target, nil, 0o666); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for _, times := range []struct {
|
||||
atime, mtime time.Time
|
||||
}{{
|
||||
atime: time.Now().Add(-1 * time.Minute),
|
||||
mtime: time.Now().Add(-1 * time.Minute),
|
||||
}, {
|
||||
atime: time.Now().Add(1 * time.Minute),
|
||||
mtime: time.Now().Add(1 * time.Minute),
|
||||
}, {
|
||||
atime: time.Time{},
|
||||
mtime: time.Now(),
|
||||
}, {
|
||||
atime: time.Now(),
|
||||
mtime: time.Time{},
|
||||
}} {
|
||||
if runtime.GOOS == "js" {
|
||||
times.atime = times.atime.Truncate(1 * time.Second)
|
||||
times.mtime = times.mtime.Truncate(1 * time.Second)
|
||||
}
|
||||
|
||||
err := root.Chtimes(test.open, times.atime, times.mtime)
|
||||
if errEndsTest(t, err, test.wantError, "root.Chtimes(%q)", test.open) {
|
||||
return
|
||||
}
|
||||
st, err := os.Stat(target)
|
||||
if err != nil {
|
||||
t.Fatalf("os.Stat(%q) = %v", target, err)
|
||||
}
|
||||
if got := st.ModTime(); !times.mtime.IsZero() && !got.Equal(times.mtime) {
|
||||
t.Errorf("after root.Chtimes(%q, %v, %v): got mtime=%v, want %v", test.open, times.atime, times.mtime, got, times.mtime)
|
||||
}
|
||||
if got := os.Atime(st); !times.atime.IsZero() && !got.Equal(times.atime) {
|
||||
t.Errorf("after root.Chtimes(%q, %v, %v): got atime=%v, want %v", test.open, times.atime, times.mtime, got, times.atime)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootMkdir(t *testing.T) {
|
||||
for _, test := range rootTestCases {
|
||||
test.run(t, func(t *testing.T, target string, root *os.Root) {
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"internal/syscall/unix"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type sysfdType = int
|
||||
@ -165,6 +166,15 @@ func chownat(parent int, name string, uid, gid int) error {
|
||||
})
|
||||
}
|
||||
|
||||
func chtimesat(parent int, name string, atime time.Time, mtime time.Time) error {
|
||||
return afterResolvingSymlink(parent, name, func() error {
|
||||
return ignoringEINTR(func() error {
|
||||
utimes := chtimesUtimes(atime, mtime)
|
||||
return unix.Utimensat(parent, name, &utimes, unix.AT_SYMLINK_NOFOLLOW)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func mkdirat(fd int, name string, perm FileMode) error {
|
||||
return ignoringEINTR(func() error {
|
||||
return unix.Mkdirat(fd, name, syscallMode(perm))
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"internal/syscall/windows"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@ -287,3 +288,25 @@ func mkdirat(dirfd syscall.Handle, name string, perm FileMode) error {
|
||||
func removeat(dirfd syscall.Handle, name string) error {
|
||||
return windows.Deleteat(dirfd, name)
|
||||
}
|
||||
|
||||
func chtimesat(dirfd syscall.Handle, name string, atime time.Time, mtime time.Time) error {
|
||||
h, err := windows.Openat(dirfd, name, syscall.O_CLOEXEC|windows.O_NOFOLLOW_ANY|windows.O_WRITE_ATTRS, 0)
|
||||
if err == syscall.ELOOP || err == syscall.ENOTDIR {
|
||||
if link, err := readReparseLinkAt(dirfd, name); err == nil {
|
||||
return errSymlink(link)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.CloseHandle(h)
|
||||
a := syscall.Filetime{}
|
||||
w := syscall.Filetime{}
|
||||
if !atime.IsZero() {
|
||||
a = syscall.NsecToFiletime(atime.UnixNano())
|
||||
}
|
||||
if !mtime.IsZero() {
|
||||
w = syscall.NsecToFiletime(mtime.UnixNano())
|
||||
}
|
||||
return syscall.SetFileTime(h, nil, &a, &w)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user