]> Cypherpunks repositories - gostls13.git/commitdiff
path/filepath: re-implement windows EvalSymlinks
authorAlex Brainman <alex.brainman@gmail.com>
Wed, 9 Aug 2017 01:33:40 +0000 (11:33 +1000)
committerAlex Brainman <alex.brainman@gmail.com>
Thu, 5 Oct 2017 04:16:00 +0000 (04:16 +0000)
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>
src/go/build/deps_test.go
src/internal/syscall/windows/syscall_windows.go
src/internal/syscall/windows/zsyscall_windows.go
src/os/os_windows_test.go
src/path/filepath/path_windows_test.go
src/path/filepath/symlink_windows.go

index 275c4835dc877f969206ba8b151bb5a38af85365..8f485f1632b3ecdb3a8ba9a5c5ef095127683b7c 100644 (file)
@@ -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"},
index ec08a5a92c67727031c3fc0fef2acc527cb60d68..af87416f5effe37900a104eddbf14eac1f088b39 100644 (file)
@@ -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()
+}
index 7a2ffeeffaf9cd42fbd21aeed92c7fc9d186a7b8..e882c89742935ec8777e70849d4c88af5c12ae16 100644 (file)
@@ -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 {
index 228fecedf88b47f917fa5368e834f99bc270fe2e..47e2611a40d8fba611fe877c62f34a4c5d6d78d5 100644 (file)
@@ -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) {
index d1b89bbc7121469d7e09fb9e06afb4c203c6b90e..2ec5f5ef4404d467f85334ab15485f7f4f7458ba 100644 (file)
@@ -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)
+       }
+}
index f771fe3a8a342c371b40a3f2e6271c80ecbfc557..78cde4aa09074552d28c31bce8c95926c68643d2 100644 (file)
@@ -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
 }
 
+// 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
+       }
+       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) {
-       path, err := walkSymlinks(path)
+       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
        }
-       return toNorm(path, normBase)
+       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
 }