diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 275c4835dc8..8f485f1632b 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -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"}, diff --git a/src/internal/syscall/windows/syscall_windows.go b/src/internal/syscall/windows/syscall_windows.go index ec08a5a92c6..af87416f5ef 100644 --- a/src/internal/syscall/windows/syscall_windows.go +++ b/src/internal/syscall/windows/syscall_windows.go @@ -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() +} diff --git a/src/internal/syscall/windows/zsyscall_windows.go b/src/internal/syscall/windows/zsyscall_windows.go index 7a2ffeeffaf..e882c897429 100644 --- a/src/internal/syscall/windows/zsyscall_windows.go +++ b/src/internal/syscall/windows/zsyscall_windows.go @@ -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 { diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index 228fecedf88..47e2611a40d 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -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) { diff --git a/src/path/filepath/path_windows_test.go b/src/path/filepath/path_windows_test.go index d1b89bbc712..2ec5f5ef440 100644 --- a/src/path/filepath/path_windows_test.go +++ b/src/path/filepath/path_windows_test.go @@ -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) + } +} diff --git a/src/path/filepath/symlink_windows.go b/src/path/filepath/symlink_windows.go index f771fe3a8a3..78cde4aa090 100644 --- a/src/path/filepath/symlink_windows.go +++ b/src/path/filepath/symlink_windows.go @@ -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 }