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 5package os_test 6 7import ( 8 "errors" 9 "fmt" 10 "internal/godebug" 11 "internal/poll" 12 "internal/syscall/windows" 13 "internal/syscall/windows/registry" 14 "internal/testenv" 15 "io" 16 "io/fs" 17 "os" 18 "os/exec" 19 "path/filepath" 20 "reflect" 21 "runtime" 22 "slices" 23 "strings" 24 "syscall" 25 "testing" 26 "unicode/utf16" 27 "unsafe" 28) 29 30var winsymlink = godebug.New("winsymlink") 31var winreadlinkvolume = godebug.New("winreadlinkvolume") 32 33// For TestRawConnReadWrite. 34type syscallDescriptor = syscall.Handle 35 36// chdir changes the current working directory to the named directory, 37// and then restore the original working directory at the end of the test. 38func chdir(t *testing.T, dir string) { 39 olddir, err := os.Getwd() 40 if err != nil { 41 t.Fatalf("chdir: %v", err) 42 } 43 if err := os.Chdir(dir); err != nil { 44 t.Fatalf("chdir %s: %v", dir, err) 45 } 46 47 t.Cleanup(func() { 48 if err := os.Chdir(olddir); err != nil { 49 t.Errorf("chdir to original working directory %s: %v", olddir, err) 50 os.Exit(1) 51 } 52 }) 53} 54 55func TestSameWindowsFile(t *testing.T) { 56 temp := t.TempDir() 57 chdir(t, temp) 58 59 f, err := os.Create("a") 60 if err != nil { 61 t.Fatal(err) 62 } 63 f.Close() 64 65 ia1, err := os.Stat("a") 66 if err != nil { 67 t.Fatal(err) 68 } 69 70 path, err := filepath.Abs("a") 71 if err != nil { 72 t.Fatal(err) 73 } 74 ia2, err := os.Stat(path) 75 if err != nil { 76 t.Fatal(err) 77 } 78 if !os.SameFile(ia1, ia2) { 79 t.Errorf("files should be same") 80 } 81 82 p := filepath.VolumeName(path) + filepath.Base(path) 83 if err != nil { 84 t.Fatal(err) 85 } 86 ia3, err := os.Stat(p) 87 if err != nil { 88 t.Fatal(err) 89 } 90 if !os.SameFile(ia1, ia3) { 91 t.Errorf("files should be same") 92 } 93} 94 95type dirLinkTest struct { 96 name string 97 mklink func(link, target string) error 98 isMountPoint bool 99} 100 101func testDirLinks(t *testing.T, tests []dirLinkTest) { 102 tmpdir := t.TempDir() 103 chdir(t, tmpdir) 104 105 dir := filepath.Join(tmpdir, "dir") 106 err := os.Mkdir(dir, 0777) 107 if err != nil { 108 t.Fatal(err) 109 } 110 fi, err := os.Stat(dir) 111 if err != nil { 112 t.Fatal(err) 113 } 114 err = os.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644) 115 if err != nil { 116 t.Fatal(err) 117 } 118 for _, test := range tests { 119 link := filepath.Join(tmpdir, test.name+"_link") 120 err := test.mklink(link, dir) 121 if err != nil { 122 t.Errorf("creating link for %q test failed: %v", test.name, err) 123 continue 124 } 125 126 data, err := os.ReadFile(filepath.Join(link, "abc")) 127 if err != nil { 128 t.Errorf("failed to read abc file: %v", err) 129 continue 130 } 131 if string(data) != "abc" { 132 t.Errorf(`abc file is expected to have "abc" in it, but has %v`, data) 133 continue 134 } 135 136 fi1, err := os.Stat(link) 137 if err != nil { 138 t.Errorf("failed to stat link %v: %v", link, err) 139 continue 140 } 141 if tp := fi1.Mode().Type(); tp != fs.ModeDir { 142 t.Errorf("Stat(%q) is type %v; want %v", link, tp, fs.ModeDir) 143 continue 144 } 145 if fi1.Name() != filepath.Base(link) { 146 t.Errorf("Stat(%q).Name() = %q, want %q", link, fi1.Name(), filepath.Base(link)) 147 continue 148 } 149 if !os.SameFile(fi, fi1) { 150 t.Errorf("%q should point to %q", link, dir) 151 continue 152 } 153 154 fi2, err := os.Lstat(link) 155 if err != nil { 156 t.Errorf("failed to lstat link %v: %v", link, err) 157 continue 158 } 159 var wantType fs.FileMode 160 if test.isMountPoint && winsymlink.Value() != "0" { 161 // Mount points are reparse points, and we no longer treat them as symlinks. 162 wantType = fs.ModeIrregular 163 } else { 164 // This is either a real symlink, or a mount point treated as a symlink. 165 wantType = fs.ModeSymlink 166 } 167 if tp := fi2.Mode().Type(); tp != wantType { 168 t.Errorf("Lstat(%q) is type %v; want %v", link, tp, wantType) 169 } 170 } 171} 172 173// reparseData is used to build reparse buffer data required for tests. 174type reparseData struct { 175 substituteName namePosition 176 printName namePosition 177 pathBuf []uint16 178} 179 180type namePosition struct { 181 offset uint16 182 length uint16 183} 184 185func (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 191func (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 length (as per PrintNameLength and SubstituteNameLength documentation) 194} 195 196func (rd *reparseData) addSubstituteName(name string) { 197 rd.substituteName.offset, rd.substituteName.length = rd.addString(name) 198} 199 200func (rd *reparseData) addPrintName(name string) { 201 rd.printName.offset, rd.printName.length = rd.addString(name) 202} 203 204func (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 210func (rd *reparseData) addSubstituteNameNoNUL(name string) { 211 rd.substituteName.offset, rd.substituteName.length = rd.addStringNoNUL(name) 212} 213 214func (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. 219func (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. 227type _REPARSE_DATA_BUFFER struct { 228 header windows.REPARSE_DATA_BUFFER_HEADER 229 detail [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte 230} 231 232func 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 252func 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 pbuflen := len(target.pathBuf) 262 copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:pbuflen:pbuflen], target.pathBuf) 263 264 var rdb _REPARSE_DATA_BUFFER 265 rdb.header.ReparseTag = windows.IO_REPARSE_TAG_MOUNT_POINT 266 rdb.header.ReparseDataLength = buflen 267 copy(rdb.detail[:], byteblob) 268 269 return createDirLink(link, &rdb) 270} 271 272func TestDirectoryJunction(t *testing.T) { 273 var tests = []dirLinkTest{ 274 { 275 // Create link similar to what mklink does, by inserting \??\ at the front of absolute target. 276 name: "standard", 277 isMountPoint: true, 278 mklink: func(link, target string) error { 279 var t reparseData 280 t.addSubstituteName(`\??\` + target) 281 t.addPrintName(target) 282 return createMountPoint(link, &t) 283 }, 284 }, 285 { 286 // Do as junction utility https://learn.microsoft.com/en-us/sysinternals/downloads/junction does - set PrintNameLength to 0. 287 name: "have_blank_print_name", 288 isMountPoint: true, 289 mklink: func(link, target string) error { 290 var t reparseData 291 t.addSubstituteName(`\??\` + target) 292 t.addPrintName("") 293 return createMountPoint(link, &t) 294 }, 295 }, 296 } 297 output, _ := testenv.Command(t, "cmd", "/c", "mklink", "/?").Output() 298 mklinkSupportsJunctionLinks := strings.Contains(string(output), " /J ") 299 if mklinkSupportsJunctionLinks { 300 tests = append(tests, 301 dirLinkTest{ 302 name: "use_mklink_cmd", 303 isMountPoint: true, 304 mklink: func(link, target string) error { 305 output, err := testenv.Command(t, "cmd", "/c", "mklink", "/J", link, target).CombinedOutput() 306 if err != nil { 307 t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output) 308 } 309 return nil 310 }, 311 }, 312 ) 313 } else { 314 t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory junctions`) 315 } 316 testDirLinks(t, tests) 317} 318 319func enableCurrentThreadPrivilege(privilegeName string) error { 320 ct, err := windows.GetCurrentThread() 321 if err != nil { 322 return err 323 } 324 var t syscall.Token 325 err = windows.OpenThreadToken(ct, syscall.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t) 326 if err != nil { 327 return err 328 } 329 defer syscall.CloseHandle(syscall.Handle(t)) 330 331 var tp windows.TOKEN_PRIVILEGES 332 333 privStr, err := syscall.UTF16PtrFromString(privilegeName) 334 if err != nil { 335 return err 336 } 337 err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid) 338 if err != nil { 339 return err 340 } 341 tp.PrivilegeCount = 1 342 tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED 343 return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil) 344} 345 346func createSymbolicLink(link string, target *reparseData, isrelative bool) error { 347 var buf *windows.SymbolicLinkReparseBuffer 348 buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation 349 byteblob := make([]byte, buflen) 350 buf = (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&byteblob[0])) 351 buf.SubstituteNameOffset = target.substituteName.offset 352 buf.SubstituteNameLength = target.substituteName.length 353 buf.PrintNameOffset = target.printName.offset 354 buf.PrintNameLength = target.printName.length 355 if isrelative { 356 buf.Flags = windows.SYMLINK_FLAG_RELATIVE 357 } 358 pbuflen := len(target.pathBuf) 359 copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:pbuflen:pbuflen], target.pathBuf) 360 361 var rdb _REPARSE_DATA_BUFFER 362 rdb.header.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK 363 rdb.header.ReparseDataLength = buflen 364 copy(rdb.detail[:], byteblob) 365 366 return createDirLink(link, &rdb) 367} 368 369func TestDirectorySymbolicLink(t *testing.T) { 370 var tests []dirLinkTest 371 output, _ := testenv.Command(t, "cmd", "/c", "mklink", "/?").Output() 372 mklinkSupportsDirectorySymbolicLinks := strings.Contains(string(output), " /D ") 373 if mklinkSupportsDirectorySymbolicLinks { 374 tests = append(tests, 375 dirLinkTest{ 376 name: "use_mklink_cmd", 377 mklink: func(link, target string) error { 378 output, err := testenv.Command(t, "cmd", "/c", "mklink", "/D", link, target).CombinedOutput() 379 if err != nil { 380 t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output) 381 } 382 return nil 383 }, 384 }, 385 ) 386 } else { 387 t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory symbolic links`) 388 } 389 390 // The rest of these test requires SeCreateSymbolicLinkPrivilege to be held. 391 runtime.LockOSThread() 392 defer runtime.UnlockOSThread() 393 394 err := windows.ImpersonateSelf(windows.SecurityImpersonation) 395 if err != nil { 396 t.Fatal(err) 397 } 398 defer windows.RevertToSelf() 399 400 err = enableCurrentThreadPrivilege("SeCreateSymbolicLinkPrivilege") 401 if err != nil { 402 t.Skipf(`skipping some tests, could not enable "SeCreateSymbolicLinkPrivilege": %v`, err) 403 } 404 tests = append(tests, 405 dirLinkTest{ 406 name: "use_os_pkg", 407 mklink: func(link, target string) error { 408 return os.Symlink(target, link) 409 }, 410 }, 411 dirLinkTest{ 412 // Create link similar to what mklink does, by inserting \??\ at the front of absolute target. 413 name: "standard", 414 mklink: func(link, target string) error { 415 var t reparseData 416 t.addPrintName(target) 417 t.addSubstituteName(`\??\` + target) 418 return createSymbolicLink(link, &t, false) 419 }, 420 }, 421 dirLinkTest{ 422 name: "relative", 423 mklink: func(link, target string) error { 424 var t reparseData 425 t.addSubstituteNameNoNUL(filepath.Base(target)) 426 t.addPrintNameNoNUL(filepath.Base(target)) 427 return createSymbolicLink(link, &t, true) 428 }, 429 }, 430 ) 431 testDirLinks(t, tests) 432} 433 434func mustHaveWorkstation(t *testing.T) { 435 mar, err := windows.OpenSCManager(nil, nil, windows.SERVICE_QUERY_STATUS) 436 if err != nil { 437 return 438 } 439 defer syscall.CloseHandle(mar) 440 //LanmanWorkstation is the service name, and Workstation is the display name. 441 srv, err := windows.OpenService(mar, syscall.StringToUTF16Ptr("LanmanWorkstation"), windows.SERVICE_QUERY_STATUS) 442 if err != nil { 443 return 444 } 445 defer syscall.CloseHandle(srv) 446 var state windows.SERVICE_STATUS 447 err = windows.QueryServiceStatus(srv, &state) 448 if err != nil { 449 return 450 } 451 if state.CurrentState != windows.SERVICE_RUNNING { 452 t.Skip("Requires the Windows service Workstation, but it is detected that it is not enabled.") 453 } 454} 455 456func TestNetworkSymbolicLink(t *testing.T) { 457 testenv.MustHaveSymlink(t) 458 459 const _NERR_ServerNotStarted = syscall.Errno(2114) 460 461 dir := t.TempDir() 462 chdir(t, dir) 463 464 pid := os.Getpid() 465 shareName := fmt.Sprintf("GoSymbolicLinkTestShare%d", pid) 466 sharePath := filepath.Join(dir, shareName) 467 testDir := "TestDir" 468 469 err := os.MkdirAll(filepath.Join(sharePath, testDir), 0777) 470 if err != nil { 471 t.Fatal(err) 472 } 473 474 wShareName, err := syscall.UTF16PtrFromString(shareName) 475 if err != nil { 476 t.Fatal(err) 477 } 478 wSharePath, err := syscall.UTF16PtrFromString(sharePath) 479 if err != nil { 480 t.Fatal(err) 481 } 482 483 // Per https://learn.microsoft.com/en-us/windows/win32/api/lmshare/ns-lmshare-share_info_2: 484 // 485 // “[The shi2_permissions field] indicates the shared resource's permissions 486 // for servers running with share-level security. A server running user-level 487 // security ignores this member. 488 // … 489 // Note that Windows does not support share-level security.” 490 // 491 // So it shouldn't matter what permissions we set here. 492 const permissions = 0 493 494 p := windows.SHARE_INFO_2{ 495 Netname: wShareName, 496 Type: windows.STYPE_DISKTREE | windows.STYPE_TEMPORARY, 497 Remark: nil, 498 Permissions: permissions, 499 MaxUses: 1, 500 CurrentUses: 0, 501 Path: wSharePath, 502 Passwd: nil, 503 } 504 505 err = windows.NetShareAdd(nil, 2, (*byte)(unsafe.Pointer(&p)), nil) 506 if err != nil { 507 if err == syscall.ERROR_ACCESS_DENIED || err == _NERR_ServerNotStarted { 508 t.Skipf("skipping: NetShareAdd: %v", err) 509 } 510 t.Fatal(err) 511 } 512 defer func() { 513 err := windows.NetShareDel(nil, wShareName, 0) 514 if err != nil { 515 t.Fatal(err) 516 } 517 }() 518 519 UNCPath := `\\localhost\` + shareName + `\` 520 521 fi1, err := os.Stat(sharePath) 522 if err != nil { 523 t.Fatal(err) 524 } 525 fi2, err := os.Stat(UNCPath) 526 if err != nil { 527 mustHaveWorkstation(t) 528 t.Fatal(err) 529 } 530 if !os.SameFile(fi1, fi2) { 531 t.Fatalf("%q and %q should be the same directory, but not", sharePath, UNCPath) 532 } 533 534 target := filepath.Join(UNCPath, testDir) 535 link := "link" 536 537 err = os.Symlink(target, link) 538 if err != nil { 539 t.Fatal(err) 540 } 541 defer os.Remove(link) 542 543 got, err := os.Readlink(link) 544 if err != nil { 545 t.Fatal(err) 546 } 547 if got != target { 548 t.Errorf(`os.Readlink(%#q): got %v, want %v`, link, got, target) 549 } 550 551 got, err = filepath.EvalSymlinks(link) 552 if err != nil { 553 t.Fatal(err) 554 } 555 if got != target { 556 t.Errorf(`filepath.EvalSymlinks(%#q): got %v, want %v`, link, got, target) 557 } 558} 559 560func TestStatLxSymLink(t *testing.T) { 561 if _, err := exec.LookPath("wsl"); err != nil { 562 t.Skip("skipping: WSL not detected") 563 } 564 565 temp := t.TempDir() 566 chdir(t, temp) 567 568 const target = "target" 569 const link = "link" 570 571 _, err := testenv.Command(t, "wsl", "/bin/mkdir", target).Output() 572 if err != nil { 573 // This normally happens when WSL still doesn't have a distro installed to run on. 574 t.Skipf("skipping: WSL is not correctly installed: %v", err) 575 } 576 577 _, err = testenv.Command(t, "wsl", "/bin/ln", "-s", target, link).Output() 578 if err != nil { 579 t.Fatal(err) 580 } 581 582 fi, err := os.Lstat(link) 583 if err != nil { 584 t.Fatal(err) 585 } 586 if m := fi.Mode(); m&fs.ModeSymlink != 0 { 587 // This can happen depending on newer WSL versions when running as admin or in developer mode. 588 t.Skip("skipping: WSL created reparse tag IO_REPARSE_TAG_SYMLINK instead of an IO_REPARSE_TAG_LX_SYMLINK") 589 } 590 // Stat'ing a IO_REPARSE_TAG_LX_SYMLINK from outside WSL always return ERROR_CANT_ACCESS_FILE. 591 // We check this condition to validate that os.Stat has tried to follow the link. 592 _, err = os.Stat(link) 593 const ERROR_CANT_ACCESS_FILE = syscall.Errno(1920) 594 if err == nil || !errors.Is(err, ERROR_CANT_ACCESS_FILE) { 595 t.Fatalf("os.Stat(%q): got %v, want ERROR_CANT_ACCESS_FILE", link, err) 596 } 597} 598 599func TestStartProcessAttr(t *testing.T) { 600 t.Parallel() 601 602 p, err := os.StartProcess(os.Getenv("COMSPEC"), []string{"/c", "cd"}, new(os.ProcAttr)) 603 if err != nil { 604 return 605 } 606 defer p.Wait() 607 t.Fatalf("StartProcess expected to fail, but succeeded.") 608} 609 610func TestShareNotExistError(t *testing.T) { 611 if testing.Short() { 612 t.Skip("slow test that uses network; skipping") 613 } 614 t.Parallel() 615 616 _, err := os.Stat(`\\no_such_server\no_such_share\no_such_file`) 617 if err == nil { 618 t.Fatal("stat succeeded, but expected to fail") 619 } 620 if !os.IsNotExist(err) { 621 t.Fatalf("os.Stat failed with %q, but os.IsNotExist(err) is false", err) 622 } 623} 624 625func TestBadNetPathError(t *testing.T) { 626 const ERROR_BAD_NETPATH = syscall.Errno(53) 627 if !os.IsNotExist(ERROR_BAD_NETPATH) { 628 t.Fatal("os.IsNotExist(syscall.Errno(53)) is false, but want true") 629 } 630} 631 632func TestStatDir(t *testing.T) { 633 defer chtmpdir(t)() 634 635 f, err := os.Open(".") 636 if err != nil { 637 t.Fatal(err) 638 } 639 defer f.Close() 640 641 fi, err := f.Stat() 642 if err != nil { 643 t.Fatal(err) 644 } 645 646 err = os.Chdir("..") 647 if err != nil { 648 t.Fatal(err) 649 } 650 651 fi2, err := f.Stat() 652 if err != nil { 653 t.Fatal(err) 654 } 655 656 if !os.SameFile(fi, fi2) { 657 t.Fatal("race condition occurred") 658 } 659} 660 661func TestOpenVolumeName(t *testing.T) { 662 tmpdir := t.TempDir() 663 chdir(t, tmpdir) 664 665 want := []string{"file1", "file2", "file3", "gopher.txt"} 666 slices.Sort(want) 667 for _, name := range want { 668 err := os.WriteFile(filepath.Join(tmpdir, name), nil, 0777) 669 if err != nil { 670 t.Fatal(err) 671 } 672 } 673 674 f, err := os.Open(filepath.VolumeName(tmpdir)) 675 if err != nil { 676 t.Fatal(err) 677 } 678 defer f.Close() 679 680 have, err := f.Readdirnames(-1) 681 if err != nil { 682 t.Fatal(err) 683 } 684 slices.Sort(have) 685 686 if strings.Join(want, "/") != strings.Join(have, "/") { 687 t.Fatalf("unexpected file list %q, want %q", have, want) 688 } 689} 690 691func TestDeleteReadOnly(t *testing.T) { 692 t.Parallel() 693 694 tmpdir := t.TempDir() 695 p := filepath.Join(tmpdir, "a") 696 // This sets FILE_ATTRIBUTE_READONLY. 697 f, err := os.OpenFile(p, os.O_CREATE, 0400) 698 if err != nil { 699 t.Fatal(err) 700 } 701 f.Close() 702 703 if err = os.Chmod(p, 0400); err != nil { 704 t.Fatal(err) 705 } 706 if err = os.Remove(p); err != nil { 707 t.Fatal(err) 708 } 709} 710 711func TestReadStdin(t *testing.T) { 712 old := poll.ReadConsole 713 defer func() { 714 poll.ReadConsole = old 715 }() 716 717 p, err := syscall.GetCurrentProcess() 718 if err != nil { 719 t.Fatalf("Unable to get handle to current process: %v", err) 720 } 721 var stdinDuplicate syscall.Handle 722 err = syscall.DuplicateHandle(p, syscall.Handle(syscall.Stdin), p, &stdinDuplicate, 0, false, syscall.DUPLICATE_SAME_ACCESS) 723 if err != nil { 724 t.Fatalf("Unable to duplicate stdin: %v", err) 725 } 726 testConsole := os.NewConsoleFile(stdinDuplicate, "test") 727 728 var tests = []string{ 729 "abc", 730 "äöü", 731 "\u3042", 732 "“hi”™", 733 "hello\x1aworld", 734 "\U0001F648\U0001F649\U0001F64A", 735 } 736 737 for _, consoleSize := range []int{1, 2, 3, 10, 16, 100, 1000} { 738 for _, readSize := range []int{1, 2, 3, 4, 5, 8, 10, 16, 20, 50, 100} { 739 for _, s := range tests { 740 t.Run(fmt.Sprintf("c%d/r%d/%s", consoleSize, readSize, s), func(t *testing.T) { 741 s16 := utf16.Encode([]rune(s)) 742 poll.ReadConsole = func(h syscall.Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) error { 743 if inputControl != nil { 744 t.Fatalf("inputControl not nil") 745 } 746 n := int(toread) 747 if n > consoleSize { 748 n = consoleSize 749 } 750 n = copy((*[10000]uint16)(unsafe.Pointer(buf))[:n:n], s16) 751 s16 = s16[n:] 752 *read = uint32(n) 753 t.Logf("read %d -> %d", toread, *read) 754 return nil 755 } 756 757 var all []string 758 var buf []byte 759 chunk := make([]byte, readSize) 760 for { 761 n, err := testConsole.Read(chunk) 762 buf = append(buf, chunk[:n]...) 763 if err == io.EOF { 764 all = append(all, string(buf)) 765 if len(all) >= 5 { 766 break 767 } 768 buf = buf[:0] 769 } else if err != nil { 770 t.Fatalf("reading %q: error: %v", s, err) 771 } 772 if len(buf) >= 2000 { 773 t.Fatalf("reading %q: stuck in loop: %q", s, buf) 774 } 775 } 776 777 want := strings.Split(s, "\x1a") 778 for len(want) < 5 { 779 want = append(want, "") 780 } 781 if !reflect.DeepEqual(all, want) { 782 t.Errorf("reading %q:\nhave %x\nwant %x", s, all, want) 783 } 784 }) 785 } 786 } 787 } 788} 789 790func TestStatPagefile(t *testing.T) { 791 t.Parallel() 792 793 const path = `c:\pagefile.sys` 794 fi, err := os.Stat(path) 795 if err == nil { 796 if fi.Name() == "" { 797 t.Fatalf("Stat(%q).Name() is empty", path) 798 } 799 t.Logf("Stat(%q).Size() = %v", path, fi.Size()) 800 return 801 } 802 if os.IsNotExist(err) { 803 t.Skip(`skipping because c:\pagefile.sys is not found`) 804 } 805 t.Fatal(err) 806} 807 808// syscallCommandLineToArgv calls syscall.CommandLineToArgv 809// and converts returned result into []string. 810func syscallCommandLineToArgv(cmd string) ([]string, error) { 811 var argc int32 812 argv, err := syscall.CommandLineToArgv(&syscall.StringToUTF16(cmd)[0], &argc) 813 if err != nil { 814 return nil, err 815 } 816 defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv)))) 817 818 var args []string 819 for _, v := range (*argv)[:argc] { 820 args = append(args, syscall.UTF16ToString((*v)[:])) 821 } 822 return args, nil 823} 824 825// compareCommandLineToArgvWithSyscall ensures that 826// os.CommandLineToArgv(cmd) and syscall.CommandLineToArgv(cmd) 827// return the same result. 828func compareCommandLineToArgvWithSyscall(t *testing.T, cmd string) { 829 syscallArgs, err := syscallCommandLineToArgv(cmd) 830 if err != nil { 831 t.Fatal(err) 832 } 833 args := os.CommandLineToArgv(cmd) 834 if want, have := fmt.Sprintf("%q", syscallArgs), fmt.Sprintf("%q", args); want != have { 835 t.Errorf("testing os.commandLineToArgv(%q) failed: have %q want %q", cmd, args, syscallArgs) 836 return 837 } 838} 839 840func TestCmdArgs(t *testing.T) { 841 if testing.Short() { 842 t.Skipf("in short mode; skipping test that builds a binary") 843 } 844 t.Parallel() 845 846 tmpdir := t.TempDir() 847 848 const prog = ` 849package main 850 851import ( 852 "fmt" 853 "os" 854) 855 856func main() { 857 fmt.Printf("%q", os.Args) 858} 859` 860 src := filepath.Join(tmpdir, "main.go") 861 if err := os.WriteFile(src, []byte(prog), 0666); err != nil { 862 t.Fatal(err) 863 } 864 865 exe := filepath.Join(tmpdir, "main.exe") 866 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src) 867 cmd.Dir = tmpdir 868 out, err := cmd.CombinedOutput() 869 if err != nil { 870 t.Fatalf("building main.exe failed: %v\n%s", err, out) 871 } 872 873 var cmds = []string{ 874 ``, 875 ` a b c`, 876 ` "`, 877 ` ""`, 878 ` """`, 879 ` "" a`, 880 ` "123"`, 881 ` \"123\"`, 882 ` \"123 456\"`, 883 ` \\"`, 884 ` \\\"`, 885 ` \\\\\"`, 886 ` \\\"x`, 887 ` """"\""\\\"`, 888 ` abc`, 889 ` \\\\\""x"""y z`, 890 "\tb\t\"x\ty\"", 891 ` "Брад" d e`, 892 // examples from https://learn.microsoft.com/en-us/cpp/cpp/main-function-command-line-args 893 ` "abc" d e`, 894 ` a\\b d"e f"g h`, 895 ` a\\\"b c d`, 896 ` a\\\\"b c" d e`, 897 // http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV 898 // from 5.4 Examples 899 ` CallMeIshmael`, 900 ` "Call Me Ishmael"`, 901 ` Cal"l Me I"shmael`, 902 ` CallMe\"Ishmael`, 903 ` "CallMe\"Ishmael"`, 904 ` "Call Me Ishmael\\"`, 905 ` "CallMe\\\"Ishmael"`, 906 ` a\\\b`, 907 ` "a\\\b"`, 908 // from 5.5 Some Common Tasks 909 ` "\"Call Me Ishmael\""`, 910 ` "C:\TEST A\\"`, 911 ` "\"C:\TEST A\\\""`, 912 // from 5.6 The Microsoft Examples Explained 913 ` "a b c" d e`, 914 ` "ab\"c" "\\" d`, 915 ` a\\\b d"e f"g h`, 916 ` a\\\"b c d`, 917 ` a\\\\"b c" d e`, 918 // from 5.7 Double Double Quote Examples (pre 2008) 919 ` "a b c""`, 920 ` """CallMeIshmael""" b c`, 921 ` """Call Me Ishmael"""`, 922 ` """"Call Me Ishmael"" b c`, 923 } 924 for _, cmd := range cmds { 925 compareCommandLineToArgvWithSyscall(t, "test"+cmd) 926 compareCommandLineToArgvWithSyscall(t, `"cmd line"`+cmd) 927 compareCommandLineToArgvWithSyscall(t, exe+cmd) 928 929 // test both syscall.EscapeArg and os.commandLineToArgv 930 args := os.CommandLineToArgv(exe + cmd) 931 out, err := testenv.Command(t, args[0], args[1:]...).CombinedOutput() 932 if err != nil { 933 t.Fatalf("running %q failed: %v\n%v", args, err, string(out)) 934 } 935 if want, have := fmt.Sprintf("%q", args), string(out); want != have { 936 t.Errorf("wrong output of executing %q: have %q want %q", args, have, want) 937 continue 938 } 939 } 940} 941 942func findOneDriveDir() (string, error) { 943 // as per https://stackoverflow.com/questions/42519624/how-to-determine-location-of-onedrive-on-windows-7-and-8-in-c 944 const onedrivekey = `SOFTWARE\Microsoft\OneDrive` 945 k, err := registry.OpenKey(registry.CURRENT_USER, onedrivekey, registry.READ) 946 if err != nil { 947 return "", fmt.Errorf("OpenKey(%q) failed: %v", onedrivekey, err) 948 } 949 defer k.Close() 950 951 path, valtype, err := k.GetStringValue("UserFolder") 952 if err != nil { 953 return "", fmt.Errorf("reading UserFolder failed: %v", err) 954 } 955 956 if valtype == registry.EXPAND_SZ { 957 expanded, err := registry.ExpandString(path) 958 if err != nil { 959 return "", fmt.Errorf("expanding UserFolder failed: %v", err) 960 } 961 path = expanded 962 } 963 964 return path, nil 965} 966 967// TestOneDrive verifies that OneDrive folder is a directory and not a symlink. 968func TestOneDrive(t *testing.T) { 969 t.Parallel() 970 971 dir, err := findOneDriveDir() 972 if err != nil { 973 t.Skipf("Skipping, because we did not find OneDrive directory: %v", err) 974 } 975 testDirStats(t, dir) 976} 977 978func TestWindowsDevNullFile(t *testing.T) { 979 t.Parallel() 980 981 f1, err := os.Open("NUL") 982 if err != nil { 983 t.Fatal(err) 984 } 985 defer f1.Close() 986 987 fi1, err := f1.Stat() 988 if err != nil { 989 t.Fatal(err) 990 } 991 992 f2, err := os.Open("nul") 993 if err != nil { 994 t.Fatal(err) 995 } 996 defer f2.Close() 997 998 fi2, err := f2.Stat() 999 if err != nil { 1000 t.Fatal(err) 1001 } 1002 1003 if !os.SameFile(fi1, fi2) { 1004 t.Errorf(`"NUL" and "nul" are not the same file`) 1005 } 1006} 1007 1008func TestFileStatNUL(t *testing.T) { 1009 t.Parallel() 1010 1011 f, err := os.Open("NUL") 1012 if err != nil { 1013 t.Fatal(err) 1014 } 1015 fi, err := f.Stat() 1016 if err != nil { 1017 t.Fatal(err) 1018 } 1019 if got, want := fi.Mode(), os.ModeDevice|os.ModeCharDevice|0666; got != want { 1020 t.Errorf("Open(%q).Stat().Mode() = %v, want %v", "NUL", got, want) 1021 } 1022} 1023 1024func TestStatNUL(t *testing.T) { 1025 t.Parallel() 1026 1027 fi, err := os.Stat("NUL") 1028 if err != nil { 1029 t.Fatal(err) 1030 } 1031 if got, want := fi.Mode(), os.ModeDevice|os.ModeCharDevice|0666; got != want { 1032 t.Errorf("Stat(%q).Mode() = %v, want %v", "NUL", got, want) 1033 } 1034} 1035 1036// TestSymlinkCreation verifies that creating a symbolic link 1037// works on Windows when developer mode is active. 1038// This is supported starting Windows 10 (1703, v10.0.14972). 1039func TestSymlinkCreation(t *testing.T) { 1040 if !testenv.HasSymlink() && !isWindowsDeveloperModeActive() { 1041 t.Skip("Windows developer mode is not active") 1042 } 1043 t.Parallel() 1044 1045 temp := t.TempDir() 1046 dummyFile := filepath.Join(temp, "file") 1047 if err := os.WriteFile(dummyFile, []byte(""), 0644); err != nil { 1048 t.Fatal(err) 1049 } 1050 1051 linkFile := filepath.Join(temp, "link") 1052 if err := os.Symlink(dummyFile, linkFile); err != nil { 1053 t.Fatal(err) 1054 } 1055} 1056 1057// isWindowsDeveloperModeActive checks whether or not the developer mode is active on Windows 10. 1058// Returns false for prior Windows versions. 1059// see https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development 1060func isWindowsDeveloperModeActive() bool { 1061 key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", registry.READ) 1062 if err != nil { 1063 return false 1064 } 1065 1066 val, _, err := key.GetIntegerValue("AllowDevelopmentWithoutDevLicense") 1067 if err != nil { 1068 return false 1069 } 1070 1071 return val != 0 1072} 1073 1074// TestRootRelativeDirSymlink verifies that symlinks to paths relative to the 1075// drive root (beginning with "\" but no volume name) are created with the 1076// correct symlink type. 1077// (See https://golang.org/issue/39183#issuecomment-632175728.) 1078func TestRootRelativeDirSymlink(t *testing.T) { 1079 testenv.MustHaveSymlink(t) 1080 t.Parallel() 1081 1082 temp := t.TempDir() 1083 dir := filepath.Join(temp, "dir") 1084 if err := os.Mkdir(dir, 0755); err != nil { 1085 t.Fatal(err) 1086 } 1087 1088 volumeRelDir := strings.TrimPrefix(dir, filepath.VolumeName(dir)) // leaves leading backslash 1089 1090 link := filepath.Join(temp, "link") 1091 err := os.Symlink(volumeRelDir, link) 1092 if err != nil { 1093 t.Fatal(err) 1094 } 1095 t.Logf("Symlink(%#q, %#q)", volumeRelDir, link) 1096 1097 f, err := os.Open(link) 1098 if err != nil { 1099 t.Fatal(err) 1100 } 1101 defer f.Close() 1102 if fi, err := f.Stat(); err != nil { 1103 t.Fatal(err) 1104 } else if !fi.IsDir() { 1105 t.Errorf("Open(%#q).Stat().IsDir() = false; want true", f.Name()) 1106 } 1107} 1108 1109// TestWorkingDirectoryRelativeSymlink verifies that symlinks to paths relative 1110// to the current working directory for the drive, such as "C:File.txt", are 1111// correctly converted to absolute links of the correct symlink type (per 1112// https://docs.microsoft.com/en-us/windows/win32/fileio/creating-symbolic-links). 1113func TestWorkingDirectoryRelativeSymlink(t *testing.T) { 1114 testenv.MustHaveSymlink(t) 1115 1116 // Construct a directory to be symlinked. 1117 temp := t.TempDir() 1118 if v := filepath.VolumeName(temp); len(v) < 2 || v[1] != ':' { 1119 t.Skipf("Can't test relative symlinks: t.TempDir() (%#q) does not begin with a drive letter.", temp) 1120 } 1121 1122 absDir := filepath.Join(temp, `dir\sub`) 1123 if err := os.MkdirAll(absDir, 0755); err != nil { 1124 t.Fatal(err) 1125 } 1126 1127 // Change to the temporary directory and construct a 1128 // working-directory-relative symlink. 1129 oldwd, err := os.Getwd() 1130 if err != nil { 1131 t.Fatal(err) 1132 } 1133 defer func() { 1134 if err := os.Chdir(oldwd); err != nil { 1135 t.Fatal(err) 1136 } 1137 }() 1138 if err := os.Chdir(temp); err != nil { 1139 t.Fatal(err) 1140 } 1141 t.Logf("Chdir(%#q)", temp) 1142 1143 wdRelDir := filepath.VolumeName(temp) + `dir\sub` // no backslash after volume. 1144 absLink := filepath.Join(temp, "link") 1145 err = os.Symlink(wdRelDir, absLink) 1146 if err != nil { 1147 t.Fatal(err) 1148 } 1149 t.Logf("Symlink(%#q, %#q)", wdRelDir, absLink) 1150 1151 // Now change back to the original working directory and verify that the 1152 // symlink still refers to its original path and is correctly marked as a 1153 // directory. 1154 if err := os.Chdir(oldwd); err != nil { 1155 t.Fatal(err) 1156 } 1157 t.Logf("Chdir(%#q)", oldwd) 1158 1159 resolved, err := os.Readlink(absLink) 1160 if err != nil { 1161 t.Errorf("Readlink(%#q): %v", absLink, err) 1162 } else if resolved != absDir { 1163 t.Errorf("Readlink(%#q) = %#q; want %#q", absLink, resolved, absDir) 1164 } 1165 1166 linkFile, err := os.Open(absLink) 1167 if err != nil { 1168 t.Fatal(err) 1169 } 1170 defer linkFile.Close() 1171 1172 linkInfo, err := linkFile.Stat() 1173 if err != nil { 1174 t.Fatal(err) 1175 } 1176 if !linkInfo.IsDir() { 1177 t.Errorf("Open(%#q).Stat().IsDir() = false; want true", absLink) 1178 } 1179 1180 absInfo, err := os.Stat(absDir) 1181 if err != nil { 1182 t.Fatal(err) 1183 } 1184 1185 if !os.SameFile(absInfo, linkInfo) { 1186 t.Errorf("SameFile(Stat(%#q), Open(%#q).Stat()) = false; want true", absDir, absLink) 1187 } 1188} 1189 1190// TestStatOfInvalidName is regression test for issue #24999. 1191func TestStatOfInvalidName(t *testing.T) { 1192 t.Parallel() 1193 1194 _, err := os.Stat("*.go") 1195 if err == nil { 1196 t.Fatal(`os.Stat("*.go") unexpectedly succeeded`) 1197 } 1198} 1199 1200// findUnusedDriveLetter searches mounted drive list on the system 1201// (starting from Z: and ending at D:) for unused drive letter. 1202// It returns path to the found drive root directory (like Z:\) or error. 1203func findUnusedDriveLetter() (string, error) { 1204 // Do not use A: and B:, because they are reserved for floppy drive. 1205 // Do not use C:, because it is normally used for main drive. 1206 for l := 'Z'; l >= 'D'; l-- { 1207 p := string(l) + `:\` 1208 _, err := os.Stat(p) 1209 if os.IsNotExist(err) { 1210 return p, nil 1211 } 1212 } 1213 return "", errors.New("Could not find unused drive letter.") 1214} 1215 1216func TestRootDirAsTemp(t *testing.T) { 1217 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 1218 fmt.Print(os.TempDir()) 1219 os.Exit(0) 1220 } 1221 1222 testenv.MustHaveExec(t) 1223 t.Parallel() 1224 1225 exe, err := os.Executable() 1226 if err != nil { 1227 t.Fatal(err) 1228 } 1229 1230 newtmp, err := findUnusedDriveLetter() 1231 if err != nil { 1232 t.Skip(err) 1233 } 1234 1235 cmd := testenv.Command(t, exe, "-test.run=^TestRootDirAsTemp$") 1236 cmd.Env = cmd.Environ() 1237 cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1") 1238 cmd.Env = append(cmd.Env, "TMP="+newtmp) 1239 cmd.Env = append(cmd.Env, "TEMP="+newtmp) 1240 output, err := cmd.CombinedOutput() 1241 if err != nil { 1242 t.Fatalf("Failed to spawn child process: %v %q", err, string(output)) 1243 } 1244 if want, have := newtmp, string(output); have != want { 1245 t.Fatalf("unexpected child process output %q, want %q", have, want) 1246 } 1247} 1248 1249// replaceDriveWithVolumeID returns path with its volume name replaced with 1250// the mounted volume ID. E.g. C:\foo -> \\?\Volume{GUID}\foo. 1251func replaceDriveWithVolumeID(t *testing.T, path string) string { 1252 t.Helper() 1253 cmd := testenv.Command(t, "cmd", "/c", "mountvol", filepath.VolumeName(path), "/L") 1254 out, err := cmd.CombinedOutput() 1255 if err != nil { 1256 t.Fatalf("%v: %v\n%s", cmd, err, out) 1257 } 1258 vol := strings.Trim(string(out), " \n\r") 1259 return filepath.Join(vol, path[len(filepath.VolumeName(path)):]) 1260} 1261 1262func TestReadlink(t *testing.T) { 1263 tests := []struct { 1264 junction bool 1265 dir bool 1266 drive bool 1267 relative bool 1268 }{ 1269 {junction: true, dir: true, drive: true, relative: false}, 1270 {junction: true, dir: true, drive: false, relative: false}, 1271 {junction: true, dir: true, drive: false, relative: true}, 1272 {junction: false, dir: true, drive: true, relative: false}, 1273 {junction: false, dir: true, drive: false, relative: false}, 1274 {junction: false, dir: true, drive: false, relative: true}, 1275 {junction: false, dir: false, drive: true, relative: false}, 1276 {junction: false, dir: false, drive: false, relative: false}, 1277 {junction: false, dir: false, drive: false, relative: true}, 1278 } 1279 for _, tt := range tests { 1280 tt := tt 1281 var name string 1282 if tt.junction { 1283 name = "junction" 1284 } else { 1285 name = "symlink" 1286 } 1287 if tt.dir { 1288 name += "_dir" 1289 } else { 1290 name += "_file" 1291 } 1292 if tt.drive { 1293 name += "_drive" 1294 } else { 1295 name += "_volume" 1296 } 1297 if tt.relative { 1298 name += "_relative" 1299 } else { 1300 name += "_absolute" 1301 } 1302 1303 t.Run(name, func(t *testing.T) { 1304 if !tt.relative { 1305 t.Parallel() 1306 } 1307 // Make sure tmpdir is not a symlink, otherwise tests will fail. 1308 tmpdir, err := filepath.EvalSymlinks(t.TempDir()) 1309 if err != nil { 1310 t.Fatal(err) 1311 } 1312 link := filepath.Join(tmpdir, "link") 1313 target := filepath.Join(tmpdir, "target") 1314 if tt.dir { 1315 if err := os.MkdirAll(target, 0777); err != nil { 1316 t.Fatal(err) 1317 } 1318 } else { 1319 if err := os.WriteFile(target, nil, 0666); err != nil { 1320 t.Fatal(err) 1321 } 1322 } 1323 var want string 1324 if tt.relative { 1325 relTarget := filepath.Base(target) 1326 if tt.junction { 1327 want = target // relative directory junction resolves to absolute path 1328 } else { 1329 want = relTarget 1330 } 1331 chdir(t, tmpdir) 1332 link = filepath.Base(link) 1333 target = relTarget 1334 } else { 1335 if tt.drive { 1336 want = target 1337 } else { 1338 volTarget := replaceDriveWithVolumeID(t, target) 1339 if winreadlinkvolume.Value() == "0" { 1340 want = target 1341 } else { 1342 want = volTarget 1343 } 1344 target = volTarget 1345 } 1346 } 1347 if tt.junction { 1348 cmd := testenv.Command(t, "cmd", "/c", "mklink", "/J", link, target) 1349 if out, err := cmd.CombinedOutput(); err != nil { 1350 t.Fatalf("%v: %v\n%s", cmd, err, out) 1351 } 1352 } else { 1353 if err := os.Symlink(target, link); err != nil { 1354 t.Fatalf("Symlink(%#q, %#q): %v", target, link, err) 1355 } 1356 } 1357 got, err := os.Readlink(link) 1358 if err != nil { 1359 t.Fatal(err) 1360 } 1361 if got != want { 1362 t.Fatalf("Readlink(%#q) = %#q; want %#q", target, got, want) 1363 } 1364 }) 1365 } 1366} 1367 1368func TestOpenDirTOCTOU(t *testing.T) { 1369 t.Parallel() 1370 1371 // Check opened directories can't be renamed until the handle is closed. 1372 // See issue 52747. 1373 tmpdir := t.TempDir() 1374 dir := filepath.Join(tmpdir, "dir") 1375 if err := os.Mkdir(dir, 0777); err != nil { 1376 t.Fatal(err) 1377 } 1378 f, err := os.Open(dir) 1379 if err != nil { 1380 t.Fatal(err) 1381 } 1382 newpath := filepath.Join(tmpdir, "dir1") 1383 err = os.Rename(dir, newpath) 1384 if err == nil || !errors.Is(err, windows.ERROR_SHARING_VIOLATION) { 1385 f.Close() 1386 t.Fatalf("Rename(%q, %q) = %v; want windows.ERROR_SHARING_VIOLATION", dir, newpath, err) 1387 } 1388 f.Close() 1389 err = os.Rename(dir, newpath) 1390 if err != nil { 1391 t.Error(err) 1392 } 1393} 1394 1395func TestAppExecLinkStat(t *testing.T) { 1396 // We expect executables installed to %LOCALAPPDATA%\Microsoft\WindowsApps to 1397 // be reparse points with tag IO_REPARSE_TAG_APPEXECLINK. Here we check that 1398 // such reparse points are treated as irregular (but executable) files, not 1399 // broken symlinks. 1400 appdata := os.Getenv("LOCALAPPDATA") 1401 if appdata == "" { 1402 t.Skipf("skipping: LOCALAPPDATA not set") 1403 } 1404 1405 pythonExeName := "python3.exe" 1406 pythonPath := filepath.Join(appdata, `Microsoft\WindowsApps`, pythonExeName) 1407 1408 lfi, err := os.Lstat(pythonPath) 1409 if err != nil { 1410 t.Skip("skipping test, because Python 3 is not installed via the Windows App Store on this system; see https://golang.org/issue/42919") 1411 } 1412 1413 // An APPEXECLINK reparse point is not a symlink, so os.Readlink should return 1414 // a non-nil error for it, and Stat should return results identical to Lstat. 1415 linkName, err := os.Readlink(pythonPath) 1416 if err == nil { 1417 t.Errorf("os.Readlink(%q) = %q, but expected an error\n(should be an APPEXECLINK reparse point, not a symlink)", pythonPath, linkName) 1418 } 1419 1420 sfi, err := os.Stat(pythonPath) 1421 if err != nil { 1422 t.Fatalf("Stat %s: %v", pythonPath, err) 1423 } 1424 1425 if lfi.Name() != sfi.Name() { 1426 t.Logf("os.Lstat(%q) = %+v", pythonPath, lfi) 1427 t.Logf("os.Stat(%q) = %+v", pythonPath, sfi) 1428 t.Errorf("files should be same") 1429 } 1430 1431 if lfi.Name() != pythonExeName { 1432 t.Errorf("Stat %s: got %q, but wanted %q", pythonPath, lfi.Name(), pythonExeName) 1433 } 1434 if tp := lfi.Mode().Type(); tp != fs.ModeIrregular { 1435 // A reparse point is not a regular file, but we don't have a more appropriate 1436 // ModeType bit for it, so it should be marked as irregular. 1437 t.Errorf("%q should not be a an irregular file (mode=0x%x)", pythonPath, uint32(tp)) 1438 } 1439 1440 if sfi.Name() != pythonExeName { 1441 t.Errorf("Stat %s: got %q, but wanted %q", pythonPath, sfi.Name(), pythonExeName) 1442 } 1443 if m := sfi.Mode(); m&fs.ModeSymlink != 0 { 1444 t.Errorf("%q should be a file, not a link (mode=0x%x)", pythonPath, uint32(m)) 1445 } 1446 if m := sfi.Mode(); m&fs.ModeDir != 0 { 1447 t.Errorf("%q should be a file, not a directory (mode=0x%x)", pythonPath, uint32(m)) 1448 } 1449 if m := sfi.Mode(); m&fs.ModeIrregular == 0 { 1450 // A reparse point is not a regular file, but we don't have a more appropriate 1451 // ModeType bit for it, so it should be marked as irregular. 1452 t.Errorf("%q should not be a regular file (mode=0x%x)", pythonPath, uint32(m)) 1453 } 1454 1455 p, err := exec.LookPath(pythonPath) 1456 if err != nil { 1457 t.Errorf("exec.LookPath(%q): %v", pythonPath, err) 1458 } 1459 if p != pythonPath { 1460 t.Errorf("exec.LookPath(%q) = %q; want %q", pythonPath, p, pythonPath) 1461 } 1462} 1463 1464func TestIllformedUTF16FileName(t *testing.T) { 1465 dir := t.TempDir() 1466 const sep = string(os.PathSeparator) 1467 if !strings.HasSuffix(dir, sep) { 1468 dir += sep 1469 } 1470 1471 // This UTF-16 file name is ill-formed as it contains low surrogates that are not preceded by high surrogates ([1:5]). 1472 namew := []uint16{0x2e, 0xdc6d, 0xdc73, 0xdc79, 0xdc73, 0x30, 0x30, 0x30, 0x31, 0} 1473 1474 // Create a file whose name contains unpaired surrogates. 1475 // Use syscall.CreateFile instead of os.Create to simulate a file that is created by 1476 // a non-Go program so the file name hasn't gone through syscall.UTF16FromString. 1477 dirw := utf16.Encode([]rune(dir)) 1478 pathw := append(dirw, namew...) 1479 fd, err := syscall.CreateFile(&pathw[0], syscall.GENERIC_ALL, 0, nil, syscall.CREATE_NEW, 0, 0) 1480 if err != nil { 1481 t.Fatal(err) 1482 } 1483 syscall.CloseHandle(fd) 1484 1485 name := syscall.UTF16ToString(namew) 1486 path := filepath.Join(dir, name) 1487 // Verify that os.Lstat can query the file. 1488 fi, err := os.Lstat(path) 1489 if err != nil { 1490 t.Fatal(err) 1491 } 1492 if got := fi.Name(); got != name { 1493 t.Errorf("got %q, want %q", got, name) 1494 } 1495 // Verify that File.Readdirnames lists the file. 1496 f, err := os.Open(dir) 1497 if err != nil { 1498 t.Fatal(err) 1499 } 1500 files, err := f.Readdirnames(0) 1501 f.Close() 1502 if err != nil { 1503 t.Fatal(err) 1504 } 1505 if !slices.Contains(files, name) { 1506 t.Error("file not listed") 1507 } 1508 // Verify that os.RemoveAll can remove the directory 1509 // and that it doesn't hang. 1510 err = os.RemoveAll(dir) 1511 if err != nil { 1512 t.Error(err) 1513 } 1514} 1515 1516func TestUTF16Alloc(t *testing.T) { 1517 allowsPerRun := func(want int, f func()) { 1518 t.Helper() 1519 got := int(testing.AllocsPerRun(5, f)) 1520 if got != want { 1521 t.Errorf("got %d allocs, want %d", got, want) 1522 } 1523 } 1524 allowsPerRun(1, func() { 1525 syscall.UTF16ToString([]uint16{'a', 'b', 'c'}) 1526 }) 1527 allowsPerRun(1, func() { 1528 syscall.UTF16FromString("abc") 1529 }) 1530} 1531 1532func TestNewFileInvalid(t *testing.T) { 1533 t.Parallel() 1534 if f := os.NewFile(uintptr(syscall.InvalidHandle), "invalid"); f != nil { 1535 t.Errorf("NewFile(InvalidHandle) got %v want nil", f) 1536 } 1537} 1538 1539func TestReadDirPipe(t *testing.T) { 1540 dir := `\\.\pipe\` 1541 fi, err := os.Stat(dir) 1542 if err != nil || !fi.IsDir() { 1543 t.Skipf("%s is not a directory", dir) 1544 } 1545 _, err = os.ReadDir(dir) 1546 if err != nil { 1547 t.Errorf("ReadDir(%q) = %v", dir, err) 1548 } 1549} 1550 1551func TestReadDirNoFileID(t *testing.T) { 1552 *os.AllowReadDirFileID = false 1553 defer func() { *os.AllowReadDirFileID = true }() 1554 1555 dir := t.TempDir() 1556 pathA := filepath.Join(dir, "a") 1557 pathB := filepath.Join(dir, "b") 1558 if err := os.WriteFile(pathA, nil, 0666); err != nil { 1559 t.Fatal(err) 1560 } 1561 if err := os.WriteFile(pathB, nil, 0666); err != nil { 1562 t.Fatal(err) 1563 } 1564 1565 files, err := os.ReadDir(dir) 1566 if err != nil { 1567 t.Fatal(err) 1568 } 1569 if len(files) != 2 { 1570 t.Fatalf("ReadDir(%q) = %v; want 2 files", dir, files) 1571 } 1572 1573 // Check that os.SameFile works with files returned by os.ReadDir. 1574 f1, err := files[0].Info() 1575 if err != nil { 1576 t.Fatal(err) 1577 } 1578 f2, err := files[1].Info() 1579 if err != nil { 1580 t.Fatal(err) 1581 } 1582 if !os.SameFile(f1, f1) { 1583 t.Errorf("SameFile(%v, %v) = false; want true", f1, f1) 1584 } 1585 if !os.SameFile(f2, f2) { 1586 t.Errorf("SameFile(%v, %v) = false; want true", f2, f2) 1587 } 1588 if os.SameFile(f1, f2) { 1589 t.Errorf("SameFile(%v, %v) = true; want false", f1, f2) 1590 } 1591 1592 // Check that os.SameFile works with a mix of os.ReadDir and os.Stat files. 1593 f1s, err := os.Stat(pathA) 1594 if err != nil { 1595 t.Fatal(err) 1596 } 1597 f2s, err := os.Stat(pathB) 1598 if err != nil { 1599 t.Fatal(err) 1600 } 1601 if !os.SameFile(f1, f1s) { 1602 t.Errorf("SameFile(%v, %v) = false; want true", f1, f1s) 1603 } 1604 if !os.SameFile(f2, f2s) { 1605 t.Errorf("SameFile(%v, %v) = false; want true", f2, f2s) 1606 } 1607} 1608