mirror of
https://github.com/golang/go.git
synced 2025-05-18 22:04:38 +00:00
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>
1006 lines
24 KiB
Go
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`)
|
|
}
|
|
}
|