[go] path/filepath: re-implement windows EvalSymlinks

178 views
Skip to first unread message

Alex Brainman (Gerrit)

unread,
Aug 12, 2017, 3:02:34 AM8/12/17
to Ian Lance Taylor, Rob Pike, Hiroshi Ioka, golang-co...@googlegroups.com

Alex Brainman has uploaded this change for review.

View 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 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.

Gerrit-Project: go
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: If2008ca0ebf639553d23bae0d6a5f77f96f0f621
Gerrit-Change-Number: 55250
Gerrit-PatchSet: 1
Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>

Hiroshi Ioka (Gerrit)

unread,
Aug 13, 2017, 8:40:03 AM8/13/17
to Alex Brainman, golang-co...@googlegroups.com

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.

View Change

    To view, visit change 55250. To unsubscribe, or for help writing mail filters, visit settings.

    Gerrit-Project: go
    Gerrit-Branch: master
    Gerrit-MessageType: comment
    Gerrit-Change-Id: If2008ca0ebf639553d23bae0d6a5f77f96f0f621
    Gerrit-Change-Number: 55250
    Gerrit-PatchSet: 1
    Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
    Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
    Gerrit-Comment-Date: Sun, 13 Aug 2017 12:39:55 +0000
    Gerrit-HasComments: No
    Gerrit-HasLabels: No

    Alex Brainman (Gerrit)

    unread,
    Aug 13, 2017, 11:49:54 PM8/13/17
    to Hiroshi Ioka, golang-co...@googlegroups.com

    ..., 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

    View Change

      To view, visit change 55250. To unsubscribe, or for help writing mail filters, visit settings.

      Gerrit-Project: go
      Gerrit-Branch: master
      Gerrit-MessageType: comment
      Gerrit-Change-Id: If2008ca0ebf639553d23bae0d6a5f77f96f0f621
      Gerrit-Change-Number: 55250
      Gerrit-PatchSet: 1
      Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
      Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
      Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
      Gerrit-Comment-Date: Mon, 14 Aug 2017 03:49:46 +0000
      Gerrit-HasComments: No
      Gerrit-HasLabels: No

      Hiroshi Ioka (Gerrit)

      unread,
      Aug 14, 2017, 11:22:08 AM8/14/17
      to Alex Brainman, golang-co...@googlegroups.com

      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.

      View Change

        To view, visit change 55250. To unsubscribe, or for help writing mail filters, visit settings.

        Gerrit-Project: go
        Gerrit-Branch: master
        Gerrit-MessageType: comment
        Gerrit-Change-Id: If2008ca0ebf639553d23bae0d6a5f77f96f0f621
        Gerrit-Change-Number: 55250
        Gerrit-PatchSet: 1
        Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
        Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
        Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
        Gerrit-Comment-Date: Mon, 14 Aug 2017 15:22:02 +0000
        Gerrit-HasComments: No
        Gerrit-HasLabels: No

        Alex Brainman (Gerrit)

        unread,
        Aug 14, 2017, 10:54:01 PM8/14/17
        to Hiroshi Ioka, golang-co...@googlegroups.com


        ... 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

        View Change

          To view, visit change 55250. To unsubscribe, or for help writing mail filters, visit settings.

          Gerrit-Project: go
          Gerrit-Branch: master
          Gerrit-MessageType: comment
          Gerrit-Change-Id: If2008ca0ebf639553d23bae0d6a5f77f96f0f621
          Gerrit-Change-Number: 55250
          Gerrit-PatchSet: 1
          Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
          Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
          Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
          Gerrit-Comment-Date: Tue, 15 Aug 2017 02:53:56 +0000
          Gerrit-HasComments: No
          Gerrit-HasLabels: No

          Alex Brainman (Gerrit)

          unread,
          Aug 15, 2017, 12:27:16 AM8/15/17
          to Ian Lance Taylor, Rob Pike, Hiroshi Ioka, golang-co...@googlegroups.com

          Alex Brainman has uploaded this change for review.

          View 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 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.

          Gerrit-Project: go
          Gerrit-Branch: master
          Gerrit-MessageType: newchange
          Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
          Gerrit-Change-Number: 55612
          Gerrit-PatchSet: 1
          Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
          Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>

          Alex Brainman (Gerrit)

          unread,
          Aug 15, 2017, 12:28:25 AM8/15/17
          to Hiroshi Ioka, golang-co...@googlegroups.com

          Alex Brainman abandoned this change.

          View Change

          Abandoned Unfortunately this CL does not quite work. For example, if I have \\server\share mounted as u:, then after this CL filepath.EvalSymlinks(`u:\`) returns \\server\share. But I think we want filepath.EvalSymlinks(`u:\`) return u:\ as before. Abandoning this in favor of https://go-review.googlesource.com/55612

          To view, visit change 55250. To unsubscribe, or for help writing mail filters, visit settings.

          Gerrit-Project: go
          Gerrit-Branch: master
          Gerrit-MessageType: abandon
          Gerrit-Change-Id: If2008ca0ebf639553d23bae0d6a5f77f96f0f621
          Gerrit-Change-Number: 55250
          Gerrit-PatchSet: 1
          Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
          Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
          Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>

          Alex Brainman (Gerrit)

          unread,
          Aug 15, 2017, 12:29:06 AM8/15/17
          to Hiroshi Ioka, golang-co...@googlegroups.com

          This CL is replacement of 55250.

          Alex

          View Change

            To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

            Gerrit-Project: go
            Gerrit-Branch: master
            Gerrit-MessageType: comment
            Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
            Gerrit-Change-Number: 55612
            Gerrit-PatchSet: 1
            Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
            Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
            Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
            Gerrit-Comment-Date: Tue, 15 Aug 2017 04:29:02 +0000
            Gerrit-HasComments: No
            Gerrit-HasLabels: No

            Alex Brainman (Gerrit)

            unread,
            Aug 17, 2017, 10:18:25 PM8/17/17
            to Hiroshi Ioka, golang-co...@googlegroups.com

            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

            View Change

              To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

              Gerrit-Project: go
              Gerrit-Branch: master
              Gerrit-MessageType: comment
              Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
              Gerrit-Change-Number: 55612
              Gerrit-PatchSet: 1
              Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
              Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
              Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
              Gerrit-Comment-Date: Fri, 18 Aug 2017 02:18:19 +0000
              Gerrit-HasComments: No
              Gerrit-HasLabels: No

              Alex Brainman (Gerrit)

              unread,
              Sep 7, 2017, 10:52:25 PM9/7/17
              to Hiroshi Ioka, golang-co...@googlegroups.com

              Alex Brainman uploaded patch set #2 to this change.

              View 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.

              Gerrit-Project: go
              Gerrit-Branch: master
              Gerrit-MessageType: newpatchset
              Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
              Gerrit-Change-Number: 55612
              Gerrit-PatchSet: 2
              Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
              Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
              Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>

              Alex Brainman (Gerrit)

              unread,
              Sep 7, 2017, 10:52:54 PM9/7/17
              to Hiroshi Ioka, golang-co...@googlegroups.com

              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

              View Change

                To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                Gerrit-Project: go
                Gerrit-Branch: master
                Gerrit-MessageType: comment
                Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                Gerrit-Change-Number: 55612
                Gerrit-PatchSet: 2
                Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                Gerrit-Comment-Date: Fri, 08 Sep 2017 02:52:48 +0000
                Gerrit-HasComments: No
                Gerrit-HasLabels: No

                Hiroshi Ioka (Gerrit)

                unread,
                Sep 8, 2017, 12:14:49 AM9/8/17
                to Alex Brainman, golang-co...@googlegroups.com

                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.

                View Change

                  To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                  Gerrit-Project: go
                  Gerrit-Branch: master
                  Gerrit-MessageType: comment
                  Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                  Gerrit-Change-Number: 55612
                  Gerrit-PatchSet: 2
                  Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                  Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                  Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                  Gerrit-Comment-Date: Fri, 08 Sep 2017 04:14:44 +0000
                  Gerrit-HasComments: No
                  Gerrit-HasLabels: No

                  Alex Brainman (Gerrit)

                  unread,
                  Sep 9, 2017, 3:56:36 AM9/9/17
                  to Hiroshi Ioka, golang-co...@googlegroups.com

                  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

                  View Change

                    To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                    Gerrit-Project: go
                    Gerrit-Branch: master
                    Gerrit-MessageType: comment
                    Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                    Gerrit-Change-Number: 55612
                    Gerrit-PatchSet: 2
                    Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                    Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                    Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                    Gerrit-Comment-Date: Sat, 09 Sep 2017 07:56:30 +0000
                    Gerrit-HasComments: No
                    Gerrit-HasLabels: No

                    Hiroshi Ioka (Gerrit)

                    unread,
                    Sep 9, 2017, 7:31:10 PM9/9/17
                    to Alex Brainman, golang-co...@googlegroups.com
                    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.

                    View Change

                      To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                      Gerrit-Project: go
                      Gerrit-Branch: master
                      Gerrit-MessageType: comment
                      Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                      Gerrit-Change-Number: 55612
                      Gerrit-PatchSet: 2
                      Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                      Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                      Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                      Gerrit-Comment-Date: Sat, 09 Sep 2017 23:31:05 +0000
                      Gerrit-HasComments: No
                      Gerrit-HasLabels: No

                      Alex Brainman (Gerrit)

                      unread,
                      Sep 9, 2017, 9:36:41 PM9/9/17
                      to Hiroshi Ioka, golang-co...@googlegroups.com

                      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

                      View Change

                        To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                        Gerrit-Project: go
                        Gerrit-Branch: master
                        Gerrit-MessageType: comment
                        Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                        Gerrit-Change-Number: 55612
                        Gerrit-PatchSet: 2
                        Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                        Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                        Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                        Gerrit-Comment-Date: Sun, 10 Sep 2017 01:36:35 +0000
                        Gerrit-HasComments: No
                        Gerrit-HasLabels: No

                        Hiroshi Ioka (Gerrit)

                        unread,
                        Sep 9, 2017, 9:42:52 PM9/9/17
                        to Alex Brainman, golang-co...@googlegroups.com

                        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.

                        View Change

                          To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                          Gerrit-Project: go
                          Gerrit-Branch: master
                          Gerrit-MessageType: comment
                          Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                          Gerrit-Change-Number: 55612
                          Gerrit-PatchSet: 2
                          Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                          Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                          Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                          Gerrit-Comment-Date: Sun, 10 Sep 2017 01:42:47 +0000
                          Gerrit-HasComments: No
                          Gerrit-HasLabels: No

                          Alex Brainman (Gerrit)

                          unread,
                          Sep 9, 2017, 11:57:38 PM9/9/17
                          to Hiroshi Ioka, golang-co...@googlegroups.com


                          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

                          View Change

                            To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                            Gerrit-Project: go
                            Gerrit-Branch: master
                            Gerrit-MessageType: comment
                            Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                            Gerrit-Change-Number: 55612
                            Gerrit-PatchSet: 2
                            Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                            Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                            Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                            Gerrit-Comment-Date: Sun, 10 Sep 2017 03:57:33 +0000
                            Gerrit-HasComments: No
                            Gerrit-HasLabels: No

                            Hiroshi Ioka (Gerrit)

                            unread,
                            Sep 10, 2017, 12:10:02 AM9/10/17
                            to Alex Brainman, golang-co...@googlegroups.com

                            What do you propose we do here?

                            If we see that drive type of the path is DRIVE_REMOTE, then use GetFinalPathByHandle?

                            View Change

                              To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                              Gerrit-Project: go
                              Gerrit-Branch: master
                              Gerrit-MessageType: comment
                              Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                              Gerrit-Change-Number: 55612
                              Gerrit-PatchSet: 2
                              Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                              Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                              Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                              Gerrit-Comment-Date: Sun, 10 Sep 2017 04:09:57 +0000
                              Gerrit-HasComments: No
                              Gerrit-HasLabels: No

                              Hiroshi Ioka (Gerrit)

                              unread,
                              Sep 10, 2017, 12:15:17 AM9/10/17
                              to Alex Brainman, golang-co...@googlegroups.com

                              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.

                              View Change

                                To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                Gerrit-Project: go
                                Gerrit-Branch: master
                                Gerrit-MessageType: comment
                                Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                Gerrit-Change-Number: 55612
                                Gerrit-PatchSet: 2
                                Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                Gerrit-Comment-Date: Sun, 10 Sep 2017 04:15:12 +0000
                                Gerrit-HasComments: No
                                Gerrit-HasLabels: No

                                Hiroshi Ioka (Gerrit)

                                unread,
                                Sep 10, 2017, 12:26:38 AM9/10/17
                                to Alex Brainman, golang-co...@googlegroups.com

                                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.

                                View Change

                                  To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                  Gerrit-Project: go
                                  Gerrit-Branch: master
                                  Gerrit-MessageType: comment
                                  Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                  Gerrit-Change-Number: 55612
                                  Gerrit-PatchSet: 2
                                  Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                  Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                  Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                  Gerrit-Comment-Date: Sun, 10 Sep 2017 04:26:34 +0000
                                  Gerrit-HasComments: No
                                  Gerrit-HasLabels: No

                                  Alex Brainman (Gerrit)

                                  unread,
                                  Sep 10, 2017, 1:13:21 AM9/10/17
                                  to Hiroshi Ioka, golang-co...@googlegroups.com

                                  Alex Brainman uploaded patch set #3 to this change.

                                  View 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.

                                  Gerrit-Project: go
                                  Gerrit-Branch: master
                                  Gerrit-MessageType: newpatchset
                                  Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                  Gerrit-Change-Number: 55612
                                  Gerrit-PatchSet: 3
                                  Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                  Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                  Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>

                                  Alex Brainman (Gerrit)

                                  unread,
                                  Sep 10, 2017, 1:13:42 AM9/10/17
                                  to Hiroshi Ioka, golang-co...@googlegroups.com

                                  Hiroshi, I decided to use os.SameFile to overcome problem with c:\alex\localdir on computer 2. Maybe it is good enough. PTAL.

                                  Alex

                                  View Change

                                    To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                    Gerrit-Project: go
                                    Gerrit-Branch: master
                                    Gerrit-MessageType: comment
                                    Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                    Gerrit-Change-Number: 55612
                                    Gerrit-PatchSet: 3
                                    Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                    Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                    Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                    Gerrit-Comment-Date: Sun, 10 Sep 2017 05:13:37 +0000
                                    Gerrit-HasComments: No
                                    Gerrit-HasLabels: No

                                    Hiroshi Ioka (Gerrit)

                                    unread,
                                    Sep 10, 2017, 1:26:06 AM9/10/17
                                    to Alex Brainman, golang-co...@googlegroups.com

                                    It looks correct at a glance. But you may need to do benchmarking.

                                    View Change

                                      To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                      Gerrit-Project: go
                                      Gerrit-Branch: master
                                      Gerrit-MessageType: comment
                                      Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                      Gerrit-Change-Number: 55612
                                      Gerrit-PatchSet: 3
                                      Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                      Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                      Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                      Gerrit-Comment-Date: Sun, 10 Sep 2017 05:26:00 +0000
                                      Gerrit-HasComments: No
                                      Gerrit-HasLabels: No

                                      Hiroshi Ioka (Gerrit)

                                      unread,
                                      Sep 10, 2017, 6:16:43 AM9/10/17
                                      to Alex Brainman, golang-co...@googlegroups.com

                                      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.

                                      View Change

                                        To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                        Gerrit-Project: go
                                        Gerrit-Branch: master
                                        Gerrit-MessageType: comment
                                        Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                        Gerrit-Change-Number: 55612
                                        Gerrit-PatchSet: 3
                                        Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                        Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                        Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                        Gerrit-Comment-Date: Sun, 10 Sep 2017 10:16:38 +0000
                                        Gerrit-HasComments: No
                                        Gerrit-HasLabels: No

                                        Alex Brainman (Gerrit)

                                        unread,
                                        Sep 10, 2017, 10:19:16 PM9/10/17
                                        to Hiroshi Ioka, golang-co...@googlegroups.com

                                        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

                                        View Change

                                          To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                          Gerrit-Project: go
                                          Gerrit-Branch: master
                                          Gerrit-MessageType: comment
                                          Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                          Gerrit-Change-Number: 55612
                                          Gerrit-PatchSet: 3
                                          Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                          Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                          Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                          Gerrit-Comment-Date: Mon, 11 Sep 2017 02:19:10 +0000
                                          Gerrit-HasComments: No
                                          Gerrit-HasLabels: No

                                          Hiroshi Ioka (Gerrit)

                                          unread,
                                          Sep 10, 2017, 11:10:44 PM9/10/17
                                          to Alex Brainman, golang-co...@googlegroups.com

                                          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.

                                          View Change

                                            To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                            Gerrit-Project: go
                                            Gerrit-Branch: master
                                            Gerrit-MessageType: comment
                                            Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                            Gerrit-Change-Number: 55612
                                            Gerrit-PatchSet: 3
                                            Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                            Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                            Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                            Gerrit-Comment-Date: Mon, 11 Sep 2017 03:10:39 +0000
                                            Gerrit-HasComments: No
                                            Gerrit-HasLabels: No

                                            Alex Brainman (Gerrit)

                                            unread,
                                            Sep 11, 2017, 2:17:55 AM9/11/17
                                            to Hiroshi Ioka, golang-co...@googlegroups.com

                                            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.

                                            Thank you.

                                            Alex

                                            View Change

                                              To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                              Gerrit-Project: go
                                              Gerrit-Branch: master
                                              Gerrit-MessageType: comment
                                              Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                              Gerrit-Change-Number: 55612
                                              Gerrit-PatchSet: 3
                                              Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                              Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                              Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                              Gerrit-Comment-Date: Mon, 11 Sep 2017 06:17:50 +0000
                                              Gerrit-HasComments: No
                                              Gerrit-HasLabels: No

                                              Hiroshi Ioka (Gerrit)

                                              unread,
                                              Sep 12, 2017, 8:37:05 PM9/12/17
                                              to Alex Brainman, golang-co...@googlegroups.com

                                              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.

                                              View Change

                                                To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                Gerrit-Project: go
                                                Gerrit-Branch: master
                                                Gerrit-MessageType: comment
                                                Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                Gerrit-Change-Number: 55612
                                                Gerrit-PatchSet: 3
                                                Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                Gerrit-Comment-Date: Wed, 13 Sep 2017 00:36:59 +0000
                                                Gerrit-HasComments: No
                                                Gerrit-HasLabels: No

                                                Alex Brainman (Gerrit)

                                                unread,
                                                Sep 18, 2017, 9:29:54 PM9/18/17
                                                to Hiroshi Ioka, golang-co...@googlegroups.com

                                                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

                                                View Change

                                                  To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                  Gerrit-Project: go
                                                  Gerrit-Branch: master
                                                  Gerrit-MessageType: comment
                                                  Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                  Gerrit-Change-Number: 55612
                                                  Gerrit-PatchSet: 3
                                                  Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                  Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                  Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                  Gerrit-Comment-Date: Tue, 19 Sep 2017 01:29:46 +0000
                                                  Gerrit-HasComments: No
                                                  Gerrit-HasLabels: No

                                                  Hiroshi Ioka (Gerrit)

                                                  unread,
                                                  Sep 19, 2017, 1:37:03 AM9/19/17
                                                  to Alex Brainman, golang-co...@googlegroups.com

                                                  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.

                                                  View Change

                                                    To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                    Gerrit-Project: go
                                                    Gerrit-Branch: master
                                                    Gerrit-MessageType: comment
                                                    Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                    Gerrit-Change-Number: 55612
                                                    Gerrit-PatchSet: 3
                                                    Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                    Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                    Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                    Gerrit-Comment-Date: Tue, 19 Sep 2017 05:36:56 +0000
                                                    Gerrit-HasComments: No
                                                    Gerrit-HasLabels: No

                                                    Alex Brainman (Gerrit)

                                                    unread,
                                                    Sep 19, 2017, 1:44:08 AM9/19/17
                                                    to Hiroshi Ioka, golang-co...@googlegroups.com

                                                    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

                                                    View Change

                                                      To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                      Gerrit-Project: go
                                                      Gerrit-Branch: master
                                                      Gerrit-MessageType: comment
                                                      Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                      Gerrit-Change-Number: 55612
                                                      Gerrit-PatchSet: 3
                                                      Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                      Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                      Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                      Gerrit-Comment-Date: Tue, 19 Sep 2017 05:44:04 +0000
                                                      Gerrit-HasComments: No
                                                      Gerrit-HasLabels: No

                                                      Alex Brainman (Gerrit)

                                                      unread,
                                                      Sep 21, 2017, 10:41:28 PM9/21/17
                                                      to goph...@pubsubhelper.golang.org, Hiroshi Ioka, golang-co...@googlegroups.com

                                                      I made some small optimization in PS4. Hopefully it helps often enough to make this change efficient enough.

                                                      PTAL

                                                      Alex

                                                      View Change

                                                        To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                        Gerrit-Project: go
                                                        Gerrit-Branch: master
                                                        Gerrit-MessageType: comment
                                                        Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                        Gerrit-Change-Number: 55612
                                                        Gerrit-PatchSet: 3
                                                        Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                        Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                        Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                        Gerrit-Comment-Date: Fri, 22 Sep 2017 02:41:20 +0000
                                                        Gerrit-HasComments: No
                                                        Gerrit-HasLabels: No

                                                        Alex Brainman (Gerrit)

                                                        unread,
                                                        Sep 21, 2017, 10:41:48 PM9/21/17
                                                        to goph...@pubsubhelper.golang.org, Hiroshi Ioka, golang-co...@googlegroups.com

                                                        Alex Brainman uploaded patch set #4 to this change.

                                                        View 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.

                                                        Gerrit-Project: go
                                                        Gerrit-Branch: master
                                                        Gerrit-MessageType: newpatchset
                                                        Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                        Gerrit-Change-Number: 55612
                                                        Gerrit-PatchSet: 4

                                                        Alex Brainman (Gerrit)

                                                        unread,
                                                        Oct 1, 2017, 3:02:53 AM10/1/17
                                                        to goph...@pubsubhelper.golang.org, Ian Lance Taylor, Hiroshi Ioka, golang-co...@googlegroups.com

                                                        Pinging Ian.

                                                        Alex

                                                        View Change

                                                          To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                          Gerrit-Project: go
                                                          Gerrit-Branch: master
                                                          Gerrit-MessageType: comment
                                                          Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                          Gerrit-Change-Number: 55612
                                                          Gerrit-PatchSet: 4
                                                          Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                          Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                          Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
                                                          Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                          Gerrit-Comment-Date: Sun, 01 Oct 2017 07:02:46 +0000
                                                          Gerrit-HasComments: No
                                                          Gerrit-HasLabels: No

                                                          Ian Lance Taylor (Gerrit)

                                                          unread,
                                                          Oct 3, 2017, 10:49:30 AM10/3/17
                                                          to Alex Brainman, goph...@pubsubhelper.golang.org, Hiroshi Ioka, golang-co...@googlegroups.com

                                                          View Change

                                                          2 comments:

                                                          To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                          Gerrit-Project: go
                                                          Gerrit-Branch: master
                                                          Gerrit-MessageType: comment
                                                          Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                          Gerrit-Change-Number: 55612
                                                          Gerrit-PatchSet: 4
                                                          Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                          Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                          Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
                                                          Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                          Gerrit-Comment-Date: Tue, 03 Oct 2017 14:49:27 +0000
                                                          Gerrit-HasComments: Yes
                                                          Gerrit-HasLabels: No

                                                          Alex Brainman (Gerrit)

                                                          unread,
                                                          Oct 4, 2017, 1:44:26 AM10/4/17
                                                          to goph...@pubsubhelper.golang.org, Ian Lance Taylor, Hiroshi Ioka, golang-co...@googlegroups.com

                                                          Thank you for review. PTAL.

                                                          Alex

                                                          View Change

                                                          2 comments:

                                                            • 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.

                                                            • Done

                                                          To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                          Gerrit-Project: go
                                                          Gerrit-Branch: master
                                                          Gerrit-MessageType: comment
                                                          Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                          Gerrit-Change-Number: 55612
                                                          Gerrit-PatchSet: 4
                                                          Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                          Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                          Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
                                                          Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                          Gerrit-Comment-Date: Wed, 04 Oct 2017 05:44:16 +0000
                                                          Gerrit-HasComments: Yes
                                                          Gerrit-HasLabels: No

                                                          Alex Brainman (Gerrit)

                                                          unread,
                                                          Oct 4, 2017, 1:44:48 AM10/4/17
                                                          to Ian Lance Taylor, goph...@pubsubhelper.golang.org, Hiroshi Ioka, golang-co...@googlegroups.com

                                                          Alex Brainman uploaded patch set #5 to this change.

                                                          View 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.

                                                          Gerrit-Project: go
                                                          Gerrit-Branch: master
                                                          Gerrit-MessageType: newpatchset
                                                          Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                          Gerrit-Change-Number: 55612
                                                          Gerrit-PatchSet: 5

                                                          Ian Lance Taylor (Gerrit)

                                                          unread,
                                                          Oct 4, 2017, 8:45:22 AM10/4/17
                                                          to Alex Brainman, goph...@pubsubhelper.golang.org, Hiroshi Ioka, golang-co...@googlegroups.com

                                                          Patch set 5:Code-Review +2

                                                          View Change

                                                            To view, visit change 55612. To unsubscribe, or for help writing mail filters, visit settings.

                                                            Gerrit-Project: go
                                                            Gerrit-Branch: master
                                                            Gerrit-MessageType: comment
                                                            Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                            Gerrit-Change-Number: 55612
                                                            Gerrit-PatchSet: 5
                                                            Gerrit-Owner: Alex Brainman <alex.b...@gmail.com>
                                                            Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
                                                            Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
                                                            Gerrit-CC: Hiroshi Ioka <hiroch...@gmail.com>
                                                            Gerrit-Comment-Date: Wed, 04 Oct 2017 12:45:19 +0000
                                                            Gerrit-HasComments: No
                                                            Gerrit-HasLabels: Yes

                                                            Alex Brainman (Gerrit)

                                                            unread,
                                                            Oct 5, 2017, 12:16:09 AM10/5/17
                                                            to goph...@pubsubhelper.golang.org, golang-...@googlegroups.com, Ian Lance Taylor, Hiroshi Ioka, golang-co...@googlegroups.com

                                                            Alex Brainman merged this change.

                                                            View Change

                                                            Approvals: Ian Lance Taylor: Looks good to me, approved
                                                            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.

                                                            Gerrit-Project: go
                                                            Gerrit-Branch: master
                                                            Gerrit-MessageType: merged
                                                            Gerrit-Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
                                                            Gerrit-Change-Number: 55612
                                                            Gerrit-PatchSet: 7
                                                            Reply all
                                                            Reply to author
                                                            Forward
                                                            0 new messages