Skip to content

Commit 5717385

Browse files
mattnYinJiaJin
authored and
YinJiaJin
committed
syscall: Readlink doesn't handle junction on windows
Fixes #9190 Change-Id: I22177687ed834feed165454019d28c11fcbf0fa2 Reviewed-on: https://go-review.googlesource.com/2307 Reviewed-by: Alex Brainman <[email protected]>
1 parent b44db4e commit 5717385

File tree

3 files changed

+77
-17
lines changed

3 files changed

+77
-17
lines changed

Diff for: src/os/os_windows_test.go

+44-6
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ package os_test
33
import (
44
"io/ioutil"
55
"os"
6+
osexec "os/exec"
67
"path/filepath"
8+
"strings"
79
"syscall"
810
"testing"
911
)
1012

13+
var supportJunctionLinks = true
14+
1115
func init() {
1216
tmpdir, err := ioutil.TempDir("", "symtest")
1317
if err != nil {
@@ -16,14 +20,18 @@ func init() {
1620
defer os.RemoveAll(tmpdir)
1721

1822
err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
19-
if err == nil {
20-
return
23+
if err != nil {
24+
err = err.(*os.LinkError).Err
25+
switch err {
26+
case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
27+
supportsSymlinks = false
28+
}
2129
}
30+
defer os.Remove("target")
2231

23-
err = err.(*os.LinkError).Err
24-
switch err {
25-
case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
26-
supportsSymlinks = false
32+
b, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
33+
if !strings.Contains(string(b), " /J ") {
34+
supportJunctionLinks = false
2735
}
2836
}
2937

@@ -79,3 +87,33 @@ func TestSameWindowsFile(t *testing.T) {
7987
t.Errorf("files should be same")
8088
}
8189
}
90+
91+
func TestStatJunctionLink(t *testing.T) {
92+
if !supportJunctionLinks {
93+
t.Skip("skipping because junction links are not supported")
94+
}
95+
96+
dir, err := ioutil.TempDir("", "go-build")
97+
if err != nil {
98+
t.Fatalf("failed to create temp directory: %v", err)
99+
}
100+
defer os.RemoveAll(dir)
101+
102+
link := filepath.Join(filepath.Dir(dir), filepath.Base(dir)+"-link")
103+
104+
output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, dir).CombinedOutput()
105+
if err != nil {
106+
t.Fatalf("failed to run mklink %v %v: %v %q", link, dir, err, output)
107+
}
108+
defer os.Remove(link)
109+
110+
fi, err := os.Stat(link)
111+
if err != nil {
112+
t.Fatalf("failed to stat link %v: %v", link, err)
113+
}
114+
expected := filepath.Base(dir)
115+
got := fi.Name()
116+
if !fi.IsDir() || expected != got {
117+
t.Fatalf("link should point to %v but points to %v instead", expected, got)
118+
}
119+
}

Diff for: src/syscall/syscall_windows.go

+14-5
Original file line numberDiff line numberDiff line change
@@ -1003,13 +1003,22 @@ func Readlink(path string, buf []byte) (n int, err error) {
10031003
}
10041004

10051005
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0]))
1006-
if uintptr(bytesReturned) < unsafe.Sizeof(*rdb) ||
1007-
rdb.ReparseTag != IO_REPARSE_TAG_SYMLINK {
1008-
// the path is not a symlink but another type of reparse point
1006+
var s string
1007+
switch rdb.ReparseTag {
1008+
case IO_REPARSE_TAG_SYMLINK:
1009+
data := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
1010+
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
1011+
s = UTF16ToString(p[data.PrintNameOffset/2 : (data.PrintNameLength-data.PrintNameOffset)/2])
1012+
case _IO_REPARSE_TAG_MOUNT_POINT:
1013+
data := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
1014+
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
1015+
s = UTF16ToString(p[data.PrintNameOffset/2 : (data.PrintNameLength-data.PrintNameOffset)/2])
1016+
default:
1017+
// the path is not a symlink or junction but another type of reparse
1018+
// point
10091019
return -1, ENOENT
10101020
}
1011-
1012-
s := UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rdb.PathBuffer[0]))[:rdb.PrintNameLength/2])
10131021
n = copy(buf, []byte(s))
1022+
10141023
return n, nil
10151024
}

Diff for: src/syscall/ztypes_windows.go

+19-6
Original file line numberDiff line numberDiff line change
@@ -1083,12 +1083,7 @@ type TCPKeepalive struct {
10831083
Interval uint32
10841084
}
10851085

1086-
type reparseDataBuffer struct {
1087-
ReparseTag uint32
1088-
ReparseDataLength uint16
1089-
Reserved uint16
1090-
1091-
// SymbolicLinkReparseBuffer
1086+
type symbolicLinkReparseBuffer struct {
10921087
SubstituteNameOffset uint16
10931088
SubstituteNameLength uint16
10941089
PrintNameOffset uint16
@@ -1097,9 +1092,27 @@ type reparseDataBuffer struct {
10971092
PathBuffer [1]uint16
10981093
}
10991094

1095+
type mountPointReparseBuffer struct {
1096+
SubstituteNameOffset uint16
1097+
SubstituteNameLength uint16
1098+
PrintNameOffset uint16
1099+
PrintNameLength uint16
1100+
PathBuffer [1]uint16
1101+
}
1102+
1103+
type reparseDataBuffer struct {
1104+
ReparseTag uint32
1105+
ReparseDataLength uint16
1106+
Reserved uint16
1107+
1108+
// GenericReparseBuffer
1109+
reparseBuffer byte
1110+
}
1111+
11001112
const (
11011113
FSCTL_GET_REPARSE_POINT = 0x900A8
11021114
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024
1115+
_IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
11031116
IO_REPARSE_TAG_SYMLINK = 0xA000000C
11041117
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
11051118
)

0 commit comments

Comments
 (0)