mirror of
https://github.com/golang/go
synced 2024-11-23 16:20:04 -07:00
path/filepath: re-implement windows EvalSymlinks
CL 41834 used approach suggested by Raymond Chen in https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/ to implement os.Stat by getting Windows I/O manager follow symbolic links. Do the same for filepath.EvalSymlinks, when existing strategy fails. Updates #19922 Fixes #20506 Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f Reviewed-on: https://go-review.googlesource.com/55612 Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
56462d0f10
commit
66c03d39f3
@ -156,7 +156,7 @@ var pkgDeps = map[string][]string{
|
||||
|
||||
"internal/poll": {"L0", "internal/race", "syscall", "time", "unicode/utf16", "unicode/utf8"},
|
||||
"os": {"L1", "os", "syscall", "time", "internal/poll", "internal/syscall/windows"},
|
||||
"path/filepath": {"L2", "os", "syscall"},
|
||||
"path/filepath": {"L2", "os", "syscall", "internal/syscall/windows"},
|
||||
"io/ioutil": {"L2", "os", "path/filepath", "time"},
|
||||
"os/exec": {"L2", "os", "context", "path/filepath", "syscall"},
|
||||
"os/signal": {"L2", "os", "syscall"},
|
||||
|
@ -165,3 +165,19 @@ type SHARE_INFO_2 struct {
|
||||
|
||||
//sys NetShareAdd(serverName *uint16, level uint32, buf *byte, parmErr *uint16) (neterr error) = netapi32.NetShareAdd
|
||||
//sys NetShareDel(serverName *uint16, netName *uint16, reserved uint32) (neterr error) = netapi32.NetShareDel
|
||||
|
||||
const (
|
||||
FILE_NAME_NORMALIZED = 0x0
|
||||
FILE_NAME_OPENED = 0x8
|
||||
|
||||
VOLUME_NAME_DOS = 0x0
|
||||
VOLUME_NAME_GUID = 0x1
|
||||
VOLUME_NAME_NONE = 0x4
|
||||
VOLUME_NAME_NT = 0x2
|
||||
)
|
||||
|
||||
//sys GetFinalPathNameByHandle(file syscall.Handle, filePath *uint16, filePathSize uint32, flags uint32) (n uint32, err error) = kernel32.GetFinalPathNameByHandleW
|
||||
|
||||
func LoadGetFinalPathNameByHandle() error {
|
||||
return procGetFinalPathNameByHandleW.Find()
|
||||
}
|
||||
|
@ -41,21 +41,22 @@ var (
|
||||
modnetapi32 = syscall.NewLazyDLL(sysdll.Add("netapi32.dll"))
|
||||
modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
|
||||
|
||||
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
|
||||
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
|
||||
procMoveFileExW = modkernel32.NewProc("MoveFileExW")
|
||||
procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW")
|
||||
procGetACP = modkernel32.NewProc("GetACP")
|
||||
procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
|
||||
procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar")
|
||||
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||
procNetShareAdd = modnetapi32.NewProc("NetShareAdd")
|
||||
procNetShareDel = modnetapi32.NewProc("NetShareDel")
|
||||
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
|
||||
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
|
||||
procMoveFileExW = modkernel32.NewProc("MoveFileExW")
|
||||
procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW")
|
||||
procGetACP = modkernel32.NewProc("GetACP")
|
||||
procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
|
||||
procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar")
|
||||
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||
procNetShareAdd = modnetapi32.NewProc("NetShareAdd")
|
||||
procNetShareDel = modnetapi32.NewProc("NetShareDel")
|
||||
procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
|
||||
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||
)
|
||||
|
||||
func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) {
|
||||
@ -157,6 +158,19 @@ func NetShareDel(serverName *uint16, netName *uint16, reserved uint32) (neterr e
|
||||
return
|
||||
}
|
||||
|
||||
func GetFinalPathNameByHandle(file syscall.Handle, filePath *uint16, filePathSize uint32, flags uint32) (n uint32, err error) {
|
||||
r0, _, e1 := syscall.Syscall6(procGetFinalPathNameByHandleW.Addr(), 4, uintptr(file), uintptr(unsafe.Pointer(filePath)), uintptr(filePathSize), uintptr(flags), 0, 0)
|
||||
n = uint32(r0)
|
||||
if n == 0 {
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ImpersonateSelf(impersonationlevel uint32) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(impersonationlevel), 0, 0)
|
||||
if r1 == 0 {
|
||||
|
@ -520,10 +520,17 @@ func TestNetworkSymbolicLink(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if got != target {
|
||||
t.Errorf(`os.Readlink("%s"): got %v, want %v`, link, got, target)
|
||||
}
|
||||
|
||||
got, err = filepath.EvalSymlinks(link)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != target {
|
||||
t.Errorf(`filepath.EvalSymlinks("%s"): got %v, want %v`, link, got, target)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartProcessAttr(t *testing.T) {
|
||||
|
@ -516,3 +516,37 @@ func TestWalkDirectorySymlink(t *testing.T) {
|
||||
testenv.MustHaveSymlink(t)
|
||||
testWalkMklink(t, "D")
|
||||
}
|
||||
|
||||
func TestNTNamespaceSymlink(t *testing.T) {
|
||||
output, _ := exec.Command("cmd", "/c", "mklink", "/?").Output()
|
||||
if !strings.Contains(string(output), " /J ") {
|
||||
t.Skip("skipping test because mklink command does not support junctions")
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "TestNTNamespaceSymlink")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
vol := filepath.VolumeName(tmpdir)
|
||||
output, err = exec.Command("cmd", "/c", "mountvol", vol, "/L").CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output)
|
||||
}
|
||||
target := strings.Trim(string(output), " \n\r")
|
||||
|
||||
link := filepath.Join(tmpdir, "link")
|
||||
output, err = exec.Command("cmd", "/c", "mklink", "/J", link, target).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to run mklink %v %v: %v %q", link, target, err, output)
|
||||
}
|
||||
|
||||
got, err := filepath.EvalSymlinks(link)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := vol + `\`; got != want {
|
||||
t.Errorf(`EvalSymlinks(%q): got %q, want %q`, link, got, want)
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,9 @@
|
||||
package filepath
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"internal/syscall/windows"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
@ -106,10 +109,100 @@ func toNorm(path string, normBase func(string) (string, error)) (string, error)
|
||||
return volume + normPath, nil
|
||||
}
|
||||
|
||||
func evalSymlinks(path string) (string, error) {
|
||||
path, err := walkSymlinks(path)
|
||||
// evalSymlinksUsingGetFinalPathNameByHandle uses Windows
|
||||
// GetFinalPathNameByHandle API to retrieve the final
|
||||
// path for the specified file.
|
||||
func evalSymlinksUsingGetFinalPathNameByHandle(path string) (string, error) {
|
||||
err := windows.LoadGetFinalPathNameByHandle()
|
||||
if err != nil {
|
||||
// we must be using old version of Windows
|
||||
return "", err
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// Use Windows I/O manager to dereference the symbolic link, as per
|
||||
// https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
|
||||
p, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return toNorm(path, normBase)
|
||||
h, err := syscall.CreateFile(p, 0, 0, nil,
|
||||
syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer syscall.CloseHandle(h)
|
||||
|
||||
buf := make([]uint16, 100)
|
||||
for {
|
||||
n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), windows.VOLUME_NAME_DOS)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n < uint32(len(buf)) {
|
||||
break
|
||||
}
|
||||
buf = make([]uint16, n)
|
||||
}
|
||||
s := syscall.UTF16ToString(buf)
|
||||
if len(s) > 4 && s[:4] == `\\?\` {
|
||||
s = s[4:]
|
||||
if len(s) > 3 && s[:3] == `UNC` {
|
||||
// return path like \\server\share\...
|
||||
return `\` + s[3:], nil
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
return "", errors.New("GetFinalPathNameByHandle returned unexpected path=" + s)
|
||||
}
|
||||
|
||||
func samefile(path1, path2 string) bool {
|
||||
fi1, err := os.Lstat(path1)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fi2, err := os.Lstat(path2)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return os.SameFile(fi1, fi2)
|
||||
}
|
||||
|
||||
func evalSymlinks(path string) (string, error) {
|
||||
newpath, err := walkSymlinks(path)
|
||||
if err != nil {
|
||||
newpath2, err2 := evalSymlinksUsingGetFinalPathNameByHandle(path)
|
||||
if err2 == nil {
|
||||
return toNorm(newpath2, normBase)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
newpath, err = toNorm(newpath, normBase)
|
||||
if err != nil {
|
||||
newpath2, err2 := evalSymlinksUsingGetFinalPathNameByHandle(path)
|
||||
if err2 == nil {
|
||||
return toNorm(newpath2, normBase)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if strings.ToUpper(newpath) == strings.ToUpper(path) {
|
||||
// walkSymlinks did not actually walk any symlinks,
|
||||
// so we don't need to try GetFinalPathNameByHandle.
|
||||
return newpath, nil
|
||||
}
|
||||
newpath2, err2 := evalSymlinksUsingGetFinalPathNameByHandle(path)
|
||||
if err2 != nil {
|
||||
return newpath, nil
|
||||
}
|
||||
newpath2, err2 = toNorm(newpath2, normBase)
|
||||
if err2 != nil {
|
||||
return newpath, nil
|
||||
}
|
||||
if samefile(newpath, newpath2) {
|
||||
return newpath, nil
|
||||
}
|
||||
return newpath2, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user