import (
"io/ioutil"
"os"
+ osexec "os/exec"
"path/filepath"
+ "strings"
"syscall"
"testing"
)
+var supportJunctionLinks = true
+
func init() {
tmpdir, err := ioutil.TempDir("", "symtest")
if err != nil {
defer os.RemoveAll(tmpdir)
err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
- if err == nil {
- return
+ if err != nil {
+ 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
- switch err {
- case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
- supportsSymlinks = false
+ b, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
+ if !strings.Contains(string(b), " /J ") {
+ supportJunctionLinks = false
}
}
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)
+ }
+}
}
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0]))
- if uintptr(bytesReturned) < unsafe.Sizeof(*rdb) ||
- rdb.ReparseTag != IO_REPARSE_TAG_SYMLINK {
- // the path is not a symlink but another type of reparse point
+ var s string
+ switch rdb.ReparseTag {
+ 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
}
-
- s := UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(&rdb.PathBuffer[0]))[:rdb.PrintNameLength/2])
n = copy(buf, []byte(s))
+
return n, nil
}
Interval uint32
}
-type reparseDataBuffer struct {
- ReparseTag uint32
- ReparseDataLength uint16
- Reserved uint16
-
- // SymbolicLinkReparseBuffer
+type symbolicLinkReparseBuffer struct {
SubstituteNameOffset uint16
SubstituteNameLength uint16
PrintNameOffset 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 (
FSCTL_GET_REPARSE_POINT = 0x900A8
MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024
+ _IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
IO_REPARSE_TAG_SYMLINK = 0xA000000C
SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1
)