github.com/flyinox/gosm@v0.0.0-20171117061539-16768cb62077/src/os/os_windows_test.go (about)

     1  // Copyright 2014 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package os_test
     6  
     7  import (
     8  	"fmt"
     9  	"internal/poll"
    10  	"internal/syscall/windows"
    11  	"internal/testenv"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  	osexec "os/exec"
    16  	"path/filepath"
    17  	"reflect"
    18  	"runtime"
    19  	"sort"
    20  	"strings"
    21  	"syscall"
    22  	"testing"
    23  	"unicode/utf16"
    24  	"unsafe"
    25  )
    26  
    27  func TestSameWindowsFile(t *testing.T) {
    28  	temp, err := ioutil.TempDir("", "TestSameWindowsFile")
    29  	if err != nil {
    30  		t.Fatal(err)
    31  	}
    32  	defer os.RemoveAll(temp)
    33  
    34  	wd, err := os.Getwd()
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  	err = os.Chdir(temp)
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	defer os.Chdir(wd)
    43  
    44  	f, err := os.Create("a")
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    48  	f.Close()
    49  
    50  	ia1, err := os.Stat("a")
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  
    55  	path, err := filepath.Abs("a")
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  	ia2, err := os.Stat(path)
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  	if !os.SameFile(ia1, ia2) {
    64  		t.Errorf("files should be same")
    65  	}
    66  
    67  	p := filepath.VolumeName(path) + filepath.Base(path)
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  	ia3, err := os.Stat(p)
    72  	if err != nil {
    73  		t.Fatal(err)
    74  	}
    75  	if !os.SameFile(ia1, ia3) {
    76  		t.Errorf("files should be same")
    77  	}
    78  }
    79  
    80  type dirLinkTest struct {
    81  	name    string
    82  	mklink  func(link, target string) error
    83  	issueNo int // correspondent issue number (for broken tests)
    84  }
    85  
    86  func testDirLinks(t *testing.T, tests []dirLinkTest) {
    87  	tmpdir, err := ioutil.TempDir("", "testDirLinks")
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  	defer os.RemoveAll(tmpdir)
    92  
    93  	oldwd, err := os.Getwd()
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  	err = os.Chdir(tmpdir)
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	defer os.Chdir(oldwd)
   102  
   103  	dir := filepath.Join(tmpdir, "dir")
   104  	err = os.Mkdir(dir, 0777)
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  	fi, err := os.Stat(dir)
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	err = ioutil.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644)
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  	for _, test := range tests {
   117  		link := filepath.Join(tmpdir, test.name+"_link")
   118  		err := test.mklink(link, dir)
   119  		if err != nil {
   120  			t.Errorf("creating link for %q test failed: %v", test.name, err)
   121  			continue
   122  		}
   123  
   124  		data, err := ioutil.ReadFile(filepath.Join(link, "abc"))
   125  		if err != nil {
   126  			t.Errorf("failed to read abc file: %v", err)
   127  			continue
   128  		}
   129  		if string(data) != "abc" {
   130  			t.Errorf(`abc file is expected to have "abc" in it, but has %v`, data)
   131  			continue
   132  		}
   133  
   134  		if test.issueNo > 0 {
   135  			t.Logf("skipping broken %q test: see issue %d", test.name, test.issueNo)
   136  			continue
   137  		}
   138  
   139  		fi1, err := os.Stat(link)
   140  		if err != nil {
   141  			t.Errorf("failed to stat link %v: %v", link, err)
   142  			continue
   143  		}
   144  		if !fi1.IsDir() {
   145  			t.Errorf("%q should be a directory", link)
   146  			continue
   147  		}
   148  		if fi1.Name() != filepath.Base(link) {
   149  			t.Errorf("Stat(%q).Name() = %q, want %q", link, fi1.Name(), filepath.Base(link))
   150  			continue
   151  		}
   152  		if !os.SameFile(fi, fi1) {
   153  			t.Errorf("%q should point to %q", link, dir)
   154  			continue
   155  		}
   156  
   157  		fi2, err := os.Lstat(link)
   158  		if err != nil {
   159  			t.Errorf("failed to lstat link %v: %v", link, err)
   160  			continue
   161  		}
   162  		if m := fi2.Mode(); m&os.ModeSymlink == 0 {
   163  			t.Errorf("%q should be a link, but is not (mode=0x%x)", link, uint32(m))
   164  			continue
   165  		}
   166  		if m := fi2.Mode(); m&os.ModeDir != 0 {
   167  			t.Errorf("%q should be a link, not a directory (mode=0x%x)", link, uint32(m))
   168  			continue
   169  		}
   170  	}
   171  }
   172  
   173  // reparseData is used to build reparse buffer data required for tests.
   174  type reparseData struct {
   175  	substituteName namePosition
   176  	printName      namePosition
   177  	pathBuf        []uint16
   178  }
   179  
   180  type namePosition struct {
   181  	offset uint16
   182  	length uint16
   183  }
   184  
   185  func (rd *reparseData) addUTF16s(s []uint16) (offset uint16) {
   186  	off := len(rd.pathBuf) * 2
   187  	rd.pathBuf = append(rd.pathBuf, s...)
   188  	return uint16(off)
   189  }
   190  
   191  func (rd *reparseData) addString(s string) (offset, length uint16) {
   192  	p := syscall.StringToUTF16(s)
   193  	return rd.addUTF16s(p), uint16(len(p)-1) * 2 // do not include terminating NUL in the legth (as per PrintNameLength and SubstituteNameLength documentation)
   194  }
   195  
   196  func (rd *reparseData) addSubstituteName(name string) {
   197  	rd.substituteName.offset, rd.substituteName.length = rd.addString(name)
   198  }
   199  
   200  func (rd *reparseData) addPrintName(name string) {
   201  	rd.printName.offset, rd.printName.length = rd.addString(name)
   202  }
   203  
   204  func (rd *reparseData) addStringNoNUL(s string) (offset, length uint16) {
   205  	p := syscall.StringToUTF16(s)
   206  	p = p[:len(p)-1]
   207  	return rd.addUTF16s(p), uint16(len(p)) * 2
   208  }
   209  
   210  func (rd *reparseData) addSubstituteNameNoNUL(name string) {
   211  	rd.substituteName.offset, rd.substituteName.length = rd.addStringNoNUL(name)
   212  }
   213  
   214  func (rd *reparseData) addPrintNameNoNUL(name string) {
   215  	rd.printName.offset, rd.printName.length = rd.addStringNoNUL(name)
   216  }
   217  
   218  // pathBuffeLen returns length of rd pathBuf in bytes.
   219  func (rd *reparseData) pathBuffeLen() uint16 {
   220  	return uint16(len(rd.pathBuf)) * 2
   221  }
   222  
   223  // Windows REPARSE_DATA_BUFFER contains union member, and cannot be
   224  // translated into Go directly. _REPARSE_DATA_BUFFER type is to help
   225  // construct alternative versions of Windows REPARSE_DATA_BUFFER with
   226  // union part of SymbolicLinkReparseBuffer or MountPointReparseBuffer type.
   227  type _REPARSE_DATA_BUFFER struct {
   228  	header windows.REPARSE_DATA_BUFFER_HEADER
   229  	detail [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
   230  }
   231  
   232  func createDirLink(link string, rdb *_REPARSE_DATA_BUFFER) error {
   233  	err := os.Mkdir(link, 0777)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	linkp := syscall.StringToUTF16(link)
   239  	fd, err := syscall.CreateFile(&linkp[0], syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING,
   240  		syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
   241  	if err != nil {
   242  		return err
   243  	}
   244  	defer syscall.CloseHandle(fd)
   245  
   246  	buflen := uint32(rdb.header.ReparseDataLength) + uint32(unsafe.Sizeof(rdb.header))
   247  	var bytesReturned uint32
   248  	return syscall.DeviceIoControl(fd, windows.FSCTL_SET_REPARSE_POINT,
   249  		(*byte)(unsafe.Pointer(&rdb.header)), buflen, nil, 0, &bytesReturned, nil)
   250  }
   251  
   252  func createMountPoint(link string, target *reparseData) error {
   253  	var buf *windows.MountPointReparseBuffer
   254  	buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
   255  	byteblob := make([]byte, buflen)
   256  	buf = (*windows.MountPointReparseBuffer)(unsafe.Pointer(&byteblob[0]))
   257  	buf.SubstituteNameOffset = target.substituteName.offset
   258  	buf.SubstituteNameLength = target.substituteName.length
   259  	buf.PrintNameOffset = target.printName.offset
   260  	buf.PrintNameLength = target.printName.length
   261  	copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf)
   262  
   263  	var rdb _REPARSE_DATA_BUFFER
   264  	rdb.header.ReparseTag = windows.IO_REPARSE_TAG_MOUNT_POINT
   265  	rdb.header.ReparseDataLength = buflen
   266  	copy(rdb.detail[:], byteblob)
   267  
   268  	return createDirLink(link, &rdb)
   269  }
   270  
   271  func TestDirectoryJunction(t *testing.T) {
   272  	var tests = []dirLinkTest{
   273  		{
   274  			// Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
   275  			name: "standard",
   276  			mklink: func(link, target string) error {
   277  				var t reparseData
   278  				t.addSubstituteName(`\??\` + target)
   279  				t.addPrintName(target)
   280  				return createMountPoint(link, &t)
   281  			},
   282  		},
   283  		{
   284  			// Do as junction utility https://technet.microsoft.com/en-au/sysinternals/bb896768.aspx does - set PrintNameLength to 0.
   285  			name: "have_blank_print_name",
   286  			mklink: func(link, target string) error {
   287  				var t reparseData
   288  				t.addSubstituteName(`\??\` + target)
   289  				t.addPrintName("")
   290  				return createMountPoint(link, &t)
   291  			},
   292  		},
   293  	}
   294  	output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
   295  	mklinkSupportsJunctionLinks := strings.Contains(string(output), " /J ")
   296  	if mklinkSupportsJunctionLinks {
   297  		tests = append(tests,
   298  			dirLinkTest{
   299  				name: "use_mklink_cmd",
   300  				mklink: func(link, target string) error {
   301  					output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, target).CombinedOutput()
   302  					if err != nil {
   303  						t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
   304  					}
   305  					return nil
   306  				},
   307  			},
   308  		)
   309  	} else {
   310  		t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory junctions`)
   311  	}
   312  	testDirLinks(t, tests)
   313  }
   314  
   315  func enableCurrentThreadPrivilege(privilegeName string) error {
   316  	ct, err := windows.GetCurrentThread()
   317  	if err != nil {
   318  		return err
   319  	}
   320  	var t syscall.Token
   321  	err = windows.OpenThreadToken(ct, syscall.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t)
   322  	if err != nil {
   323  		return err
   324  	}
   325  	defer syscall.CloseHandle(syscall.Handle(t))
   326  
   327  	var tp windows.TOKEN_PRIVILEGES
   328  
   329  	privStr, err := syscall.UTF16PtrFromString(privilegeName)
   330  	if err != nil {
   331  		return err
   332  	}
   333  	err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid)
   334  	if err != nil {
   335  		return err
   336  	}
   337  	tp.PrivilegeCount = 1
   338  	tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED
   339  	return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil)
   340  }
   341  
   342  func createSymbolicLink(link string, target *reparseData, isrelative bool) error {
   343  	var buf *windows.SymbolicLinkReparseBuffer
   344  	buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
   345  	byteblob := make([]byte, buflen)
   346  	buf = (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&byteblob[0]))
   347  	buf.SubstituteNameOffset = target.substituteName.offset
   348  	buf.SubstituteNameLength = target.substituteName.length
   349  	buf.PrintNameOffset = target.printName.offset
   350  	buf.PrintNameLength = target.printName.length
   351  	if isrelative {
   352  		buf.Flags = windows.SYMLINK_FLAG_RELATIVE
   353  	}
   354  	copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf)
   355  
   356  	var rdb _REPARSE_DATA_BUFFER
   357  	rdb.header.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
   358  	rdb.header.ReparseDataLength = buflen
   359  	copy(rdb.detail[:], byteblob)
   360  
   361  	return createDirLink(link, &rdb)
   362  }
   363  
   364  func TestDirectorySymbolicLink(t *testing.T) {
   365  	var tests []dirLinkTest
   366  	output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output()
   367  	mklinkSupportsDirectorySymbolicLinks := strings.Contains(string(output), " /D ")
   368  	if mklinkSupportsDirectorySymbolicLinks {
   369  		tests = append(tests,
   370  			dirLinkTest{
   371  				name: "use_mklink_cmd",
   372  				mklink: func(link, target string) error {
   373  					output, err := osexec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput()
   374  					if err != nil {
   375  						t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
   376  					}
   377  					return nil
   378  				},
   379  			},
   380  		)
   381  	} else {
   382  		t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory symbolic links`)
   383  	}
   384  
   385  	// The rest of these test requires SeCreateSymbolicLinkPrivilege to be held.
   386  	runtime.LockOSThread()
   387  	defer runtime.UnlockOSThread()
   388  
   389  	err := windows.ImpersonateSelf(windows.SecurityImpersonation)
   390  	if err != nil {
   391  		t.Fatal(err)
   392  	}
   393  	defer windows.RevertToSelf()
   394  
   395  	err = enableCurrentThreadPrivilege("SeCreateSymbolicLinkPrivilege")
   396  	if err != nil {
   397  		t.Skipf(`skipping some tests, could not enable "SeCreateSymbolicLinkPrivilege": %v`, err)
   398  	}
   399  	tests = append(tests,
   400  		dirLinkTest{
   401  			name: "use_os_pkg",
   402  			mklink: func(link, target string) error {
   403  				return os.Symlink(target, link)
   404  			},
   405  		},
   406  		dirLinkTest{
   407  			// Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
   408  			name: "standard",
   409  			mklink: func(link, target string) error {
   410  				var t reparseData
   411  				t.addPrintName(target)
   412  				t.addSubstituteName(`\??\` + target)
   413  				return createSymbolicLink(link, &t, false)
   414  			},
   415  		},
   416  		dirLinkTest{
   417  			name: "relative",
   418  			mklink: func(link, target string) error {
   419  				var t reparseData
   420  				t.addSubstituteNameNoNUL(filepath.Base(target))
   421  				t.addPrintNameNoNUL(filepath.Base(target))
   422  				return createSymbolicLink(link, &t, true)
   423  			},
   424  		},
   425  	)
   426  	testDirLinks(t, tests)
   427  }
   428  
   429  func TestNetworkSymbolicLink(t *testing.T) {
   430  	testenv.MustHaveSymlink(t)
   431  
   432  	const _NERR_ServerNotStarted = syscall.Errno(2114)
   433  
   434  	dir, err := ioutil.TempDir("", "TestNetworkSymbolicLink")
   435  	if err != nil {
   436  		t.Fatal(err)
   437  	}
   438  	defer os.RemoveAll(dir)
   439  
   440  	oldwd, err := os.Getwd()
   441  	if err != nil {
   442  		t.Fatal(err)
   443  	}
   444  	err = os.Chdir(dir)
   445  	if err != nil {
   446  		t.Fatal(err)
   447  	}
   448  	defer os.Chdir(oldwd)
   449  
   450  	shareName := "GoSymbolicLinkTestShare" // hope no conflictions
   451  	sharePath := filepath.Join(dir, shareName)
   452  	testDir := "TestDir"
   453  
   454  	err = os.MkdirAll(filepath.Join(sharePath, testDir), 0777)
   455  	if err != nil {
   456  		t.Fatal(err)
   457  	}
   458  
   459  	wShareName, err := syscall.UTF16PtrFromString(shareName)
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  	wSharePath, err := syscall.UTF16PtrFromString(sharePath)
   464  	if err != nil {
   465  		t.Fatal(err)
   466  	}
   467  
   468  	p := windows.SHARE_INFO_2{
   469  		Netname:     wShareName,
   470  		Type:        windows.STYPE_DISKTREE,
   471  		Remark:      nil,
   472  		Permissions: 0,
   473  		MaxUses:     1,
   474  		CurrentUses: 0,
   475  		Path:        wSharePath,
   476  		Passwd:      nil,
   477  	}
   478  
   479  	err = windows.NetShareAdd(nil, 2, (*byte)(unsafe.Pointer(&p)), nil)
   480  	if err != nil {
   481  		if err == syscall.ERROR_ACCESS_DENIED {
   482  			t.Skip("you don't have enough privileges to add network share")
   483  		}
   484  		if err == _NERR_ServerNotStarted {
   485  			t.Skip(_NERR_ServerNotStarted.Error())
   486  		}
   487  		t.Fatal(err)
   488  	}
   489  	defer func() {
   490  		err := windows.NetShareDel(nil, wShareName, 0)
   491  		if err != nil {
   492  			t.Fatal(err)
   493  		}
   494  	}()
   495  
   496  	UNCPath := `\\localhost\` + shareName + `\`
   497  
   498  	fi1, err := os.Stat(sharePath)
   499  	if err != nil {
   500  		t.Fatal(err)
   501  	}
   502  	fi2, err := os.Stat(UNCPath)
   503  	if err != nil {
   504  		t.Fatal(err)
   505  	}
   506  	if !os.SameFile(fi1, fi2) {
   507  		t.Fatalf("%q and %q should be the same directory, but not", sharePath, UNCPath)
   508  	}
   509  
   510  	target := filepath.Join(UNCPath, testDir)
   511  	link := "link"
   512  
   513  	err = os.Symlink(target, link)
   514  	if err != nil {
   515  		t.Fatal(err)
   516  	}
   517  	defer os.Remove(link)
   518  
   519  	got, err := os.Readlink(link)
   520  	if err != nil {
   521  		t.Fatal(err)
   522  	}
   523  
   524  	if got != target {
   525  		t.Errorf(`os.Readlink("%s"): got %v, want %v`, link, got, target)
   526  	}
   527  }
   528  
   529  func TestStartProcessAttr(t *testing.T) {
   530  	p, err := os.StartProcess(os.Getenv("COMSPEC"), []string{"/c", "cd"}, new(os.ProcAttr))
   531  	if err != nil {
   532  		return
   533  	}
   534  	defer p.Wait()
   535  	t.Fatalf("StartProcess expected to fail, but succeeded.")
   536  }
   537  
   538  func TestShareNotExistError(t *testing.T) {
   539  	if testing.Short() {
   540  		t.Skip("slow test that uses network; skipping")
   541  	}
   542  	_, err := os.Stat(`\\no_such_server\no_such_share\no_such_file`)
   543  	if err == nil {
   544  		t.Fatal("stat succeeded, but expected to fail")
   545  	}
   546  	if !os.IsNotExist(err) {
   547  		t.Fatalf("os.Stat failed with %q, but os.IsNotExist(err) is false", err)
   548  	}
   549  }
   550  
   551  func TestBadNetPathError(t *testing.T) {
   552  	const ERROR_BAD_NETPATH = syscall.Errno(53)
   553  	if !os.IsNotExist(ERROR_BAD_NETPATH) {
   554  		t.Fatal("os.IsNotExist(syscall.Errno(53)) is false, but want true")
   555  	}
   556  }
   557  
   558  func TestStatDir(t *testing.T) {
   559  	defer chtmpdir(t)()
   560  
   561  	f, err := os.Open(".")
   562  	if err != nil {
   563  		t.Fatal(err)
   564  	}
   565  	defer f.Close()
   566  
   567  	fi, err := f.Stat()
   568  	if err != nil {
   569  		t.Fatal(err)
   570  	}
   571  
   572  	err = os.Chdir("..")
   573  	if err != nil {
   574  		t.Fatal(err)
   575  	}
   576  
   577  	fi2, err := f.Stat()
   578  	if err != nil {
   579  		t.Fatal(err)
   580  	}
   581  
   582  	if !os.SameFile(fi, fi2) {
   583  		t.Fatal("race condition occurred")
   584  	}
   585  }
   586  
   587  func TestOpenVolumeName(t *testing.T) {
   588  	tmpdir, err := ioutil.TempDir("", "TestOpenVolumeName")
   589  	if err != nil {
   590  		t.Fatal(err)
   591  	}
   592  	defer os.RemoveAll(tmpdir)
   593  
   594  	wd, err := os.Getwd()
   595  	if err != nil {
   596  		t.Fatal(err)
   597  	}
   598  	err = os.Chdir(tmpdir)
   599  	if err != nil {
   600  		t.Fatal(err)
   601  	}
   602  	defer os.Chdir(wd)
   603  
   604  	want := []string{"file1", "file2", "file3", "gopher.txt"}
   605  	sort.Strings(want)
   606  	for _, name := range want {
   607  		err := ioutil.WriteFile(filepath.Join(tmpdir, name), nil, 0777)
   608  		if err != nil {
   609  			t.Fatal(err)
   610  		}
   611  	}
   612  
   613  	f, err := os.Open(filepath.VolumeName(tmpdir))
   614  	if err != nil {
   615  		t.Fatal(err)
   616  	}
   617  	defer f.Close()
   618  
   619  	have, err := f.Readdirnames(-1)
   620  	if err != nil {
   621  		t.Fatal(err)
   622  	}
   623  	sort.Strings(have)
   624  
   625  	if strings.Join(want, "/") != strings.Join(have, "/") {
   626  		t.Fatalf("unexpected file list %q, want %q", have, want)
   627  	}
   628  }
   629  
   630  func TestDeleteReadOnly(t *testing.T) {
   631  	tmpdir, err := ioutil.TempDir("", "TestDeleteReadOnly")
   632  	if err != nil {
   633  		t.Fatal(err)
   634  	}
   635  	defer os.RemoveAll(tmpdir)
   636  	p := filepath.Join(tmpdir, "a")
   637  	// This sets FILE_ATTRIBUTE_READONLY.
   638  	f, err := os.OpenFile(p, os.O_CREATE, 0400)
   639  	if err != nil {
   640  		t.Fatal(err)
   641  	}
   642  	f.Close()
   643  
   644  	if err = os.Chmod(p, 0400); err != nil {
   645  		t.Fatal(err)
   646  	}
   647  	if err = os.Remove(p); err != nil {
   648  		t.Fatal(err)
   649  	}
   650  }
   651  
   652  func TestStatSymlinkLoop(t *testing.T) {
   653  	testenv.MustHaveSymlink(t)
   654  
   655  	defer chtmpdir(t)()
   656  
   657  	err := os.Symlink("x", "y")
   658  	if err != nil {
   659  		t.Fatal(err)
   660  	}
   661  	defer os.Remove("y")
   662  
   663  	err = os.Symlink("y", "x")
   664  	if err != nil {
   665  		t.Fatal(err)
   666  	}
   667  	defer os.Remove("x")
   668  
   669  	_, err = os.Stat("x")
   670  	if _, ok := err.(*os.PathError); !ok {
   671  		t.Errorf("expected *PathError, got %T: %v\n", err, err)
   672  	}
   673  }
   674  
   675  func TestReadStdin(t *testing.T) {
   676  	old := poll.ReadConsole
   677  	defer func() {
   678  		poll.ReadConsole = old
   679  	}()
   680  
   681  	testConsole := os.NewConsoleFile(syscall.Stdin, "test")
   682  
   683  	var tests = []string{
   684  		"abc",
   685  		"äöü",
   686  		"\u3042",
   687  		"“hi”™",
   688  		"hello\x1aworld",
   689  		"\U0001F648\U0001F649\U0001F64A",
   690  	}
   691  
   692  	for _, consoleSize := range []int{1, 2, 3, 10, 16, 100, 1000} {
   693  		for _, readSize := range []int{1, 2, 3, 4, 5, 8, 10, 16, 20, 50, 100} {
   694  			for _, s := range tests {
   695  				t.Run(fmt.Sprintf("c%d/r%d/%s", consoleSize, readSize, s), func(t *testing.T) {
   696  					s16 := utf16.Encode([]rune(s))
   697  					poll.ReadConsole = func(h syscall.Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) error {
   698  						if inputControl != nil {
   699  							t.Fatalf("inputControl not nil")
   700  						}
   701  						n := int(toread)
   702  						if n > consoleSize {
   703  							n = consoleSize
   704  						}
   705  						n = copy((*[10000]uint16)(unsafe.Pointer(buf))[:n], s16)
   706  						s16 = s16[n:]
   707  						*read = uint32(n)
   708  						t.Logf("read %d -> %d", toread, *read)
   709  						return nil
   710  					}
   711  
   712  					var all []string
   713  					var buf []byte
   714  					chunk := make([]byte, readSize)
   715  					for {
   716  						n, err := testConsole.Read(chunk)
   717  						buf = append(buf, chunk[:n]...)
   718  						if err == io.EOF {
   719  							all = append(all, string(buf))
   720  							if len(all) >= 5 {
   721  								break
   722  							}
   723  							buf = buf[:0]
   724  						} else if err != nil {
   725  							t.Fatalf("reading %q: error: %v", s, err)
   726  						}
   727  						if len(buf) >= 2000 {
   728  							t.Fatalf("reading %q: stuck in loop: %q", s, buf)
   729  						}
   730  					}
   731  
   732  					want := strings.Split(s, "\x1a")
   733  					for len(want) < 5 {
   734  						want = append(want, "")
   735  					}
   736  					if !reflect.DeepEqual(all, want) {
   737  						t.Errorf("reading %q:\nhave %x\nwant %x", s, all, want)
   738  					}
   739  				})
   740  			}
   741  		}
   742  	}
   743  }
   744  
   745  func TestStatPagefile(t *testing.T) {
   746  	_, err := os.Stat(`c:\pagefile.sys`)
   747  	if err == nil {
   748  		return
   749  	}
   750  	if os.IsNotExist(err) {
   751  		t.Skip(`skipping because c:\pagefile.sys is not found`)
   752  	}
   753  	t.Fatal(err)
   754  }
   755  
   756  // syscallCommandLineToArgv calls syscall.CommandLineToArgv
   757  // and converts returned result into []string.
   758  func syscallCommandLineToArgv(cmd string) ([]string, error) {
   759  	var argc int32
   760  	argv, err := syscall.CommandLineToArgv(&syscall.StringToUTF16(cmd)[0], &argc)
   761  	if err != nil {
   762  		return nil, err
   763  	}
   764  	defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv))))
   765  
   766  	var args []string
   767  	for _, v := range (*argv)[:argc] {
   768  		args = append(args, syscall.UTF16ToString((*v)[:]))
   769  	}
   770  	return args, nil
   771  }
   772  
   773  // compareCommandLineToArgvWithSyscall ensures that
   774  // os.CommandLineToArgv(cmd) and syscall.CommandLineToArgv(cmd)
   775  // return the same result.
   776  func compareCommandLineToArgvWithSyscall(t *testing.T, cmd string) {
   777  	syscallArgs, err := syscallCommandLineToArgv(cmd)
   778  	if err != nil {
   779  		t.Fatal(err)
   780  	}
   781  	args := os.CommandLineToArgv(cmd)
   782  	if want, have := fmt.Sprintf("%q", syscallArgs), fmt.Sprintf("%q", args); want != have {
   783  		t.Errorf("testing os.commandLineToArgv(%q) failed: have %q want %q", cmd, args, syscallArgs)
   784  		return
   785  	}
   786  }
   787  
   788  func TestCmdArgs(t *testing.T) {
   789  	tmpdir, err := ioutil.TempDir("", "TestCmdArgs")
   790  	if err != nil {
   791  		t.Fatal(err)
   792  	}
   793  	defer os.RemoveAll(tmpdir)
   794  
   795  	const prog = `
   796  package main
   797  
   798  import (
   799  	"fmt"
   800  	"os"
   801  )
   802  
   803  func main() {
   804  	fmt.Printf("%q", os.Args)
   805  }
   806  `
   807  	src := filepath.Join(tmpdir, "main.go")
   808  	err = ioutil.WriteFile(src, []byte(prog), 0666)
   809  	if err != nil {
   810  		t.Fatal(err)
   811  	}
   812  
   813  	exe := filepath.Join(tmpdir, "main.exe")
   814  	cmd := osexec.Command("go", "build", "-o", exe, src)
   815  	cmd.Dir = tmpdir
   816  	out, err := cmd.CombinedOutput()
   817  	if err != nil {
   818  		t.Fatalf("building main.exe failed: %v\n%s", err, out)
   819  	}
   820  
   821  	var cmds = []string{
   822  		``,
   823  		` a b c`,
   824  		` "`,
   825  		` ""`,
   826  		` """`,
   827  		` "" a`,
   828  		` "123"`,
   829  		` \"123\"`,
   830  		` \"123 456\"`,
   831  		` \\"`,
   832  		` \\\"`,
   833  		` \\\\\"`,
   834  		` \\\"x`,
   835  		` """"\""\\\"`,
   836  		` abc`,
   837  		` \\\\\""x"""y z`,
   838  		"\tb\t\"x\ty\"",
   839  		` "Брад" d e`,
   840  		// examples from https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
   841  		` "abc" d e`,
   842  		` a\\b d"e f"g h`,
   843  		` a\\\"b c d`,
   844  		` a\\\\"b c" d e`,
   845  		// http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
   846  		// from 5.4  Examples
   847  		` CallMeIshmael`,
   848  		` "Call Me Ishmael"`,
   849  		` Cal"l Me I"shmael`,
   850  		` CallMe\"Ishmael`,
   851  		` "CallMe\"Ishmael"`,
   852  		` "Call Me Ishmael\\"`,
   853  		` "CallMe\\\"Ishmael"`,
   854  		` a\\\b`,
   855  		` "a\\\b"`,
   856  		// from 5.5  Some Common Tasks
   857  		` "\"Call Me Ishmael\""`,
   858  		` "C:\TEST A\\"`,
   859  		` "\"C:\TEST A\\\""`,
   860  		// from 5.6  The Microsoft Examples Explained
   861  		` "a b c"  d  e`,
   862  		` "ab\"c"  "\\"  d`,
   863  		` a\\\b d"e f"g h`,
   864  		` a\\\"b c d`,
   865  		` a\\\\"b c" d e`,
   866  		// from 5.7  Double Double Quote Examples (pre 2008)
   867  		` "a b c""`,
   868  		` """CallMeIshmael"""  b  c`,
   869  		` """Call Me Ishmael"""`,
   870  		` """"Call Me Ishmael"" b c`,
   871  	}
   872  	for _, cmd := range cmds {
   873  		compareCommandLineToArgvWithSyscall(t, "test"+cmd)
   874  		compareCommandLineToArgvWithSyscall(t, `"cmd line"`+cmd)
   875  		compareCommandLineToArgvWithSyscall(t, exe+cmd)
   876  
   877  		// test both syscall.EscapeArg and os.commandLineToArgv
   878  		args := os.CommandLineToArgv(exe + cmd)
   879  		out, err := osexec.Command(args[0], args[1:]...).CombinedOutput()
   880  		if err != nil {
   881  			t.Fatalf("running %q failed: %v\n%v", args, err, string(out))
   882  		}
   883  		if want, have := fmt.Sprintf("%q", args), string(out); want != have {
   884  			t.Errorf("wrong output of executing %q: have %q want %q", args, have, want)
   885  			continue
   886  		}
   887  	}
   888  }