Commit e77fbd59 authored by mattn's avatar mattn Committed by Alex Brainman

syscall: Readlink doesn't handle junction on windows

Fixes #9190

Change-Id: I22177687ed834feed165454019d28c11fcbf0fa2
Reviewed-on: https://go-review.googlesource.com/2307Reviewed-by: 's avatarAlex Brainman <alex.brainman@gmail.com>
parent f58a5cb9
...@@ -3,11 +3,15 @@ package os_test ...@@ -3,11 +3,15 @@ package os_test
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
osexec "os/exec"
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
"testing" "testing"
) )
var supportJunctionLinks = true
func init() { func init() {
tmpdir, err := ioutil.TempDir("", "symtest") tmpdir, err := ioutil.TempDir("", "symtest")
if err != nil { if err != nil {
...@@ -16,14 +20,18 @@ func init() { ...@@ -16,14 +20,18 @@ func init() {
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
err = os.Symlink("target", filepath.Join(tmpdir, "symlink")) err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
if err == nil { if err != nil {
return err = err.(*os.LinkError).Err
switch err {
case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
supportsSymlinks = false
}
} }
defer os.Remove("target")
err = err.(*os.LinkError).Err b, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
switch err { if !strings.Contains(string(b), " /J ") {
case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD: supportJunctionLinks = false
supportsSymlinks = false
} }
} }
...@@ -79,3 +87,33 @@ func TestSameWindowsFile(t *testing.T) { ...@@ -79,3 +87,33 @@ func TestSameWindowsFile(t *testing.T) {
t.Errorf("files should be same") t.Errorf("files should be same")
} }
} }
func TestStatJunctionLink(t *testing.T) {
if !supportJunctionLinks {
t.Skip("skipping because junction links are not supported")
}
dir, err := ioutil.TempDir("", "go-build")
if err != nil {
t.Fatalf("failed to create temp directory: %v", err)
}
defer os.RemoveAll(dir)
link := filepath.Join(filepath.Dir(dir), filepath.Base(dir)+"-link")
output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, dir).CombinedOutput()
if err != nil {
t.Fatalf("failed to run mklink %v %v: %v %q", link, dir, err, output)
}
defer os.Remove(link)
fi, err := os.Stat(link)
if err != nil {
t.Fatalf("failed to stat link %v: %v", link, err)
}
expected := filepath.Base(dir)
got := fi.Name()
if !fi.IsDir() || expected != got {
t.Fatalf("link should point to %v but points to %v instead", expected, got)
}
}
...@@ -1003,13 +1003,22 @@ func Readlink(path string, buf []byte) (n int, err error) { ...@@ -1003,13 +1003,22 @@ func Readlink(path string, buf []byte) (n int, err error) {
} }
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0])) rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0]))
if uintptr(bytesReturned) < unsafe.Sizeof(*rdb) || var s string
rdb.ReparseTag != IO_REPARSE_TAG_SYMLINK { switch rdb.ReparseTag {
// the path is not a symlink but another type of reparse point case IO_REPARSE_TAG_SYMLINK:
data := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
s = UTF16ToString(p[data.PrintNameOffset/2 : (data.PrintNameLength-data.PrintNameOffset)/2])
case _IO_REPARSE_TAG_MOUNT_POINT:
data := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
s = UTF16ToString(p[data.PrintNameOffset/2 : (data.PrintNameLength-data.PrintNameOffset)/2])
default:
// the path is not a symlink or junction but another type of reparse
// point
return -1, ENOENT return -1, ENOENT
} }
s := UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rdb.PathBuffer[0]))[:rdb.PrintNameLength/2])
n = copy(buf, []byte(s)) n = copy(buf, []byte(s))
return n, nil return n, nil
} }
...@@ -1083,12 +1083,7 @@ type TCPKeepalive struct { ...@@ -1083,12 +1083,7 @@ type TCPKeepalive struct {
Interval uint32 Interval uint32
} }
type reparseDataBuffer struct { type symbolicLinkReparseBuffer struct {
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
// SymbolicLinkReparseBuffer
SubstituteNameOffset uint16 SubstituteNameOffset uint16
SubstituteNameLength uint16 SubstituteNameLength uint16
PrintNameOffset uint16 PrintNameOffset uint16
...@@ -1097,9 +1092,27 @@ type reparseDataBuffer struct { ...@@ -1097,9 +1092,27 @@ type reparseDataBuffer struct {
PathBuffer [1]uint16 PathBuffer [1]uint16
} }
type mountPointReparseBuffer struct {
SubstituteNameOffset uint16
SubstituteNameLength uint16
PrintNameOffset uint16
PrintNameLength uint16
PathBuffer [1]uint16
}
type reparseDataBuffer struct {
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
// GenericReparseBuffer
reparseBuffer byte
}
const ( const (
FSCTL_GET_REPARSE_POINT = 0x900A8 FSCTL_GET_REPARSE_POINT = 0x900A8
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024 MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024
_IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
IO_REPARSE_TAG_SYMLINK = 0xA000000C IO_REPARSE_TAG_SYMLINK = 0xA000000C
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
) )
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment