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/syscall/windows" 10 "internal/testenv" 11 "io" 12 "io/ioutil" 13 "os" 14 osexec "os/exec" 15 "path/filepath" 16 "reflect" 17 "runtime" 18 "sort" 19 "strings" 20 "syscall" 21 "testing" 22 "unicode/utf16" 23 "unsafe" 24 ) 25 26 func TestSameWindowsFile(t *testing.T) { 27 temp, err := ioutil.TempDir("", "TestSameWindowsFile") 28 if err != nil { 29 t.Fatal(err) 30 } 31 defer os.RemoveAll(temp) 32 33 wd, err := os.Getwd() 34 if err != nil { 35 t.Fatal(err) 36 } 37 err = os.Chdir(temp) 38 if err != nil { 39 t.Fatal(err) 40 } 41 defer os.Chdir(wd) 42 43 f, err := os.Create("a") 44 if err != nil { 45 t.Fatal(err) 46 } 47 f.Close() 48 49 ia1, err := os.Stat("a") 50 if err != nil { 51 t.Fatal(err) 52 } 53 54 path, err := filepath.Abs("a") 55 if err != nil { 56 t.Fatal(err) 57 } 58 ia2, err := os.Stat(path) 59 if err != nil { 60 t.Fatal(err) 61 } 62 if !os.SameFile(ia1, ia2) { 63 t.Errorf("files should be same") 64 } 65 66 p := filepath.VolumeName(path) + filepath.Base(path) 67 if err != nil { 68 t.Fatal(err) 69 } 70 ia3, err := os.Stat(p) 71 if err != nil { 72 t.Fatal(err) 73 } 74 if !os.SameFile(ia1, ia3) { 75 t.Errorf("files should be same") 76 } 77 } 78 79 type dirLinkTest struct { 80 name string 81 mklink func(link, target string) error 82 issueNo int // correspondent issue number (for broken tests) 83 } 84 85 func testDirLinks(t *testing.T, tests []dirLinkTest) { 86 tmpdir, err := ioutil.TempDir("", "testDirLinks") 87 if err != nil { 88 t.Fatal(err) 89 } 90 defer os.RemoveAll(tmpdir) 91 92 oldwd, err := os.Getwd() 93 if err != nil { 94 t.Fatal(err) 95 } 96 err = os.Chdir(tmpdir) 97 if err != nil { 98 t.Fatal(err) 99 } 100 defer os.Chdir(oldwd) 101 102 dir := filepath.Join(tmpdir, "dir") 103 err = os.Mkdir(dir, 0777) 104 if err != nil { 105 t.Fatal(err) 106 } 107 err = ioutil.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644) 108 if err != nil { 109 t.Fatal(err) 110 } 111 for _, test := range tests { 112 link := filepath.Join(tmpdir, test.name+"_link") 113 err := test.mklink(link, dir) 114 if err != nil { 115 t.Errorf("creating link for %s test failed: %v", test.name, err) 116 continue 117 } 118 119 data, err := ioutil.ReadFile(filepath.Join(link, "abc")) 120 if err != nil { 121 t.Errorf("failed to read abc file: %v", err) 122 continue 123 } 124 if string(data) != "abc" { 125 t.Errorf(`abc file is expected to have "abc" in it, but has %v`, data) 126 continue 127 } 128 129 if test.issueNo > 0 { 130 t.Logf("skipping broken %q test: see issue %d", test.name, test.issueNo) 131 continue 132 } 133 134 fi, err := os.Stat(link) 135 if err != nil { 136 t.Errorf("failed to stat link %v: %v", link, err) 137 continue 138 } 139 expected := filepath.Base(dir) 140 got := fi.Name() 141 if !fi.IsDir() || expected != got { 142 t.Errorf("link should point to %v but points to %v instead", expected, got) 143 continue 144 } 145 } 146 } 147 148 // reparseData is used to build reparse buffer data required for tests. 149 type reparseData struct { 150 substituteName namePosition 151 printName namePosition 152 pathBuf []uint16 153 } 154 155 type namePosition struct { 156 offset uint16 157 length uint16 158 } 159 160 func (rd *reparseData) addUTF16s(s []uint16) (offset uint16) { 161 off := len(rd.pathBuf) * 2 162 rd.pathBuf = append(rd.pathBuf, s...) 163 return uint16(off) 164 } 165 166 func (rd *reparseData) addString(s string) (offset, length uint16) { 167 p := syscall.StringToUTF16(s) 168 return rd.addUTF16s(p), uint16(len(p)-1) * 2 // do not include terminating NUL in the legth (as per PrintNameLength and SubstituteNameLength documentation) 169 } 170 171 func (rd *reparseData) addSubstituteName(name string) { 172 rd.substituteName.offset, rd.substituteName.length = rd.addString(name) 173 } 174 175 func (rd *reparseData) addPrintName(name string) { 176 rd.printName.offset, rd.printName.length = rd.addString(name) 177 } 178 179 func (rd *reparseData) addStringNoNUL(s string) (offset, length uint16) { 180 p := syscall.StringToUTF16(s) 181 p = p[:len(p)-1] 182 return rd.addUTF16s(p), uint16(len(p)) * 2 183 } 184 185 func (rd *reparseData) addSubstituteNameNoNUL(name string) { 186 rd.substituteName.offset, rd.substituteName.length = rd.addStringNoNUL(name) 187 } 188 189 func (rd *reparseData) addPrintNameNoNUL(name string) { 190 rd.printName.offset, rd.printName.length = rd.addStringNoNUL(name) 191 } 192 193 // pathBuffeLen returns length of rd pathBuf in bytes. 194 func (rd *reparseData) pathBuffeLen() uint16 { 195 return uint16(len(rd.pathBuf)) * 2 196 } 197 198 // Windows REPARSE_DATA_BUFFER contains union member, and cannot be 199 // translated into Go directly. _REPARSE_DATA_BUFFER type is to help 200 // construct alternative versions of Windows REPARSE_DATA_BUFFER with 201 // union part of SymbolicLinkReparseBuffer or MountPointReparseBuffer type. 202 type _REPARSE_DATA_BUFFER struct { 203 header windows.REPARSE_DATA_BUFFER_HEADER 204 detail [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte 205 } 206 207 func createDirLink(link string, rdb *_REPARSE_DATA_BUFFER) error { 208 err := os.Mkdir(link, 0777) 209 if err != nil { 210 return err 211 } 212 213 linkp := syscall.StringToUTF16(link) 214 fd, err := syscall.CreateFile(&linkp[0], syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, 215 syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) 216 if err != nil { 217 return err 218 } 219 defer syscall.CloseHandle(fd) 220 221 buflen := uint32(rdb.header.ReparseDataLength) + uint32(unsafe.Sizeof(rdb.header)) 222 var bytesReturned uint32 223 return syscall.DeviceIoControl(fd, windows.FSCTL_SET_REPARSE_POINT, 224 (*byte)(unsafe.Pointer(&rdb.header)), buflen, nil, 0, &bytesReturned, nil) 225 } 226 227 func createMountPoint(link string, target *reparseData) error { 228 var buf *windows.MountPointReparseBuffer 229 buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation 230 byteblob := make([]byte, buflen) 231 buf = (*windows.MountPointReparseBuffer)(unsafe.Pointer(&byteblob[0])) 232 buf.SubstituteNameOffset = target.substituteName.offset 233 buf.SubstituteNameLength = target.substituteName.length 234 buf.PrintNameOffset = target.printName.offset 235 buf.PrintNameLength = target.printName.length 236 copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf) 237 238 var rdb _REPARSE_DATA_BUFFER 239 rdb.header.ReparseTag = windows.IO_REPARSE_TAG_MOUNT_POINT 240 rdb.header.ReparseDataLength = buflen 241 copy(rdb.detail[:], byteblob) 242 243 return createDirLink(link, &rdb) 244 } 245 246 func TestDirectoryJunction(t *testing.T) { 247 var tests = []dirLinkTest{ 248 { 249 // Create link similar to what mklink does, by inserting \??\ at the front of absolute target. 250 name: "standard", 251 mklink: func(link, target string) error { 252 var t reparseData 253 t.addSubstituteName(`\??\` + target) 254 t.addPrintName(target) 255 return createMountPoint(link, &t) 256 }, 257 }, 258 { 259 // Do as junction utility https://technet.microsoft.com/en-au/sysinternals/bb896768.aspx does - set PrintNameLength to 0. 260 name: "have_blank_print_name", 261 mklink: func(link, target string) error { 262 var t reparseData 263 t.addSubstituteName(`\??\` + target) 264 t.addPrintName("") 265 return createMountPoint(link, &t) 266 }, 267 }, 268 } 269 output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output() 270 mklinkSupportsJunctionLinks := strings.Contains(string(output), " /J ") 271 if mklinkSupportsJunctionLinks { 272 tests = append(tests, 273 dirLinkTest{ 274 name: "use_mklink_cmd", 275 mklink: func(link, target string) error { 276 output, err := osexec.Command("cmd", "/c", "mklink", "/J", link, target).CombinedOutput() 277 if err != nil { 278 t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output) 279 } 280 return nil 281 }, 282 }, 283 ) 284 } else { 285 t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory junctions`) 286 } 287 testDirLinks(t, tests) 288 } 289 290 func enableCurrentThreadPrivilege(privilegeName string) error { 291 ct, err := windows.GetCurrentThread() 292 if err != nil { 293 return err 294 } 295 var t syscall.Token 296 err = windows.OpenThreadToken(ct, syscall.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t) 297 if err != nil { 298 return err 299 } 300 defer syscall.CloseHandle(syscall.Handle(t)) 301 302 var tp windows.TOKEN_PRIVILEGES 303 304 privStr, err := syscall.UTF16PtrFromString(privilegeName) 305 if err != nil { 306 return err 307 } 308 err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid) 309 if err != nil { 310 return err 311 } 312 tp.PrivilegeCount = 1 313 tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED 314 return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil) 315 } 316 317 func createSymbolicLink(link string, target *reparseData, isrelative bool) error { 318 var buf *windows.SymbolicLinkReparseBuffer 319 buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation 320 byteblob := make([]byte, buflen) 321 buf = (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&byteblob[0])) 322 buf.SubstituteNameOffset = target.substituteName.offset 323 buf.SubstituteNameLength = target.substituteName.length 324 buf.PrintNameOffset = target.printName.offset 325 buf.PrintNameLength = target.printName.length 326 if isrelative { 327 buf.Flags = windows.SYMLINK_FLAG_RELATIVE 328 } 329 copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:], target.pathBuf) 330 331 var rdb _REPARSE_DATA_BUFFER 332 rdb.header.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK 333 rdb.header.ReparseDataLength = buflen 334 copy(rdb.detail[:], byteblob) 335 336 return createDirLink(link, &rdb) 337 } 338 339 func TestDirectorySymbolicLink(t *testing.T) { 340 var tests []dirLinkTest 341 output, _ := osexec.Command("cmd", "/c", "mklink", "/?").Output() 342 mklinkSupportsDirectorySymbolicLinks := strings.Contains(string(output), " /D ") 343 if mklinkSupportsDirectorySymbolicLinks { 344 tests = append(tests, 345 dirLinkTest{ 346 name: "use_mklink_cmd", 347 mklink: func(link, target string) error { 348 output, err := osexec.Command("cmd", "/c", "mklink", "/D", link, target).CombinedOutput() 349 if err != nil { 350 t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output) 351 } 352 return nil 353 }, 354 }, 355 ) 356 } else { 357 t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory symbolic links`) 358 } 359 360 // The rest of these test requires SeCreateSymbolicLinkPrivilege to be held. 361 runtime.LockOSThread() 362 defer runtime.UnlockOSThread() 363 364 err := windows.ImpersonateSelf(windows.SecurityImpersonation) 365 if err != nil { 366 t.Fatal(err) 367 } 368 defer windows.RevertToSelf() 369 370 err = enableCurrentThreadPrivilege("SeCreateSymbolicLinkPrivilege") 371 if err != nil { 372 t.Skipf(`skipping some tests, could not enable "SeCreateSymbolicLinkPrivilege": %v`, err) 373 } 374 tests = append(tests, 375 dirLinkTest{ 376 name: "use_os_pkg", 377 mklink: func(link, target string) error { 378 return os.Symlink(target, link) 379 }, 380 }, 381 dirLinkTest{ 382 // Create link similar to what mklink does, by inserting \??\ at the front of absolute target. 383 name: "standard", 384 mklink: func(link, target string) error { 385 var t reparseData 386 t.addPrintName(target) 387 t.addSubstituteName(`\??\` + target) 388 return createSymbolicLink(link, &t, false) 389 }, 390 }, 391 dirLinkTest{ 392 name: "relative", 393 mklink: func(link, target string) error { 394 var t reparseData 395 t.addSubstituteNameNoNUL(filepath.Base(target)) 396 t.addPrintNameNoNUL(filepath.Base(target)) 397 return createSymbolicLink(link, &t, true) 398 }, 399 }, 400 ) 401 testDirLinks(t, tests) 402 } 403 404 func TestNetworkSymbolicLink(t *testing.T) { 405 testenv.MustHaveSymlink(t) 406 407 dir, err := ioutil.TempDir("", "TestNetworkSymbolicLink") 408 if err != nil { 409 t.Fatal(err) 410 } 411 defer os.RemoveAll(dir) 412 413 oldwd, err := os.Getwd() 414 if err != nil { 415 t.Fatal(err) 416 } 417 err = os.Chdir(dir) 418 if err != nil { 419 t.Fatal(err) 420 } 421 defer os.Chdir(oldwd) 422 423 shareName := "GoSymbolicLinkTestShare" // hope no conflictions 424 sharePath := filepath.Join(dir, shareName) 425 testDir := "TestDir" 426 427 err = os.MkdirAll(filepath.Join(sharePath, testDir), 0777) 428 if err != nil { 429 t.Fatal(err) 430 } 431 432 wShareName, err := syscall.UTF16PtrFromString(shareName) 433 if err != nil { 434 t.Fatal(err) 435 } 436 wSharePath, err := syscall.UTF16PtrFromString(sharePath) 437 if err != nil { 438 t.Fatal(err) 439 } 440 441 p := windows.SHARE_INFO_2{ 442 Netname: wShareName, 443 Type: windows.STYPE_DISKTREE, 444 Remark: nil, 445 Permissions: 0, 446 MaxUses: 1, 447 CurrentUses: 0, 448 Path: wSharePath, 449 Passwd: nil, 450 } 451 452 err = windows.NetShareAdd(nil, 2, (*byte)(unsafe.Pointer(&p)), nil) 453 if err != nil { 454 if err == syscall.ERROR_ACCESS_DENIED { 455 t.Skip("you don't have enough privileges to add network share") 456 } 457 t.Fatal(err) 458 } 459 defer func() { 460 err := windows.NetShareDel(nil, wShareName, 0) 461 if err != nil { 462 t.Fatal(err) 463 } 464 }() 465 466 UNCPath := `\\localhost\` + shareName + `\` 467 468 fi1, err := os.Stat(sharePath) 469 if err != nil { 470 t.Fatal(err) 471 } 472 fi2, err := os.Stat(UNCPath) 473 if err != nil { 474 t.Fatal(err) 475 } 476 if !os.SameFile(fi1, fi2) { 477 t.Fatalf("%q and %q should be the same directory, but not", sharePath, UNCPath) 478 } 479 480 target := filepath.Join(UNCPath, testDir) 481 link := "link" 482 483 err = os.Symlink(target, link) 484 if err != nil { 485 t.Fatal(err) 486 } 487 defer os.Remove(link) 488 489 got, err := os.Readlink(link) 490 if err != nil { 491 t.Fatal(err) 492 } 493 494 if got != target { 495 t.Errorf(`os.Readlink("%s"): got %v, want %v`, link, got, target) 496 } 497 } 498 499 func TestStartProcessAttr(t *testing.T) { 500 p, err := os.StartProcess(os.Getenv("COMSPEC"), []string{"/c", "cd"}, new(os.ProcAttr)) 501 if err != nil { 502 return 503 } 504 defer p.Wait() 505 t.Fatalf("StartProcess expected to fail, but succeeded.") 506 } 507 508 func TestShareNotExistError(t *testing.T) { 509 if testing.Short() { 510 t.Skip("slow test that uses network; skipping") 511 } 512 _, err := os.Stat(`\\no_such_server\no_such_share\no_such_file`) 513 if err == nil { 514 t.Fatal("stat succeeded, but expected to fail") 515 } 516 if !os.IsNotExist(err) { 517 t.Fatalf("os.Stat failed with %q, but os.IsNotExist(err) is false", err) 518 } 519 } 520 521 func TestBadNetPathError(t *testing.T) { 522 const ERROR_BAD_NETPATH = syscall.Errno(53) 523 if !os.IsNotExist(ERROR_BAD_NETPATH) { 524 t.Fatal("os.IsNotExist(syscall.Errno(53)) is false, but want true") 525 } 526 } 527 528 func TestStatDir(t *testing.T) { 529 defer chtmpdir(t)() 530 531 f, err := os.Open(".") 532 if err != nil { 533 t.Fatal(err) 534 } 535 defer f.Close() 536 537 fi, err := f.Stat() 538 if err != nil { 539 t.Fatal(err) 540 } 541 542 err = os.Chdir("..") 543 if err != nil { 544 t.Fatal(err) 545 } 546 547 fi2, err := f.Stat() 548 if err != nil { 549 t.Fatal(err) 550 } 551 552 if !os.SameFile(fi, fi2) { 553 t.Fatal("race condition occurred") 554 } 555 } 556 557 func TestOpenVolumeName(t *testing.T) { 558 tmpdir, err := ioutil.TempDir("", "TestOpenVolumeName") 559 if err != nil { 560 t.Fatal(err) 561 } 562 defer os.RemoveAll(tmpdir) 563 564 wd, err := os.Getwd() 565 if err != nil { 566 t.Fatal(err) 567 } 568 err = os.Chdir(tmpdir) 569 if err != nil { 570 t.Fatal(err) 571 } 572 defer os.Chdir(wd) 573 574 want := []string{"file1", "file2", "file3", "gopher.txt"} 575 sort.Strings(want) 576 for _, name := range want { 577 err := ioutil.WriteFile(filepath.Join(tmpdir, name), nil, 0777) 578 if err != nil { 579 t.Fatal(err) 580 } 581 } 582 583 f, err := os.Open(filepath.VolumeName(tmpdir)) 584 if err != nil { 585 t.Fatal(err) 586 } 587 defer f.Close() 588 589 have, err := f.Readdirnames(-1) 590 if err != nil { 591 t.Fatal(err) 592 } 593 sort.Strings(have) 594 595 if strings.Join(want, "/") != strings.Join(have, "/") { 596 t.Fatalf("unexpected file list %q, want %q", have, want) 597 } 598 } 599 600 func TestDeleteReadOnly(t *testing.T) { 601 tmpdir, err := ioutil.TempDir("", "TestDeleteReadOnly") 602 if err != nil { 603 t.Fatal(err) 604 } 605 defer os.RemoveAll(tmpdir) 606 p := filepath.Join(tmpdir, "a") 607 // This sets FILE_ATTRIBUTE_READONLY. 608 f, err := os.OpenFile(p, os.O_CREATE, 0400) 609 if err != nil { 610 t.Fatal(err) 611 } 612 f.Close() 613 614 if err = os.Chmod(p, 0400); err != nil { 615 t.Fatal(err) 616 } 617 if err = os.Remove(p); err != nil { 618 t.Fatal(err) 619 } 620 } 621 622 func TestStatSymlinkLoop(t *testing.T) { 623 testenv.MustHaveSymlink(t) 624 625 defer chtmpdir(t)() 626 627 err := os.Symlink("x", "y") 628 if err != nil { 629 t.Fatal(err) 630 } 631 defer os.Remove("y") 632 633 err = os.Symlink("y", "x") 634 if err != nil { 635 t.Fatal(err) 636 } 637 defer os.Remove("x") 638 639 _, err = os.Stat("x") 640 if perr, ok := err.(*os.PathError); !ok || perr.Err != syscall.ELOOP { 641 t.Errorf("expected *PathError with ELOOP, got %T: %v\n", err, err) 642 } 643 } 644 645 func TestReadStdin(t *testing.T) { 646 old := *os.ReadConsoleFunc 647 defer func() { 648 *os.ReadConsoleFunc = old 649 }() 650 651 testConsole := os.NewConsoleFile(syscall.Stdin, "test") 652 653 var tests = []string{ 654 "abc", 655 "", 656 "\u3042", 657 "hi", 658 "hello\x1aworld", 659 "\U0001F648\U0001F649\U0001F64A", 660 } 661 662 for _, consoleSize := range []int{1, 2, 3, 10, 16, 100, 1000} { 663 for _, readSize := range []int{1, 2, 3, 4, 5, 8, 10, 16, 20, 50, 100} { 664 for _, s := range tests { 665 t.Run(fmt.Sprintf("c%d/r%d/%s", consoleSize, readSize, s), func(t *testing.T) { 666 s16 := utf16.Encode([]rune(s)) 667 *os.ReadConsoleFunc = func(h syscall.Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) error { 668 if inputControl != nil { 669 t.Fatalf("inputControl not nil") 670 } 671 n := int(toread) 672 if n > consoleSize { 673 n = consoleSize 674 } 675 n = copy((*[10000]uint16)(unsafe.Pointer(buf))[:n], s16) 676 s16 = s16[n:] 677 *read = uint32(n) 678 t.Logf("read %d -> %d", toread, *read) 679 return nil 680 } 681 682 var all []string 683 var buf []byte 684 chunk := make([]byte, readSize) 685 for { 686 n, err := testConsole.Read(chunk) 687 buf = append(buf, chunk[:n]...) 688 if err == io.EOF { 689 all = append(all, string(buf)) 690 if len(all) >= 5 { 691 break 692 } 693 buf = buf[:0] 694 } else if err != nil { 695 t.Fatalf("reading %q: error: %v", s, err) 696 } 697 if len(buf) >= 2000 { 698 t.Fatalf("reading %q: stuck in loop: %q", s, buf) 699 } 700 } 701 702 want := strings.Split(s, "\x1a") 703 for len(want) < 5 { 704 want = append(want, "") 705 } 706 if !reflect.DeepEqual(all, want) { 707 t.Errorf("reading %q:\nhave %x\nwant %x", s, all, want) 708 } 709 }) 710 } 711 } 712 } 713 } 714 715 func TestStatPagefile(t *testing.T) { 716 _, err := os.Stat(`c:\pagefile.sys`) 717 if err == nil { 718 return 719 } 720 if os.IsNotExist(err) { 721 t.Skip(`skipping because c:\pagefile.sys is not found`) 722 } 723 t.Fatal(err) 724 } 725