mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
os: add Root.Link
For #67002 Change-Id: I223f3f2dbc8b02726f4ce5a017c628c4a20f109a Reviewed-on: https://go-review.googlesource.com/c/go/+/659757 Reviewed-by: Quim Muntal <quimmuntal@gmail.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com>
This commit is contained in:
parent
4ae6ab2bdf
commit
d2d1fd68b6
@ -2,5 +2,6 @@ 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
|
||||
pkg os, method (*Root) Lchown(string, int, int) error #67002
|
||||
pkg os, method (*Root) Link(string, string) error #67002
|
||||
pkg os, method (*Root) Readlink(string) (string, error) #67002
|
||||
pkg os, method (*Root) Rename(string, string) error #67002
|
||||
|
@ -4,5 +4,6 @@ The [os.Root] type supports the following additional methods:
|
||||
* [os.Root.Chown]
|
||||
* [os.Root.Chtimes]
|
||||
* [os.Root.Lchown]
|
||||
* [os.Root.Link]
|
||||
* [os.Root.Readlink]
|
||||
* [os.Root.Rename]
|
||||
|
@ -28,3 +28,4 @@ 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_fchownat_trampoline(SB),NOSPLIT,$0-0; JMP libc_fchownat(SB)
|
||||
TEXT ·libc_renameat_trampoline(SB),NOSPLIT,$0-0; JMP libc_renameat(SB)
|
||||
TEXT ·libc_linkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_linkat(SB)
|
||||
|
@ -20,3 +20,5 @@ TEXT ·libc_fchownat_trampoline(SB),NOSPLIT,$0-0
|
||||
JMP libc_fchownat(SB)
|
||||
TEXT ·libc_renameat_trampoline(SB),NOSPLIT,$0-0
|
||||
JMP libc_renameat(SB)
|
||||
TEXT ·libc_linkat_trampoline(SB),NOSPLIT,$0-0
|
||||
JMP libc_linkat(SB)
|
||||
|
@ -136,3 +136,25 @@ func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int) error {
|
||||
oldp, err := syscall.BytePtrFromString(oldpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newp, err := syscall.BytePtrFromString(newpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, errno := syscall.Syscall6(linkatTrap,
|
||||
uintptr(olddirfd),
|
||||
uintptr(unsafe.Pointer(oldp)),
|
||||
uintptr(newdirfd),
|
||||
uintptr(unsafe.Pointer(newp)),
|
||||
uintptr(flag),
|
||||
0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ package unix
|
||||
//go:cgo_import_dynamic libc_fchmodat fchmodat "libc.a/shr_64.o"
|
||||
//go:cgo_import_dynamic libc_fchownat fchownat "libc.a/shr_64.o"
|
||||
//go:cgo_import_dynamic libc_fstatat fstatat "libc.a/shr_64.o"
|
||||
//go:cgo_import_dynamic libc_linkat linkat "libc.a/shr_64.o"
|
||||
//go:cgo_import_dynamic libc_openat openat "libc.a/shr_64.o"
|
||||
//go:cgo_import_dynamic libc_renameat renameat "libc.a/shr_64.o"
|
||||
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.a/shr_64.o"
|
||||
|
@ -128,3 +128,29 @@ func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func libc_linkat_trampoline()
|
||||
|
||||
//go:cgo_import_dynamic libc_linkat linkat "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int) 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_linkat_trampoline),
|
||||
uintptr(olddirfd),
|
||||
uintptr(unsafe.Pointer(oldp)),
|
||||
uintptr(newdirfd),
|
||||
uintptr(unsafe.Pointer(newp)),
|
||||
uintptr(flag),
|
||||
0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
//go:linkname procFchmodat libc_fchmodat
|
||||
//go:linkname procFchownat libc_fchownat
|
||||
//go:linkname procRenameat libc_renameat
|
||||
//go:linkname procLinkat libc_linkat
|
||||
|
||||
var (
|
||||
procFstatat,
|
||||
@ -28,7 +29,8 @@ var (
|
||||
procMkdirat,
|
||||
procFchmodat,
|
||||
procFchownat,
|
||||
procRenameat uintptr
|
||||
procRenameat,
|
||||
procLinkat uintptr
|
||||
)
|
||||
|
||||
func Unlinkat(dirfd int, path string, flags int) error {
|
||||
@ -184,3 +186,25 @@ func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int) 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(&procLinkat)), 5,
|
||||
uintptr(olddirfd),
|
||||
uintptr(unsafe.Pointer(oldp)),
|
||||
uintptr(newdirfd),
|
||||
uintptr(unsafe.Pointer(newp)),
|
||||
uintptr(flag),
|
||||
0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -119,3 +119,29 @@ func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func libc_linkat_trampoline()
|
||||
|
||||
//go:cgo_import_dynamic libc_linkat linkat "libc.so"
|
||||
|
||||
func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int) 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_linkat_trampoline),
|
||||
uintptr(olddirfd),
|
||||
uintptr(unsafe.Pointer(oldp)),
|
||||
uintptr(newdirfd),
|
||||
uintptr(unsafe.Pointer(newp)),
|
||||
uintptr(flag),
|
||||
0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, e
|
||||
//go:cgo_import_dynamic libc_fchmodat fchmodat "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_linkat linkat "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"
|
||||
|
@ -15,6 +15,7 @@ const (
|
||||
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||
linkatTrap uintptr = syscall.SYS_LINKAT
|
||||
|
||||
AT_EACCESS = 0x4
|
||||
AT_FDCWD = 0xfffafdcd
|
||||
|
@ -22,4 +22,5 @@ const (
|
||||
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||
linkatTrap uintptr = syscall.SYS_LINKAT
|
||||
)
|
||||
|
@ -13,6 +13,7 @@ const (
|
||||
mkdiratTrap uintptr = syscall.SYS_MKDIRAT
|
||||
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||
linkatTrap uintptr = syscall.SYS_LINKAT
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -15,6 +15,7 @@ const (
|
||||
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||
linkatTrap uintptr = syscall.SYS_LINKAT
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -129,6 +129,25 @@ func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error
|
||||
))
|
||||
}
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 path_link
|
||||
//go:noescape
|
||||
func path_link(oldFd int32, oldFlags uint32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno
|
||||
|
||||
func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int) error {
|
||||
if oldpath == "" || newpath == "" {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
return errnoErr(path_link(
|
||||
int32(olddirfd),
|
||||
0,
|
||||
unsafe.StringData(oldpath),
|
||||
size(len(oldpath)),
|
||||
int32(newdirfd),
|
||||
unsafe.StringData(newpath),
|
||||
size(len(newpath)),
|
||||
))
|
||||
}
|
||||
|
||||
//go:wasmimport wasi_snapshot_preview1 path_create_directory
|
||||
//go:noescape
|
||||
func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno
|
||||
|
@ -328,3 +328,51 @@ func Renameat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Linkat(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|FILE_WRITE_ATTRIBUTES,
|
||||
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)
|
||||
|
||||
linkInfo := FILE_LINK_INFORMATION{
|
||||
RootDirectory: newdirfd,
|
||||
}
|
||||
p16, err := syscall.UTF16FromString(newpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(p16) > len(linkInfo.FileName) {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
copy(linkInfo.FileName[:], p16)
|
||||
linkInfo.FileNameLength = uint32((len(p16) - 1) * 2)
|
||||
|
||||
const (
|
||||
FileLinkInformation = 11
|
||||
)
|
||||
err = NtSetInformationFile(
|
||||
h,
|
||||
&IO_STATUS_BLOCK{},
|
||||
uintptr(unsafe.Pointer(&linkInfo)),
|
||||
uint32(unsafe.Sizeof(FILE_LINK_INFORMATION{})),
|
||||
FileLinkInformation,
|
||||
)
|
||||
if st, ok := err.(NTStatus); ok {
|
||||
return st.Errno()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -238,3 +238,11 @@ type FILE_RENAME_INFORMATION_EX struct {
|
||||
FileNameLength uint32
|
||||
FileName [syscall.MAX_PATH]uint16
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_file_link_information
|
||||
type FILE_LINK_INFORMATION struct {
|
||||
ReplaceIfExists bool
|
||||
RootDirectory syscall.Handle
|
||||
FileNameLength uint32
|
||||
FileName [syscall.MAX_PATH]uint16
|
||||
}
|
||||
|
@ -850,34 +850,49 @@ func TestReaddirOfFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHardLink(t *testing.T) {
|
||||
testMaybeRooted(t, testHardLink)
|
||||
}
|
||||
func testHardLink(t *testing.T, root *Root) {
|
||||
testenv.MustHaveLink(t)
|
||||
t.Chdir(t.TempDir())
|
||||
|
||||
var (
|
||||
create = Create
|
||||
link = Link
|
||||
stat = Stat
|
||||
op = "link"
|
||||
)
|
||||
if root != nil {
|
||||
create = root.Create
|
||||
link = root.Link
|
||||
stat = root.Stat
|
||||
op = "linkat"
|
||||
}
|
||||
|
||||
from, to := "hardlinktestfrom", "hardlinktestto"
|
||||
file, err := Create(to)
|
||||
file, err := create(to)
|
||||
if err != nil {
|
||||
t.Fatalf("open %q failed: %v", to, err)
|
||||
}
|
||||
if err = file.Close(); err != nil {
|
||||
t.Errorf("close %q failed: %v", to, err)
|
||||
}
|
||||
err = Link(to, from)
|
||||
err = link(to, from)
|
||||
if err != nil {
|
||||
t.Fatalf("link %q, %q failed: %v", to, from, err)
|
||||
}
|
||||
|
||||
none := "hardlinktestnone"
|
||||
err = Link(none, none)
|
||||
err = link(none, none)
|
||||
// Check the returned error is well-formed.
|
||||
if lerr, ok := err.(*LinkError); !ok || lerr.Error() == "" {
|
||||
t.Errorf("link %q, %q failed to return a valid error", none, none)
|
||||
}
|
||||
|
||||
tostat, err := Stat(to)
|
||||
tostat, err := stat(to)
|
||||
if err != nil {
|
||||
t.Fatalf("stat %q failed: %v", to, err)
|
||||
}
|
||||
fromstat, err := Stat(from)
|
||||
fromstat, err := stat(from)
|
||||
if err != nil {
|
||||
t.Fatalf("stat %q failed: %v", from, err)
|
||||
}
|
||||
@ -885,11 +900,11 @@ func TestHardLink(t *testing.T) {
|
||||
t.Errorf("link %q, %q did not create hard link", to, from)
|
||||
}
|
||||
// We should not be able to perform the same Link() a second time
|
||||
err = Link(to, from)
|
||||
err = link(to, from)
|
||||
switch err := err.(type) {
|
||||
case *LinkError:
|
||||
if err.Op != "link" {
|
||||
t.Errorf("Link(%q, %q) err.Op = %q; want %q", to, from, err.Op, "link")
|
||||
if err.Op != op {
|
||||
t.Errorf("Link(%q, %q) err.Op = %q; want %q", to, from, err.Op, op)
|
||||
}
|
||||
if err.Old != to {
|
||||
t.Errorf("Link(%q, %q) err.Old = %q; want %q", to, from, err.Old, to)
|
||||
|
@ -206,6 +206,18 @@ func (r *Root) Rename(oldname, newname string) error {
|
||||
return rootRename(r, oldname, newname)
|
||||
}
|
||||
|
||||
// Link creates newname as a hard link to the oldname file.
|
||||
// Both paths are relative to the root.
|
||||
// See [Link] for more details.
|
||||
//
|
||||
// If oldname is a symbolic link, Link creates new link to oldname and not its target.
|
||||
// This behavior may differ from that of [Link] on some platforms.
|
||||
//
|
||||
// When GOOS=js, Link returns an error if oldname is a symbolic link.
|
||||
func (r *Root) Link(oldname, newname string) error {
|
||||
return rootLink(r, oldname, newname)
|
||||
}
|
||||
|
||||
func (r *Root) logOpen(name string) {
|
||||
if log := testlog.Logger(); log != nil {
|
||||
// This won't be right if r's name has changed since it was opened,
|
||||
|
@ -180,3 +180,21 @@ func rootRename(r *Root, oldname, newname string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rootLink(r *Root, oldname, newname string) error {
|
||||
if err := checkPathEscapesLstat(r, oldname); err != nil {
|
||||
return &PathError{Op: "linkat", Path: oldname, Err: err}
|
||||
}
|
||||
fullOldName := joinPath(r.root.name, oldname)
|
||||
if fs, err := Lstat(fullOldName); err == nil && fs.Mode()&ModeSymlink != 0 {
|
||||
return &PathError{Op: "linkat", Path: oldname, Err: errors.New("cannot create a hard link to a symlink")}
|
||||
}
|
||||
if err := checkPathEscapesLstat(r, newname); err != nil {
|
||||
return &PathError{Op: "linkat", Path: newname, Err: err}
|
||||
}
|
||||
err := Link(fullOldName, joinPath(r.root.name, newname))
|
||||
if err != nil {
|
||||
return &LinkError{"linkat", oldname, newname, underlyingError(err)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -151,6 +151,19 @@ func rootRename(r *Root, oldname, newname string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func rootLink(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{}{}, linkat(oldparent, oldname, newparent, newname)
|
||||
})
|
||||
return struct{}{}, err
|
||||
})
|
||||
if err != nil {
|
||||
return &LinkError{"linkat", oldname, newname, err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// doInRoot performs an operation on a path in a Root.
|
||||
//
|
||||
// It opens the directory containing the final element of the path,
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/testenv"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
@ -701,6 +702,16 @@ func TestRootReadlink(t *testing.T) {
|
||||
|
||||
// TestRootRenameFrom tests renaming the test case target to a known-good path.
|
||||
func TestRootRenameFrom(t *testing.T) {
|
||||
testRootMoveFrom(t, true)
|
||||
}
|
||||
|
||||
// TestRootRenameFrom tests linking the test case target to a known-good path.
|
||||
func TestRootLinkFrom(t *testing.T) {
|
||||
testenv.MustHaveLink(t)
|
||||
testRootMoveFrom(t, false)
|
||||
}
|
||||
|
||||
func testRootMoveFrom(t *testing.T, rename bool) {
|
||||
want := []byte("target")
|
||||
for _, test := range rootTestCases {
|
||||
test.run(t, func(t *testing.T, target string, root *os.Root) {
|
||||
@ -719,6 +730,11 @@ func TestRootRenameFrom(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("root.Readlink(%q) = %v, want success", test.ltarget, err)
|
||||
}
|
||||
|
||||
// When GOOS=js, creating a hard link to a symlink fails.
|
||||
if !rename && runtime.GOOS == "js" {
|
||||
wantError = true
|
||||
}
|
||||
}
|
||||
|
||||
const dstPath = "destination"
|
||||
@ -728,28 +744,67 @@ func TestRootRenameFrom(t *testing.T) {
|
||||
wantError = true
|
||||
}
|
||||
|
||||
err := root.Rename(test.open, dstPath)
|
||||
if errEndsTest(t, err, wantError, "root.Rename(%q, %q)", test.open, dstPath) {
|
||||
var op string
|
||||
var err error
|
||||
if rename {
|
||||
op = "Rename"
|
||||
err = root.Rename(test.open, dstPath)
|
||||
} else {
|
||||
op = "Link"
|
||||
err = root.Link(test.open, dstPath)
|
||||
}
|
||||
if errEndsTest(t, err, wantError, "root.%v(%q, %q)", op, test.open, dstPath) {
|
||||
return
|
||||
}
|
||||
|
||||
origPath := target
|
||||
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)
|
||||
origPath = filepath.Join(root.Name(), test.ltarget)
|
||||
}
|
||||
_, err = os.Lstat(origPath)
|
||||
if rename {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
t.Errorf("after renaming file, Lstat(%q) = %v, want ErrNotExist", origPath, err)
|
||||
}
|
||||
} 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))
|
||||
if err != nil {
|
||||
t.Errorf("after linking file, error accessing original: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
dstFullPath := filepath.Join(root.Name(), dstPath)
|
||||
if test.ltarget != "" {
|
||||
got, err := os.Readlink(dstFullPath)
|
||||
if err != nil || got != linkTarget {
|
||||
t.Errorf("os.Readlink(%q) = %q, %v, want %q", dstFullPath, got, err, linkTarget)
|
||||
}
|
||||
} else {
|
||||
got, err := os.ReadFile(dstFullPath)
|
||||
if err != nil || !bytes.Equal(got, want) {
|
||||
t.Errorf(`os.ReadFile(%q): read content %q, %v; want %q`, dstFullPath, string(got), err, string(want))
|
||||
}
|
||||
st, err := os.Lstat(dstFullPath)
|
||||
if err != nil || st.Mode()&fs.ModeSymlink != 0 {
|
||||
t.Errorf(`os.Lstat(%q) = %v, %v; want non-symlink`, dstFullPath, st.Mode(), err)
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRootRenameTo tests renaming a known-good path to the test case target.
|
||||
func TestRootRenameTo(t *testing.T) {
|
||||
testRootMoveTo(t, true)
|
||||
}
|
||||
|
||||
// TestRootLinkTo tests renaming a known-good path to the test case target.
|
||||
func TestRootLinkTo(t *testing.T) {
|
||||
testenv.MustHaveLink(t)
|
||||
testRootMoveTo(t, true)
|
||||
}
|
||||
|
||||
func testRootMoveTo(t *testing.T, rename bool) {
|
||||
want := []byte("target")
|
||||
for _, test := range rootTestCases {
|
||||
test.run(t, func(t *testing.T, target string, root *os.Root) {
|
||||
@ -771,11 +826,30 @@ func TestRootRenameTo(t *testing.T) {
|
||||
wantError = true
|
||||
}
|
||||
|
||||
err := root.Rename(srcPath, test.open)
|
||||
if errEndsTest(t, err, wantError, "root.Rename(%q, %q)", srcPath, test.open) {
|
||||
var err error
|
||||
var op string
|
||||
if rename {
|
||||
op = "Rename"
|
||||
err = root.Rename(srcPath, test.open)
|
||||
} else {
|
||||
op = "Link"
|
||||
err = root.Link(srcPath, test.open)
|
||||
}
|
||||
if errEndsTest(t, err, wantError, "root.%v(%q, %q)", op, srcPath, test.open) {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = os.Lstat(filepath.Join(root.Name(), srcPath))
|
||||
if rename {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
t.Errorf("after renaming file, Lstat(%q) = %v, want ErrNotExist", srcPath, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("after linking file, error accessing original: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
@ -1201,6 +1275,15 @@ func TestRootConsistencyReadlink(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRootConsistencyRename(t *testing.T) {
|
||||
testRootConsistencyMove(t, true)
|
||||
}
|
||||
|
||||
func TestRootConsistencyLink(t *testing.T) {
|
||||
testenv.MustHaveLink(t)
|
||||
testRootConsistencyMove(t, false)
|
||||
}
|
||||
|
||||
func testRootConsistencyMove(t *testing.T, rename bool) {
|
||||
if runtime.GOOS == "plan9" {
|
||||
// This test depends on moving files between directories.
|
||||
t.Skip("Plan 9 does not support cross-directory renames")
|
||||
@ -1222,10 +1305,19 @@ func TestRootConsistencyRename(t *testing.T) {
|
||||
}
|
||||
|
||||
test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
|
||||
rename := os.Rename
|
||||
var move func(oldname, newname string) error
|
||||
switch {
|
||||
case rename && r == nil:
|
||||
move = os.Rename
|
||||
case rename && r != nil:
|
||||
move = r.Rename
|
||||
case !rename && r == nil:
|
||||
move = os.Link
|
||||
case !rename && r != nil:
|
||||
move = r.Link
|
||||
}
|
||||
lstat := os.Lstat
|
||||
if r != nil {
|
||||
rename = r.Rename
|
||||
lstat = r.Lstat
|
||||
}
|
||||
|
||||
@ -1243,7 +1335,21 @@ func TestRootConsistencyRename(t *testing.T) {
|
||||
dstPath = path
|
||||
}
|
||||
|
||||
if err := rename(srcPath, dstPath); err != nil {
|
||||
if !rename {
|
||||
// When the source is a symlink, Root.Link creates
|
||||
// a hard link to the symlink.
|
||||
// os.Link does whatever the link syscall does,
|
||||
// which varies between operating systems and
|
||||
// their versions.
|
||||
// Skip running the consistency test when
|
||||
// the source is a symlink.
|
||||
fi, err := lstat(srcPath)
|
||||
if err == nil && fi.Mode()&os.ModeSymlink != 0 {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := move(srcPath, dstPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
fi, err := lstat(dstPath)
|
||||
|
@ -213,6 +213,10 @@ func renameat(oldfd int, oldname string, newfd int, newname string) error {
|
||||
return unix.Renameat(oldfd, oldname, newfd, newname)
|
||||
}
|
||||
|
||||
func linkat(oldfd int, oldname string, newfd int, newname string) error {
|
||||
return unix.Linkat(oldfd, oldname, newfd, newname, 0)
|
||||
}
|
||||
|
||||
// checkSymlink resolves the symlink name in parent,
|
||||
// and returns errSymlink with the link contents.
|
||||
//
|
||||
|
@ -319,6 +319,10 @@ func renameat(oldfd syscall.Handle, oldname string, newfd syscall.Handle, newnam
|
||||
return windows.Renameat(oldfd, oldname, newfd, newname)
|
||||
}
|
||||
|
||||
func linkat(oldfd syscall.Handle, oldname string, newfd syscall.Handle, newname string) error {
|
||||
return windows.Linkat(oldfd, oldname, newfd, newname)
|
||||
}
|
||||
|
||||
func readlinkat(dirfd syscall.Handle, name string) (string, error) {
|
||||
fd, err := openat(dirfd, name, windows.O_OPEN_REPARSE, 0)
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user