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:
parent
56462d0f10
commit
66c03d39f3
@ -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"},
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user