--- /dev/null
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package windows
+
+const (
+ FSCTL_SET_REPARSE_POINT = 0x000900A4
+ IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
+
+ SYMLINK_FLAG_RELATIVE = 1
+)
+
+// These structures are described
+// in https://msdn.microsoft.com/en-us/library/cc232007.aspx
+// and https://msdn.microsoft.com/en-us/library/cc232006.aspx.
+
+// REPARSE_DATA_BUFFER_HEADER is a common part of REPARSE_DATA_BUFFER structure.
+type REPARSE_DATA_BUFFER_HEADER struct {
+ ReparseTag uint32
+ // The size, in bytes, of the reparse data that follows
+ // the common portion of the REPARSE_DATA_BUFFER element.
+ // This value is the length of the data starting at the
+ // SubstituteNameOffset field.
+ ReparseDataLength uint16
+ Reserved uint16
+}
+
+type SymbolicLinkReparseBuffer struct {
+ // The integer that contains the offset, in bytes,
+ // of the substitute name string in the PathBuffer array,
+ // computed as an offset from byte 0 of PathBuffer. Note that
+ // this offset must be divided by 2 to get the array index.
+ SubstituteNameOffset uint16
+ // The integer that contains the length, in bytes, of the
+ // substitute name string. If this string is null-terminated,
+ // SubstituteNameLength does not include the Unicode null character.
+ SubstituteNameLength uint16
+ // PrintNameOffset is similar to SubstituteNameOffset.
+ PrintNameOffset uint16
+ // PrintNameLength is similar to SubstituteNameLength.
+ PrintNameLength uint16
+ // Flags specifies whether the substitute name is a full path name or
+ // a path name relative to the directory containing the symbolic link.
+ Flags uint32
+ PathBuffer [1]uint16
+}
+
+type MountPointReparseBuffer struct {
+ // The integer that contains the offset, in bytes,
+ // of the substitute name string in the PathBuffer array,
+ // computed as an offset from byte 0 of PathBuffer. Note that
+ // this offset must be divided by 2 to get the array index.
+ SubstituteNameOffset uint16
+ // The integer that contains the length, in bytes, of the
+ // substitute name string. If this string is null-terminated,
+ // SubstituteNameLength does not include the Unicode null character.
+ SubstituteNameLength uint16
+ // PrintNameOffset is similar to SubstituteNameOffset.
+ PrintNameOffset uint16
+ // PrintNameLength is similar to SubstituteNameLength.
+ PrintNameLength uint16
+ PathBuffer [1]uint16
+}
var (
modiphlpapi = syscall.NewLazyDLL(sysdll.Add("iphlpapi.dll"))
modkernel32 = syscall.NewLazyDLL(sysdll.Add("kernel32.dll"))
+ modadvapi32 = syscall.NewLazyDLL(sysdll.Add("advapi32.dll"))
- procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
- procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
- procMoveFileExW = modkernel32.NewProc("MoveFileExW")
- procGetACP = modkernel32.NewProc("GetACP")
- procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
- procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar")
+ procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
+ procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
+ procMoveFileExW = modkernel32.NewProc("MoveFileExW")
+ procGetACP = modkernel32.NewProc("GetACP")
+ procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
+ procMultiByteToWideChar = modkernel32.NewProc("MultiByteToWideChar")
+ procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
+ procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
+ procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
+ procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
+ procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
+ procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
)
func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) {
}
return
}
+
+func GetCurrentThread() (pseudoHandle syscall.Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
+ pseudoHandle = syscall.Handle(r0)
+ if pseudoHandle == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func ImpersonateSelf(impersonationlevel uint32) (err error) {
+ r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(impersonationlevel), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func RevertToSelf() (err error) {
+ r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func OpenThreadToken(h syscall.Handle, access uint32, openasself bool, token *syscall.Token) (err error) {
+ var _p0 uint32
+ if openasself {
+ _p0 = 1
+ } else {
+ _p0 = 0
+ }
+ r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(h), uintptr(access), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func LookupPrivilegeValue(systemname *uint16, name *uint16, luid *LUID) (err error) {
+ r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemname)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func adjustTokenPrivileges(token syscall.Token, disableAllPrivileges bool, newstate *TOKEN_PRIVILEGES, buflen uint32, prevstate *TOKEN_PRIVILEGES, returnlen *uint32) (ret uint32, err error) {
+ var _p0 uint32
+ if disableAllPrivileges {
+ _p0 = 1
+ } else {
+ _p0 = 0
+ }
+ r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(newstate)), uintptr(buflen), uintptr(unsafe.Pointer(prevstate)), uintptr(unsafe.Pointer(returnlen)))
+ ret = uint32(r0)
+ if true {
+ if e1 != 0 {
+ err = errnoErr(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
package os_test
import (
+ "fmt"
+ "internal/syscall/windows"
"internal/testenv"
"io/ioutil"
"os"
osexec "os/exec"
"path/filepath"
+ "runtime"
"sort"
"strings"
"syscall"
"testing"
+ "unsafe"
)
-var supportJunctionLinks = true
-
-func init() {
- b, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
- if !strings.Contains(string(b), " /J ") {
- supportJunctionLinks = false
- }
-}
-
func TestSameWindowsFile(t *testing.T) {
temp, err := ioutil.TempDir("", "TestSameWindowsFile")
if err != nil {
}
}
-func TestStatJunctionLink(t *testing.T) {
- if !supportJunctionLinks {
- t.Skip("skipping because junction links are not supported")
+type dirLinkTest struct {
+ name string
+ mklink func(link, target string) error
+ issueNo int // correspondent issue number (for broken tests)
+}
+
+func testDirLinks(t *testing.T, tests []dirLinkTest) {
+ tmpdir, err := ioutil.TempDir("", "testDirLinks")
+ if err != nil {
+ t.Fatal(err)
}
+ defer os.RemoveAll(tmpdir)
- dir, err := ioutil.TempDir("", "go-build")
+ oldwd, err := os.Getwd()
if err != nil {
- t.Fatalf("failed to create temp directory: %v", err)
+ t.Fatal(err)
}
- defer os.RemoveAll(dir)
+ err = os.Chdir(tmpdir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Chdir(oldwd)
- link := filepath.Join(filepath.Dir(dir), filepath.Base(dir)+"-link")
+ dir := filepath.Join(tmpdir, "dir")
+ err = os.Mkdir(dir, 0777)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = ioutil.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, test := range tests {
+ link := filepath.Join(tmpdir, test.name+"_link")
+ err := test.mklink(link, dir)
+ if err != nil {
+ t.Errorf("creating link for %s test failed: %v", test.name, err)
+ continue
+ }
+
+ data, err := ioutil.ReadFile(filepath.Join(link, "abc"))
+ if err != nil {
+ t.Errorf("failed to read abc file: %v", err)
+ continue
+ }
+ if string(data) != "abc" {
+ t.Errorf(`abc file is expected to have "abc" in it, but has %v`, data)
+ continue
+ }
+
+ if test.issueNo > 0 {
+ t.Logf("skipping broken %q test: see issue %d", test.name, test.issueNo)
+ continue
+ }
+
+ fi, err := os.Stat(link)
+ if err != nil {
+ t.Errorf("failed to stat link %v: %v", link, err)
+ continue
+ }
+ expected := filepath.Base(dir)
+ got := fi.Name()
+ if !fi.IsDir() || expected != got {
+ t.Errorf("link should point to %v but points to %v instead", expected, got)
+ continue
+ }
+ }
+}
+
+// reparseData is used to build reparse buffer data required for tests.
+type reparseData struct {
+ substituteName namePosition
+ printName namePosition
+ pathBuf []uint16
+}
- output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, dir).CombinedOutput()
+type namePosition struct {
+ offset uint16
+ length uint16
+}
+
+func (rd *reparseData) addUTF16s(s []uint16) (offset uint16) {
+ off := len(rd.pathBuf) * 2
+ rd.pathBuf = append(rd.pathBuf, s...)
+ return uint16(off)
+}
+
+func (rd *reparseData) addString(s string) (offset, length uint16) {
+ p := syscall.StringToUTF16(s)
+ return rd.addUTF16s(p), uint16(len(p)-1) * 2 // do not include terminating NUL in the legth (as per PrintNameLength and SubstituteNameLength documentation)
+}
+
+func (rd *reparseData) addSubstituteName(name string) {
+ rd.substituteName.offset, rd.substituteName.length = rd.addString(name)
+}
+
+func (rd *reparseData) addPrintName(name string) {
+ rd.printName.offset, rd.printName.length = rd.addString(name)
+}
+
+func (rd *reparseData) addStringNoNUL(s string) (offset, length uint16) {
+ p := syscall.StringToUTF16(s)
+ p = p[:len(p)-1]
+ return rd.addUTF16s(p), uint16(len(p)) * 2
+}
+
+func (rd *reparseData) addSubstituteNameNoNUL(name string) {
+ rd.substituteName.offset, rd.substituteName.length = rd.addStringNoNUL(name)
+}
+
+func (rd *reparseData) addPrintNameNoNUL(name string) {
+ rd.printName.offset, rd.printName.length = rd.addStringNoNUL(name)
+}
+
+// pathBuffeLen returns length of rd pathBuf in bytes.
+func (rd *reparseData) pathBuffeLen() uint16 {
+ return uint16(len(rd.pathBuf)) * 2
+}
+
+// Windows REPARSE_DATA_BUFFER contains union member, and cannot be
+// translated into Go directly. _REPARSE_DATA_BUFFER type is to help
+// construct alternative versions of Windows REPARSE_DATA_BUFFER with
+// union part of SymbolicLinkReparseBuffer or MountPointReparseBuffer type.
+type _REPARSE_DATA_BUFFER struct {
+ header windows.REPARSE_DATA_BUFFER_HEADER
+ detail [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
+}
+
+func createDirLink(link string, rdb *_REPARSE_DATA_BUFFER) error {
+ err := os.Mkdir(link, 0777)
+ if err != nil {
+ return err
+ }
+
+ linkp := syscall.StringToUTF16(link)
+ fd, err := syscall.CreateFile(&linkp[0], syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING,
+ syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
+ if err != nil {
+ return err
+ }
+ defer syscall.CloseHandle(fd)
+
+ buflen := uint32(rdb.header.ReparseDataLength) + uint32(unsafe.Sizeof(rdb.header))
+ var bytesReturned uint32
+ return syscall.DeviceIoControl(fd, windows.FSCTL_SET_REPARSE_POINT,
+ (*byte)(unsafe.Pointer(&rdb.header)), buflen, nil, 0, &bytesReturned, nil)
+}
+
+func createMountPoint(link string, target *reparseData) error {
+ var buf *windows.MountPointReparseBuffer
+ buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
+ byteblob := make([]byte, buflen)
+ buf = (*windows.MountPointReparseBuffer)(unsafe.Pointer(&byteblob[0]))
+ buf.SubstituteNameOffset = target.substituteName.offset
+ buf.SubstituteNameLength = target.substituteName.length
+ buf.PrintNameOffset = target.printName.offset
+ buf.PrintNameLength = target.printName.length
+ copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf)
+
+ var rdb _REPARSE_DATA_BUFFER
+ rdb.header.ReparseTag = windows.IO_REPARSE_TAG_MOUNT_POINT
+ rdb.header.ReparseDataLength = buflen
+ copy(rdb.detail[:], byteblob)
+
+ return createDirLink(link, &rdb)
+}
+
+func TestDirectoryJunction(t *testing.T) {
+ var tests = []dirLinkTest{
+ {
+ // Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
+ name: "standard",
+ mklink: func(link, target string) error {
+ var t reparseData
+ t.addSubstituteName(`\??\` + target)
+ t.addPrintName(target)
+ return createMountPoint(link, &t)
+ },
+ },
+ {
+ // Do as junction utility https://technet.microsoft.com/en-au/sysinternals/bb896768.aspx does - set PrintNameLength to 0.
+ name: "have_blank_print_name",
+ mklink: func(link, target string) error {
+ var t reparseData
+ t.addSubstituteName(`\??\` + target)
+ t.addPrintName("")
+ return createMountPoint(link, &t)
+ },
+ issueNo: 16145,
+ },
+ }
+ output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
+ mklinkSupportsJunctionLinks := strings.Contains(string(output), " /J ")
+ if mklinkSupportsJunctionLinks {
+ tests = append(tests,
+ dirLinkTest{
+ name: "use_mklink_cmd",
+ mklink: func(link, target string) error {
+ output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, target).CombinedOutput()
+ if err != nil {
+ fmt.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
+ }
+ return nil
+ },
+ },
+ )
+ } else {
+ t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory junctions`)
+ }
+ testDirLinks(t, tests)
+}
+
+func enableCurrentThreadPrivilege(privilegeName string) error {
+ ct, err := windows.GetCurrentThread()
if err != nil {
- t.Fatalf("failed to run mklink %v %v: %v %q", link, dir, err, output)
+ return err
}
- defer os.Remove(link)
+ var t syscall.Token
+ err = windows.OpenThreadToken(ct, syscall.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t)
+ if err != nil {
+ return err
+ }
+ defer syscall.CloseHandle(syscall.Handle(t))
+
+ var tp windows.TOKEN_PRIVILEGES
- fi, err := os.Stat(link)
+ privStr, err := syscall.UTF16PtrFromString(privilegeName)
if err != nil {
- t.Fatalf("failed to stat link %v: %v", link, err)
+ return 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)
+ err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid)
+ if err != nil {
+ return err
+ }
+ tp.PrivilegeCount = 1
+ tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED
+ return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil)
+}
+
+func createSymbolicLink(link string, target *reparseData, isrelative bool) error {
+ var buf *windows.SymbolicLinkReparseBuffer
+ buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
+ byteblob := make([]byte, buflen)
+ buf = (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&byteblob[0]))
+ buf.SubstituteNameOffset = target.substituteName.offset
+ buf.SubstituteNameLength = target.substituteName.length
+ buf.PrintNameOffset = target.printName.offset
+ buf.PrintNameLength = target.printName.length
+ if isrelative {
+ buf.Flags = windows.SYMLINK_FLAG_RELATIVE
+ }
+ copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf)
+
+ var rdb _REPARSE_DATA_BUFFER
+ rdb.header.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
+ rdb.header.ReparseDataLength = buflen
+ copy(rdb.detail[:], byteblob)
+
+ return createDirLink(link, &rdb)
+}
+
+func TestDirectorySymbolicLink(t *testing.T) {
+ var tests []dirLinkTest
+ output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
+ mklinkSupportsDirectorySymbolicLinks := strings.Contains(string(output), " /D ")
+ if mklinkSupportsDirectorySymbolicLinks {
+ tests = append(tests,
+ dirLinkTest{
+ name: "use_mklink_cmd",
+ mklink: func(link, target string) error {
+ output, err := osexec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput()
+ if err != nil {
+ fmt.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
+ }
+ return nil
+ },
+ },
+ )
+ } else {
+ t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory symbolic links`)
+ }
+
+ // The rest of these test requires SeCreateSymbolicLinkPrivilege to be held.
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ err := windows.ImpersonateSelf(windows.SecurityImpersonation)
+ if err != nil {
+ t.Fatal(err)
}
+ defer windows.RevertToSelf()
+
+ err = enableCurrentThreadPrivilege("SeCreateSymbolicLinkPrivilege")
+ if err != nil {
+ t.Skipf(`skipping some tests, could not enable "SeCreateSymbolicLinkPrivilege": %v`, err)
+ }
+ tests = append(tests,
+ dirLinkTest{
+ name: "use_os_pkg",
+ mklink: func(link, target string) error {
+ return os.Symlink(target, link)
+ },
+ },
+ dirLinkTest{
+ // Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
+ name: "standard",
+ mklink: func(link, target string) error {
+ var t reparseData
+ t.addPrintName(target)
+ t.addSubstituteName(`\??\` + target)
+ return createSymbolicLink(link, &t, false)
+ },
+ },
+ dirLinkTest{
+ name: "relative",
+ mklink: func(link, target string) error {
+ var t reparseData
+ t.addSubstituteNameNoNUL(filepath.Base(target))
+ t.addPrintNameNoNUL(filepath.Base(target))
+ return createSymbolicLink(link, &t, true)
+ },
+ issueNo: 15978,
+ },
+ )
+ testDirLinks(t, tests)
}
func TestStartProcessAttr(t *testing.T) {