1
0
mirror of https://github.com/golang/go synced 2024-11-19 11:44:45 -07:00

path/filepath: re-implement windows EvalSymlinks

CL 41834 used approach suggested by Raymond Chen in
https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
to implement os.Stat by getting Windows I/O manager
follow symbolic links.

Do the same for filepath.EvalSymlinks, when existing
strategy fails.

Updates #19922
Fixes #20506

Change-Id: I15f3d3a80256bae86ac4fb321fd8877e84d8834f
Reviewed-on: https://go-review.googlesource.com/55612
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
Alex Brainman 2017-08-09 11:33:40 +10:00
parent 56462d0f10
commit 66c03d39f3
6 changed files with 184 additions and 20 deletions

View File

@ -156,7 +156,7 @@ var pkgDeps = map[string][]string{
"internal/poll": {"L0", "internal/race", "syscall", "time", "unicode/utf16", "unicode/utf8"}, "internal/poll": {"L0", "internal/race", "syscall", "time", "unicode/utf16", "unicode/utf8"},
"os": {"L1", "os", "syscall", "time", "internal/poll", "internal/syscall/windows"}, "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"}, "io/ioutil": {"L2", "os", "path/filepath", "time"},
"os/exec": {"L2", "os", "context", "path/filepath", "syscall"}, "os/exec": {"L2", "os", "context", "path/filepath", "syscall"},
"os/signal": {"L2", "os", "syscall"}, "os/signal": {"L2", "os", "syscall"},

View File

@ -165,3 +165,19 @@ type SHARE_INFO_2 struct {
//sys NetShareAdd(serverName *uint16, level uint32, buf *byte, parmErr *uint16) (neterr error) = netapi32.NetShareAdd //sys 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 //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()
}

View File

@ -51,6 +51,7 @@ var (
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
procNetShareAdd = modnetapi32.NewProc("NetShareAdd") procNetShareAdd = modnetapi32.NewProc("NetShareAdd")
procNetShareDel = modnetapi32.NewProc("NetShareDel") procNetShareDel = modnetapi32.NewProc("NetShareDel")
procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
procRevertToSelf = modadvapi32.NewProc("RevertToSelf") procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken") procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
@ -157,6 +158,19 @@ func NetShareDel(serverName *uint16, netName *uint16, reserved uint32) (neterr e
return 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) { func ImpersonateSelf(impersonationlevel uint32) (err error) {
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(impersonationlevel), 0, 0) r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(impersonationlevel), 0, 0)
if r1 == 0 { if r1 == 0 {

View File

@ -520,10 +520,17 @@ func TestNetworkSymbolicLink(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if got != target { if got != target {
t.Errorf(`os.Readlink("%s"): got %v, want %v`, link, 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) { func TestStartProcessAttr(t *testing.T) {

View File

@ -516,3 +516,37 @@ func TestWalkDirectorySymlink(t *testing.T) {
testenv.MustHaveSymlink(t) testenv.MustHaveSymlink(t)
testWalkMklink(t, "D") 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)
}
}

View File

@ -5,6 +5,9 @@
package filepath package filepath
import ( import (
"errors"
"internal/syscall/windows"
"os"
"strings" "strings"
"syscall" "syscall"
) )
@ -106,10 +109,100 @@ func toNorm(path string, normBase func(string) (string, error)) (string, error)
return volume + normPath, nil return volume + normPath, nil
} }
func evalSymlinks(path string) (string, error) { // evalSymlinksUsingGetFinalPathNameByHandle uses Windows
path, err := walkSymlinks(path) // 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 { if err != nil {
return "", err 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
} }