go/src/os/os_windows_test.go
Alex Brainman 1c95d9728a os: use FILE_FLAG_OPEN_REPARSE_POINT in SameFile
SameFile opens file to discover identifier and volume serial
number that uniquely identify the file. SameFile uses Windows
CreateFile API to open the file, and that works well for files
and directories. But CreateFile always follows symlinks, so
SameFile always opens symlink target instead of symlink itself.

This CL uses FILE_FLAG_OPEN_REPARSE_POINT flag to adjust
CreateFile behavior when handling symlinks.

As per https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted

"... If FILE_FLAG_OPEN_REPARSE_POINT is specified and:

If an existing file is opened and it is a symbolic link, the handle
returned is a handle to the symbolic link. ...".

I also added new tests for both issue #21854 and #27225.
Issue #27225 is still to be fixed, so skipping the test on
windows for the moment.

Fixes #21854
Updates #27225

Change-Id: I8aaa13ad66ce3b4074991bb50994d2aeeeaa7c95
Reviewed-on: https://go-review.googlesource.com/134195
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
2018-09-29 04:02:38 +00:00

1006 lines
24 KiB
Go

// Copyright 2014 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 os_test
import (
"fmt"
"internal/poll"
"internal/syscall/windows"
"internal/syscall/windows/registry"
"internal/testenv"
"io"
"io/ioutil"
"os"
osexec "os/exec"
"path/filepath"
"reflect"
"runtime"
"sort"
"strings"
"syscall"
"testing"
"unicode/utf16"
"unsafe"
)
func TestSameWindowsFile(t *testing.T) {
temp, err := ioutil.TempDir("", "TestSameWindowsFile")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(temp)
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Chdir(temp)
if err != nil {
t.Fatal(err)
}
defer os.Chdir(wd)
f, err := os.Create("a")
if err != nil {
t.Fatal(err)
}
f.Close()
ia1, err := os.Stat("a")
if err != nil {
t.Fatal(err)
}
path, err := filepath.Abs("a")
if err != nil {
t.Fatal(err)
}
ia2, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if !os.SameFile(ia1, ia2) {
t.Errorf("files should be same")
}
p := filepath.VolumeName(path) + filepath.Base(path)
if err != nil {
t.Fatal(err)
}
ia3, err := os.Stat(p)
if err != nil {
t.Fatal(err)
}
if !os.SameFile(ia1, ia3) {
t.Errorf("files should be same")
}
}
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)
oldwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Chdir(tmpdir)
if err != nil {
t.Fatal(err)
}
defer os.Chdir(oldwd)
dir := filepath.Join(tmpdir, "dir")
err = os.Mkdir(dir, 0777)
if err != nil {
t.Fatal(err)
}
fi, err := os.Stat(dir)
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 %q 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
}
fi1, err := os.Stat(link)
if err != nil {
t.Errorf("failed to stat link %v: %v", link, err)
continue
}
if !fi1.IsDir() {
t.Errorf("%q should be a directory", link)
continue
}
if fi1.Name() != filepath.Base(link) {
t.Errorf("Stat(%q).Name() = %q, want %q", link, fi1.Name(), filepath.Base(link))
continue
}
if !os.SameFile(fi, fi1) {
t.Errorf("%q should point to %q", link, dir)
continue
}
fi2, err := os.Lstat(link)
if err != nil {
t.Errorf("failed to lstat link %v: %v", link, err)
continue
}
if m := fi2.Mode(); m&os.ModeSymlink == 0 {
t.Errorf("%q should be a link, but is not (mode=0x%x)", link, uint32(m))
continue
}
if m := fi2.Mode(); m&os.ModeDir != 0 {
t.Errorf("%q should be a link, not a directory (mode=0x%x)", link, uint32(m))
continue
}
}
}
// reparseData is used to build reparse buffer data required for tests.
type reparseData struct {
substituteName namePosition
printName namePosition
pathBuf []uint16
}
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 length (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)
},
},
}
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 {
t.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 {
return err
}
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
privStr, err := syscall.UTF16PtrFromString(privilegeName)
if err != nil {
return err
}
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 {
t.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)
},
},
)
testDirLinks(t, tests)
}
func TestNetworkSymbolicLink(t *testing.T) {
testenv.MustHaveSymlink(t)
const _NERR_ServerNotStarted = syscall.Errno(2114)
dir, err := ioutil.TempDir("", "TestNetworkSymbolicLink")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
oldwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Chdir(dir)
if err != nil {
t.Fatal(err)
}
defer os.Chdir(oldwd)
shareName := "GoSymbolicLinkTestShare" // hope no conflictions
sharePath := filepath.Join(dir, shareName)
testDir := "TestDir"
err = os.MkdirAll(filepath.Join(sharePath, testDir), 0777)
if err != nil {
t.Fatal(err)
}
wShareName, err := syscall.UTF16PtrFromString(shareName)
if err != nil {
t.Fatal(err)
}
wSharePath, err := syscall.UTF16PtrFromString(sharePath)
if err != nil {
t.Fatal(err)
}
p := windows.SHARE_INFO_2{
Netname: wShareName,
Type: windows.STYPE_DISKTREE,
Remark: nil,
Permissions: 0,
MaxUses: 1,
CurrentUses: 0,
Path: wSharePath,
Passwd: nil,
}
err = windows.NetShareAdd(nil, 2, (*byte)(unsafe.Pointer(&p)), nil)
if err != nil {
if err == syscall.ERROR_ACCESS_DENIED {
t.Skip("you don't have enough privileges to add network share")
}
if err == _NERR_ServerNotStarted {
t.Skip(_NERR_ServerNotStarted.Error())
}
t.Fatal(err)
}
defer func() {
err := windows.NetShareDel(nil, wShareName, 0)
if err != nil {
t.Fatal(err)
}
}()
UNCPath := `\\localhost\` + shareName + `\`
fi1, err := os.Stat(sharePath)
if err != nil {
t.Fatal(err)
}
fi2, err := os.Stat(UNCPath)
if err != nil {
t.Fatal(err)
}
if !os.SameFile(fi1, fi2) {
t.Fatalf("%q and %q should be the same directory, but not", sharePath, UNCPath)
}
target := filepath.Join(UNCPath, testDir)
link := "link"
err = os.Symlink(target, link)
if err != nil {
t.Fatal(err)
}
defer os.Remove(link)
got, err := os.Readlink(link)
if err != nil {
t.Fatal(err)
}
if 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) {
p, err := os.StartProcess(os.Getenv("COMSPEC"), []string{"/c", "cd"}, new(os.ProcAttr))
if err != nil {
return
}
defer p.Wait()
t.Fatalf("StartProcess expected to fail, but succeeded.")
}
func TestShareNotExistError(t *testing.T) {
if testing.Short() {
t.Skip("slow test that uses network; skipping")
}
_, err := os.Stat(`\\no_such_server\no_such_share\no_such_file`)
if err == nil {
t.Fatal("stat succeeded, but expected to fail")
}
if !os.IsNotExist(err) {
t.Fatalf("os.Stat failed with %q, but os.IsNotExist(err) is false", err)
}
}
func TestBadNetPathError(t *testing.T) {
const ERROR_BAD_NETPATH = syscall.Errno(53)
if !os.IsNotExist(ERROR_BAD_NETPATH) {
t.Fatal("os.IsNotExist(syscall.Errno(53)) is false, but want true")
}
}
func TestStatDir(t *testing.T) {
defer chtmpdir(t)()
f, err := os.Open(".")
if err != nil {
t.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatal(err)
}
err = os.Chdir("..")
if err != nil {
t.Fatal(err)
}
fi2, err := f.Stat()
if err != nil {
t.Fatal(err)
}
if !os.SameFile(fi, fi2) {
t.Fatal("race condition occurred")
}
}
func TestOpenVolumeName(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "TestOpenVolumeName")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.Chdir(tmpdir)
if err != nil {
t.Fatal(err)
}
defer os.Chdir(wd)
want := []string{"file1", "file2", "file3", "gopher.txt"}
sort.Strings(want)
for _, name := range want {
err := ioutil.WriteFile(filepath.Join(tmpdir, name), nil, 0777)
if err != nil {
t.Fatal(err)
}
}
f, err := os.Open(filepath.VolumeName(tmpdir))
if err != nil {
t.Fatal(err)
}
defer f.Close()
have, err := f.Readdirnames(-1)
if err != nil {
t.Fatal(err)
}
sort.Strings(have)
if strings.Join(want, "/") != strings.Join(have, "/") {
t.Fatalf("unexpected file list %q, want %q", have, want)
}
}
func TestDeleteReadOnly(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "TestDeleteReadOnly")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
p := filepath.Join(tmpdir, "a")
// This sets FILE_ATTRIBUTE_READONLY.
f, err := os.OpenFile(p, os.O_CREATE, 0400)
if err != nil {
t.Fatal(err)
}
f.Close()
if err = os.Chmod(p, 0400); err != nil {
t.Fatal(err)
}
if err = os.Remove(p); err != nil {
t.Fatal(err)
}
}
func TestStatSymlinkLoop(t *testing.T) {
testenv.MustHaveSymlink(t)
defer chtmpdir(t)()
err := os.Symlink("x", "y")
if err != nil {
t.Fatal(err)
}
defer os.Remove("y")
err = os.Symlink("y", "x")
if err != nil {
t.Fatal(err)
}
defer os.Remove("x")
_, err = os.Stat("x")
if _, ok := err.(*os.PathError); !ok {
t.Errorf("expected *PathError, got %T: %v\n", err, err)
}
}
func TestReadStdin(t *testing.T) {
old := poll.ReadConsole
defer func() {
poll.ReadConsole = old
}()
testConsole := os.NewConsoleFile(syscall.Stdin, "test")
var tests = []string{
"abc",
"äöü",
"\u3042",
"“hi”™",
"hello\x1aworld",
"\U0001F648\U0001F649\U0001F64A",
}
for _, consoleSize := range []int{1, 2, 3, 10, 16, 100, 1000} {
for _, readSize := range []int{1, 2, 3, 4, 5, 8, 10, 16, 20, 50, 100} {
for _, s := range tests {
t.Run(fmt.Sprintf("c%d/r%d/%s", consoleSize, readSize, s), func(t *testing.T) {
s16 := utf16.Encode([]rune(s))
poll.ReadConsole = func(h syscall.Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) error {
if inputControl != nil {
t.Fatalf("inputControl not nil")
}
n := int(toread)
if n > consoleSize {
n = consoleSize
}
n = copy((*[10000]uint16)(unsafe.Pointer(buf))[:n], s16)
s16 = s16[n:]
*read = uint32(n)
t.Logf("read %d -> %d", toread, *read)
return nil
}
var all []string
var buf []byte
chunk := make([]byte, readSize)
for {
n, err := testConsole.Read(chunk)
buf = append(buf, chunk[:n]...)
if err == io.EOF {
all = append(all, string(buf))
if len(all) >= 5 {
break
}
buf = buf[:0]
} else if err != nil {
t.Fatalf("reading %q: error: %v", s, err)
}
if len(buf) >= 2000 {
t.Fatalf("reading %q: stuck in loop: %q", s, buf)
}
}
want := strings.Split(s, "\x1a")
for len(want) < 5 {
want = append(want, "")
}
if !reflect.DeepEqual(all, want) {
t.Errorf("reading %q:\nhave %x\nwant %x", s, all, want)
}
})
}
}
}
}
func TestStatPagefile(t *testing.T) {
_, err := os.Stat(`c:\pagefile.sys`)
if err == nil {
return
}
if os.IsNotExist(err) {
t.Skip(`skipping because c:\pagefile.sys is not found`)
}
t.Fatal(err)
}
// syscallCommandLineToArgv calls syscall.CommandLineToArgv
// and converts returned result into []string.
func syscallCommandLineToArgv(cmd string) ([]string, error) {
var argc int32
argv, err := syscall.CommandLineToArgv(&syscall.StringToUTF16(cmd)[0], &argc)
if err != nil {
return nil, err
}
defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv))))
var args []string
for _, v := range (*argv)[:argc] {
args = append(args, syscall.UTF16ToString((*v)[:]))
}
return args, nil
}
// compareCommandLineToArgvWithSyscall ensures that
// os.CommandLineToArgv(cmd) and syscall.CommandLineToArgv(cmd)
// return the same result.
func compareCommandLineToArgvWithSyscall(t *testing.T, cmd string) {
syscallArgs, err := syscallCommandLineToArgv(cmd)
if err != nil {
t.Fatal(err)
}
args := os.CommandLineToArgv(cmd)
if want, have := fmt.Sprintf("%q", syscallArgs), fmt.Sprintf("%q", args); want != have {
t.Errorf("testing os.commandLineToArgv(%q) failed: have %q want %q", cmd, args, syscallArgs)
return
}
}
func TestCmdArgs(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "TestCmdArgs")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
const prog = `
package main
import (
"fmt"
"os"
)
func main() {
fmt.Printf("%q", os.Args)
}
`
src := filepath.Join(tmpdir, "main.go")
err = ioutil.WriteFile(src, []byte(prog), 0666)
if err != nil {
t.Fatal(err)
}
exe := filepath.Join(tmpdir, "main.exe")
cmd := osexec.Command(testenv.GoToolPath(t), "build", "-o", exe, src)
cmd.Dir = tmpdir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("building main.exe failed: %v\n%s", err, out)
}
var cmds = []string{
``,
` a b c`,
` "`,
` ""`,
` """`,
` "" a`,
` "123"`,
` \"123\"`,
` \"123 456\"`,
` \\"`,
` \\\"`,
` \\\\\"`,
` \\\"x`,
` """"\""\\\"`,
` abc`,
` \\\\\""x"""y z`,
"\tb\t\"x\ty\"",
` "Брад" d e`,
// examples from https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
` "abc" d e`,
` a\\b d"e f"g h`,
` a\\\"b c d`,
` a\\\\"b c" d e`,
// http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
// from 5.4 Examples
` CallMeIshmael`,
` "Call Me Ishmael"`,
` Cal"l Me I"shmael`,
` CallMe\"Ishmael`,
` "CallMe\"Ishmael"`,
` "Call Me Ishmael\\"`,
` "CallMe\\\"Ishmael"`,
` a\\\b`,
` "a\\\b"`,
// from 5.5 Some Common Tasks
` "\"Call Me Ishmael\""`,
` "C:\TEST A\\"`,
` "\"C:\TEST A\\\""`,
// from 5.6 The Microsoft Examples Explained
` "a b c" d e`,
` "ab\"c" "\\" d`,
` a\\\b d"e f"g h`,
` a\\\"b c d`,
` a\\\\"b c" d e`,
// from 5.7 Double Double Quote Examples (pre 2008)
` "a b c""`,
` """CallMeIshmael""" b c`,
` """Call Me Ishmael"""`,
` """"Call Me Ishmael"" b c`,
}
for _, cmd := range cmds {
compareCommandLineToArgvWithSyscall(t, "test"+cmd)
compareCommandLineToArgvWithSyscall(t, `"cmd line"`+cmd)
compareCommandLineToArgvWithSyscall(t, exe+cmd)
// test both syscall.EscapeArg and os.commandLineToArgv
args := os.CommandLineToArgv(exe + cmd)
out, err := osexec.Command(args[0], args[1:]...).CombinedOutput()
if err != nil {
t.Fatalf("running %q failed: %v\n%v", args, err, string(out))
}
if want, have := fmt.Sprintf("%q", args), string(out); want != have {
t.Errorf("wrong output of executing %q: have %q want %q", args, have, want)
continue
}
}
}
func findOneDriveDir() (string, error) {
// as per https://stackoverflow.com/questions/42519624/how-to-determine-location-of-onedrive-on-windows-7-and-8-in-c
const onedrivekey = `SOFTWARE\Microsoft\OneDrive`
k, err := registry.OpenKey(registry.CURRENT_USER, onedrivekey, registry.READ)
if err != nil {
return "", fmt.Errorf("OpenKey(%q) failed: %v", onedrivekey, err)
}
defer k.Close()
path, _, err := k.GetStringValue("UserFolder")
if err != nil {
return "", fmt.Errorf("reading UserFolder failed: %v", err)
}
return path, nil
}
// TestOneDrive verifies that OneDrive folder is a directory and not a symlink.
func TestOneDrive(t *testing.T) {
dir, err := findOneDriveDir()
if err != nil {
t.Skipf("Skipping, because we did not find OneDrive directory: %v", err)
}
testDirStats(t, dir)
}
func TestWindowsDevNullFile(t *testing.T) {
testDevNullFile(t, "NUL", true)
testDevNullFile(t, "nul", true)
testDevNullFile(t, "Nul", true)
f1, err := os.Open("NUL")
if err != nil {
t.Fatal(err)
}
defer f1.Close()
fi1, err := f1.Stat()
if err != nil {
t.Fatal(err)
}
f2, err := os.Open("nul")
if err != nil {
t.Fatal(err)
}
defer f2.Close()
fi2, err := f2.Stat()
if err != nil {
t.Fatal(err)
}
if !os.SameFile(fi1, fi2) {
t.Errorf(`"NUL" and "nul" are not the same file`)
}
}
// TestSymlinkCreation verifies that creating a symbolic link
// works on Windows when developer mode is active.
// This is supported starting Windows 10 (1703, v10.0.14972).
func TestSymlinkCreation(t *testing.T) {
if !isWindowsDeveloperModeActive() {
t.Skip("Windows developer mode is not active")
}
temp, err := ioutil.TempDir("", "TestSymlinkCreation")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(temp)
dummyFile := filepath.Join(temp, "file")
err = ioutil.WriteFile(dummyFile, []byte(""), 0644)
if err != nil {
t.Fatal(err)
}
linkFile := filepath.Join(temp, "link")
err = os.Symlink(dummyFile, linkFile)
if err != nil {
t.Fatal(err)
}
}
// isWindowsDeveloperModeActive checks whether or not the developer mode is active on Windows 10.
// Returns false for prior Windows versions.
// see https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development
func isWindowsDeveloperModeActive() bool {
key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", registry.READ)
if err != nil {
return false
}
val, _, err := key.GetIntegerValue("AllowDevelopmentWithoutDevLicense")
if err != nil {
return false
}
return val != 0
}
// TestStatOfInvalidName is regression test for issue #24999.
func TestStatOfInvalidName(t *testing.T) {
_, err := os.Stat("*.go")
if err == nil {
t.Fatal(`os.Stat("*.go") unexpectedly succeeded`)
}
}