mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
os: add Root.Symlink
For #67002 Change-Id: Ia1637b61eae49e97e1d07f058ad2390e74cd3403 Reviewed-on: https://go-review.googlesource.com/c/go/+/660635 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Quim Muntal <quimmuntal@gmail.com> Auto-Submit: Damien Neil <dneil@google.com>
This commit is contained in:
parent
656b5b3abe
commit
26fdb07d4c
@ -5,3 +5,4 @@ pkg os, method (*Root) Lchown(string, int, int) error #67002
|
|||||||
pkg os, method (*Root) Link(string, string) error #67002
|
pkg os, method (*Root) Link(string, string) 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
|
pkg os, method (*Root) Rename(string, string) error #67002
|
||||||
|
pkg os, method (*Root) Symlink(string, string) error #67002
|
||||||
|
@ -7,3 +7,4 @@ The [os.Root] type supports the following additional methods:
|
|||||||
* [os.Root.Link]
|
* [os.Root.Link]
|
||||||
* [os.Root.Readlink]
|
* [os.Root.Readlink]
|
||||||
* [os.Root.Rename]
|
* [os.Root.Rename]
|
||||||
|
* [os.Root.Symlink]
|
||||||
|
@ -29,3 +29,4 @@ 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)
|
TEXT ·libc_renameat_trampoline(SB),NOSPLIT,$0-0; JMP libc_renameat(SB)
|
||||||
TEXT ·libc_linkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_linkat(SB)
|
TEXT ·libc_linkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_linkat(SB)
|
||||||
|
TEXT ·libc_symlinkat_trampoline(SB),NOSPLIT,$0-0; JMP libc_symlinkat(SB)
|
||||||
|
@ -22,3 +22,5 @@ TEXT ·libc_renameat_trampoline(SB),NOSPLIT,$0-0
|
|||||||
JMP libc_renameat(SB)
|
JMP libc_renameat(SB)
|
||||||
TEXT ·libc_linkat_trampoline(SB),NOSPLIT,$0-0
|
TEXT ·libc_linkat_trampoline(SB),NOSPLIT,$0-0
|
||||||
JMP libc_linkat(SB)
|
JMP libc_linkat(SB)
|
||||||
|
TEXT ·libc_symlinkat_trampoline(SB),NOSPLIT,$0-0
|
||||||
|
JMP libc_symlinkat(SB)
|
||||||
|
@ -158,3 +158,22 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Symlinkat(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.Syscall(symlinkatTrap,
|
||||||
|
uintptr(unsafe.Pointer(oldp)),
|
||||||
|
uintptr(newdirfd),
|
||||||
|
uintptr(unsafe.Pointer(newp)))
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -10,6 +10,7 @@ package unix
|
|||||||
//go:cgo_import_dynamic libc_linkat linkat "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_openat openat "libc.a/shr_64.o"
|
||||||
//go:cgo_import_dynamic libc_renameat renameat "libc.a/shr_64.o"
|
//go:cgo_import_dynamic libc_renameat renameat "libc.a/shr_64.o"
|
||||||
|
//go:cgo_import_dynamic libc_symlinkat symlinkat "libc.a/shr_64.o"
|
||||||
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.a/shr_64.o"
|
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.a/shr_64.o"
|
||||||
//go:cgo_import_dynamic libc_readlinkat readlinkat "libc.a/shr_64.o"
|
//go:cgo_import_dynamic libc_readlinkat readlinkat "libc.a/shr_64.o"
|
||||||
//go:cgo_import_dynamic libc_mkdirat mkdirat "libc.a/shr_64.o"
|
//go:cgo_import_dynamic libc_mkdirat mkdirat "libc.a/shr_64.o"
|
||||||
|
@ -154,3 +154,29 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func libc_symlinkat_trampoline()
|
||||||
|
|
||||||
|
//go:cgo_import_dynamic libc_symlinkat symlinkat "/usr/lib/libSystem.B.dylib"
|
||||||
|
|
||||||
|
func Symlinkat(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_symlinkat_trampoline),
|
||||||
|
uintptr(unsafe.Pointer(oldp)),
|
||||||
|
uintptr(newdirfd),
|
||||||
|
uintptr(unsafe.Pointer(newp)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
//go:linkname procFchownat libc_fchownat
|
//go:linkname procFchownat libc_fchownat
|
||||||
//go:linkname procRenameat libc_renameat
|
//go:linkname procRenameat libc_renameat
|
||||||
//go:linkname procLinkat libc_linkat
|
//go:linkname procLinkat libc_linkat
|
||||||
|
//go:linkname procSymlinkat libc_symlinkat
|
||||||
|
|
||||||
var (
|
var (
|
||||||
procFstatat,
|
procFstatat,
|
||||||
@ -30,7 +31,8 @@ var (
|
|||||||
procFchmodat,
|
procFchmodat,
|
||||||
procFchownat,
|
procFchownat,
|
||||||
procRenameat,
|
procRenameat,
|
||||||
procLinkat uintptr
|
procLinkat,
|
||||||
|
procSymlinkat uintptr
|
||||||
)
|
)
|
||||||
|
|
||||||
func Unlinkat(dirfd int, path string, flags int) error {
|
func Unlinkat(dirfd int, path string, flags int) error {
|
||||||
@ -208,3 +210,23 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Symlinkat(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(&procSymlinkat)), 3,
|
||||||
|
uintptr(unsafe.Pointer(oldp)),
|
||||||
|
uintptr(newdirfd),
|
||||||
|
uintptr(unsafe.Pointer(newp)),
|
||||||
|
0, 0, 0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -145,3 +145,29 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func libc_symlinkat_trampoline()
|
||||||
|
|
||||||
|
//go:cgo_import_dynamic libc_symlinkat symlinkat "libc.so"
|
||||||
|
|
||||||
|
func Symlinkat(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_symlinkat_trampoline),
|
||||||
|
uintptr(unsafe.Pointer(oldp)),
|
||||||
|
uintptr(newdirfd),
|
||||||
|
uintptr(unsafe.Pointer(newp)),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, e
|
|||||||
//go:cgo_import_dynamic libc_linkat linkat "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_openat openat "libc.so"
|
||||||
//go:cgo_import_dynamic libc_renameat renameat "libc.so"
|
//go:cgo_import_dynamic libc_renameat renameat "libc.so"
|
||||||
|
//go:cgo_import_dynamic libc_symlinkat symlinkat "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"
|
||||||
|
@ -16,6 +16,7 @@ const (
|
|||||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||||
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||||
linkatTrap uintptr = syscall.SYS_LINKAT
|
linkatTrap uintptr = syscall.SYS_LINKAT
|
||||||
|
symlinkatTrap uintptr = syscall.SYS_SYMLINKAT
|
||||||
|
|
||||||
AT_EACCESS = 0x4
|
AT_EACCESS = 0x4
|
||||||
AT_FDCWD = 0xfffafdcd
|
AT_FDCWD = 0xfffafdcd
|
||||||
|
@ -23,4 +23,5 @@ const (
|
|||||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||||
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||||
linkatTrap uintptr = syscall.SYS_LINKAT
|
linkatTrap uintptr = syscall.SYS_LINKAT
|
||||||
|
symlinkatTrap uintptr = syscall.SYS_SYMLINKAT
|
||||||
)
|
)
|
||||||
|
@ -14,6 +14,7 @@ const (
|
|||||||
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
fchmodatTrap uintptr = syscall.SYS_FCHMODAT
|
||||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||||
linkatTrap uintptr = syscall.SYS_LINKAT
|
linkatTrap uintptr = syscall.SYS_LINKAT
|
||||||
|
symlinkatTrap uintptr = syscall.SYS_SYMLINKAT
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -16,6 +16,7 @@ const (
|
|||||||
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
fchownatTrap uintptr = syscall.SYS_FCHOWNAT
|
||||||
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
renameatTrap uintptr = syscall.SYS_RENAMEAT
|
||||||
linkatTrap uintptr = syscall.SYS_LINKAT
|
linkatTrap uintptr = syscall.SYS_LINKAT
|
||||||
|
symlinkatTrap uintptr = syscall.SYS_SYMLINKAT
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -101,6 +101,10 @@ func Mkdirat(dirfd int, path string, mode uint32) error {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:wasmimport wasi_snapshot_preview1 path_create_directory
|
||||||
|
//go:noescape
|
||||||
|
func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno
|
||||||
|
|
||||||
func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
|
func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
|
||||||
// WASI preview 1 doesn't support changing file modes.
|
// WASI preview 1 doesn't support changing file modes.
|
||||||
return syscall.ENOSYS
|
return syscall.ENOSYS
|
||||||
@ -111,10 +115,6 @@ 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 {
|
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error {
|
||||||
if oldpath == "" || newpath == "" {
|
if oldpath == "" || newpath == "" {
|
||||||
return syscall.EINVAL
|
return syscall.EINVAL
|
||||||
@ -129,9 +129,9 @@ func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string) error
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:wasmimport wasi_snapshot_preview1 path_link
|
//go:wasmimport wasi_snapshot_preview1 path_rename
|
||||||
//go:noescape
|
//go:noescape
|
||||||
func path_link(oldFd int32, oldFlags uint32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno
|
func path_rename(oldFd int32, 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 {
|
func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int) error {
|
||||||
if oldpath == "" || newpath == "" {
|
if oldpath == "" || newpath == "" {
|
||||||
@ -148,9 +148,26 @@ func Linkat(olddirfd int, oldpath string, newdirfd int, newpath string, flag int
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:wasmimport wasi_snapshot_preview1 path_create_directory
|
//go:wasmimport wasi_snapshot_preview1 path_link
|
||||||
//go:noescape
|
//go:noescape
|
||||||
func path_create_directory(fd int32, path *byte, pathLen size) syscall.Errno
|
func path_link(oldFd int32, oldFlags uint32, oldPath *byte, oldPathLen size, newFd int32, newPath *byte, newPathLen size) syscall.Errno
|
||||||
|
|
||||||
|
func Symlinkat(oldpath string, newdirfd int, newpath string) error {
|
||||||
|
if oldpath == "" || newpath == "" {
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
return errnoErr(path_symlink(
|
||||||
|
unsafe.StringData(oldpath),
|
||||||
|
size(len(oldpath)),
|
||||||
|
int32(newdirfd),
|
||||||
|
unsafe.StringData(newpath),
|
||||||
|
size(len(newpath)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:wasmimport wasi_snapshot_preview1 path_symlink
|
||||||
|
//go:noescape
|
||||||
|
func path_symlink(oldPath *byte, oldPathLen size, fd int32, newPath *byte, newPathLen size) syscall.Errno
|
||||||
|
|
||||||
func errnoErr(errno syscall.Errno) error {
|
func errnoErr(errno syscall.Errno) error {
|
||||||
if errno == 0 {
|
if errno == 0 {
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
package windows
|
package windows
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
|
"structs"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@ -376,3 +378,176 @@ func Linkat(olddirfd syscall.Handle, oldpath string, newdirfd syscall.Handle, ne
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SymlinkatFlags configure Symlinkat.
|
||||||
|
//
|
||||||
|
// Symbolic links have two properties: They may be directory or file links,
|
||||||
|
// and they may be absolute or relative.
|
||||||
|
//
|
||||||
|
// The Windows API defines flags describing these properties
|
||||||
|
// (SYMBOLIC_LINK_FLAG_DIRECTORY and SYMLINK_FLAG_RELATIVE),
|
||||||
|
// but the flags are passed to different system calls and
|
||||||
|
// do not have distinct values, so we define our own enumeration
|
||||||
|
// that permits expressing both.
|
||||||
|
type SymlinkatFlags uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
SYMLINKAT_DIRECTORY = SymlinkatFlags(1 << iota)
|
||||||
|
SYMLINKAT_RELATIVE
|
||||||
|
)
|
||||||
|
|
||||||
|
func Symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
|
||||||
|
// Temporarily acquire symlink-creating privileges if possible.
|
||||||
|
// This is the behavior of CreateSymbolicLinkW.
|
||||||
|
//
|
||||||
|
// (When passed the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag,
|
||||||
|
// CreateSymbolicLinkW ignores errors in acquiring privileges, as we do here.)
|
||||||
|
return withPrivilege("SeCreateSymbolicLinkPrivilege", func() error {
|
||||||
|
return symlinkat(oldname, newdirfd, newname, flags)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func symlinkat(oldname string, newdirfd syscall.Handle, newname string, flags SymlinkatFlags) error {
|
||||||
|
oldnameu16, err := syscall.UTF16FromString(oldname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oldnameu16 = oldnameu16[:len(oldnameu16)-1] // trim off terminal NUL
|
||||||
|
|
||||||
|
var options uint32
|
||||||
|
if flags&SYMLINKAT_DIRECTORY != 0 {
|
||||||
|
options |= FILE_DIRECTORY_FILE
|
||||||
|
} else {
|
||||||
|
options |= FILE_NON_DIRECTORY_FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
objAttrs := &OBJECT_ATTRIBUTES{}
|
||||||
|
if err := objAttrs.init(newdirfd, newname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var h syscall.Handle
|
||||||
|
err = NtCreateFile(
|
||||||
|
&h,
|
||||||
|
SYNCHRONIZE|FILE_WRITE_ATTRIBUTES|DELETE,
|
||||||
|
objAttrs,
|
||||||
|
&IO_STATUS_BLOCK{},
|
||||||
|
nil,
|
||||||
|
syscall.FILE_ATTRIBUTE_NORMAL,
|
||||||
|
0,
|
||||||
|
FILE_CREATE,
|
||||||
|
FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|options,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return ntCreateFileError(err, 0)
|
||||||
|
}
|
||||||
|
defer syscall.CloseHandle(h)
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/ns-ntifs-_reparse_data_buffer
|
||||||
|
type reparseDataBufferT struct {
|
||||||
|
_ structs.HostLayout
|
||||||
|
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
Flags uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
headerSize = uint16(unsafe.Offsetof(reparseDataBufferT{}.SubstituteNameOffset))
|
||||||
|
bufferSize = uint16(unsafe.Sizeof(reparseDataBufferT{}))
|
||||||
|
)
|
||||||
|
|
||||||
|
// Data buffer containing a SymbolicLinkReparseBuffer followed by the link target.
|
||||||
|
rdbbuf := make([]byte, bufferSize+uint16(2*len(oldnameu16)))
|
||||||
|
|
||||||
|
rdb := (*reparseDataBufferT)(unsafe.Pointer(&rdbbuf[0]))
|
||||||
|
rdb.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
|
||||||
|
rdb.ReparseDataLength = uint16(len(rdbbuf)) - uint16(headerSize)
|
||||||
|
rdb.SubstituteNameOffset = 0
|
||||||
|
rdb.SubstituteNameLength = uint16(2 * len(oldnameu16))
|
||||||
|
rdb.PrintNameOffset = 0
|
||||||
|
rdb.PrintNameLength = rdb.SubstituteNameLength
|
||||||
|
if flags&SYMLINKAT_RELATIVE != 0 {
|
||||||
|
rdb.Flags = SYMLINK_FLAG_RELATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
namebuf := rdbbuf[bufferSize:]
|
||||||
|
copy(namebuf, unsafe.String((*byte)(unsafe.Pointer(&oldnameu16[0])), 2*len(oldnameu16)))
|
||||||
|
|
||||||
|
err = syscall.DeviceIoControl(
|
||||||
|
h,
|
||||||
|
FSCTL_SET_REPARSE_POINT,
|
||||||
|
&rdbbuf[0],
|
||||||
|
uint32(len(rdbbuf)),
|
||||||
|
nil,
|
||||||
|
0,
|
||||||
|
nil,
|
||||||
|
nil)
|
||||||
|
if err != nil {
|
||||||
|
// Creating the symlink has failed, so try to remove the file.
|
||||||
|
const FileDispositionInformation = 13
|
||||||
|
NtSetInformationFile(
|
||||||
|
h,
|
||||||
|
&IO_STATUS_BLOCK{},
|
||||||
|
uintptr(unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
|
||||||
|
DeleteFile: true,
|
||||||
|
})),
|
||||||
|
uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
|
||||||
|
FileDispositionInformation,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// withPrivilege temporariliy acquires the named privilege and runs f.
|
||||||
|
// If the privilege cannot be acquired it runs f anyway,
|
||||||
|
// which should fail with an appropriate error.
|
||||||
|
func withPrivilege(privilege string, f func() error) error {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
err := ImpersonateSelf(SecurityImpersonation)
|
||||||
|
if err != nil {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
defer RevertToSelf()
|
||||||
|
|
||||||
|
curThread, err := GetCurrentThread()
|
||||||
|
if err != nil {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
var token syscall.Token
|
||||||
|
err = OpenThreadToken(curThread, syscall.TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES, false, &token)
|
||||||
|
if err != nil {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
defer syscall.CloseHandle(syscall.Handle(token))
|
||||||
|
|
||||||
|
privStr, err := syscall.UTF16PtrFromString(privilege)
|
||||||
|
if err != nil {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
var tokenPriv TOKEN_PRIVILEGES
|
||||||
|
err = LookupPrivilegeValue(nil, privStr, &tokenPriv.Privileges[0].Luid)
|
||||||
|
if err != nil {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenPriv.PrivilegeCount = 1
|
||||||
|
tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED
|
||||||
|
err = AdjustTokenPrivileges(token, false, &tokenPriv, 0, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
|
||||||
|
return f()
|
||||||
|
}
|
||||||
|
@ -923,43 +923,62 @@ func testHardLink(t *testing.T, root *Root) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSymlink(t *testing.T) {
|
func TestSymlink(t *testing.T) {
|
||||||
|
testMaybeRooted(t, testSymlink)
|
||||||
|
}
|
||||||
|
func testSymlink(t *testing.T, root *Root) {
|
||||||
testenv.MustHaveSymlink(t)
|
testenv.MustHaveSymlink(t)
|
||||||
t.Chdir(t.TempDir())
|
|
||||||
|
var (
|
||||||
|
create = Create
|
||||||
|
open = Open
|
||||||
|
symlink = Symlink
|
||||||
|
stat = Stat
|
||||||
|
lstat = Lstat
|
||||||
|
readlink = Readlink
|
||||||
|
)
|
||||||
|
if root != nil {
|
||||||
|
create = root.Create
|
||||||
|
open = root.Open
|
||||||
|
symlink = root.Symlink
|
||||||
|
stat = root.Stat
|
||||||
|
lstat = root.Lstat
|
||||||
|
readlink = root.Readlink
|
||||||
|
}
|
||||||
|
|
||||||
from, to := "symlinktestfrom", "symlinktestto"
|
from, to := "symlinktestfrom", "symlinktestto"
|
||||||
file, err := Create(to)
|
file, err := create(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Create(%q) failed: %v", to, err)
|
t.Fatalf("Create(%q) failed: %v", to, err)
|
||||||
}
|
}
|
||||||
if err = file.Close(); err != nil {
|
if err = file.Close(); err != nil {
|
||||||
t.Errorf("Close(%q) failed: %v", to, err)
|
t.Errorf("Close(%q) failed: %v", to, err)
|
||||||
}
|
}
|
||||||
err = Symlink(to, from)
|
err = symlink(to, from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Symlink(%q, %q) failed: %v", to, from, err)
|
t.Fatalf("Symlink(%q, %q) failed: %v", to, from, err)
|
||||||
}
|
}
|
||||||
tostat, err := Lstat(to)
|
tostat, err := lstat(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Lstat(%q) failed: %v", to, err)
|
t.Fatalf("Lstat(%q) failed: %v", to, err)
|
||||||
}
|
}
|
||||||
if tostat.Mode()&ModeSymlink != 0 {
|
if tostat.Mode()&ModeSymlink != 0 {
|
||||||
t.Fatalf("Lstat(%q).Mode()&ModeSymlink = %v, want 0", to, tostat.Mode()&ModeSymlink)
|
t.Fatalf("Lstat(%q).Mode()&ModeSymlink = %v, want 0", to, tostat.Mode()&ModeSymlink)
|
||||||
}
|
}
|
||||||
fromstat, err := Stat(from)
|
fromstat, err := stat(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Stat(%q) failed: %v", from, err)
|
t.Fatalf("Stat(%q) failed: %v", from, err)
|
||||||
}
|
}
|
||||||
if !SameFile(tostat, fromstat) {
|
if !SameFile(tostat, fromstat) {
|
||||||
t.Errorf("Symlink(%q, %q) did not create symlink", to, from)
|
t.Errorf("Symlink(%q, %q) did not create symlink", to, from)
|
||||||
}
|
}
|
||||||
fromstat, err = Lstat(from)
|
fromstat, err = lstat(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Lstat(%q) failed: %v", from, err)
|
t.Fatalf("Lstat(%q) failed: %v", from, err)
|
||||||
}
|
}
|
||||||
if fromstat.Mode()&ModeSymlink == 0 {
|
if fromstat.Mode()&ModeSymlink == 0 {
|
||||||
t.Fatalf("Lstat(%q).Mode()&ModeSymlink = 0, want %v", from, ModeSymlink)
|
t.Fatalf("Lstat(%q).Mode()&ModeSymlink = 0, want %v", from, ModeSymlink)
|
||||||
}
|
}
|
||||||
fromstat, err = Stat(from)
|
fromstat, err = stat(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Stat(%q) failed: %v", from, err)
|
t.Fatalf("Stat(%q) failed: %v", from, err)
|
||||||
}
|
}
|
||||||
@ -969,14 +988,14 @@ func TestSymlink(t *testing.T) {
|
|||||||
if fromstat.Mode()&ModeSymlink != 0 {
|
if fromstat.Mode()&ModeSymlink != 0 {
|
||||||
t.Fatalf("Stat(%q).Mode()&ModeSymlink = %v, want 0", from, fromstat.Mode()&ModeSymlink)
|
t.Fatalf("Stat(%q).Mode()&ModeSymlink = %v, want 0", from, fromstat.Mode()&ModeSymlink)
|
||||||
}
|
}
|
||||||
s, err := Readlink(from)
|
s, err := readlink(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Readlink(%q) failed: %v", from, err)
|
t.Fatalf("Readlink(%q) failed: %v", from, err)
|
||||||
}
|
}
|
||||||
if s != to {
|
if s != to {
|
||||||
t.Fatalf("Readlink(%q) = %q, want %q", from, s, to)
|
t.Fatalf("Readlink(%q) = %q, want %q", from, s, to)
|
||||||
}
|
}
|
||||||
file, err = Open(from)
|
file, err = open(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Open(%q) failed: %v", from, err)
|
t.Fatalf("Open(%q) failed: %v", from, err)
|
||||||
}
|
}
|
||||||
|
@ -218,6 +218,18 @@ func (r *Root) Link(oldname, newname string) error {
|
|||||||
return rootLink(r, oldname, newname)
|
return rootLink(r, oldname, newname)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Symlink creates newname as a symbolic link to oldname.
|
||||||
|
// See [Symlink] for more details.
|
||||||
|
//
|
||||||
|
// Symlink does not validate oldname,
|
||||||
|
// which may reference a location outside the root.
|
||||||
|
//
|
||||||
|
// On Windows, a directory link is created if oldname references
|
||||||
|
// a directory within the root. Otherwise a file link is created.
|
||||||
|
func (r *Root) Symlink(oldname, newname string) error {
|
||||||
|
return rootSymlink(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,
|
||||||
|
@ -198,3 +198,14 @@ func rootLink(r *Root, oldname, newname string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rootSymlink(r *Root, oldname, newname string) error {
|
||||||
|
if err := checkPathEscapesLstat(r, newname); err != nil {
|
||||||
|
return &PathError{Op: "symlinkat", Path: newname, Err: err}
|
||||||
|
}
|
||||||
|
err := Symlink(oldname, joinPath(r.root.name, newname))
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"symlinkat", oldname, newname, underlyingError(err)}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -858,6 +858,29 @@ func testRootMoveTo(t *testing.T, rename bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRootSymlink(t *testing.T) {
|
||||||
|
testenv.MustHaveSymlink(t)
|
||||||
|
for _, test := range rootTestCases {
|
||||||
|
test.run(t, func(t *testing.T, target string, root *os.Root) {
|
||||||
|
wantError := test.wantError
|
||||||
|
if test.ltarget != "" {
|
||||||
|
// We can't create a symlink over an existing symlink.
|
||||||
|
wantError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const wantTarget = "linktarget"
|
||||||
|
err := root.Symlink(wantTarget, test.open)
|
||||||
|
if errEndsTest(t, err, wantError, "root.Symlink(%q)", test.open) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, err := os.Readlink(target)
|
||||||
|
if err != nil || got != wantTarget {
|
||||||
|
t.Fatalf("ReadLink(%q) = %q, %v; want %q, nil", target, got, err, wantTarget)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
//
|
//
|
||||||
@ -1364,6 +1387,25 @@ func testRootConsistencyMove(t *testing.T, rename bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRootConsistencySymlink(t *testing.T) {
|
||||||
|
testenv.MustHaveSymlink(t)
|
||||||
|
for _, test := range rootConsistencyTestCases {
|
||||||
|
test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
|
||||||
|
const target = "linktarget"
|
||||||
|
var err error
|
||||||
|
var got string
|
||||||
|
if r == nil {
|
||||||
|
err = os.Symlink(target, path)
|
||||||
|
got, _ = os.Readlink(target)
|
||||||
|
} else {
|
||||||
|
err = r.Symlink(target, path)
|
||||||
|
got, _ = r.Readlink(target)
|
||||||
|
}
|
||||||
|
return got, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRootRenameAfterOpen(t *testing.T) {
|
func TestRootRenameAfterOpen(t *testing.T) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
|
@ -132,6 +132,16 @@ func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
|
|||||||
return fi, nil
|
return fi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rootSymlink(r *Root, oldname, newname string) error {
|
||||||
|
_, err := doInRoot(r, newname, func(parent sysfdType, name string) (struct{}, error) {
|
||||||
|
return struct{}{}, symlinkat(oldname, parent, name)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"symlinkat", oldname, newname, err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// On systems which use fchmodat, fchownat, etc., we have a race condition:
|
// On systems which use fchmodat, fchownat, etc., we have a race condition:
|
||||||
// When "name" is a symlink, Root.Chmod("name") should act on the target of that link.
|
// When "name" is a symlink, Root.Chmod("name") should act on the target of that link.
|
||||||
// However, fchmodat doesn't allow us to chmod a file only if it is not a symlink;
|
// However, fchmodat doesn't allow us to chmod a file only if it is not a symlink;
|
||||||
@ -217,6 +227,10 @@ func linkat(oldfd int, oldname string, newfd int, newname string) error {
|
|||||||
return unix.Linkat(oldfd, oldname, newfd, newname, 0)
|
return unix.Linkat(oldfd, oldname, newfd, newname, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func symlinkat(oldname string, newfd int, newname string) error {
|
||||||
|
return unix.Symlinkat(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.
|
||||||
//
|
//
|
||||||
|
@ -233,6 +233,52 @@ func rootStat(r *Root, name string, lstat bool) (FileInfo, error) {
|
|||||||
return fi, nil
|
return fi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rootSymlink(r *Root, oldname, newname string) error {
|
||||||
|
if oldname == "" {
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSymbolicLinkW converts volume-relative paths into absolute ones.
|
||||||
|
// Do the same.
|
||||||
|
if filepathlite.VolumeNameLen(oldname) > 0 && !filepathlite.IsAbs(oldname) {
|
||||||
|
p, err := syscall.FullPath(oldname)
|
||||||
|
if err == nil {
|
||||||
|
oldname = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If oldname can be resolved to a directory in the root, create a directory link.
|
||||||
|
// Otherwise, create a file link.
|
||||||
|
var flags windows.SymlinkatFlags
|
||||||
|
if filepathlite.VolumeNameLen(oldname) == 0 && !IsPathSeparator(oldname[0]) {
|
||||||
|
// oldname is a path relative to the directory containing newname.
|
||||||
|
// Prepend newname's directory to it to make a path relative to the root.
|
||||||
|
// For example, if oldname=old and newname=a\new, destPath=a\old.
|
||||||
|
destPath := oldname
|
||||||
|
if dir := dirname(newname); dir != "." {
|
||||||
|
destPath = dir + `\` + oldname
|
||||||
|
}
|
||||||
|
fi, err := r.Stat(destPath)
|
||||||
|
if err == nil && fi.IsDir() {
|
||||||
|
flags |= windows.SYMLINKAT_DIRECTORY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empirically, CreateSymbolicLinkW appears to set the relative flag iff
|
||||||
|
// the target does not contain a volume name.
|
||||||
|
if filepathlite.VolumeNameLen(oldname) == 0 {
|
||||||
|
flags |= windows.SYMLINKAT_RELATIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := doInRoot(r, newname, func(parent sysfdType, name string) (struct{}, error) {
|
||||||
|
return struct{}{}, windows.Symlinkat(oldname, parent, name, flags)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return &LinkError{"symlinkat", oldname, newname, err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func chmodat(parent syscall.Handle, name string, mode FileMode) error {
|
func chmodat(parent syscall.Handle, name string, mode FileMode) error {
|
||||||
// Currently, on Windows os.Chmod("symlink") will act on "symlink",
|
// Currently, on Windows os.Chmod("symlink") will act on "symlink",
|
||||||
// not on any file it points to.
|
// not on any file it points to.
|
||||||
|
@ -8,9 +8,13 @@ package os_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"internal/syscall/windows"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify that Root.Open rejects Windows reserved names.
|
// Verify that Root.Open rejects Windows reserved names.
|
||||||
@ -51,3 +55,176 @@ func TestRootWindowsCaseInsensitivity(t *testing.T) {
|
|||||||
t.Fatalf("os.Stat(file) after deletion: %v, want ErrNotFound", err)
|
t.Fatalf("os.Stat(file) after deletion: %v, want ErrNotFound", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRootSymlinkRelativity tests that symlinks created using Root.Symlink have the
|
||||||
|
// same SYMLINK_FLAG_RELATIVE value as ones creates using os.Symlink.
|
||||||
|
func TestRootSymlinkRelativity(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
root, err := os.OpenRoot(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
|
||||||
|
for i, test := range []struct {
|
||||||
|
name string
|
||||||
|
target string
|
||||||
|
}{{
|
||||||
|
name: "relative",
|
||||||
|
target: `foo`,
|
||||||
|
}, {
|
||||||
|
name: "absolute",
|
||||||
|
target: `C:\foo`,
|
||||||
|
}, {
|
||||||
|
name: "current working directory-relative",
|
||||||
|
target: `C:foo`,
|
||||||
|
}, {
|
||||||
|
name: "root-relative",
|
||||||
|
target: `\foo`,
|
||||||
|
}, {
|
||||||
|
name: "question prefix",
|
||||||
|
target: `\\?\foo`,
|
||||||
|
}, {
|
||||||
|
name: "relative with dot dot",
|
||||||
|
target: `a\..\b`, // could be cleaned (but isn't)
|
||||||
|
}} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
name := fmt.Sprintf("symlink_%v", i)
|
||||||
|
if err := os.Symlink(test.target, filepath.Join(dir, name)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := root.Symlink(test.target, name+"_at"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
osRDB, err := readSymlinkReparseData(filepath.Join(dir, name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rootRDB, err := readSymlinkReparseData(filepath.Join(dir, name+"_at"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if osRDB.Flags != rootRDB.Flags {
|
||||||
|
t.Errorf("symlink target %q: Symlink flags = %x, Root.Symlink flags = %x", test.target, osRDB.Flags, rootRDB.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare the link target.
|
||||||
|
// os.Symlink converts current working directory-relative links
|
||||||
|
// such as c:foo into absolute links.
|
||||||
|
osTarget, err := os.Readlink(filepath.Join(dir, name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
rootTarget, err := os.Readlink(filepath.Join(dir, name+"_at"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if osTarget != rootTarget {
|
||||||
|
t.Errorf("symlink created with target %q: Symlink target = %q, Root.Symlink target = %q", test.target, osTarget, rootTarget)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSymlinkReparseData(name string) (*windows.SymbolicLinkReparseBuffer, error) {
|
||||||
|
nameu16, err := syscall.UTF16FromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h, err := syscall.CreateFile(&nameu16[0], syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING,
|
||||||
|
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer syscall.CloseHandle(h)
|
||||||
|
|
||||||
|
var rdbbuf [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
|
||||||
|
var bytesReturned uint32
|
||||||
|
err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
|
||||||
|
if rdb.ReparseTag != syscall.IO_REPARSE_TAG_SYMLINK {
|
||||||
|
return nil, fmt.Errorf("%q: not a symlink", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
bufoff := unsafe.Offsetof(rdb.DUMMYUNIONNAME)
|
||||||
|
symlinkBuf := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdbbuf[bufoff]))
|
||||||
|
|
||||||
|
return symlinkBuf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRootSymlinkToDirectory tests that Root.Symlink creates directory links
|
||||||
|
// when the target is a directory contained within the root.
|
||||||
|
func TestRootSymlinkToDirectory(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
root, err := os.OpenRoot(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer root.Close()
|
||||||
|
|
||||||
|
if err := os.Mkdir(filepath.Join(dir, "dir"), 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "file"), nil, 0666); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dir2 := t.TempDir()
|
||||||
|
|
||||||
|
for i, test := range []struct {
|
||||||
|
name string
|
||||||
|
target string
|
||||||
|
wantDir bool
|
||||||
|
}{{
|
||||||
|
name: "directory outside root",
|
||||||
|
target: dir2,
|
||||||
|
wantDir: false,
|
||||||
|
}, {
|
||||||
|
name: "directory inside root",
|
||||||
|
target: "dir",
|
||||||
|
wantDir: true,
|
||||||
|
}, {
|
||||||
|
name: "file inside root",
|
||||||
|
target: "file",
|
||||||
|
wantDir: false,
|
||||||
|
}, {
|
||||||
|
name: "nonexistent inside root",
|
||||||
|
target: "nonexistent",
|
||||||
|
wantDir: false,
|
||||||
|
}} {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
name := fmt.Sprintf("symlink_%v", i)
|
||||||
|
if err := root.Symlink(test.target, name); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lstat strips the directory mode bit from reparse points,
|
||||||
|
// so we need to use GetFileInformationByHandle directly to
|
||||||
|
// determine if this is a directory link.
|
||||||
|
nameu16, err := syscall.UTF16PtrFromString(filepath.Join(dir, name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h, err := syscall.CreateFile(nameu16, 0, 0, nil, syscall.OPEN_EXISTING,
|
||||||
|
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer syscall.CloseHandle(h)
|
||||||
|
var fi syscall.ByHandleFileInformation
|
||||||
|
if err := syscall.GetFileInformationByHandle(h, &fi); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
gotDir := fi.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0
|
||||||
|
|
||||||
|
if got, want := gotDir, test.wantDir; got != want {
|
||||||
|
t.Errorf("link target %q: isDir = %v, want %v", test.target, got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user