Alex Brainman has uploaded this change for review.
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 path/filepath.EvalSymlinks.
Old version of EvalSymlinks had special treatment for
symlinks with some relative paths. But new EvalSymlinks
always returns absolute paths. So adjust TestEvalSymlinks
to accommodate for this change.
Updates #19922
Fixes #20506
Change-Id: If2008ca0ebf639553d23bae0d6a5f77f96f0f621
---
M src/go/build/deps_test.go
M src/internal/syscall/windows/syscall_windows.go
M src/internal/syscall/windows/zsyscall_windows.go
M src/os/os_windows_test.go
M src/path/filepath/path_test.go
M src/path/filepath/path_windows_test.go
M src/path/filepath/symlink_windows.go
7 files changed, 147 insertions(+), 50 deletions(-)
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 87abfba..d07debd 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -156,7 +156,7 @@
"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 ec08a5a..af87416 100644
--- a/src/internal/syscall/windows/syscall_windows.go
+++ b/src/internal/syscall/windows/syscall_windows.go
@@ -165,3 +165,19 @@
//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 7a2ffee..e882c89 100644
--- a/src/internal/syscall/windows/zsyscall_windows.go
+++ b/src/internal/syscall/windows/zsyscall_windows.go
@@ -41,21 +41,22 @@
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 @@
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 04c4a4a..121fda7 100644
--- a/src/os/os_windows_test.go
+++ b/src/os/os_windows_test.go
@@ -520,10 +520,17 @@
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_test.go b/src/path/filepath/path_test.go
index 315f61e..4530c22 100644
--- a/src/path/filepath/path_test.go
+++ b/src/path/filepath/path_test.go
@@ -771,18 +771,6 @@
{"test/linkabs", "/"},
}
-// findEvalSymlinksTestDirsDest searches testDirs
-// for matching path and returns correspondent dest.
-func findEvalSymlinksTestDirsDest(t *testing.T, testDirs []EvalSymlinksTest, path string) string {
- for _, d := range testDirs {
- if d.path == path {
- return d.dest
- }
- }
- t.Fatalf("did not find %q in testDirs slice", path)
- return ""
-}
-
// simpleJoin builds a file name from the directory and path.
// It does not use Join because we don't want ".." to be evaluated.
func simpleJoin(dir, path string) string {
@@ -842,8 +830,11 @@
for _, d := range tests {
path := simpleJoin(tmpDir, d.path)
dest := simpleJoin(tmpDir, d.dest)
- if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) {
+ if filepath.IsAbs(d.dest) {
dest = d.dest
+ } else if runtime.GOOS == "windows" && os.IsPathSeparator(d.dest[0]) {
+ // add C: in front of \...
+ dest = tmpDir[:2] + d.dest
}
if p, err := filepath.EvalSymlinks(path); err != nil {
t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
@@ -870,14 +861,10 @@
t.Errorf(`EvalSymlinks(".") in %q directory error: %v`, d.path, err)
return
}
- if p == "." {
+ if filepath.Clean(p) != filepath.Clean(dest) {
+ t.Errorf(`EvalSymlinks(".") in %q directory returns %q, want %q`, d.path, p, dest)
return
}
- want := filepath.Clean(findEvalSymlinksTestDirsDest(t, testdirs, d.path))
- if p == want {
- return
- }
- t.Errorf(`EvalSymlinks(".") in %q directory returns %q, want "." or %q`, d.path, p, want)
}()
// test EvalSymlinks("C:.") on Windows
@@ -903,14 +890,10 @@
t.Errorf(`EvalSymlinks("%s") in %q directory error: %v`, volDot, d.path, err)
return
}
- if p == volDot {
+ if filepath.Clean(p) != filepath.Clean(dest) {
+ t.Errorf(`EvalSymlinks("%s") in %q directory returns %q, want %q`, volDot, d.path, p, dest)
return
}
- want := filepath.Clean(findEvalSymlinksTestDirsDest(t, testdirs, d.path))
- if p == want {
- return
- }
- t.Errorf(`EvalSymlinks("%s") in %q directory returns %q, want %q or %q`, volDot, d.path, p, volDot, want)
}()
}
@@ -930,10 +913,6 @@
}
path := simpleJoin("..", d.path)
- dest := simpleJoin("..", d.dest)
- if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) {
- dest = d.dest
- }
if p, err := filepath.EvalSymlinks(path); err != nil {
t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
@@ -958,8 +937,8 @@
}
if p, err := filepath.EvalSymlinks(d.path); err != nil {
t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
- } else if filepath.Clean(p) != filepath.Clean(d.dest) {
- t.Errorf("EvalSymlinks(%q)=%q, want %q", d.path, p, d.dest)
+ } else if filepath.Clean(p) != filepath.Clean(dest) {
+ t.Errorf("EvalSymlinks(%q)=%q, want %q", d.path, p, dest)
}
}()
}
diff --git a/src/path/filepath/path_windows_test.go b/src/path/filepath/path_windows_test.go
index d759a83..05fc143 100644
--- a/src/path/filepath/path_windows_test.go
+++ b/src/path/filepath/path_windows_test.go
@@ -458,3 +458,37 @@
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 f771fe3..19b4cc8 100644
--- a/src/path/filepath/symlink_windows.go
+++ b/src/path/filepath/symlink_windows.go
@@ -5,8 +5,11 @@
package filepath
import (
+ "errors"
+ "internal/syscall/windows"
"strings"
"syscall"
+ "unsafe"
)
// normVolumeName is like VolumeName, but makes drive letter upper case.
@@ -106,10 +109,54 @@
return volume + normPath, nil
}
+// TODO(brainman): remove all windows related code from walkSymlinks
+
func evalSymlinks(path string) (string, error) {
- path, err := walkSymlinks(path)
+ if path == "" {
+ return path, nil
+ }
+
+ if windows.LoadGetFinalPathNameByHandle() != nil {
+ // we must be using old version of Windows
+ p, err := syscall.FullPath(path)
+ if err != nil {
+ return "", err
+ }
+ return toNorm(p, normBase)
+ }
+
+ // 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((*[1 << 29]uint16)(unsafe.Pointer(&buf[0]))[:])
+ if len(s) > 4 && s[:4] == `\\?\` {
+ s = s[4:]
+ if len(s) > 3 && s[:3] == `UNC` {
+ // return path like \\server\share\...
+ return Clean(`\` + s[3:]), nil
+ }
+ return Clean(s), nil
+ }
+ return "", errors.New("GetFinalPathNameByHandle returned unexpected path=" + s)
}
To view, visit change 55250. To unsubscribe, or for help writing mail filters, visit settings.
I tried this CL.
C:\Users\Hiroshi\test>net use
New connections will be remembered.
Status Local Remote Network
-------------------------------------------------------------------------------
OK Y: \\localhost\Users\Hiroshi\test
Microsoft Windows Network
The command completed successfully.
C:\Users\Hiroshi\test>dir
Volume in drive C is Windows10_OS
Volume Serial Number is 6203-7069
Directory of C:\Users\Hiroshi\test
08/13/2017 09:17 PM <DIR> .
08/13/2017 09:17 PM <DIR> ..
08/13/2017 07:14 PM <JUNCTION> jlink [C:\Users\Hiroshi\jtarget]
08/13/2017 07:48 PM <JUNCTION> mount [\\?\Volume{b57ba7f7-a55e-41b2-84fa-074ca673b2e4}\]
C:\Users\Hiroshi\test>type x.go
package main
import (
"fmt"
"path/filepath"
)
func main() {
fmt.Println(filepath.EvalSymlinks("y:jlink"))
fmt.Println(filepath.EvalSymlinks(`\\localhost\Users\Hiroshi\test\jlink`))
fmt.Println(filepath.EvalSymlinks(`y:mount\Users\Hiroshi`))
fmt.Println(filepath.EvalSymlinks(`mount\Users\Hiroshi`))
}C:\Users\Hiroshi\test>x.exe
\\localhost\Users\Hiroshi\test\jlink <nil>
\\localhost\Users\Hiroshi\test\jlink <nil>
\\localhost\Users\Hiroshi\test\mount\Users\Hiroshi <nil>
C:\Users\Hiroshi <nil>
Looking by the result, I think we can emulate some GetFinalPathNameByHandle behaviors without breaking compatibility.
I see GetFinalPathNameByHandle has two characteristics.
1. If the given drive is network path, it give up evaluations in any case the network is local or remote. which is the same behavior I proposed in #19922.
2. recognize NT namespace format, then call appropriate drivers per protocols. I have a hypothesis about NT namespace format. In my observation, those have always same format `\\?\` + relative path. I guess that is the way GetFinalPathNameByHandle recognize NT namespace. If this assumption is correct, we could process protocols ourselves. In above case, get real path from drive GUID.
..., I think we can emulate some
GetFinalPathNameByHandle behaviors without breaking compatibility.
The only "compatibility" we break here, is that new version of EvalSymlinks will always return absolute path. I don't see how having absolute path could be a bad thing.
But, if you want to try and fix this yourself, then go ahead.
Alex
Patch Set 1:
..., I think we can emulate some
GetFinalPathNameByHandle behaviors without breaking compatibility.The only "compatibility" we break here, is that new version of EvalSymlinks will always return absolute path. I don't see how having absolute path could be a bad thing.
I also don't know specific scenarios. But at least the document guarantees that
"If path is relative the result will be relative to the current directory".
Someone might needs this behavior.
But, if you want to try and fix this yourself, then go ahead.
Thank you, I will evaluate the idea during this week.
... But at least the document
guarantees that
"If path is relative the result will be relative to the current
directory".
Someone might needs this behavior.
You left "..., unless one of the components is an absolute symbolic link." out of your quote. And, considering that os.Readlink is broken on windows, we can assume that all windows symlinks are absolute. That is what GetFinalPathNameByHandle reports, so why should we argue differently?
... I will evaluate the idea during this week.
I am happy to wait for you as long as necessary.
Alex
Alex Brainman has uploaded this change for review.
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 path/filepath.EvalSymlinks.
New EvalSymlinks always return absolute paths for symlink
destinations, so adjust TestEvalSymlinks to accommodate
for this change.
Updates #19922
Fixes #20506
Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
---
M src/go/build/deps_test.go
M src/internal/syscall/windows/syscall_windows.go
M src/internal/syscall/windows/zsyscall_windows.go
M src/os/os_windows_test.go
M src/path/filepath/path_test.go
M src/path/filepath/path_windows_test.go
M src/path/filepath/symlink.go
M src/path/filepath/symlink_unix.go
M src/path/filepath/symlink_windows.go
9 files changed, 171 insertions(+), 50 deletions(-)
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 87abfba..d07debd 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -156,7 +156,7 @@
"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 ec08a5a..0e2daa0 100644
--- a/src/internal/syscall/windows/syscall_windows.go
+++ b/src/internal/syscall/windows/syscall_windows.go
@@ -165,3 +165,15 @@
//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
index 315f61e..dc7c211 100644@@ -873,11 +864,10 @@
if p == "." {
return
}
- want := filepath.Clean(findEvalSymlinksTestDirsDest(t, testdirs, d.path))
- if p == want {
+ if filepath.Clean(p) == filepath.Clean(dest) {
return
}
- t.Errorf(`EvalSymlinks(".") in %q directory returns %q, want "." or %q`, d.path, p, want)
+ t.Errorf(`EvalSymlinks(".") in %q directory returns %q, want %q or %q`, d.path, p, ".", dest)
}()
// test EvalSymlinks("C:.") on Windows
@@ -900,17 +890,16 @@
p, err := filepath.EvalSymlinks(volDot)
if err != nil {
- t.Errorf(`EvalSymlinks("%s") in %q directory error: %v`, volDot, d.path, err)
+ t.Errorf(`EvalSymlinks(%q) in %q directory error: %v`, volDot, d.path, err)
return
}
if p == volDot {
return
}
- want := filepath.Clean(findEvalSymlinksTestDirsDest(t, testdirs, d.path))
- if p == want {
+ if filepath.Clean(p) == filepath.Clean(dest) {
return
}
- t.Errorf(`EvalSymlinks("%s") in %q directory returns %q, want %q or %q`, volDot, d.path, p, volDot, want)
+ t.Errorf(`EvalSymlinks(%q) in %q directory returns %q, want %q or %q`, volDot, d.path, p, volDot, dest)
}()
}
@@ -923,23 +912,28 @@
}
}()
- err := os.Chdir(simpleJoin(tmpDir, "test"))
+ dir := simpleJoin(tmpDir, "test")
+ err := os.Chdir(dir)
if err != nil {
t.Error(err)
return
}
path := simpleJoin("..", d.path)
- dest := simpleJoin("..", d.dest)
- if filepath.IsAbs(d.dest) || os.IsPathSeparator(d.dest[0]) {
- dest = d.dest
- }
+ dest2 := simpleJoin("..", d.dest)
- if p, err := filepath.EvalSymlinks(path); err != nil {
- t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
- } else if filepath.Clean(p) != filepath.Clean(dest) {
- t.Errorf("EvalSymlinks(%q)=%q, want %q", path, p, dest)
+ p, err := filepath.EvalSymlinks(path)
+ if err != nil {
+ t.Errorf(`EvalSymlinks(%q) in %q directory error: %v`, path, dir, err)
+ return
}
+ if filepath.Clean(p) == filepath.Clean(dest2) {
+ return
+ }
+ if filepath.Clean(p) == filepath.Clean(dest) {
+ return
+ }
+ t.Errorf(`EvalSymlinks(%q) in %q directory returns %q, want %q or %q`, path, dir, p, dest2, dest)
}()
// test EvalSymlinks where parameter is relative path
@@ -956,11 +950,17 @@
t.Error(err)
return
}
- if p, err := filepath.EvalSymlinks(d.path); err != nil {
+ p, err := filepath.EvalSymlinks(d.path)
+ if err != nil {
t.Errorf("EvalSymlinks(%q) error: %v", d.path, err)
- } else if filepath.Clean(p) != filepath.Clean(d.dest) {
- t.Errorf("EvalSymlinks(%q)=%q, want %q", d.path, p, d.dest)
}
+ if filepath.Clean(p) == filepath.Clean(d.dest) {
+ return
+ }
+ if filepath.Clean(p) == filepath.Clean(dest) {
+ return
+ }
+ t.Errorf("EvalSymlinks(%q)=%q, want %q or %q", d.path, p, d.dest, dest)
diff --git a/src/path/filepath/symlink.go b/src/path/filepath/symlink.go
index 824aee4..8615ab4 100644
--- a/src/path/filepath/symlink.go
+++ b/src/path/filepath/symlink.go
@@ -44,7 +44,7 @@
if fi.Mode()&os.ModeSymlink == 0 {
return path, false, nil
}
- newpath, err = os.Readlink(path)
+ newpath, err = readLink(path)
if err != nil {
return "", false, err
}
diff --git a/src/path/filepath/symlink_unix.go b/src/path/filepath/symlink_unix.go
index d20e63a..2d5cfc6 100644
--- a/src/path/filepath/symlink_unix.go
+++ b/src/path/filepath/symlink_unix.go
@@ -2,6 +2,15 @@
package filepath
+import (
+ "os"
+)
+
+// readLink returns the destination of the symbolic link path.
+func readLink(path string) (string, error) {
+ return os.Readlink(path)
+}
+
func evalSymlinks(path string) (string, error) {
return walkSymlinks(path)
}
diff --git a/src/path/filepath/symlink_windows.go b/src/path/filepath/symlink_windows.go
index f771fe3..3df362e 100644
--- a/src/path/filepath/symlink_windows.go
+++ b/src/path/filepath/symlink_windows.go
@@ -5,8 +5,11 @@
package filepath
import (
+ "errors"
+ "internal/syscall/windows"
"strings"
"syscall"
+ "unsafe"
)
// normVolumeName is like VolumeName, but makes drive letter upper case.
@@ -106,6 +109,48 @@
return volume + normPath, nil
}
+// readLink returns the destination of the symbolic link path.
+func readLink(path string) (string, error) {
+ 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
+ }
+}
+
func evalSymlinks(path string) (string, error) {
path, err := walkSymlinks(path)
if err != nil {
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Alex Brainman abandoned this change.
To view, visit change 55250. To unsubscribe, or for help writing mail filters, visit settings.
This CL is replacement of 55250.
Alex
Please do not review this yet. I am struggling with this.
I tested this change against issue #19922. And EvalSymlinks fails with "too many links". Windows sometimes just stops walking symlinks chain, because it cannot, but the target is still a symlink. So we get in a loop here.
I also discovered more problems with EvalSymlinks - see issue #21511. I hope to fix the issue as part of this change.
I also sent CL 56710 to simplify tests. Hopefully it will allow us to add more tests.
Alex
Alex Brainman uploaded patch set #2 to this change.
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
---
M src/go/build/deps_test.go
M src/internal/syscall/windows/syscall_windows.go
M src/internal/syscall/windows/zsyscall_windows.go
M src/os/os_windows_test.go
M src/path/filepath/path_windows_test.go
M src/path/filepath/symlink_windows.go
6 files changed, 145 insertions(+), 20 deletions(-)
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Hiroshi you convinced me to leave existing code as is. I just added new code to handle issues #19922 and #20506. So no tests need to change. We can always change this code once we find more scenarios that are broken.
PTAL
Thank you.
Alex
If I read correctly, this doesn't fix junctions on network share.
When we see junctions on network share, we should stop symlink evaluation.
Otherwise, junctions point to local host, not remote host.
If I read correctly, this doesn't fix junctions on network share.
When we see junctions on network share, we should stop symlink
evaluation.
Otherwise, junctions point to local host, not remote host.
If you are asking if this CL fixes "filepath.EvalSymlinks" version of issue #19922, then I say yes it does fixes it.
If I setup computers as described in issue #19922, and run slightly modified program from issue #19922:
```
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
path := `x:\junc`
_, err := os.Stat(path)
if err != nil {
panic(err)
}
newpath, err := filepath.EvalSymlinks(path)
if err != nil {
panic(err)
}
fmt.Printf("EvalSymlinks(%q) returns %q\n", path, newpath)
}
```Before this CL I get:
```
C:\Users\Alex>u:\test
panic: GetFileAttributesEx c:\alex: The system cannot find the file specified.
goroutine 1 [running]:
main.main()
/home/a/src/issues/go/19922/main.go:17 +0x19e
C:\Users\Alex>
```
and after this CL, I get
```
C:\Users\Alex>u:\test
EvalSymlinks("x:\\junc") returns "\\\\computer1\\mountpoint\\junc"
C:\Users\Alex>
```
So new version does as you suggest above.
Alex
I just tested it and it doesn't work. Perhaps, you didn't setup the same junction target as remote host.
For example:
z:\junc => C:\Users\target
So, you need to prepare C:\Users\target both local and remote hosts.
I just tested it and it doesn't work. Perhaps, you didn't setup the
same junction target as remote host.
I followed these instructions https://github.com/golang/go/issues/19922. Is that what you did? What did not work? Which step did not work and how?
For example:
z:\junc => C:\Users\target
So, you need to prepare C:\Users\target both local and remote
hosts.
What are the steps I should do to test that?
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Patch Set 2:
I just tested it and it doesn't work. Perhaps, you didn't setup the
same junction target as remote host.I followed these instructions https://github.com/golang/go/issues/19922. Is that what you did? What did not work? Which step did not work and how?
For example:
z:\junc => C:\Users\target
So, you need to prepare C:\Users\target both local and remote
hosts.What are the steps I should do to test that?
Thank you.
Alex
mkdir c:\alex\localdir on computer 2, I suppose.
mkdir c:\alex\localdir on computer 2, I suppose.
If I add c:\alex\localdir on computer 2, my program returns
EvalSymlinks("x:\\junc") returns "C:\\alex\\localdir"
The old code that uses os.Readlink kicks in first, that is why we get different result. I don't see a way to fix that unless we change filepath.EvalSymlinks to return absolute path instead of relative. What do you propose we do here?
Alex
What do you propose we do here?
If we see that drive type of the path is DRIVE_REMOTE, then use GetFinalPathByHandle?
Patch Set 2:
What do you propose we do here?
If we see that drive type of the path is DRIVE_REMOTE, then use GetFinalPathByHandle?
Or handle each path parts manually, which is flexible, but less future-proof.
Patch Set 2:
Patch Set 2:
What do you propose we do here?
If we see that drive type of the path is DRIVE_REMOTE, then use GetFinalPathByHandle?
Or handle each path parts manually, which is flexible, but less future-proof.
Oh, sorry. I didn't read your comments well.
Yes, the first approach doesn't work well for relative paths.
Alex Brainman uploaded patch set #3 to this change.
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
---
M src/go/build/deps_test.go
M src/internal/syscall/windows/syscall_windows.go
M src/internal/syscall/windows/zsyscall_windows.go
M src/os/os_windows_test.go
M src/path/filepath/path_windows_test.go
M src/path/filepath/symlink_windows.go
6 files changed, 166 insertions(+), 20 deletions(-)
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Hiroshi, I decided to use os.SameFile to overcome problem with c:\alex\localdir on computer 2. Maybe it is good enough. PTAL.
Alex
It looks correct at a glance. But you may need to do benchmarking.
Patch Set 3:
It looks correct at a glance. But you may need to do benchmarking.
Oh, didn't you solve relpath=>abspath do you?
Plus, I found the another inconsistency for local shares.
Maybe we can postpone the edge case for #19922.
I'm fine with the patch set 2 if you agree.
It looks correct at a glance. But you may need to do benchmarking.
I am not sure what to benchmark here. filepath.EvalSymlinks is expected to be slow and unpredictable, because it uses disk and network io. What do you want me to benchmark?
Maybe we can even make filepath.EvalSymlinks(path) faster for when path does not have any symlinks in it - skip useGetFinalPathNameByHandle call when walkSymlinks(path) returns newpath that is newpath == path. That will make filepath.EvalSymlinks cost the same as it is now for majority of use cases.
Oh, didn't you solve relpath=>abspath do you?
I am not sure what is your question. Please, try again.
Plus, I found the another inconsistency for local shares.
Tell me more about this inconsistency.
Maybe we can postpone the edge case for #19922.
Why postpone fixing issue #19922? I think PS3 has issue #19922 fixed.
> I'm fine with the patch set 2 if you agree.
But PS2 still leaves issue #19922 broken (when you have c:\alex\localdir on computer 2). Why do you prefer PS2 over PS3?
Alex
I am not sure what to benchmark here. filepath.EvalSymlinks is expected to be slow and unpredictable, because it uses disk and network io. What do you want me to benchmark?
Obviously, PS3 do the same work twice. In the worst case, it could be 2x slower. I'm not sure that's good enough.
I am not sure what is your question. Please, try again.
I thought you fixed as filepath.EvalSymlinks("junc") on network path doesn't return absolute path, it seems not.
Tell me more about this inconsistency.
What happen if you apply evalsymlinks to files under the junction on the local share?
I suppose,
filepath.EvalSymlinks(`x:\junc`) => `\\local\share\junc`
filepath.EvalSymlinks(`x:\junc\file`) => `C:\mountpoint\file`
Why postpone fixing issue #19922? I think PS3 has issue #19922 fixed.
I feel PS3 is worse than PS2. I explained the reason above.
But PS2 still leaves issue #19922 broken (when you have c:\alex\localdir on computer 2). Why do you prefer PS2 over PS3?
At least PS2 fixes #20506 correctly.
I'm skeptical about using GetFinalPathByHandle for #19922.
Obviously, PS3 do the same work twice. In the worst case, it could
be 2x slower. I'm not sure that's good enough.
Are you comparing PS3 against current code? Yes it will be twice slower than current code. But it fixes existing issues. Do you have an alternative fix that is faster than this.
I also mentioned some way to make current code faster (see "... Maybe we can even make filepath.EvalSymlinks(path) faster ..." in my previous post). What do you think about that?
I thought you fixed as filepath.EvalSymlinks("junc") on network
path doesn't return absolute path, it seems not.
Why should filepath.EvalSymlinks(`x:\junc`) not return absolute path?
> What happen if you apply evalsymlinks to files under the junction
> on the local share?
> I suppose,
>
> filepath.EvalSymlinks(`x:\junc`) => `\\local\share\junc`
> filepath.EvalSymlinks(`x:\junc\file`) => `C:\mountpoint\file`
You have to tell me more about how to test this.
Why postpone fixing issue #19922? I think PS3 has issue #19922
fixed.I feel PS3 is worse than PS2. I explained the reason above.
PS2 is broken (as you have explained with "mkdir c:\alex\localdir on computer 2" example yourself). So PS2 is not an option as far as correctness is concerned.
At least PS2 fixes #20506 correctly.
Fair enough.
I'm skeptical about using GetFinalPathByHandle for #19922.
I think that GetFinalPathByHandle will fix #19922. I still do not have a scenario where GetFinalPathByHandle returns invalid result for #19922. Please, provide some test that breaks GetFinalPathByHandle.
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Are you comparing PS3 against current code? Yes it will be twice slower than current code. But it fixes existing issues. Do you have an alternative fix that is faster than this.
I also mentioned some way to make current code faster (see "... Maybe we can even make filepath.EvalSymlinks(path) faster ..." in my previous post). What do you think about that?
Before talking about another approach, you may need to be convinced that PS3 is incorrect.
> Why should filepath.EvalSymlinks(`x:\junc`) not return absolute path?
EvalSymlinks(`junc`), not EvalSymlinks(`x:\junc`). You could test after Chdir(`x:\`). I expect that it returns `junc`
> You have to tell me more about how to test this.
Please tell me which parts you actually don't understand.
I found my prediction is wrong because of #21854, though.
> I think that GetFinalPathByHandle will fix #19922. I still do not have a scenario where GetFinalPathByHandle returns invalid result for #19922. Please, provide some test that breaks GetFinalPathByHandle.
I'm providing test cases now.
EvalSymlinks(`junc`), not EvalSymlinks(`x:\junc`). You could test after Chdir(`x:\`). I expect that it returns `junc`
OK. I change my program to:
```
package main
import (
"fmt"
"os"
"path/filepath"
)
func main() {
err := os.Chdir(`x:\`)
if err != nil {
panic(err)
}
path := `junc`
_, err = os.Stat(path)
if err != nil {
panic(err)
}
newpath, err := filepath.EvalSymlinks(path)
if err != nil {
panic(err)
}
fmt.Printf("EvalSymlinks(%q) returns %q\n", path, newpath)
}
```
and it outputs:
```
C:\Users\Alex>u:\test
EvalSymlinks("junc") returns "\\\\computer1\\mountpoint\\junc"C:\Users\Alex>
```
The output looks OK to me. No?
Please tell me which parts you actually don't understand.
I understand nothing. Assume I know nothing. If you want me to test some scenario, please, provide everything - the code and how to setup my environment.
I found my prediction is wrong because of #21854, though.
I am not sure why you are referring to issue #21854. #21854 is about os.SameFile with symlinks, but this CL uses os.SameFile with non-symlinks. So #21854 cannot affect this CL.
I'm providing test cases now.
I hope it is the test program above. And I have tested it for you. If I misunderstood you, tell me more.
Alex
In general, I disagree about behavior changes without the good reason even if the existing behavior is not well documented.
Maybe you have a different opinion. I don't want to explain those line by line.
If you think this CL is fine, I won't complain about it.
In general, I disagree about behavior changes without the good
reason ...
The reason for changing this code is to fix issues #19922 and #20506. What other reason do you need?
If you think this CL is fine, I won't complain about it.
Sure.
Alex
I made some small optimization in PS4. Hopefully it helps often enough to make this change efficient enough.
PTAL
Alex Brainman uploaded patch set #4 to this change.
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
---
M src/go/build/deps_test.go
M src/internal/syscall/windows/syscall_windows.go
M src/internal/syscall/windows/zsyscall_windows.go
M src/os/os_windows_test.go
M src/path/filepath/path_windows_test.go
M src/path/filepath/symlink_windows.go
6 files changed, 185 insertions(+), 20 deletions(-)
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Pinging Ian.
Alex
2 comments:
File src/path/filepath/symlink_windows.go:
Patch Set #4, Line 151: [1 << 29]uint1
Why do you need to convert to the pointer to array type? Can't you just pass buf? It's already a []uint16, and we certainly don't want syscall.UTF16ToString to run past the end.
Patch Set #4, Line 193: // walkSymlinks did not actually walked any symlinks,
s/walked/walk/
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Thank you for review. PTAL.
Alex
2 comments:
Patch Set #4, Line 151: [1 << 29]uint1
Why do you need to convert to the pointer to array type? Can't you just pass buf? It's already a [ […]
You are correct. "buf" variable contains what we need.
Patch Set #4, Line 193: // walkSymlinks did not actually walked any symlinks,
s/walked/walk/
Done
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Alex Brainman uploaded patch set #5 to this change.
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
---
M src/go/build/deps_test.go
M src/internal/syscall/windows/syscall_windows.go
M src/internal/syscall/windows/zsyscall_windows.go
M src/os/os_windows_test.go
M src/path/filepath/path_windows_test.go
M src/path/filepath/symlink_windows.go
6 files changed, 184 insertions(+), 20 deletions(-)
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.
Patch set 5:Code-Review +2
Alex Brainman merged this change.
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 <ia...@golang.org>
---
M src/go/build/deps_test.go
M src/internal/syscall/windows/syscall_windows.go
M src/internal/syscall/windows/zsyscall_windows.go
M src/os/os_windows_test.go
M src/path/filepath/path_windows_test.go
M src/path/filepath/symlink_windows.go
6 files changed, 184 insertions(+), 20 deletions(-)
diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
index 275c483..8f485f1 100644
--- a/src/go/build/deps_test.go
+++ b/src/go/build/deps_test.go
@@ -156,7 +156,7 @@
"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 ec08a5a..af87416 100644
--- a/src/internal/syscall/windows/syscall_windows.go
+++ b/src/internal/syscall/windows/syscall_windows.go
@@ -165,3 +165,19 @@
//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 7a2ffee..e882c89 100644
--- a/src/internal/syscall/windows/zsyscall_windows.go
+++ b/src/internal/syscall/windows/zsyscall_windows.go
@@ -41,21 +41,22 @@
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 @@
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 228fece..47e2611 100644
--- a/src/os/os_windows_test.go
+++ b/src/os/os_windows_test.go
@@ -520,10 +520,17 @@
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 d1b89bb..2ec5f5e 100644
--- a/src/path/filepath/path_windows_test.go
+++ b/src/path/filepath/path_windows_test.go
@@ -516,3 +516,37 @@
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 f771fe3..78cde4a 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 @@
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
}
To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.