mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
os: add Root.Rename
For #67002 Change-Id: Ifb1042bc5ceaeea64296763319b24634bbcb0bf0 Reviewed-on: https://go-review.googlesource.com/c/go/+/659416 Reviewed-by: Ian Lance Taylor <iant@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com>
This commit is contained in:
parent
686128a9f3
commit
2ffda87f2d
@ -3,3 +3,4 @@ pkg os, method (*Root) Chown(string, int, int) error #67002
|
|||||||
pkg os, method (*Root) Chtimes(string, time.Time, time.Time) error #67002
|
pkg os, method (*Root) Chtimes(string, time.Time, time.Time) error #67002
|
||||||
pkg os, method (*Root) Lchown(string, int, int) error #67002
|
pkg os, method (*Root) Lchown(string, int, int) error #67002
|
||||||
pkg os, method (*Root) Readlink(string) (string, error) #67002
|
pkg os, method (*Root) Readlink(string) (string, error) #67002
|
||||||
|
pkg os, method (*Root) Rename(string, string) error #67002
|
||||||
|
@ -5,3 +5,4 @@ The [os.Root] type supports the following additional methods:
|
|||||||
* [os.Root.Chtimes]
|
* [os.Root.Chtimes]
|
||||||
* [os.Root.Lchown]
|
* [os.Root.Lchown]
|
||||||
* [os.Root.Readlink]
|
* [os.Root.Readlink]
|
||||||
|
* [os.Root.Rename]
|
||||||
|
@ -27,3 +27,4 @@ TEXT ·libc_readlinkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_readlinkat(SB)
|
|||||||
TEXT ·libc_mkdirat_trampoline(SB),NOSPLIT,$0-0; JMP libc_mkdirat(SB)
|
TEXT ·libc_mkdirat_trampoline(SB),NOSPLIT,$0-0; JMP libc_mkdirat(SB)
|
||||||
TEXT ·libc_fchmodat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchmodat(SB)
|
TEXT ·libc_fchmodat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchmodat(SB)
|
||||||
TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchownat(SB)
|
TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchownat(SB)
|
||||||
|
TEXT ·libc_renameat_trampoline(SB),NOSPLIT,$0-0; JMP libc_renameat(SB)
|
||||||
|
@ -18,3 +18,5 @@ TEXT ·libc_fchmodat_trampoline(SB),NOSPLIT,$0-0
|
|||||||
JMP libc_fchmodat(SB)
|
JMP libc_fchmodat(SB)
|
||||||
TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0
|
TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0
|
||||||
JMP libc_fchownat(SB)
|
JMP libc_fchownat(SB)
|
||||||
|
TEXT ·libc_renameat_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_renameat(SB)
|
||||||
|
@ -114,3 +114,25 @@ func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
|
||||||
|
oldp, err := syscall.BytePtrFromString(oldpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newp, err := syscall.BytePtrFromString(newpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, errno := syscall.Syscall6(renameatTrap,
|
||||||
|
uintptr(olddirfd),
|
||||||
|
uintptr(unsafe.Pointer(oldp)),
|
||||||
|
uintptr(newdirfd),
|
||||||
|
uintptr(unsafe.Pointer(newp)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -102,3 +102,29 @@ func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func libc_renameat_trampoline()
|
||||||
|
|
||||||
|
//go:cgo_import_dynamic libc_renameat renameat "/usr/lib/libSystem.B.dylib"
|
||||||
|
|
||||||
|
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
|
||||||
|
oldp, err := syscall.BytePtrFromString(oldpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newp, err := syscall.BytePtrFromString(newpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_renameat_trampoline),
|
||||||
|
uintptr(olddirfd),
|
||||||
|
uintptr(unsafe.Pointer(oldp)),
|
||||||
|
uintptr(newdirfd),
|
||||||
|
uintptr(unsafe.Pointer(newp)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
//go:linkname procMkdirat libc_mkdirat
|
//go:linkname procMkdirat libc_mkdirat
|
||||||
//go:linkname procFchmodat libc_fchmodat
|
//go:linkname procFchmodat libc_fchmodat
|
||||||
//go:linkname procFchownat libc_fchownat
|
//go:linkname procFchownat libc_fchownat
|
||||||
|
//go:linkname procRenameat libc_renameat
|
||||||
|
|
||||||
var (
|
var (
|
||||||
procFstatat,
|
procFstatat,
|
||||||
@ -26,7 +27,8 @@ var (
|
|||||||
procReadlinkat,
|
procReadlinkat,
|
||||||
procMkdirat,
|
procMkdirat,
|
||||||
procFchmodat,
|
procFchmodat,
|
||||||
procFchownat uintptr
|
procFchownat,
|
||||||
|
procRenameat uintptr
|
||||||
)
|
)
|
||||||
|
|
||||||
func Unlinkat(dirfd int, path string, flags int) error {
|
func Unlinkat(dirfd int, path string, flags int) error {
|
||||||
@ -160,3 +162,25 @@ func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
|
||||||
|
oldp, err := syscall.BytePtrFromString(oldpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newp, err := syscall.BytePtrFromString(newpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, errno := syscall6(uintptr(unsafe.Pointer(&procRenameat)), 4,
|
||||||
|
uintptr(olddirfd),
|
||||||
|
uintptr(unsafe.Pointer(oldp)),
|
||||||
|
uintptr(newdirfd),
|
||||||
|
uintptr(unsafe.Pointer(newp)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -93,3 +93,29 @@ func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:cgo_import_dynamic libc_renameat renameat "libc.so"
|
||||||
|
|
||||||
|
func libc_renameat_trampoline()
|
||||||
|
|
||||||
|
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
|
||||||
|
oldp, err := syscall.BytePtrFromString(oldpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newp, err := syscall.BytePtrFromString(newpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _, errno := syscall_syscall6(abi.FuncPCABI0(libc_renameat_trampoline),
|
||||||
|
uintptr(olddirfd),
|
||||||
|
uintptr(unsafe.Pointer(oldp)),
|
||||||
|
uintptr(newdirfd),
|
||||||
|
uintptr(unsafe.Pointer(newp)),
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, e
|
|||||||
//go:cgo_import_dynamic libc_fchownat fchownat "libc.so"
|
//go:cgo_import_dynamic libc_fchownat fchownat "libc.so"
|
||||||
//go:cgo_import_dynamic libc_fstatat fstatat "libc.so"
|
//go:cgo_import_dynamic libc_fstatat fstatat "libc.so"
|
||||||
//go:cgo_import_dynamic libc_openat openat "libc.so"
|
//go:cgo_import_dynamic libc_openat openat "libc.so"
|
||||||
|
//go:cgo_import_dynamic libc_renameat renameat "libc.so"
|
||||||
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so"
|
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so"
|
||||||
//go:cgo_import_dynamic libc_readlinkat readlinkat "libc.so"
|
//go:cgo_import_dynamic libc_readlinkat readlinkat "libc.so"
|
||||||
//go:cgo_import_dynamic libc_mkdirat mkdirat "libc.so"
|
//go:cgo_import_dynamic libc_mkdirat mkdirat "libc.so"
|
||||||
|
@ -14,6 +14,7 @@ const (
|
|||||||
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
|
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
|
||||||
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
||||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||||
|
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||||
|
|
||||||
AT_EACCESS = 0x4
|
AT_EACCESS = 0x4
|
||||||
AT_FDCWD = 0xfffafdcd
|
AT_FDCWD = 0xfffafdcd
|
||||||
|
@ -21,4 +21,5 @@ const (
|
|||||||
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
|
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
|
||||||
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
||||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||||
|
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ const (
|
|||||||
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
|
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
|
||||||
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
||||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||||
|
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -111,6 +111,24 @@ func Fchownat(dirfd int, path string, uid, gid int, flags int) error {
|
|||||||
return syscall.ENOSYS
|
return syscall.ENOSYS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport wasi_snapshot_preview1 path_rename
|
||||||
|
//go:noescape
|
||||||
|
func path_rename(oldFd int32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno
|
||||||
|
|
||||||
|
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
|
||||||
|
if oldpath == "" || newpath == "" {
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
return errnoErr(path_rename(
|
||||||
|
int32(olddirfd),
|
||||||
|
unsafe.StringData(oldpath),
|
||||||
|
size(len(oldpath)),
|
||||||
|
int32(newdirfd),
|
||||||
|
unsafe.StringData(newpath),
|
||||||
|
size(len(newpath)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
//go:wasmimport wasi_snapshot_preview1 path_create_directory
|
//go:wasmimport wasi_snapshot_preview1 path_create_directory
|
||||||
//go:noescape
|
//go:noescape
|
||||||
func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno
|
func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno
|
||||||
|
16
src/internal/syscall/unix/renameat2_sysnum_linux.go
Normal file
16
src/internal/syscall/unix/renameat2_sysnum_linux.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 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 linux && (loong64 || riscv64)
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// loong64 and riscv64 only have renameat2.
|
||||||
|
// renameat2 has an extra flags parameter.
|
||||||
|
// When called with a 0 flags it is identical to renameat.
|
||||||
|
renameatTrap uintptr = syscall.SYS_RENAMEAT2
|
||||||
|
)
|
13
src/internal/syscall/unix/renameat_sysnum_linux.go
Normal file
13
src/internal/syscall/unix/renameat_sysnum_linux.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// 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 linux && !(loong64 || riscv64)
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||||
|
)
|
@ -254,3 +254,77 @@ func Deleteat(dirfd syscall.Handle, name string) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Renameat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, newpath string) error {
|
||||||
|
objAttrs := &OBJECT_ATTRIBUTES{}
|
||||||
|
if err := objAttrs.init(olddirfd, oldpath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var h syscall.Handle
|
||||||
|
err := NtOpenFile(
|
||||||
|
&h,
|
||||||
|
SYNCHRONIZE|DELETE,
|
||||||
|
objAttrs,
|
||||||
|
&IO_STATUS_BLOCK{},
|
||||||
|
FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
|
||||||
|
FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return ntCreateFileError(err, 0)
|
||||||
|
}
|
||||||
|
defer syscall.CloseHandle(h)
|
||||||
|
|
||||||
|
renameInfoEx := FILE_RENAME_INFORMATION_EX{
|
||||||
|
Flags: FILE_RENAME_REPLACE_IF_EXISTS |
|
||||||
|
FILE_RENAME_POSIX_SEMANTICS,
|
||||||
|
RootDirectory: newdirfd,
|
||||||
|
}
|
||||||
|
p16, err := syscall.UTF16FromString(newpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(p16) > len(renameInfoEx.FileName) {
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
copy(renameInfoEx.FileName[:], p16)
|
||||||
|
renameInfoEx.FileNameLength = uint32((len(p16) - 1) * 2)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileRenameInformation = 10
|
||||||
|
FileRenameInformationEx = 65
|
||||||
|
)
|
||||||
|
err = NtSetInformationFile(
|
||||||
|
h,
|
||||||
|
&IO_STATUS_BLOCK{},
|
||||||
|
uintptr(unsafe.Pointer(&renameInfoEx)),
|
||||||
|
uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION_EX{})),
|
||||||
|
FileRenameInformationEx,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the prior rename failed, the filesystem might not support
|
||||||
|
// POSIX semantics (for example, FAT), or might not have implemented
|
||||||
|
// FILE_RENAME_INFORMATION_EX.
|
||||||
|
//
|
||||||
|
// Try again.
|
||||||
|
renameInfo := FILE_RENAME_INFORMATION{
|
||||||
|
ReplaceIfExists: true,
|
||||||
|
RootDirectory: newdirfd,
|
||||||
|
}
|
||||||
|
copy(renameInfo.FileName[:], p16)
|
||||||
|
renameInfo.FileNameLength = renameInfoEx.FileNameLength
|
||||||
|
|
||||||
|
err = NtSetInformationFile(
|
||||||
|
h,
|
||||||
|
&IO_STATUS_BLOCK{},
|
||||||
|
uintptr(unsafe.Pointer(&renameInfo)),
|
||||||
|
uint32(unsafe.Sizeof(FILE_RENAME_INFORMATION{})),
|
||||||
|
FileRenameInformation,
|
||||||
|
)
|
||||||
|
if st, ok := err.(NTStatus); ok {
|
||||||
|
return st.Errno()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -216,3 +216,25 @@ const (
|
|||||||
FILE_DISPOSITION_ON_CLOSE = 0x00000008
|
FILE_DISPOSITION_ON_CLOSE = 0x00000008
|
||||||
FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE = 0x00000010
|
FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE = 0x00000010
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Flags for FILE_RENAME_INFORMATION_EX.
|
||||||
|
const (
|
||||||
|
FILE_RENAME_REPLACE_IF_EXISTS = 0x00000001
|
||||||
|
FILE_RENAME_POSIX_SEMANTICS = 0x00000002
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_rename_information
|
||||||
|
type FILE_RENAME_INFORMATION struct {
|
||||||
|
ReplaceIfExists bool
|
||||||
|
RootDirectory syscall.Handle
|
||||||
|
FileNameLength uint32
|
||||||
|
FileName [syscall.MAX_PATH]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_rename_information
|
||||||
|
type FILE_RENAME_INFORMATION_EX struct {
|
||||||
|
Flags uint32
|
||||||
|
RootDirectory syscall.Handle
|
||||||
|
FileNameLength uint32
|
||||||
|
FileName [syscall.MAX_PATH]uint16
|
||||||
|
}
|
||||||
|
@ -199,6 +199,13 @@ func (r *Root) Readlink(name string) (string, error) {
|
|||||||
return rootReadlink(r, name)
|
return rootReadlink(r, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename renames (moves) oldname to newname.
|
||||||
|
// Both paths are relative to the root.
|
||||||
|
// See [Rename] for more details.
|
||||||
|
func (r *Root) Rename(oldname, newname string) error {
|
||||||
|
return rootRename(r, oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Root) logOpen(name string) {
|
func (r *Root) logOpen(name string) {
|
||||||
if log := testlog.Logger(); log != nil {
|
if log := testlog.Logger(); log != nil {
|
||||||
// This won't be right if r's name has changed since it was opened,
|
// This won't be right if r's name has changed since it was opened,
|
||||||
|
@ -166,3 +166,17 @@ func rootReadlink(r *Root, name string) (string, error) {
|
|||||||
}
|
}
|
||||||
return name, nil
|
return name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rootRename(r *Root, oldname, newname string) error {
|
||||||
|
if err := checkPathEscapesLstat(r, oldname); err != nil {
|
||||||
|
return &PathError{Op: "renameat", Path: oldname, Err: err}
|
||||||
|
}
|
||||||
|
if err := checkPathEscapesLstat(r, newname); err != nil {
|
||||||
|
return &PathError{Op: "renameat", Path: newname, Err: err}
|
||||||
|
}
|
||||||
|
err := Rename(joinPath(r.root.name, oldname), joinPath(r.root.name, newname))
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"renameat", oldname, newname, underlyingError(err)}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -138,6 +138,19 @@ func rootRemove(r *Root, name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rootRename(r *Root, oldname, newname string) error {
|
||||||
|
_, err := doInRoot(r, oldname, func(oldparent sysfdType, oldname string) (struct{}, error) {
|
||||||
|
_, err := doInRoot(r, newname, func(newparent sysfdType, newname string) (struct{}, error) {
|
||||||
|
return struct{}{}, renameat(oldparent, oldname, newparent, newname)
|
||||||
|
})
|
||||||
|
return struct{}{}, err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"renameat", oldname, newname, err}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// doInRoot performs an operation on a path in a Root.
|
// doInRoot performs an operation on a path in a Root.
|
||||||
//
|
//
|
||||||
// It opens the directory containing the final element of the path,
|
// It opens the directory containing the final element of the path,
|
||||||
|
@ -699,6 +699,91 @@ func TestRootReadlink(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRootRenameFrom tests renaming the test case target to a known-good path.
|
||||||
|
func TestRootRenameFrom(t *testing.T) {
|
||||||
|
want := []byte("target")
|
||||||
|
for _, test := range rootTestCases {
|
||||||
|
test.run(t, func(t *testing.T, target string, root *os.Root) {
|
||||||
|
if target != "" {
|
||||||
|
if err := os.WriteFile(target, want, 0o666); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wantError := test.wantError
|
||||||
|
var linkTarget string
|
||||||
|
if test.ltarget != "" {
|
||||||
|
// Rename will rename the link, not the file linked to.
|
||||||
|
wantError = false
|
||||||
|
var err error
|
||||||
|
linkTarget, err = root.Readlink(test.ltarget)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("root.Readlink(%q) = %v, want success", test.ltarget, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dstPath = "destination"
|
||||||
|
|
||||||
|
// Plan 9 doesn't allow cross-directory renames.
|
||||||
|
if runtime.GOOS == "plan9" && strings.Contains(test.open, "/") {
|
||||||
|
wantError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := root.Rename(test.open, dstPath)
|
||||||
|
if errEndsTest(t, err, wantError, "root.Rename(%q, %q)", test.open, dstPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.ltarget != "" {
|
||||||
|
got, err := os.Readlink(filepath.Join(root.Name(), dstPath))
|
||||||
|
if err != nil || got != linkTarget {
|
||||||
|
t.Errorf("os.Readlink(%q) = %q, %v, want %q", dstPath, got, err, linkTarget)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
got, err := os.ReadFile(filepath.Join(root.Name(), dstPath))
|
||||||
|
if err != nil || !bytes.Equal(got, want) {
|
||||||
|
t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, dstPath, string(got), err, string(want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRootRenameTo tests renaming a known-good path to the test case target.
|
||||||
|
func TestRootRenameTo(t *testing.T) {
|
||||||
|
want := []byte("target")
|
||||||
|
for _, test := range rootTestCases {
|
||||||
|
test.run(t, func(t *testing.T, target string, root *os.Root) {
|
||||||
|
const srcPath = "source"
|
||||||
|
if err := os.WriteFile(filepath.Join(root.Name(), srcPath), want, 0o666); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
target = test.target
|
||||||
|
wantError := test.wantError
|
||||||
|
if test.ltarget != "" {
|
||||||
|
// Rename will overwrite the final link rather than follow it.
|
||||||
|
target = test.ltarget
|
||||||
|
wantError = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plan 9 doesn't allow cross-directory renames.
|
||||||
|
if runtime.GOOS == "plan9" && strings.Contains(test.open, "/") {
|
||||||
|
wantError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := root.Rename(srcPath, test.open)
|
||||||
|
if errEndsTest(t, err, wantError, "root.Rename(%q, %q)", srcPath, test.open) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := os.ReadFile(filepath.Join(root.Name(), target))
|
||||||
|
if err != nil || !bytes.Equal(got, want) {
|
||||||
|
t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, target, string(got), err, string(want))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A rootConsistencyTest is a test case comparing os.Root behavior with
|
// A rootConsistencyTest is a test case comparing os.Root behavior with
|
||||||
// the corresponding non-Root function.
|
// the corresponding non-Root function.
|
||||||
//
|
//
|
||||||
@ -927,14 +1012,19 @@ func (test rootConsistencyTest) run(t *testing.T, f func(t *testing.T, path stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err1 != nil || err2 != nil {
|
if err1 != nil || err2 != nil {
|
||||||
e1, ok := err1.(*os.PathError)
|
underlyingError := func(how string, err error) error {
|
||||||
if !ok {
|
switch e := err1.(type) {
|
||||||
t.Fatalf("with root, expected PathError; got: %v", err1)
|
case *os.PathError:
|
||||||
}
|
return e.Err
|
||||||
e2, ok := err2.(*os.PathError)
|
case *os.LinkError:
|
||||||
if !ok {
|
return e.Err
|
||||||
t.Fatalf("without root, expected PathError; got: %v", err1)
|
default:
|
||||||
|
t.Fatalf("%v, expected PathError or LinkError; got: %v", how, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
e1 := underlyingError("with root", err1)
|
||||||
|
e2 := underlyingError("without root", err1)
|
||||||
detailedErrorMismatch := false
|
detailedErrorMismatch := false
|
||||||
if f := test.detailedErrorMismatch; f != nil {
|
if f := test.detailedErrorMismatch; f != nil {
|
||||||
detailedErrorMismatch = f(t)
|
detailedErrorMismatch = f(t)
|
||||||
@ -943,9 +1033,9 @@ func (test rootConsistencyTest) run(t *testing.T, f func(t *testing.T, path stri
|
|||||||
// Plan9 syscall errors aren't comparable.
|
// Plan9 syscall errors aren't comparable.
|
||||||
detailedErrorMismatch = true
|
detailedErrorMismatch = true
|
||||||
}
|
}
|
||||||
if !detailedErrorMismatch && e1.Err != e2.Err {
|
if !detailedErrorMismatch && e1 != e2 {
|
||||||
t.Errorf("with root: err=%v", e1.Err)
|
t.Errorf("with root: err=%v", e1)
|
||||||
t.Errorf("without root: err=%v", e2.Err)
|
t.Errorf("without root: err=%v", e2)
|
||||||
t.Errorf("want consistent results, got mismatch")
|
t.Errorf("want consistent results, got mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1110,6 +1200,64 @@ func TestRootConsistencyReadlink(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRootConsistencyRename(t *testing.T) {
|
||||||
|
if runtime.GOOS == "plan9" {
|
||||||
|
// This test depends on moving files between directories.
|
||||||
|
t.Skip("Plan 9 does not support cross-directory renames")
|
||||||
|
}
|
||||||
|
// Run this test in two directions:
|
||||||
|
// Renaming the test path to a known-good path (from),
|
||||||
|
// and renaming a known-good path to the test path (to).
|
||||||
|
for _, name := range []string{"from", "to"} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
for _, test := range rootConsistencyTestCases {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
// On Windows, Rename("/path/to/.", x) succeeds,
|
||||||
|
// because Windows cleans the path to just "/path/to".
|
||||||
|
// Root.Rename(".", x) fails as expected.
|
||||||
|
// Don't run this consistency test on Windows.
|
||||||
|
if test.open == "." || test.open == "./" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
|
||||||
|
rename := os.Rename
|
||||||
|
lstat := os.Lstat
|
||||||
|
if r != nil {
|
||||||
|
rename = r.Rename
|
||||||
|
lstat = r.Lstat
|
||||||
|
}
|
||||||
|
|
||||||
|
otherPath := "other"
|
||||||
|
if r == nil {
|
||||||
|
otherPath = filepath.Join(t.TempDir(), otherPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var srcPath, dstPath string
|
||||||
|
if name == "from" {
|
||||||
|
srcPath = path
|
||||||
|
dstPath = otherPath
|
||||||
|
} else {
|
||||||
|
srcPath = otherPath
|
||||||
|
dstPath = path
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rename(srcPath, dstPath); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
fi, err := lstat(dstPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("stat(%q) after successful copy: %v", dstPath, err)
|
||||||
|
return "stat error", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("name:%q size:%v mode:%v isdir:%v", fi.Name(), fi.Size(), fi.Mode(), fi.IsDir()), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRootRenameAfterOpen(t *testing.T) {
|
func TestRootRenameAfterOpen(t *testing.T) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
|
@ -209,6 +209,10 @@ func removeat(fd int, name string) error {
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func renameat(oldfd int, oldname string, newfd int, newname string) error {
|
||||||
|
return unix.Renameat(oldfd, oldname, newfd, newname)
|
||||||
|
}
|
||||||
|
|
||||||
// checkSymlink resolves the symlink name in parent,
|
// checkSymlink resolves the symlink name in parent,
|
||||||
// and returns errSymlink with the link contents.
|
// and returns errSymlink with the link contents.
|
||||||
//
|
//
|
||||||
|
@ -315,6 +315,10 @@ func chtimesat(dirfd syscall.Handle, name string, atime time.Time, mtime time.Ti
|
|||||||
return syscall.SetFileTime(h, nil, &a, &w)
|
return syscall.SetFileTime(h, nil, &a, &w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func renameat(oldfd syscall.Handle, oldname string, newfd syscall.Handle, newname string) error {
|
||||||
|
return windows.Renameat(oldfd, oldname, newfd, newname)
|
||||||
|
}
|
||||||
|
|
||||||
func readlinkat(dirfd syscall.Handle, name string) (string, error) {
|
func readlinkat(dirfd syscall.Handle, name string) (string, error) {
|
||||||
fd, err := openat(dirfd, name, windows.O_OPEN_REPARSE, 0)
|
fd, err := openat(dirfd, name, windows.O_OPEN_REPARSE, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user