summaryrefslogtreecommitdiffstats
path: root/src/os
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/os
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/os')
-rw-r--r--src/os/dir.go125
-rw-r--r--src/os/dir_darwin.go140
-rw-r--r--src/os/dir_plan9.go86
-rw-r--r--src/os/dir_unix.go198
-rw-r--r--src/os/dir_windows.go80
-rw-r--r--src/os/dirent_aix.go30
-rw-r--r--src/os/dirent_dragonfly.go55
-rw-r--r--src/os/dirent_freebsd.go47
-rw-r--r--src/os/dirent_js.go30
-rw-r--r--src/os/dirent_linux.go51
-rw-r--r--src/os/dirent_netbsd.go47
-rw-r--r--src/os/dirent_openbsd.go47
-rw-r--r--src/os/dirent_solaris.go30
-rw-r--r--src/os/dirent_wasip1.go52
-rw-r--r--src/os/endian_big.go9
-rw-r--r--src/os/endian_little.go9
-rw-r--r--src/os/env.go141
-rw-r--r--src/os/env_test.go206
-rw-r--r--src/os/env_unix_test.go56
-rw-r--r--src/os/error.go141
-rw-r--r--src/os/error_errno.go11
-rw-r--r--src/os/error_plan9.go9
-rw-r--r--src/os/error_posix.go18
-rw-r--r--src/os/error_test.go189
-rw-r--r--src/os/error_unix_test.go40
-rw-r--r--src/os/error_windows_test.go40
-rw-r--r--src/os/example_test.go265
-rw-r--r--src/os/exec.go180
-rw-r--r--src/os/exec/bench_test.go23
-rw-r--r--src/os/exec/dot_test.go192
-rw-r--r--src/os/exec/env_test.go67
-rw-r--r--src/os/exec/example_test.go169
-rw-r--r--src/os/exec/exec.go1303
-rw-r--r--src/os/exec/exec_linux_test.go45
-rw-r--r--src/os/exec/exec_other_test.go14
-rw-r--r--src/os/exec/exec_plan9.go19
-rw-r--r--src/os/exec/exec_posix_test.go276
-rw-r--r--src/os/exec/exec_test.go1784
-rw-r--r--src/os/exec/exec_unix.go24
-rw-r--r--src/os/exec/exec_unix_test.go17
-rw-r--r--src/os/exec/exec_windows.go23
-rw-r--r--src/os/exec/exec_windows_test.go109
-rw-r--r--src/os/exec/internal/fdtest/exists_plan9.go20
-rw-r--r--src/os/exec/internal/fdtest/exists_test.go21
-rw-r--r--src/os/exec/internal/fdtest/exists_unix.go19
-rw-r--r--src/os/exec/internal/fdtest/exists_windows.go12
-rw-r--r--src/os/exec/internal_test.go61
-rw-r--r--src/os/exec/lp_linux_test.go88
-rw-r--r--src/os/exec/lp_plan9.go66
-rw-r--r--src/os/exec/lp_test.go33
-rw-r--r--src/os/exec/lp_unix.go82
-rw-r--r--src/os/exec/lp_unix_test.go50
-rw-r--r--src/os/exec/lp_wasm.go23
-rw-r--r--src/os/exec/lp_windows.go145
-rw-r--r--src/os/exec/lp_windows_test.go612
-rw-r--r--src/os/exec/read3.go91
-rw-r--r--src/os/exec_plan9.go149
-rw-r--r--src/os/exec_posix.go136
-rw-r--r--src/os/exec_unix.go106
-rw-r--r--src/os/exec_unix_test.go45
-rw-r--r--src/os/exec_windows.go181
-rw-r--r--src/os/executable.go20
-rw-r--r--src/os/executable_darwin.go29
-rw-r--r--src/os/executable_dragonfly.go12
-rw-r--r--src/os/executable_freebsd.go12
-rw-r--r--src/os/executable_path.go104
-rw-r--r--src/os/executable_plan9.go22
-rw-r--r--src/os/executable_procfs.go37
-rw-r--r--src/os/executable_solaris.go32
-rw-r--r--src/os/executable_sysctl.go35
-rw-r--r--src/os/executable_test.go155
-rw-r--r--src/os/executable_wasm.go16
-rw-r--r--src/os/executable_windows.go32
-rw-r--r--src/os/export_linux_test.go11
-rw-r--r--src/os/export_test.go17
-rw-r--r--src/os/export_unix_test.go9
-rw-r--r--src/os/export_windows_test.go14
-rw-r--r--src/os/fifo_test.go207
-rw-r--r--src/os/file.go770
-rw-r--r--src/os/file_mutex_plan9.go70
-rw-r--r--src/os/file_open_unix.go17
-rw-r--r--src/os/file_open_wasip1.go31
-rw-r--r--src/os/file_plan9.go620
-rw-r--r--src/os/file_posix.go256
-rw-r--r--src/os/file_unix.go497
-rw-r--r--src/os/file_wasip1.go22
-rw-r--r--src/os/file_windows.go524
-rw-r--r--src/os/getwd.go126
-rw-r--r--src/os/os_test.go3272
-rw-r--r--src/os/os_unix_test.go348
-rw-r--r--src/os/os_windows_test.go1467
-rw-r--r--src/os/path.go79
-rw-r--r--src/os/path_plan9.go19
-rw-r--r--src/os/path_test.go121
-rw-r--r--src/os/path_unix.go75
-rw-r--r--src/os/path_windows.go227
-rw-r--r--src/os/path_windows_test.go108
-rw-r--r--src/os/pipe2_unix.go22
-rw-r--r--src/os/pipe_test.go478
-rw-r--r--src/os/pipe_unix.go28
-rw-r--r--src/os/pipe_wasm.go16
-rw-r--r--src/os/proc.go80
-rw-r--r--src/os/rawconn.go47
-rw-r--r--src/os/rawconn_test.go66
-rw-r--r--src/os/read_test.go138
-rw-r--r--src/os/readfrom_linux.go124
-rw-r--r--src/os/readfrom_linux_test.go822
-rw-r--r--src/os/readfrom_stub.go13
-rw-r--r--src/os/removeall_at.go199
-rw-r--r--src/os/removeall_noat.go142
-rw-r--r--src/os/removeall_test.go506
-rw-r--r--src/os/signal/doc.go232
-rw-r--r--src/os/signal/example_test.go38
-rw-r--r--src/os/signal/example_unix_test.go47
-rw-r--r--src/os/signal/sig.s8
-rw-r--r--src/os/signal/signal.go334
-rw-r--r--src/os/signal/signal_cgo_test.go350
-rw-r--r--src/os/signal/signal_linux_test.go42
-rw-r--r--src/os/signal/signal_plan9.go64
-rw-r--r--src/os/signal/signal_plan9_test.go167
-rw-r--r--src/os/signal/signal_test.go932
-rw-r--r--src/os/signal/signal_unix.go62
-rw-r--r--src/os/signal/signal_windows_test.go98
-rw-r--r--src/os/stat.go23
-rw-r--r--src/os/stat_aix.go51
-rw-r--r--src/os/stat_darwin.go47
-rw-r--r--src/os/stat_dragonfly.go47
-rw-r--r--src/os/stat_freebsd.go47
-rw-r--r--src/os/stat_js.go50
-rw-r--r--src/os/stat_linux.go47
-rw-r--r--src/os/stat_netbsd.go47
-rw-r--r--src/os/stat_openbsd.go47
-rw-r--r--src/os/stat_plan9.go114
-rw-r--r--src/os/stat_solaris.go57
-rw-r--r--src/os/stat_test.go296
-rw-r--r--src/os/stat_unix.go52
-rw-r--r--src/os/stat_wasip1.go40
-rw-r--r--src/os/stat_windows.go136
-rw-r--r--src/os/sticky_bsd.go11
-rw-r--r--src/os/sticky_notbsd.go9
-rw-r--r--src/os/str.go39
-rw-r--r--src/os/sys.go10
-rw-r--r--src/os/sys_aix.go26
-rw-r--r--src/os/sys_bsd.go17
-rw-r--r--src/os/sys_js.go11
-rw-r--r--src/os/sys_linux.go53
-rw-r--r--src/os/sys_plan9.go24
-rw-r--r--src/os/sys_solaris.go11
-rw-r--r--src/os/sys_unix.go14
-rw-r--r--src/os/sys_wasip1.go11
-rw-r--r--src/os/sys_windows.go33
-rw-r--r--src/os/tempfile.go128
-rw-r--r--src/os/tempfile_test.go205
-rw-r--r--src/os/testdata/dirfs/a0
-rw-r--r--src/os/testdata/dirfs/b0
-rw-r--r--src/os/testdata/dirfs/dir/x0
-rw-r--r--src/os/testdata/hello1
-rw-r--r--src/os/testdata/issue37161/a1
-rw-r--r--src/os/testdata/issue37161/b1
-rw-r--r--src/os/testdata/issue37161/c1
-rw-r--r--src/os/timeout_test.go708
-rw-r--r--src/os/types.go74
-rw-r--r--src/os/types_plan9.go30
-rw-r--r--src/os/types_unix.go30
-rw-r--r--src/os/types_windows.go262
-rw-r--r--src/os/user/cgo_listgroups_unix.go57
-rw-r--r--src/os/user/cgo_lookup_cgo.go112
-rw-r--r--src/os/user/cgo_lookup_syscall.go65
-rw-r--r--src/os/user/cgo_lookup_unix.go200
-rw-r--r--src/os/user/cgo_unix_test.go23
-rw-r--r--src/os/user/cgo_user_test.go11
-rw-r--r--src/os/user/getgrouplist_syscall.go19
-rw-r--r--src/os/user/getgrouplist_unix.go22
-rw-r--r--src/os/user/listgroups_stub.go19
-rw-r--r--src/os/user/listgroups_unix.go109
-rw-r--r--src/os/user/listgroups_unix_test.go107
-rw-r--r--src/os/user/lookup.go70
-rw-r--r--src/os/user/lookup_android.go25
-rw-r--r--src/os/user/lookup_plan9.go67
-rw-r--r--src/os/user/lookup_stubs.go83
-rw-r--r--src/os/user/lookup_unix.go234
-rw-r--r--src/os/user/lookup_unix_test.go259
-rw-r--r--src/os/user/lookup_windows.go392
-rw-r--r--src/os/user/user.go95
-rw-r--r--src/os/user/user_test.go192
-rw-r--r--src/os/wait6_dragonfly.go18
-rw-r--r--src/os/wait6_freebsd64.go20
-rw-r--r--src/os/wait6_freebsd_386.go18
-rw-r--r--src/os/wait6_freebsd_arm.go18
-rw-r--r--src/os/wait6_netbsd.go18
-rw-r--r--src/os/wait_unimp.go21
-rw-r--r--src/os/wait_wait6.go32
-rw-r--r--src/os/wait_waitid.go48
193 files changed, 27970 insertions, 0 deletions
diff --git a/src/os/dir.go b/src/os/dir.go
new file mode 100644
index 0000000..5306bcb
--- /dev/null
+++ b/src/os/dir.go
@@ -0,0 +1,125 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "io/fs"
+ "sort"
+)
+
+type readdirMode int
+
+const (
+ readdirName readdirMode = iota
+ readdirDirEntry
+ readdirFileInfo
+)
+
+// Readdir reads the contents of the directory associated with file and
+// returns a slice of up to n FileInfo values, as would be returned
+// by Lstat, in directory order. Subsequent calls on the same file will yield
+// further FileInfos.
+//
+// If n > 0, Readdir returns at most n FileInfo structures. In this case, if
+// Readdir returns an empty slice, it will return a non-nil error
+// explaining why. At the end of a directory, the error is io.EOF.
+//
+// If n <= 0, Readdir returns all the FileInfo from the directory in
+// a single slice. In this case, if Readdir succeeds (reads all
+// the way to the end of the directory), it returns the slice and a
+// nil error. If it encounters an error before the end of the
+// directory, Readdir returns the FileInfo read until that point
+// and a non-nil error.
+//
+// Most clients are better served by the more efficient ReadDir method.
+func (f *File) Readdir(n int) ([]FileInfo, error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ _, _, infos, err := f.readdir(n, readdirFileInfo)
+ if infos == nil {
+ // Readdir has historically always returned a non-nil empty slice, never nil,
+ // even on error (except misuse with nil receiver above).
+ // Keep it that way to avoid breaking overly sensitive callers.
+ infos = []FileInfo{}
+ }
+ return infos, err
+}
+
+// Readdirnames reads the contents of the directory associated with file
+// and returns a slice of up to n names of files in the directory,
+// in directory order. Subsequent calls on the same file will yield
+// further names.
+//
+// If n > 0, Readdirnames returns at most n names. In this case, if
+// Readdirnames returns an empty slice, it will return a non-nil error
+// explaining why. At the end of a directory, the error is io.EOF.
+//
+// If n <= 0, Readdirnames returns all the names from the directory in
+// a single slice. In this case, if Readdirnames succeeds (reads all
+// the way to the end of the directory), it returns the slice and a
+// nil error. If it encounters an error before the end of the
+// directory, Readdirnames returns the names read until that point and
+// a non-nil error.
+func (f *File) Readdirnames(n int) (names []string, err error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ names, _, _, err = f.readdir(n, readdirName)
+ if names == nil {
+ // Readdirnames has historically always returned a non-nil empty slice, never nil,
+ // even on error (except misuse with nil receiver above).
+ // Keep it that way to avoid breaking overly sensitive callers.
+ names = []string{}
+ }
+ return names, err
+}
+
+// A DirEntry is an entry read from a directory
+// (using the ReadDir function or a File's ReadDir method).
+type DirEntry = fs.DirEntry
+
+// ReadDir reads the contents of the directory associated with the file f
+// and returns a slice of DirEntry values in directory order.
+// Subsequent calls on the same file will yield later DirEntry records in the directory.
+//
+// If n > 0, ReadDir returns at most n DirEntry records.
+// In this case, if ReadDir returns an empty slice, it will return an error explaining why.
+// At the end of a directory, the error is io.EOF.
+//
+// If n <= 0, ReadDir returns all the DirEntry records remaining in the directory.
+// When it succeeds, it returns a nil error (not io.EOF).
+func (f *File) ReadDir(n int) ([]DirEntry, error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ _, dirents, _, err := f.readdir(n, readdirDirEntry)
+ if dirents == nil {
+ // Match Readdir and Readdirnames: don't return nil slices.
+ dirents = []DirEntry{}
+ }
+ return dirents, err
+}
+
+// testingForceReadDirLstat forces ReadDir to call Lstat, for testing that code path.
+// This can be difficult to provoke on some Unix systems otherwise.
+var testingForceReadDirLstat bool
+
+// ReadDir reads the named directory,
+// returning all its directory entries sorted by filename.
+// If an error occurs reading the directory,
+// ReadDir returns the entries it was able to read before the error,
+// along with the error.
+func ReadDir(name string) ([]DirEntry, error) {
+ f, err := Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ dirs, err := f.ReadDir(-1)
+ sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
+ return dirs, err
+}
diff --git a/src/os/dir_darwin.go b/src/os/dir_darwin.go
new file mode 100644
index 0000000..e6d5bda
--- /dev/null
+++ b/src/os/dir_darwin.go
@@ -0,0 +1,140 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "io"
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
+// Auxiliary information if the File describes a directory
+type dirInfo struct {
+ dir uintptr // Pointer to DIR structure from dirent.h
+}
+
+func (d *dirInfo) close() {
+ if d.dir == 0 {
+ return
+ }
+ closedir(d.dir)
+ d.dir = 0
+}
+
+func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
+ if f.dirinfo == nil {
+ dir, call, errno := f.pfd.OpenDir()
+ if errno != nil {
+ return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno}
+ }
+ f.dirinfo = &dirInfo{
+ dir: dir,
+ }
+ }
+ d := f.dirinfo
+
+ size := n
+ if size <= 0 {
+ size = 100
+ n = -1
+ }
+
+ var dirent syscall.Dirent
+ var entptr *syscall.Dirent
+ for len(names)+len(dirents)+len(infos) < size || n == -1 {
+ if errno := readdir_r(d.dir, &dirent, &entptr); errno != 0 {
+ if errno == syscall.EINTR {
+ continue
+ }
+ return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno}
+ }
+ if entptr == nil { // EOF
+ break
+ }
+ // Darwin may return a zero inode when a directory entry has been
+ // deleted but not yet removed from the directory. The man page for
+ // getdirentries(2) states that programs are responsible for skipping
+ // those entries:
+ //
+ // Users of getdirentries() should skip entries with d_fileno = 0,
+ // as such entries represent files which have been deleted but not
+ // yet removed from the directory entry.
+ //
+ if dirent.Ino == 0 {
+ continue
+ }
+ name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:]
+ for i, c := range name {
+ if c == 0 {
+ name = name[:i]
+ break
+ }
+ }
+ // Check for useless names before allocating a string.
+ if string(name) == "." || string(name) == ".." {
+ continue
+ }
+ if mode == readdirName {
+ names = append(names, string(name))
+ } else if mode == readdirDirEntry {
+ de, err := newUnixDirent(f.name, string(name), dtToType(dirent.Type))
+ if IsNotExist(err) {
+ // File disappeared between readdir and stat.
+ // Treat as if it didn't exist.
+ continue
+ }
+ if err != nil {
+ return nil, dirents, nil, err
+ }
+ dirents = append(dirents, de)
+ } else {
+ info, err := lstat(f.name + "/" + string(name))
+ if IsNotExist(err) {
+ // File disappeared between readdir + stat.
+ // Treat as if it didn't exist.
+ continue
+ }
+ if err != nil {
+ return nil, nil, infos, err
+ }
+ infos = append(infos, info)
+ }
+ runtime.KeepAlive(f)
+ }
+
+ if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
+ return nil, nil, nil, io.EOF
+ }
+ return names, dirents, infos, nil
+}
+
+func dtToType(typ uint8) FileMode {
+ switch typ {
+ case syscall.DT_BLK:
+ return ModeDevice
+ case syscall.DT_CHR:
+ return ModeDevice | ModeCharDevice
+ case syscall.DT_DIR:
+ return ModeDir
+ case syscall.DT_FIFO:
+ return ModeNamedPipe
+ case syscall.DT_LNK:
+ return ModeSymlink
+ case syscall.DT_REG:
+ return 0
+ case syscall.DT_SOCK:
+ return ModeSocket
+ }
+ return ^FileMode(0)
+}
+
+// Implemented in syscall/syscall_darwin.go.
+
+//go:linkname closedir syscall.closedir
+func closedir(dir uintptr) (err error)
+
+//go:linkname readdir_r syscall.readdir_r
+func readdir_r(dir uintptr, entry *syscall.Dirent, result **syscall.Dirent) (res syscall.Errno)
diff --git a/src/os/dir_plan9.go b/src/os/dir_plan9.go
new file mode 100644
index 0000000..6ea5940
--- /dev/null
+++ b/src/os/dir_plan9.go
@@ -0,0 +1,86 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "io"
+ "io/fs"
+ "syscall"
+)
+
+func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
+ // If this file has no dirinfo, create one.
+ if file.dirinfo == nil {
+ file.dirinfo = new(dirInfo)
+ }
+ d := file.dirinfo
+ size := n
+ if size <= 0 {
+ size = 100
+ n = -1
+ }
+ for n != 0 {
+ // Refill the buffer if necessary.
+ if d.bufp >= d.nbuf {
+ nb, err := file.Read(d.buf[:])
+
+ // Update the buffer state before checking for errors.
+ d.bufp, d.nbuf = 0, nb
+
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: err}
+ }
+ if nb < syscall.STATFIXLEN {
+ return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: syscall.ErrShortStat}
+ }
+ }
+
+ // Get a record from the buffer.
+ b := d.buf[d.bufp:]
+ m := int(uint16(b[0])|uint16(b[1])<<8) + 2
+ if m < syscall.STATFIXLEN {
+ return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: syscall.ErrShortStat}
+ }
+
+ dir, err := syscall.UnmarshalDir(b[:m])
+ if err != nil {
+ return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: err}
+ }
+
+ if mode == readdirName {
+ names = append(names, dir.Name)
+ } else {
+ f := fileInfoFromStat(dir)
+ if mode == readdirDirEntry {
+ dirents = append(dirents, dirEntry{f})
+ } else {
+ infos = append(infos, f)
+ }
+ }
+ d.bufp += m
+ n--
+ }
+
+ if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
+ return nil, nil, nil, io.EOF
+ }
+ return names, dirents, infos, nil
+}
+
+type dirEntry struct {
+ fs *fileStat
+}
+
+func (de dirEntry) Name() string { return de.fs.Name() }
+func (de dirEntry) IsDir() bool { return de.fs.IsDir() }
+func (de dirEntry) Type() FileMode { return de.fs.Mode().Type() }
+func (de dirEntry) Info() (FileInfo, error) { return de.fs, nil }
+
+func (de dirEntry) String() string {
+ return fs.FormatDirEntry(de)
+}
diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go
new file mode 100644
index 0000000..266a78a
--- /dev/null
+++ b/src/os/dir_unix.go
@@ -0,0 +1,198 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build aix || dragonfly || freebsd || (js && wasm) || wasip1 || linux || netbsd || openbsd || solaris
+
+package os
+
+import (
+ "io"
+ "runtime"
+ "sync"
+ "syscall"
+ "unsafe"
+)
+
+// Auxiliary information if the File describes a directory
+type dirInfo struct {
+ buf *[]byte // buffer for directory I/O
+ nbuf int // length of buf; return value from Getdirentries
+ bufp int // location of next record in buf.
+}
+
+const (
+ // More than 5760 to work around https://golang.org/issue/24015.
+ blockSize = 8192
+)
+
+var dirBufPool = sync.Pool{
+ New: func() any {
+ // The buffer must be at least a block long.
+ buf := make([]byte, blockSize)
+ return &buf
+ },
+}
+
+func (d *dirInfo) close() {
+ if d.buf != nil {
+ dirBufPool.Put(d.buf)
+ d.buf = nil
+ }
+}
+
+func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
+ // If this file has no dirinfo, create one.
+ if f.dirinfo == nil {
+ f.dirinfo = new(dirInfo)
+ f.dirinfo.buf = dirBufPool.Get().(*[]byte)
+ }
+ d := f.dirinfo
+
+ // Change the meaning of n for the implementation below.
+ //
+ // The n above was for the public interface of "if n <= 0,
+ // Readdir returns all the FileInfo from the directory in a
+ // single slice".
+ //
+ // But below, we use only negative to mean looping until the
+ // end and positive to mean bounded, with positive
+ // terminating at 0.
+ if n == 0 {
+ n = -1
+ }
+
+ for n != 0 {
+ // Refill the buffer if necessary
+ if d.bufp >= d.nbuf {
+ d.bufp = 0
+ var errno error
+ d.nbuf, errno = f.pfd.ReadDirent(*d.buf)
+ runtime.KeepAlive(f)
+ if errno != nil {
+ return names, dirents, infos, &PathError{Op: "readdirent", Path: f.name, Err: errno}
+ }
+ if d.nbuf <= 0 {
+ break // EOF
+ }
+ }
+
+ // Drain the buffer
+ buf := (*d.buf)[d.bufp:d.nbuf]
+ reclen, ok := direntReclen(buf)
+ if !ok || reclen > uint64(len(buf)) {
+ break
+ }
+ rec := buf[:reclen]
+ d.bufp += int(reclen)
+ ino, ok := direntIno(rec)
+ if !ok {
+ break
+ }
+ // When building to wasip1, the host runtime might be running on Windows
+ // or might expose a remote file system which does not have the concept
+ // of inodes. Therefore, we cannot make the assumption that it is safe
+ // to skip entries with zero inodes.
+ if ino == 0 && runtime.GOOS != "wasip1" {
+ continue
+ }
+ const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name))
+ namlen, ok := direntNamlen(rec)
+ if !ok || namoff+namlen > uint64(len(rec)) {
+ break
+ }
+ name := rec[namoff : namoff+namlen]
+ for i, c := range name {
+ if c == 0 {
+ name = name[:i]
+ break
+ }
+ }
+ // Check for useless names before allocating a string.
+ if string(name) == "." || string(name) == ".." {
+ continue
+ }
+ if n > 0 { // see 'n == 0' comment above
+ n--
+ }
+ if mode == readdirName {
+ names = append(names, string(name))
+ } else if mode == readdirDirEntry {
+ de, err := newUnixDirent(f.name, string(name), direntType(rec))
+ if IsNotExist(err) {
+ // File disappeared between readdir and stat.
+ // Treat as if it didn't exist.
+ continue
+ }
+ if err != nil {
+ return nil, dirents, nil, err
+ }
+ dirents = append(dirents, de)
+ } else {
+ info, err := lstat(f.name + "/" + string(name))
+ if IsNotExist(err) {
+ // File disappeared between readdir + stat.
+ // Treat as if it didn't exist.
+ continue
+ }
+ if err != nil {
+ return nil, nil, infos, err
+ }
+ infos = append(infos, info)
+ }
+ }
+
+ if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
+ return nil, nil, nil, io.EOF
+ }
+ return names, dirents, infos, nil
+}
+
+// readInt returns the size-bytes unsigned integer in native byte order at offset off.
+func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
+ if len(b) < int(off+size) {
+ return 0, false
+ }
+ if isBigEndian {
+ return readIntBE(b[off:], size), true
+ }
+ return readIntLE(b[off:], size), true
+}
+
+func readIntBE(b []byte, size uintptr) uint64 {
+ switch size {
+ case 1:
+ return uint64(b[0])
+ case 2:
+ _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
+ return uint64(b[1]) | uint64(b[0])<<8
+ case 4:
+ _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
+ return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24
+ case 8:
+ _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
+ return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
+ uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
+ default:
+ panic("syscall: readInt with unsupported size")
+ }
+}
+
+func readIntLE(b []byte, size uintptr) uint64 {
+ switch size {
+ case 1:
+ return uint64(b[0])
+ case 2:
+ _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
+ return uint64(b[0]) | uint64(b[1])<<8
+ case 4:
+ _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
+ return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24
+ case 8:
+ _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
+ return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
+ uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
+ default:
+ panic("syscall: readInt with unsupported size")
+ }
+}
diff --git a/src/os/dir_windows.go b/src/os/dir_windows.go
new file mode 100644
index 0000000..9dc2cd7
--- /dev/null
+++ b/src/os/dir_windows.go
@@ -0,0 +1,80 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "io"
+ "io/fs"
+ "runtime"
+ "syscall"
+)
+
+func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
+ // If this file has no dirinfo, create one.
+ needdata := true
+ if file.dirinfo == nil {
+ needdata = false
+ file.dirinfo, err = openDir(file.name)
+ if err != nil {
+ err = &PathError{Op: "readdir", Path: file.name, Err: err}
+ return
+ }
+ }
+ wantAll := n <= 0
+ if wantAll {
+ n = -1
+ }
+ d := &file.dirinfo.data
+ for n != 0 && !file.dirinfo.isempty {
+ if needdata {
+ e := syscall.FindNextFile(file.dirinfo.h, d)
+ runtime.KeepAlive(file)
+ if e != nil {
+ if e == syscall.ERROR_NO_MORE_FILES {
+ break
+ } else {
+ err = &PathError{Op: "FindNextFile", Path: file.name, Err: e}
+ return
+ }
+ }
+ }
+ needdata = true
+ name := syscall.UTF16ToString(d.FileName[0:])
+ if name == "." || name == ".." { // Useless names
+ continue
+ }
+ if mode == readdirName {
+ names = append(names, name)
+ } else {
+ f := newFileStatFromWin32finddata(d)
+ f.name = name
+ f.path = file.dirinfo.path
+ f.appendNameToPath = true
+ if mode == readdirDirEntry {
+ dirents = append(dirents, dirEntry{f})
+ } else {
+ infos = append(infos, f)
+ }
+ }
+ n--
+ }
+ if !wantAll && len(names)+len(dirents)+len(infos) == 0 {
+ return nil, nil, nil, io.EOF
+ }
+ return names, dirents, infos, nil
+}
+
+type dirEntry struct {
+ fs *fileStat
+}
+
+func (de dirEntry) Name() string { return de.fs.Name() }
+func (de dirEntry) IsDir() bool { return de.fs.IsDir() }
+func (de dirEntry) Type() FileMode { return de.fs.Mode().Type() }
+func (de dirEntry) Info() (FileInfo, error) { return de.fs, nil }
+
+func (de dirEntry) String() string {
+ return fs.FormatDirEntry(de)
+}
diff --git a/src/os/dirent_aix.go b/src/os/dirent_aix.go
new file mode 100644
index 0000000..5597b8a
--- /dev/null
+++ b/src/os/dirent_aix.go
@@ -0,0 +1,30 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func direntIno(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino))
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ reclen, ok := direntReclen(buf)
+ if !ok {
+ return 0, false
+ }
+ return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true
+}
+
+func direntType(buf []byte) FileMode {
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/dirent_dragonfly.go b/src/os/dirent_dragonfly.go
new file mode 100644
index 0000000..38cbd61
--- /dev/null
+++ b/src/os/dirent_dragonfly.go
@@ -0,0 +1,55 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func direntIno(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Fileno), unsafe.Sizeof(syscall.Dirent{}.Fileno))
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ namlen, ok := direntNamlen(buf)
+ if !ok {
+ return 0, false
+ }
+ return (16 + namlen + 1 + 7) &^ 7, true
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen))
+}
+
+func direntType(buf []byte) FileMode {
+ off := unsafe.Offsetof(syscall.Dirent{}.Type)
+ if off >= uintptr(len(buf)) {
+ return ^FileMode(0) // unknown
+ }
+ typ := buf[off]
+ switch typ {
+ case syscall.DT_BLK:
+ return ModeDevice
+ case syscall.DT_CHR:
+ return ModeDevice | ModeCharDevice
+ case syscall.DT_DBF:
+ // DT_DBF is "database record file".
+ // fillFileStatFromSys treats as regular file.
+ return 0
+ case syscall.DT_DIR:
+ return ModeDir
+ case syscall.DT_FIFO:
+ return ModeNamedPipe
+ case syscall.DT_LNK:
+ return ModeSymlink
+ case syscall.DT_REG:
+ return 0
+ case syscall.DT_SOCK:
+ return ModeSocket
+ }
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/dirent_freebsd.go b/src/os/dirent_freebsd.go
new file mode 100644
index 0000000..d600837
--- /dev/null
+++ b/src/os/dirent_freebsd.go
@@ -0,0 +1,47 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func direntIno(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Fileno), unsafe.Sizeof(syscall.Dirent{}.Fileno))
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen))
+}
+
+func direntType(buf []byte) FileMode {
+ off := unsafe.Offsetof(syscall.Dirent{}.Type)
+ if off >= uintptr(len(buf)) {
+ return ^FileMode(0) // unknown
+ }
+ typ := buf[off]
+ switch typ {
+ case syscall.DT_BLK:
+ return ModeDevice
+ case syscall.DT_CHR:
+ return ModeDevice | ModeCharDevice
+ case syscall.DT_DIR:
+ return ModeDir
+ case syscall.DT_FIFO:
+ return ModeNamedPipe
+ case syscall.DT_LNK:
+ return ModeSymlink
+ case syscall.DT_REG:
+ return 0
+ case syscall.DT_SOCK:
+ return ModeSocket
+ }
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/dirent_js.go b/src/os/dirent_js.go
new file mode 100644
index 0000000..31778c2
--- /dev/null
+++ b/src/os/dirent_js.go
@@ -0,0 +1,30 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func direntIno(buf []byte) (uint64, bool) {
+ return 1, true
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ reclen, ok := direntReclen(buf)
+ if !ok {
+ return 0, false
+ }
+ return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true
+}
+
+func direntType(buf []byte) FileMode {
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/dirent_linux.go b/src/os/dirent_linux.go
new file mode 100644
index 0000000..74a3431
--- /dev/null
+++ b/src/os/dirent_linux.go
@@ -0,0 +1,51 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func direntIno(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino))
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ reclen, ok := direntReclen(buf)
+ if !ok {
+ return 0, false
+ }
+ return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true
+}
+
+func direntType(buf []byte) FileMode {
+ off := unsafe.Offsetof(syscall.Dirent{}.Type)
+ if off >= uintptr(len(buf)) {
+ return ^FileMode(0) // unknown
+ }
+ typ := buf[off]
+ switch typ {
+ case syscall.DT_BLK:
+ return ModeDevice
+ case syscall.DT_CHR:
+ return ModeDevice | ModeCharDevice
+ case syscall.DT_DIR:
+ return ModeDir
+ case syscall.DT_FIFO:
+ return ModeNamedPipe
+ case syscall.DT_LNK:
+ return ModeSymlink
+ case syscall.DT_REG:
+ return 0
+ case syscall.DT_SOCK:
+ return ModeSocket
+ }
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/dirent_netbsd.go b/src/os/dirent_netbsd.go
new file mode 100644
index 0000000..d600837
--- /dev/null
+++ b/src/os/dirent_netbsd.go
@@ -0,0 +1,47 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func direntIno(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Fileno), unsafe.Sizeof(syscall.Dirent{}.Fileno))
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen))
+}
+
+func direntType(buf []byte) FileMode {
+ off := unsafe.Offsetof(syscall.Dirent{}.Type)
+ if off >= uintptr(len(buf)) {
+ return ^FileMode(0) // unknown
+ }
+ typ := buf[off]
+ switch typ {
+ case syscall.DT_BLK:
+ return ModeDevice
+ case syscall.DT_CHR:
+ return ModeDevice | ModeCharDevice
+ case syscall.DT_DIR:
+ return ModeDir
+ case syscall.DT_FIFO:
+ return ModeNamedPipe
+ case syscall.DT_LNK:
+ return ModeSymlink
+ case syscall.DT_REG:
+ return 0
+ case syscall.DT_SOCK:
+ return ModeSocket
+ }
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/dirent_openbsd.go b/src/os/dirent_openbsd.go
new file mode 100644
index 0000000..d600837
--- /dev/null
+++ b/src/os/dirent_openbsd.go
@@ -0,0 +1,47 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func direntIno(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Fileno), unsafe.Sizeof(syscall.Dirent{}.Fileno))
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen))
+}
+
+func direntType(buf []byte) FileMode {
+ off := unsafe.Offsetof(syscall.Dirent{}.Type)
+ if off >= uintptr(len(buf)) {
+ return ^FileMode(0) // unknown
+ }
+ typ := buf[off]
+ switch typ {
+ case syscall.DT_BLK:
+ return ModeDevice
+ case syscall.DT_CHR:
+ return ModeDevice | ModeCharDevice
+ case syscall.DT_DIR:
+ return ModeDir
+ case syscall.DT_FIFO:
+ return ModeNamedPipe
+ case syscall.DT_LNK:
+ return ModeSymlink
+ case syscall.DT_REG:
+ return 0
+ case syscall.DT_SOCK:
+ return ModeSocket
+ }
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/dirent_solaris.go b/src/os/dirent_solaris.go
new file mode 100644
index 0000000..5597b8a
--- /dev/null
+++ b/src/os/dirent_solaris.go
@@ -0,0 +1,30 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func direntIno(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino))
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ reclen, ok := direntReclen(buf)
+ if !ok {
+ return 0, false
+ }
+ return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true
+}
+
+func direntType(buf []byte) FileMode {
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/dirent_wasip1.go b/src/os/dirent_wasip1.go
new file mode 100644
index 0000000..d3f10b2
--- /dev/null
+++ b/src/os/dirent_wasip1.go
@@ -0,0 +1,52 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasip1
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-dirent-record
+const sizeOfDirent = 24
+
+func direntIno(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino))
+}
+
+func direntReclen(buf []byte) (uint64, bool) {
+ namelen, ok := direntNamlen(buf)
+ return sizeOfDirent + namelen, ok
+}
+
+func direntNamlen(buf []byte) (uint64, bool) {
+ return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen))
+}
+
+func direntType(buf []byte) FileMode {
+ off := unsafe.Offsetof(syscall.Dirent{}.Type)
+ if off >= uintptr(len(buf)) {
+ return ^FileMode(0) // unknown
+ }
+ switch syscall.Filetype(buf[off]) {
+ case syscall.FILETYPE_BLOCK_DEVICE:
+ return ModeDevice
+ case syscall.FILETYPE_CHARACTER_DEVICE:
+ return ModeDevice | ModeCharDevice
+ case syscall.FILETYPE_DIRECTORY:
+ return ModeDir
+ case syscall.FILETYPE_REGULAR_FILE:
+ return 0
+ case syscall.FILETYPE_SOCKET_DGRAM:
+ return ModeSocket
+ case syscall.FILETYPE_SOCKET_STREAM:
+ return ModeSocket
+ case syscall.FILETYPE_SYMBOLIC_LINK:
+ return ModeSymlink
+ }
+ return ^FileMode(0) // unknown
+}
diff --git a/src/os/endian_big.go b/src/os/endian_big.go
new file mode 100644
index 0000000..0375e53
--- /dev/null
+++ b/src/os/endian_big.go
@@ -0,0 +1,9 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+//go:build ppc64 || s390x || mips || mips64
+
+package os
+
+const isBigEndian = true
diff --git a/src/os/endian_little.go b/src/os/endian_little.go
new file mode 100644
index 0000000..a7cf1cd
--- /dev/null
+++ b/src/os/endian_little.go
@@ -0,0 +1,9 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+//go:build 386 || amd64 || arm || arm64 || loong64 || ppc64le || mips64le || mipsle || riscv64 || wasm
+
+package os
+
+const isBigEndian = false
diff --git a/src/os/env.go b/src/os/env.go
new file mode 100644
index 0000000..63ad5ab
--- /dev/null
+++ b/src/os/env.go
@@ -0,0 +1,141 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// General environment variables.
+
+package os
+
+import (
+ "internal/testlog"
+ "syscall"
+)
+
+// Expand replaces ${var} or $var in the string based on the mapping function.
+// For example, os.ExpandEnv(s) is equivalent to os.Expand(s, os.Getenv).
+func Expand(s string, mapping func(string) string) string {
+ var buf []byte
+ // ${} is all ASCII, so bytes are fine for this operation.
+ i := 0
+ for j := 0; j < len(s); j++ {
+ if s[j] == '$' && j+1 < len(s) {
+ if buf == nil {
+ buf = make([]byte, 0, 2*len(s))
+ }
+ buf = append(buf, s[i:j]...)
+ name, w := getShellName(s[j+1:])
+ if name == "" && w > 0 {
+ // Encountered invalid syntax; eat the
+ // characters.
+ } else if name == "" {
+ // Valid syntax, but $ was not followed by a
+ // name. Leave the dollar character untouched.
+ buf = append(buf, s[j])
+ } else {
+ buf = append(buf, mapping(name)...)
+ }
+ j += w
+ i = j + 1
+ }
+ }
+ if buf == nil {
+ return s
+ }
+ return string(buf) + s[i:]
+}
+
+// ExpandEnv replaces ${var} or $var in the string according to the values
+// of the current environment variables. References to undefined
+// variables are replaced by the empty string.
+func ExpandEnv(s string) string {
+ return Expand(s, Getenv)
+}
+
+// isShellSpecialVar reports whether the character identifies a special
+// shell variable such as $*.
+func isShellSpecialVar(c uint8) bool {
+ switch c {
+ case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ return true
+ }
+ return false
+}
+
+// isAlphaNum reports whether the byte is an ASCII letter, number, or underscore.
+func isAlphaNum(c uint8) bool {
+ return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
+}
+
+// getShellName returns the name that begins the string and the number of bytes
+// consumed to extract it. If the name is enclosed in {}, it's part of a ${}
+// expansion and two more bytes are needed than the length of the name.
+func getShellName(s string) (string, int) {
+ switch {
+ case s[0] == '{':
+ if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == '}' {
+ return s[1:2], 3
+ }
+ // Scan to closing brace
+ for i := 1; i < len(s); i++ {
+ if s[i] == '}' {
+ if i == 1 {
+ return "", 2 // Bad syntax; eat "${}"
+ }
+ return s[1:i], i + 1
+ }
+ }
+ return "", 1 // Bad syntax; eat "${"
+ case isShellSpecialVar(s[0]):
+ return s[0:1], 1
+ }
+ // Scan alphanumerics.
+ var i int
+ for i = 0; i < len(s) && isAlphaNum(s[i]); i++ {
+ }
+ return s[:i], i
+}
+
+// Getenv retrieves the value of the environment variable named by the key.
+// It returns the value, which will be empty if the variable is not present.
+// To distinguish between an empty value and an unset value, use LookupEnv.
+func Getenv(key string) string {
+ testlog.Getenv(key)
+ v, _ := syscall.Getenv(key)
+ return v
+}
+
+// LookupEnv retrieves the value of the environment variable named
+// by the key. If the variable is present in the environment the
+// value (which may be empty) is returned and the boolean is true.
+// Otherwise the returned value will be empty and the boolean will
+// be false.
+func LookupEnv(key string) (string, bool) {
+ testlog.Getenv(key)
+ return syscall.Getenv(key)
+}
+
+// Setenv sets the value of the environment variable named by the key.
+// It returns an error, if any.
+func Setenv(key, value string) error {
+ err := syscall.Setenv(key, value)
+ if err != nil {
+ return NewSyscallError("setenv", err)
+ }
+ return nil
+}
+
+// Unsetenv unsets a single environment variable.
+func Unsetenv(key string) error {
+ return syscall.Unsetenv(key)
+}
+
+// Clearenv deletes all environment variables.
+func Clearenv() {
+ syscall.Clearenv()
+}
+
+// Environ returns a copy of strings representing the environment,
+// in the form "key=value".
+func Environ() []string {
+ return syscall.Environ()
+}
diff --git a/src/os/env_test.go b/src/os/env_test.go
new file mode 100644
index 0000000..5809f4b
--- /dev/null
+++ b/src/os/env_test.go
@@ -0,0 +1,206 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ . "os"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+// testGetenv gives us a controlled set of variables for testing Expand.
+func testGetenv(s string) string {
+ switch s {
+ case "*":
+ return "all the args"
+ case "#":
+ return "NARGS"
+ case "$":
+ return "PID"
+ case "1":
+ return "ARGUMENT1"
+ case "HOME":
+ return "/usr/gopher"
+ case "H":
+ return "(Value of H)"
+ case "home_1":
+ return "/usr/foo"
+ case "_":
+ return "underscore"
+ }
+ return ""
+}
+
+var expandTests = []struct {
+ in, out string
+}{
+ {"", ""},
+ {"$*", "all the args"},
+ {"$$", "PID"},
+ {"${*}", "all the args"},
+ {"$1", "ARGUMENT1"},
+ {"${1}", "ARGUMENT1"},
+ {"now is the time", "now is the time"},
+ {"$HOME", "/usr/gopher"},
+ {"$home_1", "/usr/foo"},
+ {"${HOME}", "/usr/gopher"},
+ {"${H}OME", "(Value of H)OME"},
+ {"A$$$#$1$H$home_1*B", "APIDNARGSARGUMENT1(Value of H)/usr/foo*B"},
+ {"start$+middle$^end$", "start$+middle$^end$"},
+ {"mixed$|bag$$$", "mixed$|bagPID$"},
+ {"$", "$"},
+ {"$}", "$}"},
+ {"${", ""}, // invalid syntax; eat up the characters
+ {"${}", ""}, // invalid syntax; eat up the characters
+}
+
+func TestExpand(t *testing.T) {
+ for _, test := range expandTests {
+ result := Expand(test.in, testGetenv)
+ if result != test.out {
+ t.Errorf("Expand(%q)=%q; expected %q", test.in, result, test.out)
+ }
+ }
+}
+
+var global any
+
+func BenchmarkExpand(b *testing.B) {
+ b.Run("noop", func(b *testing.B) {
+ var s string
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ s = Expand("tick tick tick tick", func(string) string { return "" })
+ }
+ global = s
+ })
+ b.Run("multiple", func(b *testing.B) {
+ var s string
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ s = Expand("$a $a $a $a", func(string) string { return "boom" })
+ }
+ global = s
+ })
+}
+
+func TestConsistentEnviron(t *testing.T) {
+ e0 := Environ()
+ for i := 0; i < 10; i++ {
+ e1 := Environ()
+ if !reflect.DeepEqual(e0, e1) {
+ t.Fatalf("environment changed")
+ }
+ }
+}
+
+func TestUnsetenv(t *testing.T) {
+ const testKey = "GO_TEST_UNSETENV"
+ set := func() bool {
+ prefix := testKey + "="
+ for _, key := range Environ() {
+ if strings.HasPrefix(key, prefix) {
+ return true
+ }
+ }
+ return false
+ }
+ if err := Setenv(testKey, "1"); err != nil {
+ t.Fatalf("Setenv: %v", err)
+ }
+ if !set() {
+ t.Error("Setenv didn't set TestUnsetenv")
+ }
+ if err := Unsetenv(testKey); err != nil {
+ t.Fatalf("Unsetenv: %v", err)
+ }
+ if set() {
+ t.Fatal("Unsetenv didn't clear TestUnsetenv")
+ }
+}
+
+func TestClearenv(t *testing.T) {
+ const testKey = "GO_TEST_CLEARENV"
+ const testValue = "1"
+
+ // reset env
+ defer func(origEnv []string) {
+ for _, pair := range origEnv {
+ // Environment variables on Windows can begin with =
+ // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
+ i := strings.Index(pair[1:], "=") + 1
+ if err := Setenv(pair[:i], pair[i+1:]); err != nil {
+ t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i], pair[i+1:], err)
+ }
+ }
+ }(Environ())
+
+ if err := Setenv(testKey, testValue); err != nil {
+ t.Fatalf("Setenv(%q, %q) failed: %v", testKey, testValue, err)
+ }
+ if _, ok := LookupEnv(testKey); !ok {
+ t.Errorf("Setenv(%q, %q) didn't set $%s", testKey, testValue, testKey)
+ }
+ Clearenv()
+ if val, ok := LookupEnv(testKey); ok {
+ t.Errorf("Clearenv() didn't clear $%s, remained with value %q", testKey, val)
+ }
+}
+
+func TestLookupEnv(t *testing.T) {
+ const smallpox = "SMALLPOX" // No one has smallpox.
+ value, ok := LookupEnv(smallpox) // Should not exist.
+ if ok || value != "" {
+ t.Fatalf("%s=%q", smallpox, value)
+ }
+ defer Unsetenv(smallpox)
+ err := Setenv(smallpox, "virus")
+ if err != nil {
+ t.Fatalf("failed to release smallpox virus")
+ }
+ _, ok = LookupEnv(smallpox)
+ if !ok {
+ t.Errorf("smallpox release failed; world remains safe but LookupEnv is broken")
+ }
+}
+
+// On Windows, Environ was observed to report keys with a single leading "=".
+// Check that they are properly reported by LookupEnv and can be set by SetEnv.
+// See https://golang.org/issue/49886.
+func TestEnvironConsistency(t *testing.T) {
+ t.Parallel()
+
+ for _, kv := range Environ() {
+ i := strings.Index(kv, "=")
+ if i == 0 {
+ // We observe in practice keys with a single leading "=" on Windows.
+ // TODO(#49886): Should we consume only the first leading "=" as part
+ // of the key, or parse through arbitrarily many of them until a non-=,
+ // or try each possible key/value boundary until LookupEnv succeeds?
+ i = strings.Index(kv[1:], "=") + 1
+ }
+ if i < 0 {
+ t.Errorf("Environ entry missing '=': %q", kv)
+ }
+
+ k := kv[:i]
+ v := kv[i+1:]
+ v2, ok := LookupEnv(k)
+ if ok && v == v2 {
+ t.Logf("LookupEnv(%q) = %q, %t", k, v2, ok)
+ } else {
+ t.Errorf("Environ contains %q, but LookupEnv(%q) = %q, %t", kv, k, v2, ok)
+ }
+
+ // Since k=v is already present in the environment,
+ // setting it should be a no-op.
+ if err := Setenv(k, v); err == nil {
+ t.Logf("Setenv(%q, %q)", k, v)
+ } else {
+ t.Errorf("Environ contains %q, but SetEnv(%q, %q) = %q", kv, k, v, err)
+ }
+ }
+}
diff --git a/src/os/env_unix_test.go b/src/os/env_unix_test.go
new file mode 100644
index 0000000..4609fc3
--- /dev/null
+++ b/src/os/env_unix_test.go
@@ -0,0 +1,56 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package os_test
+
+import (
+ "fmt"
+ . "os"
+ "testing"
+)
+
+var setenvEinvalTests = []struct {
+ k, v string
+}{
+ {"", ""}, // empty key
+ {"k=v", ""}, // '=' in key
+ {"\x00", ""}, // '\x00' in key
+ {"k", "\x00"}, // '\x00' in value
+}
+
+func TestSetenvUnixEinval(t *testing.T) {
+ for _, tt := range setenvEinvalTests {
+ err := Setenv(tt.k, tt.v)
+ if err == nil {
+ t.Errorf(`Setenv(%q, %q) == nil, want error`, tt.k, tt.v)
+ }
+ }
+}
+
+var shellSpecialVarTests = []struct {
+ k, v string
+}{
+ {"*", "asterisk"},
+ {"#", "pound"},
+ {"$", "dollar"},
+ {"@", "at"},
+ {"!", "exclamation mark"},
+ {"?", "question mark"},
+ {"-", "dash"},
+}
+
+func TestExpandEnvShellSpecialVar(t *testing.T) {
+ for _, tt := range shellSpecialVarTests {
+ Setenv(tt.k, tt.v)
+ defer Unsetenv(tt.k)
+
+ argRaw := fmt.Sprintf("$%s", tt.k)
+ argWithBrace := fmt.Sprintf("${%s}", tt.k)
+ if gotRaw, gotBrace := ExpandEnv(argRaw), ExpandEnv(argWithBrace); gotRaw != gotBrace {
+ t.Errorf("ExpandEnv(%q) = %q, ExpandEnv(%q) = %q; expect them to be equal", argRaw, gotRaw, argWithBrace, gotBrace)
+ }
+ }
+}
diff --git a/src/os/error.go b/src/os/error.go
new file mode 100644
index 0000000..62ede9d
--- /dev/null
+++ b/src/os/error.go
@@ -0,0 +1,141 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "internal/poll"
+ "io/fs"
+)
+
+// Portable analogs of some common system call errors.
+//
+// Errors returned from this package may be tested against these errors
+// with errors.Is.
+var (
+ // ErrInvalid indicates an invalid argument.
+ // Methods on File will return this error when the receiver is nil.
+ ErrInvalid = fs.ErrInvalid // "invalid argument"
+
+ ErrPermission = fs.ErrPermission // "permission denied"
+ ErrExist = fs.ErrExist // "file already exists"
+ ErrNotExist = fs.ErrNotExist // "file does not exist"
+ ErrClosed = fs.ErrClosed // "file already closed"
+
+ ErrNoDeadline = errNoDeadline() // "file type does not support deadline"
+ ErrDeadlineExceeded = errDeadlineExceeded() // "i/o timeout"
+)
+
+func errNoDeadline() error { return poll.ErrNoDeadline }
+
+// errDeadlineExceeded returns the value for os.ErrDeadlineExceeded.
+// This error comes from the internal/poll package, which is also
+// used by package net. Doing it this way ensures that the net
+// package will return os.ErrDeadlineExceeded for an exceeded deadline,
+// as documented by net.Conn.SetDeadline, without requiring any extra
+// work in the net package and without requiring the internal/poll
+// package to import os (which it can't, because that would be circular).
+func errDeadlineExceeded() error { return poll.ErrDeadlineExceeded }
+
+type timeout interface {
+ Timeout() bool
+}
+
+// PathError records an error and the operation and file path that caused it.
+type PathError = fs.PathError
+
+// SyscallError records an error from a specific system call.
+type SyscallError struct {
+ Syscall string
+ Err error
+}
+
+func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }
+
+func (e *SyscallError) Unwrap() error { return e.Err }
+
+// Timeout reports whether this error represents a timeout.
+func (e *SyscallError) Timeout() bool {
+ t, ok := e.Err.(timeout)
+ return ok && t.Timeout()
+}
+
+// NewSyscallError returns, as an error, a new SyscallError
+// with the given system call name and error details.
+// As a convenience, if err is nil, NewSyscallError returns nil.
+func NewSyscallError(syscall string, err error) error {
+ if err == nil {
+ return nil
+ }
+ return &SyscallError{syscall, err}
+}
+
+// IsExist returns a boolean indicating whether the error is known to report
+// that a file or directory already exists. It is satisfied by ErrExist as
+// well as some syscall errors.
+//
+// This function predates errors.Is. It only supports errors returned by
+// the os package. New code should use errors.Is(err, fs.ErrExist).
+func IsExist(err error) bool {
+ return underlyingErrorIs(err, ErrExist)
+}
+
+// IsNotExist returns a boolean indicating whether the error is known to
+// report that a file or directory does not exist. It is satisfied by
+// ErrNotExist as well as some syscall errors.
+//
+// This function predates errors.Is. It only supports errors returned by
+// the os package. New code should use errors.Is(err, fs.ErrNotExist).
+func IsNotExist(err error) bool {
+ return underlyingErrorIs(err, ErrNotExist)
+}
+
+// IsPermission returns a boolean indicating whether the error is known to
+// report that permission is denied. It is satisfied by ErrPermission as well
+// as some syscall errors.
+//
+// This function predates errors.Is. It only supports errors returned by
+// the os package. New code should use errors.Is(err, fs.ErrPermission).
+func IsPermission(err error) bool {
+ return underlyingErrorIs(err, ErrPermission)
+}
+
+// IsTimeout returns a boolean indicating whether the error is known
+// to report that a timeout occurred.
+//
+// This function predates errors.Is, and the notion of whether an
+// error indicates a timeout can be ambiguous. For example, the Unix
+// error EWOULDBLOCK sometimes indicates a timeout and sometimes does not.
+// New code should use errors.Is with a value appropriate to the call
+// returning the error, such as os.ErrDeadlineExceeded.
+func IsTimeout(err error) bool {
+ terr, ok := underlyingError(err).(timeout)
+ return ok && terr.Timeout()
+}
+
+func underlyingErrorIs(err, target error) bool {
+ // Note that this function is not errors.Is:
+ // underlyingError only unwraps the specific error-wrapping types
+ // that it historically did, not all errors implementing Unwrap().
+ err = underlyingError(err)
+ if err == target {
+ return true
+ }
+ // To preserve prior behavior, only examine syscall errors.
+ e, ok := err.(syscallErrorType)
+ return ok && e.Is(target)
+}
+
+// underlyingError returns the underlying error for known os error types.
+func underlyingError(err error) error {
+ switch err := err.(type) {
+ case *PathError:
+ return err.Err
+ case *LinkError:
+ return err.Err
+ case *SyscallError:
+ return err.Err
+ }
+ return err
+}
diff --git a/src/os/error_errno.go b/src/os/error_errno.go
new file mode 100644
index 0000000..c814046
--- /dev/null
+++ b/src/os/error_errno.go
@@ -0,0 +1,11 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !plan9
+
+package os
+
+import "syscall"
+
+type syscallErrorType = syscall.Errno
diff --git a/src/os/error_plan9.go b/src/os/error_plan9.go
new file mode 100644
index 0000000..af6065d
--- /dev/null
+++ b/src/os/error_plan9.go
@@ -0,0 +1,9 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+type syscallErrorType = syscall.ErrorString
diff --git a/src/os/error_posix.go b/src/os/error_posix.go
new file mode 100644
index 0000000..b159c03
--- /dev/null
+++ b/src/os/error_posix.go
@@ -0,0 +1,18 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1 || windows
+
+package os
+
+import "syscall"
+
+// wrapSyscallError takes an error and a syscall name. If the error is
+// a syscall.Errno, it wraps it in an os.SyscallError using the syscall name.
+func wrapSyscallError(name string, err error) error {
+ if _, ok := err.(syscall.Errno); ok {
+ err = NewSyscallError(name, err)
+ }
+ return err
+}
diff --git a/src/os/error_test.go b/src/os/error_test.go
new file mode 100644
index 0000000..8f82ae6
--- /dev/null
+++ b/src/os/error_test.go
@@ -0,0 +1,189 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestErrIsExist(t *testing.T) {
+ t.Parallel()
+
+ f, err := os.CreateTemp("", "_Go_ErrIsExist")
+ if err != nil {
+ t.Fatalf("open ErrIsExist tempfile: %s", err)
+ return
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ f2, err := os.OpenFile(f.Name(), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
+ if err == nil {
+ f2.Close()
+ t.Fatal("Open should have failed")
+ }
+ if s := checkErrorPredicate("os.IsExist", os.IsExist, err, fs.ErrExist); s != "" {
+ t.Fatal(s)
+ }
+}
+
+func testErrNotExist(t *testing.T, name string) string {
+ originalWD, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ f, err := os.Open(name)
+ if err == nil {
+ f.Close()
+ return "Open should have failed"
+ }
+ if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, fs.ErrNotExist); s != "" {
+ return s
+ }
+
+ err = os.Chdir(name)
+ if err == nil {
+ if err := os.Chdir(originalWD); err != nil {
+ t.Fatalf("Chdir should have failed, failed to restore original working directory: %v", err)
+ }
+ return "Chdir should have failed, restored original working directory"
+ }
+ if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, fs.ErrNotExist); s != "" {
+ return s
+ }
+ return ""
+}
+
+func TestErrIsNotExist(t *testing.T) {
+ tmpDir := t.TempDir()
+ name := filepath.Join(tmpDir, "NotExists")
+ if s := testErrNotExist(t, name); s != "" {
+ t.Fatal(s)
+ }
+
+ name = filepath.Join(name, "NotExists2")
+ if s := testErrNotExist(t, name); s != "" {
+ t.Fatal(s)
+ }
+}
+
+func checkErrorPredicate(predName string, pred func(error) bool, err, target error) string {
+ if !pred(err) {
+ return fmt.Sprintf("%s does not work as expected for %#v", predName, err)
+ }
+ if !errors.Is(err, target) {
+ return fmt.Sprintf("errors.Is(%#v, %#v) = false, want true", err, target)
+ }
+ return ""
+}
+
+type isExistTest struct {
+ err error
+ is bool
+ isnot bool
+}
+
+var isExistTests = []isExistTest{
+ {&fs.PathError{Err: fs.ErrInvalid}, false, false},
+ {&fs.PathError{Err: fs.ErrPermission}, false, false},
+ {&fs.PathError{Err: fs.ErrExist}, true, false},
+ {&fs.PathError{Err: fs.ErrNotExist}, false, true},
+ {&fs.PathError{Err: fs.ErrClosed}, false, false},
+ {&os.LinkError{Err: fs.ErrInvalid}, false, false},
+ {&os.LinkError{Err: fs.ErrPermission}, false, false},
+ {&os.LinkError{Err: fs.ErrExist}, true, false},
+ {&os.LinkError{Err: fs.ErrNotExist}, false, true},
+ {&os.LinkError{Err: fs.ErrClosed}, false, false},
+ {&os.SyscallError{Err: fs.ErrNotExist}, false, true},
+ {&os.SyscallError{Err: fs.ErrExist}, true, false},
+ {nil, false, false},
+}
+
+func TestIsExist(t *testing.T) {
+ for _, tt := range isExistTests {
+ if is := os.IsExist(tt.err); is != tt.is {
+ t.Errorf("os.IsExist(%T %v) = %v, want %v", tt.err, tt.err, is, tt.is)
+ }
+ if is := errors.Is(tt.err, fs.ErrExist); is != tt.is {
+ t.Errorf("errors.Is(%T %v, fs.ErrExist) = %v, want %v", tt.err, tt.err, is, tt.is)
+ }
+ if isnot := os.IsNotExist(tt.err); isnot != tt.isnot {
+ t.Errorf("os.IsNotExist(%T %v) = %v, want %v", tt.err, tt.err, isnot, tt.isnot)
+ }
+ if isnot := errors.Is(tt.err, fs.ErrNotExist); isnot != tt.isnot {
+ t.Errorf("errors.Is(%T %v, fs.ErrNotExist) = %v, want %v", tt.err, tt.err, isnot, tt.isnot)
+ }
+ }
+}
+
+type isPermissionTest struct {
+ err error
+ want bool
+}
+
+var isPermissionTests = []isPermissionTest{
+ {nil, false},
+ {&fs.PathError{Err: fs.ErrPermission}, true},
+ {&os.SyscallError{Err: fs.ErrPermission}, true},
+}
+
+func TestIsPermission(t *testing.T) {
+ for _, tt := range isPermissionTests {
+ if got := os.IsPermission(tt.err); got != tt.want {
+ t.Errorf("os.IsPermission(%#v) = %v; want %v", tt.err, got, tt.want)
+ }
+ if got := errors.Is(tt.err, fs.ErrPermission); got != tt.want {
+ t.Errorf("errors.Is(%#v, fs.ErrPermission) = %v; want %v", tt.err, got, tt.want)
+ }
+ }
+}
+
+func TestErrPathNUL(t *testing.T) {
+ t.Parallel()
+
+ f, err := os.CreateTemp("", "_Go_ErrPathNUL\x00")
+ if err == nil {
+ f.Close()
+ t.Fatal("TempFile should have failed")
+ }
+ f, err = os.CreateTemp("", "_Go_ErrPathNUL")
+ if err != nil {
+ t.Fatalf("open ErrPathNUL tempfile: %s", err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ f2, err := os.OpenFile(f.Name(), os.O_RDWR, 0600)
+ if err != nil {
+ t.Fatalf("open ErrPathNUL: %s", err)
+ }
+ f2.Close()
+ f2, err = os.OpenFile(f.Name()+"\x00", os.O_RDWR, 0600)
+ if err == nil {
+ f2.Close()
+ t.Fatal("Open should have failed")
+ }
+}
+
+func TestPathErrorUnwrap(t *testing.T) {
+ pe := &fs.PathError{Err: fs.ErrInvalid}
+ if !errors.Is(pe, fs.ErrInvalid) {
+ t.Error("errors.Is failed, wanted success")
+ }
+}
+
+type myErrorIs struct{ error }
+
+func (e myErrorIs) Is(target error) bool { return target == e.error }
+
+func TestErrorIsMethods(t *testing.T) {
+ if os.IsPermission(myErrorIs{fs.ErrPermission}) {
+ t.Error("os.IsPermission(err) = true when err.Is(fs.ErrPermission), wanted false")
+ }
+}
diff --git a/src/os/error_unix_test.go b/src/os/error_unix_test.go
new file mode 100644
index 0000000..07a3286
--- /dev/null
+++ b/src/os/error_unix_test.go
@@ -0,0 +1,40 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1
+
+package os_test
+
+import (
+ "io/fs"
+ "os"
+ "syscall"
+)
+
+func init() {
+ isExistTests = append(isExistTests,
+ isExistTest{err: &fs.PathError{Err: syscall.EEXIST}, is: true, isnot: false},
+ isExistTest{err: &fs.PathError{Err: syscall.ENOTEMPTY}, is: true, isnot: false},
+
+ isExistTest{err: &os.LinkError{Err: syscall.EEXIST}, is: true, isnot: false},
+ isExistTest{err: &os.LinkError{Err: syscall.ENOTEMPTY}, is: true, isnot: false},
+
+ isExistTest{err: &os.SyscallError{Err: syscall.EEXIST}, is: true, isnot: false},
+ isExistTest{err: &os.SyscallError{Err: syscall.ENOTEMPTY}, is: true, isnot: false},
+ )
+ isPermissionTests = append(isPermissionTests,
+ isPermissionTest{err: &fs.PathError{Err: syscall.EACCES}, want: true},
+ isPermissionTest{err: &fs.PathError{Err: syscall.EPERM}, want: true},
+ isPermissionTest{err: &fs.PathError{Err: syscall.EEXIST}, want: false},
+
+ isPermissionTest{err: &os.LinkError{Err: syscall.EACCES}, want: true},
+ isPermissionTest{err: &os.LinkError{Err: syscall.EPERM}, want: true},
+ isPermissionTest{err: &os.LinkError{Err: syscall.EEXIST}, want: false},
+
+ isPermissionTest{err: &os.SyscallError{Err: syscall.EACCES}, want: true},
+ isPermissionTest{err: &os.SyscallError{Err: syscall.EPERM}, want: true},
+ isPermissionTest{err: &os.SyscallError{Err: syscall.EEXIST}, want: false},
+ )
+
+}
diff --git a/src/os/error_windows_test.go b/src/os/error_windows_test.go
new file mode 100644
index 0000000..86c8a98
--- /dev/null
+++ b/src/os/error_windows_test.go
@@ -0,0 +1,40 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package os_test
+
+import (
+ "io/fs"
+ "os"
+ "syscall"
+)
+
+func init() {
+ const _ERROR_BAD_NETPATH = syscall.Errno(53)
+
+ isExistTests = append(isExistTests,
+ isExistTest{err: &fs.PathError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true},
+ isExistTest{err: &os.LinkError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true},
+ isExistTest{err: &os.SyscallError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true},
+
+ isExistTest{err: &fs.PathError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true},
+ isExistTest{err: &os.LinkError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true},
+ isExistTest{err: &os.SyscallError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true},
+
+ isExistTest{err: &fs.PathError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true},
+ isExistTest{err: &os.LinkError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true},
+ isExistTest{err: &os.SyscallError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true},
+
+ isExistTest{err: &fs.PathError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false},
+ isExistTest{err: &os.LinkError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false},
+ isExistTest{err: &os.SyscallError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false},
+ )
+ isPermissionTests = append(isPermissionTests,
+ isPermissionTest{err: &fs.PathError{Err: syscall.ERROR_ACCESS_DENIED}, want: true},
+ isPermissionTest{err: &os.LinkError{Err: syscall.ERROR_ACCESS_DENIED}, want: true},
+ isPermissionTest{err: &os.SyscallError{Err: syscall.ERROR_ACCESS_DENIED}, want: true},
+ )
+}
diff --git a/src/os/example_test.go b/src/os/example_test.go
new file mode 100644
index 0000000..5c7c6ea
--- /dev/null
+++ b/src/os/example_test.go
@@ -0,0 +1,265 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+func ExampleOpenFile() {
+ f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0755)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleOpenFile_append() {
+ // If the file doesn't exist, create it, or append to the file
+ f, err := os.OpenFile("access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if _, err := f.Write([]byte("appended some data\n")); err != nil {
+ f.Close() // ignore error; Write error takes precedence
+ log.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleChmod() {
+ if err := os.Chmod("some-filename", 0644); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleChtimes() {
+ mtime := time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC)
+ atime := time.Date(2007, time.March, 2, 4, 5, 6, 0, time.UTC)
+ if err := os.Chtimes("some-filename", atime, mtime); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleFileMode() {
+ fi, err := os.Lstat("some-filename")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("permissions: %#o\n", fi.Mode().Perm()) // 0400, 0777, etc.
+ switch mode := fi.Mode(); {
+ case mode.IsRegular():
+ fmt.Println("regular file")
+ case mode.IsDir():
+ fmt.Println("directory")
+ case mode&fs.ModeSymlink != 0:
+ fmt.Println("symbolic link")
+ case mode&fs.ModeNamedPipe != 0:
+ fmt.Println("named pipe")
+ }
+}
+
+func ExampleErrNotExist() {
+ filename := "a-nonexistent-file"
+ if _, err := os.Stat(filename); errors.Is(err, fs.ErrNotExist) {
+ fmt.Println("file does not exist")
+ }
+ // Output:
+ // file does not exist
+}
+
+func ExampleExpand() {
+ mapper := func(placeholderName string) string {
+ switch placeholderName {
+ case "DAY_PART":
+ return "morning"
+ case "NAME":
+ return "Gopher"
+ }
+
+ return ""
+ }
+
+ fmt.Println(os.Expand("Good ${DAY_PART}, $NAME!", mapper))
+
+ // Output:
+ // Good morning, Gopher!
+}
+
+func ExampleExpandEnv() {
+ os.Setenv("NAME", "gopher")
+ os.Setenv("BURROW", "/usr/gopher")
+
+ fmt.Println(os.ExpandEnv("$NAME lives in ${BURROW}."))
+
+ // Output:
+ // gopher lives in /usr/gopher.
+}
+
+func ExampleLookupEnv() {
+ show := func(key string) {
+ val, ok := os.LookupEnv(key)
+ if !ok {
+ fmt.Printf("%s not set\n", key)
+ } else {
+ fmt.Printf("%s=%s\n", key, val)
+ }
+ }
+
+ os.Setenv("SOME_KEY", "value")
+ os.Setenv("EMPTY_KEY", "")
+
+ show("SOME_KEY")
+ show("EMPTY_KEY")
+ show("MISSING_KEY")
+
+ // Output:
+ // SOME_KEY=value
+ // EMPTY_KEY=
+ // MISSING_KEY not set
+}
+
+func ExampleGetenv() {
+ os.Setenv("NAME", "gopher")
+ os.Setenv("BURROW", "/usr/gopher")
+
+ fmt.Printf("%s lives in %s.\n", os.Getenv("NAME"), os.Getenv("BURROW"))
+
+ // Output:
+ // gopher lives in /usr/gopher.
+}
+
+func ExampleUnsetenv() {
+ os.Setenv("TMPDIR", "/my/tmp")
+ defer os.Unsetenv("TMPDIR")
+}
+
+func ExampleReadDir() {
+ files, err := os.ReadDir(".")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ for _, file := range files {
+ fmt.Println(file.Name())
+ }
+}
+
+func ExampleMkdirTemp() {
+ dir, err := os.MkdirTemp("", "example")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer os.RemoveAll(dir) // clean up
+
+ file := filepath.Join(dir, "tmpfile")
+ if err := os.WriteFile(file, []byte("content"), 0666); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleMkdirTemp_suffix() {
+ logsDir, err := os.MkdirTemp("", "*-logs")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer os.RemoveAll(logsDir) // clean up
+
+ // Logs can be cleaned out earlier if needed by searching
+ // for all directories whose suffix ends in *-logs.
+ globPattern := filepath.Join(os.TempDir(), "*-logs")
+ matches, err := filepath.Glob(globPattern)
+ if err != nil {
+ log.Fatalf("Failed to match %q: %v", globPattern, err)
+ }
+
+ for _, match := range matches {
+ if err := os.RemoveAll(match); err != nil {
+ log.Printf("Failed to remove %q: %v", match, err)
+ }
+ }
+}
+
+func ExampleCreateTemp() {
+ f, err := os.CreateTemp("", "example")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer os.Remove(f.Name()) // clean up
+
+ if _, err := f.Write([]byte("content")); err != nil {
+ log.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleCreateTemp_suffix() {
+ f, err := os.CreateTemp("", "example.*.txt")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer os.Remove(f.Name()) // clean up
+
+ if _, err := f.Write([]byte("content")); err != nil {
+ f.Close()
+ log.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleReadFile() {
+ data, err := os.ReadFile("testdata/hello")
+ if err != nil {
+ log.Fatal(err)
+ }
+ os.Stdout.Write(data)
+
+ // Output:
+ // Hello, Gophers!
+}
+
+func ExampleWriteFile() {
+ err := os.WriteFile("testdata/hello", []byte("Hello, Gophers!"), 0666)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleMkdir() {
+ err := os.Mkdir("testdir", 0750)
+ if err != nil && !os.IsExist(err) {
+ log.Fatal(err)
+ }
+ err = os.WriteFile("testdir/testfile.txt", []byte("Hello, Gophers!"), 0660)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleMkdirAll() {
+ err := os.MkdirAll("test/subdir", 0750)
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = os.WriteFile("test/subdir/testfile.txt", []byte("Hello, Gophers!"), 0660)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/src/os/exec.go b/src/os/exec.go
new file mode 100644
index 0000000..ed5a75c
--- /dev/null
+++ b/src/os/exec.go
@@ -0,0 +1,180 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "errors"
+ "internal/testlog"
+ "runtime"
+ "sync"
+ "sync/atomic"
+ "syscall"
+ "time"
+)
+
+// ErrProcessDone indicates a Process has finished.
+var ErrProcessDone = errors.New("os: process already finished")
+
+// Process stores the information about a process created by StartProcess.
+type Process struct {
+ Pid int
+ handle uintptr // handle is accessed atomically on Windows
+ isdone atomic.Bool // process has been successfully waited on
+ sigMu sync.RWMutex // avoid race between wait and signal
+}
+
+func newProcess(pid int, handle uintptr) *Process {
+ p := &Process{Pid: pid, handle: handle}
+ runtime.SetFinalizer(p, (*Process).Release)
+ return p
+}
+
+func (p *Process) setDone() {
+ p.isdone.Store(true)
+}
+
+func (p *Process) done() bool {
+ return p.isdone.Load()
+}
+
+// ProcAttr holds the attributes that will be applied to a new process
+// started by StartProcess.
+type ProcAttr struct {
+ // If Dir is non-empty, the child changes into the directory before
+ // creating the process.
+ Dir string
+ // If Env is non-nil, it gives the environment variables for the
+ // new process in the form returned by Environ.
+ // If it is nil, the result of Environ will be used.
+ Env []string
+ // Files specifies the open files inherited by the new process. The
+ // first three entries correspond to standard input, standard output, and
+ // standard error. An implementation may support additional entries,
+ // depending on the underlying operating system. A nil entry corresponds
+ // to that file being closed when the process starts.
+ // On Unix systems, StartProcess will change these File values
+ // to blocking mode, which means that SetDeadline will stop working
+ // and calling Close will not interrupt a Read or Write.
+ Files []*File
+
+ // Operating system-specific process creation attributes.
+ // Note that setting this field means that your program
+ // may not execute properly or even compile on some
+ // operating systems.
+ Sys *syscall.SysProcAttr
+}
+
+// A Signal represents an operating system signal.
+// The usual underlying implementation is operating system-dependent:
+// on Unix it is syscall.Signal.
+type Signal interface {
+ String() string
+ Signal() // to distinguish from other Stringers
+}
+
+// Getpid returns the process id of the caller.
+func Getpid() int { return syscall.Getpid() }
+
+// Getppid returns the process id of the caller's parent.
+func Getppid() int { return syscall.Getppid() }
+
+// FindProcess looks for a running process by its pid.
+//
+// The Process it returns can be used to obtain information
+// about the underlying operating system process.
+//
+// On Unix systems, FindProcess always succeeds and returns a Process
+// for the given pid, regardless of whether the process exists. To test whether
+// the process actually exists, see whether p.Signal(syscall.Signal(0)) reports
+// an error.
+func FindProcess(pid int) (*Process, error) {
+ return findProcess(pid)
+}
+
+// StartProcess starts a new process with the program, arguments and attributes
+// specified by name, argv and attr. The argv slice will become os.Args in the
+// new process, so it normally starts with the program name.
+//
+// If the calling goroutine has locked the operating system thread
+// with runtime.LockOSThread and modified any inheritable OS-level
+// thread state (for example, Linux or Plan 9 name spaces), the new
+// process will inherit the caller's thread state.
+//
+// StartProcess is a low-level interface. The os/exec package provides
+// higher-level interfaces.
+//
+// If there is an error, it will be of type *PathError.
+func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
+ testlog.Open(name)
+ return startProcess(name, argv, attr)
+}
+
+// Release releases any resources associated with the Process p,
+// rendering it unusable in the future.
+// Release only needs to be called if Wait is not.
+func (p *Process) Release() error {
+ return p.release()
+}
+
+// Kill causes the Process to exit immediately. Kill does not wait until
+// the Process has actually exited. This only kills the Process itself,
+// not any other processes it may have started.
+func (p *Process) Kill() error {
+ return p.kill()
+}
+
+// Wait waits for the Process to exit, and then returns a
+// ProcessState describing its status and an error, if any.
+// Wait releases any resources associated with the Process.
+// On most operating systems, the Process must be a child
+// of the current process or an error will be returned.
+func (p *Process) Wait() (*ProcessState, error) {
+ return p.wait()
+}
+
+// Signal sends a signal to the Process.
+// Sending Interrupt on Windows is not implemented.
+func (p *Process) Signal(sig Signal) error {
+ return p.signal(sig)
+}
+
+// UserTime returns the user CPU time of the exited process and its children.
+func (p *ProcessState) UserTime() time.Duration {
+ return p.userTime()
+}
+
+// SystemTime returns the system CPU time of the exited process and its children.
+func (p *ProcessState) SystemTime() time.Duration {
+ return p.systemTime()
+}
+
+// Exited reports whether the program has exited.
+// On Unix systems this reports true if the program exited due to calling exit,
+// but false if the program terminated due to a signal.
+func (p *ProcessState) Exited() bool {
+ return p.exited()
+}
+
+// Success reports whether the program exited successfully,
+// such as with exit status 0 on Unix.
+func (p *ProcessState) Success() bool {
+ return p.success()
+}
+
+// Sys returns system-dependent exit information about
+// the process. Convert it to the appropriate underlying
+// type, such as syscall.WaitStatus on Unix, to access its contents.
+func (p *ProcessState) Sys() any {
+ return p.sys()
+}
+
+// SysUsage returns system-dependent resource usage information about
+// the exited process. Convert it to the appropriate underlying
+// type, such as *syscall.Rusage on Unix, to access its contents.
+// (On Unix, *syscall.Rusage matches struct rusage as defined in the
+// getrusage(2) manual page.)
+func (p *ProcessState) SysUsage() any {
+ return p.sysUsage()
+}
diff --git a/src/os/exec/bench_test.go b/src/os/exec/bench_test.go
new file mode 100644
index 0000000..9a94001
--- /dev/null
+++ b/src/os/exec/bench_test.go
@@ -0,0 +1,23 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "testing"
+)
+
+func BenchmarkExecHostname(b *testing.B) {
+ b.ReportAllocs()
+ path, err := LookPath("hostname")
+ if err != nil {
+ b.Fatalf("could not find hostname: %v", err)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ if err := Command(path).Run(); err != nil {
+ b.Fatalf("hostname: %v", err)
+ }
+ }
+}
diff --git a/src/os/exec/dot_test.go b/src/os/exec/dot_test.go
new file mode 100644
index 0000000..66c92f7
--- /dev/null
+++ b/src/os/exec/dot_test.go
@@ -0,0 +1,192 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec_test
+
+import (
+ "errors"
+ "internal/testenv"
+ "os"
+ . "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+var pathVar string = func() string {
+ if runtime.GOOS == "plan9" {
+ return "path"
+ }
+ return "PATH"
+}()
+
+func TestLookPath(t *testing.T) {
+ testenv.MustHaveExec(t)
+ // Not parallel: uses os.Chdir and t.Setenv.
+
+ tmpDir := filepath.Join(t.TempDir(), "testdir")
+ if err := os.Mkdir(tmpDir, 0777); err != nil {
+ t.Fatal(err)
+ }
+
+ executable := "execabs-test"
+ if runtime.GOOS == "windows" {
+ executable += ".exe"
+ }
+ if err := os.WriteFile(filepath.Join(tmpDir, executable), []byte{1, 2, 3}, 0777); err != nil {
+ t.Fatal(err)
+ }
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := os.Chdir(cwd); err != nil {
+ panic(err)
+ }
+ }()
+ if err = os.Chdir(tmpDir); err != nil {
+ t.Fatal(err)
+ }
+ t.Setenv("PWD", tmpDir)
+ t.Logf(". is %#q", tmpDir)
+
+ origPath := os.Getenv(pathVar)
+
+ // Add "." to PATH so that exec.LookPath looks in the current directory on all systems.
+ // And try to trick it with "../testdir" too.
+ for _, errdot := range []string{"1", "0"} {
+ t.Run("GODEBUG=execerrdot="+errdot, func(t *testing.T) {
+ t.Setenv("GODEBUG", "execerrdot="+errdot+",execwait=2")
+ for _, dir := range []string{".", "../testdir"} {
+ t.Run(pathVar+"="+dir, func(t *testing.T) {
+ t.Setenv(pathVar, dir+string(filepath.ListSeparator)+origPath)
+ good := dir + "/execabs-test"
+ if found, err := LookPath(good); err != nil || !strings.HasPrefix(found, good) {
+ t.Fatalf(`LookPath(%#q) = %#q, %v, want "%s...", nil`, good, found, err, good)
+ }
+ if runtime.GOOS == "windows" {
+ good = dir + `\execabs-test`
+ if found, err := LookPath(good); err != nil || !strings.HasPrefix(found, good) {
+ t.Fatalf(`LookPath(%#q) = %#q, %v, want "%s...", nil`, good, found, err, good)
+ }
+ }
+
+ _, err := LookPath("execabs-test")
+ if errdot == "1" {
+ if err == nil {
+ t.Fatalf("LookPath didn't fail when finding a non-relative path")
+ } else if !errors.Is(err, ErrDot) {
+ t.Fatalf("LookPath returned unexpected error: want Is ErrDot, got %q", err)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("LookPath failed unexpectedly: %v", err)
+ }
+ }
+
+ cmd := Command("execabs-test")
+ if errdot == "1" {
+ if cmd.Err == nil {
+ t.Fatalf("Command didn't fail when finding a non-relative path")
+ } else if !errors.Is(cmd.Err, ErrDot) {
+ t.Fatalf("Command returned unexpected error: want Is ErrDot, got %q", cmd.Err)
+ }
+ cmd.Err = nil
+ } else {
+ if cmd.Err != nil {
+ t.Fatalf("Command failed unexpectedly: %v", err)
+ }
+ }
+
+ // Clearing cmd.Err should let the execution proceed,
+ // and it should fail because it's not a valid binary.
+ if err := cmd.Run(); err == nil {
+ t.Fatalf("Run did not fail: expected exec error")
+ } else if errors.Is(err, ErrDot) {
+ t.Fatalf("Run returned unexpected error ErrDot: want error like ENOEXEC: %q", err)
+ }
+ })
+ }
+ })
+ }
+
+ // Test the behavior when the first entry in PATH is an absolute name for the
+ // current directory.
+ //
+ // On Windows, "." may or may not be implicitly included before the explicit
+ // %PATH%, depending on the process environment;
+ // see https://go.dev/issue/4394.
+ //
+ // If the relative entry from "." resolves to the same executable as what
+ // would be resolved from an absolute entry in %PATH% alone, LookPath should
+ // return the absolute version of the path instead of ErrDot.
+ // (See https://go.dev/issue/53536.)
+ //
+ // If PATH does not implicitly include "." (such as on Unix platforms, or on
+ // Windows configured with NoDefaultCurrentDirectoryInExePath), then this
+ // lookup should succeed regardless of the behavior for ".", so it may be
+ // useful to run as a control case even on those platforms.
+ t.Run(pathVar+"=$PWD", func(t *testing.T) {
+ t.Setenv(pathVar, tmpDir+string(filepath.ListSeparator)+origPath)
+ good := filepath.Join(tmpDir, "execabs-test")
+ if found, err := LookPath(good); err != nil || !strings.HasPrefix(found, good) {
+ t.Fatalf(`LookPath(%#q) = %#q, %v, want \"%s...\", nil`, good, found, err, good)
+ }
+
+ if found, err := LookPath("execabs-test"); err != nil || !strings.HasPrefix(found, good) {
+ t.Fatalf(`LookPath(%#q) = %#q, %v, want \"%s...\", nil`, "execabs-test", found, err, good)
+ }
+
+ cmd := Command("execabs-test")
+ if cmd.Err != nil {
+ t.Fatalf("Command(%#q).Err = %v; want nil", "execabs-test", cmd.Err)
+ }
+ })
+
+ t.Run(pathVar+"=$OTHER", func(t *testing.T) {
+ // Control case: if the lookup returns ErrDot when PATH is empty, then we
+ // know that PATH implicitly includes ".". If it does not, then we don't
+ // expect to see ErrDot at all in this test (because the path will be
+ // unambiguously absolute).
+ wantErrDot := false
+ t.Setenv(pathVar, "")
+ if found, err := LookPath("execabs-test"); errors.Is(err, ErrDot) {
+ wantErrDot = true
+ } else if err == nil {
+ t.Fatalf(`with PATH='', LookPath(%#q) = %#q; want non-nil error`, "execabs-test", found)
+ }
+
+ // Set PATH to include an explicit directory that contains a completely
+ // independent executable that happens to have the same name as an
+ // executable in ".". If "." is included implicitly, looking up the
+ // (unqualified) executable name will return ErrDot; otherwise, the
+ // executable in "." should have no effect and the lookup should
+ // unambiguously resolve to the directory in PATH.
+
+ dir := t.TempDir()
+ executable := "execabs-test"
+ if runtime.GOOS == "windows" {
+ executable += ".exe"
+ }
+ if err := os.WriteFile(filepath.Join(dir, executable), []byte{1, 2, 3}, 0777); err != nil {
+ t.Fatal(err)
+ }
+ t.Setenv(pathVar, dir+string(filepath.ListSeparator)+origPath)
+
+ found, err := LookPath("execabs-test")
+ if wantErrDot {
+ wantFound := filepath.Join(".", executable)
+ if found != wantFound || !errors.Is(err, ErrDot) {
+ t.Fatalf(`LookPath(%#q) = %#q, %v, want %#q, Is ErrDot`, "execabs-test", found, err, wantFound)
+ }
+ } else {
+ wantFound := filepath.Join(dir, executable)
+ if found != wantFound || err != nil {
+ t.Fatalf(`LookPath(%#q) = %#q, %v, want %#q, nil`, "execabs-test", found, err, wantFound)
+ }
+ }
+ })
+}
diff --git a/src/os/exec/env_test.go b/src/os/exec/env_test.go
new file mode 100644
index 0000000..ea06af3
--- /dev/null
+++ b/src/os/exec/env_test.go
@@ -0,0 +1,67 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestDedupEnv(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ noCase bool
+ nulOK bool
+ in []string
+ want []string
+ wantErr bool
+ }{
+ {
+ noCase: true,
+ in: []string{"k1=v1", "k2=v2", "K1=v3"},
+ want: []string{"k2=v2", "K1=v3"},
+ },
+ {
+ noCase: false,
+ in: []string{"k1=v1", "K1=V2", "k1=v3"},
+ want: []string{"K1=V2", "k1=v3"},
+ },
+ {
+ in: []string{"=a", "=b", "foo", "bar"},
+ want: []string{"=b", "foo", "bar"},
+ },
+ {
+ // #49886: preserve weird Windows keys with leading "=" signs.
+ noCase: true,
+ in: []string{`=C:=C:\golang`, `=D:=D:\tmp`, `=D:=D:\`},
+ want: []string{`=C:=C:\golang`, `=D:=D:\`},
+ },
+ {
+ // #52436: preserve invalid key-value entries (for now).
+ // (Maybe filter them out or error out on them at some point.)
+ in: []string{"dodgy", "entries"},
+ want: []string{"dodgy", "entries"},
+ },
+ {
+ // Filter out entries containing NULs.
+ in: []string{"A=a\x00b", "B=b", "C\x00C=c"},
+ want: []string{"B=b"},
+ wantErr: true,
+ },
+ {
+ // Plan 9 needs to preserve environment variables with NUL (#56544).
+ nulOK: true,
+ in: []string{"path=one\x00two"},
+ want: []string{"path=one\x00two"},
+ },
+ }
+ for _, tt := range tests {
+ got, err := dedupEnvCase(tt.noCase, tt.nulOK, tt.in)
+ if !reflect.DeepEqual(got, tt.want) || (err != nil) != tt.wantErr {
+ t.Errorf("Dedup(%v, %q) = %q, %v; want %q, error:%v", tt.noCase, tt.in, got, err, tt.want, tt.wantErr)
+ }
+ }
+}
diff --git a/src/os/exec/example_test.go b/src/os/exec/example_test.go
new file mode 100644
index 0000000..150f5cf
--- /dev/null
+++ b/src/os/exec/example_test.go
@@ -0,0 +1,169 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec_test
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+)
+
+func ExampleLookPath() {
+ path, err := exec.LookPath("fortune")
+ if err != nil {
+ log.Fatal("installing fortune is in your future")
+ }
+ fmt.Printf("fortune is available at %s\n", path)
+}
+
+func ExampleCommand() {
+ cmd := exec.Command("tr", "a-z", "A-Z")
+ cmd.Stdin = strings.NewReader("some input")
+ var out strings.Builder
+ cmd.Stdout = &out
+ err := cmd.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("in all caps: %q\n", out.String())
+}
+
+func ExampleCommand_environment() {
+ cmd := exec.Command("prog")
+ cmd.Env = append(os.Environ(),
+ "FOO=duplicate_value", // ignored
+ "FOO=actual_value", // this value is used
+ )
+ if err := cmd.Run(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleCmd_Output() {
+ out, err := exec.Command("date").Output()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("The date is %s\n", out)
+}
+
+func ExampleCmd_Run() {
+ cmd := exec.Command("sleep", "1")
+ log.Printf("Running command and waiting for it to finish...")
+ err := cmd.Run()
+ log.Printf("Command finished with error: %v", err)
+}
+
+func ExampleCmd_Start() {
+ cmd := exec.Command("sleep", "5")
+ err := cmd.Start()
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("Waiting for command to finish...")
+ err = cmd.Wait()
+ log.Printf("Command finished with error: %v", err)
+}
+
+func ExampleCmd_StdoutPipe() {
+ cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := cmd.Start(); err != nil {
+ log.Fatal(err)
+ }
+ var person struct {
+ Name string
+ Age int
+ }
+ if err := json.NewDecoder(stdout).Decode(&person); err != nil {
+ log.Fatal(err)
+ }
+ if err := cmd.Wait(); err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s is %d years old\n", person.Name, person.Age)
+}
+
+func ExampleCmd_StdinPipe() {
+ cmd := exec.Command("cat")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ go func() {
+ defer stdin.Close()
+ io.WriteString(stdin, "values written to stdin are passed to cmd's standard input")
+ }()
+
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("%s\n", out)
+}
+
+func ExampleCmd_StderrPipe() {
+ cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err := cmd.Start(); err != nil {
+ log.Fatal(err)
+ }
+
+ slurp, _ := io.ReadAll(stderr)
+ fmt.Printf("%s\n", slurp)
+
+ if err := cmd.Wait(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func ExampleCmd_CombinedOutput() {
+ cmd := exec.Command("sh", "-c", "echo stdout; echo 1>&2 stderr")
+ stdoutStderr, err := cmd.CombinedOutput()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s\n", stdoutStderr)
+}
+
+func ExampleCmd_Environ() {
+ cmd := exec.Command("pwd")
+
+ // Set Dir before calling cmd.Environ so that it will include an
+ // updated PWD variable (on platforms where that is used).
+ cmd.Dir = ".."
+ cmd.Env = append(cmd.Environ(), "POSIXLY_CORRECT=1")
+
+ out, err := cmd.Output()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s\n", out)
+}
+
+func ExampleCommandContext() {
+ ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
+ defer cancel()
+
+ if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
+ // This will fail after 100 milliseconds. The 5 second sleep
+ // will be interrupted.
+ }
+}
diff --git a/src/os/exec/exec.go b/src/os/exec/exec.go
new file mode 100644
index 0000000..138be29
--- /dev/null
+++ b/src/os/exec/exec.go
@@ -0,0 +1,1303 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package exec runs external commands. It wraps os.StartProcess to make it
+// easier to remap stdin and stdout, connect I/O with pipes, and do other
+// adjustments.
+//
+// Unlike the "system" library call from C and other languages, the
+// os/exec package intentionally does not invoke the system shell and
+// does not expand any glob patterns or handle other expansions,
+// pipelines, or redirections typically done by shells. The package
+// behaves more like C's "exec" family of functions. To expand glob
+// patterns, either call the shell directly, taking care to escape any
+// dangerous input, or use the path/filepath package's Glob function.
+// To expand environment variables, use package os's ExpandEnv.
+//
+// Note that the examples in this package assume a Unix system.
+// They may not run on Windows, and they do not run in the Go Playground
+// used by golang.org and godoc.org.
+//
+// # Executables in the current directory
+//
+// The functions Command and LookPath look for a program
+// in the directories listed in the current path, following the
+// conventions of the host operating system.
+// Operating systems have for decades included the current
+// directory in this search, sometimes implicitly and sometimes
+// configured explicitly that way by default.
+// Modern practice is that including the current directory
+// is usually unexpected and often leads to security problems.
+//
+// To avoid those security problems, as of Go 1.19, this package will not resolve a program
+// using an implicit or explicit path entry relative to the current directory.
+// That is, if you run exec.LookPath("go"), it will not successfully return
+// ./go on Unix nor .\go.exe on Windows, no matter how the path is configured.
+// Instead, if the usual path algorithms would result in that answer,
+// these functions return an error err satisfying errors.Is(err, ErrDot).
+//
+// For example, consider these two program snippets:
+//
+// path, err := exec.LookPath("prog")
+// if err != nil {
+// log.Fatal(err)
+// }
+// use(path)
+//
+// and
+//
+// cmd := exec.Command("prog")
+// if err := cmd.Run(); err != nil {
+// log.Fatal(err)
+// }
+//
+// These will not find and run ./prog or .\prog.exe,
+// no matter how the current path is configured.
+//
+// Code that always wants to run a program from the current directory
+// can be rewritten to say "./prog" instead of "prog".
+//
+// Code that insists on including results from relative path entries
+// can instead override the error using an errors.Is check:
+//
+// path, err := exec.LookPath("prog")
+// if errors.Is(err, exec.ErrDot) {
+// err = nil
+// }
+// if err != nil {
+// log.Fatal(err)
+// }
+// use(path)
+//
+// and
+//
+// cmd := exec.Command("prog")
+// if errors.Is(cmd.Err, exec.ErrDot) {
+// cmd.Err = nil
+// }
+// if err := cmd.Run(); err != nil {
+// log.Fatal(err)
+// }
+//
+// Setting the environment variable GODEBUG=execerrdot=0
+// disables generation of ErrDot entirely, temporarily restoring the pre-Go 1.19
+// behavior for programs that are unable to apply more targeted fixes.
+// A future version of Go may remove support for this variable.
+//
+// Before adding such overrides, make sure you understand the
+// security implications of doing so.
+// See https://go.dev/blog/path-security for more information.
+package exec
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "internal/godebug"
+ "internal/syscall/execenv"
+ "io"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+)
+
+// Error is returned by LookPath when it fails to classify a file as an
+// executable.
+type Error struct {
+ // Name is the file name for which the error occurred.
+ Name string
+ // Err is the underlying error.
+ Err error
+}
+
+func (e *Error) Error() string {
+ return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error()
+}
+
+func (e *Error) Unwrap() error { return e.Err }
+
+// ErrWaitDelay is returned by (*Cmd).Wait if the process exits with a
+// successful status code but its output pipes are not closed before the
+// command's WaitDelay expires.
+var ErrWaitDelay = errors.New("exec: WaitDelay expired before I/O complete")
+
+// wrappedError wraps an error without relying on fmt.Errorf.
+type wrappedError struct {
+ prefix string
+ err error
+}
+
+func (w wrappedError) Error() string {
+ return w.prefix + ": " + w.err.Error()
+}
+
+func (w wrappedError) Unwrap() error {
+ return w.err
+}
+
+// Cmd represents an external command being prepared or run.
+//
+// A Cmd cannot be reused after calling its Run, Output or CombinedOutput
+// methods.
+type Cmd struct {
+ // Path is the path of the command to run.
+ //
+ // This is the only field that must be set to a non-zero
+ // value. If Path is relative, it is evaluated relative
+ // to Dir.
+ Path string
+
+ // Args holds command line arguments, including the command as Args[0].
+ // If the Args field is empty or nil, Run uses {Path}.
+ //
+ // In typical use, both Path and Args are set by calling Command.
+ Args []string
+
+ // Env specifies the environment of the process.
+ // Each entry is of the form "key=value".
+ // If Env is nil, the new process uses the current process's
+ // environment.
+ // If Env contains duplicate environment keys, only the last
+ // value in the slice for each duplicate key is used.
+ // As a special case on Windows, SYSTEMROOT is always added if
+ // missing and not explicitly set to the empty string.
+ Env []string
+
+ // Dir specifies the working directory of the command.
+ // If Dir is the empty string, Run runs the command in the
+ // calling process's current directory.
+ Dir string
+
+ // Stdin specifies the process's standard input.
+ //
+ // If Stdin is nil, the process reads from the null device (os.DevNull).
+ //
+ // If Stdin is an *os.File, the process's standard input is connected
+ // directly to that file.
+ //
+ // Otherwise, during the execution of the command a separate
+ // goroutine reads from Stdin and delivers that data to the command
+ // over a pipe. In this case, Wait does not complete until the goroutine
+ // stops copying, either because it has reached the end of Stdin
+ // (EOF or a read error), or because writing to the pipe returned an error,
+ // or because a nonzero WaitDelay was set and expired.
+ Stdin io.Reader
+
+ // Stdout and Stderr specify the process's standard output and error.
+ //
+ // If either is nil, Run connects the corresponding file descriptor
+ // to the null device (os.DevNull).
+ //
+ // If either is an *os.File, the corresponding output from the process
+ // is connected directly to that file.
+ //
+ // Otherwise, during the execution of the command a separate goroutine
+ // reads from the process over a pipe and delivers that data to the
+ // corresponding Writer. In this case, Wait does not complete until the
+ // goroutine reaches EOF or encounters an error or a nonzero WaitDelay
+ // expires.
+ //
+ // If Stdout and Stderr are the same writer, and have a type that can
+ // be compared with ==, at most one goroutine at a time will call Write.
+ Stdout io.Writer
+ Stderr io.Writer
+
+ // ExtraFiles specifies additional open files to be inherited by the
+ // new process. It does not include standard input, standard output, or
+ // standard error. If non-nil, entry i becomes file descriptor 3+i.
+ //
+ // ExtraFiles is not supported on Windows.
+ ExtraFiles []*os.File
+
+ // SysProcAttr holds optional, operating system-specific attributes.
+ // Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
+ SysProcAttr *syscall.SysProcAttr
+
+ // Process is the underlying process, once started.
+ Process *os.Process
+
+ // ProcessState contains information about an exited process.
+ // If the process was started successfully, Wait or Run will
+ // populate its ProcessState when the command completes.
+ ProcessState *os.ProcessState
+
+ // ctx is the context passed to CommandContext, if any.
+ ctx context.Context
+
+ Err error // LookPath error, if any.
+
+ // If Cancel is non-nil, the command must have been created with
+ // CommandContext and Cancel will be called when the command's
+ // Context is done. By default, CommandContext sets Cancel to
+ // call the Kill method on the command's Process.
+ //
+ // Typically a custom Cancel will send a signal to the command's
+ // Process, but it may instead take other actions to initiate cancellation,
+ // such as closing a stdin or stdout pipe or sending a shutdown request on a
+ // network socket.
+ //
+ // If the command exits with a success status after Cancel is
+ // called, and Cancel does not return an error equivalent to
+ // os.ErrProcessDone, then Wait and similar methods will return a non-nil
+ // error: either an error wrapping the one returned by Cancel,
+ // or the error from the Context.
+ // (If the command exits with a non-success status, or Cancel
+ // returns an error that wraps os.ErrProcessDone, Wait and similar methods
+ // continue to return the command's usual exit status.)
+ //
+ // If Cancel is set to nil, nothing will happen immediately when the command's
+ // Context is done, but a nonzero WaitDelay will still take effect. That may
+ // be useful, for example, to work around deadlocks in commands that do not
+ // support shutdown signals but are expected to always finish quickly.
+ //
+ // Cancel will not be called if Start returns a non-nil error.
+ Cancel func() error
+
+ // If WaitDelay is non-zero, it bounds the time spent waiting on two sources
+ // of unexpected delay in Wait: a child process that fails to exit after the
+ // associated Context is canceled, and a child process that exits but leaves
+ // its I/O pipes unclosed.
+ //
+ // The WaitDelay timer starts when either the associated Context is done or a
+ // call to Wait observes that the child process has exited, whichever occurs
+ // first. When the delay has elapsed, the command shuts down the child process
+ // and/or its I/O pipes.
+ //
+ // If the child process has failed to exit — perhaps because it ignored or
+ // failed to receive a shutdown signal from a Cancel function, or because no
+ // Cancel function was set — then it will be terminated using os.Process.Kill.
+ //
+ // Then, if the I/O pipes communicating with the child process are still open,
+ // those pipes are closed in order to unblock any goroutines currently blocked
+ // on Read or Write calls.
+ //
+ // If pipes are closed due to WaitDelay, no Cancel call has occurred,
+ // and the command has otherwise exited with a successful status, Wait and
+ // similar methods will return ErrWaitDelay instead of nil.
+ //
+ // If WaitDelay is zero (the default), I/O pipes will be read until EOF,
+ // which might not occur until orphaned subprocesses of the command have
+ // also closed their descriptors for the pipes.
+ WaitDelay time.Duration
+
+ // childIOFiles holds closers for any of the child process's
+ // stdin, stdout, and/or stderr files that were opened by the Cmd itself
+ // (not supplied by the caller). These should be closed as soon as they
+ // are inherited by the child process.
+ childIOFiles []io.Closer
+
+ // parentIOPipes holds closers for the parent's end of any pipes
+ // connected to the child's stdin, stdout, and/or stderr streams
+ // that were opened by the Cmd itself (not supplied by the caller).
+ // These should be closed after Wait sees the command and copying
+ // goroutines exit, or after WaitDelay has expired.
+ parentIOPipes []io.Closer
+
+ // goroutine holds a set of closures to execute to copy data
+ // to and/or from the command's I/O pipes.
+ goroutine []func() error
+
+ // If goroutineErr is non-nil, it receives the first error from a copying
+ // goroutine once all such goroutines have completed.
+ // goroutineErr is set to nil once its error has been received.
+ goroutineErr <-chan error
+
+ // If ctxResult is non-nil, it receives the result of watchCtx exactly once.
+ ctxResult <-chan ctxResult
+
+ // The stack saved when the Command was created, if GODEBUG contains
+ // execwait=2. Used for debugging leaks.
+ createdByStack []byte
+
+ // For a security release long ago, we created x/sys/execabs,
+ // which manipulated the unexported lookPathErr error field
+ // in this struct. For Go 1.19 we exported the field as Err error,
+ // above, but we have to keep lookPathErr around for use by
+ // old programs building against new toolchains.
+ // The String and Start methods look for an error in lookPathErr
+ // in preference to Err, to preserve the errors that execabs sets.
+ //
+ // In general we don't guarantee misuse of reflect like this,
+ // but the misuse of reflect was by us, the best of various bad
+ // options to fix the security problem, and people depend on
+ // those old copies of execabs continuing to work.
+ // The result is that we have to leave this variable around for the
+ // rest of time, a compatibility scar.
+ //
+ // See https://go.dev/blog/path-security
+ // and https://go.dev/issue/43724 for more context.
+ lookPathErr error
+}
+
+// A ctxResult reports the result of watching the Context associated with a
+// running command (and sending corresponding signals if needed).
+type ctxResult struct {
+ err error
+
+ // If timer is non-nil, it expires after WaitDelay has elapsed after
+ // the Context is done.
+ //
+ // (If timer is nil, that means that the Context was not done before the
+ // command completed, or no WaitDelay was set, or the WaitDelay already
+ // expired and its effect was already applied.)
+ timer *time.Timer
+}
+
+var execwait = godebug.New("#execwait")
+var execerrdot = godebug.New("execerrdot")
+
+// Command returns the Cmd struct to execute the named program with
+// the given arguments.
+//
+// It sets only the Path and Args in the returned structure.
+//
+// If name contains no path separators, Command uses LookPath to
+// resolve name to a complete path if possible. Otherwise it uses name
+// directly as Path.
+//
+// The returned Cmd's Args field is constructed from the command name
+// followed by the elements of arg, so arg should not include the
+// command name itself. For example, Command("echo", "hello").
+// Args[0] is always name, not the possibly resolved Path.
+//
+// On Windows, processes receive the whole command line as a single string
+// and do their own parsing. Command combines and quotes Args into a command
+// line string with an algorithm compatible with applications using
+// CommandLineToArgvW (which is the most common way). Notable exceptions are
+// msiexec.exe and cmd.exe (and thus, all batch files), which have a different
+// unquoting algorithm. In these or other similar cases, you can do the
+// quoting yourself and provide the full command line in SysProcAttr.CmdLine,
+// leaving Args empty.
+func Command(name string, arg ...string) *Cmd {
+ cmd := &Cmd{
+ Path: name,
+ Args: append([]string{name}, arg...),
+ }
+
+ if v := execwait.Value(); v != "" {
+ if v == "2" {
+ // Obtain the caller stack. (This is equivalent to runtime/debug.Stack,
+ // copied to avoid importing the whole package.)
+ stack := make([]byte, 1024)
+ for {
+ n := runtime.Stack(stack, false)
+ if n < len(stack) {
+ stack = stack[:n]
+ break
+ }
+ stack = make([]byte, 2*len(stack))
+ }
+
+ if i := bytes.Index(stack, []byte("\nos/exec.Command(")); i >= 0 {
+ stack = stack[i+1:]
+ }
+ cmd.createdByStack = stack
+ }
+
+ runtime.SetFinalizer(cmd, func(c *Cmd) {
+ if c.Process != nil && c.ProcessState == nil {
+ debugHint := ""
+ if c.createdByStack == nil {
+ debugHint = " (set GODEBUG=execwait=2 to capture stacks for debugging)"
+ } else {
+ os.Stderr.WriteString("GODEBUG=execwait=2 detected a leaked exec.Cmd created by:\n")
+ os.Stderr.Write(c.createdByStack)
+ os.Stderr.WriteString("\n")
+ debugHint = ""
+ }
+ panic("exec: Cmd started a Process but leaked without a call to Wait" + debugHint)
+ }
+ })
+ }
+
+ if filepath.Base(name) == name {
+ lp, err := LookPath(name)
+ if lp != "" {
+ // Update cmd.Path even if err is non-nil.
+ // If err is ErrDot (especially on Windows), lp may include a resolved
+ // extension (like .exe or .bat) that should be preserved.
+ cmd.Path = lp
+ }
+ if err != nil {
+ cmd.Err = err
+ }
+ }
+ return cmd
+}
+
+// CommandContext is like Command but includes a context.
+//
+// The provided context is used to interrupt the process
+// (by calling cmd.Cancel or os.Process.Kill)
+// if the context becomes done before the command completes on its own.
+//
+// CommandContext sets the command's Cancel function to invoke the Kill method
+// on its Process, and leaves its WaitDelay unset. The caller may change the
+// cancellation behavior by modifying those fields before starting the command.
+func CommandContext(ctx context.Context, name string, arg ...string) *Cmd {
+ if ctx == nil {
+ panic("nil Context")
+ }
+ cmd := Command(name, arg...)
+ cmd.ctx = ctx
+ cmd.Cancel = func() error {
+ return cmd.Process.Kill()
+ }
+ return cmd
+}
+
+// String returns a human-readable description of c.
+// It is intended only for debugging.
+// In particular, it is not suitable for use as input to a shell.
+// The output of String may vary across Go releases.
+func (c *Cmd) String() string {
+ if c.Err != nil || c.lookPathErr != nil {
+ // failed to resolve path; report the original requested path (plus args)
+ return strings.Join(c.Args, " ")
+ }
+ // report the exact executable path (plus args)
+ b := new(strings.Builder)
+ b.WriteString(c.Path)
+ for _, a := range c.Args[1:] {
+ b.WriteByte(' ')
+ b.WriteString(a)
+ }
+ return b.String()
+}
+
+// interfaceEqual protects against panics from doing equality tests on
+// two interfaces with non-comparable underlying types.
+func interfaceEqual(a, b any) bool {
+ defer func() {
+ recover()
+ }()
+ return a == b
+}
+
+func (c *Cmd) argv() []string {
+ if len(c.Args) > 0 {
+ return c.Args
+ }
+ return []string{c.Path}
+}
+
+func (c *Cmd) childStdin() (*os.File, error) {
+ if c.Stdin == nil {
+ f, err := os.Open(os.DevNull)
+ if err != nil {
+ return nil, err
+ }
+ c.childIOFiles = append(c.childIOFiles, f)
+ return f, nil
+ }
+
+ if f, ok := c.Stdin.(*os.File); ok {
+ return f, nil
+ }
+
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+
+ c.childIOFiles = append(c.childIOFiles, pr)
+ c.parentIOPipes = append(c.parentIOPipes, pw)
+ c.goroutine = append(c.goroutine, func() error {
+ _, err := io.Copy(pw, c.Stdin)
+ if skipStdinCopyError(err) {
+ err = nil
+ }
+ if err1 := pw.Close(); err == nil {
+ err = err1
+ }
+ return err
+ })
+ return pr, nil
+}
+
+func (c *Cmd) childStdout() (*os.File, error) {
+ return c.writerDescriptor(c.Stdout)
+}
+
+func (c *Cmd) childStderr(childStdout *os.File) (*os.File, error) {
+ if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
+ return childStdout, nil
+ }
+ return c.writerDescriptor(c.Stderr)
+}
+
+// writerDescriptor returns an os.File to which the child process
+// can write to send data to w.
+//
+// If w is nil, writerDescriptor returns a File that writes to os.DevNull.
+func (c *Cmd) writerDescriptor(w io.Writer) (*os.File, error) {
+ if w == nil {
+ f, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0)
+ if err != nil {
+ return nil, err
+ }
+ c.childIOFiles = append(c.childIOFiles, f)
+ return f, nil
+ }
+
+ if f, ok := w.(*os.File); ok {
+ return f, nil
+ }
+
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+
+ c.childIOFiles = append(c.childIOFiles, pw)
+ c.parentIOPipes = append(c.parentIOPipes, pr)
+ c.goroutine = append(c.goroutine, func() error {
+ _, err := io.Copy(w, pr)
+ pr.Close() // in case io.Copy stopped due to write error
+ return err
+ })
+ return pw, nil
+}
+
+func closeDescriptors(closers []io.Closer) {
+ for _, fd := range closers {
+ fd.Close()
+ }
+}
+
+// Run starts the specified command and waits for it to complete.
+//
+// The returned error is nil if the command runs, has no problems
+// copying stdin, stdout, and stderr, and exits with a zero exit
+// status.
+//
+// If the command starts but does not complete successfully, the error is of
+// type *ExitError. Other error types may be returned for other situations.
+//
+// If the calling goroutine has locked the operating system thread
+// with runtime.LockOSThread and modified any inheritable OS-level
+// thread state (for example, Linux or Plan 9 name spaces), the new
+// process will inherit the caller's thread state.
+func (c *Cmd) Run() error {
+ if err := c.Start(); err != nil {
+ return err
+ }
+ return c.Wait()
+}
+
+// lookExtensions finds windows executable by its dir and path.
+// It uses LookPath to try appropriate extensions.
+// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`.
+func lookExtensions(path, dir string) (string, error) {
+ if filepath.Base(path) == path {
+ path = "." + string(filepath.Separator) + path
+ }
+ if dir == "" {
+ return LookPath(path)
+ }
+ if filepath.VolumeName(path) != "" {
+ return LookPath(path)
+ }
+ if len(path) > 1 && os.IsPathSeparator(path[0]) {
+ return LookPath(path)
+ }
+ dirandpath := filepath.Join(dir, path)
+ // We assume that LookPath will only add file extension.
+ lp, err := LookPath(dirandpath)
+ if err != nil {
+ return "", err
+ }
+ ext := strings.TrimPrefix(lp, dirandpath)
+ return path + ext, nil
+}
+
+// Start starts the specified command but does not wait for it to complete.
+//
+// If Start returns successfully, the c.Process field will be set.
+//
+// After a successful call to Start the Wait method must be called in
+// order to release associated system resources.
+func (c *Cmd) Start() error {
+ // Check for doubled Start calls before we defer failure cleanup. If the prior
+ // call to Start succeeded, we don't want to spuriously close its pipes.
+ if c.Process != nil {
+ return errors.New("exec: already started")
+ }
+
+ started := false
+ defer func() {
+ closeDescriptors(c.childIOFiles)
+ c.childIOFiles = nil
+
+ if !started {
+ closeDescriptors(c.parentIOPipes)
+ c.parentIOPipes = nil
+ }
+ }()
+
+ if c.Path == "" && c.Err == nil && c.lookPathErr == nil {
+ c.Err = errors.New("exec: no command")
+ }
+ if c.Err != nil || c.lookPathErr != nil {
+ if c.lookPathErr != nil {
+ return c.lookPathErr
+ }
+ return c.Err
+ }
+ if runtime.GOOS == "windows" {
+ lp, err := lookExtensions(c.Path, c.Dir)
+ if err != nil {
+ return err
+ }
+ c.Path = lp
+ }
+ if c.Cancel != nil && c.ctx == nil {
+ return errors.New("exec: command with a non-nil Cancel was not created with CommandContext")
+ }
+ if c.ctx != nil {
+ select {
+ case <-c.ctx.Done():
+ return c.ctx.Err()
+ default:
+ }
+ }
+
+ childFiles := make([]*os.File, 0, 3+len(c.ExtraFiles))
+ stdin, err := c.childStdin()
+ if err != nil {
+ return err
+ }
+ childFiles = append(childFiles, stdin)
+ stdout, err := c.childStdout()
+ if err != nil {
+ return err
+ }
+ childFiles = append(childFiles, stdout)
+ stderr, err := c.childStderr(stdout)
+ if err != nil {
+ return err
+ }
+ childFiles = append(childFiles, stderr)
+ childFiles = append(childFiles, c.ExtraFiles...)
+
+ env, err := c.environ()
+ if err != nil {
+ return err
+ }
+
+ c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
+ Dir: c.Dir,
+ Files: childFiles,
+ Env: env,
+ Sys: c.SysProcAttr,
+ })
+ if err != nil {
+ return err
+ }
+ started = true
+
+ // Don't allocate the goroutineErr channel unless there are goroutines to start.
+ if len(c.goroutine) > 0 {
+ goroutineErr := make(chan error, 1)
+ c.goroutineErr = goroutineErr
+
+ type goroutineStatus struct {
+ running int
+ firstErr error
+ }
+ statusc := make(chan goroutineStatus, 1)
+ statusc <- goroutineStatus{running: len(c.goroutine)}
+ for _, fn := range c.goroutine {
+ go func(fn func() error) {
+ err := fn()
+
+ status := <-statusc
+ if status.firstErr == nil {
+ status.firstErr = err
+ }
+ status.running--
+ if status.running == 0 {
+ goroutineErr <- status.firstErr
+ } else {
+ statusc <- status
+ }
+ }(fn)
+ }
+ c.goroutine = nil // Allow the goroutines' closures to be GC'd when they complete.
+ }
+
+ // If we have anything to do when the command's Context expires,
+ // start a goroutine to watch for cancellation.
+ //
+ // (Even if the command was created by CommandContext, a helper library may
+ // have explicitly set its Cancel field back to nil, indicating that it should
+ // be allowed to continue running after cancellation after all.)
+ if (c.Cancel != nil || c.WaitDelay != 0) && c.ctx != nil && c.ctx.Done() != nil {
+ resultc := make(chan ctxResult)
+ c.ctxResult = resultc
+ go c.watchCtx(resultc)
+ }
+
+ return nil
+}
+
+// watchCtx watches c.ctx until it is able to send a result to resultc.
+//
+// If c.ctx is done before a result can be sent, watchCtx calls c.Cancel,
+// and/or kills cmd.Process it after c.WaitDelay has elapsed.
+//
+// watchCtx manipulates c.goroutineErr, so its result must be received before
+// c.awaitGoroutines is called.
+func (c *Cmd) watchCtx(resultc chan<- ctxResult) {
+ select {
+ case resultc <- ctxResult{}:
+ return
+ case <-c.ctx.Done():
+ }
+
+ var err error
+ if c.Cancel != nil {
+ if interruptErr := c.Cancel(); interruptErr == nil {
+ // We appear to have successfully interrupted the command, so any
+ // program behavior from this point may be due to ctx even if the
+ // command exits with code 0.
+ err = c.ctx.Err()
+ } else if errors.Is(interruptErr, os.ErrProcessDone) {
+ // The process already finished: we just didn't notice it yet.
+ // (Perhaps c.Wait hadn't been called, or perhaps it happened to race with
+ // c.ctx being cancelled.) Don't inject a needless error.
+ } else {
+ err = wrappedError{
+ prefix: "exec: canceling Cmd",
+ err: interruptErr,
+ }
+ }
+ }
+ if c.WaitDelay == 0 {
+ resultc <- ctxResult{err: err}
+ return
+ }
+
+ timer := time.NewTimer(c.WaitDelay)
+ select {
+ case resultc <- ctxResult{err: err, timer: timer}:
+ // c.Process.Wait returned and we've handed the timer off to c.Wait.
+ // It will take care of goroutine shutdown from here.
+ return
+ case <-timer.C:
+ }
+
+ killed := false
+ if killErr := c.Process.Kill(); killErr == nil {
+ // We appear to have killed the process. c.Process.Wait should return a
+ // non-nil error to c.Wait unless the Kill signal races with a successful
+ // exit, and if that does happen we shouldn't report a spurious error,
+ // so don't set err to anything here.
+ killed = true
+ } else if !errors.Is(killErr, os.ErrProcessDone) {
+ err = wrappedError{
+ prefix: "exec: killing Cmd",
+ err: killErr,
+ }
+ }
+
+ if c.goroutineErr != nil {
+ select {
+ case goroutineErr := <-c.goroutineErr:
+ // Forward goroutineErr only if we don't have reason to believe it was
+ // caused by a call to Cancel or Kill above.
+ if err == nil && !killed {
+ err = goroutineErr
+ }
+ default:
+ // Close the child process's I/O pipes, in case it abandoned some
+ // subprocess that inherited them and is still holding them open
+ // (see https://go.dev/issue/23019).
+ //
+ // We close the goroutine pipes only after we have sent any signals we're
+ // going to send to the process (via Signal or Kill above): if we send
+ // SIGKILL to the process, we would prefer for it to die of SIGKILL, not
+ // SIGPIPE. (However, this may still cause any orphaned subprocesses to
+ // terminate with SIGPIPE.)
+ closeDescriptors(c.parentIOPipes)
+ // Wait for the copying goroutines to finish, but report ErrWaitDelay for
+ // the error: any other error here could result from closing the pipes.
+ _ = <-c.goroutineErr
+ if err == nil {
+ err = ErrWaitDelay
+ }
+ }
+
+ // Since we have already received the only result from c.goroutineErr,
+ // set it to nil to prevent awaitGoroutines from blocking on it.
+ c.goroutineErr = nil
+ }
+
+ resultc <- ctxResult{err: err}
+}
+
+// An ExitError reports an unsuccessful exit by a command.
+type ExitError struct {
+ *os.ProcessState
+
+ // Stderr holds a subset of the standard error output from the
+ // Cmd.Output method if standard error was not otherwise being
+ // collected.
+ //
+ // If the error output is long, Stderr may contain only a prefix
+ // and suffix of the output, with the middle replaced with
+ // text about the number of omitted bytes.
+ //
+ // Stderr is provided for debugging, for inclusion in error messages.
+ // Users with other needs should redirect Cmd.Stderr as needed.
+ Stderr []byte
+}
+
+func (e *ExitError) Error() string {
+ return e.ProcessState.String()
+}
+
+// Wait waits for the command to exit and waits for any copying to
+// stdin or copying from stdout or stderr to complete.
+//
+// The command must have been started by Start.
+//
+// The returned error is nil if the command runs, has no problems
+// copying stdin, stdout, and stderr, and exits with a zero exit
+// status.
+//
+// If the command fails to run or doesn't complete successfully, the
+// error is of type *ExitError. Other error types may be
+// returned for I/O problems.
+//
+// If any of c.Stdin, c.Stdout or c.Stderr are not an *os.File, Wait also waits
+// for the respective I/O loop copying to or from the process to complete.
+//
+// Wait releases any resources associated with the Cmd.
+func (c *Cmd) Wait() error {
+ if c.Process == nil {
+ return errors.New("exec: not started")
+ }
+ if c.ProcessState != nil {
+ return errors.New("exec: Wait was already called")
+ }
+
+ state, err := c.Process.Wait()
+ if err == nil && !state.Success() {
+ err = &ExitError{ProcessState: state}
+ }
+ c.ProcessState = state
+
+ var timer *time.Timer
+ if c.ctxResult != nil {
+ watch := <-c.ctxResult
+ timer = watch.timer
+ // If c.Process.Wait returned an error, prefer that.
+ // Otherwise, report any error from the watchCtx goroutine,
+ // such as a Context cancellation or a WaitDelay overrun.
+ if err == nil && watch.err != nil {
+ err = watch.err
+ }
+ }
+
+ if goroutineErr := c.awaitGoroutines(timer); err == nil {
+ // Report an error from the copying goroutines only if the program otherwise
+ // exited normally on its own. Otherwise, the copying error may be due to the
+ // abnormal termination.
+ err = goroutineErr
+ }
+ closeDescriptors(c.parentIOPipes)
+ c.parentIOPipes = nil
+
+ return err
+}
+
+// awaitGoroutines waits for the results of the goroutines copying data to or
+// from the command's I/O pipes.
+//
+// If c.WaitDelay elapses before the goroutines complete, awaitGoroutines
+// forcibly closes their pipes and returns ErrWaitDelay.
+//
+// If timer is non-nil, it must send to timer.C at the end of c.WaitDelay.
+func (c *Cmd) awaitGoroutines(timer *time.Timer) error {
+ defer func() {
+ if timer != nil {
+ timer.Stop()
+ }
+ c.goroutineErr = nil
+ }()
+
+ if c.goroutineErr == nil {
+ return nil // No running goroutines to await.
+ }
+
+ if timer == nil {
+ if c.WaitDelay == 0 {
+ return <-c.goroutineErr
+ }
+
+ select {
+ case err := <-c.goroutineErr:
+ // Avoid the overhead of starting a timer.
+ return err
+ default:
+ }
+
+ // No existing timer was started: either there is no Context associated with
+ // the command, or c.Process.Wait completed before the Context was done.
+ timer = time.NewTimer(c.WaitDelay)
+ }
+
+ select {
+ case <-timer.C:
+ closeDescriptors(c.parentIOPipes)
+ // Wait for the copying goroutines to finish, but ignore any error
+ // (since it was probably caused by closing the pipes).
+ _ = <-c.goroutineErr
+ return ErrWaitDelay
+
+ case err := <-c.goroutineErr:
+ return err
+ }
+}
+
+// Output runs the command and returns its standard output.
+// Any returned error will usually be of type *ExitError.
+// If c.Stderr was nil, Output populates ExitError.Stderr.
+func (c *Cmd) Output() ([]byte, error) {
+ if c.Stdout != nil {
+ return nil, errors.New("exec: Stdout already set")
+ }
+ var stdout bytes.Buffer
+ c.Stdout = &stdout
+
+ captureErr := c.Stderr == nil
+ if captureErr {
+ c.Stderr = &prefixSuffixSaver{N: 32 << 10}
+ }
+
+ err := c.Run()
+ if err != nil && captureErr {
+ if ee, ok := err.(*ExitError); ok {
+ ee.Stderr = c.Stderr.(*prefixSuffixSaver).Bytes()
+ }
+ }
+ return stdout.Bytes(), err
+}
+
+// CombinedOutput runs the command and returns its combined standard
+// output and standard error.
+func (c *Cmd) CombinedOutput() ([]byte, error) {
+ if c.Stdout != nil {
+ return nil, errors.New("exec: Stdout already set")
+ }
+ if c.Stderr != nil {
+ return nil, errors.New("exec: Stderr already set")
+ }
+ var b bytes.Buffer
+ c.Stdout = &b
+ c.Stderr = &b
+ err := c.Run()
+ return b.Bytes(), err
+}
+
+// StdinPipe returns a pipe that will be connected to the command's
+// standard input when the command starts.
+// The pipe will be closed automatically after Wait sees the command exit.
+// A caller need only call Close to force the pipe to close sooner.
+// For example, if the command being run will not exit until standard input
+// is closed, the caller must close the pipe.
+func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
+ if c.Stdin != nil {
+ return nil, errors.New("exec: Stdin already set")
+ }
+ if c.Process != nil {
+ return nil, errors.New("exec: StdinPipe after process started")
+ }
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ c.Stdin = pr
+ c.childIOFiles = append(c.childIOFiles, pr)
+ c.parentIOPipes = append(c.parentIOPipes, pw)
+ return pw, nil
+}
+
+// StdoutPipe returns a pipe that will be connected to the command's
+// standard output when the command starts.
+//
+// Wait will close the pipe after seeing the command exit, so most callers
+// need not close the pipe themselves. It is thus incorrect to call Wait
+// before all reads from the pipe have completed.
+// For the same reason, it is incorrect to call Run when using StdoutPipe.
+// See the example for idiomatic usage.
+func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
+ if c.Stdout != nil {
+ return nil, errors.New("exec: Stdout already set")
+ }
+ if c.Process != nil {
+ return nil, errors.New("exec: StdoutPipe after process started")
+ }
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ c.Stdout = pw
+ c.childIOFiles = append(c.childIOFiles, pw)
+ c.parentIOPipes = append(c.parentIOPipes, pr)
+ return pr, nil
+}
+
+// StderrPipe returns a pipe that will be connected to the command's
+// standard error when the command starts.
+//
+// Wait will close the pipe after seeing the command exit, so most callers
+// need not close the pipe themselves. It is thus incorrect to call Wait
+// before all reads from the pipe have completed.
+// For the same reason, it is incorrect to use Run when using StderrPipe.
+// See the StdoutPipe example for idiomatic usage.
+func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
+ if c.Stderr != nil {
+ return nil, errors.New("exec: Stderr already set")
+ }
+ if c.Process != nil {
+ return nil, errors.New("exec: StderrPipe after process started")
+ }
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ c.Stderr = pw
+ c.childIOFiles = append(c.childIOFiles, pw)
+ c.parentIOPipes = append(c.parentIOPipes, pr)
+ return pr, nil
+}
+
+// prefixSuffixSaver is an io.Writer which retains the first N bytes
+// and the last N bytes written to it. The Bytes() methods reconstructs
+// it with a pretty error message.
+type prefixSuffixSaver struct {
+ N int // max size of prefix or suffix
+ prefix []byte
+ suffix []byte // ring buffer once len(suffix) == N
+ suffixOff int // offset to write into suffix
+ skipped int64
+
+ // TODO(bradfitz): we could keep one large []byte and use part of it for
+ // the prefix, reserve space for the '... Omitting N bytes ...' message,
+ // then the ring buffer suffix, and just rearrange the ring buffer
+ // suffix when Bytes() is called, but it doesn't seem worth it for
+ // now just for error messages. It's only ~64KB anyway.
+}
+
+func (w *prefixSuffixSaver) Write(p []byte) (n int, err error) {
+ lenp := len(p)
+ p = w.fill(&w.prefix, p)
+
+ // Only keep the last w.N bytes of suffix data.
+ if overage := len(p) - w.N; overage > 0 {
+ p = p[overage:]
+ w.skipped += int64(overage)
+ }
+ p = w.fill(&w.suffix, p)
+
+ // w.suffix is full now if p is non-empty. Overwrite it in a circle.
+ for len(p) > 0 { // 0, 1, or 2 iterations.
+ n := copy(w.suffix[w.suffixOff:], p)
+ p = p[n:]
+ w.skipped += int64(n)
+ w.suffixOff += n
+ if w.suffixOff == w.N {
+ w.suffixOff = 0
+ }
+ }
+ return lenp, nil
+}
+
+// fill appends up to len(p) bytes of p to *dst, such that *dst does not
+// grow larger than w.N. It returns the un-appended suffix of p.
+func (w *prefixSuffixSaver) fill(dst *[]byte, p []byte) (pRemain []byte) {
+ if remain := w.N - len(*dst); remain > 0 {
+ add := minInt(len(p), remain)
+ *dst = append(*dst, p[:add]...)
+ p = p[add:]
+ }
+ return p
+}
+
+func (w *prefixSuffixSaver) Bytes() []byte {
+ if w.suffix == nil {
+ return w.prefix
+ }
+ if w.skipped == 0 {
+ return append(w.prefix, w.suffix...)
+ }
+ var buf bytes.Buffer
+ buf.Grow(len(w.prefix) + len(w.suffix) + 50)
+ buf.Write(w.prefix)
+ buf.WriteString("\n... omitting ")
+ buf.WriteString(strconv.FormatInt(w.skipped, 10))
+ buf.WriteString(" bytes ...\n")
+ buf.Write(w.suffix[w.suffixOff:])
+ buf.Write(w.suffix[:w.suffixOff])
+ return buf.Bytes()
+}
+
+func minInt(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}
+
+// environ returns a best-effort copy of the environment in which the command
+// would be run as it is currently configured. If an error occurs in computing
+// the environment, it is returned alongside the best-effort copy.
+func (c *Cmd) environ() ([]string, error) {
+ var err error
+
+ env := c.Env
+ if env == nil {
+ env, err = execenv.Default(c.SysProcAttr)
+ if err != nil {
+ env = os.Environ()
+ // Note that the non-nil err is preserved despite env being overridden.
+ }
+
+ if c.Dir != "" {
+ switch runtime.GOOS {
+ case "windows", "plan9":
+ // Windows and Plan 9 do not use the PWD variable, so we don't need to
+ // keep it accurate.
+ default:
+ // On POSIX platforms, PWD represents “an absolute pathname of the
+ // current working directory.” Since we are changing the working
+ // directory for the command, we should also update PWD to reflect that.
+ //
+ // Unfortunately, we didn't always do that, so (as proposed in
+ // https://go.dev/issue/50599) to avoid unintended collateral damage we
+ // only implicitly update PWD when Env is nil. That way, we're much
+ // less likely to override an intentional change to the variable.
+ if pwd, absErr := filepath.Abs(c.Dir); absErr == nil {
+ env = append(env, "PWD="+pwd)
+ } else if err == nil {
+ err = absErr
+ }
+ }
+ }
+ }
+
+ env, dedupErr := dedupEnv(env)
+ if err == nil {
+ err = dedupErr
+ }
+ return addCriticalEnv(env), err
+}
+
+// Environ returns a copy of the environment in which the command would be run
+// as it is currently configured.
+func (c *Cmd) Environ() []string {
+ // Intentionally ignore errors: environ returns a best-effort environment no matter what.
+ env, _ := c.environ()
+ return env
+}
+
+// dedupEnv returns a copy of env with any duplicates removed, in favor of
+// later values.
+// Items not of the normal environment "key=value" form are preserved unchanged.
+// Except on Plan 9, items containing NUL characters are removed, and
+// an error is returned along with the remaining values.
+func dedupEnv(env []string) ([]string, error) {
+ return dedupEnvCase(runtime.GOOS == "windows", runtime.GOOS == "plan9", env)
+}
+
+// dedupEnvCase is dedupEnv with a case option for testing.
+// If caseInsensitive is true, the case of keys is ignored.
+// If nulOK is false, items containing NUL characters are allowed.
+func dedupEnvCase(caseInsensitive, nulOK bool, env []string) ([]string, error) {
+ // Construct the output in reverse order, to preserve the
+ // last occurrence of each key.
+ var err error
+ out := make([]string, 0, len(env))
+ saw := make(map[string]bool, len(env))
+ for n := len(env); n > 0; n-- {
+ kv := env[n-1]
+
+ // Reject NUL in environment variables to prevent security issues (#56284);
+ // except on Plan 9, which uses NUL as os.PathListSeparator (#56544).
+ if !nulOK && strings.IndexByte(kv, 0) != -1 {
+ err = errors.New("exec: environment variable contains NUL")
+ continue
+ }
+
+ i := strings.Index(kv, "=")
+ if i == 0 {
+ // We observe in practice keys with a single leading "=" on Windows.
+ // TODO(#49886): Should we consume only the first leading "=" as part
+ // of the key, or parse through arbitrarily many of them until a non-"="?
+ i = strings.Index(kv[1:], "=") + 1
+ }
+ if i < 0 {
+ if kv != "" {
+ // The entry is not of the form "key=value" (as it is required to be).
+ // Leave it as-is for now.
+ // TODO(#52436): should we strip or reject these bogus entries?
+ out = append(out, kv)
+ }
+ continue
+ }
+ k := kv[:i]
+ if caseInsensitive {
+ k = strings.ToLower(k)
+ }
+ if saw[k] {
+ continue
+ }
+
+ saw[k] = true
+ out = append(out, kv)
+ }
+
+ // Now reverse the slice to restore the original order.
+ for i := 0; i < len(out)/2; i++ {
+ j := len(out) - i - 1
+ out[i], out[j] = out[j], out[i]
+ }
+
+ return out, err
+}
+
+// addCriticalEnv adds any critical environment variables that are required
+// (or at least almost always required) on the operating system.
+// Currently this is only used for Windows.
+func addCriticalEnv(env []string) []string {
+ if runtime.GOOS != "windows" {
+ return env
+ }
+ for _, kv := range env {
+ k, _, ok := strings.Cut(kv, "=")
+ if !ok {
+ continue
+ }
+ if strings.EqualFold(k, "SYSTEMROOT") {
+ // We already have it.
+ return env
+ }
+ }
+ return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT"))
+}
+
+// ErrDot indicates that a path lookup resolved to an executable
+// in the current directory due to ‘.’ being in the path, either
+// implicitly or explicitly. See the package documentation for details.
+//
+// Note that functions in this package do not return ErrDot directly.
+// Code should use errors.Is(err, ErrDot), not err == ErrDot,
+// to test whether a returned error err is due to this condition.
+var ErrDot = errors.New("cannot run executable found relative to current directory")
diff --git a/src/os/exec/exec_linux_test.go b/src/os/exec/exec_linux_test.go
new file mode 100644
index 0000000..b9f6b7b
--- /dev/null
+++ b/src/os/exec/exec_linux_test.go
@@ -0,0 +1,45 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux && cgo
+
+// On systems that use glibc, calling malloc can create a new arena,
+// and creating a new arena can read /sys/devices/system/cpu/online.
+// If we are using cgo, we will call malloc when creating a new thread.
+// That can break TestExtraFiles if we create a new thread that creates
+// a new arena and opens the /sys file while we are checking for open
+// file descriptors. Work around the problem by creating threads up front.
+// See issue 25628.
+
+package exec_test
+
+import (
+ "os"
+ "sync"
+ "syscall"
+ "time"
+)
+
+func init() {
+ if os.Getenv("GO_EXEC_TEST_PID") == "" {
+ return
+ }
+
+ // Start some threads. 10 is arbitrary but intended to be enough
+ // to ensure that the code won't have to create any threads itself.
+ // In particular this should be more than the number of threads
+ // the garbage collector might create.
+ const threads = 10
+
+ var wg sync.WaitGroup
+ wg.Add(threads)
+ ts := syscall.NsecToTimespec((100 * time.Microsecond).Nanoseconds())
+ for i := 0; i < threads; i++ {
+ go func() {
+ defer wg.Done()
+ syscall.Nanosleep(&ts, nil)
+ }()
+ }
+ wg.Wait()
+}
diff --git a/src/os/exec/exec_other_test.go b/src/os/exec/exec_other_test.go
new file mode 100644
index 0000000..64c819c
--- /dev/null
+++ b/src/os/exec/exec_other_test.go
@@ -0,0 +1,14 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !unix && !windows
+
+package exec_test
+
+import "os"
+
+var (
+ quitSignal os.Signal = nil
+ pipeSignal os.Signal = nil
+)
diff --git a/src/os/exec/exec_plan9.go b/src/os/exec/exec_plan9.go
new file mode 100644
index 0000000..8920bec
--- /dev/null
+++ b/src/os/exec/exec_plan9.go
@@ -0,0 +1,19 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import "io/fs"
+
+// skipStdinCopyError optionally specifies a function which reports
+// whether the provided stdin copy error should be ignored.
+func skipStdinCopyError(err error) bool {
+ // Ignore hungup errors copying to stdin if the program
+ // completed successfully otherwise.
+ // See Issue 35753.
+ pe, ok := err.(*fs.PathError)
+ return ok &&
+ pe.Op == "write" && pe.Path == "|1" &&
+ pe.Err.Error() == "i/o on hungup channel"
+}
diff --git a/src/os/exec/exec_posix_test.go b/src/os/exec/exec_posix_test.go
new file mode 100644
index 0000000..5d828b3
--- /dev/null
+++ b/src/os/exec/exec_posix_test.go
@@ -0,0 +1,276 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package exec_test
+
+import (
+ "fmt"
+ "internal/testenv"
+ "io"
+ "os"
+ "os/user"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func init() {
+ registerHelperCommand("pwd", cmdPwd)
+}
+
+func cmdPwd(...string) {
+ pwd, err := os.Getwd()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println(pwd)
+}
+
+func TestCredentialNoSetGroups(t *testing.T) {
+ if runtime.GOOS == "android" {
+ maySkipHelperCommand("echo")
+ t.Skip("unsupported on Android")
+ }
+ t.Parallel()
+
+ u, err := user.Current()
+ if err != nil {
+ t.Fatalf("error getting current user: %v", err)
+ }
+
+ uid, err := strconv.Atoi(u.Uid)
+ if err != nil {
+ t.Fatalf("error converting Uid=%s to integer: %v", u.Uid, err)
+ }
+
+ gid, err := strconv.Atoi(u.Gid)
+ if err != nil {
+ t.Fatalf("error converting Gid=%s to integer: %v", u.Gid, err)
+ }
+
+ // If NoSetGroups is true, setgroups isn't called and cmd.Run should succeed
+ cmd := helperCommand(t, "echo", "foo")
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Credential: &syscall.Credential{
+ Uid: uint32(uid),
+ Gid: uint32(gid),
+ NoSetGroups: true,
+ },
+ }
+
+ if err = cmd.Run(); err != nil {
+ t.Errorf("Failed to run command: %v", err)
+ }
+}
+
+// For issue #19314: make sure that SIGSTOP does not cause the process
+// to appear done.
+func TestWaitid(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "pipetest")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Wait for the child process to come up and register any signal handlers.
+ const msg = "O:ping\n"
+ if _, err := io.WriteString(stdin, msg); err != nil {
+ t.Fatal(err)
+ }
+ buf := make([]byte, len(msg))
+ if _, err := io.ReadFull(stdout, buf); err != nil {
+ t.Fatal(err)
+ }
+ // Now leave the pipes open so that the process will hang until we close stdin.
+
+ if err := cmd.Process.Signal(syscall.SIGSTOP); err != nil {
+ cmd.Process.Kill()
+ t.Fatal(err)
+ }
+
+ ch := make(chan error)
+ go func() {
+ ch <- cmd.Wait()
+ }()
+
+ // Give a little time for Wait to block on waiting for the process.
+ // (This is just to give some time to trigger the bug; it should not be
+ // necessary for the test to pass.)
+ if testing.Short() {
+ time.Sleep(1 * time.Millisecond)
+ } else {
+ time.Sleep(10 * time.Millisecond)
+ }
+
+ // This call to Signal should succeed because the process still exists.
+ // (Prior to the fix for #19314, this would fail with os.ErrProcessDone
+ // or an equivalent error.)
+ if err := cmd.Process.Signal(syscall.SIGCONT); err != nil {
+ t.Error(err)
+ syscall.Kill(cmd.Process.Pid, syscall.SIGCONT)
+ }
+
+ // The SIGCONT should allow the process to wake up, notice that stdin
+ // is closed, and exit successfully.
+ stdin.Close()
+ err = <-ch
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+// https://go.dev/issue/50599: if Env is not set explicitly, setting Dir should
+// implicitly update PWD to the correct path, and Environ should list the
+// updated value.
+func TestImplicitPWD(t *testing.T) {
+ t.Parallel()
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ name string
+ dir string
+ want string
+ }{
+ {"empty", "", cwd},
+ {"dot", ".", cwd},
+ {"dotdot", "..", filepath.Dir(cwd)},
+ {"PWD", cwd, cwd},
+ {"PWDdotdot", cwd + string(filepath.Separator) + "..", filepath.Dir(cwd)},
+ }
+
+ for _, tc := range cases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "pwd")
+ if cmd.Env != nil {
+ t.Fatalf("test requires helperCommand not to set Env field")
+ }
+ cmd.Dir = tc.dir
+
+ var pwds []string
+ for _, kv := range cmd.Environ() {
+ if strings.HasPrefix(kv, "PWD=") {
+ pwds = append(pwds, strings.TrimPrefix(kv, "PWD="))
+ }
+ }
+
+ wantPWDs := []string{tc.want}
+ if tc.dir == "" {
+ if _, ok := os.LookupEnv("PWD"); !ok {
+ wantPWDs = nil
+ }
+ }
+ if !reflect.DeepEqual(pwds, wantPWDs) {
+ t.Errorf("PWD entries in cmd.Environ():\n\t%s\nwant:\n\t%s", strings.Join(pwds, "\n\t"), strings.Join(wantPWDs, "\n\t"))
+ }
+
+ cmd.Stderr = new(strings.Builder)
+ out, err := cmd.Output()
+ if err != nil {
+ t.Fatalf("%v:\n%s", err, cmd.Stderr)
+ }
+ got := strings.Trim(string(out), "\r\n")
+ t.Logf("in\n\t%s\n`pwd` reported\n\t%s", tc.dir, got)
+ if got != tc.want {
+ t.Errorf("want\n\t%s", tc.want)
+ }
+ })
+ }
+}
+
+// However, if cmd.Env is set explicitly, setting Dir should not override it.
+// (This checks that the implementation for https://go.dev/issue/50599 doesn't
+// break existing users who may have explicitly mismatched the PWD variable.)
+func TestExplicitPWD(t *testing.T) {
+ t.Parallel()
+
+ maySkipHelperCommand("pwd")
+ testenv.MustHaveSymlink(t)
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ link := filepath.Join(t.TempDir(), "link")
+ if err := os.Symlink(cwd, link); err != nil {
+ t.Fatal(err)
+ }
+
+ // Now link is another equally-valid name for cwd. If we set Dir to one and
+ // PWD to the other, the subprocess should report the PWD version.
+ cases := []struct {
+ name string
+ dir string
+ pwd string
+ }{
+ {name: "original PWD", pwd: cwd},
+ {name: "link PWD", pwd: link},
+ {name: "in link with original PWD", dir: link, pwd: cwd},
+ {name: "in dir with link PWD", dir: cwd, pwd: link},
+ // Ideally we would also like to test what happens if we set PWD to
+ // something totally bogus (or the empty string), but then we would have no
+ // idea what output the subprocess should actually produce: cwd itself may
+ // contain symlinks preserved from the PWD value in the test's environment.
+ }
+ for _, tc := range cases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "pwd")
+ // This is intentionally opposite to the usual order of setting cmd.Dir
+ // and then calling cmd.Environ. Here, we *want* PWD not to match cmd.Dir,
+ // so we don't care whether cmd.Dir is reflected in cmd.Environ.
+ cmd.Env = append(cmd.Environ(), "PWD="+tc.pwd)
+ cmd.Dir = tc.dir
+
+ var pwds []string
+ for _, kv := range cmd.Environ() {
+ if strings.HasPrefix(kv, "PWD=") {
+ pwds = append(pwds, strings.TrimPrefix(kv, "PWD="))
+ }
+ }
+
+ wantPWDs := []string{tc.pwd}
+ if !reflect.DeepEqual(pwds, wantPWDs) {
+ t.Errorf("PWD entries in cmd.Environ():\n\t%s\nwant:\n\t%s", strings.Join(pwds, "\n\t"), strings.Join(wantPWDs, "\n\t"))
+ }
+
+ cmd.Stderr = new(strings.Builder)
+ out, err := cmd.Output()
+ if err != nil {
+ t.Fatalf("%v:\n%s", err, cmd.Stderr)
+ }
+ got := strings.Trim(string(out), "\r\n")
+ t.Logf("in\n\t%s\nwith PWD=%s\nsubprocess os.Getwd() reported\n\t%s", tc.dir, tc.pwd, got)
+ if got != tc.pwd {
+ t.Errorf("want\n\t%s", tc.pwd)
+ }
+ })
+ }
+}
diff --git a/src/os/exec/exec_test.go b/src/os/exec/exec_test.go
new file mode 100644
index 0000000..473f92b
--- /dev/null
+++ b/src/os/exec/exec_test.go
@@ -0,0 +1,1784 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec
+// circular dependency on non-cgo darwin.
+
+package exec_test
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "errors"
+ "flag"
+ "fmt"
+ "internal/poll"
+ "internal/testenv"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "os/exec/internal/fdtest"
+ "os/signal"
+ "path/filepath"
+ "runtime"
+ "runtime/debug"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+)
+
+// haveUnexpectedFDs is set at init time to report whether any file descriptors
+// were open at program start.
+var haveUnexpectedFDs bool
+
+func init() {
+ godebug := os.Getenv("GODEBUG")
+ if godebug != "" {
+ godebug += ","
+ }
+ godebug += "execwait=2"
+ os.Setenv("GODEBUG", godebug)
+
+ if os.Getenv("GO_EXEC_TEST_PID") != "" {
+ return
+ }
+ if runtime.GOOS == "windows" {
+ return
+ }
+ for fd := uintptr(3); fd <= 100; fd++ {
+ if poll.IsPollDescriptor(fd) {
+ continue
+ }
+
+ if fdtest.Exists(fd) {
+ haveUnexpectedFDs = true
+ return
+ }
+ }
+}
+
+// TestMain allows the test binary to impersonate many other binaries,
+// some of which may manipulate os.Stdin, os.Stdout, and/or os.Stderr
+// (and thus cannot run as an ordinary Test function, since the testing
+// package monkey-patches those variables before running tests).
+func TestMain(m *testing.M) {
+ flag.Parse()
+
+ pid := os.Getpid()
+ if os.Getenv("GO_EXEC_TEST_PID") == "" {
+ os.Setenv("GO_EXEC_TEST_PID", strconv.Itoa(pid))
+
+ code := m.Run()
+ if code == 0 && flag.Lookup("test.run").Value.String() == "" && flag.Lookup("test.list").Value.String() == "" {
+ for cmd := range helperCommands {
+ if _, ok := helperCommandUsed.Load(cmd); !ok {
+ fmt.Fprintf(os.Stderr, "helper command unused: %q\n", cmd)
+ code = 1
+ }
+ }
+ }
+
+ if !testing.Short() {
+ // Run a couple of GC cycles to increase the odds of detecting
+ // process leaks using the finalizers installed by GODEBUG=execwait=2.
+ runtime.GC()
+ runtime.GC()
+ }
+
+ os.Exit(code)
+ }
+
+ args := flag.Args()
+ if len(args) == 0 {
+ fmt.Fprintf(os.Stderr, "No command\n")
+ os.Exit(2)
+ }
+
+ cmd, args := args[0], args[1:]
+ f, ok := helperCommands[cmd]
+ if !ok {
+ fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
+ os.Exit(2)
+ }
+ f(args...)
+ os.Exit(0)
+}
+
+// registerHelperCommand registers a command that the test process can impersonate.
+// A command should be registered in the same source file in which it is used.
+// If all tests are run and pass, all registered commands must be used.
+// (This prevents stale commands from accreting if tests are removed or
+// refactored over time.)
+func registerHelperCommand(name string, f func(...string)) {
+ if helperCommands[name] != nil {
+ panic("duplicate command registered: " + name)
+ }
+ helperCommands[name] = f
+}
+
+// maySkipHelperCommand records that the test that uses the named helper command
+// was invoked, but may call Skip on the test before actually calling
+// helperCommand.
+func maySkipHelperCommand(name string) {
+ helperCommandUsed.Store(name, true)
+}
+
+// helperCommand returns an exec.Cmd that will run the named helper command.
+func helperCommand(t *testing.T, name string, args ...string) *exec.Cmd {
+ t.Helper()
+ return helperCommandContext(t, nil, name, args...)
+}
+
+// helperCommandContext is like helperCommand, but also accepts a Context under
+// which to run the command.
+func helperCommandContext(t *testing.T, ctx context.Context, name string, args ...string) (cmd *exec.Cmd) {
+ helperCommandUsed.LoadOrStore(name, true)
+
+ t.Helper()
+ testenv.MustHaveExec(t)
+
+ cs := append([]string{name}, args...)
+ if ctx != nil {
+ cmd = exec.CommandContext(ctx, exePath(t), cs...)
+ } else {
+ cmd = exec.Command(exePath(t), cs...)
+ }
+ return cmd
+}
+
+// exePath returns the path to the running executable.
+func exePath(t testing.TB) string {
+ exeOnce.Do(func() {
+ // Use os.Executable instead of os.Args[0] in case the caller modifies
+ // cmd.Dir: if the test binary is invoked like "./exec.test", it should
+ // not fail spuriously.
+ exeOnce.path, exeOnce.err = os.Executable()
+ })
+
+ if exeOnce.err != nil {
+ if t == nil {
+ panic(exeOnce.err)
+ }
+ t.Fatal(exeOnce.err)
+ }
+
+ return exeOnce.path
+}
+
+var exeOnce struct {
+ path string
+ err error
+ sync.Once
+}
+
+var helperCommandUsed sync.Map
+
+var helperCommands = map[string]func(...string){
+ "echo": cmdEcho,
+ "echoenv": cmdEchoEnv,
+ "cat": cmdCat,
+ "pipetest": cmdPipeTest,
+ "stdinClose": cmdStdinClose,
+ "exit": cmdExit,
+ "describefiles": cmdDescribeFiles,
+ "stderrfail": cmdStderrFail,
+ "yes": cmdYes,
+ "hang": cmdHang,
+}
+
+func cmdEcho(args ...string) {
+ iargs := []any{}
+ for _, s := range args {
+ iargs = append(iargs, s)
+ }
+ fmt.Println(iargs...)
+}
+
+func cmdEchoEnv(args ...string) {
+ for _, s := range args {
+ fmt.Println(os.Getenv(s))
+ }
+}
+
+func cmdCat(args ...string) {
+ if len(args) == 0 {
+ io.Copy(os.Stdout, os.Stdin)
+ return
+ }
+ exit := 0
+ for _, fn := range args {
+ f, err := os.Open(fn)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ exit = 2
+ } else {
+ defer f.Close()
+ io.Copy(os.Stdout, f)
+ }
+ }
+ os.Exit(exit)
+}
+
+func cmdPipeTest(...string) {
+ bufr := bufio.NewReader(os.Stdin)
+ for {
+ line, _, err := bufr.ReadLine()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ os.Exit(1)
+ }
+ if bytes.HasPrefix(line, []byte("O:")) {
+ os.Stdout.Write(line)
+ os.Stdout.Write([]byte{'\n'})
+ } else if bytes.HasPrefix(line, []byte("E:")) {
+ os.Stderr.Write(line)
+ os.Stderr.Write([]byte{'\n'})
+ } else {
+ os.Exit(1)
+ }
+ }
+}
+
+func cmdStdinClose(...string) {
+ b, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+ if s := string(b); s != stdinCloseTestString {
+ fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
+ os.Exit(1)
+ }
+}
+
+func cmdExit(args ...string) {
+ n, _ := strconv.Atoi(args[0])
+ os.Exit(n)
+}
+
+func cmdDescribeFiles(args ...string) {
+ f := os.NewFile(3, fmt.Sprintf("fd3"))
+ ln, err := net.FileListener(f)
+ if err == nil {
+ fmt.Printf("fd3: listener %s\n", ln.Addr())
+ ln.Close()
+ }
+}
+
+func cmdStderrFail(...string) {
+ fmt.Fprintf(os.Stderr, "some stderr text\n")
+ os.Exit(1)
+}
+
+func cmdYes(args ...string) {
+ if len(args) == 0 {
+ args = []string{"y"}
+ }
+ s := strings.Join(args, " ") + "\n"
+ for {
+ _, err := os.Stdout.WriteString(s)
+ if err != nil {
+ os.Exit(1)
+ }
+ }
+}
+
+func TestEcho(t *testing.T) {
+ t.Parallel()
+
+ bs, err := helperCommand(t, "echo", "foo bar", "baz").Output()
+ if err != nil {
+ t.Errorf("echo: %v", err)
+ }
+ if g, e := string(bs), "foo bar baz\n"; g != e {
+ t.Errorf("echo: want %q, got %q", e, g)
+ }
+}
+
+func TestCommandRelativeName(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "echo", "foo")
+
+ // Run our own binary as a relative path
+ // (e.g. "_test/exec.test") our parent directory.
+ base := filepath.Base(os.Args[0]) // "exec.test"
+ dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test"
+ if dir == "." {
+ t.Skip("skipping; running test at root somehow")
+ }
+ parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec"
+ dirBase := filepath.Base(dir) // "_test"
+ if dirBase == "." {
+ t.Skipf("skipping; unexpected shallow dir of %q", dir)
+ }
+
+ cmd.Path = filepath.Join(dirBase, base)
+ cmd.Dir = parentDir
+
+ out, err := cmd.Output()
+ if err != nil {
+ t.Errorf("echo: %v", err)
+ }
+ if g, e := string(out), "foo\n"; g != e {
+ t.Errorf("echo: want %q, got %q", e, g)
+ }
+}
+
+func TestCatStdin(t *testing.T) {
+ t.Parallel()
+
+ // Cat, testing stdin and stdout.
+ input := "Input string\nLine 2"
+ p := helperCommand(t, "cat")
+ p.Stdin = strings.NewReader(input)
+ bs, err := p.Output()
+ if err != nil {
+ t.Errorf("cat: %v", err)
+ }
+ s := string(bs)
+ if s != input {
+ t.Errorf("cat: want %q, got %q", input, s)
+ }
+}
+
+func TestEchoFileRace(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "echo")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatalf("StdinPipe: %v", err)
+ }
+ if err := cmd.Start(); err != nil {
+ t.Fatalf("Start: %v", err)
+ }
+ wrote := make(chan bool)
+ go func() {
+ defer close(wrote)
+ fmt.Fprint(stdin, "echo\n")
+ }()
+ if err := cmd.Wait(); err != nil {
+ t.Fatalf("Wait: %v", err)
+ }
+ <-wrote
+}
+
+func TestCatGoodAndBadFile(t *testing.T) {
+ t.Parallel()
+
+ // Testing combined output and error values.
+ bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
+ if _, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err)
+ }
+ errLine, body, ok := strings.Cut(string(bs), "\n")
+ if !ok {
+ t.Fatalf("expected two lines from cat; got %q", bs)
+ }
+ if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") {
+ t.Errorf("expected stderr to complain about file; got %q", errLine)
+ }
+ if !strings.Contains(body, "func TestCatGoodAndBadFile(t *testing.T)") {
+ t.Errorf("expected test code; got %q (len %d)", body, len(body))
+ }
+}
+
+func TestNoExistExecutable(t *testing.T) {
+ t.Parallel()
+
+ // Can't run a non-existent executable
+ err := exec.Command("/no-exist-executable").Run()
+ if err == nil {
+ t.Error("expected error from /no-exist-executable")
+ }
+}
+
+func TestExitStatus(t *testing.T) {
+ t.Parallel()
+
+ // Test that exit values are returned correctly
+ cmd := helperCommand(t, "exit", "42")
+ err := cmd.Run()
+ want := "exit status 42"
+ switch runtime.GOOS {
+ case "plan9":
+ want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid())
+ }
+ if werr, ok := err.(*exec.ExitError); ok {
+ if s := werr.Error(); s != want {
+ t.Errorf("from exit 42 got exit %q, want %q", s, want)
+ }
+ } else {
+ t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err)
+ }
+}
+
+func TestExitCode(t *testing.T) {
+ t.Parallel()
+
+ // Test that exit code are returned correctly
+ cmd := helperCommand(t, "exit", "42")
+ cmd.Run()
+ want := 42
+ if runtime.GOOS == "plan9" {
+ want = 1
+ }
+ got := cmd.ProcessState.ExitCode()
+ if want != got {
+ t.Errorf("ExitCode got %d, want %d", got, want)
+ }
+
+ cmd = helperCommand(t, "/no-exist-executable")
+ cmd.Run()
+ want = 2
+ if runtime.GOOS == "plan9" {
+ want = 1
+ }
+ got = cmd.ProcessState.ExitCode()
+ if want != got {
+ t.Errorf("ExitCode got %d, want %d", got, want)
+ }
+
+ cmd = helperCommand(t, "exit", "255")
+ cmd.Run()
+ want = 255
+ if runtime.GOOS == "plan9" {
+ want = 1
+ }
+ got = cmd.ProcessState.ExitCode()
+ if want != got {
+ t.Errorf("ExitCode got %d, want %d", got, want)
+ }
+
+ cmd = helperCommand(t, "cat")
+ cmd.Run()
+ want = 0
+ got = cmd.ProcessState.ExitCode()
+ if want != got {
+ t.Errorf("ExitCode got %d, want %d", got, want)
+ }
+
+ // Test when command does not call Run().
+ cmd = helperCommand(t, "cat")
+ want = -1
+ got = cmd.ProcessState.ExitCode()
+ if want != got {
+ t.Errorf("ExitCode got %d, want %d", got, want)
+ }
+}
+
+func TestPipes(t *testing.T) {
+ t.Parallel()
+
+ check := func(what string, err error) {
+ if err != nil {
+ t.Fatalf("%s: %v", what, err)
+ }
+ }
+ // Cat, testing stdin and stdout.
+ c := helperCommand(t, "pipetest")
+ stdin, err := c.StdinPipe()
+ check("StdinPipe", err)
+ stdout, err := c.StdoutPipe()
+ check("StdoutPipe", err)
+ stderr, err := c.StderrPipe()
+ check("StderrPipe", err)
+
+ outbr := bufio.NewReader(stdout)
+ errbr := bufio.NewReader(stderr)
+ line := func(what string, br *bufio.Reader) string {
+ line, _, err := br.ReadLine()
+ if err != nil {
+ t.Fatalf("%s: %v", what, err)
+ }
+ return string(line)
+ }
+
+ err = c.Start()
+ check("Start", err)
+
+ _, err = stdin.Write([]byte("O:I am output\n"))
+ check("first stdin Write", err)
+ if g, e := line("first output line", outbr), "O:I am output"; g != e {
+ t.Errorf("got %q, want %q", g, e)
+ }
+
+ _, err = stdin.Write([]byte("E:I am error\n"))
+ check("second stdin Write", err)
+ if g, e := line("first error line", errbr), "E:I am error"; g != e {
+ t.Errorf("got %q, want %q", g, e)
+ }
+
+ _, err = stdin.Write([]byte("O:I am output2\n"))
+ check("third stdin Write 3", err)
+ if g, e := line("second output line", outbr), "O:I am output2"; g != e {
+ t.Errorf("got %q, want %q", g, e)
+ }
+
+ stdin.Close()
+ err = c.Wait()
+ check("Wait", err)
+}
+
+const stdinCloseTestString = "Some test string."
+
+// Issue 6270.
+func TestStdinClose(t *testing.T) {
+ t.Parallel()
+
+ check := func(what string, err error) {
+ if err != nil {
+ t.Fatalf("%s: %v", what, err)
+ }
+ }
+ cmd := helperCommand(t, "stdinClose")
+ stdin, err := cmd.StdinPipe()
+ check("StdinPipe", err)
+ // Check that we can access methods of the underlying os.File.`
+ if _, ok := stdin.(interface {
+ Fd() uintptr
+ }); !ok {
+ t.Error("can't access methods of underlying *os.File")
+ }
+ check("Start", cmd.Start())
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ defer wg.Wait()
+ go func() {
+ defer wg.Done()
+
+ _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString))
+ check("Copy", err)
+
+ // Before the fix, this next line would race with cmd.Wait.
+ if err := stdin.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
+ t.Errorf("Close: %v", err)
+ }
+ }()
+
+ check("Wait", cmd.Wait())
+}
+
+// Issue 17647.
+// It used to be the case that TestStdinClose, above, would fail when
+// run under the race detector. This test is a variant of TestStdinClose
+// that also used to fail when run under the race detector.
+// This test is run by cmd/dist under the race detector to verify that
+// the race detector no longer reports any problems.
+func TestStdinCloseRace(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "stdinClose")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatalf("StdinPipe: %v", err)
+ }
+ if err := cmd.Start(); err != nil {
+ t.Fatalf("Start: %v", err)
+
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+ defer wg.Wait()
+
+ go func() {
+ defer wg.Done()
+ // We don't check the error return of Kill. It is
+ // possible that the process has already exited, in
+ // which case Kill will return an error "process
+ // already finished". The purpose of this test is to
+ // see whether the race detector reports an error; it
+ // doesn't matter whether this Kill succeeds or not.
+ cmd.Process.Kill()
+ }()
+
+ go func() {
+ defer wg.Done()
+ // Send the wrong string, so that the child fails even
+ // if the other goroutine doesn't manage to kill it first.
+ // This test is to check that the race detector does not
+ // falsely report an error, so it doesn't matter how the
+ // child process fails.
+ io.Copy(stdin, strings.NewReader("unexpected string"))
+ if err := stdin.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
+ t.Errorf("stdin.Close: %v", err)
+ }
+ }()
+
+ if err := cmd.Wait(); err == nil {
+ t.Fatalf("Wait: succeeded unexpectedly")
+ }
+}
+
+// Issue 5071
+func TestPipeLookPathLeak(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skip("we don't currently suppore counting open handles on windows")
+ }
+ // Not parallel: checks for leaked file descriptors
+
+ openFDs := func() []uintptr {
+ var fds []uintptr
+ for i := uintptr(0); i < 100; i++ {
+ if fdtest.Exists(i) {
+ fds = append(fds, i)
+ }
+ }
+ return fds
+ }
+
+ old := map[uintptr]bool{}
+ for _, fd := range openFDs() {
+ old[fd] = true
+ }
+
+ for i := 0; i < 6; i++ {
+ cmd := exec.Command("something-that-does-not-exist-executable")
+ cmd.StdoutPipe()
+ cmd.StderrPipe()
+ cmd.StdinPipe()
+ if err := cmd.Run(); err == nil {
+ t.Fatal("unexpected success")
+ }
+ }
+
+ // Since this test is not running in parallel, we don't expect any new file
+ // descriptors to be opened while it runs. However, if there are additional
+ // FDs present at the start of the test (for example, opened by libc), those
+ // may be closed due to a timeout of some sort. Allow those to go away, but
+ // check that no new FDs are added.
+ for _, fd := range openFDs() {
+ if !old[fd] {
+ t.Errorf("leaked file descriptor %v", fd)
+ }
+ }
+}
+
+func TestExtraFiles(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test in short mode that would build a helper binary")
+ }
+
+ if haveUnexpectedFDs {
+ // The point of this test is to make sure that any
+ // descriptors we open are marked close-on-exec.
+ // If haveUnexpectedFDs is true then there were other
+ // descriptors open when we started the test,
+ // so those descriptors are clearly not close-on-exec,
+ // and they will confuse the test. We could modify
+ // the test to expect those descriptors to remain open,
+ // but since we don't know where they came from or what
+ // they are doing, that seems fragile. For example,
+ // perhaps they are from the startup code on this
+ // system for some reason. Also, this test is not
+ // system-specific; as long as most systems do not skip
+ // the test, we will still be testing what we care about.
+ t.Skip("skipping test because test was run with FDs open")
+ }
+
+ testenv.MustHaveExec(t)
+ testenv.MustHaveGoBuild(t)
+
+ // This test runs with cgo disabled. External linking needs cgo, so
+ // it doesn't work if external linking is required.
+ testenv.MustInternalLink(t, false)
+
+ if runtime.GOOS == "windows" {
+ t.Skipf("skipping test on %q", runtime.GOOS)
+ }
+
+ // Force network usage, to verify the epoll (or whatever) fd
+ // doesn't leak to the child,
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ln.Close()
+
+ // Make sure duplicated fds don't leak to the child.
+ f, err := ln.(*net.TCPListener).File()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ ln2, err := net.FileListener(f)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ln2.Close()
+
+ // Force TLS root certs to be loaded (which might involve
+ // cgo), to make sure none of that potential C code leaks fds.
+ ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+ // quiet expected TLS handshake error "remote error: bad certificate"
+ ts.Config.ErrorLog = log.New(io.Discard, "", 0)
+ ts.StartTLS()
+ defer ts.Close()
+ _, err = http.Get(ts.URL)
+ if err == nil {
+ t.Errorf("success trying to fetch %s; want an error", ts.URL)
+ }
+
+ tf, err := os.CreateTemp("", "")
+ if err != nil {
+ t.Fatalf("TempFile: %v", err)
+ }
+ defer os.Remove(tf.Name())
+ defer tf.Close()
+
+ const text = "Hello, fd 3!"
+ _, err = tf.Write([]byte(text))
+ if err != nil {
+ t.Fatalf("Write: %v", err)
+ }
+ _, err = tf.Seek(0, io.SeekStart)
+ if err != nil {
+ t.Fatalf("Seek: %v", err)
+ }
+
+ tempdir := t.TempDir()
+ exe := filepath.Join(tempdir, "read3.exe")
+
+ c := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "read3.go")
+ // Build the test without cgo, so that C library functions don't
+ // open descriptors unexpectedly. See issue 25628.
+ c.Env = append(os.Environ(), "CGO_ENABLED=0")
+ if output, err := c.CombinedOutput(); err != nil {
+ t.Logf("go build -o %s read3.go\n%s", exe, output)
+ t.Fatalf("go build failed: %v", err)
+ }
+
+ // Use a deadline to try to get some output even if the program hangs.
+ ctx := context.Background()
+ if deadline, ok := t.Deadline(); ok {
+ // Leave a 20% grace period to flush output, which may be large on the
+ // linux/386 builders because we're running the subprocess under strace.
+ deadline = deadline.Add(-time.Until(deadline) / 5)
+
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithDeadline(ctx, deadline)
+ defer cancel()
+ }
+
+ c = exec.CommandContext(ctx, exe)
+ var stdout, stderr strings.Builder
+ c.Stdout = &stdout
+ c.Stderr = &stderr
+ c.ExtraFiles = []*os.File{tf}
+ if runtime.GOOS == "illumos" {
+ // Some facilities in illumos are implemented via access
+ // to /proc by libc; such accesses can briefly occupy a
+ // low-numbered fd. If this occurs concurrently with the
+ // test that checks for leaked descriptors, the check can
+ // become confused and report a spurious leaked descriptor.
+ // (See issue #42431 for more detailed analysis.)
+ //
+ // Attempt to constrain the use of additional threads in the
+ // child process to make this test less flaky:
+ c.Env = append(os.Environ(), "GOMAXPROCS=1")
+ }
+ err = c.Run()
+ if err != nil {
+ t.Fatalf("Run: %v\n--- stdout:\n%s--- stderr:\n%s", err, stdout.String(), stderr.String())
+ }
+ if stdout.String() != text {
+ t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
+ }
+}
+
+func TestExtraFilesRace(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ maySkipHelperCommand("describefiles")
+ t.Skip("no operating system support; skipping")
+ }
+ t.Parallel()
+
+ listen := func() net.Listener {
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ return ln
+ }
+ listenerFile := func(ln net.Listener) *os.File {
+ f, err := ln.(*net.TCPListener).File()
+ if err != nil {
+ t.Fatal(err)
+ }
+ return f
+ }
+ runCommand := func(c *exec.Cmd, out chan<- string) {
+ bout, err := c.CombinedOutput()
+ if err != nil {
+ out <- "ERROR:" + err.Error()
+ } else {
+ out <- string(bout)
+ }
+ }
+
+ for i := 0; i < 10; i++ {
+ if testing.Short() && i >= 3 {
+ break
+ }
+ la := listen()
+ ca := helperCommand(t, "describefiles")
+ ca.ExtraFiles = []*os.File{listenerFile(la)}
+ lb := listen()
+ cb := helperCommand(t, "describefiles")
+ cb.ExtraFiles = []*os.File{listenerFile(lb)}
+ ares := make(chan string)
+ bres := make(chan string)
+ go runCommand(ca, ares)
+ go runCommand(cb, bres)
+ if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
+ t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
+ }
+ if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
+ t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
+ }
+ la.Close()
+ lb.Close()
+ for _, f := range ca.ExtraFiles {
+ f.Close()
+ }
+ for _, f := range cb.ExtraFiles {
+ f.Close()
+ }
+ }
+}
+
+type delayedInfiniteReader struct{}
+
+func (delayedInfiniteReader) Read(b []byte) (int, error) {
+ time.Sleep(100 * time.Millisecond)
+ for i := range b {
+ b[i] = 'x'
+ }
+ return len(b), nil
+}
+
+// Issue 9173: ignore stdin pipe writes if the program completes successfully.
+func TestIgnorePipeErrorOnSuccess(t *testing.T) {
+ t.Parallel()
+
+ testWith := func(r io.Reader) func(*testing.T) {
+ return func(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "echo", "foo")
+ var out strings.Builder
+ cmd.Stdin = r
+ cmd.Stdout = &out
+ if err := cmd.Run(); err != nil {
+ t.Fatal(err)
+ }
+ if got, want := out.String(), "foo\n"; got != want {
+ t.Errorf("output = %q; want %q", got, want)
+ }
+ }
+ }
+ t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20))))
+ t.Run("Infinite", testWith(delayedInfiniteReader{}))
+}
+
+type badWriter struct{}
+
+func (w *badWriter) Write(data []byte) (int, error) {
+ return 0, io.ErrUnexpectedEOF
+}
+
+func TestClosePipeOnCopyError(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "yes")
+ cmd.Stdout = new(badWriter)
+ err := cmd.Run()
+ if err == nil {
+ t.Errorf("yes unexpectedly completed successfully")
+ }
+}
+
+func TestOutputStderrCapture(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "stderrfail")
+ _, err := cmd.Output()
+ ee, ok := err.(*exec.ExitError)
+ if !ok {
+ t.Fatalf("Output error type = %T; want ExitError", err)
+ }
+ got := string(ee.Stderr)
+ want := "some stderr text\n"
+ if got != want {
+ t.Errorf("ExitError.Stderr = %q; want %q", got, want)
+ }
+}
+
+func TestContext(t *testing.T) {
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ c := helperCommandContext(t, ctx, "pipetest")
+ stdin, err := c.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ stdout, err := c.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := c.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := stdin.Write([]byte("O:hi\n")); err != nil {
+ t.Fatal(err)
+ }
+ buf := make([]byte, 5)
+ n, err := io.ReadFull(stdout, buf)
+ if n != len(buf) || err != nil || string(buf) != "O:hi\n" {
+ t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n])
+ }
+ go cancel()
+
+ if err := c.Wait(); err == nil {
+ t.Fatal("expected Wait failure")
+ }
+}
+
+func TestContextCancel(t *testing.T) {
+ if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm64" {
+ maySkipHelperCommand("cat")
+ testenv.SkipFlaky(t, 42061)
+ }
+
+ // To reduce noise in the final goroutine dump,
+ // let other parallel tests complete if possible.
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ c := helperCommandContext(t, ctx, "cat")
+
+ stdin, err := c.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer stdin.Close()
+
+ if err := c.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ // At this point the process is alive. Ensure it by sending data to stdin.
+ if _, err := io.WriteString(stdin, "echo"); err != nil {
+ t.Fatal(err)
+ }
+
+ cancel()
+
+ // Calling cancel should have killed the process, so writes
+ // should now fail. Give the process a little while to die.
+ start := time.Now()
+ delay := 1 * time.Millisecond
+ for {
+ if _, err := io.WriteString(stdin, "echo"); err != nil {
+ break
+ }
+
+ if time.Since(start) > time.Minute {
+ // Panic instead of calling t.Fatal so that we get a goroutine dump.
+ // We want to know exactly what the os/exec goroutines got stuck on.
+ debug.SetTraceback("system")
+ panic("canceling context did not stop program")
+ }
+
+ // Back off exponentially (up to 1-second sleeps) to give the OS time to
+ // terminate the process.
+ delay *= 2
+ if delay > 1*time.Second {
+ delay = 1 * time.Second
+ }
+ time.Sleep(delay)
+ }
+
+ if err := c.Wait(); err == nil {
+ t.Error("program unexpectedly exited successfully")
+ } else {
+ t.Logf("exit status: %v", err)
+ }
+}
+
+// test that environment variables are de-duped.
+func TestDedupEnvEcho(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "echoenv", "FOO")
+ cmd.Env = append(cmd.Environ(), "FOO=bad", "FOO=good")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := strings.TrimSpace(string(out)), "good"; got != want {
+ t.Errorf("output = %q; want %q", got, want)
+ }
+}
+
+func TestEnvNULCharacter(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ t.Skip("plan9 explicitly allows NUL in the environment")
+ }
+ cmd := helperCommand(t, "echoenv", "FOO", "BAR")
+ cmd.Env = append(cmd.Environ(), "FOO=foo\x00BAR=bar")
+ out, err := cmd.CombinedOutput()
+ if err == nil {
+ t.Errorf("output = %q; want error", string(out))
+ }
+}
+
+func TestString(t *testing.T) {
+ t.Parallel()
+
+ echoPath, err := exec.LookPath("echo")
+ if err != nil {
+ t.Skip(err)
+ }
+ tests := [...]struct {
+ path string
+ args []string
+ want string
+ }{
+ {"echo", nil, echoPath},
+ {"echo", []string{"a"}, echoPath + " a"},
+ {"echo", []string{"a", "b"}, echoPath + " a b"},
+ }
+ for _, test := range tests {
+ cmd := exec.Command(test.path, test.args...)
+ if got := cmd.String(); got != test.want {
+ t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want)
+ }
+ }
+}
+
+func TestStringPathNotResolved(t *testing.T) {
+ t.Parallel()
+
+ _, err := exec.LookPath("makemeasandwich")
+ if err == nil {
+ t.Skip("wow, thanks")
+ }
+
+ cmd := exec.Command("makemeasandwich", "-lettuce")
+ want := "makemeasandwich -lettuce"
+ if got := cmd.String(); got != want {
+ t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want)
+ }
+}
+
+func TestNoPath(t *testing.T) {
+ err := new(exec.Cmd).Start()
+ want := "exec: no command"
+ if err == nil || err.Error() != want {
+ t.Errorf("new(Cmd).Start() = %v, want %q", err, want)
+ }
+}
+
+// TestDoubleStartLeavesPipesOpen checks for a regression in which calling
+// Start twice, which returns an error on the second call, would spuriously
+// close the pipes established in the first call.
+func TestDoubleStartLeavesPipesOpen(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "pipetest")
+ in, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := cmd.Wait(); err != nil {
+ t.Error(err)
+ }
+ })
+
+ if err := cmd.Start(); err == nil || !strings.HasSuffix(err.Error(), "already started") {
+ t.Fatalf("second call to Start returned a nil; want an 'already started' error")
+ }
+
+ outc := make(chan []byte, 1)
+ go func() {
+ b, err := io.ReadAll(out)
+ if err != nil {
+ t.Error(err)
+ }
+ outc <- b
+ }()
+
+ const msg = "O:Hello, pipe!\n"
+
+ _, err = io.WriteString(in, msg)
+ if err != nil {
+ t.Fatal(err)
+ }
+ in.Close()
+
+ b := <-outc
+ if !bytes.Equal(b, []byte(msg)) {
+ t.Fatalf("read %q from stdout pipe; want %q", b, msg)
+ }
+}
+
+func cmdHang(args ...string) {
+ sleep, err := time.ParseDuration(args[0])
+ if err != nil {
+ panic(err)
+ }
+
+ fs := flag.NewFlagSet("hang", flag.ExitOnError)
+ exitOnInterrupt := fs.Bool("interrupt", false, "if true, commands should exit 0 on os.Interrupt")
+ subsleep := fs.Duration("subsleep", 0, "amount of time for the 'hang' helper to leave an orphaned subprocess sleeping with stderr open")
+ probe := fs.Duration("probe", 0, "if nonzero, the 'hang' helper should write to stderr at this interval, and exit nonzero if a write fails")
+ read := fs.Bool("read", false, "if true, the 'hang' helper should read stdin to completion before sleeping")
+ fs.Parse(args[1:])
+
+ pid := os.Getpid()
+
+ if *subsleep != 0 {
+ cmd := exec.Command(exePath(nil), "hang", subsleep.String(), "-read=true", "-probe="+probe.String())
+ cmd.Stdin = os.Stdin
+ cmd.Stderr = os.Stderr
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ cmd.Start()
+
+ buf := new(strings.Builder)
+ if _, err := io.Copy(buf, out); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ cmd.Process.Kill()
+ cmd.Wait()
+ os.Exit(1)
+ }
+ fmt.Fprintf(os.Stderr, "%d: started %d: %v\n", pid, cmd.Process.Pid, cmd)
+ go cmd.Wait() // Release resources if cmd happens not to outlive this process.
+ }
+
+ if *exitOnInterrupt {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ go func() {
+ sig := <-c
+ fmt.Fprintf(os.Stderr, "%d: received %v\n", pid, sig)
+ os.Exit(0)
+ }()
+ } else {
+ signal.Ignore(os.Interrupt)
+ }
+
+ // Signal that the process is set up by closing stdout.
+ os.Stdout.Close()
+
+ if *read {
+ if pipeSignal != nil {
+ signal.Ignore(pipeSignal)
+ }
+ r := bufio.NewReader(os.Stdin)
+ for {
+ line, err := r.ReadBytes('\n')
+ if len(line) > 0 {
+ // Ignore write errors: we want to keep reading even if stderr is closed.
+ fmt.Fprintf(os.Stderr, "%d: read %s", pid, line)
+ }
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%d: finished read: %v", pid, err)
+ break
+ }
+ }
+ }
+
+ if *probe != 0 {
+ ticker := time.NewTicker(*probe)
+ go func() {
+ for range ticker.C {
+ if _, err := fmt.Fprintf(os.Stderr, "%d: ok\n", pid); err != nil {
+ os.Exit(1)
+ }
+ }
+ }()
+ }
+
+ if sleep != 0 {
+ time.Sleep(sleep)
+ fmt.Fprintf(os.Stderr, "%d: slept %v\n", pid, sleep)
+ }
+}
+
+// A tickReader reads an unbounded sequence of timestamps at no more than a
+// fixed interval.
+type tickReader struct {
+ interval time.Duration
+ lastTick time.Time
+ s string
+}
+
+func newTickReader(interval time.Duration) *tickReader {
+ return &tickReader{interval: interval}
+}
+
+func (r *tickReader) Read(p []byte) (n int, err error) {
+ if len(r.s) == 0 {
+ if d := r.interval - time.Since(r.lastTick); d > 0 {
+ time.Sleep(d)
+ }
+ r.lastTick = time.Now()
+ r.s = r.lastTick.Format(time.RFC3339Nano + "\n")
+ }
+
+ n = copy(p, r.s)
+ r.s = r.s[n:]
+ return n, nil
+}
+
+func startHang(t *testing.T, ctx context.Context, hangTime time.Duration, interrupt os.Signal, waitDelay time.Duration, flags ...string) *exec.Cmd {
+ t.Helper()
+
+ args := append([]string{hangTime.String()}, flags...)
+ cmd := helperCommandContext(t, ctx, "hang", args...)
+ cmd.Stdin = newTickReader(1 * time.Millisecond)
+ cmd.Stderr = new(strings.Builder)
+ if interrupt == nil {
+ cmd.Cancel = nil
+ } else {
+ cmd.Cancel = func() error {
+ return cmd.Process.Signal(interrupt)
+ }
+ }
+ cmd.WaitDelay = waitDelay
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log(cmd)
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Wait for cmd to close stdout to signal that its handlers are installed.
+ buf := new(strings.Builder)
+ if _, err := io.Copy(buf, out); err != nil {
+ t.Error(err)
+ cmd.Process.Kill()
+ cmd.Wait()
+ t.FailNow()
+ }
+ if buf.Len() > 0 {
+ t.Logf("stdout %v:\n%s", cmd.Args, buf)
+ }
+
+ return cmd
+}
+
+func TestWaitInterrupt(t *testing.T) {
+ t.Parallel()
+
+ // tooLong is an arbitrary duration that is expected to be much longer than
+ // the test runs, but short enough that leaked processes will eventually exit
+ // on their own.
+ const tooLong = 10 * time.Minute
+
+ // Control case: with no cancellation and no WaitDelay, we should wait for the
+ // process to exit.
+ t.Run("Wait", func(t *testing.T) {
+ t.Parallel()
+ cmd := startHang(t, context.Background(), 1*time.Millisecond, os.Kill, 0)
+ err := cmd.Wait()
+ t.Logf("stderr:\n%s", cmd.Stderr)
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ if err != nil {
+ t.Errorf("Wait: %v; want <nil>", err)
+ }
+ if ps := cmd.ProcessState; !ps.Exited() {
+ t.Errorf("cmd did not exit: %v", ps)
+ } else if code := ps.ExitCode(); code != 0 {
+ t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
+ }
+ })
+
+ // With a very long WaitDelay and no Cancel function, we should wait for the
+ // process to exit even if the command's Context is cancelled.
+ t.Run("WaitDelay", func(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skipf("skipping: os.Interrupt is not implemented on Windows")
+ }
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cmd := startHang(t, ctx, tooLong, nil, tooLong, "-interrupt=true")
+ cancel()
+
+ time.Sleep(1 * time.Millisecond)
+ // At this point cmd should still be running (because we passed nil to
+ // startHang for the cancel signal). Sending it an explicit Interrupt signal
+ // should succeed.
+ if err := cmd.Process.Signal(os.Interrupt); err != nil {
+ t.Error(err)
+ }
+
+ err := cmd.Wait()
+ t.Logf("stderr:\n%s", cmd.Stderr)
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ // This program exits with status 0,
+ // but pretty much always does so during the wait delay.
+ // Since the Cmd itself didn't do anything to stop the process when the
+ // context expired, a successful exit is valid (even if late) and does
+ // not merit a non-nil error.
+ if err != nil {
+ t.Errorf("Wait: %v; want nil", err)
+ }
+ if ps := cmd.ProcessState; !ps.Exited() {
+ t.Errorf("cmd did not exit: %v", ps)
+ } else if code := ps.ExitCode(); code != 0 {
+ t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
+ }
+ })
+
+ // If the context is cancelled and the Cancel function sends os.Kill,
+ // the process should be terminated immediately, and its output
+ // pipes should be closed (causing Wait to return) after WaitDelay
+ // even if a child process is still writing to them.
+ t.Run("SIGKILL-hang", func(t *testing.T) {
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cmd := startHang(t, ctx, tooLong, os.Kill, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms")
+ cancel()
+ err := cmd.Wait()
+ t.Logf("stderr:\n%s", cmd.Stderr)
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ // This test should kill the child process after 10ms,
+ // leaving a grandchild process writing probes in a loop.
+ // The child process should be reported as failed,
+ // and the grandchild will exit (or die by SIGPIPE) once the
+ // stderr pipe is closed.
+ if ee := new(*exec.ExitError); !errors.As(err, ee) {
+ t.Errorf("Wait error = %v; want %T", err, *ee)
+ }
+ })
+
+ // If the process exits with status 0 but leaves a child behind writing
+ // to its output pipes, Wait should only wait for WaitDelay before
+ // closing the pipes and returning. Wait should return ErrWaitDelay
+ // to indicate that the piped output may be incomplete even though the
+ // command returned a “success” code.
+ t.Run("Exit-hang", func(t *testing.T) {
+ t.Parallel()
+
+ cmd := startHang(t, context.Background(), 1*time.Millisecond, nil, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms")
+ err := cmd.Wait()
+ t.Logf("stderr:\n%s", cmd.Stderr)
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ // This child process should exit immediately,
+ // leaving a grandchild process writing probes in a loop.
+ // Since the child has no ExitError to report but we did not
+ // read all of its output, Wait should return ErrWaitDelay.
+ if !errors.Is(err, exec.ErrWaitDelay) {
+ t.Errorf("Wait error = %v; want %T", err, exec.ErrWaitDelay)
+ }
+ })
+
+ // If the Cancel function sends a signal that the process can handle, and it
+ // handles that signal without actually exiting, then it should be terminated
+ // after the WaitDelay.
+ t.Run("SIGINT-ignored", func(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skipf("skipping: os.Interrupt is not implemented on Windows")
+ }
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cmd := startHang(t, ctx, tooLong, os.Interrupt, 10*time.Millisecond, "-interrupt=false")
+ cancel()
+ err := cmd.Wait()
+ t.Logf("stderr:\n%s", cmd.Stderr)
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ // This command ignores SIGINT, sleeping until it is killed.
+ // Wait should return the usual error for a killed process.
+ if ee := new(*exec.ExitError); !errors.As(err, ee) {
+ t.Errorf("Wait error = %v; want %T", err, *ee)
+ }
+ })
+
+ // If the process handles the cancellation signal and exits with status 0,
+ // Wait should report a non-nil error (because the process had to be
+ // interrupted), and it should be a context error (because there is no error
+ // to report from the child process itself).
+ t.Run("SIGINT-handled", func(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skipf("skipping: os.Interrupt is not implemented on Windows")
+ }
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cmd := startHang(t, ctx, tooLong, os.Interrupt, 0, "-interrupt=true")
+ cancel()
+ err := cmd.Wait()
+ t.Logf("stderr:\n%s", cmd.Stderr)
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ if !errors.Is(err, ctx.Err()) {
+ t.Errorf("Wait error = %v; want %v", err, ctx.Err())
+ }
+ if ps := cmd.ProcessState; !ps.Exited() {
+ t.Errorf("cmd did not exit: %v", ps)
+ } else if code := ps.ExitCode(); code != 0 {
+ t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
+ }
+ })
+
+ // If the Cancel function sends SIGQUIT, it should be handled in the usual
+ // way: a Go program should dump its goroutines and exit with non-success
+ // status. (We expect SIGQUIT to be a common pattern in real-world use.)
+ t.Run("SIGQUIT", func(t *testing.T) {
+ if quitSignal == nil {
+ t.Skipf("skipping: SIGQUIT is not supported on %v", runtime.GOOS)
+ }
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cmd := startHang(t, ctx, tooLong, quitSignal, 0)
+ cancel()
+ err := cmd.Wait()
+ t.Logf("stderr:\n%s", cmd.Stderr)
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ if ee := new(*exec.ExitError); !errors.As(err, ee) {
+ t.Errorf("Wait error = %v; want %v", err, ctx.Err())
+ }
+
+ if ps := cmd.ProcessState; !ps.Exited() {
+ t.Errorf("cmd did not exit: %v", ps)
+ } else if code := ps.ExitCode(); code != 2 {
+ // The default os/signal handler exits with code 2.
+ t.Errorf("cmd.ProcessState.ExitCode() = %v; want 2", code)
+ }
+
+ if !strings.Contains(fmt.Sprint(cmd.Stderr), "\n\ngoroutine ") {
+ t.Errorf("cmd.Stderr does not contain a goroutine dump")
+ }
+ })
+}
+
+func TestCancelErrors(t *testing.T) {
+ t.Parallel()
+
+ // If Cancel returns a non-ErrProcessDone error and the process
+ // exits successfully, Wait should wrap the error from Cancel.
+ t.Run("success after error", func(t *testing.T) {
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ cmd := helperCommandContext(t, ctx, "pipetest")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ errArbitrary := errors.New("arbitrary error")
+ cmd.Cancel = func() error {
+ stdin.Close()
+ t.Logf("Cancel returning %v", errArbitrary)
+ return errArbitrary
+ }
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ cancel()
+
+ err = cmd.Wait()
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+ if !errors.Is(err, errArbitrary) || err == errArbitrary {
+ t.Errorf("Wait error = %v; want an error wrapping %v", err, errArbitrary)
+ }
+ })
+
+ // If Cancel returns an error equivalent to ErrProcessDone,
+ // Wait should ignore that error. (ErrProcessDone indicates that the
+ // process was already done before we tried to interrupt it — maybe we
+ // just didn't notice because Wait hadn't been called yet.)
+ t.Run("success after ErrProcessDone", func(t *testing.T) {
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ cmd := helperCommandContext(t, ctx, "pipetest")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // We intentionally race Cancel against the process exiting,
+ // but ensure that the process wins the race (and return ErrProcessDone
+ // from Cancel to report that).
+ interruptCalled := make(chan struct{})
+ done := make(chan struct{})
+ cmd.Cancel = func() error {
+ close(interruptCalled)
+ <-done
+ t.Logf("Cancel returning an error wrapping ErrProcessDone")
+ return fmt.Errorf("%w: stdout closed", os.ErrProcessDone)
+ }
+
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ cancel()
+ <-interruptCalled
+ stdin.Close()
+ io.Copy(io.Discard, stdout) // reaches EOF when the process exits
+ close(done)
+
+ err = cmd.Wait()
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+ if err != nil {
+ t.Errorf("Wait error = %v; want nil", err)
+ }
+ })
+
+ // If Cancel returns an error and the process is killed after
+ // WaitDelay, Wait should report the usual SIGKILL ExitError, not the
+ // error from Cancel.
+ t.Run("killed after error", func(t *testing.T) {
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ cmd := helperCommandContext(t, ctx, "pipetest")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer stdin.Close()
+
+ errArbitrary := errors.New("arbitrary error")
+ var interruptCalled atomic.Bool
+ cmd.Cancel = func() error {
+ t.Logf("Cancel called")
+ interruptCalled.Store(true)
+ return errArbitrary
+ }
+ cmd.WaitDelay = 1 * time.Millisecond
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ cancel()
+
+ err = cmd.Wait()
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ // Ensure that Cancel actually had the opportunity to
+ // return the error.
+ if !interruptCalled.Load() {
+ t.Errorf("Cancel was not called when the context was canceled")
+ }
+
+ // This test should kill the child process after 1ms,
+ // To maximize compatibility with existing uses of exec.CommandContext, the
+ // resulting error should be an exec.ExitError without additional wrapping.
+ if ee, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("Wait error = %v; want %T", err, *ee)
+ }
+ })
+
+ // If Cancel returns ErrProcessDone but the process is not actually done
+ // (and has to be killed), Wait should report the usual SIGKILL ExitError,
+ // not the error from Cancel.
+ t.Run("killed after spurious ErrProcessDone", func(t *testing.T) {
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ cmd := helperCommandContext(t, ctx, "pipetest")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer stdin.Close()
+
+ var interruptCalled atomic.Bool
+ cmd.Cancel = func() error {
+ t.Logf("Cancel returning an error wrapping ErrProcessDone")
+ interruptCalled.Store(true)
+ return fmt.Errorf("%w: stdout closed", os.ErrProcessDone)
+ }
+ cmd.WaitDelay = 1 * time.Millisecond
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ cancel()
+
+ err = cmd.Wait()
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ // Ensure that Cancel actually had the opportunity to
+ // return the error.
+ if !interruptCalled.Load() {
+ t.Errorf("Cancel was not called when the context was canceled")
+ }
+
+ // This test should kill the child process after 1ms,
+ // To maximize compatibility with existing uses of exec.CommandContext, the
+ // resulting error should be an exec.ExitError without additional wrapping.
+ if ee, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("Wait error of type %T; want %T", err, ee)
+ }
+ })
+
+ // If Cancel returns an error and the process exits with an
+ // unsuccessful exit code, the process error should take precedence over the
+ // Cancel error.
+ t.Run("nonzero exit after error", func(t *testing.T) {
+ t.Parallel()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ cmd := helperCommandContext(t, ctx, "stderrfail")
+ stderr, err := cmd.StderrPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ errArbitrary := errors.New("arbitrary error")
+ interrupted := make(chan struct{})
+ cmd.Cancel = func() error {
+ close(interrupted)
+ return errArbitrary
+ }
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ cancel()
+ <-interrupted
+ io.Copy(io.Discard, stderr)
+
+ err = cmd.Wait()
+ t.Logf("[%d] %v", cmd.Process.Pid, err)
+
+ if ee, ok := err.(*exec.ExitError); !ok || ee.ProcessState.ExitCode() != 1 {
+ t.Errorf("Wait error = %v; want exit status 1", err)
+ }
+ })
+}
+
+// TestConcurrentExec is a regression test for https://go.dev/issue/61080.
+//
+// Forking multiple child processes concurrently would sometimes hang on darwin.
+// (This test hung on a gomote with -count=100 after only a few iterations.)
+func TestConcurrentExec(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+
+ // This test will spawn nHangs subprocesses that hang reading from stdin,
+ // and nExits subprocesses that exit immediately.
+ //
+ // When issue #61080 was present, a long-lived "hang" subprocess would
+ // occasionally inherit the fork/exec status pipe from an "exit" subprocess,
+ // causing the parent process (which expects to see an EOF on that pipe almost
+ // immediately) to unexpectedly block on reading from the pipe.
+ var (
+ nHangs = runtime.GOMAXPROCS(0)
+ nExits = runtime.GOMAXPROCS(0)
+ hangs, exits sync.WaitGroup
+ )
+ hangs.Add(nHangs)
+ exits.Add(nExits)
+
+ // ready is done when the goroutines have done as much work as possible to
+ // prepare to create subprocesses. It isn't strictly necessary for the test,
+ // but helps to increase the repro rate by making it more likely that calls to
+ // syscall.StartProcess for the "hang" and "exit" goroutines overlap.
+ var ready sync.WaitGroup
+ ready.Add(nHangs + nExits)
+
+ for i := 0; i < nHangs; i++ {
+ go func() {
+ defer hangs.Done()
+
+ cmd := helperCommandContext(t, ctx, "pipetest")
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ ready.Done()
+ t.Error(err)
+ return
+ }
+ cmd.Cancel = stdin.Close
+ ready.Done()
+
+ ready.Wait()
+ if err := cmd.Start(); err != nil {
+ if !errors.Is(err, context.Canceled) {
+ t.Error(err)
+ }
+ return
+ }
+
+ cmd.Wait()
+ }()
+ }
+
+ for i := 0; i < nExits; i++ {
+ go func() {
+ defer exits.Done()
+
+ cmd := helperCommandContext(t, ctx, "exit", "0")
+ ready.Done()
+
+ ready.Wait()
+ if err := cmd.Run(); err != nil {
+ t.Error(err)
+ }
+ }()
+ }
+
+ exits.Wait()
+ cancel()
+ hangs.Wait()
+}
diff --git a/src/os/exec/exec_unix.go b/src/os/exec/exec_unix.go
new file mode 100644
index 0000000..3ed672a
--- /dev/null
+++ b/src/os/exec/exec_unix.go
@@ -0,0 +1,24 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !plan9 && !windows
+
+package exec
+
+import (
+ "io/fs"
+ "syscall"
+)
+
+// skipStdinCopyError optionally specifies a function which reports
+// whether the provided stdin copy error should be ignored.
+func skipStdinCopyError(err error) bool {
+ // Ignore EPIPE errors copying to stdin if the program
+ // completed successfully otherwise.
+ // See Issue 9173.
+ pe, ok := err.(*fs.PathError)
+ return ok &&
+ pe.Op == "write" && pe.Path == "|1" &&
+ pe.Err == syscall.EPIPE
+}
diff --git a/src/os/exec/exec_unix_test.go b/src/os/exec/exec_unix_test.go
new file mode 100644
index 0000000..d26c93a
--- /dev/null
+++ b/src/os/exec/exec_unix_test.go
@@ -0,0 +1,17 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package exec_test
+
+import (
+ "os"
+ "syscall"
+)
+
+var (
+ quitSignal os.Signal = syscall.SIGQUIT
+ pipeSignal os.Signal = syscall.SIGPIPE
+)
diff --git a/src/os/exec/exec_windows.go b/src/os/exec/exec_windows.go
new file mode 100644
index 0000000..e7a2ee6
--- /dev/null
+++ b/src/os/exec/exec_windows.go
@@ -0,0 +1,23 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "io/fs"
+ "syscall"
+)
+
+// skipStdinCopyError optionally specifies a function which reports
+// whether the provided stdin copy error should be ignored.
+func skipStdinCopyError(err error) bool {
+ // Ignore ERROR_BROKEN_PIPE and ERROR_NO_DATA errors copying
+ // to stdin if the program completed successfully otherwise.
+ // See Issue 20445.
+ const _ERROR_NO_DATA = syscall.Errno(0xe8)
+ pe, ok := err.(*fs.PathError)
+ return ok &&
+ pe.Op == "write" && pe.Path == "|1" &&
+ (pe.Err == syscall.ERROR_BROKEN_PIPE || pe.Err == _ERROR_NO_DATA)
+}
diff --git a/src/os/exec/exec_windows_test.go b/src/os/exec/exec_windows_test.go
new file mode 100644
index 0000000..efd3710
--- /dev/null
+++ b/src/os/exec/exec_windows_test.go
@@ -0,0 +1,109 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package exec_test
+
+import (
+ "fmt"
+ "internal/testenv"
+ "io"
+ "os"
+ "os/exec"
+ "strconv"
+ "strings"
+ "syscall"
+ "testing"
+)
+
+var (
+ quitSignal os.Signal = nil
+ pipeSignal os.Signal = syscall.SIGPIPE
+)
+
+func init() {
+ registerHelperCommand("pipehandle", cmdPipeHandle)
+}
+
+func cmdPipeHandle(args ...string) {
+ handle, _ := strconv.ParseUint(args[0], 16, 64)
+ pipe := os.NewFile(uintptr(handle), "")
+ _, err := fmt.Fprint(pipe, args[1])
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "writing to pipe failed: %v\n", err)
+ os.Exit(1)
+ }
+ pipe.Close()
+}
+
+func TestPipePassing(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Error(err)
+ }
+ const marker = "arrakis, dune, desert planet"
+ childProc := helperCommand(t, "pipehandle", strconv.FormatUint(uint64(w.Fd()), 16), marker)
+ childProc.SysProcAttr = &syscall.SysProcAttr{AdditionalInheritedHandles: []syscall.Handle{syscall.Handle(w.Fd())}}
+ err = childProc.Start()
+ if err != nil {
+ t.Error(err)
+ }
+ w.Close()
+ response, err := io.ReadAll(r)
+ if err != nil {
+ t.Error(err)
+ }
+ r.Close()
+ if string(response) != marker {
+ t.Errorf("got %q; want %q", string(response), marker)
+ }
+ err = childProc.Wait()
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestNoInheritHandles(t *testing.T) {
+ t.Parallel()
+
+ cmd := testenv.Command(t, "cmd", "/c exit 88")
+ cmd.SysProcAttr = &syscall.SysProcAttr{NoInheritHandles: true}
+ err := cmd.Run()
+ exitError, ok := err.(*exec.ExitError)
+ if !ok {
+ t.Fatalf("got error %v; want ExitError", err)
+ }
+ if exitError.ExitCode() != 88 {
+ t.Fatalf("got exit code %d; want 88", exitError.ExitCode())
+ }
+}
+
+// start a child process without the user code explicitly starting
+// with a copy of the parent's SYSTEMROOT.
+// (See issue 25210.)
+func TestChildCriticalEnv(t *testing.T) {
+ t.Parallel()
+ cmd := helperCommand(t, "echoenv", "SYSTEMROOT")
+
+ // Explicitly remove SYSTEMROOT from the command's environment.
+ var env []string
+ for _, kv := range cmd.Environ() {
+ k, _, ok := strings.Cut(kv, "=")
+ if !ok || !strings.EqualFold(k, "SYSTEMROOT") {
+ env = append(env, kv)
+ }
+ }
+ cmd.Env = env
+
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.TrimSpace(string(out)) == "" {
+ t.Error("no SYSTEMROOT found")
+ }
+}
diff --git a/src/os/exec/internal/fdtest/exists_plan9.go b/src/os/exec/internal/fdtest/exists_plan9.go
new file mode 100644
index 0000000..8886e06
--- /dev/null
+++ b/src/os/exec/internal/fdtest/exists_plan9.go
@@ -0,0 +1,20 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build plan9
+
+package fdtest
+
+import (
+ "syscall"
+)
+
+const errBadFd = syscall.ErrorString("fd out of range or not open")
+
+// Exists returns true if fd is a valid file descriptor.
+func Exists(fd uintptr) bool {
+ var buf [1]byte
+ _, err := syscall.Fstat(int(fd), buf[:])
+ return err != errBadFd
+}
diff --git a/src/os/exec/internal/fdtest/exists_test.go b/src/os/exec/internal/fdtest/exists_test.go
new file mode 100644
index 0000000..a02dddf
--- /dev/null
+++ b/src/os/exec/internal/fdtest/exists_test.go
@@ -0,0 +1,21 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fdtest
+
+import (
+ "os"
+ "runtime"
+ "testing"
+)
+
+func TestExists(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skip("Exists not implemented for windows")
+ }
+
+ if !Exists(os.Stdout.Fd()) {
+ t.Errorf("Exists(%d) got false want true", os.Stdout.Fd())
+ }
+}
diff --git a/src/os/exec/internal/fdtest/exists_unix.go b/src/os/exec/internal/fdtest/exists_unix.go
new file mode 100644
index 0000000..472a802
--- /dev/null
+++ b/src/os/exec/internal/fdtest/exists_unix.go
@@ -0,0 +1,19 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || wasm
+
+// Package fdtest provides test helpers for working with file descriptors across exec.
+package fdtest
+
+import (
+ "syscall"
+)
+
+// Exists returns true if fd is a valid file descriptor.
+func Exists(fd uintptr) bool {
+ var s syscall.Stat_t
+ err := syscall.Fstat(int(fd), &s)
+ return err != syscall.EBADF
+}
diff --git a/src/os/exec/internal/fdtest/exists_windows.go b/src/os/exec/internal/fdtest/exists_windows.go
new file mode 100644
index 0000000..72b8ccf
--- /dev/null
+++ b/src/os/exec/internal/fdtest/exists_windows.go
@@ -0,0 +1,12 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package fdtest
+
+// Exists is not implemented on windows and panics.
+func Exists(fd uintptr) bool {
+ panic("unimplemented")
+}
diff --git a/src/os/exec/internal_test.go b/src/os/exec/internal_test.go
new file mode 100644
index 0000000..68d517f
--- /dev/null
+++ b/src/os/exec/internal_test.go
@@ -0,0 +1,61 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "io"
+ "testing"
+)
+
+func TestPrefixSuffixSaver(t *testing.T) {
+ tests := []struct {
+ N int
+ writes []string
+ want string
+ }{
+ {
+ N: 2,
+ writes: nil,
+ want: "",
+ },
+ {
+ N: 2,
+ writes: []string{"a"},
+ want: "a",
+ },
+ {
+ N: 2,
+ writes: []string{"abc", "d"},
+ want: "abcd",
+ },
+ {
+ N: 2,
+ writes: []string{"abc", "d", "e"},
+ want: "ab\n... omitting 1 bytes ...\nde",
+ },
+ {
+ N: 2,
+ writes: []string{"ab______________________yz"},
+ want: "ab\n... omitting 22 bytes ...\nyz",
+ },
+ {
+ N: 2,
+ writes: []string{"ab_______________________y", "z"},
+ want: "ab\n... omitting 23 bytes ...\nyz",
+ },
+ }
+ for i, tt := range tests {
+ w := &prefixSuffixSaver{N: tt.N}
+ for _, s := range tt.writes {
+ n, err := io.WriteString(w, s)
+ if err != nil || n != len(s) {
+ t.Errorf("%d. WriteString(%q) = %v, %v; want %v, %v", i, s, n, err, len(s), nil)
+ }
+ }
+ if got := string(w.Bytes()); got != tt.want {
+ t.Errorf("%d. Bytes = %q; want %q", i, got, tt.want)
+ }
+ }
+}
diff --git a/src/os/exec/lp_linux_test.go b/src/os/exec/lp_linux_test.go
new file mode 100644
index 0000000..60cb13e
--- /dev/null
+++ b/src/os/exec/lp_linux_test.go
@@ -0,0 +1,88 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec_test
+
+import (
+ "errors"
+ "internal/syscall/unix"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "syscall"
+ "testing"
+)
+
+func TestFindExecutableVsNoexec(t *testing.T) {
+ t.Parallel()
+
+ // This test case relies on faccessat2(2) syscall, which appeared in Linux v5.8.
+ if major, minor := unix.KernelVersion(); major < 5 || (major == 5 && minor < 8) {
+ t.Skip("requires Linux kernel v5.8 with faccessat2(2) syscall")
+ }
+
+ tmp := t.TempDir()
+
+ // Create a tmpfs mount.
+ err := syscall.Mount("tmpfs", tmp, "tmpfs", 0, "")
+ if testenv.SyscallIsNotSupported(err) {
+ // Usually this means lack of CAP_SYS_ADMIN, but there might be
+ // other reasons, especially in restricted test environments.
+ t.Skipf("requires ability to mount tmpfs (%v)", err)
+ } else if err != nil {
+ t.Fatalf("mount %s failed: %v", tmp, err)
+ }
+ t.Cleanup(func() {
+ if err := syscall.Unmount(tmp, 0); err != nil {
+ t.Error(err)
+ }
+ })
+
+ // Create an executable.
+ path := filepath.Join(tmp, "program")
+ err = os.WriteFile(path, []byte("#!/bin/sh\necho 123\n"), 0o755)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Check that it works as expected.
+ _, err = exec.LookPath(path)
+ if err != nil {
+ t.Fatalf("findExecutable: got %v, want nil", err)
+ }
+
+ for {
+ err = exec.Command(path).Run()
+ if err == nil {
+ break
+ }
+ if errors.Is(err, syscall.ETXTBSY) {
+ // A fork+exec in another process may be holding open the FD that we used
+ // to write the executable (see https://go.dev/issue/22315).
+ // Since the descriptor should have CLOEXEC set, the problem should resolve
+ // as soon as the forked child reaches its exec call.
+ // Keep retrying until that happens.
+ } else {
+ t.Fatalf("exec: got %v, want nil", err)
+ }
+ }
+
+ // Remount with noexec flag.
+ err = syscall.Mount("", tmp, "", syscall.MS_REMOUNT|syscall.MS_NOEXEC, "")
+ if testenv.SyscallIsNotSupported(err) {
+ t.Skipf("requires ability to re-mount tmpfs (%v)", err)
+ } else if err != nil {
+ t.Fatalf("remount %s with noexec failed: %v", tmp, err)
+ }
+
+ if err := exec.Command(path).Run(); err == nil {
+ t.Fatal("exec on noexec filesystem: got nil, want error")
+ }
+
+ _, err = exec.LookPath(path)
+ if err == nil {
+ t.Fatalf("LookPath: got nil, want error")
+ }
+}
diff --git a/src/os/exec/lp_plan9.go b/src/os/exec/lp_plan9.go
new file mode 100644
index 0000000..9344b14
--- /dev/null
+++ b/src/os/exec/lp_plan9.go
@@ -0,0 +1,66 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "errors"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = errors.New("executable file not found in $path")
+
+func findExecutable(file string) error {
+ d, err := os.Stat(file)
+ if err != nil {
+ return err
+ }
+ if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
+ return nil
+ }
+ return fs.ErrPermission
+}
+
+// LookPath searches for an executable named file in the
+// directories named by the path environment variable.
+// If file begins with "/", "#", "./", or "../", it is tried
+// directly and the path is not consulted.
+// On success, the result is an absolute path.
+//
+// In older versions of Go, LookPath could return a path relative to the current directory.
+// As of Go 1.19, LookPath will instead return that path along with an error satisfying
+// errors.Is(err, ErrDot). See the package documentation for more details.
+func LookPath(file string) (string, error) {
+ // skip the path lookup for these prefixes
+ skip := []string{"/", "#", "./", "../"}
+
+ for _, p := range skip {
+ if strings.HasPrefix(file, p) {
+ err := findExecutable(file)
+ if err == nil {
+ return file, nil
+ }
+ return "", &Error{file, err}
+ }
+ }
+
+ path := os.Getenv("path")
+ for _, dir := range filepath.SplitList(path) {
+ path := filepath.Join(dir, file)
+ if err := findExecutable(path); err == nil {
+ if !filepath.IsAbs(path) {
+ if execerrdot.Value() != "0" {
+ return path, &Error{file, ErrDot}
+ }
+ execerrdot.IncNonDefault()
+ }
+ return path, nil
+ }
+ }
+ return "", &Error{file, ErrNotFound}
+}
diff --git a/src/os/exec/lp_test.go b/src/os/exec/lp_test.go
new file mode 100644
index 0000000..77d8e84
--- /dev/null
+++ b/src/os/exec/lp_test.go
@@ -0,0 +1,33 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "testing"
+)
+
+var nonExistentPaths = []string{
+ "some-non-existent-path",
+ "non-existent-path/slashed",
+}
+
+func TestLookPathNotFound(t *testing.T) {
+ for _, name := range nonExistentPaths {
+ path, err := LookPath(name)
+ if err == nil {
+ t.Fatalf("LookPath found %q in $PATH", name)
+ }
+ if path != "" {
+ t.Fatalf("LookPath path == %q when err != nil", path)
+ }
+ perr, ok := err.(*Error)
+ if !ok {
+ t.Fatal("LookPath error is not an exec.Error")
+ }
+ if perr.Name != name {
+ t.Fatalf("want Error name %q, got %q", name, perr.Name)
+ }
+ }
+}
diff --git a/src/os/exec/lp_unix.go b/src/os/exec/lp_unix.go
new file mode 100644
index 0000000..fd2c6ef
--- /dev/null
+++ b/src/os/exec/lp_unix.go
@@ -0,0 +1,82 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package exec
+
+import (
+ "errors"
+ "internal/syscall/unix"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+)
+
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = errors.New("executable file not found in $PATH")
+
+func findExecutable(file string) error {
+ d, err := os.Stat(file)
+ if err != nil {
+ return err
+ }
+ m := d.Mode()
+ if m.IsDir() {
+ return syscall.EISDIR
+ }
+ err = unix.Eaccess(file, unix.X_OK)
+ // ENOSYS means Eaccess is not available or not implemented.
+ // EPERM can be returned by Linux containers employing seccomp.
+ // In both cases, fall back to checking the permission bits.
+ if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
+ return err
+ }
+ if m&0111 != 0 {
+ return nil
+ }
+ return fs.ErrPermission
+}
+
+// LookPath searches for an executable named file in the
+// directories named by the PATH environment variable.
+// If file contains a slash, it is tried directly and the PATH is not consulted.
+// Otherwise, on success, the result is an absolute path.
+//
+// In older versions of Go, LookPath could return a path relative to the current directory.
+// As of Go 1.19, LookPath will instead return that path along with an error satisfying
+// errors.Is(err, ErrDot). See the package documentation for more details.
+func LookPath(file string) (string, error) {
+ // NOTE(rsc): I wish we could use the Plan 9 behavior here
+ // (only bypass the path if file begins with / or ./ or ../)
+ // but that would not match all the Unix shells.
+
+ if strings.Contains(file, "/") {
+ err := findExecutable(file)
+ if err == nil {
+ return file, nil
+ }
+ return "", &Error{file, err}
+ }
+ path := os.Getenv("PATH")
+ for _, dir := range filepath.SplitList(path) {
+ if dir == "" {
+ // Unix shell semantics: path element "" means "."
+ dir = "."
+ }
+ path := filepath.Join(dir, file)
+ if err := findExecutable(path); err == nil {
+ if !filepath.IsAbs(path) {
+ if execerrdot.Value() != "0" {
+ return path, &Error{file, ErrDot}
+ }
+ execerrdot.IncNonDefault()
+ }
+ return path, nil
+ }
+ }
+ return "", &Error{file, ErrNotFound}
+}
diff --git a/src/os/exec/lp_unix_test.go b/src/os/exec/lp_unix_test.go
new file mode 100644
index 0000000..181b1f0
--- /dev/null
+++ b/src/os/exec/lp_unix_test.go
@@ -0,0 +1,50 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package exec
+
+import (
+ "os"
+ "testing"
+)
+
+func TestLookPathUnixEmptyPath(t *testing.T) {
+ // Not parallel: uses os.Chdir.
+
+ tmp, err := os.MkdirTemp("", "TestLookPathUnixEmptyPath")
+ if err != nil {
+ t.Fatal("TempDir failed: ", err)
+ }
+ defer os.RemoveAll(tmp)
+ wd, err := os.Getwd()
+ if err != nil {
+ t.Fatal("Getwd failed: ", err)
+ }
+ err = os.Chdir(tmp)
+ if err != nil {
+ t.Fatal("Chdir failed: ", err)
+ }
+ defer os.Chdir(wd)
+
+ f, err := os.OpenFile("exec_me", os.O_CREATE|os.O_EXCL, 0700)
+ if err != nil {
+ t.Fatal("OpenFile failed: ", err)
+ }
+ err = f.Close()
+ if err != nil {
+ t.Fatal("Close failed: ", err)
+ }
+
+ t.Setenv("PATH", "")
+
+ path, err := LookPath("exec_me")
+ if err == nil {
+ t.Fatal("LookPath found exec_me in empty $PATH")
+ }
+ if path != "" {
+ t.Fatalf("LookPath path == %q when err != nil", path)
+ }
+}
diff --git a/src/os/exec/lp_wasm.go b/src/os/exec/lp_wasm.go
new file mode 100644
index 0000000..f2c8e9c
--- /dev/null
+++ b/src/os/exec/lp_wasm.go
@@ -0,0 +1,23 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasm
+
+package exec
+
+import (
+ "errors"
+)
+
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = errors.New("executable file not found in $PATH")
+
+// LookPath searches for an executable named file in the
+// directories named by the PATH environment variable.
+// If file contains a slash, it is tried directly and the PATH is not consulted.
+// The result may be an absolute path or a path relative to the current directory.
+func LookPath(file string) (string, error) {
+ // Wasm can not execute processes, so act as if there are no executables at all.
+ return "", &Error{file, ErrNotFound}
+}
diff --git a/src/os/exec/lp_windows.go b/src/os/exec/lp_windows.go
new file mode 100644
index 0000000..066d38d
--- /dev/null
+++ b/src/os/exec/lp_windows.go
@@ -0,0 +1,145 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "errors"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+)
+
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = errors.New("executable file not found in %PATH%")
+
+func chkStat(file string) error {
+ d, err := os.Stat(file)
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return fs.ErrPermission
+ }
+ return nil
+}
+
+func hasExt(file string) bool {
+ i := strings.LastIndex(file, ".")
+ if i < 0 {
+ return false
+ }
+ return strings.LastIndexAny(file, `:\/`) < i
+}
+
+func findExecutable(file string, exts []string) (string, error) {
+ if len(exts) == 0 {
+ return file, chkStat(file)
+ }
+ if hasExt(file) {
+ if chkStat(file) == nil {
+ return file, nil
+ }
+ }
+ for _, e := range exts {
+ if f := file + e; chkStat(f) == nil {
+ return f, nil
+ }
+ }
+ return "", fs.ErrNotExist
+}
+
+// LookPath searches for an executable named file in the
+// directories named by the PATH environment variable.
+// LookPath also uses PATHEXT environment variable to match
+// a suitable candidate.
+// If file contains a slash, it is tried directly and the PATH is not consulted.
+// Otherwise, on success, the result is an absolute path.
+//
+// In older versions of Go, LookPath could return a path relative to the current directory.
+// As of Go 1.19, LookPath will instead return that path along with an error satisfying
+// errors.Is(err, ErrDot). See the package documentation for more details.
+func LookPath(file string) (string, error) {
+ var exts []string
+ x := os.Getenv(`PATHEXT`)
+ if x != "" {
+ for _, e := range strings.Split(strings.ToLower(x), `;`) {
+ if e == "" {
+ continue
+ }
+ if e[0] != '.' {
+ e = "." + e
+ }
+ exts = append(exts, e)
+ }
+ } else {
+ exts = []string{".com", ".exe", ".bat", ".cmd"}
+ }
+
+ if strings.ContainsAny(file, `:\/`) {
+ f, err := findExecutable(file, exts)
+ if err == nil {
+ return f, nil
+ }
+ return "", &Error{file, err}
+ }
+
+ // On Windows, creating the NoDefaultCurrentDirectoryInExePath
+ // environment variable (with any value or no value!) signals that
+ // path lookups should skip the current directory.
+ // In theory we are supposed to call NeedCurrentDirectoryForExePathW
+ // "as the registry location of this environment variable can change"
+ // but that seems exceedingly unlikely: it would break all users who
+ // have configured their environment this way!
+ // https://docs.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-needcurrentdirectoryforexepathw
+ // See also go.dev/issue/43947.
+ var (
+ dotf string
+ dotErr error
+ )
+ if _, found := syscall.Getenv("NoDefaultCurrentDirectoryInExePath"); !found {
+ if f, err := findExecutable(filepath.Join(".", file), exts); err == nil {
+ if execerrdot.Value() == "0" {
+ execerrdot.IncNonDefault()
+ return f, nil
+ }
+ dotf, dotErr = f, &Error{file, ErrDot}
+ }
+ }
+
+ path := os.Getenv("path")
+ for _, dir := range filepath.SplitList(path) {
+ if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil {
+ if dotErr != nil {
+ // https://go.dev/issue/53536: if we resolved a relative path implicitly,
+ // and it is the same executable that would be resolved from the explicit %PATH%,
+ // prefer the explicit name for the executable (and, likely, no error) instead
+ // of the equivalent implicit name with ErrDot.
+ //
+ // Otherwise, return the ErrDot for the implicit path as soon as we find
+ // out that the explicit one doesn't match.
+ dotfi, dotfiErr := os.Lstat(dotf)
+ fi, fiErr := os.Lstat(f)
+ if dotfiErr != nil || fiErr != nil || !os.SameFile(dotfi, fi) {
+ return dotf, dotErr
+ }
+ }
+
+ if !filepath.IsAbs(f) {
+ if execerrdot.Value() != "0" {
+ return f, &Error{file, ErrDot}
+ }
+ execerrdot.IncNonDefault()
+ }
+ return f, nil
+ }
+ }
+
+ if dotErr != nil {
+ return dotf, dotErr
+ }
+ return "", &Error{file, ErrNotFound}
+}
diff --git a/src/os/exec/lp_windows_test.go b/src/os/exec/lp_windows_test.go
new file mode 100644
index 0000000..4d85a5f
--- /dev/null
+++ b/src/os/exec/lp_windows_test.go
@@ -0,0 +1,612 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Use an external test to avoid os/exec -> internal/testenv -> os/exec
+// circular dependency.
+
+package exec_test
+
+import (
+ "errors"
+ "fmt"
+ "internal/testenv"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+func init() {
+ registerHelperCommand("exec", cmdExec)
+ registerHelperCommand("lookpath", cmdLookPath)
+}
+
+func cmdLookPath(args ...string) {
+ p, err := exec.LookPath(args[0])
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err)
+ os.Exit(1)
+ }
+ fmt.Print(p)
+}
+
+func cmdExec(args ...string) {
+ cmd := exec.Command(args[1])
+ cmd.Dir = args[0]
+ if errors.Is(cmd.Err, exec.ErrDot) {
+ cmd.Err = nil
+ }
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output))
+ os.Exit(1)
+ }
+ fmt.Printf("%s", string(output))
+}
+
+func installExe(t *testing.T, dest, src string) {
+ fsrc, err := os.Open(src)
+ if err != nil {
+ t.Fatal("os.Open failed: ", err)
+ }
+ defer fsrc.Close()
+ fdest, err := os.Create(dest)
+ if err != nil {
+ t.Fatal("os.Create failed: ", err)
+ }
+ defer fdest.Close()
+ _, err = io.Copy(fdest, fsrc)
+ if err != nil {
+ t.Fatal("io.Copy failed: ", err)
+ }
+}
+
+func installBat(t *testing.T, dest string) {
+ f, err := os.Create(dest)
+ if err != nil {
+ t.Fatalf("failed to create batch file: %v", err)
+ }
+ defer f.Close()
+ fmt.Fprintf(f, "@echo %s\n", dest)
+}
+
+func installProg(t *testing.T, dest, srcExe string) {
+ err := os.MkdirAll(filepath.Dir(dest), 0700)
+ if err != nil {
+ t.Fatal("os.MkdirAll failed: ", err)
+ }
+ if strings.ToLower(filepath.Ext(dest)) == ".bat" {
+ installBat(t, dest)
+ return
+ }
+ installExe(t, dest, srcExe)
+}
+
+type lookPathTest struct {
+ rootDir string
+ PATH string
+ PATHEXT string
+ files []string
+ searchFor string
+ fails bool // test is expected to fail
+}
+
+func (test lookPathTest) runProg(t *testing.T, env []string, cmd *exec.Cmd) (string, error) {
+ cmd.Env = env
+ cmd.Dir = test.rootDir
+ args := append([]string(nil), cmd.Args...)
+ args[0] = filepath.Base(args[0])
+ cmdText := fmt.Sprintf("%q command", strings.Join(args, " "))
+ out, err := cmd.CombinedOutput()
+ if (err != nil) != test.fails {
+ if test.fails {
+ t.Fatalf("test=%+v: %s succeeded, but expected to fail", test, cmdText)
+ }
+ t.Fatalf("test=%+v: %s failed, but expected to succeed: %v - %v", test, cmdText, err, string(out))
+ }
+ if err != nil {
+ return "", fmt.Errorf("test=%+v: %s failed: %v - %v", test, cmdText, err, string(out))
+ }
+ // normalise program output
+ p := string(out)
+ // trim terminating \r and \n that batch file outputs
+ for len(p) > 0 && (p[len(p)-1] == '\n' || p[len(p)-1] == '\r') {
+ p = p[:len(p)-1]
+ }
+ if !filepath.IsAbs(p) {
+ return p, nil
+ }
+ if p[:len(test.rootDir)] != test.rootDir {
+ t.Fatalf("test=%+v: %s output is wrong: %q must have %q prefix", test, cmdText, p, test.rootDir)
+ }
+ return p[len(test.rootDir)+1:], nil
+}
+
+func updateEnv(env []string, name, value string) []string {
+ for i, e := range env {
+ if strings.HasPrefix(strings.ToUpper(e), name+"=") {
+ env[i] = name + "=" + value
+ return env
+ }
+ }
+ return append(env, name+"="+value)
+}
+
+func createEnv(dir, PATH, PATHEXT string) []string {
+ env := os.Environ()
+ env = updateEnv(env, "PATHEXT", PATHEXT)
+ // Add dir in front of every directory in the PATH.
+ dirs := filepath.SplitList(PATH)
+ for i := range dirs {
+ dirs[i] = filepath.Join(dir, dirs[i])
+ }
+ path := strings.Join(dirs, ";")
+ env = updateEnv(env, "PATH", os.Getenv("SystemRoot")+"/System32;"+path)
+ return env
+}
+
+// createFiles copies srcPath file into multiply files.
+// It uses dir as prefix for all destination files.
+func createFiles(t *testing.T, dir string, files []string, srcPath string) {
+ for _, f := range files {
+ installProg(t, filepath.Join(dir, f), srcPath)
+ }
+}
+
+func (test lookPathTest) run(t *testing.T, tmpdir, printpathExe string) {
+ test.rootDir = tmpdir
+ createFiles(t, test.rootDir, test.files, printpathExe)
+ env := createEnv(test.rootDir, test.PATH, test.PATHEXT)
+ // Run "cmd.exe /c test.searchFor" with new environment and
+ // work directory set. All candidates are copies of printpath.exe.
+ // These will output their program paths when run.
+ should, errCmd := test.runProg(t, env, testenv.Command(t, "cmd", "/c", test.searchFor))
+ // Run the lookpath program with new environment and work directory set.
+ have, errLP := test.runProg(t, env, helperCommand(t, "lookpath", test.searchFor))
+ // Compare results.
+ if errCmd == nil && errLP == nil {
+ // both succeeded
+ if should != have {
+ t.Fatalf("test=%+v:\ncmd /c ran: %s\nlookpath found: %s", test, should, have)
+ }
+ return
+ }
+ if errCmd != nil && errLP != nil {
+ // both failed -> continue
+ return
+ }
+ if errCmd != nil {
+ t.Fatal(errCmd)
+ }
+ if errLP != nil {
+ t.Fatal(errLP)
+ }
+}
+
+var lookPathTests = []lookPathTest{
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`, `p2\a`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1.dir;p2.dir`,
+ files: []string{`p1.dir\a`, `p2.dir\a.exe`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a.exe`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\b.exe`},
+ searchFor: `b`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\b`, `p2\a`},
+ searchFor: `a`,
+ fails: true, // TODO(brainman): do not know why this fails
+ },
+ // If the command name specifies a path, the shell searches
+ // the specified path for an executable file matching
+ // the command name. If a match is found, the external
+ // command (the executable file) executes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `p2\a`,
+ },
+ // If the command name specifies a path, the shell searches
+ // the specified path for an executable file matching the command
+ // name. ... If no match is found, the shell reports an error
+ // and command processing completes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\b.exe`, `p2\a.exe`},
+ searchFor: `p2\b`,
+ fails: true,
+ },
+ // If the command name does not specify a path, the shell
+ // searches the current directory for an executable file
+ // matching the command name. If a match is found, the external
+ // command (the executable file) executes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`a`, `p1\a.exe`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ // The shell now searches each directory specified by the
+ // PATH environment variable, in the order listed, for an
+ // executable file matching the command name. If a match
+ // is found, the external command (the executable file) executes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ // The shell now searches each directory specified by the
+ // PATH environment variable, in the order listed, for an
+ // executable file matching the command name. If no match
+ // is found, the shell reports an error and command processing
+ // completes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `b`,
+ fails: true,
+ },
+ // If the command name includes a file extension, the shell
+ // searches each directory for the exact file name specified
+ // by the command name.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a.exe`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a.com`,
+ fails: true, // includes extension and not exact file name match
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1`,
+ files: []string{`p1\a.exe.exe`},
+ searchFor: `a.exe`,
+ },
+ {
+ PATHEXT: `.COM;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a.exe`,
+ },
+ // If the command name does not include a file extension, the shell
+ // adds the extensions listed in the PATHEXT environment variable,
+ // one by one, and searches the directory for that file name. Note
+ // that the shell tries all possible file extensions in a specific
+ // directory before moving on to search the next directory
+ // (if there is one).
+ {
+ PATHEXT: `.COM;.EXE`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.bat`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.bat`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.bat`, `p1\a.exe`, `p2\a.bat`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.bat`, `p2\a.exe`},
+ searchFor: `a`,
+ fails: true, // tried all extensions in PATHEXT, but none matches
+ },
+}
+
+func TestLookPathWindows(t *testing.T) {
+ if testing.Short() {
+ maySkipHelperCommand("lookpath")
+ t.Skipf("skipping test in short mode that would build a helper binary")
+ }
+ t.Parallel()
+
+ tmp := t.TempDir()
+ printpathExe := buildPrintPathExe(t, tmp)
+
+ // Run all tests.
+ for i, test := range lookPathTests {
+ i, test := i, test
+ t.Run(fmt.Sprint(i), func(t *testing.T) {
+ t.Parallel()
+
+ dir := filepath.Join(tmp, "d"+strconv.Itoa(i))
+ err := os.Mkdir(dir, 0700)
+ if err != nil {
+ t.Fatal("Mkdir failed: ", err)
+ }
+ test.run(t, dir, printpathExe)
+ })
+ }
+}
+
+type commandTest struct {
+ PATH string
+ files []string
+ dir string
+ arg0 string
+ want string
+ fails bool // test is expected to fail
+}
+
+func (test commandTest) isSuccess(rootDir, output string, err error) error {
+ if err != nil {
+ return fmt.Errorf("test=%+v: exec: %v %v", test, err, output)
+ }
+ path := output
+ if path[:len(rootDir)] != rootDir {
+ return fmt.Errorf("test=%+v: %q must have %q prefix", test, path, rootDir)
+ }
+ path = path[len(rootDir)+1:]
+ if path != test.want {
+ return fmt.Errorf("test=%+v: want %q, got %q", test, test.want, path)
+ }
+ return nil
+}
+
+func (test commandTest) runOne(t *testing.T, rootDir string, env []string, dir, arg0 string) {
+ cmd := helperCommand(t, "exec", dir, arg0)
+ cmd.Dir = rootDir
+ cmd.Env = env
+ output, err := cmd.CombinedOutput()
+ err = test.isSuccess(rootDir, string(output), err)
+ if (err != nil) != test.fails {
+ if test.fails {
+ t.Errorf("test=%+v: succeeded, but expected to fail", test)
+ } else {
+ t.Error(err)
+ }
+ }
+}
+
+func (test commandTest) run(t *testing.T, rootDir, printpathExe string) {
+ createFiles(t, rootDir, test.files, printpathExe)
+ PATHEXT := `.COM;.EXE;.BAT`
+ env := createEnv(rootDir, test.PATH, PATHEXT)
+ test.runOne(t, rootDir, env, test.dir, test.arg0)
+}
+
+var commandTests = []commandTest{
+ // testing commands with no slash, like `a.exe`
+ {
+ // should find a.exe in current directory
+ files: []string{`a.exe`},
+ arg0: `a.exe`,
+ want: `a.exe`,
+ },
+ {
+ // like above, but add PATH in attempt to break the test
+ PATH: `p2;p`,
+ files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
+ arg0: `a.exe`,
+ want: `a.exe`,
+ },
+ {
+ // like above, but use "a" instead of "a.exe" for command
+ PATH: `p2;p`,
+ files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
+ arg0: `a`,
+ want: `a.exe`,
+ },
+ // testing commands with slash, like `.\a.exe`
+ {
+ // should find p\a.exe
+ files: []string{`p\a.exe`},
+ arg0: `p\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but adding `.` in front of executable should still be OK
+ files: []string{`p\a.exe`},
+ arg0: `.\p\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but with PATH added in attempt to break it
+ PATH: `p2`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ arg0: `p\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but make sure .exe is tried even for commands with slash
+ PATH: `p2`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ arg0: `p\a`,
+ want: `p\a.exe`,
+ },
+ // tests commands, like `a.exe`, with c.Dir set
+ {
+ // should not find a.exe in p, because LookPath(`a.exe`) will fail
+ files: []string{`p\a.exe`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `p\a.exe`,
+ fails: true,
+ },
+ {
+ // LookPath(`a.exe`) will find `.\a.exe`, but prefixing that with
+ // dir `p\a.exe` will refer to a non-existent file
+ files: []string{`a.exe`, `p\not_important_file`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `a.exe`,
+ fails: true,
+ },
+ {
+ // like above, but making test succeed by installing file
+ // in referred destination (so LookPath(`a.exe`) will still
+ // find `.\a.exe`, but we successfully execute `p\a.exe`)
+ files: []string{`a.exe`, `p\a.exe`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but add PATH in attempt to break the test
+ PATH: `p2;p`,
+ files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but use "a" instead of "a.exe" for command
+ PATH: `p2;p`,
+ files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `a`,
+ want: `p\a.exe`,
+ },
+ {
+ // finds `a.exe` in the PATH regardless of dir set
+ // because LookPath returns full path in that case
+ PATH: `p2;p`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `p2\a.exe`,
+ },
+ // tests commands, like `.\a.exe`, with c.Dir set
+ {
+ // should use dir when command is path, like ".\a.exe"
+ files: []string{`p\a.exe`},
+ dir: `p`,
+ arg0: `.\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but with PATH added in attempt to break it
+ PATH: `p2`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `.\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but make sure .exe is tried even for commands with slash
+ PATH: `p2`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `.\a`,
+ want: `p\a.exe`,
+ },
+}
+
+func TestCommand(t *testing.T) {
+ if testing.Short() {
+ maySkipHelperCommand("exec")
+ t.Skipf("skipping test in short mode that would build a helper binary")
+ }
+ t.Parallel()
+
+ tmp := t.TempDir()
+ printpathExe := buildPrintPathExe(t, tmp)
+
+ // Run all tests.
+ for i, test := range commandTests {
+ i, test := i, test
+ t.Run(fmt.Sprint(i), func(t *testing.T) {
+ t.Parallel()
+
+ dir := filepath.Join(tmp, "d"+strconv.Itoa(i))
+ err := os.Mkdir(dir, 0700)
+ if err != nil {
+ t.Fatal("Mkdir failed: ", err)
+ }
+ test.run(t, dir, printpathExe)
+ })
+ }
+}
+
+// buildPrintPathExe creates a Go program that prints its own path.
+// dir is a temp directory where executable will be created.
+// The function returns full path to the created program.
+func buildPrintPathExe(t *testing.T, dir string) string {
+ const name = "printpath"
+ srcname := name + ".go"
+ err := os.WriteFile(filepath.Join(dir, srcname), []byte(printpathSrc), 0644)
+ if err != nil {
+ t.Fatalf("failed to create source: %v", err)
+ }
+ if err != nil {
+ t.Fatalf("failed to execute template: %v", err)
+ }
+ outname := name + ".exe"
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", outname, srcname)
+ cmd.Dir = dir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to build executable: %v - %v", err, string(out))
+ }
+ return filepath.Join(dir, outname)
+}
+
+const printpathSrc = `
+package main
+
+import (
+ "os"
+ "syscall"
+ "unsafe"
+)
+
+func getMyName() (string, error) {
+ var sysproc = syscall.MustLoadDLL("kernel32.dll").MustFindProc("GetModuleFileNameW")
+ b := make([]uint16, syscall.MAX_PATH)
+ r, _, err := sysproc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)))
+ n := uint32(r)
+ if n == 0 {
+ return "", err
+ }
+ return syscall.UTF16ToString(b[0:n]), nil
+}
+
+func main() {
+ path, err := getMyName()
+ if err != nil {
+ os.Stderr.Write([]byte("getMyName failed: " + err.Error() + "\n"))
+ os.Exit(1)
+ }
+ os.Stdout.Write([]byte(path))
+}
+`
diff --git a/src/os/exec/read3.go b/src/os/exec/read3.go
new file mode 100644
index 0000000..8327d73
--- /dev/null
+++ b/src/os/exec/read3.go
@@ -0,0 +1,91 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+// This is a test program that verifies that it can read from
+// descriptor 3 and that no other descriptors are open.
+// This is not done via TestHelperProcess and GO_EXEC_TEST_PID
+// because we want to ensure that this program does not use cgo,
+// because C libraries can open file descriptors behind our backs
+// and confuse the test. See issue 25628.
+package main
+
+import (
+ "fmt"
+ "internal/poll"
+ "io"
+ "os"
+ "os/exec"
+ "os/exec/internal/fdtest"
+ "runtime"
+ "strings"
+)
+
+func main() {
+ fd3 := os.NewFile(3, "fd3")
+ defer fd3.Close()
+
+ bs, err := io.ReadAll(fd3)
+ if err != nil {
+ fmt.Printf("ReadAll from fd 3: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Now verify that there are no other open fds.
+ // stdin == 0
+ // stdout == 1
+ // stderr == 2
+ // descriptor from parent == 3
+ // All descriptors 4 and up should be available,
+ // except for any used by the network poller.
+ for fd := uintptr(4); fd <= 100; fd++ {
+ if poll.IsPollDescriptor(fd) {
+ continue
+ }
+
+ if !fdtest.Exists(fd) {
+ continue
+ }
+
+ fmt.Printf("leaked parent file. fdtest.Exists(%d) got true want false\n", fd)
+
+ fdfile := fmt.Sprintf("/proc/self/fd/%d", fd)
+ link, err := os.Readlink(fdfile)
+ fmt.Printf("readlink(%q) = %q, %v\n", fdfile, link, err)
+
+ var args []string
+ switch runtime.GOOS {
+ case "plan9":
+ args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())}
+ case "aix", "solaris", "illumos":
+ args = []string{fmt.Sprint(os.Getpid())}
+ default:
+ args = []string{"-p", fmt.Sprint(os.Getpid())}
+ }
+
+ // Determine which command to use to display open files.
+ ofcmd := "lsof"
+ switch runtime.GOOS {
+ case "dragonfly", "freebsd", "netbsd", "openbsd":
+ ofcmd = "fstat"
+ case "plan9":
+ ofcmd = "/bin/cat"
+ case "aix":
+ ofcmd = "procfiles"
+ case "solaris", "illumos":
+ ofcmd = "pfiles"
+ }
+
+ cmd := exec.Command(ofcmd, args...)
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s failed: %v\n", strings.Join(cmd.Args, " "), err)
+ }
+ fmt.Printf("%s", out)
+ os.Exit(1)
+ }
+
+ os.Stdout.Write(bs)
+}
diff --git a/src/os/exec_plan9.go b/src/os/exec_plan9.go
new file mode 100644
index 0000000..69714ff
--- /dev/null
+++ b/src/os/exec_plan9.go
@@ -0,0 +1,149 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "internal/itoa"
+ "runtime"
+ "syscall"
+ "time"
+)
+
+// The only signal values guaranteed to be present in the os package
+// on all systems are Interrupt (send the process an interrupt) and
+// Kill (force the process to exit). Interrupt is not implemented on
+// Windows; using it with os.Process.Signal will return an error.
+var (
+ Interrupt Signal = syscall.Note("interrupt")
+ Kill Signal = syscall.Note("kill")
+)
+
+func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
+ sysattr := &syscall.ProcAttr{
+ Dir: attr.Dir,
+ Env: attr.Env,
+ Sys: attr.Sys,
+ }
+
+ sysattr.Files = make([]uintptr, 0, len(attr.Files))
+ for _, f := range attr.Files {
+ sysattr.Files = append(sysattr.Files, f.Fd())
+ }
+
+ pid, h, e := syscall.StartProcess(name, argv, sysattr)
+ if e != nil {
+ return nil, &PathError{Op: "fork/exec", Path: name, Err: e}
+ }
+
+ return newProcess(pid, h), nil
+}
+
+func (p *Process) writeProcFile(file string, data string) error {
+ f, e := OpenFile("/proc/"+itoa.Itoa(p.Pid)+"/"+file, O_WRONLY, 0)
+ if e != nil {
+ return e
+ }
+ defer f.Close()
+ _, e = f.Write([]byte(data))
+ return e
+}
+
+func (p *Process) signal(sig Signal) error {
+ if p.done() {
+ return ErrProcessDone
+ }
+ if e := p.writeProcFile("note", sig.String()); e != nil {
+ return NewSyscallError("signal", e)
+ }
+ return nil
+}
+
+func (p *Process) kill() error {
+ return p.signal(Kill)
+}
+
+func (p *Process) wait() (ps *ProcessState, err error) {
+ var waitmsg syscall.Waitmsg
+
+ if p.Pid == -1 {
+ return nil, ErrInvalid
+ }
+ err = syscall.WaitProcess(p.Pid, &waitmsg)
+ if err != nil {
+ return nil, NewSyscallError("wait", err)
+ }
+
+ p.setDone()
+ ps = &ProcessState{
+ pid: waitmsg.Pid,
+ status: &waitmsg,
+ }
+ return ps, nil
+}
+
+func (p *Process) release() error {
+ // NOOP for Plan 9.
+ p.Pid = -1
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(p, nil)
+ return nil
+}
+
+func findProcess(pid int) (p *Process, err error) {
+ // NOOP for Plan 9.
+ return newProcess(pid, 0), nil
+}
+
+// ProcessState stores information about a process, as reported by Wait.
+type ProcessState struct {
+ pid int // The process's id.
+ status *syscall.Waitmsg // System-dependent status info.
+}
+
+// Pid returns the process id of the exited process.
+func (p *ProcessState) Pid() int {
+ return p.pid
+}
+
+func (p *ProcessState) exited() bool {
+ return p.status.Exited()
+}
+
+func (p *ProcessState) success() bool {
+ return p.status.ExitStatus() == 0
+}
+
+func (p *ProcessState) sys() any {
+ return p.status
+}
+
+func (p *ProcessState) sysUsage() any {
+ return p.status
+}
+
+func (p *ProcessState) userTime() time.Duration {
+ return time.Duration(p.status.Time[0]) * time.Millisecond
+}
+
+func (p *ProcessState) systemTime() time.Duration {
+ return time.Duration(p.status.Time[1]) * time.Millisecond
+}
+
+func (p *ProcessState) String() string {
+ if p == nil {
+ return "<nil>"
+ }
+ return "exit status: " + p.status.Msg
+}
+
+// ExitCode returns the exit code of the exited process, or -1
+// if the process hasn't exited or was terminated by a signal.
+func (p *ProcessState) ExitCode() int {
+ // return -1 if the process hasn't started.
+ if p == nil {
+ return -1
+ }
+ return p.status.ExitStatus()
+}
diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go
new file mode 100644
index 0000000..a512d51
--- /dev/null
+++ b/src/os/exec_posix.go
@@ -0,0 +1,136 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1 || windows
+
+package os
+
+import (
+ "internal/itoa"
+ "internal/syscall/execenv"
+ "runtime"
+ "syscall"
+)
+
+// The only signal values guaranteed to be present in the os package on all
+// systems are os.Interrupt (send the process an interrupt) and os.Kill (force
+// the process to exit). On Windows, sending os.Interrupt to a process with
+// os.Process.Signal is not implemented; it will return an error instead of
+// sending a signal.
+var (
+ Interrupt Signal = syscall.SIGINT
+ Kill Signal = syscall.SIGKILL
+)
+
+func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
+ // If there is no SysProcAttr (ie. no Chroot or changed
+ // UID/GID), double-check existence of the directory we want
+ // to chdir into. We can make the error clearer this way.
+ if attr != nil && attr.Sys == nil && attr.Dir != "" {
+ if _, err := Stat(attr.Dir); err != nil {
+ pe := err.(*PathError)
+ pe.Op = "chdir"
+ return nil, pe
+ }
+ }
+
+ sysattr := &syscall.ProcAttr{
+ Dir: attr.Dir,
+ Env: attr.Env,
+ Sys: attr.Sys,
+ }
+ if sysattr.Env == nil {
+ sysattr.Env, err = execenv.Default(sysattr.Sys)
+ if err != nil {
+ return nil, err
+ }
+ }
+ sysattr.Files = make([]uintptr, 0, len(attr.Files))
+ for _, f := range attr.Files {
+ sysattr.Files = append(sysattr.Files, f.Fd())
+ }
+
+ pid, h, e := syscall.StartProcess(name, argv, sysattr)
+
+ // Make sure we don't run the finalizers of attr.Files.
+ runtime.KeepAlive(attr)
+
+ if e != nil {
+ return nil, &PathError{Op: "fork/exec", Path: name, Err: e}
+ }
+
+ return newProcess(pid, h), nil
+}
+
+func (p *Process) kill() error {
+ return p.Signal(Kill)
+}
+
+// ProcessState stores information about a process, as reported by Wait.
+type ProcessState struct {
+ pid int // The process's id.
+ status syscall.WaitStatus // System-dependent status info.
+ rusage *syscall.Rusage
+}
+
+// Pid returns the process id of the exited process.
+func (p *ProcessState) Pid() int {
+ return p.pid
+}
+
+func (p *ProcessState) exited() bool {
+ return p.status.Exited()
+}
+
+func (p *ProcessState) success() bool {
+ return p.status.ExitStatus() == 0
+}
+
+func (p *ProcessState) sys() any {
+ return p.status
+}
+
+func (p *ProcessState) sysUsage() any {
+ return p.rusage
+}
+
+func (p *ProcessState) String() string {
+ if p == nil {
+ return "<nil>"
+ }
+ status := p.Sys().(syscall.WaitStatus)
+ res := ""
+ switch {
+ case status.Exited():
+ code := status.ExitStatus()
+ if runtime.GOOS == "windows" && uint(code) >= 1<<16 { // windows uses large hex numbers
+ res = "exit status " + uitox(uint(code))
+ } else { // unix systems use small decimal integers
+ res = "exit status " + itoa.Itoa(code) // unix
+ }
+ case status.Signaled():
+ res = "signal: " + status.Signal().String()
+ case status.Stopped():
+ res = "stop signal: " + status.StopSignal().String()
+ if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != 0 {
+ res += " (trap " + itoa.Itoa(status.TrapCause()) + ")"
+ }
+ case status.Continued():
+ res = "continued"
+ }
+ if status.CoreDump() {
+ res += " (core dumped)"
+ }
+ return res
+}
+
+// ExitCode returns the exit code of the exited process, or -1
+// if the process hasn't exited or was terminated by a signal.
+func (p *ProcessState) ExitCode() int {
+ // return -1 if the process hasn't started.
+ if p == nil {
+ return -1
+ }
+ return p.status.ExitStatus()
+}
diff --git a/src/os/exec_unix.go b/src/os/exec_unix.go
new file mode 100644
index 0000000..f9063b4
--- /dev/null
+++ b/src/os/exec_unix.go
@@ -0,0 +1,106 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1
+
+package os
+
+import (
+ "errors"
+ "runtime"
+ "syscall"
+ "time"
+)
+
+func (p *Process) wait() (ps *ProcessState, err error) {
+ if p.Pid == -1 {
+ return nil, syscall.EINVAL
+ }
+
+ // If we can block until Wait4 will succeed immediately, do so.
+ ready, err := p.blockUntilWaitable()
+ if err != nil {
+ return nil, err
+ }
+ if ready {
+ // Mark the process done now, before the call to Wait4,
+ // so that Process.signal will not send a signal.
+ p.setDone()
+ // Acquire a write lock on sigMu to wait for any
+ // active call to the signal method to complete.
+ p.sigMu.Lock()
+ p.sigMu.Unlock()
+ }
+
+ var (
+ status syscall.WaitStatus
+ rusage syscall.Rusage
+ pid1 int
+ e error
+ )
+ for {
+ pid1, e = syscall.Wait4(p.Pid, &status, 0, &rusage)
+ if e != syscall.EINTR {
+ break
+ }
+ }
+ if e != nil {
+ return nil, NewSyscallError("wait", e)
+ }
+ if pid1 != 0 {
+ p.setDone()
+ }
+ ps = &ProcessState{
+ pid: pid1,
+ status: status,
+ rusage: &rusage,
+ }
+ return ps, nil
+}
+
+func (p *Process) signal(sig Signal) error {
+ if p.Pid == -1 {
+ return errors.New("os: process already released")
+ }
+ if p.Pid == 0 {
+ return errors.New("os: process not initialized")
+ }
+ p.sigMu.RLock()
+ defer p.sigMu.RUnlock()
+ if p.done() {
+ return ErrProcessDone
+ }
+ s, ok := sig.(syscall.Signal)
+ if !ok {
+ return errors.New("os: unsupported signal type")
+ }
+ if e := syscall.Kill(p.Pid, s); e != nil {
+ if e == syscall.ESRCH {
+ return ErrProcessDone
+ }
+ return e
+ }
+ return nil
+}
+
+func (p *Process) release() error {
+ // NOOP for unix.
+ p.Pid = -1
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(p, nil)
+ return nil
+}
+
+func findProcess(pid int) (p *Process, err error) {
+ // NOOP for unix.
+ return newProcess(pid, 0), nil
+}
+
+func (p *ProcessState) userTime() time.Duration {
+ return time.Duration(p.rusage.Utime.Nano()) * time.Nanosecond
+}
+
+func (p *ProcessState) systemTime() time.Duration {
+ return time.Duration(p.rusage.Stime.Nano()) * time.Nanosecond
+}
diff --git a/src/os/exec_unix_test.go b/src/os/exec_unix_test.go
new file mode 100644
index 0000000..2604519
--- /dev/null
+++ b/src/os/exec_unix_test.go
@@ -0,0 +1,45 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package os_test
+
+import (
+ "internal/testenv"
+ . "os"
+ "syscall"
+ "testing"
+)
+
+func TestErrProcessDone(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ t.Parallel()
+
+ p, err := StartProcess(testenv.GoToolPath(t), []string{"go"}, &ProcAttr{})
+ if err != nil {
+ t.Errorf("starting test process: %v", err)
+ }
+ p.Wait()
+ if got := p.Signal(Kill); got != ErrProcessDone {
+ t.Errorf("got %v want %v", got, ErrProcessDone)
+ }
+}
+
+func TestUNIXProcessAlive(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ t.Parallel()
+
+ p, err := StartProcess(testenv.GoToolPath(t), []string{"sleep", "1"}, &ProcAttr{})
+ if err != nil {
+ t.Skipf("starting test process: %v", err)
+ }
+ defer p.Kill()
+
+ proc, _ := FindProcess(p.Pid)
+ err = proc.Signal(syscall.Signal(0))
+ if err != nil {
+ t.Errorf("OS reported error for running process: %v", err)
+ }
+}
diff --git a/src/os/exec_windows.go b/src/os/exec_windows.go
new file mode 100644
index 0000000..239bed1
--- /dev/null
+++ b/src/os/exec_windows.go
@@ -0,0 +1,181 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "errors"
+ "internal/syscall/windows"
+ "runtime"
+ "sync/atomic"
+ "syscall"
+ "time"
+)
+
+func (p *Process) wait() (ps *ProcessState, err error) {
+ handle := atomic.LoadUintptr(&p.handle)
+ s, e := syscall.WaitForSingleObject(syscall.Handle(handle), syscall.INFINITE)
+ switch s {
+ case syscall.WAIT_OBJECT_0:
+ break
+ case syscall.WAIT_FAILED:
+ return nil, NewSyscallError("WaitForSingleObject", e)
+ default:
+ return nil, errors.New("os: unexpected result from WaitForSingleObject")
+ }
+ var ec uint32
+ e = syscall.GetExitCodeProcess(syscall.Handle(handle), &ec)
+ if e != nil {
+ return nil, NewSyscallError("GetExitCodeProcess", e)
+ }
+ var u syscall.Rusage
+ e = syscall.GetProcessTimes(syscall.Handle(handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime)
+ if e != nil {
+ return nil, NewSyscallError("GetProcessTimes", e)
+ }
+ p.setDone()
+ // NOTE(brainman): It seems that sometimes process is not dead
+ // when WaitForSingleObject returns. But we do not know any
+ // other way to wait for it. Sleeping for a while seems to do
+ // the trick sometimes.
+ // See https://golang.org/issue/25965 for details.
+ defer time.Sleep(5 * time.Millisecond)
+ defer p.Release()
+ return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
+}
+
+func (p *Process) signal(sig Signal) error {
+ handle := atomic.LoadUintptr(&p.handle)
+ if handle == uintptr(syscall.InvalidHandle) {
+ return syscall.EINVAL
+ }
+ if p.done() {
+ return ErrProcessDone
+ }
+ if sig == Kill {
+ var terminationHandle syscall.Handle
+ e := syscall.DuplicateHandle(^syscall.Handle(0), syscall.Handle(handle), ^syscall.Handle(0), &terminationHandle, syscall.PROCESS_TERMINATE, false, 0)
+ if e != nil {
+ return NewSyscallError("DuplicateHandle", e)
+ }
+ runtime.KeepAlive(p)
+ defer syscall.CloseHandle(terminationHandle)
+ e = syscall.TerminateProcess(syscall.Handle(terminationHandle), 1)
+ return NewSyscallError("TerminateProcess", e)
+ }
+ // TODO(rsc): Handle Interrupt too?
+ return syscall.Errno(syscall.EWINDOWS)
+}
+
+func (p *Process) release() error {
+ handle := atomic.SwapUintptr(&p.handle, uintptr(syscall.InvalidHandle))
+ if handle == uintptr(syscall.InvalidHandle) {
+ return syscall.EINVAL
+ }
+ e := syscall.CloseHandle(syscall.Handle(handle))
+ if e != nil {
+ return NewSyscallError("CloseHandle", e)
+ }
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(p, nil)
+ return nil
+}
+
+func findProcess(pid int) (p *Process, err error) {
+ const da = syscall.STANDARD_RIGHTS_READ |
+ syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
+ h, e := syscall.OpenProcess(da, false, uint32(pid))
+ if e != nil {
+ return nil, NewSyscallError("OpenProcess", e)
+ }
+ return newProcess(pid, uintptr(h)), nil
+}
+
+func init() {
+ cmd := windows.UTF16PtrToString(syscall.GetCommandLine())
+ if len(cmd) == 0 {
+ arg0, _ := Executable()
+ Args = []string{arg0}
+ } else {
+ Args = commandLineToArgv(cmd)
+ }
+}
+
+// appendBSBytes appends n '\\' bytes to b and returns the resulting slice.
+func appendBSBytes(b []byte, n int) []byte {
+ for ; n > 0; n-- {
+ b = append(b, '\\')
+ }
+ return b
+}
+
+// readNextArg splits command line string cmd into next
+// argument and command line remainder.
+func readNextArg(cmd string) (arg []byte, rest string) {
+ var b []byte
+ var inquote bool
+ var nslash int
+ for ; len(cmd) > 0; cmd = cmd[1:] {
+ c := cmd[0]
+ switch c {
+ case ' ', '\t':
+ if !inquote {
+ return appendBSBytes(b, nslash), cmd[1:]
+ }
+ case '"':
+ b = appendBSBytes(b, nslash/2)
+ if nslash%2 == 0 {
+ // use "Prior to 2008" rule from
+ // http://daviddeley.com/autohotkey/parameters/parameters.htm
+ // section 5.2 to deal with double double quotes
+ if inquote && len(cmd) > 1 && cmd[1] == '"' {
+ b = append(b, c)
+ cmd = cmd[1:]
+ }
+ inquote = !inquote
+ } else {
+ b = append(b, c)
+ }
+ nslash = 0
+ continue
+ case '\\':
+ nslash++
+ continue
+ }
+ b = appendBSBytes(b, nslash)
+ nslash = 0
+ b = append(b, c)
+ }
+ return appendBSBytes(b, nslash), ""
+}
+
+// commandLineToArgv splits a command line into individual argument
+// strings, following the Windows conventions documented
+// at http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
+func commandLineToArgv(cmd string) []string {
+ var args []string
+ for len(cmd) > 0 {
+ if cmd[0] == ' ' || cmd[0] == '\t' {
+ cmd = cmd[1:]
+ continue
+ }
+ var arg []byte
+ arg, cmd = readNextArg(cmd)
+ args = append(args, string(arg))
+ }
+ return args
+}
+
+func ftToDuration(ft *syscall.Filetime) time.Duration {
+ n := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // in 100-nanosecond intervals
+ return time.Duration(n*100) * time.Nanosecond
+}
+
+func (p *ProcessState) userTime() time.Duration {
+ return ftToDuration(&p.rusage.UserTime)
+}
+
+func (p *ProcessState) systemTime() time.Duration {
+ return ftToDuration(&p.rusage.KernelTime)
+}
diff --git a/src/os/executable.go b/src/os/executable.go
new file mode 100644
index 0000000..cc3134a
--- /dev/null
+++ b/src/os/executable.go
@@ -0,0 +1,20 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// Executable returns the path name for the executable that started
+// the current process. There is no guarantee that the path is still
+// pointing to the correct executable. If a symlink was used to start
+// the process, depending on the operating system, the result might
+// be the symlink or the path it pointed to. If a stable result is
+// needed, path/filepath.EvalSymlinks might help.
+//
+// Executable returns an absolute path unless an error occurred.
+//
+// The main use case is finding resources located relative to an
+// executable.
+func Executable() (string, error) {
+ return executable()
+}
diff --git a/src/os/executable_darwin.go b/src/os/executable_darwin.go
new file mode 100644
index 0000000..dae9f4e
--- /dev/null
+++ b/src/os/executable_darwin.go
@@ -0,0 +1,29 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "errors"
+
+var executablePath string // set by ../runtime/os_darwin.go
+
+var initCwd, initCwdErr = Getwd()
+
+func executable() (string, error) {
+ ep := executablePath
+ if len(ep) == 0 {
+ return ep, errors.New("cannot find executable path")
+ }
+ if ep[0] != '/' {
+ if initCwdErr != nil {
+ return ep, initCwdErr
+ }
+ if len(ep) > 2 && ep[0:2] == "./" {
+ // skip "./"
+ ep = ep[2:]
+ }
+ ep = initCwd + "/" + ep
+ }
+ return ep, nil
+}
diff --git a/src/os/executable_dragonfly.go b/src/os/executable_dragonfly.go
new file mode 100644
index 0000000..19c2ae8
--- /dev/null
+++ b/src/os/executable_dragonfly.go
@@ -0,0 +1,12 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// From DragonFly's <sys/sysctl.h>
+const (
+ _CTL_KERN = 1
+ _KERN_PROC = 14
+ _KERN_PROC_PATHNAME = 9
+)
diff --git a/src/os/executable_freebsd.go b/src/os/executable_freebsd.go
new file mode 100644
index 0000000..95f1a93
--- /dev/null
+++ b/src/os/executable_freebsd.go
@@ -0,0 +1,12 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// From FreeBSD's <sys/sysctl.h>
+const (
+ _CTL_KERN = 1
+ _KERN_PROC = 14
+ _KERN_PROC_PATHNAME = 12
+)
diff --git a/src/os/executable_path.go b/src/os/executable_path.go
new file mode 100644
index 0000000..d6161bc
--- /dev/null
+++ b/src/os/executable_path.go
@@ -0,0 +1,104 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build aix || openbsd
+
+package os
+
+// We query the working directory at init, to use it later to search for the
+// executable file
+// errWd will be checked later, if we need to use initWd
+var initWd, errWd = Getwd()
+
+func executable() (string, error) {
+ var exePath string
+ if len(Args) == 0 || Args[0] == "" {
+ return "", ErrNotExist
+ }
+ if IsPathSeparator(Args[0][0]) {
+ // Args[0] is an absolute path, so it is the executable.
+ // Note that we only need to worry about Unix paths here.
+ exePath = Args[0]
+ } else {
+ for i := 1; i < len(Args[0]); i++ {
+ if IsPathSeparator(Args[0][i]) {
+ // Args[0] is a relative path: prepend the
+ // initial working directory.
+ if errWd != nil {
+ return "", errWd
+ }
+ exePath = initWd + string(PathSeparator) + Args[0]
+ break
+ }
+ }
+ }
+ if exePath != "" {
+ if err := isExecutable(exePath); err != nil {
+ return "", err
+ }
+ return exePath, nil
+ }
+ // Search for executable in $PATH.
+ for _, dir := range splitPathList(Getenv("PATH")) {
+ if len(dir) == 0 {
+ dir = "."
+ }
+ if !IsPathSeparator(dir[0]) {
+ if errWd != nil {
+ return "", errWd
+ }
+ dir = initWd + string(PathSeparator) + dir
+ }
+ exePath = dir + string(PathSeparator) + Args[0]
+ switch isExecutable(exePath) {
+ case nil:
+ return exePath, nil
+ case ErrPermission:
+ return "", ErrPermission
+ }
+ }
+ return "", ErrNotExist
+}
+
+// isExecutable returns an error if a given file is not an executable.
+func isExecutable(path string) error {
+ stat, err := Stat(path)
+ if err != nil {
+ return err
+ }
+ mode := stat.Mode()
+ if !mode.IsRegular() {
+ return ErrPermission
+ }
+ if (mode & 0111) == 0 {
+ return ErrPermission
+ }
+ return nil
+}
+
+// splitPathList splits a path list.
+// This is based on genSplit from strings/strings.go
+func splitPathList(pathList string) []string {
+ if pathList == "" {
+ return nil
+ }
+ n := 1
+ for i := 0; i < len(pathList); i++ {
+ if pathList[i] == PathListSeparator {
+ n++
+ }
+ }
+ start := 0
+ a := make([]string, n)
+ na := 0
+ for i := 0; i+1 <= len(pathList) && na+1 < n; i++ {
+ if pathList[i] == PathListSeparator {
+ a[na] = pathList[start:i]
+ na++
+ start = i + 1
+ }
+ }
+ a[na] = pathList[start:]
+ return a[:na+1]
+}
diff --git a/src/os/executable_plan9.go b/src/os/executable_plan9.go
new file mode 100644
index 0000000..8d8c832
--- /dev/null
+++ b/src/os/executable_plan9.go
@@ -0,0 +1,22 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build plan9
+
+package os
+
+import (
+ "internal/itoa"
+ "syscall"
+)
+
+func executable() (string, error) {
+ fn := "/proc/" + itoa.Itoa(Getpid()) + "/text"
+ f, err := Open(fn)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ return syscall.Fd2path(int(f.Fd()))
+}
diff --git a/src/os/executable_procfs.go b/src/os/executable_procfs.go
new file mode 100644
index 0000000..94e674e
--- /dev/null
+++ b/src/os/executable_procfs.go
@@ -0,0 +1,37 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux || netbsd
+
+package os
+
+import (
+ "errors"
+ "runtime"
+)
+
+func executable() (string, error) {
+ var procfn string
+ switch runtime.GOOS {
+ default:
+ return "", errors.New("Executable not implemented for " + runtime.GOOS)
+ case "linux", "android":
+ procfn = "/proc/self/exe"
+ case "netbsd":
+ procfn = "/proc/curproc/exe"
+ }
+ path, err := Readlink(procfn)
+
+ // When the executable has been deleted then Readlink returns a
+ // path appended with " (deleted)".
+ return stringsTrimSuffix(path, " (deleted)"), err
+}
+
+// stringsTrimSuffix is the same as strings.TrimSuffix.
+func stringsTrimSuffix(s, suffix string) string {
+ if len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix {
+ return s[:len(s)-len(suffix)]
+ }
+ return s
+}
diff --git a/src/os/executable_solaris.go b/src/os/executable_solaris.go
new file mode 100644
index 0000000..b145980
--- /dev/null
+++ b/src/os/executable_solaris.go
@@ -0,0 +1,32 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+var executablePath string // set by sysauxv in ../runtime/os3_solaris.go
+
+var initCwd, initCwdErr = Getwd()
+
+func executable() (string, error) {
+ path := executablePath
+ if len(path) == 0 {
+ path, err := syscall.Getexecname()
+ if err != nil {
+ return path, err
+ }
+ }
+ if len(path) > 0 && path[0] != '/' {
+ if initCwdErr != nil {
+ return path, initCwdErr
+ }
+ if len(path) > 2 && path[0:2] == "./" {
+ // skip "./"
+ path = path[2:]
+ }
+ return initCwd + "/" + path, nil
+ }
+ return path, nil
+}
diff --git a/src/os/executable_sysctl.go b/src/os/executable_sysctl.go
new file mode 100644
index 0000000..3c2aeac
--- /dev/null
+++ b/src/os/executable_sysctl.go
@@ -0,0 +1,35 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build freebsd || dragonfly
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+func executable() (string, error) {
+ mib := [4]int32{_CTL_KERN, _KERN_PROC, _KERN_PROC_PATHNAME, -1}
+
+ n := uintptr(0)
+ // get length
+ _, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
+ if err != 0 {
+ return "", err
+ }
+ if n == 0 { // shouldn't happen
+ return "", nil
+ }
+ buf := make([]byte, n)
+ _, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
+ if err != 0 {
+ return "", err
+ }
+ if n == 0 { // shouldn't happen
+ return "", nil
+ }
+ return string(buf[:n-1]), nil
+}
diff --git a/src/os/executable_test.go b/src/os/executable_test.go
new file mode 100644
index 0000000..c835bb4
--- /dev/null
+++ b/src/os/executable_test.go
@@ -0,0 +1,155 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "fmt"
+ "internal/testenv"
+ "os"
+ "path/filepath"
+ "runtime"
+ "testing"
+)
+
+const executable_EnvVar = "OSTEST_OUTPUT_EXECPATH"
+
+func TestExecutable(t *testing.T) {
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ ep, err := os.Executable()
+ if err != nil {
+ t.Fatalf("Executable failed: %v", err)
+ }
+ // we want fn to be of the form "dir/prog"
+ dir := filepath.Dir(filepath.Dir(ep))
+ fn, err := filepath.Rel(dir, ep)
+ if err != nil {
+ t.Fatalf("filepath.Rel: %v", err)
+ }
+
+ cmd := testenv.Command(t, fn, "-test.run=XXXX")
+ // make child start with a relative program path
+ cmd.Dir = dir
+ cmd.Path = fn
+ if runtime.GOOS == "openbsd" || runtime.GOOS == "aix" {
+ // OpenBSD and AIX rely on argv[0]
+ } else {
+ // forge argv[0] for child, so that we can verify we could correctly
+ // get real path of the executable without influenced by argv[0].
+ cmd.Args[0] = "-"
+ }
+ cmd.Env = append(cmd.Environ(), fmt.Sprintf("%s=1", executable_EnvVar))
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("exec(self) failed: %v", err)
+ }
+ outs := string(out)
+ if !filepath.IsAbs(outs) {
+ t.Fatalf("Child returned %q, want an absolute path", out)
+ }
+ if !sameFile(outs, ep) {
+ t.Fatalf("Child returned %q, not the same file as %q", out, ep)
+ }
+}
+
+func sameFile(fn1, fn2 string) bool {
+ fi1, err := os.Stat(fn1)
+ if err != nil {
+ return false
+ }
+ fi2, err := os.Stat(fn2)
+ if err != nil {
+ return false
+ }
+ return os.SameFile(fi1, fi2)
+}
+
+func init() {
+ if e := os.Getenv(executable_EnvVar); e != "" {
+ // first chdir to another path
+ dir := "/"
+ if runtime.GOOS == "windows" {
+ cwd, err := os.Getwd()
+ if err != nil {
+ panic(err)
+ }
+ dir = filepath.VolumeName(cwd)
+ }
+ os.Chdir(dir)
+ if ep, err := os.Executable(); err != nil {
+ fmt.Fprint(os.Stderr, "ERROR: ", err)
+ } else {
+ fmt.Fprint(os.Stderr, ep)
+ }
+ os.Exit(0)
+ }
+}
+
+func TestExecutableDeleted(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ switch runtime.GOOS {
+ case "windows", "plan9":
+ t.Skipf("%v does not support deleting running binary", runtime.GOOS)
+ case "openbsd", "freebsd", "aix":
+ t.Skipf("%v does not support reading deleted binary name", runtime.GOOS)
+ }
+ t.Parallel()
+
+ dir := t.TempDir()
+
+ src := filepath.Join(dir, "testdel.go")
+ exe := filepath.Join(dir, "testdel.exe")
+
+ err := os.WriteFile(src, []byte(testExecutableDeletion), 0666)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src).CombinedOutput()
+ t.Logf("build output:\n%s", out)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ out, err = testenv.Command(t, exe).CombinedOutput()
+ t.Logf("exec output:\n%s", out)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+const testExecutableDeletion = `package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ before, err := os.Executable()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to read executable name before deletion: %v\n", err)
+ os.Exit(1)
+ }
+
+ err = os.Remove(before)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to remove executable: %v\n", err)
+ os.Exit(1)
+ }
+
+ after, err := os.Executable()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "failed to read executable name after deletion: %v\n", err)
+ os.Exit(1)
+ }
+
+ if before != after {
+ fmt.Fprintf(os.Stderr, "before and after do not match: %v != %v\n", before, after)
+ os.Exit(1)
+ }
+}
+`
diff --git a/src/os/executable_wasm.go b/src/os/executable_wasm.go
new file mode 100644
index 0000000..a88360c
--- /dev/null
+++ b/src/os/executable_wasm.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasm
+
+package os
+
+import (
+ "errors"
+ "runtime"
+)
+
+func executable() (string, error) {
+ return "", errors.New("Executable not implemented for " + runtime.GOOS)
+}
diff --git a/src/os/executable_windows.go b/src/os/executable_windows.go
new file mode 100644
index 0000000..fc5cf86
--- /dev/null
+++ b/src/os/executable_windows.go
@@ -0,0 +1,32 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "internal/syscall/windows"
+ "syscall"
+)
+
+func getModuleFileName(handle syscall.Handle) (string, error) {
+ n := uint32(1024)
+ var buf []uint16
+ for {
+ buf = make([]uint16, n)
+ r, err := windows.GetModuleFileName(handle, &buf[0], n)
+ if err != nil {
+ return "", err
+ }
+ if r < n {
+ break
+ }
+ // r == n means n not big enough
+ n += 1024
+ }
+ return syscall.UTF16ToString(buf), nil
+}
+
+func executable() (string, error) {
+ return getModuleFileName(0)
+}
diff --git a/src/os/export_linux_test.go b/src/os/export_linux_test.go
new file mode 100644
index 0000000..3fd5e61
--- /dev/null
+++ b/src/os/export_linux_test.go
@@ -0,0 +1,11 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+var (
+ PollCopyFileRangeP = &pollCopyFileRange
+ PollSpliceFile = &pollSplice
+ GetPollFDForTest = getPollFD
+)
diff --git a/src/os/export_test.go b/src/os/export_test.go
new file mode 100644
index 0000000..dc7caae
--- /dev/null
+++ b/src/os/export_test.go
@@ -0,0 +1,17 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// Export for testing.
+
+var Atime = atime
+var LstatP = &lstat
+var ErrWriteAtInAppendMode = errWriteAtInAppendMode
+var TestingForceReadDirLstat = &testingForceReadDirLstat
+var ErrPatternHasSeparator = errPatternHasSeparator
+
+func init() {
+ checkWrapErr = true
+}
diff --git a/src/os/export_unix_test.go b/src/os/export_unix_test.go
new file mode 100644
index 0000000..b8dcca0
--- /dev/null
+++ b/src/os/export_unix_test.go
@@ -0,0 +1,9 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1
+
+package os
+
+var SplitPath = splitPath
diff --git a/src/os/export_windows_test.go b/src/os/export_windows_test.go
new file mode 100644
index 0000000..ff4f899
--- /dev/null
+++ b/src/os/export_windows_test.go
@@ -0,0 +1,14 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// Export for testing.
+
+var (
+ FixLongPath = fixLongPath
+ CanUseLongPaths = canUseLongPaths
+ NewConsoleFile = newConsoleFile
+ CommandLineToArgv = commandLineToArgv
+)
diff --git a/src/os/fifo_test.go b/src/os/fifo_test.go
new file mode 100644
index 0000000..df4b2ee
--- /dev/null
+++ b/src/os/fifo_test.go
@@ -0,0 +1,207 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin || dragonfly || freebsd || (linux && !android) || netbsd || openbsd
+
+package os_test
+
+import (
+ "errors"
+ "internal/syscall/unix"
+ "internal/testenv"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "strconv"
+ "sync"
+ "syscall"
+ "testing"
+)
+
+func TestFifoEOF(t *testing.T) {
+ t.Parallel()
+
+ dir := t.TempDir()
+ fifoName := filepath.Join(dir, "fifo")
+ if err := syscall.Mkfifo(fifoName, 0600); err != nil {
+ t.Fatal(err)
+ }
+
+ // Per https://pubs.opengroup.org/onlinepubs/9699919799/functions/open.html#tag_16_357_03:
+ //
+ // - “If O_NONBLOCK is clear, an open() for reading-only shall block the
+ // calling thread until a thread opens the file for writing. An open() for
+ // writing-only shall block the calling thread until a thread opens the file
+ // for reading.”
+ //
+ // In order to unblock both open calls, we open the two ends of the FIFO
+ // simultaneously in separate goroutines.
+
+ rc := make(chan *os.File, 1)
+ go func() {
+ r, err := os.Open(fifoName)
+ if err != nil {
+ t.Error(err)
+ }
+ rc <- r
+ }()
+
+ w, err := os.OpenFile(fifoName, os.O_WRONLY, 0)
+ if err != nil {
+ t.Error(err)
+ }
+
+ r := <-rc
+ if t.Failed() {
+ if r != nil {
+ r.Close()
+ }
+ if w != nil {
+ w.Close()
+ }
+ return
+ }
+
+ testPipeEOF(t, r, w)
+}
+
+// Issue #59545.
+func TestNonPollable(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test with tight loops in short mode")
+ }
+
+ // We need to open a non-pollable file.
+ // This is almost certainly Linux-specific,
+ // but if other systems have non-pollable files,
+ // we can add them here.
+ const nonPollable = "/dev/net/tun"
+
+ f, err := os.OpenFile(nonPollable, os.O_RDWR, 0)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrPermission) || testenv.SyscallIsNotSupported(err) {
+ t.Skipf("can't open %q: %v", nonPollable, err)
+ }
+ t.Fatal(err)
+ }
+ f.Close()
+
+ // On a Linux laptop, before the problem was fixed,
+ // this test failed about 50% of the time with this
+ // number of iterations.
+ // It takes about 1/2 second when it passes.
+ const attempts = 20000
+
+ start := make(chan bool)
+ var wg sync.WaitGroup
+ wg.Add(1)
+ defer wg.Wait()
+ go func() {
+ defer wg.Done()
+ close(start)
+ for i := 0; i < attempts; i++ {
+ f, err := os.OpenFile(nonPollable, os.O_RDWR, 0)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if err := f.Close(); err != nil {
+ t.Error(err)
+ return
+ }
+ }
+ }()
+
+ dir := t.TempDir()
+ <-start
+ for i := 0; i < attempts; i++ {
+ name := filepath.Join(dir, strconv.Itoa(i))
+ if err := syscall.Mkfifo(name, 0o600); err != nil {
+ t.Fatal(err)
+ }
+ // The problem only occurs if we use O_NONBLOCK here.
+ rd, err := os.OpenFile(name, os.O_RDONLY|syscall.O_NONBLOCK, 0o600)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wr, err := os.OpenFile(name, os.O_WRONLY|syscall.O_NONBLOCK, 0o600)
+ if err != nil {
+ t.Fatal(err)
+ }
+ const msg = "message"
+ if _, err := wr.Write([]byte(msg)); err != nil {
+ if errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.ENOBUFS) {
+ t.Logf("ignoring write error %v", err)
+ rd.Close()
+ wr.Close()
+ continue
+ }
+ t.Fatalf("write to fifo %d failed: %v", i, err)
+ }
+ if _, err := rd.Read(make([]byte, len(msg))); err != nil {
+ if errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.ENOBUFS) {
+ t.Logf("ignoring read error %v", err)
+ rd.Close()
+ wr.Close()
+ continue
+ }
+ t.Fatalf("read from fifo %d failed; %v", i, err)
+ }
+ if err := rd.Close(); err != nil {
+ t.Fatal(err)
+ }
+ if err := wr.Close(); err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+// Issue 60211.
+func TestOpenFileNonBlocking(t *testing.T) {
+ exe, err := os.Executable()
+ if err != nil {
+ t.Skipf("can't find executable: %v", err)
+ }
+ f, err := os.OpenFile(exe, os.O_RDONLY|syscall.O_NONBLOCK, 0666)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ nonblock, err := unix.IsNonblock(int(f.Fd()))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !nonblock {
+ t.Errorf("file opened with O_NONBLOCK but in blocking mode")
+ }
+}
+
+func TestNewFileNonBlocking(t *testing.T) {
+ var p [2]int
+ if err := syscall.Pipe(p[:]); err != nil {
+ t.Fatal(err)
+ }
+ if err := syscall.SetNonblock(p[0], true); err != nil {
+ t.Fatal(err)
+ }
+ f := os.NewFile(uintptr(p[0]), "pipe")
+ nonblock, err := unix.IsNonblock(p[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !nonblock {
+ t.Error("pipe blocking after NewFile")
+ }
+ fd := f.Fd()
+ if fd != uintptr(p[0]) {
+ t.Errorf("Fd returned %d, want %d", fd, p[0])
+ }
+ nonblock, err = unix.IsNonblock(p[0])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !nonblock {
+ t.Error("pipe blocking after Fd")
+ }
+}
diff --git a/src/os/file.go b/src/os/file.go
new file mode 100644
index 0000000..7fd2f5d
--- /dev/null
+++ b/src/os/file.go
@@ -0,0 +1,770 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package os provides a platform-independent interface to operating system
+// functionality. The design is Unix-like, although the error handling is
+// Go-like; failing calls return values of type error rather than error numbers.
+// Often, more information is available within the error. For example,
+// if a call that takes a file name fails, such as Open or Stat, the error
+// will include the failing file name when printed and will be of type
+// *PathError, which may be unpacked for more information.
+//
+// The os interface is intended to be uniform across all operating systems.
+// Features not generally available appear in the system-specific package syscall.
+//
+// Here is a simple example, opening a file and reading some of it.
+//
+// file, err := os.Open("file.go") // For read access.
+// if err != nil {
+// log.Fatal(err)
+// }
+//
+// If the open fails, the error string will be self-explanatory, like
+//
+// open file.go: no such file or directory
+//
+// The file's data can then be read into a slice of bytes. Read and
+// Write take their byte counts from the length of the argument slice.
+//
+// data := make([]byte, 100)
+// count, err := file.Read(data)
+// if err != nil {
+// log.Fatal(err)
+// }
+// fmt.Printf("read %d bytes: %q\n", count, data[:count])
+//
+// Note: The maximum number of concurrent operations on a File may be limited by
+// the OS or the system. The number should be high, but exceeding it may degrade
+// performance or cause other issues.
+package os
+
+import (
+ "errors"
+ "internal/poll"
+ "internal/safefilepath"
+ "internal/testlog"
+ "io"
+ "io/fs"
+ "runtime"
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+// Name returns the name of the file as presented to Open.
+func (f *File) Name() string { return f.name }
+
+// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
+// standard output, and standard error file descriptors.
+//
+// Note that the Go runtime writes to standard error for panics and crashes;
+// closing Stderr may cause those messages to go elsewhere, perhaps
+// to a file opened later.
+var (
+ Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
+ Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
+ Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
+)
+
+// Flags to OpenFile wrapping those of the underlying system. Not all
+// flags may be implemented on a given system.
+const (
+ // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
+ O_RDONLY int = syscall.O_RDONLY // open the file read-only.
+ O_WRONLY int = syscall.O_WRONLY // open the file write-only.
+ O_RDWR int = syscall.O_RDWR // open the file read-write.
+ // The remaining values may be or'ed in to control behavior.
+ O_APPEND int = syscall.O_APPEND // append data to the file when writing.
+ O_CREATE int = syscall.O_CREAT // create a new file if none exists.
+ O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist.
+ O_SYNC int = syscall.O_SYNC // open for synchronous I/O.
+ O_TRUNC int = syscall.O_TRUNC // truncate regular writable file when opened.
+)
+
+// Seek whence values.
+//
+// Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd.
+const (
+ SEEK_SET int = 0 // seek relative to the origin of the file
+ SEEK_CUR int = 1 // seek relative to the current offset
+ SEEK_END int = 2 // seek relative to the end
+)
+
+// LinkError records an error during a link or symlink or rename
+// system call and the paths that caused it.
+type LinkError struct {
+ Op string
+ Old string
+ New string
+ Err error
+}
+
+func (e *LinkError) Error() string {
+ return e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error()
+}
+
+func (e *LinkError) Unwrap() error {
+ return e.Err
+}
+
+// Read reads up to len(b) bytes from the File and stores them in b.
+// It returns the number of bytes read and any error encountered.
+// At end of file, Read returns 0, io.EOF.
+func (f *File) Read(b []byte) (n int, err error) {
+ if err := f.checkValid("read"); err != nil {
+ return 0, err
+ }
+ n, e := f.read(b)
+ return n, f.wrapErr("read", e)
+}
+
+// ReadAt reads len(b) bytes from the File starting at byte offset off.
+// It returns the number of bytes read and the error, if any.
+// ReadAt always returns a non-nil error when n < len(b).
+// At end of file, that error is io.EOF.
+func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
+ if err := f.checkValid("read"); err != nil {
+ return 0, err
+ }
+
+ if off < 0 {
+ return 0, &PathError{Op: "readat", Path: f.name, Err: errors.New("negative offset")}
+ }
+
+ for len(b) > 0 {
+ m, e := f.pread(b, off)
+ if e != nil {
+ err = f.wrapErr("read", e)
+ break
+ }
+ n += m
+ b = b[m:]
+ off += int64(m)
+ }
+ return
+}
+
+// ReadFrom implements io.ReaderFrom.
+func (f *File) ReadFrom(r io.Reader) (n int64, err error) {
+ if err := f.checkValid("write"); err != nil {
+ return 0, err
+ }
+ n, handled, e := f.readFrom(r)
+ if !handled {
+ return genericReadFrom(f, r) // without wrapping
+ }
+ return n, f.wrapErr("write", e)
+}
+
+func genericReadFrom(f *File, r io.Reader) (int64, error) {
+ return io.Copy(fileWithoutReadFrom{f}, r)
+}
+
+// fileWithoutReadFrom implements all the methods of *File other
+// than ReadFrom. This is used to permit ReadFrom to call io.Copy
+// without leading to a recursive call to ReadFrom.
+type fileWithoutReadFrom struct {
+ *File
+}
+
+// This ReadFrom method hides the *File ReadFrom method.
+func (fileWithoutReadFrom) ReadFrom(fileWithoutReadFrom) {
+ panic("unreachable")
+}
+
+// Write writes len(b) bytes from b to the File.
+// It returns the number of bytes written and an error, if any.
+// Write returns a non-nil error when n != len(b).
+func (f *File) Write(b []byte) (n int, err error) {
+ if err := f.checkValid("write"); err != nil {
+ return 0, err
+ }
+ n, e := f.write(b)
+ if n < 0 {
+ n = 0
+ }
+ if n != len(b) {
+ err = io.ErrShortWrite
+ }
+
+ epipecheck(f, e)
+
+ if e != nil {
+ err = f.wrapErr("write", e)
+ }
+
+ return n, err
+}
+
+var errWriteAtInAppendMode = errors.New("os: invalid use of WriteAt on file opened with O_APPEND")
+
+// WriteAt writes len(b) bytes to the File starting at byte offset off.
+// It returns the number of bytes written and an error, if any.
+// WriteAt returns a non-nil error when n != len(b).
+//
+// If file was opened with the O_APPEND flag, WriteAt returns an error.
+func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
+ if err := f.checkValid("write"); err != nil {
+ return 0, err
+ }
+ if f.appendMode {
+ return 0, errWriteAtInAppendMode
+ }
+
+ if off < 0 {
+ return 0, &PathError{Op: "writeat", Path: f.name, Err: errors.New("negative offset")}
+ }
+
+ for len(b) > 0 {
+ m, e := f.pwrite(b, off)
+ if e != nil {
+ err = f.wrapErr("write", e)
+ break
+ }
+ n += m
+ b = b[m:]
+ off += int64(m)
+ }
+ return
+}
+
+// Seek sets the offset for the next Read or Write on file to offset, interpreted
+// according to whence: 0 means relative to the origin of the file, 1 means
+// relative to the current offset, and 2 means relative to the end.
+// It returns the new offset and an error, if any.
+// The behavior of Seek on a file opened with O_APPEND is not specified.
+func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
+ if err := f.checkValid("seek"); err != nil {
+ return 0, err
+ }
+ r, e := f.seek(offset, whence)
+ if e == nil && f.dirinfo != nil && r != 0 {
+ e = syscall.EISDIR
+ }
+ if e != nil {
+ return 0, f.wrapErr("seek", e)
+ }
+ return r, nil
+}
+
+// WriteString is like Write, but writes the contents of string s rather than
+// a slice of bytes.
+func (f *File) WriteString(s string) (n int, err error) {
+ b := unsafe.Slice(unsafe.StringData(s), len(s))
+ return f.Write(b)
+}
+
+// Mkdir creates a new directory with the specified name and permission
+// bits (before umask).
+// If there is an error, it will be of type *PathError.
+func Mkdir(name string, perm FileMode) error {
+ longName := fixLongPath(name)
+ e := ignoringEINTR(func() error {
+ return syscall.Mkdir(longName, syscallMode(perm))
+ })
+
+ if e != nil {
+ return &PathError{Op: "mkdir", Path: name, Err: e}
+ }
+
+ // mkdir(2) itself won't handle the sticky bit on *BSD and Solaris
+ if !supportsCreateWithStickyBit && perm&ModeSticky != 0 {
+ e = setStickyBit(name)
+
+ if e != nil {
+ Remove(name)
+ return e
+ }
+ }
+
+ return nil
+}
+
+// setStickyBit adds ModeSticky to the permission bits of path, non atomic.
+func setStickyBit(name string) error {
+ fi, err := Stat(name)
+ if err != nil {
+ return err
+ }
+ return Chmod(name, fi.Mode()|ModeSticky)
+}
+
+// Chdir changes the current working directory to the named directory.
+// If there is an error, it will be of type *PathError.
+func Chdir(dir string) error {
+ if e := syscall.Chdir(dir); e != nil {
+ testlog.Open(dir) // observe likely non-existent directory
+ return &PathError{Op: "chdir", Path: dir, Err: e}
+ }
+ if log := testlog.Logger(); log != nil {
+ wd, err := Getwd()
+ if err == nil {
+ log.Chdir(wd)
+ }
+ }
+ return nil
+}
+
+// Open opens the named file for reading. If successful, methods on
+// the returned file can be used for reading; the associated file
+// descriptor has mode O_RDONLY.
+// If there is an error, it will be of type *PathError.
+func Open(name string) (*File, error) {
+ return OpenFile(name, O_RDONLY, 0)
+}
+
+// Create creates or truncates the named file. If the file already exists,
+// it is truncated. If the file does not exist, it is created with mode 0666
+// (before umask). If successful, methods on the returned File can
+// be used for I/O; the associated file descriptor has mode O_RDWR.
+// If there is an error, it will be of type *PathError.
+func Create(name string) (*File, error) {
+ return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
+}
+
+// OpenFile is the generalized open call; most users will use Open
+// or Create instead. It opens the named file with specified flag
+// (O_RDONLY etc.). If the file does not exist, and the O_CREATE flag
+// is passed, it is created with mode perm (before umask). If successful,
+// methods on the returned File can be used for I/O.
+// If there is an error, it will be of type *PathError.
+func OpenFile(name string, flag int, perm FileMode) (*File, error) {
+ testlog.Open(name)
+ f, err := openFileNolog(name, flag, perm)
+ if err != nil {
+ return nil, err
+ }
+ f.appendMode = flag&O_APPEND != 0
+
+ return f, nil
+}
+
+// lstat is overridden in tests.
+var lstat = Lstat
+
+// Rename renames (moves) oldpath to newpath.
+// If newpath already exists and is not a directory, Rename replaces it.
+// OS-specific restrictions may apply when oldpath and newpath are in different directories.
+// Even within the same directory, on non-Unix platforms Rename is not an atomic operation.
+// If there is an error, it will be of type *LinkError.
+func Rename(oldpath, newpath string) error {
+ return rename(oldpath, newpath)
+}
+
+// Many functions in package syscall return a count of -1 instead of 0.
+// Using fixCount(call()) instead of call() corrects the count.
+func fixCount(n int, err error) (int, error) {
+ if n < 0 {
+ n = 0
+ }
+ return n, err
+}
+
+// checkWrapErr is the test hook to enable checking unexpected wrapped errors of poll.ErrFileClosing.
+// It is set to true in the export_test.go for tests (including fuzz tests).
+var checkWrapErr = false
+
+// wrapErr wraps an error that occurred during an operation on an open file.
+// It passes io.EOF through unchanged, otherwise converts
+// poll.ErrFileClosing to ErrClosed and wraps the error in a PathError.
+func (f *File) wrapErr(op string, err error) error {
+ if err == nil || err == io.EOF {
+ return err
+ }
+ if err == poll.ErrFileClosing {
+ err = ErrClosed
+ } else if checkWrapErr && errors.Is(err, poll.ErrFileClosing) {
+ panic("unexpected error wrapping poll.ErrFileClosing: " + err.Error())
+ }
+ return &PathError{Op: op, Path: f.name, Err: err}
+}
+
+// TempDir returns the default directory to use for temporary files.
+//
+// On Unix systems, it returns $TMPDIR if non-empty, else /tmp.
+// On Windows, it uses GetTempPath, returning the first non-empty
+// value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory.
+// On Plan 9, it returns /tmp.
+//
+// The directory is neither guaranteed to exist nor have accessible
+// permissions.
+func TempDir() string {
+ return tempDir()
+}
+
+// UserCacheDir returns the default root directory to use for user-specific
+// cached data. Users should create their own application-specific subdirectory
+// within this one and use that.
+//
+// On Unix systems, it returns $XDG_CACHE_HOME as specified by
+// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if
+// non-empty, else $HOME/.cache.
+// On Darwin, it returns $HOME/Library/Caches.
+// On Windows, it returns %LocalAppData%.
+// On Plan 9, it returns $home/lib/cache.
+//
+// If the location cannot be determined (for example, $HOME is not defined),
+// then it will return an error.
+func UserCacheDir() (string, error) {
+ var dir string
+
+ switch runtime.GOOS {
+ case "windows":
+ dir = Getenv("LocalAppData")
+ if dir == "" {
+ return "", errors.New("%LocalAppData% is not defined")
+ }
+
+ case "darwin", "ios":
+ dir = Getenv("HOME")
+ if dir == "" {
+ return "", errors.New("$HOME is not defined")
+ }
+ dir += "/Library/Caches"
+
+ case "plan9":
+ dir = Getenv("home")
+ if dir == "" {
+ return "", errors.New("$home is not defined")
+ }
+ dir += "/lib/cache"
+
+ default: // Unix
+ dir = Getenv("XDG_CACHE_HOME")
+ if dir == "" {
+ dir = Getenv("HOME")
+ if dir == "" {
+ return "", errors.New("neither $XDG_CACHE_HOME nor $HOME are defined")
+ }
+ dir += "/.cache"
+ }
+ }
+
+ return dir, nil
+}
+
+// UserConfigDir returns the default root directory to use for user-specific
+// configuration data. Users should create their own application-specific
+// subdirectory within this one and use that.
+//
+// On Unix systems, it returns $XDG_CONFIG_HOME as specified by
+// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if
+// non-empty, else $HOME/.config.
+// On Darwin, it returns $HOME/Library/Application Support.
+// On Windows, it returns %AppData%.
+// On Plan 9, it returns $home/lib.
+//
+// If the location cannot be determined (for example, $HOME is not defined),
+// then it will return an error.
+func UserConfigDir() (string, error) {
+ var dir string
+
+ switch runtime.GOOS {
+ case "windows":
+ dir = Getenv("AppData")
+ if dir == "" {
+ return "", errors.New("%AppData% is not defined")
+ }
+
+ case "darwin", "ios":
+ dir = Getenv("HOME")
+ if dir == "" {
+ return "", errors.New("$HOME is not defined")
+ }
+ dir += "/Library/Application Support"
+
+ case "plan9":
+ dir = Getenv("home")
+ if dir == "" {
+ return "", errors.New("$home is not defined")
+ }
+ dir += "/lib"
+
+ default: // Unix
+ dir = Getenv("XDG_CONFIG_HOME")
+ if dir == "" {
+ dir = Getenv("HOME")
+ if dir == "" {
+ return "", errors.New("neither $XDG_CONFIG_HOME nor $HOME are defined")
+ }
+ dir += "/.config"
+ }
+ }
+
+ return dir, nil
+}
+
+// UserHomeDir returns the current user's home directory.
+//
+// On Unix, including macOS, it returns the $HOME environment variable.
+// On Windows, it returns %USERPROFILE%.
+// On Plan 9, it returns the $home environment variable.
+//
+// If the expected variable is not set in the environment, UserHomeDir
+// returns either a platform-specific default value or a non-nil error.
+func UserHomeDir() (string, error) {
+ env, enverr := "HOME", "$HOME"
+ switch runtime.GOOS {
+ case "windows":
+ env, enverr = "USERPROFILE", "%userprofile%"
+ case "plan9":
+ env, enverr = "home", "$home"
+ }
+ if v := Getenv(env); v != "" {
+ return v, nil
+ }
+ // On some geese the home directory is not always defined.
+ switch runtime.GOOS {
+ case "android":
+ return "/sdcard", nil
+ case "ios":
+ return "/", nil
+ }
+ return "", errors.New(enverr + " is not defined")
+}
+
+// Chmod changes the mode of the named file to mode.
+// If the file is a symbolic link, it changes the mode of the link's target.
+// If there is an error, it will be of type *PathError.
+//
+// A different subset of the mode bits are used, depending on the
+// operating system.
+//
+// On Unix, the mode's permission bits, ModeSetuid, ModeSetgid, and
+// ModeSticky are used.
+//
+// On Windows, only the 0200 bit (owner writable) of mode is used; it
+// controls whether the file's read-only attribute is set or cleared.
+// The other bits are currently unused. For compatibility with Go 1.12
+// and earlier, use a non-zero mode. Use mode 0400 for a read-only
+// file and 0600 for a readable+writable file.
+//
+// On Plan 9, the mode's permission bits, ModeAppend, ModeExclusive,
+// and ModeTemporary are used.
+func Chmod(name string, mode FileMode) error { return chmod(name, mode) }
+
+// Chmod changes the mode of the file to mode.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chmod(mode FileMode) error { return f.chmod(mode) }
+
+// SetDeadline sets the read and write deadlines for a File.
+// It is equivalent to calling both SetReadDeadline and SetWriteDeadline.
+//
+// Only some kinds of files support setting a deadline. Calls to SetDeadline
+// for files that do not support deadlines will return ErrNoDeadline.
+// On most systems ordinary files do not support deadlines, but pipes do.
+//
+// A deadline is an absolute time after which I/O operations fail with an
+// error instead of blocking. The deadline applies to all future and pending
+// I/O, not just the immediately following call to Read or Write.
+// After a deadline has been exceeded, the connection can be refreshed
+// by setting a deadline in the future.
+//
+// If the deadline is exceeded a call to Read or Write or to other I/O
+// methods will return an error that wraps ErrDeadlineExceeded.
+// This can be tested using errors.Is(err, os.ErrDeadlineExceeded).
+// That error implements the Timeout method, and calling the Timeout
+// method will return true, but there are other possible errors for which
+// the Timeout will return true even if the deadline has not been exceeded.
+//
+// An idle timeout can be implemented by repeatedly extending
+// the deadline after successful Read or Write calls.
+//
+// A zero value for t means I/O operations will not time out.
+func (f *File) SetDeadline(t time.Time) error {
+ return f.setDeadline(t)
+}
+
+// SetReadDeadline sets the deadline for future Read calls and any
+// currently-blocked Read call.
+// A zero value for t means Read will not time out.
+// Not all files support setting deadlines; see SetDeadline.
+func (f *File) SetReadDeadline(t time.Time) error {
+ return f.setReadDeadline(t)
+}
+
+// SetWriteDeadline sets the deadline for any future Write calls and any
+// currently-blocked Write call.
+// Even if Write times out, it may return n > 0, indicating that
+// some of the data was successfully written.
+// A zero value for t means Write will not time out.
+// Not all files support setting deadlines; see SetDeadline.
+func (f *File) SetWriteDeadline(t time.Time) error {
+ return f.setWriteDeadline(t)
+}
+
+// SyscallConn returns a raw file.
+// This implements the syscall.Conn interface.
+func (f *File) SyscallConn() (syscall.RawConn, error) {
+ if err := f.checkValid("SyscallConn"); err != nil {
+ return nil, err
+ }
+ return newRawConn(f)
+}
+
+// DirFS returns a file system (an fs.FS) for the tree of files rooted at the directory dir.
+//
+// Note that DirFS("/prefix") only guarantees that the Open calls it makes to the
+// operating system will begin with "/prefix": DirFS("/prefix").Open("file") is the
+// same as os.Open("/prefix/file"). So if /prefix/file is a symbolic link pointing outside
+// the /prefix tree, then using DirFS does not stop the access any more than using
+// os.Open does. Additionally, the root of the fs.FS returned for a relative path,
+// DirFS("prefix"), will be affected by later calls to Chdir. DirFS is therefore not
+// a general substitute for a chroot-style security mechanism when the directory tree
+// contains arbitrary content.
+//
+// The directory dir must not be "".
+//
+// The result implements [io/fs.StatFS], [io/fs.ReadFileFS] and
+// [io/fs.ReadDirFS].
+func DirFS(dir string) fs.FS {
+ return dirFS(dir)
+}
+
+// containsAny reports whether any bytes in chars are within s.
+func containsAny(s, chars string) bool {
+ for i := 0; i < len(s); i++ {
+ for j := 0; j < len(chars); j++ {
+ if s[i] == chars[j] {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+type dirFS string
+
+func (dir dirFS) Open(name string) (fs.File, error) {
+ fullname, err := dir.join(name)
+ if err != nil {
+ return nil, &PathError{Op: "stat", Path: name, Err: err}
+ }
+ f, err := Open(fullname)
+ if err != nil {
+ // DirFS takes a string appropriate for GOOS,
+ // while the name argument here is always slash separated.
+ // dir.join will have mixed the two; undo that for
+ // error reporting.
+ err.(*PathError).Path = name
+ return nil, err
+ }
+ return f, nil
+}
+
+// The ReadFile method calls the [ReadFile] function for the file
+// with the given name in the directory. The function provides
+// robust handling for small files and special file systems.
+// Through this method, dirFS implements [io/fs.ReadFileFS].
+func (dir dirFS) ReadFile(name string) ([]byte, error) {
+ fullname, err := dir.join(name)
+ if err != nil {
+ return nil, &PathError{Op: "readfile", Path: name, Err: err}
+ }
+ return ReadFile(fullname)
+}
+
+// ReadDir reads the named directory, returning all its directory entries sorted
+// by filename. Through this method, dirFS implements [io/fs.ReadDirFS].
+func (dir dirFS) ReadDir(name string) ([]DirEntry, error) {
+ fullname, err := dir.join(name)
+ if err != nil {
+ return nil, &PathError{Op: "readdir", Path: name, Err: err}
+ }
+ return ReadDir(fullname)
+}
+
+func (dir dirFS) Stat(name string) (fs.FileInfo, error) {
+ fullname, err := dir.join(name)
+ if err != nil {
+ return nil, &PathError{Op: "stat", Path: name, Err: err}
+ }
+ f, err := Stat(fullname)
+ if err != nil {
+ // See comment in dirFS.Open.
+ err.(*PathError).Path = name
+ return nil, err
+ }
+ return f, nil
+}
+
+// join returns the path for name in dir.
+func (dir dirFS) join(name string) (string, error) {
+ if dir == "" {
+ return "", errors.New("os: DirFS with empty root")
+ }
+ if !fs.ValidPath(name) {
+ return "", ErrInvalid
+ }
+ name, err := safefilepath.FromFS(name)
+ if err != nil {
+ return "", ErrInvalid
+ }
+ if IsPathSeparator(dir[len(dir)-1]) {
+ return string(dir) + name, nil
+ }
+ return string(dir) + string(PathSeparator) + name, nil
+}
+
+// ReadFile reads the named file and returns the contents.
+// A successful call returns err == nil, not err == EOF.
+// Because ReadFile reads the whole file, it does not treat an EOF from Read
+// as an error to be reported.
+func ReadFile(name string) ([]byte, error) {
+ f, err := Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ var size int
+ if info, err := f.Stat(); err == nil {
+ size64 := info.Size()
+ if int64(int(size64)) == size64 {
+ size = int(size64)
+ }
+ }
+ size++ // one byte for final read at EOF
+
+ // If a file claims a small size, read at least 512 bytes.
+ // In particular, files in Linux's /proc claim size 0 but
+ // then do not work right if read in small pieces,
+ // so an initial read of 1 byte would not work correctly.
+ if size < 512 {
+ size = 512
+ }
+
+ data := make([]byte, 0, size)
+ for {
+ if len(data) >= cap(data) {
+ d := append(data[:cap(data)], 0)
+ data = d[:len(data)]
+ }
+ n, err := f.Read(data[len(data):cap(data)])
+ data = data[:len(data)+n]
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ return data, err
+ }
+ }
+}
+
+// WriteFile writes data to the named file, creating it if necessary.
+// If the file does not exist, WriteFile creates it with permissions perm (before umask);
+// otherwise WriteFile truncates it before writing, without changing permissions.
+// Since WriteFile requires multiple system calls to complete, a failure mid-operation
+// can leave the file in a partially written state.
+func WriteFile(name string, data []byte, perm FileMode) error {
+ f, err := OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm)
+ if err != nil {
+ return err
+ }
+ _, err = f.Write(data)
+ if err1 := f.Close(); err1 != nil && err == nil {
+ err = err1
+ }
+ return err
+}
diff --git a/src/os/file_mutex_plan9.go b/src/os/file_mutex_plan9.go
new file mode 100644
index 0000000..26bf5a7
--- /dev/null
+++ b/src/os/file_mutex_plan9.go
@@ -0,0 +1,70 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// File locking support for Plan 9. This uses fdMutex from the
+// internal/poll package.
+
+// incref adds a reference to the file. It returns an error if the file
+// is already closed. This method is on File so that we can incorporate
+// a nil test.
+func (f *File) incref(op string) (err error) {
+ if f == nil {
+ return ErrInvalid
+ }
+ if !f.fdmu.Incref() {
+ err = ErrClosed
+ if op != "" {
+ err = &PathError{Op: op, Path: f.name, Err: err}
+ }
+ }
+ return err
+}
+
+// decref removes a reference to the file. If this is the last
+// remaining reference, and the file has been marked to be closed,
+// then actually close it.
+func (file *file) decref() error {
+ if file.fdmu.Decref() {
+ return file.destroy()
+ }
+ return nil
+}
+
+// readLock adds a reference to the file and locks it for reading.
+// It returns an error if the file is already closed.
+func (file *file) readLock() error {
+ if !file.fdmu.ReadLock() {
+ return ErrClosed
+ }
+ return nil
+}
+
+// readUnlock removes a reference from the file and unlocks it for reading.
+// It also closes the file if it marked as closed and there is no remaining
+// reference.
+func (file *file) readUnlock() {
+ if file.fdmu.ReadUnlock() {
+ file.destroy()
+ }
+}
+
+// writeLock adds a reference to the file and locks it for writing.
+// It returns an error if the file is already closed.
+func (file *file) writeLock() error {
+ if !file.fdmu.WriteLock() {
+ return ErrClosed
+ }
+ return nil
+}
+
+// writeUnlock removes a reference from the file and unlocks it for writing.
+// It also closes the file if it is marked as closed and there is no remaining
+// reference.
+func (file *file) writeUnlock() {
+ if file.fdmu.WriteUnlock() {
+ file.destroy()
+ }
+}
diff --git a/src/os/file_open_unix.go b/src/os/file_open_unix.go
new file mode 100644
index 0000000..a3336ea
--- /dev/null
+++ b/src/os/file_open_unix.go
@@ -0,0 +1,17 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm)
+
+package os
+
+import (
+ "internal/poll"
+ "syscall"
+)
+
+func open(path string, flag int, perm uint32) (int, poll.SysFile, error) {
+ fd, err := syscall.Open(path, flag, perm)
+ return fd, poll.SysFile{}, err
+}
diff --git a/src/os/file_open_wasip1.go b/src/os/file_open_wasip1.go
new file mode 100644
index 0000000..f3ef165
--- /dev/null
+++ b/src/os/file_open_wasip1.go
@@ -0,0 +1,31 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasip1
+
+package os
+
+import (
+ "internal/poll"
+ "syscall"
+)
+
+func open(filePath string, flag int, perm uint32) (int, poll.SysFile, error) {
+ if filePath == "" {
+ return -1, poll.SysFile{}, syscall.EINVAL
+ }
+ absPath := filePath
+ // os.(*File).Chdir is emulated by setting the working directory to the
+ // absolute path that this file was opened at, which is why we have to
+ // resolve and capture it here.
+ if filePath[0] != '/' {
+ wd, err := syscall.Getwd()
+ if err != nil {
+ return -1, poll.SysFile{}, err
+ }
+ absPath = joinPath(wd, filePath)
+ }
+ fd, err := syscall.Open(absPath, flag, perm)
+ return fd, poll.SysFile{Path: absPath}, err
+}
diff --git a/src/os/file_plan9.go b/src/os/file_plan9.go
new file mode 100644
index 0000000..8336487
--- /dev/null
+++ b/src/os/file_plan9.go
@@ -0,0 +1,620 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "internal/poll"
+ "io"
+ "runtime"
+ "syscall"
+ "time"
+)
+
+// fixLongPath is a noop on non-Windows platforms.
+func fixLongPath(path string) string {
+ return path
+}
+
+// file is the real representation of *File.
+// The extra level of indirection ensures that no clients of os
+// can overwrite this data, which could cause the finalizer
+// to close the wrong file descriptor.
+type file struct {
+ fdmu poll.FDMutex
+ fd int
+ name string
+ dirinfo *dirInfo // nil unless directory being read
+ appendMode bool // whether file is opened for appending
+}
+
+// Fd returns the integer Plan 9 file descriptor referencing the open file.
+// If f is closed, the file descriptor becomes invalid.
+// If f is garbage collected, a finalizer may close the file descriptor,
+// making it invalid; see runtime.SetFinalizer for more information on when
+// a finalizer might be run. On Unix systems this will cause the SetDeadline
+// methods to stop working.
+//
+// As an alternative, see the f.SyscallConn method.
+func (f *File) Fd() uintptr {
+ if f == nil {
+ return ^(uintptr(0))
+ }
+ return uintptr(f.fd)
+}
+
+// NewFile returns a new File with the given file descriptor and
+// name. The returned value will be nil if fd is not a valid file
+// descriptor.
+func NewFile(fd uintptr, name string) *File {
+ fdi := int(fd)
+ if fdi < 0 {
+ return nil
+ }
+ f := &File{&file{fd: fdi, name: name}}
+ runtime.SetFinalizer(f.file, (*file).close)
+ return f
+}
+
+// Auxiliary information if the File describes a directory
+type dirInfo struct {
+ buf [syscall.STATMAX]byte // buffer for directory I/O
+ nbuf int // length of buf; return value from Read
+ bufp int // location of next record in buf.
+}
+
+func epipecheck(file *File, e error) {
+}
+
+// DevNull is the name of the operating system's “null device.”
+// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
+const DevNull = "/dev/null"
+
+// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
+func syscallMode(i FileMode) (o uint32) {
+ o |= uint32(i.Perm())
+ if i&ModeAppend != 0 {
+ o |= syscall.DMAPPEND
+ }
+ if i&ModeExclusive != 0 {
+ o |= syscall.DMEXCL
+ }
+ if i&ModeTemporary != 0 {
+ o |= syscall.DMTMP
+ }
+ return
+}
+
+// openFileNolog is the Plan 9 implementation of OpenFile.
+func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
+ var (
+ fd int
+ e error
+ create bool
+ excl bool
+ trunc bool
+ append bool
+ )
+
+ if flag&O_CREATE == O_CREATE {
+ flag = flag & ^O_CREATE
+ create = true
+ }
+ if flag&O_EXCL == O_EXCL {
+ excl = true
+ }
+ if flag&O_TRUNC == O_TRUNC {
+ trunc = true
+ }
+ // O_APPEND is emulated on Plan 9
+ if flag&O_APPEND == O_APPEND {
+ flag = flag &^ O_APPEND
+ append = true
+ }
+
+ if (create && trunc) || excl {
+ fd, e = syscall.Create(name, flag, syscallMode(perm))
+ } else {
+ fd, e = syscall.Open(name, flag)
+ if IsNotExist(e) && create {
+ fd, e = syscall.Create(name, flag, syscallMode(perm))
+ if e != nil {
+ return nil, &PathError{Op: "create", Path: name, Err: e}
+ }
+ }
+ }
+
+ if e != nil {
+ return nil, &PathError{Op: "open", Path: name, Err: e}
+ }
+
+ if append {
+ if _, e = syscall.Seek(fd, 0, io.SeekEnd); e != nil {
+ return nil, &PathError{Op: "seek", Path: name, Err: e}
+ }
+ }
+
+ return NewFile(uintptr(fd), name), nil
+}
+
+// Close closes the File, rendering it unusable for I/O.
+// On files that support SetDeadline, any pending I/O operations will
+// be canceled and return immediately with an ErrClosed error.
+// Close will return an error if it has already been called.
+func (f *File) Close() error {
+ if f == nil {
+ return ErrInvalid
+ }
+ return f.file.close()
+}
+
+func (file *file) close() error {
+ if !file.fdmu.IncrefAndClose() {
+ return &PathError{Op: "close", Path: file.name, Err: ErrClosed}
+ }
+
+ // At this point we should cancel any pending I/O.
+ // How do we do that on Plan 9?
+
+ err := file.decref()
+
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(file, nil)
+ return err
+}
+
+// destroy actually closes the descriptor. This is called when
+// there are no remaining references, by the decref, readUnlock,
+// and writeUnlock methods.
+func (file *file) destroy() error {
+ var err error
+ if e := syscall.Close(file.fd); e != nil {
+ err = &PathError{Op: "close", Path: file.name, Err: e}
+ }
+ return err
+}
+
+// Stat returns the FileInfo structure describing file.
+// If there is an error, it will be of type *PathError.
+func (f *File) Stat() (FileInfo, error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ d, err := dirstat(f)
+ if err != nil {
+ return nil, err
+ }
+ return fileInfoFromStat(d), nil
+}
+
+// Truncate changes the size of the file.
+// It does not change the I/O offset.
+// If there is an error, it will be of type *PathError.
+func (f *File) Truncate(size int64) error {
+ if f == nil {
+ return ErrInvalid
+ }
+
+ var d syscall.Dir
+ d.Null()
+ d.Length = size
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{Op: "truncate", Path: f.name, Err: err}
+ }
+
+ if err := f.incref("truncate"); err != nil {
+ return err
+ }
+ defer f.decref()
+
+ if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
+ return &PathError{Op: "truncate", Path: f.name, Err: err}
+ }
+ return nil
+}
+
+const chmodMask = uint32(syscall.DMAPPEND | syscall.DMEXCL | syscall.DMTMP | ModePerm)
+
+func (f *File) chmod(mode FileMode) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ var d syscall.Dir
+
+ odir, e := dirstat(f)
+ if e != nil {
+ return &PathError{Op: "chmod", Path: f.name, Err: e}
+ }
+ d.Null()
+ d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{Op: "chmod", Path: f.name, Err: err}
+ }
+
+ if err := f.incref("chmod"); err != nil {
+ return err
+ }
+ defer f.decref()
+
+ if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
+ return &PathError{Op: "chmod", Path: f.name, Err: err}
+ }
+ return nil
+}
+
+// Sync commits the current contents of the file to stable storage.
+// Typically, this means flushing the file system's in-memory copy
+// of recently written data to disk.
+func (f *File) Sync() error {
+ if f == nil {
+ return ErrInvalid
+ }
+ var d syscall.Dir
+ d.Null()
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{Op: "sync", Path: f.name, Err: err}
+ }
+
+ if err := f.incref("sync"); err != nil {
+ return err
+ }
+ defer f.decref()
+
+ if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
+ return &PathError{Op: "sync", Path: f.name, Err: err}
+ }
+ return nil
+}
+
+// read reads up to len(b) bytes from the File.
+// It returns the number of bytes read and an error, if any.
+func (f *File) read(b []byte) (n int, err error) {
+ if err := f.readLock(); err != nil {
+ return 0, err
+ }
+ defer f.readUnlock()
+ n, e := fixCount(syscall.Read(f.fd, b))
+ if n == 0 && len(b) > 0 && e == nil {
+ return 0, io.EOF
+ }
+ return n, e
+}
+
+// pread reads len(b) bytes from the File starting at byte offset off.
+// It returns the number of bytes read and the error, if any.
+// EOF is signaled by a zero count with err set to nil.
+func (f *File) pread(b []byte, off int64) (n int, err error) {
+ if err := f.readLock(); err != nil {
+ return 0, err
+ }
+ defer f.readUnlock()
+ n, e := fixCount(syscall.Pread(f.fd, b, off))
+ if n == 0 && len(b) > 0 && e == nil {
+ return 0, io.EOF
+ }
+ return n, e
+}
+
+// write writes len(b) bytes to the File.
+// It returns the number of bytes written and an error, if any.
+// Since Plan 9 preserves message boundaries, never allow
+// a zero-byte write.
+func (f *File) write(b []byte) (n int, err error) {
+ if err := f.writeLock(); err != nil {
+ return 0, err
+ }
+ defer f.writeUnlock()
+ if len(b) == 0 {
+ return 0, nil
+ }
+ return fixCount(syscall.Write(f.fd, b))
+}
+
+// pwrite writes len(b) bytes to the File starting at byte offset off.
+// It returns the number of bytes written and an error, if any.
+// Since Plan 9 preserves message boundaries, never allow
+// a zero-byte write.
+func (f *File) pwrite(b []byte, off int64) (n int, err error) {
+ if err := f.writeLock(); err != nil {
+ return 0, err
+ }
+ defer f.writeUnlock()
+ if len(b) == 0 {
+ return 0, nil
+ }
+ return fixCount(syscall.Pwrite(f.fd, b, off))
+}
+
+// seek sets the offset for the next Read or Write on file to offset, interpreted
+// according to whence: 0 means relative to the origin of the file, 1 means
+// relative to the current offset, and 2 means relative to the end.
+// It returns the new offset and an error, if any.
+func (f *File) seek(offset int64, whence int) (ret int64, err error) {
+ if err := f.incref(""); err != nil {
+ return 0, err
+ }
+ defer f.decref()
+ if f.dirinfo != nil {
+ // Free cached dirinfo, so we allocate a new one if we
+ // access this file as a directory again. See #35767 and #37161.
+ f.dirinfo = nil
+ }
+ return syscall.Seek(f.fd, offset, whence)
+}
+
+// Truncate changes the size of the named file.
+// If the file is a symbolic link, it changes the size of the link's target.
+// If there is an error, it will be of type *PathError.
+func Truncate(name string, size int64) error {
+ var d syscall.Dir
+
+ d.Null()
+ d.Length = size
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{Op: "truncate", Path: name, Err: err}
+ }
+ if err = syscall.Wstat(name, buf[:n]); err != nil {
+ return &PathError{Op: "truncate", Path: name, Err: err}
+ }
+ return nil
+}
+
+// Remove removes the named file or directory.
+// If there is an error, it will be of type *PathError.
+func Remove(name string) error {
+ if e := syscall.Remove(name); e != nil {
+ return &PathError{Op: "remove", Path: name, Err: e}
+ }
+ return nil
+}
+
+// hasPrefix from the strings package.
+func hasPrefix(s, prefix string) bool {
+ return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
+}
+
+func rename(oldname, newname string) error {
+ dirname := oldname[:lastIndex(oldname, '/')+1]
+ if hasPrefix(newname, dirname) {
+ newname = newname[len(dirname):]
+ } else {
+ return &LinkError{"rename", oldname, newname, ErrInvalid}
+ }
+
+ // If newname still contains slashes after removing the oldname
+ // prefix, the rename is cross-directory and must be rejected.
+ if lastIndex(newname, '/') >= 0 {
+ return &LinkError{"rename", oldname, newname, ErrInvalid}
+ }
+
+ var d syscall.Dir
+
+ d.Null()
+ d.Name = newname
+
+ buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &LinkError{"rename", oldname, newname, err}
+ }
+
+ // If newname already exists and is not a directory, rename replaces it.
+ f, err := Stat(dirname + newname)
+ if err == nil && !f.IsDir() {
+ Remove(dirname + newname)
+ }
+
+ if err = syscall.Wstat(oldname, buf[:n]); err != nil {
+ return &LinkError{"rename", oldname, newname, err}
+ }
+ return nil
+}
+
+// See docs in file.go:Chmod.
+func chmod(name string, mode FileMode) error {
+ var d syscall.Dir
+
+ odir, e := dirstat(name)
+ if e != nil {
+ return &PathError{Op: "chmod", Path: name, Err: e}
+ }
+ d.Null()
+ d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{Op: "chmod", Path: name, Err: err}
+ }
+ if err = syscall.Wstat(name, buf[:n]); err != nil {
+ return &PathError{Op: "chmod", Path: name, Err: err}
+ }
+ return nil
+}
+
+// Chtimes changes the access and modification times of the named
+// file, similar to the Unix utime() or utimes() functions.
+// A zero time.Time value will leave the corresponding file time unchanged.
+//
+// The underlying filesystem may truncate or round the values to a
+// less precise time unit.
+// If there is an error, it will be of type *PathError.
+func Chtimes(name string, atime time.Time, mtime time.Time) error {
+ var d syscall.Dir
+
+ d.Null()
+ d.Atime = uint32(atime.Unix())
+ d.Mtime = uint32(mtime.Unix())
+ if atime.IsZero() {
+ d.Atime = 0xFFFFFFFF
+ }
+ if mtime.IsZero() {
+ d.Mtime = 0xFFFFFFFF
+ }
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{Op: "chtimes", Path: name, Err: err}
+ }
+ if err = syscall.Wstat(name, buf[:n]); err != nil {
+ return &PathError{Op: "chtimes", Path: name, Err: err}
+ }
+ return nil
+}
+
+// Pipe returns a connected pair of Files; reads from r return bytes
+// written to w. It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]int
+
+ if e := syscall.Pipe(p[0:]); e != nil {
+ return nil, nil, NewSyscallError("pipe", e)
+ }
+
+ return NewFile(uintptr(p[0]), "|0"), NewFile(uintptr(p[1]), "|1"), nil
+}
+
+// not supported on Plan 9
+
+// Link creates newname as a hard link to the oldname file.
+// If there is an error, it will be of type *LinkError.
+func Link(oldname, newname string) error {
+ return &LinkError{"link", oldname, newname, syscall.EPLAN9}
+}
+
+// Symlink creates newname as a symbolic link to oldname.
+// On Windows, a symlink to a non-existent oldname creates a file symlink;
+// if oldname is later created as a directory the symlink will not work.
+// If there is an error, it will be of type *LinkError.
+func Symlink(oldname, newname string) error {
+ return &LinkError{"symlink", oldname, newname, syscall.EPLAN9}
+}
+
+// Readlink returns the destination of the named symbolic link.
+// If there is an error, it will be of type *PathError.
+func Readlink(name string) (string, error) {
+ return "", &PathError{Op: "readlink", Path: name, Err: syscall.EPLAN9}
+}
+
+// Chown changes the numeric uid and gid of the named file.
+// If the file is a symbolic link, it changes the uid and gid of the link's target.
+// A uid or gid of -1 means to not change that value.
+// If there is an error, it will be of type *PathError.
+//
+// On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or
+// EPLAN9 error, wrapped in *PathError.
+func Chown(name string, uid, gid int) error {
+ return &PathError{Op: "chown", Path: name, Err: syscall.EPLAN9}
+}
+
+// Lchown changes the numeric uid and gid of the named file.
+// If the file is a symbolic link, it changes the uid and gid of the link itself.
+// If there is an error, it will be of type *PathError.
+func Lchown(name string, uid, gid int) error {
+ return &PathError{Op: "lchown", Path: name, Err: syscall.EPLAN9}
+}
+
+// Chown changes the numeric uid and gid of the named file.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chown(uid, gid int) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ return &PathError{Op: "chown", Path: f.name, Err: syscall.EPLAN9}
+}
+
+func tempDir() string {
+ dir := Getenv("TMPDIR")
+ if dir == "" {
+ dir = "/tmp"
+ }
+ return dir
+
+}
+
+// Chdir changes the current working directory to the file,
+// which must be a directory.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chdir() error {
+ if err := f.incref("chdir"); err != nil {
+ return err
+ }
+ defer f.decref()
+ if e := syscall.Fchdir(f.fd); e != nil {
+ return &PathError{Op: "chdir", Path: f.name, Err: e}
+ }
+ return nil
+}
+
+// setDeadline sets the read and write deadline.
+func (f *File) setDeadline(time.Time) error {
+ if err := f.checkValid("SetDeadline"); err != nil {
+ return err
+ }
+ return poll.ErrNoDeadline
+}
+
+// setReadDeadline sets the read deadline.
+func (f *File) setReadDeadline(time.Time) error {
+ if err := f.checkValid("SetReadDeadline"); err != nil {
+ return err
+ }
+ return poll.ErrNoDeadline
+}
+
+// setWriteDeadline sets the write deadline.
+func (f *File) setWriteDeadline(time.Time) error {
+ if err := f.checkValid("SetWriteDeadline"); err != nil {
+ return err
+ }
+ return poll.ErrNoDeadline
+}
+
+// checkValid checks whether f is valid for use, but does not prepare
+// to actually use it. If f is not ready checkValid returns an appropriate
+// error, perhaps incorporating the operation name op.
+func (f *File) checkValid(op string) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ if err := f.incref(op); err != nil {
+ return err
+ }
+ return f.decref()
+}
+
+type rawConn struct{}
+
+func (c *rawConn) Control(f func(uintptr)) error {
+ return syscall.EPLAN9
+}
+
+func (c *rawConn) Read(f func(uintptr) bool) error {
+ return syscall.EPLAN9
+}
+
+func (c *rawConn) Write(f func(uintptr) bool) error {
+ return syscall.EPLAN9
+}
+
+func newRawConn(file *File) (*rawConn, error) {
+ return nil, syscall.EPLAN9
+}
+
+func ignoringEINTR(fn func() error) error {
+ return fn()
+}
diff --git a/src/os/file_posix.go b/src/os/file_posix.go
new file mode 100644
index 0000000..5692657
--- /dev/null
+++ b/src/os/file_posix.go
@@ -0,0 +1,256 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1 || windows
+
+package os
+
+import (
+ "runtime"
+ "syscall"
+ "time"
+)
+
+// Close closes the File, rendering it unusable for I/O.
+// On files that support SetDeadline, any pending I/O operations will
+// be canceled and return immediately with an ErrClosed error.
+// Close will return an error if it has already been called.
+func (f *File) Close() error {
+ if f == nil {
+ return ErrInvalid
+ }
+ return f.file.close()
+}
+
+// read reads up to len(b) bytes from the File.
+// It returns the number of bytes read and an error, if any.
+func (f *File) read(b []byte) (n int, err error) {
+ n, err = f.pfd.Read(b)
+ runtime.KeepAlive(f)
+ return n, err
+}
+
+// pread reads len(b) bytes from the File starting at byte offset off.
+// It returns the number of bytes read and the error, if any.
+// EOF is signaled by a zero count with err set to nil.
+func (f *File) pread(b []byte, off int64) (n int, err error) {
+ n, err = f.pfd.Pread(b, off)
+ runtime.KeepAlive(f)
+ return n, err
+}
+
+// write writes len(b) bytes to the File.
+// It returns the number of bytes written and an error, if any.
+func (f *File) write(b []byte) (n int, err error) {
+ n, err = f.pfd.Write(b)
+ runtime.KeepAlive(f)
+ return n, err
+}
+
+// pwrite writes len(b) bytes to the File starting at byte offset off.
+// It returns the number of bytes written and an error, if any.
+func (f *File) pwrite(b []byte, off int64) (n int, err error) {
+ n, err = f.pfd.Pwrite(b, off)
+ runtime.KeepAlive(f)
+ return n, err
+}
+
+// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
+func syscallMode(i FileMode) (o uint32) {
+ o |= uint32(i.Perm())
+ if i&ModeSetuid != 0 {
+ o |= syscall.S_ISUID
+ }
+ if i&ModeSetgid != 0 {
+ o |= syscall.S_ISGID
+ }
+ if i&ModeSticky != 0 {
+ o |= syscall.S_ISVTX
+ }
+ // No mapping for Go's ModeTemporary (plan9 only).
+ return
+}
+
+// See docs in file.go:Chmod.
+func chmod(name string, mode FileMode) error {
+ longName := fixLongPath(name)
+ e := ignoringEINTR(func() error {
+ return syscall.Chmod(longName, syscallMode(mode))
+ })
+ if e != nil {
+ return &PathError{Op: "chmod", Path: name, Err: e}
+ }
+ return nil
+}
+
+// See docs in file.go:(*File).Chmod.
+func (f *File) chmod(mode FileMode) error {
+ if err := f.checkValid("chmod"); err != nil {
+ return err
+ }
+ if e := f.pfd.Fchmod(syscallMode(mode)); e != nil {
+ return f.wrapErr("chmod", e)
+ }
+ return nil
+}
+
+// Chown changes the numeric uid and gid of the named file.
+// If the file is a symbolic link, it changes the uid and gid of the link's target.
+// A uid or gid of -1 means to not change that value.
+// If there is an error, it will be of type *PathError.
+//
+// On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or
+// EPLAN9 error, wrapped in *PathError.
+func Chown(name string, uid, gid int) error {
+ e := ignoringEINTR(func() error {
+ return syscall.Chown(name, uid, gid)
+ })
+ if e != nil {
+ return &PathError{Op: "chown", Path: name, Err: e}
+ }
+ return nil
+}
+
+// Lchown changes the numeric uid and gid of the named file.
+// If the file is a symbolic link, it changes the uid and gid of the link itself.
+// If there is an error, it will be of type *PathError.
+//
+// On Windows, it always returns the syscall.EWINDOWS error, wrapped
+// in *PathError.
+func Lchown(name string, uid, gid int) error {
+ e := ignoringEINTR(func() error {
+ return syscall.Lchown(name, uid, gid)
+ })
+ if e != nil {
+ return &PathError{Op: "lchown", Path: name, Err: e}
+ }
+ return nil
+}
+
+// Chown changes the numeric uid and gid of the named file.
+// If there is an error, it will be of type *PathError.
+//
+// On Windows, it always returns the syscall.EWINDOWS error, wrapped
+// in *PathError.
+func (f *File) Chown(uid, gid int) error {
+ if err := f.checkValid("chown"); err != nil {
+ return err
+ }
+ if e := f.pfd.Fchown(uid, gid); e != nil {
+ return f.wrapErr("chown", e)
+ }
+ return nil
+}
+
+// Truncate changes the size of the file.
+// It does not change the I/O offset.
+// If there is an error, it will be of type *PathError.
+func (f *File) Truncate(size int64) error {
+ if err := f.checkValid("truncate"); err != nil {
+ return err
+ }
+ if e := f.pfd.Ftruncate(size); e != nil {
+ return f.wrapErr("truncate", e)
+ }
+ return nil
+}
+
+// Sync commits the current contents of the file to stable storage.
+// Typically, this means flushing the file system's in-memory copy
+// of recently written data to disk.
+func (f *File) Sync() error {
+ if err := f.checkValid("sync"); err != nil {
+ return err
+ }
+ if e := f.pfd.Fsync(); e != nil {
+ return f.wrapErr("sync", e)
+ }
+ return nil
+}
+
+// Chtimes changes the access and modification times of the named
+// file, similar to the Unix utime() or utimes() functions.
+// A zero time.Time value will leave the corresponding file time unchanged.
+//
+// The underlying filesystem may truncate or round the values to a
+// less precise time unit.
+// If there is an error, it will be of type *PathError.
+func Chtimes(name string, atime time.Time, mtime time.Time) error {
+ var utimes [2]syscall.Timespec
+ set := func(i int, t time.Time) {
+ if t.IsZero() {
+ utimes[i] = syscall.Timespec{Sec: _UTIME_OMIT, Nsec: _UTIME_OMIT}
+ } else {
+ utimes[i] = syscall.NsecToTimespec(t.UnixNano())
+ }
+ }
+ set(0, atime)
+ set(1, mtime)
+ if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil {
+ return &PathError{Op: "chtimes", Path: name, Err: e}
+ }
+ return nil
+}
+
+// Chdir changes the current working directory to the file,
+// which must be a directory.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chdir() error {
+ if err := f.checkValid("chdir"); err != nil {
+ return err
+ }
+ if e := f.pfd.Fchdir(); e != nil {
+ return f.wrapErr("chdir", e)
+ }
+ return nil
+}
+
+// setDeadline sets the read and write deadline.
+func (f *File) setDeadline(t time.Time) error {
+ if err := f.checkValid("SetDeadline"); err != nil {
+ return err
+ }
+ return f.pfd.SetDeadline(t)
+}
+
+// setReadDeadline sets the read deadline.
+func (f *File) setReadDeadline(t time.Time) error {
+ if err := f.checkValid("SetReadDeadline"); err != nil {
+ return err
+ }
+ return f.pfd.SetReadDeadline(t)
+}
+
+// setWriteDeadline sets the write deadline.
+func (f *File) setWriteDeadline(t time.Time) error {
+ if err := f.checkValid("SetWriteDeadline"); err != nil {
+ return err
+ }
+ return f.pfd.SetWriteDeadline(t)
+}
+
+// checkValid checks whether f is valid for use.
+// If not, it returns an appropriate error, perhaps incorporating the operation name op.
+func (f *File) checkValid(op string) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ return nil
+}
+
+// ignoringEINTR makes a function call and repeats it if it returns an
+// EINTR error. This appears to be required even though we install all
+// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.
+// Also #20400 and #36644 are issues in which a signal handler is
+// installed without setting SA_RESTART. None of these are the common case,
+// but there are enough of them that it seems that we can't avoid
+// an EINTR loop.
+func ignoringEINTR(fn func() error) error {
+ for {
+ err := fn()
+ if err != syscall.EINTR {
+ return err
+ }
+ }
+}
diff --git a/src/os/file_unix.go b/src/os/file_unix.go
new file mode 100644
index 0000000..533a484
--- /dev/null
+++ b/src/os/file_unix.go
@@ -0,0 +1,497 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1
+
+package os
+
+import (
+ "internal/poll"
+ "internal/syscall/unix"
+ "io/fs"
+ "runtime"
+ "syscall"
+ _ "unsafe" // for go:linkname
+)
+
+const _UTIME_OMIT = unix.UTIME_OMIT
+
+// fixLongPath is a noop on non-Windows platforms.
+func fixLongPath(path string) string {
+ return path
+}
+
+func rename(oldname, newname string) error {
+ fi, err := Lstat(newname)
+ if err == nil && fi.IsDir() {
+ // There are two independent errors this function can return:
+ // one for a bad oldname, and one for a bad newname.
+ // At this point we've determined the newname is bad.
+ // But just in case oldname is also bad, prioritize returning
+ // the oldname error because that's what we did historically.
+ // However, if the old name and new name are not the same, yet
+ // they refer to the same file, it implies a case-only
+ // rename on a case-insensitive filesystem, which is ok.
+ if ofi, err := Lstat(oldname); err != nil {
+ if pe, ok := err.(*PathError); ok {
+ err = pe.Err
+ }
+ return &LinkError{"rename", oldname, newname, err}
+ } else if newname == oldname || !SameFile(fi, ofi) {
+ return &LinkError{"rename", oldname, newname, syscall.EEXIST}
+ }
+ }
+ err = ignoringEINTR(func() error {
+ return syscall.Rename(oldname, newname)
+ })
+ if err != nil {
+ return &LinkError{"rename", oldname, newname, err}
+ }
+ return nil
+}
+
+// file is the real representation of *File.
+// The extra level of indirection ensures that no clients of os
+// can overwrite this data, which could cause the finalizer
+// to close the wrong file descriptor.
+type file struct {
+ pfd poll.FD
+ name string
+ dirinfo *dirInfo // nil unless directory being read
+ nonblock bool // whether we set nonblocking mode
+ stdoutOrErr bool // whether this is stdout or stderr
+ appendMode bool // whether file is opened for appending
+}
+
+// Fd returns the integer Unix file descriptor referencing the open file.
+// If f is closed, the file descriptor becomes invalid.
+// If f is garbage collected, a finalizer may close the file descriptor,
+// making it invalid; see runtime.SetFinalizer for more information on when
+// a finalizer might be run. On Unix systems this will cause the SetDeadline
+// methods to stop working.
+// Because file descriptors can be reused, the returned file descriptor may
+// only be closed through the Close method of f, or by its finalizer during
+// garbage collection. Otherwise, during garbage collection the finalizer
+// may close an unrelated file descriptor with the same (reused) number.
+//
+// As an alternative, see the f.SyscallConn method.
+func (f *File) Fd() uintptr {
+ if f == nil {
+ return ^(uintptr(0))
+ }
+
+ // If we put the file descriptor into nonblocking mode,
+ // then set it to blocking mode before we return it,
+ // because historically we have always returned a descriptor
+ // opened in blocking mode. The File will continue to work,
+ // but any blocking operation will tie up a thread.
+ if f.nonblock {
+ f.pfd.SetBlocking()
+ }
+
+ return uintptr(f.pfd.Sysfd)
+}
+
+// NewFile returns a new File with the given file descriptor and
+// name. The returned value will be nil if fd is not a valid file
+// descriptor. On Unix systems, if the file descriptor is in
+// non-blocking mode, NewFile will attempt to return a pollable File
+// (one for which the SetDeadline methods work).
+//
+// After passing it to NewFile, fd may become invalid under the same
+// conditions described in the comments of the Fd method, and the same
+// constraints apply.
+func NewFile(fd uintptr, name string) *File {
+ fdi := int(fd)
+ if fdi < 0 {
+ return nil
+ }
+
+ kind := kindNewFile
+ appendMode := false
+ if flags, err := unix.Fcntl(fdi, syscall.F_GETFL, 0); err == nil {
+ if unix.HasNonblockFlag(flags) {
+ kind = kindNonBlock
+ }
+ appendMode = flags&syscall.O_APPEND != 0
+ }
+ f := newFile(fdi, name, kind)
+ f.appendMode = appendMode
+ return f
+}
+
+// net_newUnixFile is a hidden entry point called by net.conn.File.
+// This is used so that a nonblocking network connection will become
+// blocking if code calls the Fd method. We don't want that for direct
+// calls to NewFile: passing a nonblocking descriptor to NewFile should
+// remain nonblocking if you get it back using Fd. But for net.conn.File
+// the call to NewFile is hidden from the user. Historically in that case
+// the Fd method has returned a blocking descriptor, and we want to
+// retain that behavior because existing code expects it and depends on it.
+//
+//go:linkname net_newUnixFile net.newUnixFile
+func net_newUnixFile(fd int, name string) *File {
+ if fd < 0 {
+ panic("invalid FD")
+ }
+
+ f := newFile(fd, name, kindNonBlock)
+ f.nonblock = true // tell Fd to return blocking descriptor
+ return f
+}
+
+// newFileKind describes the kind of file to newFile.
+type newFileKind int
+
+const (
+ // kindNewFile means that the descriptor was passed to us via NewFile.
+ kindNewFile newFileKind = iota
+ // kindOpenFile means that the descriptor was opened using
+ // Open, Create, or OpenFile (without O_NONBLOCK).
+ kindOpenFile
+ // kindPipe means that the descriptor was opened using Pipe.
+ kindPipe
+ // kindNonBlock means that the descriptor is already in
+ // non-blocking mode.
+ kindNonBlock
+ // kindNoPoll means that we should not put the descriptor into
+ // non-blocking mode, because we know it is not a pipe or FIFO.
+ // Used by openFdAt for directories.
+ kindNoPoll
+)
+
+// newFile is like NewFile, but if called from OpenFile or Pipe
+// (as passed in the kind parameter) it tries to add the file to
+// the runtime poller.
+func newFile(fd int, name string, kind newFileKind) *File {
+ f := &File{&file{
+ pfd: poll.FD{
+ Sysfd: fd,
+ IsStream: true,
+ ZeroReadIsEOF: true,
+ },
+ name: name,
+ stdoutOrErr: fd == 1 || fd == 2,
+ }}
+
+ pollable := kind == kindOpenFile || kind == kindPipe || kind == kindNonBlock
+
+ // If the caller passed a non-blocking filedes (kindNonBlock),
+ // we assume they know what they are doing so we allow it to be
+ // used with kqueue.
+ if kind == kindOpenFile {
+ switch runtime.GOOS {
+ case "darwin", "ios", "dragonfly", "freebsd", "netbsd", "openbsd":
+ var st syscall.Stat_t
+ err := ignoringEINTR(func() error {
+ return syscall.Fstat(fd, &st)
+ })
+ typ := st.Mode & syscall.S_IFMT
+ // Don't try to use kqueue with regular files on *BSDs.
+ // On FreeBSD a regular file is always
+ // reported as ready for writing.
+ // On Dragonfly, NetBSD and OpenBSD the fd is signaled
+ // only once as ready (both read and write).
+ // Issue 19093.
+ // Also don't add directories to the netpoller.
+ if err == nil && (typ == syscall.S_IFREG || typ == syscall.S_IFDIR) {
+ pollable = false
+ }
+
+ // In addition to the behavior described above for regular files,
+ // on Darwin, kqueue does not work properly with fifos:
+ // closing the last writer does not cause a kqueue event
+ // for any readers. See issue #24164.
+ if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && typ == syscall.S_IFIFO {
+ pollable = false
+ }
+ }
+ }
+
+ clearNonBlock := false
+ if pollable {
+ if kind == kindNonBlock {
+ // The descriptor is already in non-blocking mode.
+ // We only set f.nonblock if we put the file into
+ // non-blocking mode.
+ } else if err := syscall.SetNonblock(fd, true); err == nil {
+ f.nonblock = true
+ clearNonBlock = true
+ } else {
+ pollable = false
+ }
+ }
+
+ // An error here indicates a failure to register
+ // with the netpoll system. That can happen for
+ // a file descriptor that is not supported by
+ // epoll/kqueue; for example, disk files on
+ // Linux systems. We assume that any real error
+ // will show up in later I/O.
+ // We do restore the blocking behavior if it was set by us.
+ if pollErr := f.pfd.Init("file", pollable); pollErr != nil && clearNonBlock {
+ if err := syscall.SetNonblock(fd, false); err == nil {
+ f.nonblock = false
+ }
+ }
+
+ runtime.SetFinalizer(f.file, (*file).close)
+ return f
+}
+
+func sigpipe() // implemented in package runtime
+
+// epipecheck raises SIGPIPE if we get an EPIPE error on standard
+// output or standard error. See the SIGPIPE docs in os/signal, and
+// issue 11845.
+func epipecheck(file *File, e error) {
+ if e == syscall.EPIPE && file.stdoutOrErr {
+ sigpipe()
+ }
+}
+
+// DevNull is the name of the operating system's “null device.”
+// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
+const DevNull = "/dev/null"
+
+// openFileNolog is the Unix implementation of OpenFile.
+// Changes here should be reflected in openFdAt, if relevant.
+func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
+ setSticky := false
+ if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
+ if _, err := Stat(name); IsNotExist(err) {
+ setSticky = true
+ }
+ }
+
+ var r int
+ var s poll.SysFile
+ for {
+ var e error
+ r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
+ if e == nil {
+ break
+ }
+
+ // We have to check EINTR here, per issues 11180 and 39237.
+ if e == syscall.EINTR {
+ continue
+ }
+
+ return nil, &PathError{Op: "open", Path: name, Err: e}
+ }
+
+ // open(2) itself won't handle the sticky bit on *BSD and Solaris
+ if setSticky {
+ setStickyBit(name)
+ }
+
+ // There's a race here with fork/exec, which we are
+ // content to live with. See ../syscall/exec_unix.go.
+ if !supportsCloseOnExec {
+ syscall.CloseOnExec(r)
+ }
+
+ kind := kindOpenFile
+ if unix.HasNonblockFlag(flag) {
+ kind = kindNonBlock
+ }
+
+ f := newFile(r, name, kind)
+ f.pfd.SysFile = s
+ return f, nil
+}
+
+func (file *file) close() error {
+ if file == nil {
+ return syscall.EINVAL
+ }
+ if file.dirinfo != nil {
+ file.dirinfo.close()
+ file.dirinfo = nil
+ }
+ var err error
+ if e := file.pfd.Close(); e != nil {
+ if e == poll.ErrFileClosing {
+ e = ErrClosed
+ }
+ err = &PathError{Op: "close", Path: file.name, Err: e}
+ }
+
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(file, nil)
+ return err
+}
+
+// seek sets the offset for the next Read or Write on file to offset, interpreted
+// according to whence: 0 means relative to the origin of the file, 1 means
+// relative to the current offset, and 2 means relative to the end.
+// It returns the new offset and an error, if any.
+func (f *File) seek(offset int64, whence int) (ret int64, err error) {
+ if f.dirinfo != nil {
+ // Free cached dirinfo, so we allocate a new one if we
+ // access this file as a directory again. See #35767 and #37161.
+ f.dirinfo.close()
+ f.dirinfo = nil
+ }
+ ret, err = f.pfd.Seek(offset, whence)
+ runtime.KeepAlive(f)
+ return ret, err
+}
+
+// Truncate changes the size of the named file.
+// If the file is a symbolic link, it changes the size of the link's target.
+// If there is an error, it will be of type *PathError.
+func Truncate(name string, size int64) error {
+ e := ignoringEINTR(func() error {
+ return syscall.Truncate(name, size)
+ })
+ if e != nil {
+ return &PathError{Op: "truncate", Path: name, Err: e}
+ }
+ return nil
+}
+
+// Remove removes the named file or (empty) directory.
+// If there is an error, it will be of type *PathError.
+func Remove(name string) error {
+ // System call interface forces us to know
+ // whether name is a file or directory.
+ // Try both: it is cheaper on average than
+ // doing a Stat plus the right one.
+ e := ignoringEINTR(func() error {
+ return syscall.Unlink(name)
+ })
+ if e == nil {
+ return nil
+ }
+ e1 := ignoringEINTR(func() error {
+ return syscall.Rmdir(name)
+ })
+ if e1 == nil {
+ return nil
+ }
+
+ // Both failed: figure out which error to return.
+ // OS X and Linux differ on whether unlink(dir)
+ // returns EISDIR, so can't use that. However,
+ // both agree that rmdir(file) returns ENOTDIR,
+ // so we can use that to decide which error is real.
+ // Rmdir might also return ENOTDIR if given a bad
+ // file path, like /etc/passwd/foo, but in that case,
+ // both errors will be ENOTDIR, so it's okay to
+ // use the error from unlink.
+ if e1 != syscall.ENOTDIR {
+ e = e1
+ }
+ return &PathError{Op: "remove", Path: name, Err: e}
+}
+
+func tempDir() string {
+ dir := Getenv("TMPDIR")
+ if dir == "" {
+ if runtime.GOOS == "android" {
+ dir = "/data/local/tmp"
+ } else {
+ dir = "/tmp"
+ }
+ }
+ return dir
+}
+
+// Link creates newname as a hard link to the oldname file.
+// If there is an error, it will be of type *LinkError.
+func Link(oldname, newname string) error {
+ e := ignoringEINTR(func() error {
+ return syscall.Link(oldname, newname)
+ })
+ if e != nil {
+ return &LinkError{"link", oldname, newname, e}
+ }
+ return nil
+}
+
+// Symlink creates newname as a symbolic link to oldname.
+// On Windows, a symlink to a non-existent oldname creates a file symlink;
+// if oldname is later created as a directory the symlink will not work.
+// If there is an error, it will be of type *LinkError.
+func Symlink(oldname, newname string) error {
+ e := ignoringEINTR(func() error {
+ return syscall.Symlink(oldname, newname)
+ })
+ if e != nil {
+ return &LinkError{"symlink", oldname, newname, e}
+ }
+ return nil
+}
+
+// Readlink returns the destination of the named symbolic link.
+// If there is an error, it will be of type *PathError.
+func Readlink(name string) (string, error) {
+ for len := 128; ; len *= 2 {
+ b := make([]byte, len)
+ var (
+ n int
+ e error
+ )
+ for {
+ n, e = fixCount(syscall.Readlink(name, b))
+ if e != syscall.EINTR {
+ break
+ }
+ }
+ // buffer too small
+ if (runtime.GOOS == "aix" || runtime.GOOS == "wasip1") && e == syscall.ERANGE {
+ continue
+ }
+ if e != nil {
+ return "", &PathError{Op: "readlink", Path: name, Err: e}
+ }
+ if n < len {
+ return string(b[0:n]), nil
+ }
+ }
+}
+
+type unixDirent struct {
+ parent string
+ name string
+ typ FileMode
+ info FileInfo
+}
+
+func (d *unixDirent) Name() string { return d.name }
+func (d *unixDirent) IsDir() bool { return d.typ.IsDir() }
+func (d *unixDirent) Type() FileMode { return d.typ }
+
+func (d *unixDirent) Info() (FileInfo, error) {
+ if d.info != nil {
+ return d.info, nil
+ }
+ return lstat(d.parent + "/" + d.name)
+}
+
+func (d *unixDirent) String() string {
+ return fs.FormatDirEntry(d)
+}
+
+func newUnixDirent(parent, name string, typ FileMode) (DirEntry, error) {
+ ude := &unixDirent{
+ parent: parent,
+ name: name,
+ typ: typ,
+ }
+ if typ != ^FileMode(0) && !testingForceReadDirLstat {
+ return ude, nil
+ }
+
+ info, err := lstat(parent + "/" + name)
+ if err != nil {
+ return nil, err
+ }
+
+ ude.typ = info.Mode().Type()
+ ude.info = info
+ return ude, nil
+}
diff --git a/src/os/file_wasip1.go b/src/os/file_wasip1.go
new file mode 100644
index 0000000..c9b05b3
--- /dev/null
+++ b/src/os/file_wasip1.go
@@ -0,0 +1,22 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasip1
+
+package os
+
+import "internal/poll"
+
+// PollFD returns the poll.FD of the file.
+//
+// Other packages in std that also import internal/poll (such as net)
+// can use a type assertion to access this extension method so that
+// they can pass the *poll.FD to functions like poll.Splice.
+//
+// There is an equivalent function in net.rawConn.
+//
+// PollFD is not intended for use outside the standard library.
+func (f *file) PollFD() *poll.FD {
+ return &f.pfd
+}
diff --git a/src/os/file_windows.go b/src/os/file_windows.go
new file mode 100644
index 0000000..8d77a63
--- /dev/null
+++ b/src/os/file_windows.go
@@ -0,0 +1,524 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "errors"
+ "internal/poll"
+ "internal/syscall/windows"
+ "runtime"
+ "sync"
+ "syscall"
+ "unsafe"
+)
+
+// This matches the value in syscall/syscall_windows.go.
+const _UTIME_OMIT = -1
+
+// file is the real representation of *File.
+// The extra level of indirection ensures that no clients of os
+// can overwrite this data, which could cause the finalizer
+// to close the wrong file descriptor.
+type file struct {
+ pfd poll.FD
+ name string
+ dirinfo *dirInfo // nil unless directory being read
+ appendMode bool // whether file is opened for appending
+}
+
+// Fd returns the Windows handle referencing the open file.
+// If f is closed, the file descriptor becomes invalid.
+// If f is garbage collected, a finalizer may close the file descriptor,
+// making it invalid; see runtime.SetFinalizer for more information on when
+// a finalizer might be run. On Unix systems this will cause the SetDeadline
+// methods to stop working.
+func (file *File) Fd() uintptr {
+ if file == nil {
+ return uintptr(syscall.InvalidHandle)
+ }
+ return uintptr(file.pfd.Sysfd)
+}
+
+// newFile returns a new File with the given file handle and name.
+// Unlike NewFile, it does not check that h is syscall.InvalidHandle.
+func newFile(h syscall.Handle, name string, kind string) *File {
+ if kind == "file" {
+ var m uint32
+ if syscall.GetConsoleMode(h, &m) == nil {
+ kind = "console"
+ }
+ if t, err := syscall.GetFileType(h); err == nil && t == syscall.FILE_TYPE_PIPE {
+ kind = "pipe"
+ }
+ }
+
+ f := &File{&file{
+ pfd: poll.FD{
+ Sysfd: h,
+ IsStream: true,
+ ZeroReadIsEOF: true,
+ },
+ name: name,
+ }}
+ runtime.SetFinalizer(f.file, (*file).close)
+
+ // Ignore initialization errors.
+ // Assume any problems will show up in later I/O.
+ f.pfd.Init(kind, false)
+
+ return f
+}
+
+// newConsoleFile creates new File that will be used as console.
+func newConsoleFile(h syscall.Handle, name string) *File {
+ return newFile(h, name, "console")
+}
+
+// NewFile returns a new File with the given file descriptor and
+// name. The returned value will be nil if fd is not a valid file
+// descriptor.
+func NewFile(fd uintptr, name string) *File {
+ h := syscall.Handle(fd)
+ if h == syscall.InvalidHandle {
+ return nil
+ }
+ return newFile(h, name, "file")
+}
+
+// Auxiliary information if the File describes a directory
+type dirInfo struct {
+ h syscall.Handle // search handle created with FindFirstFile
+ data syscall.Win32finddata
+ path string
+ isempty bool // set if FindFirstFile returns ERROR_FILE_NOT_FOUND
+}
+
+func (d *dirInfo) close() error {
+ return syscall.FindClose(d.h)
+}
+
+func epipecheck(file *File, e error) {
+}
+
+// DevNull is the name of the operating system's “null device.”
+// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
+const DevNull = "NUL"
+
+func openDir(name string) (d *dirInfo, e error) {
+ var mask string
+
+ path := fixLongPath(name)
+
+ if len(path) == 2 && path[1] == ':' { // it is a drive letter, like C:
+ mask = path + `*`
+ } else if len(path) > 0 {
+ lc := path[len(path)-1]
+ if lc == '/' || lc == '\\' {
+ mask = path + `*`
+ } else {
+ mask = path + `\*`
+ }
+ } else {
+ mask = `\*`
+ }
+ maskp, e := syscall.UTF16PtrFromString(mask)
+ if e != nil {
+ return nil, e
+ }
+ d = new(dirInfo)
+ d.h, e = syscall.FindFirstFile(maskp, &d.data)
+ if e != nil {
+ // FindFirstFile returns ERROR_FILE_NOT_FOUND when
+ // no matching files can be found. Then, if directory
+ // exists, we should proceed.
+ // If FindFirstFile failed because name does not point
+ // to a directory, we should return ENOTDIR.
+ var fa syscall.Win32FileAttributeData
+ pathp, e1 := syscall.UTF16PtrFromString(path)
+ if e1 != nil {
+ return nil, e
+ }
+ e1 = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
+ if e1 != nil {
+ return nil, e
+ }
+ if fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 {
+ return nil, syscall.ENOTDIR
+ }
+ if e != syscall.ERROR_FILE_NOT_FOUND {
+ return nil, e
+ }
+ d.isempty = true
+ }
+ d.path = path
+ if !isAbs(d.path) {
+ d.path, e = syscall.FullPath(d.path)
+ if e != nil {
+ d.close()
+ return nil, e
+ }
+ }
+ return d, nil
+}
+
+// openFileNolog is the Windows implementation of OpenFile.
+func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
+ if name == "" {
+ return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT}
+ }
+ path := fixLongPath(name)
+ r, e := syscall.Open(path, flag|syscall.O_CLOEXEC, syscallMode(perm))
+ if e != nil {
+ // We should return EISDIR when we are trying to open a directory with write access.
+ if e == syscall.ERROR_ACCESS_DENIED && (flag&O_WRONLY != 0 || flag&O_RDWR != 0) {
+ pathp, e1 := syscall.UTF16PtrFromString(path)
+ if e1 == nil {
+ var fa syscall.Win32FileAttributeData
+ e1 = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
+ if e1 == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ e = syscall.EISDIR
+ }
+ }
+ }
+ return nil, &PathError{Op: "open", Path: name, Err: e}
+ }
+ f, e := newFile(r, name, "file"), nil
+ if e != nil {
+ return nil, &PathError{Op: "open", Path: name, Err: e}
+ }
+ return f, nil
+}
+
+func (file *file) close() error {
+ if file == nil {
+ return syscall.EINVAL
+ }
+ if file.dirinfo != nil {
+ file.dirinfo.close()
+ file.dirinfo = nil
+ }
+ var err error
+ if e := file.pfd.Close(); e != nil {
+ if e == poll.ErrFileClosing {
+ e = ErrClosed
+ }
+ err = &PathError{Op: "close", Path: file.name, Err: e}
+ }
+
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(file, nil)
+ return err
+}
+
+// seek sets the offset for the next Read or Write on file to offset, interpreted
+// according to whence: 0 means relative to the origin of the file, 1 means
+// relative to the current offset, and 2 means relative to the end.
+// It returns the new offset and an error, if any.
+func (f *File) seek(offset int64, whence int) (ret int64, err error) {
+ if f.dirinfo != nil {
+ // Free cached dirinfo, so we allocate a new one if we
+ // access this file as a directory again. See #35767 and #37161.
+ f.dirinfo.close()
+ f.dirinfo = nil
+ }
+ ret, err = f.pfd.Seek(offset, whence)
+ runtime.KeepAlive(f)
+ return ret, err
+}
+
+// Truncate changes the size of the named file.
+// If the file is a symbolic link, it changes the size of the link's target.
+func Truncate(name string, size int64) error {
+ f, e := OpenFile(name, O_WRONLY, 0666)
+ if e != nil {
+ return e
+ }
+ defer f.Close()
+ e1 := f.Truncate(size)
+ if e1 != nil {
+ return e1
+ }
+ return nil
+}
+
+// Remove removes the named file or directory.
+// If there is an error, it will be of type *PathError.
+func Remove(name string) error {
+ p, e := syscall.UTF16PtrFromString(fixLongPath(name))
+ if e != nil {
+ return &PathError{Op: "remove", Path: name, Err: e}
+ }
+
+ // Go file interface forces us to know whether
+ // name is a file or directory. Try both.
+ e = syscall.DeleteFile(p)
+ if e == nil {
+ return nil
+ }
+ e1 := syscall.RemoveDirectory(p)
+ if e1 == nil {
+ return nil
+ }
+
+ // Both failed: figure out which error to return.
+ if e1 != e {
+ a, e2 := syscall.GetFileAttributes(p)
+ if e2 != nil {
+ e = e2
+ } else {
+ if a&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ e = e1
+ } else if a&syscall.FILE_ATTRIBUTE_READONLY != 0 {
+ if e1 = syscall.SetFileAttributes(p, a&^syscall.FILE_ATTRIBUTE_READONLY); e1 == nil {
+ if e = syscall.DeleteFile(p); e == nil {
+ return nil
+ }
+ }
+ }
+ }
+ }
+ return &PathError{Op: "remove", Path: name, Err: e}
+}
+
+func rename(oldname, newname string) error {
+ e := windows.Rename(fixLongPath(oldname), fixLongPath(newname))
+ if e != nil {
+ return &LinkError{"rename", oldname, newname, e}
+ }
+ return nil
+}
+
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an error, if any. The Windows handles underlying
+// the returned files are marked as inheritable by child processes.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]syscall.Handle
+ e := syscall.Pipe(p[:])
+ if e != nil {
+ return nil, nil, NewSyscallError("pipe", e)
+ }
+ return newFile(p[0], "|0", "pipe"), newFile(p[1], "|1", "pipe"), nil
+}
+
+var (
+ useGetTempPath2Once sync.Once
+ useGetTempPath2 bool
+)
+
+func tempDir() string {
+ useGetTempPath2Once.Do(func() {
+ useGetTempPath2 = (windows.ErrorLoadingGetTempPath2() == nil)
+ })
+ getTempPath := syscall.GetTempPath
+ if useGetTempPath2 {
+ getTempPath = windows.GetTempPath2
+ }
+ n := uint32(syscall.MAX_PATH)
+ for {
+ b := make([]uint16, n)
+ n, _ = getTempPath(uint32(len(b)), &b[0])
+ if n > uint32(len(b)) {
+ continue
+ }
+ if n == 3 && b[1] == ':' && b[2] == '\\' {
+ // Do nothing for path, like C:\.
+ } else if n > 0 && b[n-1] == '\\' {
+ // Otherwise remove terminating \.
+ n--
+ }
+ return syscall.UTF16ToString(b[:n])
+ }
+}
+
+// Link creates newname as a hard link to the oldname file.
+// If there is an error, it will be of type *LinkError.
+func Link(oldname, newname string) error {
+ n, err := syscall.UTF16PtrFromString(fixLongPath(newname))
+ if err != nil {
+ return &LinkError{"link", oldname, newname, err}
+ }
+ o, err := syscall.UTF16PtrFromString(fixLongPath(oldname))
+ if err != nil {
+ return &LinkError{"link", oldname, newname, err}
+ }
+ err = syscall.CreateHardLink(n, o, 0)
+ if err != nil {
+ return &LinkError{"link", oldname, newname, err}
+ }
+ return nil
+}
+
+// Symlink creates newname as a symbolic link to oldname.
+// On Windows, a symlink to a non-existent oldname creates a file symlink;
+// if oldname is later created as a directory the symlink will not work.
+// If there is an error, it will be of type *LinkError.
+func Symlink(oldname, newname string) error {
+ // '/' does not work in link's content
+ oldname = fromSlash(oldname)
+
+ // need the exact location of the oldname when it's relative to determine if it's a directory
+ destpath := oldname
+ if v := volumeName(oldname); v == "" {
+ if len(oldname) > 0 && IsPathSeparator(oldname[0]) {
+ // oldname is relative to the volume containing newname.
+ if v = volumeName(newname); v != "" {
+ // Prepend the volume explicitly, because it may be different from the
+ // volume of the current working directory.
+ destpath = v + oldname
+ }
+ } else {
+ // oldname is relative to newname.
+ destpath = dirname(newname) + `\` + oldname
+ }
+ }
+
+ fi, err := Stat(destpath)
+ isdir := err == nil && fi.IsDir()
+
+ n, err := syscall.UTF16PtrFromString(fixLongPath(newname))
+ if err != nil {
+ return &LinkError{"symlink", oldname, newname, err}
+ }
+ o, err := syscall.UTF16PtrFromString(fixLongPath(oldname))
+ if err != nil {
+ return &LinkError{"symlink", oldname, newname, err}
+ }
+
+ var flags uint32 = windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
+ if isdir {
+ flags |= syscall.SYMBOLIC_LINK_FLAG_DIRECTORY
+ }
+ err = syscall.CreateSymbolicLink(n, o, flags)
+ if err != nil {
+ // the unprivileged create flag is unsupported
+ // below Windows 10 (1703, v10.0.14972). retry without it.
+ flags &^= windows.SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
+ err = syscall.CreateSymbolicLink(n, o, flags)
+ if err != nil {
+ return &LinkError{"symlink", oldname, newname, err}
+ }
+ }
+ return nil
+}
+
+// openSymlink calls CreateFile Windows API with FILE_FLAG_OPEN_REPARSE_POINT
+// parameter, so that Windows does not follow symlink, if path is a symlink.
+// openSymlink returns opened file handle.
+func openSymlink(path string) (syscall.Handle, error) {
+ p, err := syscall.UTF16PtrFromString(path)
+ if err != nil {
+ return 0, err
+ }
+ attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS)
+ // Use FILE_FLAG_OPEN_REPARSE_POINT, otherwise CreateFile will follow symlink.
+ // See https://docs.microsoft.com/en-us/windows/desktop/FileIO/symbolic-link-effects-on-file-systems-functions#createfile-and-createfiletransacted
+ attrs |= syscall.FILE_FLAG_OPEN_REPARSE_POINT
+ h, err := syscall.CreateFile(p, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
+ if err != nil {
+ return 0, err
+ }
+ return h, nil
+}
+
+// normaliseLinkPath converts absolute paths returned by
+// DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, ...)
+// into paths acceptable by all Windows APIs.
+// For example, it converts
+//
+// \??\C:\foo\bar into C:\foo\bar
+// \??\UNC\foo\bar into \\foo\bar
+// \??\Volume{abc}\ into C:\
+func normaliseLinkPath(path string) (string, error) {
+ if len(path) < 4 || path[:4] != `\??\` {
+ // unexpected path, return it as is
+ return path, nil
+ }
+ // we have path that start with \??\
+ s := path[4:]
+ switch {
+ case len(s) >= 2 && s[1] == ':': // \??\C:\foo\bar
+ return s, nil
+ case len(s) >= 4 && s[:4] == `UNC\`: // \??\UNC\foo\bar
+ return `\\` + s[4:], nil
+ }
+
+ // handle paths, like \??\Volume{abc}\...
+
+ err := windows.LoadGetFinalPathNameByHandle()
+ if err != nil {
+ // we must be using old version of Windows
+ return "", err
+ }
+
+ h, err := openSymlink(path)
+ if err != nil {
+ return "", err
+ }
+ defer syscall.CloseHandle(h)
+
+ buf := make([]uint16, 100)
+ for {
+ n, err := windows.GetFinalPathNameByHandle(h, &buf[0], uint32(len(buf)), windows.VOLUME_NAME_DOS)
+ if err != nil {
+ return "", err
+ }
+ if n < uint32(len(buf)) {
+ break
+ }
+ buf = make([]uint16, n)
+ }
+ s = syscall.UTF16ToString(buf)
+ if len(s) > 4 && s[:4] == `\\?\` {
+ s = s[4:]
+ if len(s) > 3 && s[:3] == `UNC` {
+ // return path like \\server\share\...
+ return `\` + s[3:], nil
+ }
+ return s, nil
+ }
+ return "", errors.New("GetFinalPathNameByHandle returned unexpected path: " + s)
+}
+
+func readlink(path string) (string, error) {
+ h, err := openSymlink(path)
+ if err != nil {
+ return "", err
+ }
+ defer syscall.CloseHandle(h)
+
+ rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
+ var bytesReturned uint32
+ err = syscall.DeviceIoControl(h, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
+ if err != nil {
+ return "", err
+ }
+
+ rdb := (*windows.REPARSE_DATA_BUFFER)(unsafe.Pointer(&rdbbuf[0]))
+ switch rdb.ReparseTag {
+ case syscall.IO_REPARSE_TAG_SYMLINK:
+ rb := (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME))
+ s := rb.Path()
+ if rb.Flags&windows.SYMLINK_FLAG_RELATIVE != 0 {
+ return s, nil
+ }
+ return normaliseLinkPath(s)
+ case windows.IO_REPARSE_TAG_MOUNT_POINT:
+ return normaliseLinkPath((*windows.MountPointReparseBuffer)(unsafe.Pointer(&rdb.DUMMYUNIONNAME)).Path())
+ default:
+ // the path is not a symlink or junction but another type of reparse
+ // point
+ return "", syscall.ENOENT
+ }
+}
+
+// Readlink returns the destination of the named symbolic link.
+// If there is an error, it will be of type *PathError.
+func Readlink(name string) (string, error) {
+ s, err := readlink(fixLongPath(name))
+ if err != nil {
+ return "", &PathError{Op: "readlink", Path: name, Err: err}
+ }
+ return s, nil
+}
diff --git a/src/os/getwd.go b/src/os/getwd.go
new file mode 100644
index 0000000..90604cf
--- /dev/null
+++ b/src/os/getwd.go
@@ -0,0 +1,126 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "runtime"
+ "sync"
+ "syscall"
+)
+
+var getwdCache struct {
+ sync.Mutex
+ dir string
+}
+
+// Getwd returns a rooted path name corresponding to the
+// current directory. If the current directory can be
+// reached via multiple paths (due to symbolic links),
+// Getwd may return any one of them.
+func Getwd() (dir string, err error) {
+ if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
+ return syscall.Getwd()
+ }
+
+ // Clumsy but widespread kludge:
+ // if $PWD is set and matches ".", use it.
+ dot, err := statNolog(".")
+ if err != nil {
+ return "", err
+ }
+ dir = Getenv("PWD")
+ if len(dir) > 0 && dir[0] == '/' {
+ d, err := statNolog(dir)
+ if err == nil && SameFile(dot, d) {
+ return dir, nil
+ }
+ }
+
+ // If the operating system provides a Getwd call, use it.
+ // Otherwise, we're trying to find our way back to ".".
+ if syscall.ImplementsGetwd {
+ var (
+ s string
+ e error
+ )
+ for {
+ s, e = syscall.Getwd()
+ if e != syscall.EINTR {
+ break
+ }
+ }
+ return s, NewSyscallError("getwd", e)
+ }
+
+ // Apply same kludge but to cached dir instead of $PWD.
+ getwdCache.Lock()
+ dir = getwdCache.dir
+ getwdCache.Unlock()
+ if len(dir) > 0 {
+ d, err := statNolog(dir)
+ if err == nil && SameFile(dot, d) {
+ return dir, nil
+ }
+ }
+
+ // Root is a special case because it has no parent
+ // and ends in a slash.
+ root, err := statNolog("/")
+ if err != nil {
+ // Can't stat root - no hope.
+ return "", err
+ }
+ if SameFile(root, dot) {
+ return "/", nil
+ }
+
+ // General algorithm: find name in parent
+ // and then find name of parent. Each iteration
+ // adds /name to the beginning of dir.
+ dir = ""
+ for parent := ".."; ; parent = "../" + parent {
+ if len(parent) >= 1024 { // Sanity check
+ return "", syscall.ENAMETOOLONG
+ }
+ fd, err := openFileNolog(parent, O_RDONLY, 0)
+ if err != nil {
+ return "", err
+ }
+
+ for {
+ names, err := fd.Readdirnames(100)
+ if err != nil {
+ fd.Close()
+ return "", err
+ }
+ for _, name := range names {
+ d, _ := lstatNolog(parent + "/" + name)
+ if SameFile(d, dot) {
+ dir = "/" + name + dir
+ goto Found
+ }
+ }
+ }
+
+ Found:
+ pd, err := fd.Stat()
+ fd.Close()
+ if err != nil {
+ return "", err
+ }
+ if SameFile(pd, root) {
+ break
+ }
+ // Set up for next round.
+ dot = pd
+ }
+
+ // Save answer as hint to avoid the expensive path next time.
+ getwdCache.Lock()
+ getwdCache.dir = dir
+ getwdCache.Unlock()
+
+ return dir, nil
+}
diff --git a/src/os/os_test.go b/src/os/os_test.go
new file mode 100644
index 0000000..94c3ad0
--- /dev/null
+++ b/src/os/os_test.go
@@ -0,0 +1,3272 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "internal/testenv"
+ "io"
+ "io/fs"
+ . "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "runtime/debug"
+ "sort"
+ "strings"
+ "sync"
+ "syscall"
+ "testing"
+ "testing/fstest"
+ "time"
+)
+
+func TestMain(m *testing.M) {
+ if Getenv("GO_OS_TEST_DRAIN_STDIN") == "1" {
+ Stdout.Close()
+ io.Copy(io.Discard, Stdin)
+ Exit(0)
+ }
+
+ Exit(m.Run())
+}
+
+var dot = []string{
+ "dir_unix.go",
+ "env.go",
+ "error.go",
+ "file.go",
+ "os_test.go",
+ "types.go",
+ "stat_darwin.go",
+ "stat_linux.go",
+}
+
+type sysDir struct {
+ name string
+ files []string
+}
+
+var sysdir = func() *sysDir {
+ switch runtime.GOOS {
+ case "android":
+ return &sysDir{
+ "/system/lib",
+ []string{
+ "libmedia.so",
+ "libpowermanager.so",
+ },
+ }
+ case "ios":
+ wd, err := syscall.Getwd()
+ if err != nil {
+ wd = err.Error()
+ }
+ sd := &sysDir{
+ filepath.Join(wd, "..", ".."),
+ []string{
+ "ResourceRules.plist",
+ "Info.plist",
+ },
+ }
+ found := true
+ for _, f := range sd.files {
+ path := filepath.Join(sd.name, f)
+ if _, err := Stat(path); err != nil {
+ found = false
+ break
+ }
+ }
+ if found {
+ return sd
+ }
+ // In a self-hosted iOS build the above files might
+ // not exist. Look for system files instead below.
+ case "windows":
+ return &sysDir{
+ Getenv("SystemRoot") + "\\system32\\drivers\\etc",
+ []string{
+ "networks",
+ "protocol",
+ "services",
+ },
+ }
+ case "plan9":
+ return &sysDir{
+ "/lib/ndb",
+ []string{
+ "common",
+ "local",
+ },
+ }
+ case "wasip1":
+ // wasmtime has issues resolving symbolic links that are often present
+ // in directories like /etc/group below (e.g. private/etc/group on OSX).
+ // For this reason we use files in the Go source tree instead.
+ return &sysDir{
+ runtime.GOROOT(),
+ []string{
+ "go.env",
+ "LICENSE",
+ "CONTRIBUTING.md",
+ },
+ }
+ }
+ return &sysDir{
+ "/etc",
+ []string{
+ "group",
+ "hosts",
+ "passwd",
+ },
+ }
+}()
+
+func size(name string, t *testing.T) int64 {
+ file, err := Open(name)
+ if err != nil {
+ t.Fatal("open failed:", err)
+ }
+ defer func() {
+ if err := file.Close(); err != nil {
+ t.Error(err)
+ }
+ }()
+ n, err := io.Copy(io.Discard, file)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return n
+}
+
+func equal(name1, name2 string) (r bool) {
+ switch runtime.GOOS {
+ case "windows":
+ r = strings.ToLower(name1) == strings.ToLower(name2)
+ default:
+ r = name1 == name2
+ }
+ return
+}
+
+// localTmp returns a local temporary directory not on NFS.
+func localTmp() string {
+ switch runtime.GOOS {
+ case "android", "ios", "windows":
+ return TempDir()
+ }
+ return "/tmp"
+}
+
+func newFile(testName string, t *testing.T) (f *File) {
+ f, err := CreateTemp(localTmp(), "_Go_"+testName)
+ if err != nil {
+ t.Fatalf("TempFile %s: %s", testName, err)
+ }
+ return
+}
+
+func newDir(testName string, t *testing.T) (name string) {
+ name, err := MkdirTemp(localTmp(), "_Go_"+testName)
+ if err != nil {
+ t.Fatalf("TempDir %s: %s", testName, err)
+ }
+ return
+}
+
+var sfdir = sysdir.name
+var sfname = sysdir.files[0]
+
+func TestStat(t *testing.T) {
+ t.Parallel()
+
+ path := sfdir + "/" + sfname
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatal("stat failed:", err)
+ }
+ if !equal(sfname, dir.Name()) {
+ t.Error("name should be ", sfname, "; is", dir.Name())
+ }
+ filesize := size(path, t)
+ if dir.Size() != filesize {
+ t.Error("size should be", filesize, "; is", dir.Size())
+ }
+}
+
+func TestStatError(t *testing.T) {
+ defer chtmpdir(t)()
+
+ path := "no-such-file"
+
+ fi, err := Stat(path)
+ if err == nil {
+ t.Fatal("got nil, want error")
+ }
+ if fi != nil {
+ t.Errorf("got %v, want nil", fi)
+ }
+ if perr, ok := err.(*PathError); !ok {
+ t.Errorf("got %T, want %T", err, perr)
+ }
+
+ testenv.MustHaveSymlink(t)
+
+ link := "symlink"
+ err = Symlink(path, link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ fi, err = Stat(link)
+ if err == nil {
+ t.Fatal("got nil, want error")
+ }
+ if fi != nil {
+ t.Errorf("got %v, want nil", fi)
+ }
+ if perr, ok := err.(*PathError); !ok {
+ t.Errorf("got %T, want %T", err, perr)
+ }
+}
+
+func TestStatSymlinkLoop(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+
+ defer chtmpdir(t)()
+
+ err := Symlink("x", "y")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer Remove("y")
+
+ err = Symlink("y", "x")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer Remove("x")
+
+ _, err = Stat("x")
+ if _, ok := err.(*fs.PathError); !ok {
+ t.Errorf("expected *PathError, got %T: %v\n", err, err)
+ }
+}
+
+func TestFstat(t *testing.T) {
+ t.Parallel()
+
+ path := sfdir + "/" + sfname
+ file, err1 := Open(path)
+ if err1 != nil {
+ t.Fatal("open failed:", err1)
+ }
+ defer file.Close()
+ dir, err2 := file.Stat()
+ if err2 != nil {
+ t.Fatal("fstat failed:", err2)
+ }
+ if !equal(sfname, dir.Name()) {
+ t.Error("name should be ", sfname, "; is", dir.Name())
+ }
+ filesize := size(path, t)
+ if dir.Size() != filesize {
+ t.Error("size should be", filesize, "; is", dir.Size())
+ }
+}
+
+func TestLstat(t *testing.T) {
+ t.Parallel()
+
+ path := sfdir + "/" + sfname
+ dir, err := Lstat(path)
+ if err != nil {
+ t.Fatal("lstat failed:", err)
+ }
+ if !equal(sfname, dir.Name()) {
+ t.Error("name should be ", sfname, "; is", dir.Name())
+ }
+ if dir.Mode()&ModeSymlink == 0 {
+ filesize := size(path, t)
+ if dir.Size() != filesize {
+ t.Error("size should be", filesize, "; is", dir.Size())
+ }
+ }
+}
+
+// Read with length 0 should not return EOF.
+func TestRead0(t *testing.T) {
+ t.Parallel()
+
+ path := sfdir + "/" + sfname
+ f, err := Open(path)
+ if err != nil {
+ t.Fatal("open failed:", err)
+ }
+ defer f.Close()
+
+ b := make([]byte, 0)
+ n, err := f.Read(b)
+ if n != 0 || err != nil {
+ t.Errorf("Read(0) = %d, %v, want 0, nil", n, err)
+ }
+ b = make([]byte, 100)
+ n, err = f.Read(b)
+ if n <= 0 || err != nil {
+ t.Errorf("Read(100) = %d, %v, want >0, nil", n, err)
+ }
+}
+
+// Reading a closed file should return ErrClosed error
+func TestReadClosed(t *testing.T) {
+ t.Parallel()
+
+ path := sfdir + "/" + sfname
+ file, err := Open(path)
+ if err != nil {
+ t.Fatal("open failed:", err)
+ }
+ file.Close() // close immediately
+
+ b := make([]byte, 100)
+ _, err = file.Read(b)
+
+ e, ok := err.(*PathError)
+ if !ok || e.Err != ErrClosed {
+ t.Fatalf("Read: got %T(%v), want %T(%v)", err, err, e, ErrClosed)
+ }
+}
+
+func testReaddirnames(dir string, contents []string) func(*testing.T) {
+ return func(t *testing.T) {
+ t.Parallel()
+
+ file, err := Open(dir)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", dir, err)
+ }
+ defer file.Close()
+ s, err2 := file.Readdirnames(-1)
+ if err2 != nil {
+ t.Fatalf("Readdirnames %q failed: %v", dir, err2)
+ }
+ for _, m := range contents {
+ found := false
+ for _, n := range s {
+ if n == "." || n == ".." {
+ t.Errorf("got %q in directory", n)
+ }
+ if !equal(m, n) {
+ continue
+ }
+ if found {
+ t.Error("present twice:", m)
+ }
+ found = true
+ }
+ if !found {
+ t.Error("could not find", m)
+ }
+ }
+ if s == nil {
+ t.Error("Readdirnames returned nil instead of empty slice")
+ }
+ }
+}
+
+func testReaddir(dir string, contents []string) func(*testing.T) {
+ return func(t *testing.T) {
+ t.Parallel()
+
+ file, err := Open(dir)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", dir, err)
+ }
+ defer file.Close()
+ s, err2 := file.Readdir(-1)
+ if err2 != nil {
+ t.Fatalf("Readdir %q failed: %v", dir, err2)
+ }
+ for _, m := range contents {
+ found := false
+ for _, n := range s {
+ if n.Name() == "." || n.Name() == ".." {
+ t.Errorf("got %q in directory", n.Name())
+ }
+ if !equal(m, n.Name()) {
+ continue
+ }
+ if found {
+ t.Error("present twice:", m)
+ }
+ found = true
+ }
+ if !found {
+ t.Error("could not find", m)
+ }
+ }
+ if s == nil {
+ t.Error("Readdir returned nil instead of empty slice")
+ }
+ }
+}
+
+func testReadDir(dir string, contents []string) func(*testing.T) {
+ return func(t *testing.T) {
+ t.Parallel()
+
+ file, err := Open(dir)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", dir, err)
+ }
+ defer file.Close()
+ s, err2 := file.ReadDir(-1)
+ if err2 != nil {
+ t.Fatalf("ReadDir %q failed: %v", dir, err2)
+ }
+ for _, m := range contents {
+ found := false
+ for _, n := range s {
+ if n.Name() == "." || n.Name() == ".." {
+ t.Errorf("got %q in directory", n)
+ }
+ if !equal(m, n.Name()) {
+ continue
+ }
+ if found {
+ t.Error("present twice:", m)
+ }
+ found = true
+ lstat, err := Lstat(dir + "/" + m)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n.IsDir() != lstat.IsDir() {
+ t.Errorf("%s: IsDir=%v, want %v", m, n.IsDir(), lstat.IsDir())
+ }
+ if n.Type() != lstat.Mode().Type() {
+ t.Errorf("%s: IsDir=%v, want %v", m, n.Type(), lstat.Mode().Type())
+ }
+ info, err := n.Info()
+ if err != nil {
+ t.Errorf("%s: Info: %v", m, err)
+ continue
+ }
+ if !SameFile(info, lstat) {
+ t.Errorf("%s: Info: SameFile(info, lstat) = false", m)
+ }
+ }
+ if !found {
+ t.Error("could not find", m)
+ }
+ }
+ if s == nil {
+ t.Error("ReadDir returned nil instead of empty slice")
+ }
+ }
+}
+
+func TestFileReaddirnames(t *testing.T) {
+ t.Parallel()
+
+ t.Run(".", testReaddirnames(".", dot))
+ t.Run("sysdir", testReaddirnames(sysdir.name, sysdir.files))
+ t.Run("TempDir", testReaddirnames(t.TempDir(), nil))
+}
+
+func TestFileReaddir(t *testing.T) {
+ t.Parallel()
+
+ t.Run(".", testReaddir(".", dot))
+ t.Run("sysdir", testReaddir(sysdir.name, sysdir.files))
+ t.Run("TempDir", testReaddir(t.TempDir(), nil))
+}
+
+func TestFileReadDir(t *testing.T) {
+ t.Parallel()
+
+ t.Run(".", testReadDir(".", dot))
+ t.Run("sysdir", testReadDir(sysdir.name, sysdir.files))
+ t.Run("TempDir", testReadDir(t.TempDir(), nil))
+}
+
+func benchmarkReaddirname(path string, b *testing.B) {
+ var nentries int
+ for i := 0; i < b.N; i++ {
+ f, err := Open(path)
+ if err != nil {
+ b.Fatalf("open %q failed: %v", path, err)
+ }
+ ns, err := f.Readdirnames(-1)
+ f.Close()
+ if err != nil {
+ b.Fatalf("readdirnames %q failed: %v", path, err)
+ }
+ nentries = len(ns)
+ }
+ b.Logf("benchmarkReaddirname %q: %d entries", path, nentries)
+}
+
+func benchmarkReaddir(path string, b *testing.B) {
+ var nentries int
+ for i := 0; i < b.N; i++ {
+ f, err := Open(path)
+ if err != nil {
+ b.Fatalf("open %q failed: %v", path, err)
+ }
+ fs, err := f.Readdir(-1)
+ f.Close()
+ if err != nil {
+ b.Fatalf("readdir %q failed: %v", path, err)
+ }
+ nentries = len(fs)
+ }
+ b.Logf("benchmarkReaddir %q: %d entries", path, nentries)
+}
+
+func benchmarkReadDir(path string, b *testing.B) {
+ var nentries int
+ for i := 0; i < b.N; i++ {
+ f, err := Open(path)
+ if err != nil {
+ b.Fatalf("open %q failed: %v", path, err)
+ }
+ fs, err := f.ReadDir(-1)
+ f.Close()
+ if err != nil {
+ b.Fatalf("readdir %q failed: %v", path, err)
+ }
+ nentries = len(fs)
+ }
+ b.Logf("benchmarkReadDir %q: %d entries", path, nentries)
+}
+
+func BenchmarkReaddirname(b *testing.B) {
+ benchmarkReaddirname(".", b)
+}
+
+func BenchmarkReaddir(b *testing.B) {
+ benchmarkReaddir(".", b)
+}
+
+func BenchmarkReadDir(b *testing.B) {
+ benchmarkReadDir(".", b)
+}
+
+func benchmarkStat(b *testing.B, path string) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := Stat(path)
+ if err != nil {
+ b.Fatalf("Stat(%q) failed: %v", path, err)
+ }
+ }
+}
+
+func benchmarkLstat(b *testing.B, path string) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := Lstat(path)
+ if err != nil {
+ b.Fatalf("Lstat(%q) failed: %v", path, err)
+ }
+ }
+}
+
+func BenchmarkStatDot(b *testing.B) {
+ benchmarkStat(b, ".")
+}
+
+func BenchmarkStatFile(b *testing.B) {
+ benchmarkStat(b, filepath.Join(runtime.GOROOT(), "src/os/os_test.go"))
+}
+
+func BenchmarkStatDir(b *testing.B) {
+ benchmarkStat(b, filepath.Join(runtime.GOROOT(), "src/os"))
+}
+
+func BenchmarkLstatDot(b *testing.B) {
+ benchmarkLstat(b, ".")
+}
+
+func BenchmarkLstatFile(b *testing.B) {
+ benchmarkLstat(b, filepath.Join(runtime.GOROOT(), "src/os/os_test.go"))
+}
+
+func BenchmarkLstatDir(b *testing.B) {
+ benchmarkLstat(b, filepath.Join(runtime.GOROOT(), "src/os"))
+}
+
+// Read the directory one entry at a time.
+func smallReaddirnames(file *File, length int, t *testing.T) []string {
+ names := make([]string, length)
+ count := 0
+ for {
+ d, err := file.Readdirnames(1)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatalf("readdirnames %q failed: %v", file.Name(), err)
+ }
+ if len(d) == 0 {
+ t.Fatalf("readdirnames %q returned empty slice and no error", file.Name())
+ }
+ names[count] = d[0]
+ count++
+ }
+ return names[0:count]
+}
+
+// Check that reading a directory one entry at a time gives the same result
+// as reading it all at once.
+func TestReaddirnamesOneAtATime(t *testing.T) {
+ t.Parallel()
+
+ // big directory that doesn't change often.
+ dir := "/usr/bin"
+ switch runtime.GOOS {
+ case "android":
+ dir = "/system/bin"
+ case "ios", "wasip1":
+ wd, err := Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ dir = wd
+ case "plan9":
+ dir = "/bin"
+ case "windows":
+ dir = Getenv("SystemRoot") + "\\system32"
+ }
+ file, err := Open(dir)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", dir, err)
+ }
+ defer file.Close()
+ all, err1 := file.Readdirnames(-1)
+ if err1 != nil {
+ t.Fatalf("readdirnames %q failed: %v", dir, err1)
+ }
+ file1, err2 := Open(dir)
+ if err2 != nil {
+ t.Fatalf("open %q failed: %v", dir, err2)
+ }
+ defer file1.Close()
+ small := smallReaddirnames(file1, len(all)+100, t) // +100 in case we screw up
+ if len(small) < len(all) {
+ t.Fatalf("len(small) is %d, less than %d", len(small), len(all))
+ }
+ for i, n := range all {
+ if small[i] != n {
+ t.Errorf("small read %q mismatch: %v", small[i], n)
+ }
+ }
+}
+
+func TestReaddirNValues(t *testing.T) {
+ if testing.Short() {
+ t.Skip("test.short; skipping")
+ }
+ t.Parallel()
+
+ dir := t.TempDir()
+ for i := 1; i <= 105; i++ {
+ f, err := Create(filepath.Join(dir, fmt.Sprintf("%d", i)))
+ if err != nil {
+ t.Fatalf("Create: %v", err)
+ }
+ f.Write([]byte(strings.Repeat("X", i)))
+ f.Close()
+ }
+
+ var d *File
+ openDir := func() {
+ var err error
+ d, err = Open(dir)
+ if err != nil {
+ t.Fatalf("Open directory: %v", err)
+ }
+ }
+
+ readdirExpect := func(n, want int, wantErr error) {
+ t.Helper()
+ fi, err := d.Readdir(n)
+ if err != wantErr {
+ t.Fatalf("Readdir of %d got error %v, want %v", n, err, wantErr)
+ }
+ if g, e := len(fi), want; g != e {
+ t.Errorf("Readdir of %d got %d files, want %d", n, g, e)
+ }
+ }
+
+ readDirExpect := func(n, want int, wantErr error) {
+ t.Helper()
+ de, err := d.ReadDir(n)
+ if err != wantErr {
+ t.Fatalf("ReadDir of %d got error %v, want %v", n, err, wantErr)
+ }
+ if g, e := len(de), want; g != e {
+ t.Errorf("ReadDir of %d got %d files, want %d", n, g, e)
+ }
+ }
+
+ readdirnamesExpect := func(n, want int, wantErr error) {
+ t.Helper()
+ fi, err := d.Readdirnames(n)
+ if err != wantErr {
+ t.Fatalf("Readdirnames of %d got error %v, want %v", n, err, wantErr)
+ }
+ if g, e := len(fi), want; g != e {
+ t.Errorf("Readdirnames of %d got %d files, want %d", n, g, e)
+ }
+ }
+
+ for _, fn := range []func(int, int, error){readdirExpect, readdirnamesExpect, readDirExpect} {
+ // Test the slurp case
+ openDir()
+ fn(0, 105, nil)
+ fn(0, 0, nil)
+ d.Close()
+
+ // Slurp with -1 instead
+ openDir()
+ fn(-1, 105, nil)
+ fn(-2, 0, nil)
+ fn(0, 0, nil)
+ d.Close()
+
+ // Test the bounded case
+ openDir()
+ fn(1, 1, nil)
+ fn(2, 2, nil)
+ fn(105, 102, nil) // and tests buffer >100 case
+ fn(3, 0, io.EOF)
+ d.Close()
+ }
+}
+
+func touch(t *testing.T, name string) {
+ f, err := Create(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestReaddirStatFailures(t *testing.T) {
+ switch runtime.GOOS {
+ case "windows", "plan9":
+ // Windows and Plan 9 already do this correctly,
+ // but are structured with different syscalls such
+ // that they don't use Lstat, so the hook below for
+ // testing it wouldn't work.
+ t.Skipf("skipping test on %v", runtime.GOOS)
+ }
+
+ var xerr error // error to return for x
+ *LstatP = func(path string) (FileInfo, error) {
+ if xerr != nil && strings.HasSuffix(path, "x") {
+ return nil, xerr
+ }
+ return Lstat(path)
+ }
+ defer func() { *LstatP = Lstat }()
+
+ dir := t.TempDir()
+ touch(t, filepath.Join(dir, "good1"))
+ touch(t, filepath.Join(dir, "x")) // will disappear or have an error
+ touch(t, filepath.Join(dir, "good2"))
+ readDir := func() ([]FileInfo, error) {
+ d, err := Open(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer d.Close()
+ return d.Readdir(-1)
+ }
+ mustReadDir := func(testName string) []FileInfo {
+ fis, err := readDir()
+ if err != nil {
+ t.Fatalf("%s: Readdir: %v", testName, err)
+ }
+ return fis
+ }
+ names := func(fis []FileInfo) []string {
+ s := make([]string, len(fis))
+ for i, fi := range fis {
+ s[i] = fi.Name()
+ }
+ sort.Strings(s)
+ return s
+ }
+
+ if got, want := names(mustReadDir("initial readdir")),
+ []string{"good1", "good2", "x"}; !reflect.DeepEqual(got, want) {
+ t.Errorf("initial readdir got %q; want %q", got, want)
+ }
+
+ xerr = ErrNotExist
+ if got, want := names(mustReadDir("with x disappearing")),
+ []string{"good1", "good2"}; !reflect.DeepEqual(got, want) {
+ t.Errorf("with x disappearing, got %q; want %q", got, want)
+ }
+
+ xerr = errors.New("some real error")
+ if _, err := readDir(); err != xerr {
+ t.Errorf("with a non-ErrNotExist error, got error %v; want %v", err, xerr)
+ }
+}
+
+// Readdir on a regular file should fail.
+func TestReaddirOfFile(t *testing.T) {
+ t.Parallel()
+
+ f, err := CreateTemp(t.TempDir(), "_Go_ReaddirOfFile")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.Write([]byte("foo"))
+ f.Close()
+ reg, err := Open(f.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer reg.Close()
+
+ names, err := reg.Readdirnames(-1)
+ if err == nil {
+ t.Error("Readdirnames succeeded; want non-nil error")
+ }
+ var pe *PathError
+ if !errors.As(err, &pe) || pe.Path != f.Name() {
+ t.Errorf("Readdirnames returned %q; want a PathError with path %q", err, f.Name())
+ }
+ if len(names) > 0 {
+ t.Errorf("unexpected dir names in regular file: %q", names)
+ }
+}
+
+func TestHardLink(t *testing.T) {
+ testenv.MustHaveLink(t)
+
+ defer chtmpdir(t)()
+ from, to := "hardlinktestfrom", "hardlinktestto"
+ file, err := Create(to)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", to, err)
+ }
+ if err = file.Close(); err != nil {
+ t.Errorf("close %q failed: %v", to, err)
+ }
+ err = Link(to, from)
+ if err != nil {
+ t.Fatalf("link %q, %q failed: %v", to, from, err)
+ }
+
+ none := "hardlinktestnone"
+ err = Link(none, none)
+ // Check the returned error is well-formed.
+ if lerr, ok := err.(*LinkError); !ok || lerr.Error() == "" {
+ t.Errorf("link %q, %q failed to return a valid error", none, none)
+ }
+
+ tostat, err := Stat(to)
+ if err != nil {
+ t.Fatalf("stat %q failed: %v", to, err)
+ }
+ fromstat, err := Stat(from)
+ if err != nil {
+ t.Fatalf("stat %q failed: %v", from, err)
+ }
+ if !SameFile(tostat, fromstat) {
+ t.Errorf("link %q, %q did not create hard link", to, from)
+ }
+ // We should not be able to perform the same Link() a second time
+ err = Link(to, from)
+ switch err := err.(type) {
+ case *LinkError:
+ if err.Op != "link" {
+ t.Errorf("Link(%q, %q) err.Op = %q; want %q", to, from, err.Op, "link")
+ }
+ if err.Old != to {
+ t.Errorf("Link(%q, %q) err.Old = %q; want %q", to, from, err.Old, to)
+ }
+ if err.New != from {
+ t.Errorf("Link(%q, %q) err.New = %q; want %q", to, from, err.New, from)
+ }
+ if !IsExist(err.Err) {
+ t.Errorf("Link(%q, %q) err.Err = %q; want %q", to, from, err.Err, "file exists error")
+ }
+ case nil:
+ t.Errorf("link %q, %q: expected error, got nil", from, to)
+ default:
+ t.Errorf("link %q, %q: expected %T, got %T %v", from, to, new(LinkError), err, err)
+ }
+}
+
+// chtmpdir changes the working directory to a new temporary directory and
+// provides a cleanup function.
+func chtmpdir(t *testing.T) func() {
+ oldwd, err := Getwd()
+ if err != nil {
+ t.Fatalf("chtmpdir: %v", err)
+ }
+ d, err := MkdirTemp("", "test")
+ if err != nil {
+ t.Fatalf("chtmpdir: %v", err)
+ }
+ if err := Chdir(d); err != nil {
+ t.Fatalf("chtmpdir: %v", err)
+ }
+ return func() {
+ if err := Chdir(oldwd); err != nil {
+ t.Fatalf("chtmpdir: %v", err)
+ }
+ RemoveAll(d)
+ }
+}
+
+func TestSymlink(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+
+ defer chtmpdir(t)()
+ from, to := "symlinktestfrom", "symlinktestto"
+ file, err := Create(to)
+ if err != nil {
+ t.Fatalf("Create(%q) failed: %v", to, err)
+ }
+ if err = file.Close(); err != nil {
+ t.Errorf("Close(%q) failed: %v", to, err)
+ }
+ err = Symlink(to, from)
+ if err != nil {
+ t.Fatalf("Symlink(%q, %q) failed: %v", to, from, err)
+ }
+ tostat, err := Lstat(to)
+ if err != nil {
+ t.Fatalf("Lstat(%q) failed: %v", to, err)
+ }
+ if tostat.Mode()&ModeSymlink != 0 {
+ t.Fatalf("Lstat(%q).Mode()&ModeSymlink = %v, want 0", to, tostat.Mode()&ModeSymlink)
+ }
+ fromstat, err := Stat(from)
+ if err != nil {
+ t.Fatalf("Stat(%q) failed: %v", from, err)
+ }
+ if !SameFile(tostat, fromstat) {
+ t.Errorf("Symlink(%q, %q) did not create symlink", to, from)
+ }
+ fromstat, err = Lstat(from)
+ if err != nil {
+ t.Fatalf("Lstat(%q) failed: %v", from, err)
+ }
+ if fromstat.Mode()&ModeSymlink == 0 {
+ t.Fatalf("Lstat(%q).Mode()&ModeSymlink = 0, want %v", from, ModeSymlink)
+ }
+ fromstat, err = Stat(from)
+ if err != nil {
+ t.Fatalf("Stat(%q) failed: %v", from, err)
+ }
+ if fromstat.Name() != from {
+ t.Errorf("Stat(%q).Name() = %q, want %q", from, fromstat.Name(), from)
+ }
+ if fromstat.Mode()&ModeSymlink != 0 {
+ t.Fatalf("Stat(%q).Mode()&ModeSymlink = %v, want 0", from, fromstat.Mode()&ModeSymlink)
+ }
+ s, err := Readlink(from)
+ if err != nil {
+ t.Fatalf("Readlink(%q) failed: %v", from, err)
+ }
+ if s != to {
+ t.Fatalf("Readlink(%q) = %q, want %q", from, s, to)
+ }
+ file, err = Open(from)
+ if err != nil {
+ t.Fatalf("Open(%q) failed: %v", from, err)
+ }
+ file.Close()
+}
+
+func TestLongSymlink(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+
+ defer chtmpdir(t)()
+ s := "0123456789abcdef"
+ // Long, but not too long: a common limit is 255.
+ s = s + s + s + s + s + s + s + s + s + s + s + s + s + s + s
+ from := "longsymlinktestfrom"
+ err := Symlink(s, from)
+ if err != nil {
+ t.Fatalf("symlink %q, %q failed: %v", s, from, err)
+ }
+ r, err := Readlink(from)
+ if err != nil {
+ t.Fatalf("readlink %q failed: %v", from, err)
+ }
+ if r != s {
+ t.Fatalf("after symlink %q != %q", r, s)
+ }
+}
+
+func TestRename(t *testing.T) {
+ defer chtmpdir(t)()
+ from, to := "renamefrom", "renameto"
+
+ file, err := Create(from)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", from, err)
+ }
+ if err = file.Close(); err != nil {
+ t.Errorf("close %q failed: %v", from, err)
+ }
+ err = Rename(from, to)
+ if err != nil {
+ t.Fatalf("rename %q, %q failed: %v", to, from, err)
+ }
+ _, err = Stat(to)
+ if err != nil {
+ t.Errorf("stat %q failed: %v", to, err)
+ }
+}
+
+func TestRenameOverwriteDest(t *testing.T) {
+ defer chtmpdir(t)()
+ from, to := "renamefrom", "renameto"
+
+ toData := []byte("to")
+ fromData := []byte("from")
+
+ err := WriteFile(to, toData, 0777)
+ if err != nil {
+ t.Fatalf("write file %q failed: %v", to, err)
+ }
+
+ err = WriteFile(from, fromData, 0777)
+ if err != nil {
+ t.Fatalf("write file %q failed: %v", from, err)
+ }
+ err = Rename(from, to)
+ if err != nil {
+ t.Fatalf("rename %q, %q failed: %v", to, from, err)
+ }
+
+ _, err = Stat(from)
+ if err == nil {
+ t.Errorf("from file %q still exists", from)
+ }
+ if err != nil && !IsNotExist(err) {
+ t.Fatalf("stat from: %v", err)
+ }
+ toFi, err := Stat(to)
+ if err != nil {
+ t.Fatalf("stat %q failed: %v", to, err)
+ }
+ if toFi.Size() != int64(len(fromData)) {
+ t.Errorf(`"to" size = %d; want %d (old "from" size)`, toFi.Size(), len(fromData))
+ }
+}
+
+func TestRenameFailed(t *testing.T) {
+ defer chtmpdir(t)()
+ from, to := "renamefrom", "renameto"
+
+ err := Rename(from, to)
+ switch err := err.(type) {
+ case *LinkError:
+ if err.Op != "rename" {
+ t.Errorf("rename %q, %q: err.Op: want %q, got %q", from, to, "rename", err.Op)
+ }
+ if err.Old != from {
+ t.Errorf("rename %q, %q: err.Old: want %q, got %q", from, to, from, err.Old)
+ }
+ if err.New != to {
+ t.Errorf("rename %q, %q: err.New: want %q, got %q", from, to, to, err.New)
+ }
+ case nil:
+ t.Errorf("rename %q, %q: expected error, got nil", from, to)
+ default:
+ t.Errorf("rename %q, %q: expected %T, got %T %v", from, to, new(LinkError), err, err)
+ }
+}
+
+func TestRenameNotExisting(t *testing.T) {
+ defer chtmpdir(t)()
+ from, to := "doesnt-exist", "dest"
+
+ Mkdir(to, 0777)
+
+ if err := Rename(from, to); !IsNotExist(err) {
+ t.Errorf("Rename(%q, %q) = %v; want an IsNotExist error", from, to, err)
+ }
+}
+
+func TestRenameToDirFailed(t *testing.T) {
+ defer chtmpdir(t)()
+ from, to := "renamefrom", "renameto"
+
+ Mkdir(from, 0777)
+ Mkdir(to, 0777)
+
+ err := Rename(from, to)
+ switch err := err.(type) {
+ case *LinkError:
+ if err.Op != "rename" {
+ t.Errorf("rename %q, %q: err.Op: want %q, got %q", from, to, "rename", err.Op)
+ }
+ if err.Old != from {
+ t.Errorf("rename %q, %q: err.Old: want %q, got %q", from, to, from, err.Old)
+ }
+ if err.New != to {
+ t.Errorf("rename %q, %q: err.New: want %q, got %q", from, to, to, err.New)
+ }
+ case nil:
+ t.Errorf("rename %q, %q: expected error, got nil", from, to)
+ default:
+ t.Errorf("rename %q, %q: expected %T, got %T %v", from, to, new(LinkError), err, err)
+ }
+}
+
+func TestRenameCaseDifference(pt *testing.T) {
+ from, to := "renameFROM", "RENAMEfrom"
+ tests := []struct {
+ name string
+ create func() error
+ }{
+ {"dir", func() error {
+ return Mkdir(from, 0777)
+ }},
+ {"file", func() error {
+ fd, err := Create(from)
+ if err != nil {
+ return err
+ }
+ return fd.Close()
+ }},
+ }
+
+ for _, test := range tests {
+ pt.Run(test.name, func(t *testing.T) {
+ defer chtmpdir(t)()
+
+ if err := test.create(); err != nil {
+ t.Fatalf("failed to create test file: %s", err)
+ }
+
+ if _, err := Stat(to); err != nil {
+ // Sanity check that the underlying filesystem is not case sensitive.
+ if IsNotExist(err) {
+ t.Skipf("case sensitive filesystem")
+ }
+ t.Fatalf("stat %q, got: %q", to, err)
+ }
+
+ if err := Rename(from, to); err != nil {
+ t.Fatalf("unexpected error when renaming from %q to %q: %s", from, to, err)
+ }
+
+ fd, err := Open(".")
+ if err != nil {
+ t.Fatalf("Open .: %s", err)
+ }
+
+ // Stat does not return the real case of the file (it returns what the called asked for)
+ // So we have to use readdir to get the real name of the file.
+ dirNames, err := fd.Readdirnames(-1)
+ if err != nil {
+ t.Fatalf("readdirnames: %s", err)
+ }
+
+ if dirNamesLen := len(dirNames); dirNamesLen != 1 {
+ t.Fatalf("unexpected dirNames len, got %q, want %q", dirNamesLen, 1)
+ }
+
+ if dirNames[0] != to {
+ t.Errorf("unexpected name, got %q, want %q", dirNames[0], to)
+ }
+ })
+ }
+}
+
+func testStartProcess(dir, cmd string, args []string, expect string) func(t *testing.T) {
+ return func(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := Pipe()
+ if err != nil {
+ t.Fatalf("Pipe: %v", err)
+ }
+ defer r.Close()
+ attr := &ProcAttr{Dir: dir, Files: []*File{nil, w, Stderr}}
+ p, err := StartProcess(cmd, args, attr)
+ if err != nil {
+ t.Fatalf("StartProcess: %v", err)
+ }
+ w.Close()
+
+ var b strings.Builder
+ io.Copy(&b, r)
+ output := b.String()
+
+ fi1, _ := Stat(strings.TrimSpace(output))
+ fi2, _ := Stat(expect)
+ if !SameFile(fi1, fi2) {
+ t.Errorf("exec %q returned %q wanted %q",
+ strings.Join(append([]string{cmd}, args...), " "), output, expect)
+ }
+ p.Wait()
+ }
+}
+
+func TestStartProcess(t *testing.T) {
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ var dir, cmd string
+ var args []string
+ switch runtime.GOOS {
+ case "android":
+ t.Skip("android doesn't have /bin/pwd")
+ case "windows":
+ cmd = Getenv("COMSPEC")
+ dir = Getenv("SystemRoot")
+ args = []string{"/c", "cd"}
+ default:
+ var err error
+ cmd, err = exec.LookPath("pwd")
+ if err != nil {
+ t.Fatalf("Can't find pwd: %v", err)
+ }
+ dir = "/"
+ args = []string{}
+ t.Logf("Testing with %v", cmd)
+ }
+ cmddir, cmdbase := filepath.Split(cmd)
+ args = append([]string{cmdbase}, args...)
+ t.Run("absolute", testStartProcess(dir, cmd, args, dir))
+ t.Run("relative", testStartProcess(cmddir, cmdbase, args, cmddir))
+}
+
+func checkMode(t *testing.T, path string, mode FileMode) {
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err)
+ }
+ if dir.Mode()&ModePerm != mode {
+ t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode)
+ }
+}
+
+func TestChmod(t *testing.T) {
+ // Chmod is not supported on wasip1.
+ if runtime.GOOS == "wasip1" {
+ t.Skip("Chmod is not supported on " + runtime.GOOS)
+ }
+ t.Parallel()
+
+ f := newFile("TestChmod", t)
+ defer Remove(f.Name())
+ defer f.Close()
+ // Creation mode is read write
+
+ fm := FileMode(0456)
+ if runtime.GOOS == "windows" {
+ fm = FileMode(0444) // read-only file
+ }
+ if err := Chmod(f.Name(), fm); err != nil {
+ t.Fatalf("chmod %s %#o: %s", f.Name(), fm, err)
+ }
+ checkMode(t, f.Name(), fm)
+
+ fm = FileMode(0123)
+ if runtime.GOOS == "windows" {
+ fm = FileMode(0666) // read-write file
+ }
+ if err := f.Chmod(fm); err != nil {
+ t.Fatalf("chmod %s %#o: %s", f.Name(), fm, err)
+ }
+ checkMode(t, f.Name(), fm)
+}
+
+func checkSize(t *testing.T, f *File, size int64) {
+ t.Helper()
+ dir, err := f.Stat()
+ if err != nil {
+ t.Fatalf("Stat %q (looking for size %d): %s", f.Name(), size, err)
+ }
+ if dir.Size() != size {
+ t.Errorf("Stat %q: size %d want %d", f.Name(), dir.Size(), size)
+ }
+}
+
+func TestFTruncate(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestFTruncate", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ checkSize(t, f, 0)
+ f.Write([]byte("hello, world\n"))
+ checkSize(t, f, 13)
+ f.Truncate(10)
+ checkSize(t, f, 10)
+ f.Truncate(1024)
+ checkSize(t, f, 1024)
+ f.Truncate(0)
+ checkSize(t, f, 0)
+ _, err := f.Write([]byte("surprise!"))
+ if err == nil {
+ checkSize(t, f, 13+9) // wrote at offset past where hello, world was.
+ }
+}
+
+func TestTruncate(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestTruncate", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ checkSize(t, f, 0)
+ f.Write([]byte("hello, world\n"))
+ checkSize(t, f, 13)
+ Truncate(f.Name(), 10)
+ checkSize(t, f, 10)
+ Truncate(f.Name(), 1024)
+ checkSize(t, f, 1024)
+ Truncate(f.Name(), 0)
+ checkSize(t, f, 0)
+ _, err := f.Write([]byte("surprise!"))
+ if err == nil {
+ checkSize(t, f, 13+9) // wrote at offset past where hello, world was.
+ }
+}
+
+func TestTruncateNonexistentFile(t *testing.T) {
+ t.Parallel()
+
+ assertPathError := func(t testing.TB, path string, err error) {
+ t.Helper()
+ if pe, ok := err.(*PathError); !ok || !IsNotExist(err) || pe.Path != path {
+ t.Errorf("got error: %v\nwant an ErrNotExist PathError with path %q", err, path)
+ }
+ }
+
+ path := filepath.Join(t.TempDir(), "nonexistent")
+
+ err := Truncate(path, 1)
+ assertPathError(t, path, err)
+
+ // Truncate shouldn't create any new file.
+ _, err = Stat(path)
+ assertPathError(t, path, err)
+}
+
+// Use TempDir (via newFile) to make sure we're on a local file system,
+// so that timings are not distorted by latency and caching.
+// On NFS, timings can be off due to caching of meta-data on
+// NFS servers (Issue 848).
+func TestChtimes(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestChtimes", t)
+ defer Remove(f.Name())
+
+ f.Write([]byte("hello, world\n"))
+ f.Close()
+
+ testChtimes(t, f.Name())
+}
+
+func TestChtimesWithZeroTimes(t *testing.T) {
+ file := newFile("chtimes-with-zero", t)
+ _, err := file.Write([]byte("hello, world\n"))
+ if err != nil {
+ t.Fatalf("Write: %s", err)
+ }
+ fName := file.Name()
+ defer Remove(file.Name())
+ err = file.Close()
+ if err != nil {
+ t.Errorf("%v", err)
+ }
+ fs, err := Stat(fName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ startAtime := Atime(fs)
+ startMtime := fs.ModTime()
+ switch runtime.GOOS {
+ case "js":
+ startAtime = startAtime.Truncate(time.Second)
+ startMtime = startMtime.Truncate(time.Second)
+ }
+ at0 := startAtime
+ mt0 := startMtime
+ t0 := startMtime.Truncate(time.Second).Add(1 * time.Hour)
+
+ tests := []struct {
+ aTime time.Time
+ mTime time.Time
+ wantATime time.Time
+ wantMTime time.Time
+ }{
+ {
+ aTime: time.Time{},
+ mTime: time.Time{},
+ wantATime: startAtime,
+ wantMTime: startMtime,
+ },
+ {
+ aTime: t0.Add(200 * time.Second),
+ mTime: time.Time{},
+ wantATime: t0.Add(200 * time.Second),
+ wantMTime: startMtime,
+ },
+ {
+ aTime: time.Time{},
+ mTime: t0.Add(100 * time.Second),
+ wantATime: t0.Add(200 * time.Second),
+ wantMTime: t0.Add(100 * time.Second),
+ },
+ {
+ aTime: t0.Add(300 * time.Second),
+ mTime: t0.Add(100 * time.Second),
+ wantATime: t0.Add(300 * time.Second),
+ wantMTime: t0.Add(100 * time.Second),
+ },
+ }
+
+ for _, tt := range tests {
+ // Now change the times accordingly.
+ if err := Chtimes(fName, tt.aTime, tt.mTime); err != nil {
+ t.Error(err)
+ }
+
+ // Finally verify the expectations.
+ fs, err = Stat(fName)
+ if err != nil {
+ t.Error(err)
+ }
+ at0 = Atime(fs)
+ mt0 = fs.ModTime()
+
+ if got, want := at0, tt.wantATime; !got.Equal(want) {
+ errormsg := fmt.Sprintf("AccessTime mismatch with values ATime:%q-MTime:%q\ngot: %q\nwant: %q", tt.aTime, tt.mTime, got, want)
+ switch runtime.GOOS {
+ case "plan9":
+ // Mtime is the time of the last change of
+ // content. Similarly, atime is set whenever
+ // the contents are accessed; also, it is set
+ // whenever mtime is set.
+ case "windows":
+ t.Error(errormsg)
+ default: // unix's
+ if got, want := at0, tt.wantATime; !got.Equal(want) {
+ mounts, err := ReadFile("/bin/mounts")
+ if err != nil {
+ mounts, err = ReadFile("/etc/mtab")
+ }
+ if strings.Contains(string(mounts), "noatime") {
+ t.Log(errormsg)
+ t.Log("A filesystem is mounted with noatime; ignoring.")
+ } else {
+ switch runtime.GOOS {
+ case "netbsd", "dragonfly":
+ // On a 64-bit implementation, birth time is generally supported and cannot be changed.
+ // When supported, atime update is restricted and depends on the file system and on the
+ // OS configuration.
+ if strings.Contains(runtime.GOARCH, "64") {
+ t.Log(errormsg)
+ t.Log("Filesystem might not support atime changes; ignoring.")
+ }
+ default:
+ t.Error(errormsg)
+ }
+ }
+ }
+ }
+ }
+ if got, want := mt0, tt.wantMTime; !got.Equal(want) {
+ errormsg := fmt.Sprintf("ModTime mismatch with values ATime:%q-MTime:%q\ngot: %q\nwant: %q", tt.aTime, tt.mTime, got, want)
+ switch runtime.GOOS {
+ case "dragonfly":
+ t.Log(errormsg)
+ t.Log("Mtime is always updated; ignoring.")
+ default:
+ t.Error(errormsg)
+ }
+ }
+ }
+}
+
+// Use TempDir (via newDir) to make sure we're on a local file system,
+// so that timings are not distorted by latency and caching.
+// On NFS, timings can be off due to caching of meta-data on
+// NFS servers (Issue 848).
+func TestChtimesDir(t *testing.T) {
+ t.Parallel()
+
+ name := newDir("TestChtimes", t)
+ defer RemoveAll(name)
+
+ testChtimes(t, name)
+}
+
+func testChtimes(t *testing.T, name string) {
+ st, err := Stat(name)
+ if err != nil {
+ t.Fatalf("Stat %s: %s", name, err)
+ }
+ preStat := st
+
+ // Move access and modification time back a second
+ at := Atime(preStat)
+ mt := preStat.ModTime()
+ err = Chtimes(name, at.Add(-time.Second), mt.Add(-time.Second))
+ if err != nil {
+ t.Fatalf("Chtimes %s: %s", name, err)
+ }
+
+ st, err = Stat(name)
+ if err != nil {
+ t.Fatalf("second Stat %s: %s", name, err)
+ }
+ postStat := st
+
+ pat := Atime(postStat)
+ pmt := postStat.ModTime()
+ if !pat.Before(at) {
+ switch runtime.GOOS {
+ case "plan9":
+ // Mtime is the time of the last change of
+ // content. Similarly, atime is set whenever
+ // the contents are accessed; also, it is set
+ // whenever mtime is set.
+ case "netbsd":
+ mounts, _ := ReadFile("/proc/mounts")
+ if strings.Contains(string(mounts), "noatime") {
+ t.Logf("AccessTime didn't go backwards, but see a filesystem mounted noatime; ignoring. Issue 19293.")
+ } else {
+ t.Logf("AccessTime didn't go backwards; was=%v, after=%v (Ignoring on NetBSD, assuming noatime, Issue 19293)", at, pat)
+ }
+ default:
+ t.Errorf("AccessTime didn't go backwards; was=%v, after=%v", at, pat)
+ }
+ }
+
+ if !pmt.Before(mt) {
+ t.Errorf("ModTime didn't go backwards; was=%v, after=%v", mt, pmt)
+ }
+}
+
+func TestChtimesToUnixZero(t *testing.T) {
+ file := newFile("chtimes-to-unix-zero", t)
+ fn := file.Name()
+ defer Remove(fn)
+ if _, err := file.Write([]byte("hi")); err != nil {
+ t.Fatal(err)
+ }
+ if err := file.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ unixZero := time.Unix(0, 0)
+ if err := Chtimes(fn, unixZero, unixZero); err != nil {
+ t.Fatalf("Chtimes failed: %v", err)
+ }
+
+ st, err := Stat(fn)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if mt := st.ModTime(); mt != unixZero {
+ t.Errorf("mtime is %v, want %v", mt, unixZero)
+ }
+}
+
+func TestFileChdir(t *testing.T) {
+ wd, err := Getwd()
+ if err != nil {
+ t.Fatalf("Getwd: %s", err)
+ }
+ defer Chdir(wd)
+
+ fd, err := Open(".")
+ if err != nil {
+ t.Fatalf("Open .: %s", err)
+ }
+ defer fd.Close()
+
+ if err := Chdir("/"); err != nil {
+ t.Fatalf("Chdir /: %s", err)
+ }
+
+ if err := fd.Chdir(); err != nil {
+ t.Fatalf("fd.Chdir: %s", err)
+ }
+
+ wdNew, err := Getwd()
+ if err != nil {
+ t.Fatalf("Getwd: %s", err)
+ }
+ if !equal(wdNew, wd) {
+ t.Fatalf("fd.Chdir failed, got %s, want %s", wdNew, wd)
+ }
+}
+
+func TestChdirAndGetwd(t *testing.T) {
+ fd, err := Open(".")
+ if err != nil {
+ t.Fatalf("Open .: %s", err)
+ }
+ // These are chosen carefully not to be symlinks on a Mac
+ // (unlike, say, /var, /etc), except /tmp, which we handle below.
+ dirs := []string{"/", "/usr/bin", "/tmp"}
+ // /usr/bin does not usually exist on Plan 9 or Android.
+ switch runtime.GOOS {
+ case "android":
+ dirs = []string{"/system/bin"}
+ case "plan9":
+ dirs = []string{"/", "/usr"}
+ case "ios", "windows", "wasip1":
+ dirs = nil
+ for _, dir := range []string{t.TempDir(), t.TempDir()} {
+ // Expand symlinks so path equality tests work.
+ dir, err = filepath.EvalSymlinks(dir)
+ if err != nil {
+ t.Fatalf("EvalSymlinks: %v", err)
+ }
+ dirs = append(dirs, dir)
+ }
+ }
+ oldwd := Getenv("PWD")
+ for mode := 0; mode < 2; mode++ {
+ for _, d := range dirs {
+ if mode == 0 {
+ err = Chdir(d)
+ } else {
+ fd1, err1 := Open(d)
+ if err1 != nil {
+ t.Errorf("Open %s: %s", d, err1)
+ continue
+ }
+ err = fd1.Chdir()
+ fd1.Close()
+ }
+ if d == "/tmp" {
+ Setenv("PWD", "/tmp")
+ }
+ pwd, err1 := Getwd()
+ Setenv("PWD", oldwd)
+ err2 := fd.Chdir()
+ if err2 != nil {
+ // We changed the current directory and cannot go back.
+ // Don't let the tests continue; they'll scribble
+ // all over some other directory.
+ fmt.Fprintf(Stderr, "fchdir back to dot failed: %s\n", err2)
+ Exit(1)
+ }
+ if err != nil {
+ fd.Close()
+ t.Fatalf("Chdir %s: %s", d, err)
+ }
+ if err1 != nil {
+ fd.Close()
+ t.Fatalf("Getwd in %s: %s", d, err1)
+ }
+ if !equal(pwd, d) {
+ fd.Close()
+ t.Fatalf("Getwd returned %q want %q", pwd, d)
+ }
+ }
+ }
+ fd.Close()
+}
+
+// Test that Chdir+Getwd is program-wide.
+func TestProgWideChdir(t *testing.T) {
+ const N = 10
+ var wg sync.WaitGroup
+ hold := make(chan struct{})
+ done := make(chan struct{})
+
+ d := t.TempDir()
+ oldwd, err := Getwd()
+ if err != nil {
+ t.Fatalf("Getwd: %v", err)
+ }
+ defer func() {
+ if err := Chdir(oldwd); err != nil {
+ // It's not safe to continue with tests if we can't get back to
+ // the original working directory.
+ panic(err)
+ }
+ }()
+
+ // Note the deferred Wait must be called after the deferred close(done),
+ // to ensure the N goroutines have been released even if the main goroutine
+ // calls Fatalf. It must be called before the Chdir back to the original
+ // directory, and before the deferred deletion implied by TempDir,
+ // so as not to interfere while the N goroutines are still running.
+ defer wg.Wait()
+ defer close(done)
+
+ for i := 0; i < N; i++ {
+ wg.Add(1)
+ go func(i int) {
+ defer wg.Done()
+ // Lock half the goroutines in their own operating system
+ // thread to exercise more scheduler possibilities.
+ if i%2 == 1 {
+ // On Plan 9, after calling LockOSThread, the goroutines
+ // run on different processes which don't share the working
+ // directory. This used to be an issue because Go expects
+ // the working directory to be program-wide.
+ // See issue 9428.
+ runtime.LockOSThread()
+ }
+ select {
+ case <-done:
+ return
+ case <-hold:
+ }
+ // Getwd might be wrong
+ f0, err := Stat(".")
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ pwd, err := Getwd()
+ if err != nil {
+ t.Errorf("Getwd: %v", err)
+ return
+ }
+ if pwd != d {
+ t.Errorf("Getwd() = %q, want %q", pwd, d)
+ return
+ }
+ f1, err := Stat(pwd)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if !SameFile(f0, f1) {
+ t.Errorf(`Samefile(Stat("."), Getwd()) reports false (%s != %s)`, f0.Name(), f1.Name())
+ return
+ }
+ }(i)
+ }
+ if err = Chdir(d); err != nil {
+ t.Fatalf("Chdir: %v", err)
+ }
+ // OS X sets TMPDIR to a symbolic link.
+ // So we resolve our working directory again before the test.
+ d, err = Getwd()
+ if err != nil {
+ t.Fatalf("Getwd: %v", err)
+ }
+ close(hold)
+ wg.Wait()
+}
+
+func TestSeek(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestSeek", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ type test struct {
+ in int64
+ whence int
+ out int64
+ }
+ var tests = []test{
+ {0, io.SeekCurrent, int64(len(data))},
+ {0, io.SeekStart, 0},
+ {5, io.SeekStart, 5},
+ {0, io.SeekEnd, int64(len(data))},
+ {0, io.SeekStart, 0},
+ {-1, io.SeekEnd, int64(len(data)) - 1},
+ {1 << 33, io.SeekStart, 1 << 33},
+ {1 << 33, io.SeekEnd, 1<<33 + int64(len(data))},
+
+ // Issue 21681, Windows 4G-1, etc:
+ {1<<32 - 1, io.SeekStart, 1<<32 - 1},
+ {0, io.SeekCurrent, 1<<32 - 1},
+ {2<<32 - 1, io.SeekStart, 2<<32 - 1},
+ {0, io.SeekCurrent, 2<<32 - 1},
+ }
+ for i, tt := range tests {
+ off, err := f.Seek(tt.in, tt.whence)
+ if off != tt.out || err != nil {
+ if e, ok := err.(*PathError); ok && e.Err == syscall.EINVAL && tt.out > 1<<32 && runtime.GOOS == "linux" {
+ mounts, _ := ReadFile("/proc/mounts")
+ if strings.Contains(string(mounts), "reiserfs") {
+ // Reiserfs rejects the big seeks.
+ t.Skipf("skipping test known to fail on reiserfs; https://golang.org/issue/91")
+ }
+ }
+ t.Errorf("#%d: Seek(%v, %v) = %v, %v want %v, nil", i, tt.in, tt.whence, off, err, tt.out)
+ }
+ }
+}
+
+func TestSeekError(t *testing.T) {
+ switch runtime.GOOS {
+ case "js", "plan9", "wasip1":
+ t.Skipf("skipping test on %v", runtime.GOOS)
+ }
+ t.Parallel()
+
+ r, w, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = r.Seek(0, 0)
+ if err == nil {
+ t.Fatal("Seek on pipe should fail")
+ }
+ if perr, ok := err.(*PathError); !ok || perr.Err != syscall.ESPIPE {
+ t.Errorf("Seek returned error %v, want &PathError{Err: syscall.ESPIPE}", err)
+ }
+ _, err = w.Seek(0, 0)
+ if err == nil {
+ t.Fatal("Seek on pipe should fail")
+ }
+ if perr, ok := err.(*PathError); !ok || perr.Err != syscall.ESPIPE {
+ t.Errorf("Seek returned error %v, want &PathError{Err: syscall.ESPIPE}", err)
+ }
+}
+
+type openErrorTest struct {
+ path string
+ mode int
+ error error
+}
+
+var openErrorTests = []openErrorTest{
+ {
+ sfdir + "/no-such-file",
+ O_RDONLY,
+ syscall.ENOENT,
+ },
+ {
+ sfdir,
+ O_WRONLY,
+ syscall.EISDIR,
+ },
+ {
+ sfdir + "/" + sfname + "/no-such-file",
+ O_WRONLY,
+ syscall.ENOTDIR,
+ },
+}
+
+func TestOpenError(t *testing.T) {
+ t.Parallel()
+
+ for _, tt := range openErrorTests {
+ f, err := OpenFile(tt.path, tt.mode, 0)
+ if err == nil {
+ t.Errorf("Open(%q, %d) succeeded", tt.path, tt.mode)
+ f.Close()
+ continue
+ }
+ perr, ok := err.(*PathError)
+ if !ok {
+ t.Errorf("Open(%q, %d) returns error of %T type; want *PathError", tt.path, tt.mode, err)
+ }
+ if perr.Err != tt.error {
+ if runtime.GOOS == "plan9" {
+ syscallErrStr := perr.Err.Error()
+ expectedErrStr := strings.Replace(tt.error.Error(), "file ", "", 1)
+ if !strings.HasSuffix(syscallErrStr, expectedErrStr) {
+ // Some Plan 9 file servers incorrectly return
+ // EACCES rather than EISDIR when a directory is
+ // opened for write.
+ if tt.error == syscall.EISDIR && strings.HasSuffix(syscallErrStr, syscall.EACCES.Error()) {
+ continue
+ }
+ t.Errorf("Open(%q, %d) = _, %q; want suffix %q", tt.path, tt.mode, syscallErrStr, expectedErrStr)
+ }
+ continue
+ }
+ if runtime.GOOS == "dragonfly" {
+ // DragonFly incorrectly returns EACCES rather
+ // EISDIR when a directory is opened for write.
+ if tt.error == syscall.EISDIR && perr.Err == syscall.EACCES {
+ continue
+ }
+ }
+ t.Errorf("Open(%q, %d) = _, %q; want %q", tt.path, tt.mode, perr.Err.Error(), tt.error.Error())
+ }
+ }
+}
+
+func TestOpenNoName(t *testing.T) {
+ f, err := Open("")
+ if err == nil {
+ f.Close()
+ t.Fatal(`Open("") succeeded`)
+ }
+}
+
+func runBinHostname(t *testing.T) string {
+ // Run /bin/hostname and collect output.
+ r, w, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+
+ path, err := exec.LookPath("hostname")
+ if err != nil {
+ if errors.Is(err, exec.ErrNotFound) {
+ t.Skip("skipping test; test requires hostname but it does not exist")
+ }
+ t.Fatal(err)
+ }
+
+ argv := []string{"hostname"}
+ if runtime.GOOS == "aix" {
+ argv = []string{"hostname", "-s"}
+ }
+ p, err := StartProcess(path, argv, &ProcAttr{Files: []*File{nil, w, Stderr}})
+ if err != nil {
+ t.Fatal(err)
+ }
+ w.Close()
+
+ var b strings.Builder
+ io.Copy(&b, r)
+ _, err = p.Wait()
+ if err != nil {
+ t.Fatalf("run hostname Wait: %v", err)
+ }
+ err = p.Kill()
+ if err == nil {
+ t.Errorf("expected an error from Kill running 'hostname'")
+ }
+ output := b.String()
+ if n := len(output); n > 0 && output[n-1] == '\n' {
+ output = output[0 : n-1]
+ }
+ if output == "" {
+ t.Fatalf("/bin/hostname produced no output")
+ }
+
+ return output
+}
+
+func testWindowsHostname(t *testing.T, hostname string) {
+ cmd := testenv.Command(t, "hostname")
+ out, err := cmd.Output()
+ if err != nil {
+ t.Fatalf("Failed to execute hostname command: %v %s", err, out)
+ }
+ want := strings.Trim(string(out), "\r\n")
+ if hostname != want {
+ t.Fatalf("Hostname() = %q != system hostname of %q", hostname, want)
+ }
+}
+
+func TestHostname(t *testing.T) {
+ t.Parallel()
+
+ hostname, err := Hostname()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if hostname == "" {
+ t.Fatal("Hostname returned empty string and no error")
+ }
+ if strings.Contains(hostname, "\x00") {
+ t.Fatalf("unexpected zero byte in hostname: %q", hostname)
+ }
+
+ // There is no other way to fetch hostname on windows, but via winapi.
+ // On Plan 9 it can be taken from #c/sysname as Hostname() does.
+ switch runtime.GOOS {
+ case "android", "plan9":
+ // No /bin/hostname to verify against.
+ return
+ case "windows":
+ testWindowsHostname(t, hostname)
+ return
+ }
+
+ testenv.MustHaveExec(t)
+
+ // Check internal Hostname() against the output of /bin/hostname.
+ // Allow that the internal Hostname returns a Fully Qualified Domain Name
+ // and the /bin/hostname only returns the first component
+ want := runBinHostname(t)
+ if hostname != want {
+ host, _, ok := strings.Cut(hostname, ".")
+ if !ok || host != want {
+ t.Errorf("Hostname() = %q, want %q", hostname, want)
+ }
+ }
+}
+
+func TestReadAt(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestReadAt", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ b := make([]byte, 5)
+ n, err := f.ReadAt(b, 7)
+ if err != nil || n != len(b) {
+ t.Fatalf("ReadAt 7: %d, %v", n, err)
+ }
+ if string(b) != "world" {
+ t.Fatalf("ReadAt 7: have %q want %q", string(b), "world")
+ }
+}
+
+// Verify that ReadAt doesn't affect seek offset.
+// In the Plan 9 kernel, there used to be a bug in the implementation of
+// the pread syscall, where the channel offset was erroneously updated after
+// calling pread on a file.
+func TestReadAtOffset(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestReadAtOffset", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ f.Seek(0, 0)
+ b := make([]byte, 5)
+
+ n, err := f.ReadAt(b, 7)
+ if err != nil || n != len(b) {
+ t.Fatalf("ReadAt 7: %d, %v", n, err)
+ }
+ if string(b) != "world" {
+ t.Fatalf("ReadAt 7: have %q want %q", string(b), "world")
+ }
+
+ n, err = f.Read(b)
+ if err != nil || n != len(b) {
+ t.Fatalf("Read: %d, %v", n, err)
+ }
+ if string(b) != "hello" {
+ t.Fatalf("Read: have %q want %q", string(b), "hello")
+ }
+}
+
+// Verify that ReadAt doesn't allow negative offset.
+func TestReadAtNegativeOffset(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestReadAtNegativeOffset", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ f.Seek(0, 0)
+ b := make([]byte, 5)
+
+ n, err := f.ReadAt(b, -10)
+
+ const wantsub = "negative offset"
+ if !strings.Contains(fmt.Sprint(err), wantsub) || n != 0 {
+ t.Errorf("ReadAt(-10) = %v, %v; want 0, ...%q...", n, err, wantsub)
+ }
+}
+
+func TestWriteAt(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestWriteAt", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ n, err := f.WriteAt([]byte("WORLD"), 7)
+ if err != nil || n != 5 {
+ t.Fatalf("WriteAt 7: %d, %v", n, err)
+ }
+
+ b, err := ReadFile(f.Name())
+ if err != nil {
+ t.Fatalf("ReadFile %s: %v", f.Name(), err)
+ }
+ if string(b) != "hello, WORLD\n" {
+ t.Fatalf("after write: have %q want %q", string(b), "hello, WORLD\n")
+ }
+}
+
+// Verify that WriteAt doesn't allow negative offset.
+func TestWriteAtNegativeOffset(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestWriteAtNegativeOffset", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ n, err := f.WriteAt([]byte("WORLD"), -10)
+
+ const wantsub = "negative offset"
+ if !strings.Contains(fmt.Sprint(err), wantsub) || n != 0 {
+ t.Errorf("WriteAt(-10) = %v, %v; want 0, ...%q...", n, err, wantsub)
+ }
+}
+
+// Verify that WriteAt doesn't work in append mode.
+func TestWriteAtInAppendMode(t *testing.T) {
+ defer chtmpdir(t)()
+ f, err := OpenFile("write_at_in_append_mode.txt", O_APPEND|O_CREATE, 0666)
+ if err != nil {
+ t.Fatalf("OpenFile: %v", err)
+ }
+ defer f.Close()
+
+ _, err = f.WriteAt([]byte(""), 1)
+ if err != ErrWriteAtInAppendMode {
+ t.Fatalf("f.WriteAt returned %v, expected %v", err, ErrWriteAtInAppendMode)
+ }
+}
+
+func writeFile(t *testing.T, fname string, flag int, text string) string {
+ f, err := OpenFile(fname, flag, 0666)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+ n, err := io.WriteString(f, text)
+ if err != nil {
+ t.Fatalf("WriteString: %d, %v", n, err)
+ }
+ f.Close()
+ data, err := ReadFile(fname)
+ if err != nil {
+ t.Fatalf("ReadFile: %v", err)
+ }
+ return string(data)
+}
+
+func TestAppend(t *testing.T) {
+ defer chtmpdir(t)()
+ const f = "append.txt"
+ s := writeFile(t, f, O_CREATE|O_TRUNC|O_RDWR, "new")
+ if s != "new" {
+ t.Fatalf("writeFile: have %q want %q", s, "new")
+ }
+ s = writeFile(t, f, O_APPEND|O_RDWR, "|append")
+ if s != "new|append" {
+ t.Fatalf("writeFile: have %q want %q", s, "new|append")
+ }
+ s = writeFile(t, f, O_CREATE|O_APPEND|O_RDWR, "|append")
+ if s != "new|append|append" {
+ t.Fatalf("writeFile: have %q want %q", s, "new|append|append")
+ }
+ err := Remove(f)
+ if err != nil {
+ t.Fatalf("Remove: %v", err)
+ }
+ s = writeFile(t, f, O_CREATE|O_APPEND|O_RDWR, "new&append")
+ if s != "new&append" {
+ t.Fatalf("writeFile: after append have %q want %q", s, "new&append")
+ }
+ s = writeFile(t, f, O_CREATE|O_RDWR, "old")
+ if s != "old&append" {
+ t.Fatalf("writeFile: after create have %q want %q", s, "old&append")
+ }
+ s = writeFile(t, f, O_CREATE|O_TRUNC|O_RDWR, "new")
+ if s != "new" {
+ t.Fatalf("writeFile: after truncate have %q want %q", s, "new")
+ }
+}
+
+func TestStatDirWithTrailingSlash(t *testing.T) {
+ t.Parallel()
+
+ // Create new temporary directory and arrange to clean it up.
+ path := t.TempDir()
+
+ // Stat of path should succeed.
+ if _, err := Stat(path); err != nil {
+ t.Fatalf("stat %s failed: %s", path, err)
+ }
+
+ // Stat of path+"/" should succeed too.
+ path += "/"
+ if _, err := Stat(path); err != nil {
+ t.Fatalf("stat %s failed: %s", path, err)
+ }
+}
+
+func TestNilProcessStateString(t *testing.T) {
+ var ps *ProcessState
+ s := ps.String()
+ if s != "<nil>" {
+ t.Errorf("(*ProcessState)(nil).String() = %q, want %q", s, "<nil>")
+ }
+}
+
+func TestSameFile(t *testing.T) {
+ defer chtmpdir(t)()
+ fa, err := Create("a")
+ if err != nil {
+ t.Fatalf("Create(a): %v", err)
+ }
+ fa.Close()
+ fb, err := Create("b")
+ if err != nil {
+ t.Fatalf("Create(b): %v", err)
+ }
+ fb.Close()
+
+ ia1, err := Stat("a")
+ if err != nil {
+ t.Fatalf("Stat(a): %v", err)
+ }
+ ia2, err := Stat("a")
+ if err != nil {
+ t.Fatalf("Stat(a): %v", err)
+ }
+ if !SameFile(ia1, ia2) {
+ t.Errorf("files should be same")
+ }
+
+ ib, err := Stat("b")
+ if err != nil {
+ t.Fatalf("Stat(b): %v", err)
+ }
+ if SameFile(ia1, ib) {
+ t.Errorf("files should be different")
+ }
+}
+
+func testDevNullFileInfo(t *testing.T, statname, devNullName string, fi FileInfo) {
+ pre := fmt.Sprintf("%s(%q): ", statname, devNullName)
+ if fi.Size() != 0 {
+ t.Errorf(pre+"wrong file size have %d want 0", fi.Size())
+ }
+ if fi.Mode()&ModeDevice == 0 {
+ t.Errorf(pre+"wrong file mode %q: ModeDevice is not set", fi.Mode())
+ }
+ if fi.Mode()&ModeCharDevice == 0 {
+ t.Errorf(pre+"wrong file mode %q: ModeCharDevice is not set", fi.Mode())
+ }
+ if fi.Mode().IsRegular() {
+ t.Errorf(pre+"wrong file mode %q: IsRegular returns true", fi.Mode())
+ }
+}
+
+func testDevNullFile(t *testing.T, devNullName string) {
+ f, err := Open(devNullName)
+ if err != nil {
+ t.Fatalf("Open(%s): %v", devNullName, err)
+ }
+ defer f.Close()
+
+ fi, err := f.Stat()
+ if err != nil {
+ t.Fatalf("Stat(%s): %v", devNullName, err)
+ }
+ testDevNullFileInfo(t, "f.Stat", devNullName, fi)
+
+ fi, err = Stat(devNullName)
+ if err != nil {
+ t.Fatalf("Stat(%s): %v", devNullName, err)
+ }
+ testDevNullFileInfo(t, "Stat", devNullName, fi)
+}
+
+func TestDevNullFile(t *testing.T) {
+ t.Parallel()
+
+ testDevNullFile(t, DevNull)
+ if runtime.GOOS == "windows" {
+ testDevNullFile(t, "./nul")
+ testDevNullFile(t, "//./nul")
+ }
+}
+
+var testLargeWrite = flag.Bool("large_write", false, "run TestLargeWriteToConsole test that floods console with output")
+
+func TestLargeWriteToConsole(t *testing.T) {
+ if !*testLargeWrite {
+ t.Skip("skipping console-flooding test; enable with -large_write")
+ }
+ b := make([]byte, 32000)
+ for i := range b {
+ b[i] = '.'
+ }
+ b[len(b)-1] = '\n'
+ n, err := Stdout.Write(b)
+ if err != nil {
+ t.Fatalf("Write to os.Stdout failed: %v", err)
+ }
+ if n != len(b) {
+ t.Errorf("Write to os.Stdout should return %d; got %d", len(b), n)
+ }
+ n, err = Stderr.Write(b)
+ if err != nil {
+ t.Fatalf("Write to os.Stderr failed: %v", err)
+ }
+ if n != len(b) {
+ t.Errorf("Write to os.Stderr should return %d; got %d", len(b), n)
+ }
+}
+
+func TestStatDirModeExec(t *testing.T) {
+ if runtime.GOOS == "wasip1" {
+ t.Skip("Chmod is not supported on " + runtime.GOOS)
+ }
+ t.Parallel()
+
+ const mode = 0111
+
+ path := t.TempDir()
+ if err := Chmod(path, 0777); err != nil {
+ t.Fatalf("Chmod %q 0777: %v", path, err)
+ }
+
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err)
+ }
+ if dir.Mode()&mode != mode {
+ t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode()&mode, mode)
+ }
+}
+
+func TestStatStdin(t *testing.T) {
+ switch runtime.GOOS {
+ case "android", "plan9":
+ t.Skipf("%s doesn't have /bin/sh", runtime.GOOS)
+ }
+
+ if Getenv("GO_WANT_HELPER_PROCESS") == "1" {
+ st, err := Stdin.Stat()
+ if err != nil {
+ t.Fatalf("Stat failed: %v", err)
+ }
+ fmt.Println(st.Mode() & ModeNamedPipe)
+ Exit(0)
+ }
+
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ fi, err := Stdin.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ switch mode := fi.Mode(); {
+ case mode&ModeCharDevice != 0 && mode&ModeDevice != 0:
+ case mode&ModeNamedPipe != 0:
+ default:
+ t.Fatalf("unexpected Stdin mode (%v), want ModeCharDevice or ModeNamedPipe", mode)
+ }
+
+ var cmd *exec.Cmd
+ if runtime.GOOS == "windows" {
+ cmd = testenv.Command(t, "cmd", "/c", "echo output | "+Args[0]+" -test.run=TestStatStdin")
+ } else {
+ cmd = testenv.Command(t, "/bin/sh", "-c", "echo output | "+Args[0]+" -test.run=TestStatStdin")
+ }
+ cmd.Env = append(Environ(), "GO_WANT_HELPER_PROCESS=1")
+
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("Failed to spawn child process: %v %q", err, string(output))
+ }
+
+ // result will be like "prw-rw-rw"
+ if len(output) < 1 || output[0] != 'p' {
+ t.Fatalf("Child process reports stdin is not pipe '%v'", string(output))
+ }
+}
+
+func TestStatRelativeSymlink(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+ t.Parallel()
+
+ tmpdir := t.TempDir()
+ target := filepath.Join(tmpdir, "target")
+ f, err := Create(target)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ st, err := f.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ link := filepath.Join(tmpdir, "link")
+ err = Symlink(filepath.Base(target), link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ st1, err := Stat(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !SameFile(st, st1) {
+ t.Error("Stat doesn't follow relative symlink")
+ }
+
+ if runtime.GOOS == "windows" {
+ Remove(link)
+ err = Symlink(target[len(filepath.VolumeName(target)):], link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ st1, err := Stat(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !SameFile(st, st1) {
+ t.Error("Stat doesn't follow relative symlink")
+ }
+ }
+}
+
+func TestReadAtEOF(t *testing.T) {
+ t.Parallel()
+
+ f := newFile("TestReadAtEOF", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ _, err := f.ReadAt(make([]byte, 10), 0)
+ switch err {
+ case io.EOF:
+ // all good
+ case nil:
+ t.Fatalf("ReadAt succeeded")
+ default:
+ t.Fatalf("ReadAt failed: %s", err)
+ }
+}
+
+func TestLongPath(t *testing.T) {
+ t.Parallel()
+
+ tmpdir := newDir("TestLongPath", t)
+ defer func(d string) {
+ if err := RemoveAll(d); err != nil {
+ t.Fatalf("RemoveAll failed: %v", err)
+ }
+ }(tmpdir)
+
+ // Test the boundary of 247 and fewer bytes (normal) and 248 and more bytes (adjusted).
+ sizes := []int{247, 248, 249, 400}
+ for len(tmpdir) < 400 {
+ tmpdir += "/dir3456789"
+ }
+ for _, sz := range sizes {
+ t.Run(fmt.Sprintf("length=%d", sz), func(t *testing.T) {
+ sizedTempDir := tmpdir[:sz-1] + "x" // Ensure it does not end with a slash.
+
+ // The various sized runs are for this call to trigger the boundary
+ // condition.
+ if err := MkdirAll(sizedTempDir, 0755); err != nil {
+ t.Fatalf("MkdirAll failed: %v", err)
+ }
+ data := []byte("hello world\n")
+ if err := WriteFile(sizedTempDir+"/foo.txt", data, 0644); err != nil {
+ t.Fatalf("os.WriteFile() failed: %v", err)
+ }
+ if err := Rename(sizedTempDir+"/foo.txt", sizedTempDir+"/bar.txt"); err != nil {
+ t.Fatalf("Rename failed: %v", err)
+ }
+ mtime := time.Now().Truncate(time.Minute)
+ if err := Chtimes(sizedTempDir+"/bar.txt", mtime, mtime); err != nil {
+ t.Fatalf("Chtimes failed: %v", err)
+ }
+ names := []string{"bar.txt"}
+ if testenv.HasSymlink() {
+ if err := Symlink(sizedTempDir+"/bar.txt", sizedTempDir+"/symlink.txt"); err != nil {
+ t.Fatalf("Symlink failed: %v", err)
+ }
+ names = append(names, "symlink.txt")
+ }
+ if testenv.HasLink() {
+ if err := Link(sizedTempDir+"/bar.txt", sizedTempDir+"/link.txt"); err != nil {
+ t.Fatalf("Link failed: %v", err)
+ }
+ names = append(names, "link.txt")
+ }
+ for _, wantSize := range []int64{int64(len(data)), 0} {
+ for _, name := range names {
+ path := sizedTempDir + "/" + name
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatalf("Stat(%q) failed: %v", path, err)
+ }
+ filesize := size(path, t)
+ if dir.Size() != filesize || filesize != wantSize {
+ t.Errorf("Size(%q) is %d, len(ReadFile()) is %d, want %d", path, dir.Size(), filesize, wantSize)
+ }
+ if runtime.GOOS != "wasip1" { // Chmod is not supported on wasip1
+ err = Chmod(path, dir.Mode())
+ if err != nil {
+ t.Fatalf("Chmod(%q) failed: %v", path, err)
+ }
+ }
+ }
+ if err := Truncate(sizedTempDir+"/bar.txt", 0); err != nil {
+ t.Fatalf("Truncate failed: %v", err)
+ }
+ }
+ })
+ }
+}
+
+func testKillProcess(t *testing.T, processKiller func(p *Process)) {
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ // Re-exec the test binary to start a process that hangs until stdin is closed.
+ cmd := testenv.Command(t, Args[0])
+ cmd.Env = append(cmd.Environ(), "GO_OS_TEST_DRAIN_STDIN=1")
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = cmd.Start()
+ if err != nil {
+ t.Fatalf("Failed to start test process: %v", err)
+ }
+
+ defer func() {
+ if err := cmd.Wait(); err == nil {
+ t.Errorf("Test process succeeded, but expected to fail")
+ }
+ stdin.Close() // Keep stdin alive until the process has finished dying.
+ }()
+
+ // Wait for the process to be started.
+ // (It will close its stdout when it reaches TestMain.)
+ io.Copy(io.Discard, stdout)
+
+ processKiller(cmd.Process)
+}
+
+func TestKillStartProcess(t *testing.T) {
+ testKillProcess(t, func(p *Process) {
+ err := p.Kill()
+ if err != nil {
+ t.Fatalf("Failed to kill test process: %v", err)
+ }
+ })
+}
+
+func TestGetppid(t *testing.T) {
+ if runtime.GOOS == "plan9" {
+ // TODO: golang.org/issue/8206
+ t.Skipf("skipping test on plan9; see issue 8206")
+ }
+
+ if Getenv("GO_WANT_HELPER_PROCESS") == "1" {
+ fmt.Print(Getppid())
+ Exit(0)
+ }
+
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ cmd := testenv.Command(t, Args[0], "-test.run=TestGetppid")
+ cmd.Env = append(Environ(), "GO_WANT_HELPER_PROCESS=1")
+
+ // verify that Getppid() from the forked process reports our process id
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("Failed to spawn child process: %v %q", err, string(output))
+ }
+
+ childPpid := string(output)
+ ourPid := fmt.Sprintf("%d", Getpid())
+ if childPpid != ourPid {
+ t.Fatalf("Child process reports parent process id '%v', expected '%v'", childPpid, ourPid)
+ }
+}
+
+func TestKillFindProcess(t *testing.T) {
+ testKillProcess(t, func(p *Process) {
+ p2, err := FindProcess(p.Pid)
+ if err != nil {
+ t.Fatalf("Failed to find test process: %v", err)
+ }
+ err = p2.Kill()
+ if err != nil {
+ t.Fatalf("Failed to kill test process: %v", err)
+ }
+ })
+}
+
+var nilFileMethodTests = []struct {
+ name string
+ f func(*File) error
+}{
+ {"Chdir", func(f *File) error { return f.Chdir() }},
+ {"Close", func(f *File) error { return f.Close() }},
+ {"Chmod", func(f *File) error { return f.Chmod(0) }},
+ {"Chown", func(f *File) error { return f.Chown(0, 0) }},
+ {"Read", func(f *File) error { _, err := f.Read(make([]byte, 0)); return err }},
+ {"ReadAt", func(f *File) error { _, err := f.ReadAt(make([]byte, 0), 0); return err }},
+ {"Readdir", func(f *File) error { _, err := f.Readdir(1); return err }},
+ {"Readdirnames", func(f *File) error { _, err := f.Readdirnames(1); return err }},
+ {"Seek", func(f *File) error { _, err := f.Seek(0, io.SeekStart); return err }},
+ {"Stat", func(f *File) error { _, err := f.Stat(); return err }},
+ {"Sync", func(f *File) error { return f.Sync() }},
+ {"Truncate", func(f *File) error { return f.Truncate(0) }},
+ {"Write", func(f *File) error { _, err := f.Write(make([]byte, 0)); return err }},
+ {"WriteAt", func(f *File) error { _, err := f.WriteAt(make([]byte, 0), 0); return err }},
+ {"WriteString", func(f *File) error { _, err := f.WriteString(""); return err }},
+}
+
+// Test that all File methods give ErrInvalid if the receiver is nil.
+func TestNilFileMethods(t *testing.T) {
+ t.Parallel()
+
+ for _, tt := range nilFileMethodTests {
+ var file *File
+ got := tt.f(file)
+ if got != ErrInvalid {
+ t.Errorf("%v should fail when f is nil; got %v", tt.name, got)
+ }
+ }
+}
+
+func mkdirTree(t *testing.T, root string, level, max int) {
+ if level >= max {
+ return
+ }
+ level++
+ for i := 'a'; i < 'c'; i++ {
+ dir := filepath.Join(root, string(i))
+ if err := Mkdir(dir, 0700); err != nil {
+ t.Fatal(err)
+ }
+ mkdirTree(t, dir, level, max)
+ }
+}
+
+// Test that simultaneous RemoveAll do not report an error.
+// As long as it gets removed, we should be happy.
+func TestRemoveAllRace(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ // Windows has very strict rules about things like
+ // removing directories while someone else has
+ // them open. The racing doesn't work out nicely
+ // like it does on Unix.
+ t.Skip("skipping on windows")
+ }
+ if runtime.GOOS == "dragonfly" {
+ testenv.SkipFlaky(t, 52301)
+ }
+
+ n := runtime.GOMAXPROCS(16)
+ defer runtime.GOMAXPROCS(n)
+ root, err := MkdirTemp("", "issue")
+ if err != nil {
+ t.Fatal(err)
+ }
+ mkdirTree(t, root, 1, 6)
+ hold := make(chan struct{})
+ var wg sync.WaitGroup
+ for i := 0; i < 4; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ <-hold
+ err := RemoveAll(root)
+ if err != nil {
+ t.Errorf("unexpected error: %T, %q", err, err)
+ }
+ }()
+ }
+ close(hold) // let workers race to remove root
+ wg.Wait()
+}
+
+// Test that reading from a pipe doesn't use up a thread.
+func TestPipeThreads(t *testing.T) {
+ switch runtime.GOOS {
+ case "illumos", "solaris":
+ t.Skip("skipping on Solaris and illumos; issue 19111")
+ case "windows":
+ t.Skip("skipping on Windows; issue 19098")
+ case "plan9":
+ t.Skip("skipping on Plan 9; does not support runtime poller")
+ case "js":
+ t.Skip("skipping on js; no support for os.Pipe")
+ case "wasip1":
+ t.Skip("skipping on wasip1; no support for os.Pipe")
+ }
+
+ threads := 100
+
+ // OpenBSD has a low default for max number of files.
+ if runtime.GOOS == "openbsd" {
+ threads = 50
+ }
+
+ r := make([]*File, threads)
+ w := make([]*File, threads)
+ for i := 0; i < threads; i++ {
+ rp, wp, err := Pipe()
+ if err != nil {
+ for j := 0; j < i; j++ {
+ r[j].Close()
+ w[j].Close()
+ }
+ t.Fatal(err)
+ }
+ r[i] = rp
+ w[i] = wp
+ }
+
+ defer debug.SetMaxThreads(debug.SetMaxThreads(threads / 2))
+
+ creading := make(chan bool, threads)
+ cdone := make(chan bool, threads)
+ for i := 0; i < threads; i++ {
+ go func(i int) {
+ var b [1]byte
+ creading <- true
+ if _, err := r[i].Read(b[:]); err != nil {
+ t.Error(err)
+ }
+ if err := r[i].Close(); err != nil {
+ t.Error(err)
+ }
+ cdone <- true
+ }(i)
+ }
+
+ for i := 0; i < threads; i++ {
+ <-creading
+ }
+
+ // If we are still alive, it means that the 100 goroutines did
+ // not require 100 threads.
+
+ for i := 0; i < threads; i++ {
+ if _, err := w[i].Write([]byte{0}); err != nil {
+ t.Error(err)
+ }
+ if err := w[i].Close(); err != nil {
+ t.Error(err)
+ }
+ <-cdone
+ }
+}
+
+func testDoubleCloseError(path string) func(*testing.T) {
+ return func(t *testing.T) {
+ t.Parallel()
+
+ file, err := Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := file.Close(); err != nil {
+ t.Fatalf("unexpected error from Close: %v", err)
+ }
+ if err := file.Close(); err == nil {
+ t.Error("second Close did not fail")
+ } else if pe, ok := err.(*PathError); !ok {
+ t.Errorf("second Close: got %T, want %T", err, pe)
+ } else if pe.Err != ErrClosed {
+ t.Errorf("second Close: got %q, want %q", pe.Err, ErrClosed)
+ } else {
+ t.Logf("second close returned expected error %q", err)
+ }
+ }
+}
+
+func TestDoubleCloseError(t *testing.T) {
+ t.Parallel()
+ t.Run("file", testDoubleCloseError(filepath.Join(sfdir, sfname)))
+ t.Run("dir", testDoubleCloseError(sfdir))
+}
+
+func TestUserHomeDir(t *testing.T) {
+ t.Parallel()
+
+ dir, err := UserHomeDir()
+ if dir == "" && err == nil {
+ t.Fatal("UserHomeDir returned an empty string but no error")
+ }
+ if err != nil {
+ // UserHomeDir may return a non-nil error if the environment variable
+ // for the home directory is empty or unset in the environment.
+ t.Skipf("skipping: %v", err)
+ }
+
+ fi, err := Stat(dir)
+ if err != nil {
+ if IsNotExist(err) {
+ // The user's home directory has a well-defined location, but does not
+ // exist. (Maybe nothing has written to it yet? That could happen, for
+ // example, on minimal VM images used for CI testing.)
+ t.Log(err)
+ return
+ }
+ t.Fatal(err)
+ }
+ if !fi.IsDir() {
+ t.Fatalf("dir %s is not directory; type = %v", dir, fi.Mode())
+ }
+}
+
+func TestDirSeek(t *testing.T) {
+ t.Parallel()
+
+ wd, err := Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ f, err := Open(wd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ dirnames1, err := f.Readdirnames(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ret, err := f.Seek(0, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if ret != 0 {
+ t.Fatalf("seek result not zero: %d", ret)
+ }
+
+ dirnames2, err := f.Readdirnames(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if len(dirnames1) != len(dirnames2) {
+ t.Fatalf("listings have different lengths: %d and %d\n", len(dirnames1), len(dirnames2))
+ }
+ for i, n1 := range dirnames1 {
+ n2 := dirnames2[i]
+ if n1 != n2 {
+ t.Fatalf("different name i=%d n1=%s n2=%s\n", i, n1, n2)
+ }
+ }
+}
+
+func TestReaddirSmallSeek(t *testing.T) {
+ // See issue 37161. Read only one entry from a directory,
+ // seek to the beginning, and read again. We should not see
+ // duplicate entries.
+ t.Parallel()
+
+ wd, err := Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ df, err := Open(filepath.Join(wd, "testdata", "issue37161"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ names1, err := df.Readdirnames(1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err = df.Seek(0, 0); err != nil {
+ t.Fatal(err)
+ }
+ names2, err := df.Readdirnames(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(names2) != 3 {
+ t.Fatalf("first names: %v, second names: %v", names1, names2)
+ }
+}
+
+// isDeadlineExceeded reports whether err is or wraps ErrDeadlineExceeded.
+// We also check that the error has a Timeout method that returns true.
+func isDeadlineExceeded(err error) bool {
+ if !IsTimeout(err) {
+ return false
+ }
+ if !errors.Is(err, ErrDeadlineExceeded) {
+ return false
+ }
+ return true
+}
+
+// Test that opening a file does not change its permissions. Issue 38225.
+func TestOpenFileKeepsPermissions(t *testing.T) {
+ t.Parallel()
+
+ dir := t.TempDir()
+ name := filepath.Join(dir, "x")
+ f, err := Create(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Error(err)
+ }
+ f, err = OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if fi, err := f.Stat(); err != nil {
+ t.Error(err)
+ } else if fi.Mode()&0222 == 0 {
+ t.Errorf("f.Stat.Mode after OpenFile is %v, should be writable", fi.Mode())
+ }
+ if err := f.Close(); err != nil {
+ t.Error(err)
+ }
+ if fi, err := Stat(name); err != nil {
+ t.Error(err)
+ } else if fi.Mode()&0222 == 0 {
+ t.Errorf("Stat after OpenFile is %v, should be writable", fi.Mode())
+ }
+}
+
+func TestDirFS(t *testing.T) {
+ t.Parallel()
+
+ // On Windows, we force the MFT to update by reading the actual metadata from GetFileInformationByHandle and then
+ // explicitly setting that. Otherwise it might get out of sync with FindFirstFile. See golang.org/issues/42637.
+ if runtime.GOOS == "windows" {
+ if err := filepath.WalkDir("./testdata/dirfs", func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ t.Fatal(err)
+ }
+ info, err := d.Info()
+ if err != nil {
+ t.Fatal(err)
+ }
+ stat, err := Stat(path) // This uses GetFileInformationByHandle internally.
+ if err != nil {
+ t.Fatal(err)
+ }
+ if stat.ModTime() == info.ModTime() {
+ return nil
+ }
+ if err := Chtimes(path, stat.ModTime(), stat.ModTime()); err != nil {
+ t.Log(err) // We only log, not die, in case the test directory is not writable.
+ }
+ return nil
+ }); err != nil {
+ t.Fatal(err)
+ }
+ }
+ fsys := DirFS("./testdata/dirfs")
+ if err := fstest.TestFS(fsys, "a", "b", "dir/x"); err != nil {
+ t.Fatal(err)
+ }
+
+ rdfs, ok := fsys.(fs.ReadDirFS)
+ if !ok {
+ t.Error("expected DirFS result to implement fs.ReadDirFS")
+ }
+ if _, err := rdfs.ReadDir("nonexistent"); err == nil {
+ t.Error("fs.ReadDir of nonexistent directory succeeded")
+ }
+
+ // Test that the error message does not contain a backslash,
+ // and does not contain the DirFS argument.
+ const nonesuch = "dir/nonesuch"
+ _, err := fsys.Open(nonesuch)
+ if err == nil {
+ t.Error("fs.Open of nonexistent file succeeded")
+ } else {
+ if !strings.Contains(err.Error(), nonesuch) {
+ t.Errorf("error %q does not contain %q", err, nonesuch)
+ }
+ if strings.Contains(err.(*PathError).Path, "testdata") {
+ t.Errorf("error %q contains %q", err, "testdata")
+ }
+ }
+
+ // Test that Open does not accept backslash as separator.
+ d := DirFS(".")
+ _, err = d.Open(`testdata\dirfs`)
+ if err == nil {
+ t.Fatalf(`Open testdata\dirfs succeeded`)
+ }
+
+ // Test that Open does not open Windows device files.
+ _, err = d.Open(`NUL`)
+ if err == nil {
+ t.Errorf(`Open NUL succeeded`)
+ }
+}
+
+func TestDirFSRootDir(t *testing.T) {
+ t.Parallel()
+
+ cwd, err := Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ cwd = cwd[len(filepath.VolumeName(cwd)):] // trim volume prefix (C:) on Windows
+ cwd = filepath.ToSlash(cwd) // convert \ to /
+ cwd = strings.TrimPrefix(cwd, "/") // trim leading /
+
+ // Test that Open can open a path starting at /.
+ d := DirFS("/")
+ f, err := d.Open(cwd + "/testdata/dirfs/a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+}
+
+func TestDirFSEmptyDir(t *testing.T) {
+ t.Parallel()
+
+ d := DirFS("")
+ cwd, _ := Getwd()
+ for _, path := range []string{
+ "testdata/dirfs/a", // not DirFS(".")
+ filepath.ToSlash(cwd) + "/testdata/dirfs/a", // not DirFS("/")
+ } {
+ _, err := d.Open(path)
+ if err == nil {
+ t.Fatalf(`DirFS("").Open(%q) succeeded`, path)
+ }
+ }
+}
+
+func TestDirFSPathsValid(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skipf("skipping on Windows")
+ }
+ t.Parallel()
+
+ d := t.TempDir()
+ if err := WriteFile(filepath.Join(d, "control.txt"), []byte(string("Hello, world!")), 0644); err != nil {
+ t.Fatal(err)
+ }
+ if err := WriteFile(filepath.Join(d, `e:xperi\ment.txt`), []byte(string("Hello, colon and backslash!")), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ fsys := DirFS(d)
+ err := fs.WalkDir(fsys, ".", func(path string, e fs.DirEntry, err error) error {
+ if fs.ValidPath(e.Name()) {
+ t.Logf("%q ok", e.Name())
+ } else {
+ t.Errorf("%q INVALID", e.Name())
+ }
+ return nil
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestReadFileProc(t *testing.T) {
+ t.Parallel()
+
+ // Linux files in /proc report 0 size,
+ // but then if ReadFile reads just a single byte at offset 0,
+ // the read at offset 1 returns EOF instead of more data.
+ // ReadFile has a minimum read size of 512 to work around this,
+ // but test explicitly that it's working.
+ name := "/proc/sys/fs/pipe-max-size"
+ if _, err := Stat(name); err != nil {
+ t.Skip(err)
+ }
+ data, err := ReadFile(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(data) == 0 || data[len(data)-1] != '\n' {
+ t.Fatalf("read %s: not newline-terminated: %q", name, data)
+ }
+}
+
+func TestDirFSReadFileProc(t *testing.T) {
+ t.Parallel()
+
+ fsys := DirFS("/")
+ name := "proc/sys/fs/pipe-max-size"
+ if _, err := fs.Stat(fsys, name); err != nil {
+ t.Skip()
+ }
+ data, err := fs.ReadFile(fsys, name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(data) == 0 || data[len(data)-1] != '\n' {
+ t.Fatalf("read %s: not newline-terminated: %q", name, data)
+ }
+}
+
+func TestWriteStringAlloc(t *testing.T) {
+ if runtime.GOOS == "js" {
+ t.Skip("js allocates a lot during File.WriteString")
+ }
+ d := t.TempDir()
+ f, err := Create(filepath.Join(d, "whiteboard.txt"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ allocs := testing.AllocsPerRun(100, func() {
+ f.WriteString("I will not allocate when passed a string longer than 32 bytes.\n")
+ })
+ if allocs != 0 {
+ t.Errorf("expected 0 allocs for File.WriteString, got %v", allocs)
+ }
+}
+
+// Test that it's OK to have parallel I/O and Close on a pipe.
+func TestPipeIOCloseRace(t *testing.T) {
+ // Skip on wasm, which doesn't have pipes.
+ if runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
+ t.Skipf("skipping on %s: no pipes", runtime.GOOS)
+ }
+ t.Parallel()
+
+ r, w, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(3)
+
+ go func() {
+ defer wg.Done()
+ for {
+ n, err := w.Write([]byte("hi"))
+ if err != nil {
+ // We look at error strings as the
+ // expected errors are OS-specific.
+ switch {
+ case errors.Is(err, ErrClosed),
+ strings.Contains(err.Error(), "broken pipe"),
+ strings.Contains(err.Error(), "pipe is being closed"),
+ strings.Contains(err.Error(), "hungup channel"):
+ // Ignore an expected error.
+ default:
+ // Unexpected error.
+ t.Error(err)
+ }
+ return
+ }
+ if n != 2 {
+ t.Errorf("wrote %d bytes, expected 2", n)
+ return
+ }
+ }
+ }()
+
+ go func() {
+ defer wg.Done()
+ for {
+ var buf [2]byte
+ n, err := r.Read(buf[:])
+ if err != nil {
+ if err != io.EOF && !errors.Is(err, ErrClosed) {
+ t.Error(err)
+ }
+ return
+ }
+ if n != 2 {
+ t.Errorf("read %d bytes, want 2", n)
+ }
+ }
+ }()
+
+ go func() {
+ defer wg.Done()
+
+ // Let the other goroutines start. This is just to get
+ // a better test, the test will still pass if they
+ // don't start.
+ time.Sleep(time.Millisecond)
+
+ if err := r.Close(); err != nil {
+ t.Error(err)
+ }
+ if err := w.Close(); err != nil {
+ t.Error(err)
+ }
+ }()
+
+ wg.Wait()
+}
+
+// Test that it's OK to call Close concurrently on a pipe.
+func TestPipeCloseRace(t *testing.T) {
+ // Skip on wasm, which doesn't have pipes.
+ if runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
+ t.Skipf("skipping on %s: no pipes", runtime.GOOS)
+ }
+ t.Parallel()
+
+ r, w, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var wg sync.WaitGroup
+ c := make(chan error, 4)
+ f := func() {
+ defer wg.Done()
+ c <- r.Close()
+ c <- w.Close()
+ }
+ wg.Add(2)
+ go f()
+ go f()
+ nils, errs := 0, 0
+ for i := 0; i < 4; i++ {
+ err := <-c
+ if err == nil {
+ nils++
+ } else {
+ errs++
+ }
+ }
+ if nils != 2 || errs != 2 {
+ t.Errorf("got nils %d errs %d, want 2 2", nils, errs)
+ }
+}
diff --git a/src/os/os_unix_test.go b/src/os/os_unix_test.go
new file mode 100644
index 0000000..98e7afd
--- /dev/null
+++ b/src/os/os_unix_test.go
@@ -0,0 +1,348 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1
+
+package os_test
+
+import (
+ "internal/testenv"
+ "io"
+ . "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func init() {
+ isReadonlyError = func(err error) bool { return err == syscall.EROFS }
+}
+
+// For TestRawConnReadWrite.
+type syscallDescriptor = int
+
+func checkUidGid(t *testing.T, path string, uid, gid int) {
+ dir, err := Lstat(path)
+ if err != nil {
+ t.Fatalf("Lstat %q (looking for uid/gid %d/%d): %s", path, uid, gid, err)
+ }
+ sys := dir.Sys().(*syscall.Stat_t)
+ if int(sys.Uid) != uid {
+ t.Errorf("Lstat %q: uid %d want %d", path, sys.Uid, uid)
+ }
+ if int(sys.Gid) != gid {
+ t.Errorf("Lstat %q: gid %d want %d", path, sys.Gid, gid)
+ }
+}
+
+func TestChown(t *testing.T) {
+ if runtime.GOOS == "wasip1" {
+ t.Skip("file ownership not supported on " + runtime.GOOS)
+ }
+ t.Parallel()
+
+ // Use TempDir() to make sure we're on a local file system,
+ // so that the group ids returned by Getgroups will be allowed
+ // on the file. On NFS, the Getgroups groups are
+ // basically useless.
+ f := newFile("TestChown", t)
+ defer Remove(f.Name())
+ defer f.Close()
+ dir, err := f.Stat()
+ if err != nil {
+ t.Fatalf("stat %s: %s", f.Name(), err)
+ }
+
+ // Can't change uid unless root, but can try
+ // changing the group id. First try our current group.
+ gid := Getgid()
+ t.Log("gid:", gid)
+ if err = Chown(f.Name(), -1, gid); err != nil {
+ t.Fatalf("chown %s -1 %d: %s", f.Name(), gid, err)
+ }
+ sys := dir.Sys().(*syscall.Stat_t)
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+
+ // Then try all the auxiliary groups.
+ groups, err := Getgroups()
+ if err != nil {
+ t.Fatalf("getgroups: %s", err)
+ }
+ t.Log("groups: ", groups)
+ for _, g := range groups {
+ if err = Chown(f.Name(), -1, g); err != nil {
+ t.Fatalf("chown %s -1 %d: %s", f.Name(), g, err)
+ }
+ checkUidGid(t, f.Name(), int(sys.Uid), g)
+
+ // change back to gid to test fd.Chown
+ if err = f.Chown(-1, gid); err != nil {
+ t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err)
+ }
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+ }
+}
+
+func TestFileChown(t *testing.T) {
+ if runtime.GOOS == "wasip1" {
+ t.Skip("file ownership not supported on " + runtime.GOOS)
+ }
+ t.Parallel()
+
+ // Use TempDir() to make sure we're on a local file system,
+ // so that the group ids returned by Getgroups will be allowed
+ // on the file. On NFS, the Getgroups groups are
+ // basically useless.
+ f := newFile("TestFileChown", t)
+ defer Remove(f.Name())
+ defer f.Close()
+ dir, err := f.Stat()
+ if err != nil {
+ t.Fatalf("stat %s: %s", f.Name(), err)
+ }
+
+ // Can't change uid unless root, but can try
+ // changing the group id. First try our current group.
+ gid := Getgid()
+ t.Log("gid:", gid)
+ if err = f.Chown(-1, gid); err != nil {
+ t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err)
+ }
+ sys := dir.Sys().(*syscall.Stat_t)
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+
+ // Then try all the auxiliary groups.
+ groups, err := Getgroups()
+ if err != nil {
+ t.Fatalf("getgroups: %s", err)
+ }
+ t.Log("groups: ", groups)
+ for _, g := range groups {
+ if err = f.Chown(-1, g); err != nil {
+ t.Fatalf("fchown %s -1 %d: %s", f.Name(), g, err)
+ }
+ checkUidGid(t, f.Name(), int(sys.Uid), g)
+
+ // change back to gid to test fd.Chown
+ if err = f.Chown(-1, gid); err != nil {
+ t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err)
+ }
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+ }
+}
+
+func TestLchown(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+ t.Parallel()
+
+ // Use TempDir() to make sure we're on a local file system,
+ // so that the group ids returned by Getgroups will be allowed
+ // on the file. On NFS, the Getgroups groups are
+ // basically useless.
+ f := newFile("TestLchown", t)
+ defer Remove(f.Name())
+ defer f.Close()
+ dir, err := f.Stat()
+ if err != nil {
+ t.Fatalf("stat %s: %s", f.Name(), err)
+ }
+
+ linkname := f.Name() + "2"
+ if err := Symlink(f.Name(), linkname); err != nil {
+ if runtime.GOOS == "android" && IsPermission(err) {
+ t.Skip("skipping test on Android; permission error creating symlink")
+ }
+ t.Fatalf("link %s -> %s: %v", f.Name(), linkname, err)
+ }
+ defer Remove(linkname)
+
+ // Can't change uid unless root, but can try
+ // changing the group id. First try our current group.
+ gid := Getgid()
+ t.Log("gid:", gid)
+ if err = Lchown(linkname, -1, gid); err != nil {
+ if err, ok := err.(*PathError); ok && err.Err == syscall.ENOSYS {
+ t.Skip("lchown is unavailable")
+ }
+ t.Fatalf("lchown %s -1 %d: %s", linkname, gid, err)
+ }
+ sys := dir.Sys().(*syscall.Stat_t)
+ checkUidGid(t, linkname, int(sys.Uid), gid)
+
+ // Then try all the auxiliary groups.
+ groups, err := Getgroups()
+ if err != nil {
+ t.Fatalf("getgroups: %s", err)
+ }
+ t.Log("groups: ", groups)
+ for _, g := range groups {
+ if err = Lchown(linkname, -1, g); err != nil {
+ t.Fatalf("lchown %s -1 %d: %s", linkname, g, err)
+ }
+ checkUidGid(t, linkname, int(sys.Uid), g)
+
+ // Check that link target's gid is unchanged.
+ checkUidGid(t, f.Name(), int(sys.Uid), int(sys.Gid))
+ }
+}
+
+// Issue 16919: Readdir must return a non-empty slice or an error.
+func TestReaddirRemoveRace(t *testing.T) {
+ oldStat := *LstatP
+ defer func() { *LstatP = oldStat }()
+ *LstatP = func(name string) (FileInfo, error) {
+ if strings.HasSuffix(name, "some-file") {
+ // Act like it's been deleted.
+ return nil, ErrNotExist
+ }
+ return oldStat(name)
+ }
+ dir := newDir("TestReaddirRemoveRace", t)
+ defer RemoveAll(dir)
+ if err := WriteFile(filepath.Join(dir, "some-file"), []byte("hello"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ d, err := Open(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer d.Close()
+ fis, err := d.Readdir(2) // notably, greater than zero
+ if len(fis) == 0 && err == nil {
+ // This is what used to happen (Issue 16919)
+ t.Fatal("Readdir = empty slice & err == nil")
+ }
+ if len(fis) != 0 || err != io.EOF {
+ t.Errorf("Readdir = %d entries: %v; want 0, io.EOF", len(fis), err)
+ for i, fi := range fis {
+ t.Errorf(" entry[%d]: %q, %v", i, fi.Name(), fi.Mode())
+ }
+ t.FailNow()
+ }
+}
+
+// Issue 23120: respect umask when doing Mkdir with the sticky bit
+func TestMkdirStickyUmask(t *testing.T) {
+ if runtime.GOOS == "wasip1" {
+ t.Skip("file permissions not supported on " + runtime.GOOS)
+ }
+ t.Parallel()
+
+ const umask = 0077
+ dir := newDir("TestMkdirStickyUmask", t)
+ defer RemoveAll(dir)
+ oldUmask := syscall.Umask(umask)
+ defer syscall.Umask(oldUmask)
+ p := filepath.Join(dir, "dir1")
+ if err := Mkdir(p, ModeSticky|0755); err != nil {
+ t.Fatal(err)
+ }
+ fi, err := Stat(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if mode := fi.Mode(); (mode&umask) != 0 || (mode&^ModePerm) != (ModeDir|ModeSticky) {
+ t.Errorf("unexpected mode %s", mode)
+ }
+}
+
+// See also issues: 22939, 24331
+func newFileTest(t *testing.T, blocking bool) {
+ if runtime.GOOS == "js" || runtime.GOOS == "wasip1" {
+ t.Skipf("syscall.Pipe is not available on %s.", runtime.GOOS)
+ }
+
+ p := make([]int, 2)
+ if err := syscall.Pipe(p); err != nil {
+ t.Fatalf("pipe: %v", err)
+ }
+ defer syscall.Close(p[1])
+
+ // Set the read-side to non-blocking.
+ if !blocking {
+ if err := syscall.SetNonblock(p[0], true); err != nil {
+ syscall.Close(p[0])
+ t.Fatalf("SetNonblock: %v", err)
+ }
+ }
+ // Convert it to a file.
+ file := NewFile(uintptr(p[0]), "notapipe")
+ if file == nil {
+ syscall.Close(p[0])
+ t.Fatalf("failed to convert fd to file!")
+ }
+ defer file.Close()
+
+ timeToWrite := 100 * time.Millisecond
+ timeToDeadline := 1 * time.Millisecond
+ if !blocking {
+ // Use a longer time to avoid flakes.
+ // We won't be waiting this long anyhow.
+ timeToWrite = 1 * time.Second
+ }
+
+ // Try to read with deadline (but don't block forever).
+ b := make([]byte, 1)
+ timer := time.AfterFunc(timeToWrite, func() { syscall.Write(p[1], []byte("a")) })
+ defer timer.Stop()
+ file.SetReadDeadline(time.Now().Add(timeToDeadline))
+ _, err := file.Read(b)
+ if !blocking {
+ // We want it to fail with a timeout.
+ if !isDeadlineExceeded(err) {
+ t.Fatalf("No timeout reading from file: %v", err)
+ }
+ } else {
+ // We want it to succeed after 100ms
+ if err != nil {
+ t.Fatalf("Error reading from file: %v", err)
+ }
+ }
+}
+
+func TestNewFileBlock(t *testing.T) {
+ t.Parallel()
+ newFileTest(t, true)
+}
+
+func TestNewFileNonBlock(t *testing.T) {
+ t.Parallel()
+ newFileTest(t, false)
+}
+
+func TestNewFileInvalid(t *testing.T) {
+ t.Parallel()
+ const negOne = ^uintptr(0)
+ if f := NewFile(negOne, "invalid"); f != nil {
+ t.Errorf("NewFile(-1) got %v want nil", f)
+ }
+}
+
+func TestSplitPath(t *testing.T) {
+ t.Parallel()
+ for _, tt := range []struct{ path, wantDir, wantBase string }{
+ {"a", ".", "a"},
+ {"a/", ".", "a"},
+ {"a//", ".", "a"},
+ {"a/b", "a", "b"},
+ {"a/b/", "a", "b"},
+ {"a/b/c", "a/b", "c"},
+ {"/a", "/", "a"},
+ {"/a/", "/", "a"},
+ {"/a/b", "/a", "b"},
+ {"/a/b/", "/a", "b"},
+ {"/a/b/c", "/a/b", "c"},
+ {"//a", "/", "a"},
+ {"//a/", "/", "a"},
+ {"///a", "/", "a"},
+ {"///a/", "/", "a"},
+ } {
+ if dir, base := SplitPath(tt.path); dir != tt.wantDir || base != tt.wantBase {
+ t.Errorf("splitPath(%q) = %q, %q, want %q, %q", tt.path, dir, base, tt.wantDir, tt.wantBase)
+ }
+ }
+}
diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go
new file mode 100644
index 0000000..d6aab18
--- /dev/null
+++ b/src/os/os_windows_test.go
@@ -0,0 +1,1467 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "errors"
+ "fmt"
+ "internal/poll"
+ "internal/syscall/windows"
+ "internal/syscall/windows/registry"
+ "internal/testenv"
+ "io"
+ "io/fs"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "slices"
+ "sort"
+ "strings"
+ "syscall"
+ "testing"
+ "unicode/utf16"
+ "unsafe"
+)
+
+// For TestRawConnReadWrite.
+type syscallDescriptor = syscall.Handle
+
+// chdir changes the current working directory to the named directory,
+// and then restore the original working directory at the end of the test.
+func chdir(t *testing.T, dir string) {
+ olddir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("chdir: %v", err)
+ }
+ if err := os.Chdir(dir); err != nil {
+ t.Fatalf("chdir %s: %v", dir, err)
+ }
+
+ t.Cleanup(func() {
+ if err := os.Chdir(olddir); err != nil {
+ t.Errorf("chdir to original working directory %s: %v", olddir, err)
+ os.Exit(1)
+ }
+ })
+}
+
+func TestSameWindowsFile(t *testing.T) {
+ temp := t.TempDir()
+ chdir(t, temp)
+
+ f, err := os.Create("a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+
+ ia1, err := os.Stat("a")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ path, err := filepath.Abs("a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ia2, err := os.Stat(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !os.SameFile(ia1, ia2) {
+ t.Errorf("files should be same")
+ }
+
+ p := filepath.VolumeName(path) + filepath.Base(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ia3, err := os.Stat(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !os.SameFile(ia1, ia3) {
+ t.Errorf("files should be same")
+ }
+}
+
+type dirLinkTest struct {
+ name string
+ mklink func(link, target string) error
+ issueNo int // correspondent issue number (for broken tests)
+}
+
+func testDirLinks(t *testing.T, tests []dirLinkTest) {
+ tmpdir := t.TempDir()
+ chdir(t, tmpdir)
+
+ dir := filepath.Join(tmpdir, "dir")
+ err := os.Mkdir(dir, 0777)
+ if err != nil {
+ t.Fatal(err)
+ }
+ fi, err := os.Stat(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = os.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, test := range tests {
+ link := filepath.Join(tmpdir, test.name+"_link")
+ err := test.mklink(link, dir)
+ if err != nil {
+ t.Errorf("creating link for %q test failed: %v", test.name, err)
+ continue
+ }
+
+ data, err := os.ReadFile(filepath.Join(link, "abc"))
+ if err != nil {
+ t.Errorf("failed to read abc file: %v", err)
+ continue
+ }
+ if string(data) != "abc" {
+ t.Errorf(`abc file is expected to have "abc" in it, but has %v`, data)
+ continue
+ }
+
+ if test.issueNo > 0 {
+ t.Logf("skipping broken %q test: see issue %d", test.name, test.issueNo)
+ continue
+ }
+
+ fi1, err := os.Stat(link)
+ if err != nil {
+ t.Errorf("failed to stat link %v: %v", link, err)
+ continue
+ }
+ if !fi1.IsDir() {
+ t.Errorf("%q should be a directory", link)
+ continue
+ }
+ if fi1.Name() != filepath.Base(link) {
+ t.Errorf("Stat(%q).Name() = %q, want %q", link, fi1.Name(), filepath.Base(link))
+ continue
+ }
+ if !os.SameFile(fi, fi1) {
+ t.Errorf("%q should point to %q", link, dir)
+ continue
+ }
+
+ fi2, err := os.Lstat(link)
+ if err != nil {
+ t.Errorf("failed to lstat link %v: %v", link, err)
+ continue
+ }
+ if m := fi2.Mode(); m&fs.ModeSymlink == 0 {
+ t.Errorf("%q should be a link, but is not (mode=0x%x)", link, uint32(m))
+ continue
+ }
+ if m := fi2.Mode(); m&fs.ModeDir != 0 {
+ t.Errorf("%q should be a link, not a directory (mode=0x%x)", link, uint32(m))
+ continue
+ }
+ }
+}
+
+// reparseData is used to build reparse buffer data required for tests.
+type reparseData struct {
+ substituteName namePosition
+ printName namePosition
+ pathBuf []uint16
+}
+
+type namePosition struct {
+ offset uint16
+ length uint16
+}
+
+func (rd *reparseData) addUTF16s(s []uint16) (offset uint16) {
+ off := len(rd.pathBuf) * 2
+ rd.pathBuf = append(rd.pathBuf, s...)
+ return uint16(off)
+}
+
+func (rd *reparseData) addString(s string) (offset, length uint16) {
+ p := syscall.StringToUTF16(s)
+ return rd.addUTF16s(p), uint16(len(p)-1) * 2 // do not include terminating NUL in the length (as per PrintNameLength and SubstituteNameLength documentation)
+}
+
+func (rd *reparseData) addSubstituteName(name string) {
+ rd.substituteName.offset, rd.substituteName.length = rd.addString(name)
+}
+
+func (rd *reparseData) addPrintName(name string) {
+ rd.printName.offset, rd.printName.length = rd.addString(name)
+}
+
+func (rd *reparseData) addStringNoNUL(s string) (offset, length uint16) {
+ p := syscall.StringToUTF16(s)
+ p = p[:len(p)-1]
+ return rd.addUTF16s(p), uint16(len(p)) * 2
+}
+
+func (rd *reparseData) addSubstituteNameNoNUL(name string) {
+ rd.substituteName.offset, rd.substituteName.length = rd.addStringNoNUL(name)
+}
+
+func (rd *reparseData) addPrintNameNoNUL(name string) {
+ rd.printName.offset, rd.printName.length = rd.addStringNoNUL(name)
+}
+
+// pathBuffeLen returns length of rd pathBuf in bytes.
+func (rd *reparseData) pathBuffeLen() uint16 {
+ return uint16(len(rd.pathBuf)) * 2
+}
+
+// Windows REPARSE_DATA_BUFFER contains union member, and cannot be
+// translated into Go directly. _REPARSE_DATA_BUFFER type is to help
+// construct alternative versions of Windows REPARSE_DATA_BUFFER with
+// union part of SymbolicLinkReparseBuffer or MountPointReparseBuffer type.
+type _REPARSE_DATA_BUFFER struct {
+ header windows.REPARSE_DATA_BUFFER_HEADER
+ detail [syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]byte
+}
+
+func createDirLink(link string, rdb *_REPARSE_DATA_BUFFER) error {
+ err := os.Mkdir(link, 0777)
+ if err != nil {
+ return err
+ }
+
+ linkp := syscall.StringToUTF16(link)
+ fd, err := syscall.CreateFile(&linkp[0], syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING,
+ syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
+ if err != nil {
+ return err
+ }
+ defer syscall.CloseHandle(fd)
+
+ buflen := uint32(rdb.header.ReparseDataLength) + uint32(unsafe.Sizeof(rdb.header))
+ var bytesReturned uint32
+ return syscall.DeviceIoControl(fd, windows.FSCTL_SET_REPARSE_POINT,
+ (*byte)(unsafe.Pointer(&rdb.header)), buflen, nil, 0, &bytesReturned, nil)
+}
+
+func createMountPoint(link string, target *reparseData) error {
+ var buf *windows.MountPointReparseBuffer
+ buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
+ byteblob := make([]byte, buflen)
+ buf = (*windows.MountPointReparseBuffer)(unsafe.Pointer(&byteblob[0]))
+ buf.SubstituteNameOffset = target.substituteName.offset
+ buf.SubstituteNameLength = target.substituteName.length
+ buf.PrintNameOffset = target.printName.offset
+ buf.PrintNameLength = target.printName.length
+ pbuflen := len(target.pathBuf)
+ copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:pbuflen:pbuflen], target.pathBuf)
+
+ var rdb _REPARSE_DATA_BUFFER
+ rdb.header.ReparseTag = windows.IO_REPARSE_TAG_MOUNT_POINT
+ rdb.header.ReparseDataLength = buflen
+ copy(rdb.detail[:], byteblob)
+
+ return createDirLink(link, &rdb)
+}
+
+func TestDirectoryJunction(t *testing.T) {
+ var tests = []dirLinkTest{
+ {
+ // Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
+ name: "standard",
+ mklink: func(link, target string) error {
+ var t reparseData
+ t.addSubstituteName(`\??\` + target)
+ t.addPrintName(target)
+ return createMountPoint(link, &t)
+ },
+ },
+ {
+ // Do as junction utility https://technet.microsoft.com/en-au/sysinternals/bb896768.aspx does - set PrintNameLength to 0.
+ name: "have_blank_print_name",
+ mklink: func(link, target string) error {
+ var t reparseData
+ t.addSubstituteName(`\??\` + target)
+ t.addPrintName("")
+ return createMountPoint(link, &t)
+ },
+ },
+ }
+ output, _ := testenv.Command(t, "cmd", "/c", "mklink", "/?").Output()
+ mklinkSupportsJunctionLinks := strings.Contains(string(output), " /J ")
+ if mklinkSupportsJunctionLinks {
+ tests = append(tests,
+ dirLinkTest{
+ name: "use_mklink_cmd",
+ mklink: func(link, target string) error {
+ output, err := testenv.Command(t, "cmd", "/c", "mklink", "/J", link, target).CombinedOutput()
+ if err != nil {
+ t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
+ }
+ return nil
+ },
+ },
+ )
+ } else {
+ t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory junctions`)
+ }
+ testDirLinks(t, tests)
+}
+
+func enableCurrentThreadPrivilege(privilegeName string) error {
+ ct, err := windows.GetCurrentThread()
+ if err != nil {
+ return err
+ }
+ var t syscall.Token
+ err = windows.OpenThreadToken(ct, syscall.TOKEN_QUERY|windows.TOKEN_ADJUST_PRIVILEGES, false, &t)
+ if err != nil {
+ return err
+ }
+ defer syscall.CloseHandle(syscall.Handle(t))
+
+ var tp windows.TOKEN_PRIVILEGES
+
+ privStr, err := syscall.UTF16PtrFromString(privilegeName)
+ if err != nil {
+ return err
+ }
+ err = windows.LookupPrivilegeValue(nil, privStr, &tp.Privileges[0].Luid)
+ if err != nil {
+ return err
+ }
+ tp.PrivilegeCount = 1
+ tp.Privileges[0].Attributes = windows.SE_PRIVILEGE_ENABLED
+ return windows.AdjustTokenPrivileges(t, false, &tp, 0, nil, nil)
+}
+
+func createSymbolicLink(link string, target *reparseData, isrelative bool) error {
+ var buf *windows.SymbolicLinkReparseBuffer
+ buflen := uint16(unsafe.Offsetof(buf.PathBuffer)) + target.pathBuffeLen() // see ReparseDataLength documentation
+ byteblob := make([]byte, buflen)
+ buf = (*windows.SymbolicLinkReparseBuffer)(unsafe.Pointer(&byteblob[0]))
+ buf.SubstituteNameOffset = target.substituteName.offset
+ buf.SubstituteNameLength = target.substituteName.length
+ buf.PrintNameOffset = target.printName.offset
+ buf.PrintNameLength = target.printName.length
+ if isrelative {
+ buf.Flags = windows.SYMLINK_FLAG_RELATIVE
+ }
+ pbuflen := len(target.pathBuf)
+ copy((*[2048]uint16)(unsafe.Pointer(&buf.PathBuffer[0]))[:pbuflen:pbuflen], target.pathBuf)
+
+ var rdb _REPARSE_DATA_BUFFER
+ rdb.header.ReparseTag = syscall.IO_REPARSE_TAG_SYMLINK
+ rdb.header.ReparseDataLength = buflen
+ copy(rdb.detail[:], byteblob)
+
+ return createDirLink(link, &rdb)
+}
+
+func TestDirectorySymbolicLink(t *testing.T) {
+ var tests []dirLinkTest
+ output, _ := testenv.Command(t, "cmd", "/c", "mklink", "/?").Output()
+ mklinkSupportsDirectorySymbolicLinks := strings.Contains(string(output), " /D ")
+ if mklinkSupportsDirectorySymbolicLinks {
+ tests = append(tests,
+ dirLinkTest{
+ name: "use_mklink_cmd",
+ mklink: func(link, target string) error {
+ output, err := testenv.Command(t, "cmd", "/c", "mklink", "/D", link, target).CombinedOutput()
+ if err != nil {
+ t.Errorf("failed to run mklink %v %v: %v %q", link, target, err, output)
+ }
+ return nil
+ },
+ },
+ )
+ } else {
+ t.Log(`skipping "use_mklink_cmd" test, mklink does not supports directory symbolic links`)
+ }
+
+ // The rest of these test requires SeCreateSymbolicLinkPrivilege to be held.
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ err := windows.ImpersonateSelf(windows.SecurityImpersonation)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer windows.RevertToSelf()
+
+ err = enableCurrentThreadPrivilege("SeCreateSymbolicLinkPrivilege")
+ if err != nil {
+ t.Skipf(`skipping some tests, could not enable "SeCreateSymbolicLinkPrivilege": %v`, err)
+ }
+ tests = append(tests,
+ dirLinkTest{
+ name: "use_os_pkg",
+ mklink: func(link, target string) error {
+ return os.Symlink(target, link)
+ },
+ },
+ dirLinkTest{
+ // Create link similar to what mklink does, by inserting \??\ at the front of absolute target.
+ name: "standard",
+ mklink: func(link, target string) error {
+ var t reparseData
+ t.addPrintName(target)
+ t.addSubstituteName(`\??\` + target)
+ return createSymbolicLink(link, &t, false)
+ },
+ },
+ dirLinkTest{
+ name: "relative",
+ mklink: func(link, target string) error {
+ var t reparseData
+ t.addSubstituteNameNoNUL(filepath.Base(target))
+ t.addPrintNameNoNUL(filepath.Base(target))
+ return createSymbolicLink(link, &t, true)
+ },
+ },
+ )
+ testDirLinks(t, tests)
+}
+
+func TestNetworkSymbolicLink(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+
+ const _NERR_ServerNotStarted = syscall.Errno(2114)
+
+ dir := t.TempDir()
+ chdir(t, dir)
+
+ shareName := "GoSymbolicLinkTestShare" // hope no conflictions
+ sharePath := filepath.Join(dir, shareName)
+ testDir := "TestDir"
+
+ err := os.MkdirAll(filepath.Join(sharePath, testDir), 0777)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ wShareName, err := syscall.UTF16PtrFromString(shareName)
+ if err != nil {
+ t.Fatal(err)
+ }
+ wSharePath, err := syscall.UTF16PtrFromString(sharePath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ p := windows.SHARE_INFO_2{
+ Netname: wShareName,
+ Type: windows.STYPE_DISKTREE,
+ Remark: nil,
+ Permissions: 0,
+ MaxUses: 1,
+ CurrentUses: 0,
+ Path: wSharePath,
+ Passwd: nil,
+ }
+
+ err = windows.NetShareAdd(nil, 2, (*byte)(unsafe.Pointer(&p)), nil)
+ if err != nil {
+ if err == syscall.ERROR_ACCESS_DENIED {
+ t.Skip("you don't have enough privileges to add network share")
+ }
+ if err == _NERR_ServerNotStarted {
+ t.Skip(_NERR_ServerNotStarted.Error())
+ }
+ t.Fatal(err)
+ }
+ defer func() {
+ err := windows.NetShareDel(nil, wShareName, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }()
+
+ UNCPath := `\\localhost\` + shareName + `\`
+
+ fi1, err := os.Stat(sharePath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ fi2, err := os.Stat(UNCPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !os.SameFile(fi1, fi2) {
+ t.Fatalf("%q and %q should be the same directory, but not", sharePath, UNCPath)
+ }
+
+ target := filepath.Join(UNCPath, testDir)
+ link := "link"
+
+ err = os.Symlink(target, link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(link)
+
+ got, err := os.Readlink(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got != target {
+ t.Errorf(`os.Readlink("%s"): got %v, want %v`, link, got, target)
+ }
+
+ got, err = filepath.EvalSymlinks(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got != target {
+ t.Errorf(`filepath.EvalSymlinks("%s"): got %v, want %v`, link, got, target)
+ }
+}
+
+func TestStartProcessAttr(t *testing.T) {
+ t.Parallel()
+
+ p, err := os.StartProcess(os.Getenv("COMSPEC"), []string{"/c", "cd"}, new(os.ProcAttr))
+ if err != nil {
+ return
+ }
+ defer p.Wait()
+ t.Fatalf("StartProcess expected to fail, but succeeded.")
+}
+
+func TestShareNotExistError(t *testing.T) {
+ if testing.Short() {
+ t.Skip("slow test that uses network; skipping")
+ }
+ t.Parallel()
+
+ _, err := os.Stat(`\\no_such_server\no_such_share\no_such_file`)
+ if err == nil {
+ t.Fatal("stat succeeded, but expected to fail")
+ }
+ if !os.IsNotExist(err) {
+ t.Fatalf("os.Stat failed with %q, but os.IsNotExist(err) is false", err)
+ }
+}
+
+func TestBadNetPathError(t *testing.T) {
+ const ERROR_BAD_NETPATH = syscall.Errno(53)
+ if !os.IsNotExist(ERROR_BAD_NETPATH) {
+ t.Fatal("os.IsNotExist(syscall.Errno(53)) is false, but want true")
+ }
+}
+
+func TestStatDir(t *testing.T) {
+ defer chtmpdir(t)()
+
+ f, err := os.Open(".")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ fi, err := f.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = os.Chdir("..")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ fi2, err := f.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !os.SameFile(fi, fi2) {
+ t.Fatal("race condition occurred")
+ }
+}
+
+func TestOpenVolumeName(t *testing.T) {
+ tmpdir := t.TempDir()
+ chdir(t, tmpdir)
+
+ want := []string{"file1", "file2", "file3", "gopher.txt"}
+ sort.Strings(want)
+ for _, name := range want {
+ err := os.WriteFile(filepath.Join(tmpdir, name), nil, 0777)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ f, err := os.Open(filepath.VolumeName(tmpdir))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ have, err := f.Readdirnames(-1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ sort.Strings(have)
+
+ if strings.Join(want, "/") != strings.Join(have, "/") {
+ t.Fatalf("unexpected file list %q, want %q", have, want)
+ }
+}
+
+func TestDeleteReadOnly(t *testing.T) {
+ t.Parallel()
+
+ tmpdir := t.TempDir()
+ p := filepath.Join(tmpdir, "a")
+ // This sets FILE_ATTRIBUTE_READONLY.
+ f, err := os.OpenFile(p, os.O_CREATE, 0400)
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+
+ if err = os.Chmod(p, 0400); err != nil {
+ t.Fatal(err)
+ }
+ if err = os.Remove(p); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestReadStdin(t *testing.T) {
+ old := poll.ReadConsole
+ defer func() {
+ poll.ReadConsole = old
+ }()
+
+ p, err := syscall.GetCurrentProcess()
+ if err != nil {
+ t.Fatalf("Unable to get handle to current process: %v", err)
+ }
+ var stdinDuplicate syscall.Handle
+ err = syscall.DuplicateHandle(p, syscall.Handle(syscall.Stdin), p, &stdinDuplicate, 0, false, syscall.DUPLICATE_SAME_ACCESS)
+ if err != nil {
+ t.Fatalf("Unable to duplicate stdin: %v", err)
+ }
+ testConsole := os.NewConsoleFile(stdinDuplicate, "test")
+
+ var tests = []string{
+ "abc",
+ "äöü",
+ "\u3042",
+ "“hi”™",
+ "hello\x1aworld",
+ "\U0001F648\U0001F649\U0001F64A",
+ }
+
+ for _, consoleSize := range []int{1, 2, 3, 10, 16, 100, 1000} {
+ for _, readSize := range []int{1, 2, 3, 4, 5, 8, 10, 16, 20, 50, 100} {
+ for _, s := range tests {
+ t.Run(fmt.Sprintf("c%d/r%d/%s", consoleSize, readSize, s), func(t *testing.T) {
+ s16 := utf16.Encode([]rune(s))
+ poll.ReadConsole = func(h syscall.Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) error {
+ if inputControl != nil {
+ t.Fatalf("inputControl not nil")
+ }
+ n := int(toread)
+ if n > consoleSize {
+ n = consoleSize
+ }
+ n = copy((*[10000]uint16)(unsafe.Pointer(buf))[:n:n], s16)
+ s16 = s16[n:]
+ *read = uint32(n)
+ t.Logf("read %d -> %d", toread, *read)
+ return nil
+ }
+
+ var all []string
+ var buf []byte
+ chunk := make([]byte, readSize)
+ for {
+ n, err := testConsole.Read(chunk)
+ buf = append(buf, chunk[:n]...)
+ if err == io.EOF {
+ all = append(all, string(buf))
+ if len(all) >= 5 {
+ break
+ }
+ buf = buf[:0]
+ } else if err != nil {
+ t.Fatalf("reading %q: error: %v", s, err)
+ }
+ if len(buf) >= 2000 {
+ t.Fatalf("reading %q: stuck in loop: %q", s, buf)
+ }
+ }
+
+ want := strings.Split(s, "\x1a")
+ for len(want) < 5 {
+ want = append(want, "")
+ }
+ if !reflect.DeepEqual(all, want) {
+ t.Errorf("reading %q:\nhave %x\nwant %x", s, all, want)
+ }
+ })
+ }
+ }
+ }
+}
+
+func TestStatPagefile(t *testing.T) {
+ t.Parallel()
+
+ const path = `c:\pagefile.sys`
+ fi, err := os.Stat(path)
+ if err == nil {
+ if fi.Name() == "" {
+ t.Fatalf("Stat(%q).Name() is empty", path)
+ }
+ t.Logf("Stat(%q).Size() = %v", path, fi.Size())
+ return
+ }
+ if os.IsNotExist(err) {
+ t.Skip(`skipping because c:\pagefile.sys is not found`)
+ }
+ t.Fatal(err)
+}
+
+// syscallCommandLineToArgv calls syscall.CommandLineToArgv
+// and converts returned result into []string.
+func syscallCommandLineToArgv(cmd string) ([]string, error) {
+ var argc int32
+ argv, err := syscall.CommandLineToArgv(&syscall.StringToUTF16(cmd)[0], &argc)
+ if err != nil {
+ return nil, err
+ }
+ defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv))))
+
+ var args []string
+ for _, v := range (*argv)[:argc] {
+ args = append(args, syscall.UTF16ToString((*v)[:]))
+ }
+ return args, nil
+}
+
+// compareCommandLineToArgvWithSyscall ensures that
+// os.CommandLineToArgv(cmd) and syscall.CommandLineToArgv(cmd)
+// return the same result.
+func compareCommandLineToArgvWithSyscall(t *testing.T, cmd string) {
+ syscallArgs, err := syscallCommandLineToArgv(cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ args := os.CommandLineToArgv(cmd)
+ if want, have := fmt.Sprintf("%q", syscallArgs), fmt.Sprintf("%q", args); want != have {
+ t.Errorf("testing os.commandLineToArgv(%q) failed: have %q want %q", cmd, args, syscallArgs)
+ return
+ }
+}
+
+func TestCmdArgs(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("in short mode; skipping test that builds a binary")
+ }
+ t.Parallel()
+
+ tmpdir := t.TempDir()
+
+ const prog = `
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ fmt.Printf("%q", os.Args)
+}
+`
+ src := filepath.Join(tmpdir, "main.go")
+ if err := os.WriteFile(src, []byte(prog), 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ exe := filepath.Join(tmpdir, "main.exe")
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src)
+ cmd.Dir = tmpdir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("building main.exe failed: %v\n%s", err, out)
+ }
+
+ var cmds = []string{
+ ``,
+ ` a b c`,
+ ` "`,
+ ` ""`,
+ ` """`,
+ ` "" a`,
+ ` "123"`,
+ ` \"123\"`,
+ ` \"123 456\"`,
+ ` \\"`,
+ ` \\\"`,
+ ` \\\\\"`,
+ ` \\\"x`,
+ ` """"\""\\\"`,
+ ` abc`,
+ ` \\\\\""x"""y z`,
+ "\tb\t\"x\ty\"",
+ ` "Брад" d e`,
+ // examples from https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
+ ` "abc" d e`,
+ ` a\\b d"e f"g h`,
+ ` a\\\"b c d`,
+ ` a\\\\"b c" d e`,
+ // http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
+ // from 5.4 Examples
+ ` CallMeIshmael`,
+ ` "Call Me Ishmael"`,
+ ` Cal"l Me I"shmael`,
+ ` CallMe\"Ishmael`,
+ ` "CallMe\"Ishmael"`,
+ ` "Call Me Ishmael\\"`,
+ ` "CallMe\\\"Ishmael"`,
+ ` a\\\b`,
+ ` "a\\\b"`,
+ // from 5.5 Some Common Tasks
+ ` "\"Call Me Ishmael\""`,
+ ` "C:\TEST A\\"`,
+ ` "\"C:\TEST A\\\""`,
+ // from 5.6 The Microsoft Examples Explained
+ ` "a b c" d e`,
+ ` "ab\"c" "\\" d`,
+ ` a\\\b d"e f"g h`,
+ ` a\\\"b c d`,
+ ` a\\\\"b c" d e`,
+ // from 5.7 Double Double Quote Examples (pre 2008)
+ ` "a b c""`,
+ ` """CallMeIshmael""" b c`,
+ ` """Call Me Ishmael"""`,
+ ` """"Call Me Ishmael"" b c`,
+ }
+ for _, cmd := range cmds {
+ compareCommandLineToArgvWithSyscall(t, "test"+cmd)
+ compareCommandLineToArgvWithSyscall(t, `"cmd line"`+cmd)
+ compareCommandLineToArgvWithSyscall(t, exe+cmd)
+
+ // test both syscall.EscapeArg and os.commandLineToArgv
+ args := os.CommandLineToArgv(exe + cmd)
+ out, err := testenv.Command(t, args[0], args[1:]...).CombinedOutput()
+ if err != nil {
+ t.Fatalf("running %q failed: %v\n%v", args, err, string(out))
+ }
+ if want, have := fmt.Sprintf("%q", args), string(out); want != have {
+ t.Errorf("wrong output of executing %q: have %q want %q", args, have, want)
+ continue
+ }
+ }
+}
+
+func findOneDriveDir() (string, error) {
+ // as per https://stackoverflow.com/questions/42519624/how-to-determine-location-of-onedrive-on-windows-7-and-8-in-c
+ const onedrivekey = `SOFTWARE\Microsoft\OneDrive`
+ k, err := registry.OpenKey(registry.CURRENT_USER, onedrivekey, registry.READ)
+ if err != nil {
+ return "", fmt.Errorf("OpenKey(%q) failed: %v", onedrivekey, err)
+ }
+ defer k.Close()
+
+ path, valtype, err := k.GetStringValue("UserFolder")
+ if err != nil {
+ return "", fmt.Errorf("reading UserFolder failed: %v", err)
+ }
+
+ if valtype == registry.EXPAND_SZ {
+ expanded, err := registry.ExpandString(path)
+ if err != nil {
+ return "", fmt.Errorf("expanding UserFolder failed: %v", err)
+ }
+ path = expanded
+ }
+
+ return path, nil
+}
+
+// TestOneDrive verifies that OneDrive folder is a directory and not a symlink.
+func TestOneDrive(t *testing.T) {
+ t.Parallel()
+
+ dir, err := findOneDriveDir()
+ if err != nil {
+ t.Skipf("Skipping, because we did not find OneDrive directory: %v", err)
+ }
+ testDirStats(t, dir)
+}
+
+func TestWindowsDevNullFile(t *testing.T) {
+ t.Parallel()
+
+ f1, err := os.Open("NUL")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f1.Close()
+
+ fi1, err := f1.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ f2, err := os.Open("nul")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f2.Close()
+
+ fi2, err := f2.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !os.SameFile(fi1, fi2) {
+ t.Errorf(`"NUL" and "nul" are not the same file`)
+ }
+}
+
+func TestFileStatNUL(t *testing.T) {
+ t.Parallel()
+
+ f, err := os.Open("NUL")
+ if err != nil {
+ t.Fatal(err)
+ }
+ fi, err := f.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := fi.Mode(), os.ModeDevice|os.ModeCharDevice|0666; got != want {
+ t.Errorf("Open(%q).Stat().Mode() = %v, want %v", "NUL", got, want)
+ }
+}
+
+func TestStatNUL(t *testing.T) {
+ t.Parallel()
+
+ fi, err := os.Stat("NUL")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := fi.Mode(), os.ModeDevice|os.ModeCharDevice|0666; got != want {
+ t.Errorf("Stat(%q).Mode() = %v, want %v", "NUL", got, want)
+ }
+}
+
+// TestSymlinkCreation verifies that creating a symbolic link
+// works on Windows when developer mode is active.
+// This is supported starting Windows 10 (1703, v10.0.14972).
+func TestSymlinkCreation(t *testing.T) {
+ if !testenv.HasSymlink() && !isWindowsDeveloperModeActive() {
+ t.Skip("Windows developer mode is not active")
+ }
+ t.Parallel()
+
+ temp := t.TempDir()
+ dummyFile := filepath.Join(temp, "file")
+ if err := os.WriteFile(dummyFile, []byte(""), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ linkFile := filepath.Join(temp, "link")
+ if err := os.Symlink(dummyFile, linkFile); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// isWindowsDeveloperModeActive checks whether or not the developer mode is active on Windows 10.
+// Returns false for prior Windows versions.
+// see https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development
+func isWindowsDeveloperModeActive() bool {
+ key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", registry.READ)
+ if err != nil {
+ return false
+ }
+
+ val, _, err := key.GetIntegerValue("AllowDevelopmentWithoutDevLicense")
+ if err != nil {
+ return false
+ }
+
+ return val != 0
+}
+
+// TestRootRelativeDirSymlink verifies that symlinks to paths relative to the
+// drive root (beginning with "\" but no volume name) are created with the
+// correct symlink type.
+// (See https://golang.org/issue/39183#issuecomment-632175728.)
+func TestRootRelativeDirSymlink(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+ t.Parallel()
+
+ temp := t.TempDir()
+ dir := filepath.Join(temp, "dir")
+ if err := os.Mkdir(dir, 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ volumeRelDir := strings.TrimPrefix(dir, filepath.VolumeName(dir)) // leaves leading backslash
+
+ link := filepath.Join(temp, "link")
+ err := os.Symlink(volumeRelDir, link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("Symlink(%#q, %#q)", volumeRelDir, link)
+
+ f, err := os.Open(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ if fi, err := f.Stat(); err != nil {
+ t.Fatal(err)
+ } else if !fi.IsDir() {
+ t.Errorf("Open(%#q).Stat().IsDir() = false; want true", f.Name())
+ }
+}
+
+// TestWorkingDirectoryRelativeSymlink verifies that symlinks to paths relative
+// to the current working directory for the drive, such as "C:File.txt", are
+// correctly converted to absolute links of the correct symlink type (per
+// https://docs.microsoft.com/en-us/windows/win32/fileio/creating-symbolic-links).
+func TestWorkingDirectoryRelativeSymlink(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+
+ // Construct a directory to be symlinked.
+ temp := t.TempDir()
+ if v := filepath.VolumeName(temp); len(v) < 2 || v[1] != ':' {
+ t.Skipf("Can't test relative symlinks: t.TempDir() (%#q) does not begin with a drive letter.", temp)
+ }
+
+ absDir := filepath.Join(temp, `dir\sub`)
+ if err := os.MkdirAll(absDir, 0755); err != nil {
+ t.Fatal(err)
+ }
+
+ // Change to the temporary directory and construct a
+ // working-directory-relative symlink.
+ oldwd, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := os.Chdir(oldwd); err != nil {
+ t.Fatal(err)
+ }
+ }()
+ if err := os.Chdir(temp); err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("Chdir(%#q)", temp)
+
+ wdRelDir := filepath.VolumeName(temp) + `dir\sub` // no backslash after volume.
+ absLink := filepath.Join(temp, "link")
+ err = os.Symlink(wdRelDir, absLink)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("Symlink(%#q, %#q)", wdRelDir, absLink)
+
+ // Now change back to the original working directory and verify that the
+ // symlink still refers to its original path and is correctly marked as a
+ // directory.
+ if err := os.Chdir(oldwd); err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("Chdir(%#q)", oldwd)
+
+ resolved, err := os.Readlink(absLink)
+ if err != nil {
+ t.Errorf("Readlink(%#q): %v", absLink, err)
+ } else if resolved != absDir {
+ t.Errorf("Readlink(%#q) = %#q; want %#q", absLink, resolved, absDir)
+ }
+
+ linkFile, err := os.Open(absLink)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer linkFile.Close()
+
+ linkInfo, err := linkFile.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !linkInfo.IsDir() {
+ t.Errorf("Open(%#q).Stat().IsDir() = false; want true", absLink)
+ }
+
+ absInfo, err := os.Stat(absDir)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !os.SameFile(absInfo, linkInfo) {
+ t.Errorf("SameFile(Stat(%#q), Open(%#q).Stat()) = false; want true", absDir, absLink)
+ }
+}
+
+// TestStatOfInvalidName is regression test for issue #24999.
+func TestStatOfInvalidName(t *testing.T) {
+ t.Parallel()
+
+ _, err := os.Stat("*.go")
+ if err == nil {
+ t.Fatal(`os.Stat("*.go") unexpectedly succeeded`)
+ }
+}
+
+// findUnusedDriveLetter searches mounted drive list on the system
+// (starting from Z: and ending at D:) for unused drive letter.
+// It returns path to the found drive root directory (like Z:\) or error.
+func findUnusedDriveLetter() (string, error) {
+ // Do not use A: and B:, because they are reserved for floppy drive.
+ // Do not use C:, because it is normally used for main drive.
+ for l := 'Z'; l >= 'D'; l-- {
+ p := string(l) + `:\`
+ _, err := os.Stat(p)
+ if os.IsNotExist(err) {
+ return p, nil
+ }
+ }
+ return "", errors.New("Could not find unused drive letter.")
+}
+
+func TestRootDirAsTemp(t *testing.T) {
+ if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
+ fmt.Print(os.TempDir())
+ os.Exit(0)
+ }
+
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ exe, err := os.Executable()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ newtmp, err := findUnusedDriveLetter()
+ if err != nil {
+ t.Skip(err)
+ }
+
+ cmd := testenv.Command(t, exe, "-test.run=TestRootDirAsTemp")
+ cmd.Env = cmd.Environ()
+ cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
+ cmd.Env = append(cmd.Env, "TMP="+newtmp)
+ cmd.Env = append(cmd.Env, "TEMP="+newtmp)
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("Failed to spawn child process: %v %q", err, string(output))
+ }
+ if want, have := newtmp, string(output); have != want {
+ t.Fatalf("unexpected child process output %q, want %q", have, want)
+ }
+}
+
+func testReadlink(t *testing.T, path, want string) {
+ got, err := os.Readlink(path)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if got != want {
+ t.Errorf(`Readlink(%q): got %q, want %q`, path, got, want)
+ }
+}
+
+func mklink(t *testing.T, link, target string) {
+ output, err := testenv.Command(t, "cmd", "/c", "mklink", link, target).CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to run mklink %v %v: %v %q", link, target, err, output)
+ }
+}
+
+func mklinkj(t *testing.T, link, target string) {
+ output, err := testenv.Command(t, "cmd", "/c", "mklink", "/J", link, target).CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to run mklink %v %v: %v %q", link, target, err, output)
+ }
+}
+
+func mklinkd(t *testing.T, link, target string) {
+ output, err := testenv.Command(t, "cmd", "/c", "mklink", "/D", link, target).CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to run mklink %v %v: %v %q", link, target, err, output)
+ }
+}
+
+func TestWindowsReadlink(t *testing.T) {
+ tmpdir, err := os.MkdirTemp("", "TestWindowsReadlink")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ // Make sure tmpdir is not a symlink, otherwise tests will fail.
+ tmpdir, err = filepath.EvalSymlinks(tmpdir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ chdir(t, tmpdir)
+
+ vol := filepath.VolumeName(tmpdir)
+ output, err := testenv.Command(t, "cmd", "/c", "mountvol", vol, "/L").CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to run mountvol %v /L: %v %q", vol, err, output)
+ }
+ ntvol := strings.Trim(string(output), " \n\r")
+
+ dir := filepath.Join(tmpdir, "dir")
+ err = os.MkdirAll(dir, 0777)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ absdirjlink := filepath.Join(tmpdir, "absdirjlink")
+ mklinkj(t, absdirjlink, dir)
+ testReadlink(t, absdirjlink, dir)
+
+ ntdirjlink := filepath.Join(tmpdir, "ntdirjlink")
+ mklinkj(t, ntdirjlink, ntvol+absdirjlink[len(filepath.VolumeName(absdirjlink)):])
+ testReadlink(t, ntdirjlink, absdirjlink)
+
+ ntdirjlinktolink := filepath.Join(tmpdir, "ntdirjlinktolink")
+ mklinkj(t, ntdirjlinktolink, ntvol+absdirjlink[len(filepath.VolumeName(absdirjlink)):])
+ testReadlink(t, ntdirjlinktolink, absdirjlink)
+
+ mklinkj(t, "reldirjlink", "dir")
+ testReadlink(t, "reldirjlink", dir) // relative directory junction resolves to absolute path
+
+ // Make sure we have sufficient privilege to run mklink command.
+ testenv.MustHaveSymlink(t)
+
+ absdirlink := filepath.Join(tmpdir, "absdirlink")
+ mklinkd(t, absdirlink, dir)
+ testReadlink(t, absdirlink, dir)
+
+ ntdirlink := filepath.Join(tmpdir, "ntdirlink")
+ mklinkd(t, ntdirlink, ntvol+absdirlink[len(filepath.VolumeName(absdirlink)):])
+ testReadlink(t, ntdirlink, absdirlink)
+
+ mklinkd(t, "reldirlink", "dir")
+ testReadlink(t, "reldirlink", "dir")
+
+ file := filepath.Join(tmpdir, "file")
+ err = os.WriteFile(file, []byte(""), 0666)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ filelink := filepath.Join(tmpdir, "filelink")
+ mklink(t, filelink, file)
+ testReadlink(t, filelink, file)
+
+ linktofilelink := filepath.Join(tmpdir, "linktofilelink")
+ mklink(t, linktofilelink, ntvol+filelink[len(filepath.VolumeName(filelink)):])
+ testReadlink(t, linktofilelink, filelink)
+
+ mklink(t, "relfilelink", "file")
+ testReadlink(t, "relfilelink", "file")
+}
+
+func TestOpenDirTOCTOU(t *testing.T) {
+ t.Parallel()
+
+ // Check opened directories can't be renamed until the handle is closed.
+ // See issue 52747.
+ tmpdir := t.TempDir()
+ dir := filepath.Join(tmpdir, "dir")
+ if err := os.Mkdir(dir, 0777); err != nil {
+ t.Fatal(err)
+ }
+ f, err := os.Open(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ newpath := filepath.Join(tmpdir, "dir1")
+ err = os.Rename(dir, newpath)
+ if err == nil || !errors.Is(err, windows.ERROR_SHARING_VIOLATION) {
+ f.Close()
+ t.Fatalf("Rename(%q, %q) = %v; want windows.ERROR_SHARING_VIOLATION", dir, newpath, err)
+ }
+ f.Close()
+ err = os.Rename(dir, newpath)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestAppExecLinkStat(t *testing.T) {
+ // We expect executables installed to %LOCALAPPDATA%\Microsoft\WindowsApps to
+ // be reparse points with tag IO_REPARSE_TAG_APPEXECLINK. Here we check that
+ // such reparse points are treated as irregular (but executable) files, not
+ // broken symlinks.
+ appdata := os.Getenv("LOCALAPPDATA")
+ if appdata == "" {
+ t.Skipf("skipping: LOCALAPPDATA not set")
+ }
+
+ pythonExeName := "python3.exe"
+ pythonPath := filepath.Join(appdata, `Microsoft\WindowsApps`, pythonExeName)
+
+ lfi, err := os.Lstat(pythonPath)
+ if err != nil {
+ t.Skip("skipping test, because Python 3 is not installed via the Windows App Store on this system; see https://golang.org/issue/42919")
+ }
+
+ // An APPEXECLINK reparse point is not a symlink, so os.Readlink should return
+ // a non-nil error for it, and Stat should return results identical to Lstat.
+ linkName, err := os.Readlink(pythonPath)
+ if err == nil {
+ t.Errorf("os.Readlink(%q) = %q, but expected an error\n(should be an APPEXECLINK reparse point, not a symlink)", pythonPath, linkName)
+ }
+
+ sfi, err := os.Stat(pythonPath)
+ if err != nil {
+ t.Fatalf("Stat %s: %v", pythonPath, err)
+ }
+
+ if lfi.Name() != sfi.Name() {
+ t.Logf("os.Lstat(%q) = %+v", pythonPath, lfi)
+ t.Logf("os.Stat(%q) = %+v", pythonPath, sfi)
+ t.Errorf("files should be same")
+ }
+
+ if lfi.Name() != pythonExeName {
+ t.Errorf("Stat %s: got %q, but wanted %q", pythonPath, lfi.Name(), pythonExeName)
+ }
+ if m := lfi.Mode(); m&fs.ModeSymlink != 0 {
+ t.Errorf("%q should be a file, not a link (mode=0x%x)", pythonPath, uint32(m))
+ }
+ if m := lfi.Mode(); m&fs.ModeDir != 0 {
+ t.Errorf("%q should be a file, not a directory (mode=0x%x)", pythonPath, uint32(m))
+ }
+ if m := lfi.Mode(); m&fs.ModeIrregular == 0 {
+ // A reparse point is not a regular file, but we don't have a more appropriate
+ // ModeType bit for it, so it should be marked as irregular.
+ t.Errorf("%q should not be a regular file (mode=0x%x)", pythonPath, uint32(m))
+ }
+
+ if sfi.Name() != pythonExeName {
+ t.Errorf("Stat %s: got %q, but wanted %q", pythonPath, sfi.Name(), pythonExeName)
+ }
+ if m := sfi.Mode(); m&fs.ModeSymlink != 0 {
+ t.Errorf("%q should be a file, not a link (mode=0x%x)", pythonPath, uint32(m))
+ }
+ if m := sfi.Mode(); m&fs.ModeDir != 0 {
+ t.Errorf("%q should be a file, not a directory (mode=0x%x)", pythonPath, uint32(m))
+ }
+ if m := sfi.Mode(); m&fs.ModeIrregular == 0 {
+ // A reparse point is not a regular file, but we don't have a more appropriate
+ // ModeType bit for it, so it should be marked as irregular.
+ t.Errorf("%q should not be a regular file (mode=0x%x)", pythonPath, uint32(m))
+ }
+
+ p, err := exec.LookPath(pythonPath)
+ if err != nil {
+ t.Errorf("exec.LookPath(%q): %v", pythonPath, err)
+ }
+ if p != pythonPath {
+ t.Errorf("exec.LookPath(%q) = %q; want %q", pythonPath, p, pythonPath)
+ }
+}
+
+func TestIllformedUTF16FileName(t *testing.T) {
+ dir := t.TempDir()
+ const sep = string(os.PathSeparator)
+ if !strings.HasSuffix(dir, sep) {
+ dir += sep
+ }
+
+ // This UTF-16 file name is ill-formed as it contains low surrogates that are not preceded by high surrogates ([1:5]).
+ namew := []uint16{0x2e, 0xdc6d, 0xdc73, 0xdc79, 0xdc73, 0x30, 0x30, 0x30, 0x31, 0}
+
+ // Create a file whose name contains unpaired surrogates.
+ // Use syscall.CreateFile instead of os.Create to simulate a file that is created by
+ // a non-Go program so the file name hasn't gone through syscall.UTF16FromString.
+ dirw := utf16.Encode([]rune(dir))
+ pathw := append(dirw, namew...)
+ fd, err := syscall.CreateFile(&pathw[0], syscall.GENERIC_ALL, 0, nil, syscall.CREATE_NEW, 0, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ syscall.CloseHandle(fd)
+
+ name := syscall.UTF16ToString(namew)
+ path := filepath.Join(dir, name)
+ // Verify that os.Lstat can query the file.
+ fi, err := os.Lstat(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got := fi.Name(); got != name {
+ t.Errorf("got %q, want %q", got, name)
+ }
+ // Verify that File.Readdirnames lists the file.
+ f, err := os.Open(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ files, err := f.Readdirnames(0)
+ f.Close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !slices.Contains(files, name) {
+ t.Error("file not listed")
+ }
+ // Verify that os.RemoveAll can remove the directory
+ // and that it doesn't hang.
+ err = os.RemoveAll(dir)
+ if err != nil {
+ t.Error(err)
+ }
+}
+
+func TestUTF16Alloc(t *testing.T) {
+ allowsPerRun := func(want int, f func()) {
+ t.Helper()
+ got := int(testing.AllocsPerRun(5, f))
+ if got != want {
+ t.Errorf("got %d allocs, want %d", got, want)
+ }
+ }
+ allowsPerRun(1, func() {
+ syscall.UTF16ToString([]uint16{'a', 'b', 'c'})
+ })
+ allowsPerRun(1, func() {
+ syscall.UTF16FromString("abc")
+ })
+}
+
+func TestNewFileInvalid(t *testing.T) {
+ t.Parallel()
+ if f := os.NewFile(uintptr(syscall.InvalidHandle), "invalid"); f != nil {
+ t.Errorf("NewFile(InvalidHandle) got %v want nil", f)
+ }
+}
+
+func TestReadDirPipe(t *testing.T) {
+ dir := `\\.\pipe\`
+ fi, err := os.Stat(dir)
+ if err != nil || !fi.IsDir() {
+ t.Skipf("%s is not a directory", dir)
+ }
+ _, err = os.ReadDir(dir)
+ if err != nil {
+ t.Errorf("ReadDir(%q) = %v", dir, err)
+ }
+}
diff --git a/src/os/path.go b/src/os/path.go
new file mode 100644
index 0000000..df87887
--- /dev/null
+++ b/src/os/path.go
@@ -0,0 +1,79 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+)
+
+// MkdirAll creates a directory named path,
+// along with any necessary parents, and returns nil,
+// or else returns an error.
+// The permission bits perm (before umask) are used for all
+// directories that MkdirAll creates.
+// If path is already a directory, MkdirAll does nothing
+// and returns nil.
+func MkdirAll(path string, perm FileMode) error {
+ // Fast path: if we can tell whether path is a directory or file, stop with success or error.
+ dir, err := Stat(path)
+ if err == nil {
+ if dir.IsDir() {
+ return nil
+ }
+ return &PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
+ }
+
+ // Slow path: make sure parent exists and then call Mkdir for path.
+ i := len(path)
+ for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator.
+ i--
+ }
+
+ j := i
+ for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element.
+ j--
+ }
+
+ if j > 1 {
+ // Create parent.
+ err = MkdirAll(fixRootDirectory(path[:j-1]), perm)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Parent now exists; invoke Mkdir and use its result.
+ err = Mkdir(path, perm)
+ if err != nil {
+ // Handle arguments like "foo/." by
+ // double-checking that directory doesn't exist.
+ dir, err1 := Lstat(path)
+ if err1 == nil && dir.IsDir() {
+ return nil
+ }
+ return err
+ }
+ return nil
+}
+
+// RemoveAll removes path and any children it contains.
+// It removes everything it can but returns the first error
+// it encounters. If the path does not exist, RemoveAll
+// returns nil (no error).
+// If there is an error, it will be of type *PathError.
+func RemoveAll(path string) error {
+ return removeAll(path)
+}
+
+// endsWithDot reports whether the final component of path is ".".
+func endsWithDot(path string) bool {
+ if path == "." {
+ return true
+ }
+ if len(path) >= 2 && path[len(path)-1] == '.' && IsPathSeparator(path[len(path)-2]) {
+ return true
+ }
+ return false
+}
diff --git a/src/os/path_plan9.go b/src/os/path_plan9.go
new file mode 100644
index 0000000..a54b4b9
--- /dev/null
+++ b/src/os/path_plan9.go
@@ -0,0 +1,19 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+const (
+ PathSeparator = '/' // OS-specific path separator
+ PathListSeparator = '\000' // OS-specific path list separator
+)
+
+// IsPathSeparator reports whether c is a directory separator character.
+func IsPathSeparator(c uint8) bool {
+ return PathSeparator == c
+}
+
+func fixRootDirectory(p string) string {
+ return p
+}
diff --git a/src/os/path_test.go b/src/os/path_test.go
new file mode 100644
index 0000000..2a4e956
--- /dev/null
+++ b/src/os/path_test.go
@@ -0,0 +1,121 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "internal/testenv"
+ . "os"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "testing"
+)
+
+var isReadonlyError = func(error) bool { return false }
+
+func TestMkdirAll(t *testing.T) {
+ t.Parallel()
+
+ tmpDir := TempDir()
+ path := tmpDir + "/_TestMkdirAll_/dir/./dir2"
+ err := MkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
+ defer RemoveAll(tmpDir + "/_TestMkdirAll_")
+
+ // Already exists, should succeed.
+ err = MkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q (second time): %s", path, err)
+ }
+
+ // Make file.
+ fpath := path + "/file"
+ f, err := Create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ defer f.Close()
+
+ // Can't make directory named after file.
+ err = MkdirAll(fpath, 0777)
+ if err == nil {
+ t.Fatalf("MkdirAll %q: no error", fpath)
+ }
+ perr, ok := err.(*PathError)
+ if !ok {
+ t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err)
+ }
+ if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
+ t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
+ }
+
+ // Can't make subdirectory of file.
+ ffpath := fpath + "/subdir"
+ err = MkdirAll(ffpath, 0777)
+ if err == nil {
+ t.Fatalf("MkdirAll %q: no error", ffpath)
+ }
+ perr, ok = err.(*PathError)
+ if !ok {
+ t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err)
+ }
+ if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
+ t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
+ }
+
+ if runtime.GOOS == "windows" {
+ path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\`
+ err := MkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
+ }
+}
+
+func TestMkdirAllWithSymlink(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+ t.Parallel()
+
+ tmpDir := t.TempDir()
+ dir := tmpDir + "/dir"
+ if err := Mkdir(dir, 0755); err != nil {
+ t.Fatalf("Mkdir %s: %s", dir, err)
+ }
+
+ link := tmpDir + "/link"
+ if err := Symlink("dir", link); err != nil {
+ t.Fatalf("Symlink %s: %s", link, err)
+ }
+
+ path := link + "/foo"
+ if err := MkdirAll(path, 0755); err != nil {
+ t.Errorf("MkdirAll %q: %s", path, err)
+ }
+}
+
+func TestMkdirAllAtSlash(t *testing.T) {
+ switch runtime.GOOS {
+ case "android", "ios", "plan9", "windows":
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+ if testenv.Builder() == "" {
+ t.Skipf("skipping non-hermetic test outside of Go builders")
+ }
+
+ RemoveAll("/_go_os_test")
+ const dir = "/_go_os_test/dir"
+ err := MkdirAll(dir, 0777)
+ if err != nil {
+ pathErr, ok := err.(*PathError)
+ // common for users not to be able to write to /
+ if ok && (pathErr.Err == syscall.EACCES || isReadonlyError(pathErr.Err)) {
+ t.Skipf("could not create %v: %v", dir, err)
+ }
+ t.Fatalf(`MkdirAll "/_go_os_test/dir": %v, %s`, err, pathErr.Err)
+ }
+ RemoveAll("/_go_os_test")
+}
diff --git a/src/os/path_unix.go b/src/os/path_unix.go
new file mode 100644
index 0000000..c975cdb
--- /dev/null
+++ b/src/os/path_unix.go
@@ -0,0 +1,75 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1
+
+package os
+
+const (
+ PathSeparator = '/' // OS-specific path separator
+ PathListSeparator = ':' // OS-specific path list separator
+)
+
+// IsPathSeparator reports whether c is a directory separator character.
+func IsPathSeparator(c uint8) bool {
+ return PathSeparator == c
+}
+
+// basename removes trailing slashes and the leading directory name from path name.
+func basename(name string) string {
+ i := len(name) - 1
+ // Remove trailing slashes
+ for ; i > 0 && name[i] == '/'; i-- {
+ name = name[:i]
+ }
+ // Remove leading directory name
+ for i--; i >= 0; i-- {
+ if name[i] == '/' {
+ name = name[i+1:]
+ break
+ }
+ }
+
+ return name
+}
+
+// splitPath returns the base name and parent directory.
+func splitPath(path string) (string, string) {
+ // if no better parent is found, the path is relative from "here"
+ dirname := "."
+
+ // Remove all but one leading slash.
+ for len(path) > 1 && path[0] == '/' && path[1] == '/' {
+ path = path[1:]
+ }
+
+ i := len(path) - 1
+
+ // Remove trailing slashes.
+ for ; i > 0 && path[i] == '/'; i-- {
+ path = path[:i]
+ }
+
+ // if no slashes in path, base is path
+ basename := path
+
+ // Remove leading directory path
+ for i--; i >= 0; i-- {
+ if path[i] == '/' {
+ if i == 0 {
+ dirname = path[:1]
+ } else {
+ dirname = path[:i]
+ }
+ basename = path[i+1:]
+ break
+ }
+ }
+
+ return dirname, basename
+}
+
+func fixRootDirectory(p string) string {
+ return p
+}
diff --git a/src/os/path_windows.go b/src/os/path_windows.go
new file mode 100644
index 0000000..3356908
--- /dev/null
+++ b/src/os/path_windows.go
@@ -0,0 +1,227 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+const (
+ PathSeparator = '\\' // OS-specific path separator
+ PathListSeparator = ';' // OS-specific path list separator
+)
+
+// IsPathSeparator reports whether c is a directory separator character.
+func IsPathSeparator(c uint8) bool {
+ // NOTE: Windows accepts / as path separator.
+ return c == '\\' || c == '/'
+}
+
+// basename removes trailing slashes and the leading
+// directory name and drive letter from path name.
+func basename(name string) string {
+ // Remove drive letter
+ if len(name) == 2 && name[1] == ':' {
+ name = "."
+ } else if len(name) > 2 && name[1] == ':' {
+ name = name[2:]
+ }
+ i := len(name) - 1
+ // Remove trailing slashes
+ for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i-- {
+ name = name[:i]
+ }
+ // Remove leading directory name
+ for i--; i >= 0; i-- {
+ if name[i] == '/' || name[i] == '\\' {
+ name = name[i+1:]
+ break
+ }
+ }
+ return name
+}
+
+func isAbs(path string) (b bool) {
+ v := volumeName(path)
+ if v == "" {
+ return false
+ }
+ path = path[len(v):]
+ if path == "" {
+ return false
+ }
+ return IsPathSeparator(path[0])
+}
+
+func volumeName(path string) (v string) {
+ if len(path) < 2 {
+ return ""
+ }
+ // with drive letter
+ c := path[0]
+ if path[1] == ':' &&
+ ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
+ 'A' <= c && c <= 'Z') {
+ return path[:2]
+ }
+ // is it UNC
+ if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) &&
+ !IsPathSeparator(path[2]) && path[2] != '.' {
+ // first, leading `\\` and next shouldn't be `\`. its server name.
+ for n := 3; n < l-1; n++ {
+ // second, next '\' shouldn't be repeated.
+ if IsPathSeparator(path[n]) {
+ n++
+ // third, following something characters. its share name.
+ if !IsPathSeparator(path[n]) {
+ if path[n] == '.' {
+ break
+ }
+ for ; n < l; n++ {
+ if IsPathSeparator(path[n]) {
+ break
+ }
+ }
+ return path[:n]
+ }
+ break
+ }
+ }
+ }
+ return ""
+}
+
+func fromSlash(path string) string {
+ // Replace each '/' with '\\' if present
+ var pathbuf []byte
+ var lastSlash int
+ for i, b := range path {
+ if b == '/' {
+ if pathbuf == nil {
+ pathbuf = make([]byte, len(path))
+ }
+ copy(pathbuf[lastSlash:], path[lastSlash:i])
+ pathbuf[i] = '\\'
+ lastSlash = i + 1
+ }
+ }
+ if pathbuf == nil {
+ return path
+ }
+
+ copy(pathbuf[lastSlash:], path[lastSlash:])
+ return string(pathbuf)
+}
+
+func dirname(path string) string {
+ vol := volumeName(path)
+ i := len(path) - 1
+ for i >= len(vol) && !IsPathSeparator(path[i]) {
+ i--
+ }
+ dir := path[len(vol) : i+1]
+ last := len(dir) - 1
+ if last > 0 && IsPathSeparator(dir[last]) {
+ dir = dir[:last]
+ }
+ if dir == "" {
+ dir = "."
+ }
+ return vol + dir
+}
+
+// This is set via go:linkname on runtime.canUseLongPaths, and is true when the OS
+// supports opting into proper long path handling without the need for fixups.
+var canUseLongPaths bool
+
+// fixLongPath returns the extended-length (\\?\-prefixed) form of
+// path when needed, in order to avoid the default 260 character file
+// path limit imposed by Windows. If path is not easily converted to
+// the extended-length form (for example, if path is a relative path
+// or contains .. elements), or is short enough, fixLongPath returns
+// path unmodified.
+//
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
+func fixLongPath(path string) string {
+ if canUseLongPaths {
+ return path
+ }
+ // Do nothing (and don't allocate) if the path is "short".
+ // Empirically (at least on the Windows Server 2013 builder),
+ // the kernel is arbitrarily okay with < 248 bytes. That
+ // matches what the docs above say:
+ // "When using an API to create a directory, the specified
+ // path cannot be so long that you cannot append an 8.3 file
+ // name (that is, the directory name cannot exceed MAX_PATH
+ // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
+ //
+ // The MSDN docs appear to say that a normal path that is 248 bytes long
+ // will work; empirically the path must be less then 248 bytes long.
+ if len(path) < 248 {
+ // Don't fix. (This is how Go 1.7 and earlier worked,
+ // not automatically generating the \\?\ form)
+ return path
+ }
+
+ // The extended form begins with \\?\, as in
+ // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
+ // The extended form disables evaluation of . and .. path
+ // elements and disables the interpretation of / as equivalent
+ // to \. The conversion here rewrites / to \ and elides
+ // . elements as well as trailing or duplicate separators. For
+ // simplicity it avoids the conversion entirely for relative
+ // paths or paths containing .. elements. For now,
+ // \\server\share paths are not converted to
+ // \\?\UNC\server\share paths because the rules for doing so
+ // are less well-specified.
+ if len(path) >= 2 && path[:2] == `\\` {
+ // Don't canonicalize UNC paths.
+ return path
+ }
+ if !isAbs(path) {
+ // Relative path
+ return path
+ }
+
+ const prefix = `\\?`
+
+ pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
+ copy(pathbuf, prefix)
+ n := len(path)
+ r, w := 0, len(prefix)
+ for r < n {
+ switch {
+ case IsPathSeparator(path[r]):
+ // empty block
+ r++
+ case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])):
+ // /./
+ r++
+ case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])):
+ // /../ is currently unhandled
+ return path
+ default:
+ pathbuf[w] = '\\'
+ w++
+ for ; r < n && !IsPathSeparator(path[r]); r++ {
+ pathbuf[w] = path[r]
+ w++
+ }
+ }
+ }
+ // A drive's root directory needs a trailing \
+ if w == len(`\\?\c:`) {
+ pathbuf[w] = '\\'
+ w++
+ }
+ return string(pathbuf[:w])
+}
+
+// fixRootDirectory fixes a reference to a drive's root directory to
+// have the required trailing slash.
+func fixRootDirectory(p string) string {
+ if len(p) == len(`\\?\c:`) {
+ if IsPathSeparator(p[0]) && IsPathSeparator(p[1]) && p[2] == '?' && IsPathSeparator(p[3]) && p[5] == ':' {
+ return p + `\`
+ }
+ }
+ return p
+}
diff --git a/src/os/path_windows_test.go b/src/os/path_windows_test.go
new file mode 100644
index 0000000..2506b4f
--- /dev/null
+++ b/src/os/path_windows_test.go
@@ -0,0 +1,108 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "os"
+ "strings"
+ "syscall"
+ "testing"
+)
+
+func TestFixLongPath(t *testing.T) {
+ if os.CanUseLongPaths {
+ return
+ }
+ t.Parallel()
+
+ // 248 is long enough to trigger the longer-than-248 checks in
+ // fixLongPath, but short enough not to make a path component
+ // longer than 255, which is illegal on Windows. (which
+ // doesn't really matter anyway, since this is purely a string
+ // function we're testing, and it's not actually being used to
+ // do a system call)
+ veryLong := "l" + strings.Repeat("o", 248) + "ng"
+ for _, test := range []struct{ in, want string }{
+ // Short; unchanged:
+ {`C:\short.txt`, `C:\short.txt`},
+ {`C:\`, `C:\`},
+ {`C:`, `C:`},
+ // The "long" substring is replaced by a looooooong
+ // string which triggers the rewriting. Except in the
+ // cases below where it doesn't.
+ {`C:\long\foo.txt`, `\\?\C:\long\foo.txt`},
+ {`C:/long/foo.txt`, `\\?\C:\long\foo.txt`},
+ {`C:\long\foo\\bar\.\baz\\`, `\\?\C:\long\foo\bar\baz`},
+ {`\\unc\path`, `\\unc\path`},
+ {`long.txt`, `long.txt`},
+ {`C:long.txt`, `C:long.txt`},
+ {`c:\long\..\bar\baz`, `c:\long\..\bar\baz`},
+ {`\\?\c:\long\foo.txt`, `\\?\c:\long\foo.txt`},
+ {`\\?\c:\long/foo.txt`, `\\?\c:\long/foo.txt`},
+ } {
+ in := strings.ReplaceAll(test.in, "long", veryLong)
+ want := strings.ReplaceAll(test.want, "long", veryLong)
+ if got := os.FixLongPath(in); got != want {
+ got = strings.ReplaceAll(got, veryLong, "long")
+ t.Errorf("fixLongPath(%q) = %q; want %q", test.in, got, test.want)
+ }
+ }
+}
+
+func TestMkdirAllLongPath(t *testing.T) {
+ t.Parallel()
+
+ tmpDir := t.TempDir()
+ path := tmpDir
+ for i := 0; i < 100; i++ {
+ path += `\another-path-component`
+ }
+ if err := os.MkdirAll(path, 0777); err != nil {
+ t.Fatalf("MkdirAll(%q) failed; %v", path, err)
+ }
+ if err := os.RemoveAll(tmpDir); err != nil {
+ t.Fatalf("RemoveAll(%q) failed; %v", tmpDir, err)
+ }
+}
+
+func TestMkdirAllExtendedLength(t *testing.T) {
+ t.Parallel()
+ tmpDir := t.TempDir()
+
+ const prefix = `\\?\`
+ if len(tmpDir) < 4 || tmpDir[:4] != prefix {
+ fullPath, err := syscall.FullPath(tmpDir)
+ if err != nil {
+ t.Fatalf("FullPath(%q) fails: %v", tmpDir, err)
+ }
+ tmpDir = prefix + fullPath
+ }
+ path := tmpDir + `\dir\`
+ if err := os.MkdirAll(path, 0777); err != nil {
+ t.Fatalf("MkdirAll(%q) failed: %v", path, err)
+ }
+
+ path = path + `.\dir2`
+ if err := os.MkdirAll(path, 0777); err == nil {
+ t.Fatalf("MkdirAll(%q) should have failed, but did not", path)
+ }
+}
+
+func TestOpenRootSlash(t *testing.T) {
+ t.Parallel()
+
+ tests := []string{
+ `/`,
+ `\`,
+ }
+
+ for _, test := range tests {
+ dir, err := os.Open(test)
+ if err != nil {
+ t.Fatalf("Open(%q) failed: %v", test, err)
+ }
+ dir.Close()
+ }
+}
diff --git a/src/os/pipe2_unix.go b/src/os/pipe2_unix.go
new file mode 100644
index 0000000..2d293fd
--- /dev/null
+++ b/src/os/pipe2_unix.go
@@ -0,0 +1,22 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
+
+package os
+
+import "syscall"
+
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]int
+
+ e := syscall.Pipe2(p[0:], syscall.O_CLOEXEC)
+ if e != nil {
+ return nil, nil, NewSyscallError("pipe2", e)
+ }
+
+ return newFile(p[0], "|0", kindPipe), newFile(p[1], "|1", kindPipe), nil
+}
diff --git a/src/os/pipe_test.go b/src/os/pipe_test.go
new file mode 100644
index 0000000..6f01d30
--- /dev/null
+++ b/src/os/pipe_test.go
@@ -0,0 +1,478 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Test broken pipes on Unix systems.
+//
+//go:build !plan9 && !js && !wasip1
+
+package os_test
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "internal/testenv"
+ "io"
+ "io/fs"
+ "os"
+ "os/exec"
+ "os/signal"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func TestEPIPE(t *testing.T) {
+ // This test cannot be run in parallel because of a race similar
+ // to the one reported in https://go.dev/issue/22315.
+ //
+ // Even though the pipe is opened with O_CLOEXEC, if another test forks in
+ // between the call to os.Pipe and the call to r.Close, that child process can
+ // retain an open copy of r's file descriptor until it execs. If one of our
+ // Write calls occurs during that interval it can spuriously succeed,
+ // buffering the write to the child's copy of the pipe (even though the child
+ // will not actually read the buffered bytes).
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := r.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ expect := syscall.EPIPE
+ if runtime.GOOS == "windows" {
+ // 232 is Windows error code ERROR_NO_DATA, "The pipe is being closed".
+ expect = syscall.Errno(232)
+ }
+ // Every time we write to the pipe we should get an EPIPE.
+ for i := 0; i < 20; i++ {
+ _, err = w.Write([]byte("hi"))
+ if err == nil {
+ t.Fatal("unexpected success of Write to broken pipe")
+ }
+ if pe, ok := err.(*fs.PathError); ok {
+ err = pe.Err
+ }
+ if se, ok := err.(*os.SyscallError); ok {
+ err = se.Err
+ }
+ if err != expect {
+ t.Errorf("iteration %d: got %v, expected %v", i, err, expect)
+ }
+ }
+}
+
+func TestStdPipe(t *testing.T) {
+ switch runtime.GOOS {
+ case "windows":
+ t.Skip("Windows doesn't support SIGPIPE")
+ }
+
+ if os.Getenv("GO_TEST_STD_PIPE_HELPER") != "" {
+ if os.Getenv("GO_TEST_STD_PIPE_HELPER_SIGNAL") != "" {
+ signal.Notify(make(chan os.Signal, 1), syscall.SIGPIPE)
+ }
+ switch os.Getenv("GO_TEST_STD_PIPE_HELPER") {
+ case "1":
+ os.Stdout.Write([]byte("stdout"))
+ case "2":
+ os.Stderr.Write([]byte("stderr"))
+ case "3":
+ if _, err := os.NewFile(3, "3").Write([]byte("3")); err == nil {
+ os.Exit(3)
+ }
+ default:
+ panic("unrecognized value for GO_TEST_STD_PIPE_HELPER")
+ }
+ // For stdout/stderr, we should have crashed with a broken pipe error.
+ // The caller will be looking for that exit status,
+ // so just exit normally here to cause a failure in the caller.
+ // For descriptor 3, a normal exit is expected.
+ os.Exit(0)
+ }
+
+ testenv.MustHaveExec(t)
+ // This test cannot be run in parallel due to the same race as for TestEPIPE.
+ // (We expect a write to a closed pipe can fail, but a concurrent fork of a
+ // child process can cause the pipe to unexpectedly remain open.)
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := r.Close(); err != nil {
+ t.Fatal(err)
+ }
+ // Invoke the test program to run the test and write to a closed pipe.
+ // If sig is false:
+ // writing to stdout or stderr should cause an immediate SIGPIPE;
+ // writing to descriptor 3 should fail with EPIPE and then exit 0.
+ // If sig is true:
+ // all writes should fail with EPIPE and then exit 0.
+ for _, sig := range []bool{false, true} {
+ for dest := 1; dest < 4; dest++ {
+ cmd := testenv.Command(t, os.Args[0], "-test.run", "TestStdPipe")
+ cmd.Stdout = w
+ cmd.Stderr = w
+ cmd.ExtraFiles = []*os.File{w}
+ cmd.Env = append(os.Environ(), fmt.Sprintf("GO_TEST_STD_PIPE_HELPER=%d", dest))
+ if sig {
+ cmd.Env = append(cmd.Env, "GO_TEST_STD_PIPE_HELPER_SIGNAL=1")
+ }
+ if err := cmd.Run(); err == nil {
+ if !sig && dest < 3 {
+ t.Errorf("unexpected success of write to closed pipe %d sig %t in child", dest, sig)
+ }
+ } else if ee, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("unexpected exec error type %T: %v", err, err)
+ } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
+ t.Errorf("unexpected wait status type %T: %v", ee.Sys(), ee.Sys())
+ } else if ws.Signaled() && ws.Signal() == syscall.SIGPIPE {
+ if sig || dest > 2 {
+ t.Errorf("unexpected SIGPIPE signal for descriptor %d sig %t", dest, sig)
+ }
+ } else {
+ t.Errorf("unexpected exit status %v for descriptor %d sig %t", err, dest, sig)
+ }
+ }
+ }
+
+ // Test redirecting stdout but not stderr. Issue 40076.
+ cmd := testenv.Command(t, os.Args[0], "-test.run", "TestStdPipe")
+ cmd.Stdout = w
+ var stderr bytes.Buffer
+ cmd.Stderr = &stderr
+ cmd.Env = append(cmd.Environ(), "GO_TEST_STD_PIPE_HELPER=1")
+ if err := cmd.Run(); err == nil {
+ t.Errorf("unexpected success of write to closed stdout")
+ } else if ee, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("unexpected exec error type %T: %v", err, err)
+ } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
+ t.Errorf("unexpected wait status type %T: %v", ee.Sys(), ee.Sys())
+ } else if !ws.Signaled() || ws.Signal() != syscall.SIGPIPE {
+ t.Errorf("unexpected exit status %v for write to closed stdout", err)
+ }
+ if output := stderr.Bytes(); len(output) > 0 {
+ t.Errorf("unexpected output on stderr: %s", output)
+ }
+}
+
+func testClosedPipeRace(t *testing.T, read bool) {
+ // This test cannot be run in parallel due to the same race as for TestEPIPE.
+ // (We expect a write to a closed pipe can fail, but a concurrent fork of a
+ // child process can cause the pipe to unexpectedly remain open.)
+
+ limit := 1
+ if !read {
+ // Get the amount we have to write to overload a pipe
+ // with no reader.
+ limit = 131073
+ if b, err := os.ReadFile("/proc/sys/fs/pipe-max-size"); err == nil {
+ if i, err := strconv.Atoi(strings.TrimSpace(string(b))); err == nil {
+ limit = i + 1
+ }
+ }
+ t.Logf("using pipe write limit of %d", limit)
+ }
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ // Close the read end of the pipe in a goroutine while we are
+ // writing to the write end, or vice-versa.
+ go func() {
+ // Give the main goroutine a chance to enter the Read or
+ // Write call. This is sloppy but the test will pass even
+ // if we close before the read/write.
+ time.Sleep(20 * time.Millisecond)
+
+ var err error
+ if read {
+ err = r.Close()
+ } else {
+ err = w.Close()
+ }
+ if err != nil {
+ t.Error(err)
+ }
+ }()
+
+ b := make([]byte, limit)
+ if read {
+ _, err = r.Read(b[:])
+ } else {
+ _, err = w.Write(b[:])
+ }
+ if err == nil {
+ t.Error("I/O on closed pipe unexpectedly succeeded")
+ } else if pe, ok := err.(*fs.PathError); !ok {
+ t.Errorf("I/O on closed pipe returned unexpected error type %T; expected fs.PathError", pe)
+ } else if pe.Err != fs.ErrClosed {
+ t.Errorf("got error %q but expected %q", pe.Err, fs.ErrClosed)
+ } else {
+ t.Logf("I/O returned expected error %q", err)
+ }
+}
+
+func TestClosedPipeRaceRead(t *testing.T) {
+ testClosedPipeRace(t, true)
+}
+
+func TestClosedPipeRaceWrite(t *testing.T) {
+ testClosedPipeRace(t, false)
+}
+
+// Issue 20915: Reading on nonblocking fd should not return "waiting
+// for unsupported file type." Currently it returns EAGAIN; it is
+// possible that in the future it will simply wait for data.
+func TestReadNonblockingFd(t *testing.T) {
+ switch runtime.GOOS {
+ case "windows":
+ t.Skip("Windows doesn't support SetNonblock")
+ }
+ if os.Getenv("GO_WANT_READ_NONBLOCKING_FD") == "1" {
+ fd := syscallDescriptor(os.Stdin.Fd())
+ syscall.SetNonblock(fd, true)
+ defer syscall.SetNonblock(fd, false)
+ _, err := os.Stdin.Read(make([]byte, 1))
+ if err != nil {
+ if perr, ok := err.(*fs.PathError); !ok || perr.Err != syscall.EAGAIN {
+ t.Fatalf("read on nonblocking stdin got %q, should have gotten EAGAIN", err)
+ }
+ }
+ os.Exit(0)
+ }
+
+ testenv.MustHaveExec(t)
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+ cmd := testenv.Command(t, os.Args[0], "-test.run="+t.Name())
+ cmd.Env = append(cmd.Environ(), "GO_WANT_READ_NONBLOCKING_FD=1")
+ cmd.Stdin = r
+ output, err := cmd.CombinedOutput()
+ t.Logf("%s", output)
+ if err != nil {
+ t.Errorf("child process failed: %v", err)
+ }
+}
+
+func TestCloseWithBlockingReadByNewFile(t *testing.T) {
+ t.Parallel()
+
+ var p [2]syscallDescriptor
+ err := syscall.Pipe(p[:])
+ if err != nil {
+ t.Fatal(err)
+ }
+ // os.NewFile returns a blocking mode file.
+ testCloseWithBlockingRead(t, os.NewFile(uintptr(p[0]), "reader"), os.NewFile(uintptr(p[1]), "writer"))
+}
+
+func TestCloseWithBlockingReadByFd(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Calling Fd will put the file into blocking mode.
+ _ = r.Fd()
+ testCloseWithBlockingRead(t, r, w)
+}
+
+// Test that we don't let a blocking read prevent a close.
+func testCloseWithBlockingRead(t *testing.T, r, w *os.File) {
+ var (
+ enteringRead = make(chan struct{})
+ done = make(chan struct{})
+ )
+ go func() {
+ var b [1]byte
+ close(enteringRead)
+ _, err := r.Read(b[:])
+ if err == nil {
+ t.Error("I/O on closed pipe unexpectedly succeeded")
+ }
+
+ if pe, ok := err.(*fs.PathError); ok {
+ err = pe.Err
+ }
+ if err != io.EOF && err != fs.ErrClosed {
+ t.Errorf("got %v, expected EOF or closed", err)
+ }
+ close(done)
+ }()
+
+ // Give the goroutine a chance to enter the Read
+ // or Write call. This is sloppy but the test will
+ // pass even if we close before the read/write.
+ <-enteringRead
+ time.Sleep(20 * time.Millisecond)
+
+ if err := r.Close(); err != nil {
+ t.Error(err)
+ }
+ // r.Close has completed, but since we assume r is in blocking mode that
+ // probably didn't unblock the call to r.Read. Close w to unblock it.
+ w.Close()
+ <-done
+}
+
+func TestPipeEOF(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ testPipeEOF(t, r, w)
+}
+
+// testPipeEOF tests that when the write side of a pipe or FIFO is closed,
+// a blocked Read call on the reader side returns io.EOF.
+//
+// This scenario previously failed to unblock the Read call on darwin.
+// (See https://go.dev/issue/24164.)
+func testPipeEOF(t *testing.T, r io.ReadCloser, w io.WriteCloser) {
+ // parkDelay is an arbitrary delay we wait for a pipe-reader goroutine to park
+ // before issuing the corresponding write. The test should pass no matter what
+ // delay we use, but with a longer delay is has a higher chance of detecting
+ // poller bugs.
+ parkDelay := 10 * time.Millisecond
+ if testing.Short() {
+ parkDelay = 100 * time.Microsecond
+ }
+ writerDone := make(chan struct{})
+ defer func() {
+ if err := r.Close(); err != nil {
+ t.Errorf("error closing reader: %v", err)
+ }
+ <-writerDone
+ }()
+
+ write := make(chan int, 1)
+ go func() {
+ defer close(writerDone)
+
+ for i := range write {
+ time.Sleep(parkDelay)
+ _, err := fmt.Fprintf(w, "line %d\n", i)
+ if err != nil {
+ t.Errorf("error writing to fifo: %v", err)
+ return
+ }
+ }
+
+ time.Sleep(parkDelay)
+ if err := w.Close(); err != nil {
+ t.Errorf("error closing writer: %v", err)
+ }
+ }()
+
+ rbuf := bufio.NewReader(r)
+ for i := 0; i < 3; i++ {
+ write <- i
+ b, err := rbuf.ReadBytes('\n')
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("%s\n", bytes.TrimSpace(b))
+ }
+
+ close(write)
+ b, err := rbuf.ReadBytes('\n')
+ if err != io.EOF || len(b) != 0 {
+ t.Errorf(`ReadBytes: %q, %v; want "", io.EOF`, b, err)
+ }
+}
+
+// Issue 24481.
+func TestFdRace(t *testing.T) {
+ // This test starts 100 simultaneous goroutines, which could bury a more
+ // interesting stack if this or some other test happens to panic. It is also
+ // nearly instantaneous, so any latency benefit from running it in parallel
+ // would be minimal.
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ var wg sync.WaitGroup
+ call := func() {
+ defer wg.Done()
+ w.Fd()
+ }
+
+ const tries = 100
+ for i := 0; i < tries; i++ {
+ wg.Add(1)
+ go call()
+ }
+ wg.Wait()
+}
+
+func TestFdReadRace(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ const count = 10
+
+ c := make(chan bool, 1)
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ var buf [count]byte
+ r.SetReadDeadline(time.Now().Add(time.Minute))
+ c <- true
+ if _, err := r.Read(buf[:]); os.IsTimeout(err) {
+ t.Error("read timed out")
+ }
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ <-c
+ // Give the other goroutine a chance to enter the Read.
+ // It doesn't matter if this occasionally fails, the test
+ // will still pass, it just won't test anything.
+ time.Sleep(10 * time.Millisecond)
+ r.Fd()
+
+ // The bug was that Fd would hang until Read timed out.
+ // If the bug is fixed, then writing to w and closing r here
+ // will cause the Read to exit before the timeout expires.
+ w.Write(make([]byte, count))
+ r.Close()
+ }()
+
+ wg.Wait()
+}
diff --git a/src/os/pipe_unix.go b/src/os/pipe_unix.go
new file mode 100644
index 0000000..2eb11a0
--- /dev/null
+++ b/src/os/pipe_unix.go
@@ -0,0 +1,28 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build aix || darwin
+
+package os
+
+import "syscall"
+
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]int
+
+ // See ../syscall/exec.go for description of lock.
+ syscall.ForkLock.RLock()
+ e := syscall.Pipe(p[0:])
+ if e != nil {
+ syscall.ForkLock.RUnlock()
+ return nil, nil, NewSyscallError("pipe", e)
+ }
+ syscall.CloseOnExec(p[0])
+ syscall.CloseOnExec(p[1])
+ syscall.ForkLock.RUnlock()
+
+ return newFile(p[0], "|0", kindPipe), newFile(p[1], "|1", kindPipe), nil
+}
diff --git a/src/os/pipe_wasm.go b/src/os/pipe_wasm.go
new file mode 100644
index 0000000..87a29b1
--- /dev/null
+++ b/src/os/pipe_wasm.go
@@ -0,0 +1,16 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasm
+
+package os
+
+import "syscall"
+
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ // Neither GOOS=js nor GOOS=wasip1 have pipes.
+ return nil, nil, NewSyscallError("pipe", syscall.ENOSYS)
+}
diff --git a/src/os/proc.go b/src/os/proc.go
new file mode 100644
index 0000000..3aae568
--- /dev/null
+++ b/src/os/proc.go
@@ -0,0 +1,80 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Process etc.
+
+package os
+
+import (
+ "internal/testlog"
+ "runtime"
+ "syscall"
+)
+
+// Args hold the command-line arguments, starting with the program name.
+var Args []string
+
+func init() {
+ if runtime.GOOS == "windows" {
+ // Initialized in exec_windows.go.
+ return
+ }
+ Args = runtime_args()
+}
+
+func runtime_args() []string // in package runtime
+
+// Getuid returns the numeric user id of the caller.
+//
+// On Windows, it returns -1.
+func Getuid() int { return syscall.Getuid() }
+
+// Geteuid returns the numeric effective user id of the caller.
+//
+// On Windows, it returns -1.
+func Geteuid() int { return syscall.Geteuid() }
+
+// Getgid returns the numeric group id of the caller.
+//
+// On Windows, it returns -1.
+func Getgid() int { return syscall.Getgid() }
+
+// Getegid returns the numeric effective group id of the caller.
+//
+// On Windows, it returns -1.
+func Getegid() int { return syscall.Getegid() }
+
+// Getgroups returns a list of the numeric ids of groups that the caller belongs to.
+//
+// On Windows, it returns syscall.EWINDOWS. See the os/user package
+// for a possible alternative.
+func Getgroups() ([]int, error) {
+ gids, e := syscall.Getgroups()
+ return gids, NewSyscallError("getgroups", e)
+}
+
+// Exit causes the current program to exit with the given status code.
+// Conventionally, code zero indicates success, non-zero an error.
+// The program terminates immediately; deferred functions are not run.
+//
+// For portability, the status code should be in the range [0, 125].
+func Exit(code int) {
+ if code == 0 && testlog.PanicOnExit0() {
+ // We were told to panic on calls to os.Exit(0).
+ // This is used to fail tests that make an early
+ // unexpected call to os.Exit(0).
+ panic("unexpected call to os.Exit(0) during test")
+ }
+
+ // Inform the runtime that os.Exit is being called. If -race is
+ // enabled, this will give race detector a chance to fail the
+ // program (racy programs do not have the right to finish
+ // successfully). If coverage is enabled, then this call will
+ // enable us to write out a coverage data file.
+ runtime_beforeExit(code)
+
+ syscall.Exit(code)
+}
+
+func runtime_beforeExit(exitCode int) // implemented in runtime
diff --git a/src/os/rawconn.go b/src/os/rawconn.go
new file mode 100644
index 0000000..14a495d
--- /dev/null
+++ b/src/os/rawconn.go
@@ -0,0 +1,47 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !plan9
+
+package os
+
+import (
+ "runtime"
+)
+
+// rawConn implements syscall.RawConn.
+type rawConn struct {
+ file *File
+}
+
+func (c *rawConn) Control(f func(uintptr)) error {
+ if err := c.file.checkValid("SyscallConn.Control"); err != nil {
+ return err
+ }
+ err := c.file.pfd.RawControl(f)
+ runtime.KeepAlive(c.file)
+ return err
+}
+
+func (c *rawConn) Read(f func(uintptr) bool) error {
+ if err := c.file.checkValid("SyscallConn.Read"); err != nil {
+ return err
+ }
+ err := c.file.pfd.RawRead(f)
+ runtime.KeepAlive(c.file)
+ return err
+}
+
+func (c *rawConn) Write(f func(uintptr) bool) error {
+ if err := c.file.checkValid("SyscallConn.Write"); err != nil {
+ return err
+ }
+ err := c.file.pfd.RawWrite(f)
+ runtime.KeepAlive(c.file)
+ return err
+}
+
+func newRawConn(file *File) (*rawConn, error) {
+ return &rawConn{file: file}, nil
+}
diff --git a/src/os/rawconn_test.go b/src/os/rawconn_test.go
new file mode 100644
index 0000000..8aae7ae
--- /dev/null
+++ b/src/os/rawconn_test.go
@@ -0,0 +1,66 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Test use of raw connections.
+//
+//go:build !plan9 && !js && !wasip1
+
+package os_test
+
+import (
+ "os"
+ "syscall"
+ "testing"
+)
+
+func TestRawConnReadWrite(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ rconn, err := r.SyscallConn()
+ if err != nil {
+ t.Fatal(err)
+ }
+ wconn, err := w.SyscallConn()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var operr error
+ err = wconn.Write(func(s uintptr) bool {
+ _, operr = syscall.Write(syscallDescriptor(s), []byte{'b'})
+ return operr != syscall.EAGAIN
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if operr != nil {
+ t.Fatal(err)
+ }
+
+ var n int
+ buf := make([]byte, 1)
+ err = rconn.Read(func(s uintptr) bool {
+ n, operr = syscall.Read(syscallDescriptor(s), buf)
+ return operr != syscall.EAGAIN
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ if operr != nil {
+ t.Fatal(operr)
+ }
+ if n != 1 {
+ t.Errorf("read %d bytes, expected 1", n)
+ }
+ if buf[0] != 'b' {
+ t.Errorf("read %q, expected %q", buf, "b")
+ }
+}
diff --git a/src/os/read_test.go b/src/os/read_test.go
new file mode 100644
index 0000000..18f7d54
--- /dev/null
+++ b/src/os/read_test.go
@@ -0,0 +1,138 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "bytes"
+ . "os"
+ "path/filepath"
+ "runtime"
+ "testing"
+)
+
+func checkNamedSize(t *testing.T, path string, size int64) {
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatalf("Stat %q (looking for size %d): %s", path, size, err)
+ }
+ if dir.Size() != size {
+ t.Errorf("Stat %q: size %d want %d", path, dir.Size(), size)
+ }
+}
+
+func TestReadFile(t *testing.T) {
+ t.Parallel()
+
+ filename := "rumpelstilzchen"
+ contents, err := ReadFile(filename)
+ if err == nil {
+ t.Fatalf("ReadFile %s: error expected, none found", filename)
+ }
+
+ filename = "read_test.go"
+ contents, err = ReadFile(filename)
+ if err != nil {
+ t.Fatalf("ReadFile %s: %v", filename, err)
+ }
+
+ checkNamedSize(t, filename, int64(len(contents)))
+}
+
+func TestWriteFile(t *testing.T) {
+ t.Parallel()
+
+ f, err := CreateTemp("", "ioutil-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ defer Remove(f.Name())
+
+ msg := "Programming today is a race between software engineers striving to " +
+ "build bigger and better idiot-proof programs, and the Universe trying " +
+ "to produce bigger and better idiots. So far, the Universe is winning."
+
+ if err := WriteFile(f.Name(), []byte(msg), 0644); err != nil {
+ t.Fatalf("WriteFile %s: %v", f.Name(), err)
+ }
+
+ data, err := ReadFile(f.Name())
+ if err != nil {
+ t.Fatalf("ReadFile %s: %v", f.Name(), err)
+ }
+
+ if string(data) != msg {
+ t.Fatalf("ReadFile: wrong data:\nhave %q\nwant %q", string(data), msg)
+ }
+}
+
+func TestReadOnlyWriteFile(t *testing.T) {
+ if Getuid() == 0 {
+ t.Skipf("Root can write to read-only files anyway, so skip the read-only test.")
+ }
+ if runtime.GOOS == "wasip1" {
+ t.Skip("no support for file permissions on " + runtime.GOOS)
+ }
+ t.Parallel()
+
+ // We don't want to use CreateTemp directly, since that opens a file for us as 0600.
+ tempDir, err := MkdirTemp("", t.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(tempDir)
+ filename := filepath.Join(tempDir, "blurp.txt")
+
+ shmorp := []byte("shmorp")
+ florp := []byte("florp")
+ err = WriteFile(filename, shmorp, 0444)
+ if err != nil {
+ t.Fatalf("WriteFile %s: %v", filename, err)
+ }
+ err = WriteFile(filename, florp, 0444)
+ if err == nil {
+ t.Fatalf("Expected an error when writing to read-only file %s", filename)
+ }
+ got, err := ReadFile(filename)
+ if err != nil {
+ t.Fatalf("ReadFile %s: %v", filename, err)
+ }
+ if !bytes.Equal(got, shmorp) {
+ t.Fatalf("want %s, got %s", shmorp, got)
+ }
+}
+
+func TestReadDir(t *testing.T) {
+ t.Parallel()
+
+ dirname := "rumpelstilzchen"
+ _, err := ReadDir(dirname)
+ if err == nil {
+ t.Fatalf("ReadDir %s: error expected, none found", dirname)
+ }
+
+ dirname = "."
+ list, err := ReadDir(dirname)
+ if err != nil {
+ t.Fatalf("ReadDir %s: %v", dirname, err)
+ }
+
+ foundFile := false
+ foundSubDir := false
+ for _, dir := range list {
+ switch {
+ case !dir.IsDir() && dir.Name() == "read_test.go":
+ foundFile = true
+ case dir.IsDir() && dir.Name() == "exec":
+ foundSubDir = true
+ }
+ }
+ if !foundFile {
+ t.Fatalf("ReadDir %s: read_test.go file not found", dirname)
+ }
+ if !foundSubDir {
+ t.Fatalf("ReadDir %s: exec directory not found", dirname)
+ }
+}
diff --git a/src/os/readfrom_linux.go b/src/os/readfrom_linux.go
new file mode 100644
index 0000000..7e80240
--- /dev/null
+++ b/src/os/readfrom_linux.go
@@ -0,0 +1,124 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "internal/poll"
+ "io"
+ "syscall"
+)
+
+var (
+ pollCopyFileRange = poll.CopyFileRange
+ pollSplice = poll.Splice
+)
+
+func (f *File) readFrom(r io.Reader) (written int64, handled bool, err error) {
+ // Neither copy_file_range(2) nor splice(2) supports destinations opened with
+ // O_APPEND, so don't bother to try zero-copy with these system calls.
+ //
+ // Visit https://man7.org/linux/man-pages/man2/copy_file_range.2.html#ERRORS and
+ // https://man7.org/linux/man-pages/man2/splice.2.html#ERRORS for details.
+ if f.appendMode {
+ return 0, false, nil
+ }
+
+ written, handled, err = f.copyFileRange(r)
+ if handled {
+ return
+ }
+ return f.spliceToFile(r)
+}
+
+func (f *File) spliceToFile(r io.Reader) (written int64, handled bool, err error) {
+ var (
+ remain int64
+ lr *io.LimitedReader
+ )
+ if lr, r, remain = tryLimitedReader(r); remain <= 0 {
+ return 0, true, nil
+ }
+
+ pfd := getPollFD(r)
+ // TODO(panjf2000): run some tests to see if we should unlock the non-streams for splice.
+ // Streams benefit the most from the splice(2), non-streams are not even supported in old kernels
+ // where splice(2) will just return EINVAL; newer kernels support non-streams like UDP, but I really
+ // doubt that splice(2) could help non-streams, cuz they usually send small frames respectively
+ // and one splice call would result in one frame.
+ // splice(2) is suitable for large data but the generation of fragments defeats its edge here.
+ // Therefore, don't bother to try splice if the r is not a streaming descriptor.
+ if pfd == nil || !pfd.IsStream {
+ return
+ }
+
+ var syscallName string
+ written, handled, syscallName, err = pollSplice(&f.pfd, pfd, remain)
+
+ if lr != nil {
+ lr.N = remain - written
+ }
+
+ return written, handled, wrapSyscallError(syscallName, err)
+}
+
+// getPollFD tries to get the poll.FD from the given io.Reader by expecting
+// the underlying type of r to be the implementation of syscall.Conn that contains
+// a *net.rawConn.
+func getPollFD(r io.Reader) *poll.FD {
+ sc, ok := r.(syscall.Conn)
+ if !ok {
+ return nil
+ }
+ rc, err := sc.SyscallConn()
+ if err != nil {
+ return nil
+ }
+ ipfd, ok := rc.(interface{ PollFD() *poll.FD })
+ if !ok {
+ return nil
+ }
+ return ipfd.PollFD()
+}
+
+func (f *File) copyFileRange(r io.Reader) (written int64, handled bool, err error) {
+ var (
+ remain int64
+ lr *io.LimitedReader
+ )
+ if lr, r, remain = tryLimitedReader(r); remain <= 0 {
+ return 0, true, nil
+ }
+
+ src, ok := r.(*File)
+ if !ok {
+ return 0, false, nil
+ }
+ if src.checkValid("ReadFrom") != nil {
+ // Avoid returning the error as we report handled as false,
+ // leave further error handling as the responsibility of the caller.
+ return 0, false, nil
+ }
+
+ written, handled, err = pollCopyFileRange(&f.pfd, &src.pfd, remain)
+ if lr != nil {
+ lr.N -= written
+ }
+ return written, handled, wrapSyscallError("copy_file_range", err)
+}
+
+// tryLimitedReader tries to assert the io.Reader to io.LimitedReader, it returns the io.LimitedReader,
+// the underlying io.Reader and the remaining amount of bytes if the assertion succeeds,
+// otherwise it just returns the original io.Reader and the theoretical unlimited remaining amount of bytes.
+func tryLimitedReader(r io.Reader) (*io.LimitedReader, io.Reader, int64) {
+ var remain int64 = 1<<63 - 1 // by default, copy until EOF
+
+ lr, ok := r.(*io.LimitedReader)
+ if !ok {
+ return nil, r, remain
+ }
+
+ remain = lr.N
+ return lr, lr.R, remain
+}
diff --git a/src/os/readfrom_linux_test.go b/src/os/readfrom_linux_test.go
new file mode 100644
index 0000000..4f98be4
--- /dev/null
+++ b/src/os/readfrom_linux_test.go
@@ -0,0 +1,822 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "bytes"
+ "errors"
+ "internal/poll"
+ "internal/testpty"
+ "io"
+ "math/rand"
+ "net"
+ . "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "golang.org/x/net/nettest"
+)
+
+func TestCopyFileRange(t *testing.T) {
+ sizes := []int{
+ 1,
+ 42,
+ 1025,
+ syscall.Getpagesize() + 1,
+ 32769,
+ }
+ t.Run("Basic", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testCopyFileRange(t, int64(size), -1)
+ })
+ }
+ })
+ t.Run("Limited", func(t *testing.T) {
+ t.Run("OneLess", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testCopyFileRange(t, int64(size), int64(size)-1)
+ })
+ }
+ })
+ t.Run("Half", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testCopyFileRange(t, int64(size), int64(size)/2)
+ })
+ }
+ })
+ t.Run("More", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testCopyFileRange(t, int64(size), int64(size)+7)
+ })
+ }
+ })
+ })
+ t.Run("DoesntTryInAppendMode", func(t *testing.T) {
+ dst, src, data, hook := newCopyFileRangeTest(t, 42)
+
+ dst2, err := OpenFile(dst.Name(), O_RDWR|O_APPEND, 0755)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer dst2.Close()
+
+ if _, err := io.Copy(dst2, src); err != nil {
+ t.Fatal(err)
+ }
+ if hook.called {
+ t.Fatal("called poll.CopyFileRange for destination in O_APPEND mode")
+ }
+ mustSeekStart(t, dst2)
+ mustContainData(t, dst2, data) // through traditional means
+ })
+ t.Run("CopyFileItself", func(t *testing.T) {
+ hook := hookCopyFileRange(t)
+
+ f, err := CreateTemp("", "file-readfrom-itself-test")
+ if err != nil {
+ t.Fatalf("failed to create tmp file: %v", err)
+ }
+ t.Cleanup(func() {
+ f.Close()
+ Remove(f.Name())
+ })
+
+ data := []byte("hello world!")
+ if _, err := f.Write(data); err != nil {
+ t.Fatalf("failed to create and feed the file: %v", err)
+ }
+
+ if err := f.Sync(); err != nil {
+ t.Fatalf("failed to save the file: %v", err)
+ }
+
+ // Rewind it.
+ if _, err := f.Seek(0, io.SeekStart); err != nil {
+ t.Fatalf("failed to rewind the file: %v", err)
+ }
+
+ // Read data from the file itself.
+ if _, err := io.Copy(f, f); err != nil {
+ t.Fatalf("failed to read from the file: %v", err)
+ }
+
+ if !hook.called || hook.written != 0 || hook.handled || hook.err != nil {
+ t.Fatalf("poll.CopyFileRange should be called and return the EINVAL error, but got hook.called=%t, hook.err=%v", hook.called, hook.err)
+ }
+
+ // Rewind it.
+ if _, err := f.Seek(0, io.SeekStart); err != nil {
+ t.Fatalf("failed to rewind the file: %v", err)
+ }
+
+ data2, err := io.ReadAll(f)
+ if err != nil {
+ t.Fatalf("failed to read from the file: %v", err)
+ }
+
+ // It should wind up a double of the original data.
+ if strings.Repeat(string(data), 2) != string(data2) {
+ t.Fatalf("data mismatch: %s != %s", string(data), string(data2))
+ }
+ })
+ t.Run("NotRegular", func(t *testing.T) {
+ t.Run("BothPipes", func(t *testing.T) {
+ hook := hookCopyFileRange(t)
+
+ pr1, pw1, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer pr1.Close()
+ defer pw1.Close()
+
+ pr2, pw2, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer pr2.Close()
+ defer pw2.Close()
+
+ // The pipe is empty, and PIPE_BUF is large enough
+ // for this, by (POSIX) definition, so there is no
+ // need for an additional goroutine.
+ data := []byte("hello")
+ if _, err := pw1.Write(data); err != nil {
+ t.Fatal(err)
+ }
+ pw1.Close()
+
+ n, err := io.Copy(pw2, pr1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n != int64(len(data)) {
+ t.Fatalf("transferred %d, want %d", n, len(data))
+ }
+ if !hook.called {
+ t.Fatalf("should have called poll.CopyFileRange")
+ }
+ pw2.Close()
+ mustContainData(t, pr2, data)
+ })
+ t.Run("DstPipe", func(t *testing.T) {
+ dst, src, data, hook := newCopyFileRangeTest(t, 255)
+ dst.Close()
+
+ pr, pw, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer pr.Close()
+ defer pw.Close()
+
+ n, err := io.Copy(pw, src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n != int64(len(data)) {
+ t.Fatalf("transferred %d, want %d", n, len(data))
+ }
+ if !hook.called {
+ t.Fatalf("should have called poll.CopyFileRange")
+ }
+ pw.Close()
+ mustContainData(t, pr, data)
+ })
+ t.Run("SrcPipe", func(t *testing.T) {
+ dst, src, data, hook := newCopyFileRangeTest(t, 255)
+ src.Close()
+
+ pr, pw, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer pr.Close()
+ defer pw.Close()
+
+ // The pipe is empty, and PIPE_BUF is large enough
+ // for this, by (POSIX) definition, so there is no
+ // need for an additional goroutine.
+ if _, err := pw.Write(data); err != nil {
+ t.Fatal(err)
+ }
+ pw.Close()
+
+ n, err := io.Copy(dst, pr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if n != int64(len(data)) {
+ t.Fatalf("transferred %d, want %d", n, len(data))
+ }
+ if !hook.called {
+ t.Fatalf("should have called poll.CopyFileRange")
+ }
+ mustSeekStart(t, dst)
+ mustContainData(t, dst, data)
+ })
+ })
+ t.Run("Nil", func(t *testing.T) {
+ var nilFile *File
+ anyFile, err := CreateTemp("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer Remove(anyFile.Name())
+ defer anyFile.Close()
+
+ if _, err := io.Copy(nilFile, nilFile); err != ErrInvalid {
+ t.Errorf("io.Copy(nilFile, nilFile) = %v, want %v", err, ErrInvalid)
+ }
+ if _, err := io.Copy(anyFile, nilFile); err != ErrInvalid {
+ t.Errorf("io.Copy(anyFile, nilFile) = %v, want %v", err, ErrInvalid)
+ }
+ if _, err := io.Copy(nilFile, anyFile); err != ErrInvalid {
+ t.Errorf("io.Copy(nilFile, anyFile) = %v, want %v", err, ErrInvalid)
+ }
+
+ if _, err := nilFile.ReadFrom(nilFile); err != ErrInvalid {
+ t.Errorf("nilFile.ReadFrom(nilFile) = %v, want %v", err, ErrInvalid)
+ }
+ if _, err := anyFile.ReadFrom(nilFile); err != ErrInvalid {
+ t.Errorf("anyFile.ReadFrom(nilFile) = %v, want %v", err, ErrInvalid)
+ }
+ if _, err := nilFile.ReadFrom(anyFile); err != ErrInvalid {
+ t.Errorf("nilFile.ReadFrom(anyFile) = %v, want %v", err, ErrInvalid)
+ }
+ })
+}
+
+func TestSpliceFile(t *testing.T) {
+ sizes := []int{
+ 1,
+ 42,
+ 1025,
+ syscall.Getpagesize() + 1,
+ 32769,
+ }
+ t.Run("Basic-TCP", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testSpliceFile(t, "tcp", int64(size), -1)
+ })
+ }
+ })
+ t.Run("Basic-Unix", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testSpliceFile(t, "unix", int64(size), -1)
+ })
+ }
+ })
+ t.Run("TCP-To-TTY", func(t *testing.T) {
+ testSpliceToTTY(t, "tcp", 32768)
+ })
+ t.Run("Unix-To-TTY", func(t *testing.T) {
+ testSpliceToTTY(t, "unix", 32768)
+ })
+ t.Run("Limited", func(t *testing.T) {
+ t.Run("OneLess-TCP", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testSpliceFile(t, "tcp", int64(size), int64(size)-1)
+ })
+ }
+ })
+ t.Run("OneLess-Unix", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testSpliceFile(t, "unix", int64(size), int64(size)-1)
+ })
+ }
+ })
+ t.Run("Half-TCP", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testSpliceFile(t, "tcp", int64(size), int64(size)/2)
+ })
+ }
+ })
+ t.Run("Half-Unix", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testSpliceFile(t, "unix", int64(size), int64(size)/2)
+ })
+ }
+ })
+ t.Run("More-TCP", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testSpliceFile(t, "tcp", int64(size), int64(size)+1)
+ })
+ }
+ })
+ t.Run("More-Unix", func(t *testing.T) {
+ for _, size := range sizes {
+ t.Run(strconv.Itoa(size), func(t *testing.T) {
+ testSpliceFile(t, "unix", int64(size), int64(size)+1)
+ })
+ }
+ })
+ })
+}
+
+func testSpliceFile(t *testing.T, proto string, size, limit int64) {
+ dst, src, data, hook, cleanup := newSpliceFileTest(t, proto, size)
+ defer cleanup()
+
+ // If we have a limit, wrap the reader.
+ var (
+ r io.Reader
+ lr *io.LimitedReader
+ )
+ if limit >= 0 {
+ lr = &io.LimitedReader{N: limit, R: src}
+ r = lr
+ if limit < int64(len(data)) {
+ data = data[:limit]
+ }
+ } else {
+ r = src
+ }
+ // Now call ReadFrom (through io.Copy), which will hopefully call poll.Splice
+ n, err := io.Copy(dst, r)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // We should have called poll.Splice with the right file descriptor arguments.
+ if n > 0 && !hook.called {
+ t.Fatal("expected to called poll.Splice")
+ }
+ if hook.called && hook.dstfd != int(dst.Fd()) {
+ t.Fatalf("wrong destination file descriptor: got %d, want %d", hook.dstfd, dst.Fd())
+ }
+ sc, ok := src.(syscall.Conn)
+ if !ok {
+ t.Fatalf("server Conn is not a syscall.Conn")
+ }
+ rc, err := sc.SyscallConn()
+ if err != nil {
+ t.Fatalf("server Conn SyscallConn error: %v", err)
+ }
+ if err = rc.Control(func(fd uintptr) {
+ if hook.called && hook.srcfd != int(fd) {
+ t.Fatalf("wrong source file descriptor: got %d, want %d", hook.srcfd, int(fd))
+ }
+ }); err != nil {
+ t.Fatalf("server Conn Control error: %v", err)
+ }
+
+ // Check that the offsets after the transfer make sense, that the size
+ // of the transfer was reported correctly, and that the destination
+ // file contains exactly the bytes we expect it to contain.
+ dstoff, err := dst.Seek(0, io.SeekCurrent)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if dstoff != int64(len(data)) {
+ t.Errorf("dstoff = %d, want %d", dstoff, len(data))
+ }
+ if n != int64(len(data)) {
+ t.Errorf("short ReadFrom: wrote %d bytes, want %d", n, len(data))
+ }
+ mustSeekStart(t, dst)
+ mustContainData(t, dst, data)
+
+ // If we had a limit, check that it was updated.
+ if lr != nil {
+ if want := limit - n; lr.N != want {
+ t.Fatalf("didn't update limit correctly: got %d, want %d", lr.N, want)
+ }
+ }
+}
+
+// Issue #59041.
+func testSpliceToTTY(t *testing.T, proto string, size int64) {
+ var wg sync.WaitGroup
+
+ // Call wg.Wait as the final deferred function,
+ // because the goroutines may block until some of
+ // the deferred Close calls.
+ defer wg.Wait()
+
+ pty, ttyName, err := testpty.Open()
+ if err != nil {
+ t.Skipf("skipping test because pty open failed: %v", err)
+ }
+ defer pty.Close()
+
+ // Open the tty directly, rather than via OpenFile.
+ // This bypasses the non-blocking support and is required
+ // to recreate the problem in the issue (#59041).
+ ttyFD, err := syscall.Open(ttyName, syscall.O_RDWR, 0)
+ if err != nil {
+ t.Skipf("skipping test becaused failed to open tty: %v", err)
+ }
+ defer syscall.Close(ttyFD)
+
+ tty := NewFile(uintptr(ttyFD), "tty")
+ defer tty.Close()
+
+ client, server := createSocketPair(t, proto)
+
+ data := bytes.Repeat([]byte{'a'}, int(size))
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ // The problem (issue #59041) occurs when writing
+ // a series of blocks of data. It does not occur
+ // when all the data is written at once.
+ for i := 0; i < len(data); i += 1024 {
+ if _, err := client.Write(data[i : i+1024]); err != nil {
+ // If we get here because the client was
+ // closed, skip the error.
+ if !errors.Is(err, net.ErrClosed) {
+ t.Errorf("error writing to socket: %v", err)
+ }
+ return
+ }
+ }
+ client.Close()
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ buf := make([]byte, 32)
+ for {
+ if _, err := pty.Read(buf); err != nil {
+ if err != io.EOF && !errors.Is(err, ErrClosed) {
+ // An error here doesn't matter for
+ // our test.
+ t.Logf("error reading from pty: %v", err)
+ }
+ return
+ }
+ }
+ }()
+
+ // Close Client to wake up the writing goroutine if necessary.
+ defer client.Close()
+
+ _, err = io.Copy(tty, server)
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+func testCopyFileRange(t *testing.T, size int64, limit int64) {
+ dst, src, data, hook := newCopyFileRangeTest(t, size)
+
+ // If we have a limit, wrap the reader.
+ var (
+ realsrc io.Reader
+ lr *io.LimitedReader
+ )
+ if limit >= 0 {
+ lr = &io.LimitedReader{N: limit, R: src}
+ realsrc = lr
+ if limit < int64(len(data)) {
+ data = data[:limit]
+ }
+ } else {
+ realsrc = src
+ }
+
+ // Now call ReadFrom (through io.Copy), which will hopefully call
+ // poll.CopyFileRange.
+ n, err := io.Copy(dst, realsrc)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // If we didn't have a limit, we should have called poll.CopyFileRange
+ // with the right file descriptor arguments.
+ if limit > 0 && !hook.called {
+ t.Fatal("never called poll.CopyFileRange")
+ }
+ if hook.called && hook.dstfd != int(dst.Fd()) {
+ t.Fatalf("wrong destination file descriptor: got %d, want %d", hook.dstfd, dst.Fd())
+ }
+ if hook.called && hook.srcfd != int(src.Fd()) {
+ t.Fatalf("wrong source file descriptor: got %d, want %d", hook.srcfd, src.Fd())
+ }
+
+ // Check that the offsets after the transfer make sense, that the size
+ // of the transfer was reported correctly, and that the destination
+ // file contains exactly the bytes we expect it to contain.
+ dstoff, err := dst.Seek(0, io.SeekCurrent)
+ if err != nil {
+ t.Fatal(err)
+ }
+ srcoff, err := src.Seek(0, io.SeekCurrent)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if dstoff != srcoff {
+ t.Errorf("offsets differ: dstoff = %d, srcoff = %d", dstoff, srcoff)
+ }
+ if dstoff != int64(len(data)) {
+ t.Errorf("dstoff = %d, want %d", dstoff, len(data))
+ }
+ if n != int64(len(data)) {
+ t.Errorf("short ReadFrom: wrote %d bytes, want %d", n, len(data))
+ }
+ mustSeekStart(t, dst)
+ mustContainData(t, dst, data)
+
+ // If we had a limit, check that it was updated.
+ if lr != nil {
+ if want := limit - n; lr.N != want {
+ t.Fatalf("didn't update limit correctly: got %d, want %d", lr.N, want)
+ }
+ }
+}
+
+// newCopyFileRangeTest initializes a new test for copy_file_range.
+//
+// It creates source and destination files, and populates the source file
+// with random data of the specified size. It also hooks package os' call
+// to poll.CopyFileRange and returns the hook so it can be inspected.
+func newCopyFileRangeTest(t *testing.T, size int64) (dst, src *File, data []byte, hook *copyFileRangeHook) {
+ t.Helper()
+
+ hook = hookCopyFileRange(t)
+ tmp := t.TempDir()
+
+ src, err := Create(filepath.Join(tmp, "src"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { src.Close() })
+
+ dst, err = Create(filepath.Join(tmp, "dst"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { dst.Close() })
+
+ // Populate the source file with data, then rewind it, so it can be
+ // consumed by copy_file_range(2).
+ prng := rand.New(rand.NewSource(time.Now().Unix()))
+ data = make([]byte, size)
+ prng.Read(data)
+ if _, err := src.Write(data); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := src.Seek(0, io.SeekStart); err != nil {
+ t.Fatal(err)
+ }
+
+ return dst, src, data, hook
+}
+
+// newSpliceFileTest initializes a new test for splice.
+//
+// It creates source sockets and destination file, and populates the source sockets
+// with random data of the specified size. It also hooks package os' call
+// to poll.Splice and returns the hook so it can be inspected.
+func newSpliceFileTest(t *testing.T, proto string, size int64) (*File, net.Conn, []byte, *spliceFileHook, func()) {
+ t.Helper()
+
+ hook := hookSpliceFile(t)
+
+ client, server := createSocketPair(t, proto)
+
+ dst, err := CreateTemp(t.TempDir(), "dst-splice-file-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() { dst.Close() })
+
+ randSeed := time.Now().Unix()
+ t.Logf("random data seed: %d\n", randSeed)
+ prng := rand.New(rand.NewSource(randSeed))
+ data := make([]byte, size)
+ prng.Read(data)
+
+ done := make(chan struct{})
+ go func() {
+ client.Write(data)
+ client.Close()
+ close(done)
+ }()
+
+ return dst, server, data, hook, func() { <-done }
+}
+
+// mustContainData ensures that the specified file contains exactly the
+// specified data.
+func mustContainData(t *testing.T, f *File, data []byte) {
+ t.Helper()
+
+ got := make([]byte, len(data))
+ if _, err := io.ReadFull(f, got); err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(got, data) {
+ t.Fatalf("didn't get the same data back from %s", f.Name())
+ }
+ if _, err := f.Read(make([]byte, 1)); err != io.EOF {
+ t.Fatalf("not at EOF")
+ }
+}
+
+func mustSeekStart(t *testing.T, f *File) {
+ if _, err := f.Seek(0, io.SeekStart); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func hookCopyFileRange(t *testing.T) *copyFileRangeHook {
+ h := new(copyFileRangeHook)
+ h.install()
+ t.Cleanup(h.uninstall)
+ return h
+}
+
+type copyFileRangeHook struct {
+ called bool
+ dstfd int
+ srcfd int
+ remain int64
+
+ written int64
+ handled bool
+ err error
+
+ original func(dst, src *poll.FD, remain int64) (int64, bool, error)
+}
+
+func (h *copyFileRangeHook) install() {
+ h.original = *PollCopyFileRangeP
+ *PollCopyFileRangeP = func(dst, src *poll.FD, remain int64) (int64, bool, error) {
+ h.called = true
+ h.dstfd = dst.Sysfd
+ h.srcfd = src.Sysfd
+ h.remain = remain
+ h.written, h.handled, h.err = h.original(dst, src, remain)
+ return h.written, h.handled, h.err
+ }
+}
+
+func (h *copyFileRangeHook) uninstall() {
+ *PollCopyFileRangeP = h.original
+}
+
+func hookSpliceFile(t *testing.T) *spliceFileHook {
+ h := new(spliceFileHook)
+ h.install()
+ t.Cleanup(h.uninstall)
+ return h
+}
+
+type spliceFileHook struct {
+ called bool
+ dstfd int
+ srcfd int
+ remain int64
+
+ written int64
+ handled bool
+ sc string
+ err error
+
+ original func(dst, src *poll.FD, remain int64) (int64, bool, string, error)
+}
+
+func (h *spliceFileHook) install() {
+ h.original = *PollSpliceFile
+ *PollSpliceFile = func(dst, src *poll.FD, remain int64) (int64, bool, string, error) {
+ h.called = true
+ h.dstfd = dst.Sysfd
+ h.srcfd = src.Sysfd
+ h.remain = remain
+ h.written, h.handled, h.sc, h.err = h.original(dst, src, remain)
+ return h.written, h.handled, h.sc, h.err
+ }
+}
+
+func (h *spliceFileHook) uninstall() {
+ *PollSpliceFile = h.original
+}
+
+// On some kernels copy_file_range fails on files in /proc.
+func TestProcCopy(t *testing.T) {
+ t.Parallel()
+
+ const cmdlineFile = "/proc/self/cmdline"
+ cmdline, err := ReadFile(cmdlineFile)
+ if err != nil {
+ t.Skipf("can't read /proc file: %v", err)
+ }
+ in, err := Open(cmdlineFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer in.Close()
+ outFile := filepath.Join(t.TempDir(), "cmdline")
+ out, err := Create(outFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := io.Copy(out, in); err != nil {
+ t.Fatal(err)
+ }
+ if err := out.Close(); err != nil {
+ t.Fatal(err)
+ }
+ copy, err := ReadFile(outFile)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !bytes.Equal(cmdline, copy) {
+ t.Errorf("copy of %q got %q want %q\n", cmdlineFile, copy, cmdline)
+ }
+}
+
+func TestGetPollFDFromReader(t *testing.T) {
+ t.Run("tcp", func(t *testing.T) { testGetPollFromReader(t, "tcp") })
+ t.Run("unix", func(t *testing.T) { testGetPollFromReader(t, "unix") })
+}
+
+func testGetPollFromReader(t *testing.T, proto string) {
+ _, server := createSocketPair(t, proto)
+ sc, ok := server.(syscall.Conn)
+ if !ok {
+ t.Fatalf("server Conn is not a syscall.Conn")
+ }
+ rc, err := sc.SyscallConn()
+ if err != nil {
+ t.Fatalf("server SyscallConn error: %v", err)
+ }
+ if err = rc.Control(func(fd uintptr) {
+ pfd := GetPollFDForTest(server)
+ if pfd == nil {
+ t.Fatalf("GetPollFDForTest didn't return poll.FD")
+ }
+ if pfd.Sysfd != int(fd) {
+ t.Fatalf("GetPollFDForTest returned wrong poll.FD, got: %d, want: %d", pfd.Sysfd, int(fd))
+ }
+ if !pfd.IsStream {
+ t.Fatalf("expected IsStream to be true")
+ }
+ if err = pfd.Init(proto, true); err == nil {
+ t.Fatalf("Init should have failed with the initialized poll.FD and return EEXIST error")
+ }
+ }); err != nil {
+ t.Fatalf("server Control error: %v", err)
+ }
+}
+
+func createSocketPair(t *testing.T, proto string) (client, server net.Conn) {
+ t.Helper()
+ if !nettest.TestableNetwork(proto) {
+ t.Skipf("%s does not support %q", runtime.GOOS, proto)
+ }
+
+ ln, err := nettest.NewLocalListener(proto)
+ if err != nil {
+ t.Fatalf("NewLocalListener error: %v", err)
+ }
+ t.Cleanup(func() {
+ if ln != nil {
+ ln.Close()
+ }
+ if client != nil {
+ client.Close()
+ }
+ if server != nil {
+ server.Close()
+ }
+ })
+ ch := make(chan struct{})
+ go func() {
+ var err error
+ server, err = ln.Accept()
+ if err != nil {
+ t.Errorf("Accept new connection error: %v", err)
+ }
+ ch <- struct{}{}
+ }()
+ client, err = net.Dial(proto, ln.Addr().String())
+ <-ch
+ if err != nil {
+ t.Fatalf("Dial new connection error: %v", err)
+ }
+ return client, server
+}
diff --git a/src/os/readfrom_stub.go b/src/os/readfrom_stub.go
new file mode 100644
index 0000000..8b7d5fb
--- /dev/null
+++ b/src/os/readfrom_stub.go
@@ -0,0 +1,13 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !linux
+
+package os
+
+import "io"
+
+func (f *File) readFrom(r io.Reader) (n int64, handled bool, err error) {
+ return 0, false, nil
+}
diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go
new file mode 100644
index 0000000..8ea5df4
--- /dev/null
+++ b/src/os/removeall_at.go
@@ -0,0 +1,199 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package os
+
+import (
+ "internal/syscall/unix"
+ "io"
+ "syscall"
+)
+
+func removeAll(path string) error {
+ if path == "" {
+ // fail silently to retain compatibility with previous behavior
+ // of RemoveAll. See issue 28830.
+ return nil
+ }
+
+ // The rmdir system call does not permit removing ".",
+ // so we don't permit it either.
+ if endsWithDot(path) {
+ return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL}
+ }
+
+ // Simple case: if Remove works, we're done.
+ err := Remove(path)
+ if err == nil || IsNotExist(err) {
+ return nil
+ }
+
+ // RemoveAll recurses by deleting the path base from
+ // its parent directory
+ parentDir, base := splitPath(path)
+
+ parent, err := Open(parentDir)
+ if IsNotExist(err) {
+ // If parent does not exist, base cannot exist. Fail silently
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+ defer parent.Close()
+
+ if err := removeAllFrom(parent, base); err != nil {
+ if pathErr, ok := err.(*PathError); ok {
+ pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
+ err = pathErr
+ }
+ return err
+ }
+ return nil
+}
+
+func removeAllFrom(parent *File, base string) error {
+ parentFd := int(parent.Fd())
+ // Simple case: if Unlink (aka remove) works, we're done.
+ err := ignoringEINTR(func() error {
+ return unix.Unlinkat(parentFd, base, 0)
+ })
+ if err == nil || IsNotExist(err) {
+ return nil
+ }
+
+ // EISDIR means that we have a directory, and we need to
+ // remove its contents.
+ // EPERM or EACCES means that we don't have write permission on
+ // the parent directory, but this entry might still be a directory
+ // whose contents need to be removed.
+ // Otherwise just return the error.
+ if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
+ return &PathError{Op: "unlinkat", Path: base, Err: err}
+ }
+
+ // Is this a directory we need to recurse into?
+ var statInfo syscall.Stat_t
+ statErr := ignoringEINTR(func() error {
+ return unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
+ })
+ if statErr != nil {
+ if IsNotExist(statErr) {
+ return nil
+ }
+ return &PathError{Op: "fstatat", Path: base, Err: statErr}
+ }
+ if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
+ // Not a directory; return the error from the unix.Unlinkat.
+ return &PathError{Op: "unlinkat", Path: base, Err: err}
+ }
+
+ // Remove the directory's entries.
+ var recurseErr error
+ for {
+ const reqSize = 1024
+ var respSize int
+
+ // Open the directory to recurse into
+ file, err := openFdAt(parentFd, base)
+ if err != nil {
+ if IsNotExist(err) {
+ return nil
+ }
+ recurseErr = &PathError{Op: "openfdat", Path: base, Err: err}
+ break
+ }
+
+ for {
+ numErr := 0
+
+ names, readErr := file.Readdirnames(reqSize)
+ // Errors other than EOF should stop us from continuing.
+ if readErr != nil && readErr != io.EOF {
+ file.Close()
+ if IsNotExist(readErr) {
+ return nil
+ }
+ return &PathError{Op: "readdirnames", Path: base, Err: readErr}
+ }
+
+ respSize = len(names)
+ for _, name := range names {
+ err := removeAllFrom(file, name)
+ if err != nil {
+ if pathErr, ok := err.(*PathError); ok {
+ pathErr.Path = base + string(PathSeparator) + pathErr.Path
+ }
+ numErr++
+ if recurseErr == nil {
+ recurseErr = err
+ }
+ }
+ }
+
+ // If we can delete any entry, break to start new iteration.
+ // Otherwise, we discard current names, get next entries and try deleting them.
+ if numErr != reqSize {
+ break
+ }
+ }
+
+ // Removing files from the directory may have caused
+ // the OS to reshuffle it. Simply calling Readdirnames
+ // again may skip some entries. The only reliable way
+ // to avoid this is to close and re-open the
+ // directory. See issue 20841.
+ file.Close()
+
+ // Finish when the end of the directory is reached
+ if respSize < reqSize {
+ break
+ }
+ }
+
+ // Remove the directory itself.
+ unlinkError := ignoringEINTR(func() error {
+ return unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR)
+ })
+ if unlinkError == nil || IsNotExist(unlinkError) {
+ return nil
+ }
+
+ if recurseErr != nil {
+ return recurseErr
+ }
+ return &PathError{Op: "unlinkat", Path: base, Err: unlinkError}
+}
+
+// openFdAt opens path relative to the directory in fd.
+// Other than that this should act like openFileNolog.
+// This acts like openFileNolog rather than OpenFile because
+// we are going to (try to) remove the file.
+// The contents of this file are not relevant for test caching.
+func openFdAt(dirfd int, name string) (*File, error) {
+ var r int
+ for {
+ var e error
+ r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
+ if e == nil {
+ break
+ }
+
+ // See comment in openFileNolog.
+ if e == syscall.EINTR {
+ continue
+ }
+
+ return nil, e
+ }
+
+ if !supportsCloseOnExec {
+ syscall.CloseOnExec(r)
+ }
+
+ // We use kindNoPoll because we know that this is a directory.
+ return newFile(r, name, kindNoPoll), nil
+}
diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go
new file mode 100644
index 0000000..2b8a772
--- /dev/null
+++ b/src/os/removeall_noat.go
@@ -0,0 +1,142 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !unix
+
+package os
+
+import (
+ "io"
+ "runtime"
+ "syscall"
+)
+
+func removeAll(path string) error {
+ if path == "" {
+ // fail silently to retain compatibility with previous behavior
+ // of RemoveAll. See issue 28830.
+ return nil
+ }
+
+ // The rmdir system call permits removing "." on Plan 9,
+ // so we don't permit it to remain consistent with the
+ // "at" implementation of RemoveAll.
+ if endsWithDot(path) {
+ return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL}
+ }
+
+ // Simple case: if Remove works, we're done.
+ err := Remove(path)
+ if err == nil || IsNotExist(err) {
+ return nil
+ }
+
+ // Otherwise, is this a directory we need to recurse into?
+ dir, serr := Lstat(path)
+ if serr != nil {
+ if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
+ return nil
+ }
+ return serr
+ }
+ if !dir.IsDir() {
+ // Not a directory; return the error from Remove.
+ return err
+ }
+
+ // Remove contents & return first error.
+ err = nil
+ for {
+ fd, err := Open(path)
+ if err != nil {
+ if IsNotExist(err) {
+ // Already deleted by someone else.
+ return nil
+ }
+ return err
+ }
+
+ const reqSize = 1024
+ var names []string
+ var readErr error
+
+ for {
+ numErr := 0
+ names, readErr = fd.Readdirnames(reqSize)
+
+ for _, name := range names {
+ err1 := RemoveAll(path + string(PathSeparator) + name)
+ if err == nil {
+ err = err1
+ }
+ if err1 != nil {
+ numErr++
+ }
+ }
+
+ // If we can delete any entry, break to start new iteration.
+ // Otherwise, we discard current names, get next entries and try deleting them.
+ if numErr != reqSize {
+ break
+ }
+ }
+
+ // Removing files from the directory may have caused
+ // the OS to reshuffle it. Simply calling Readdirnames
+ // again may skip some entries. The only reliable way
+ // to avoid this is to close and re-open the
+ // directory. See issue 20841.
+ fd.Close()
+
+ if readErr == io.EOF {
+ break
+ }
+ // If Readdirnames returned an error, use it.
+ if err == nil {
+ err = readErr
+ }
+ if len(names) == 0 {
+ break
+ }
+
+ // We don't want to re-open unnecessarily, so if we
+ // got fewer than request names from Readdirnames, try
+ // simply removing the directory now. If that
+ // succeeds, we are done.
+ if len(names) < reqSize {
+ err1 := Remove(path)
+ if err1 == nil || IsNotExist(err1) {
+ return nil
+ }
+
+ if err != nil {
+ // We got some error removing the
+ // directory contents, and since we
+ // read fewer names than we requested
+ // there probably aren't more files to
+ // remove. Don't loop around to read
+ // the directory again. We'll probably
+ // just get the same error.
+ return err
+ }
+ }
+ }
+
+ // Remove directory.
+ err1 := Remove(path)
+ if err1 == nil || IsNotExist(err1) {
+ return nil
+ }
+ if runtime.GOOS == "windows" && IsPermission(err1) {
+ if fs, err := Stat(path); err == nil {
+ if err = Chmod(path, FileMode(0200|int(fs.Mode()))); err == nil {
+ err1 = Remove(path)
+ }
+ }
+ }
+ if err == nil {
+ err = err1
+ }
+ return err
+}
diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go
new file mode 100644
index 0000000..2f7938b
--- /dev/null
+++ b/src/os/removeall_test.go
@@ -0,0 +1,506 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "bytes"
+ "fmt"
+ "internal/testenv"
+ . "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+func TestRemoveAll(t *testing.T) {
+ t.Parallel()
+
+ tmpDir := t.TempDir()
+ if err := RemoveAll(""); err != nil {
+ t.Errorf("RemoveAll(\"\"): %v; want nil", err)
+ }
+
+ file := filepath.Join(tmpDir, "file")
+ path := filepath.Join(tmpDir, "_TestRemoveAll_")
+ fpath := filepath.Join(path, "file")
+ dpath := filepath.Join(path, "dir")
+
+ // Make a regular file and remove
+ fd, err := Create(file)
+ if err != nil {
+ t.Fatalf("create %q: %s", file, err)
+ }
+ fd.Close()
+ if err = RemoveAll(file); err != nil {
+ t.Fatalf("RemoveAll %q (first): %s", file, err)
+ }
+ if _, err = Lstat(file); err == nil {
+ t.Fatalf("Lstat %q succeeded after RemoveAll (first)", file)
+ }
+
+ // Make directory with 1 file and remove.
+ if err := MkdirAll(path, 0777); err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
+ fd, err = Create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ fd.Close()
+ if err = RemoveAll(path); err != nil {
+ t.Fatalf("RemoveAll %q (second): %s", path, err)
+ }
+ if _, err = Lstat(path); err == nil {
+ t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path)
+ }
+
+ // Make directory with file and subdirectory and remove.
+ if err = MkdirAll(dpath, 0777); err != nil {
+ t.Fatalf("MkdirAll %q: %s", dpath, err)
+ }
+ fd, err = Create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ fd.Close()
+ fd, err = Create(dpath + "/file")
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ fd.Close()
+ if err = RemoveAll(path); err != nil {
+ t.Fatalf("RemoveAll %q (third): %s", path, err)
+ }
+ if _, err := Lstat(path); err == nil {
+ t.Fatalf("Lstat %q succeeded after RemoveAll (third)", path)
+ }
+
+ // Chmod is not supported under Windows or wasip1 and test fails as root.
+ if runtime.GOOS != "windows" && runtime.GOOS != "wasip1" && Getuid() != 0 {
+ // Make directory with file and subdirectory and trigger error.
+ if err = MkdirAll(dpath, 0777); err != nil {
+ t.Fatalf("MkdirAll %q: %s", dpath, err)
+ }
+
+ for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} {
+ fd, err = Create(s)
+ if err != nil {
+ t.Fatalf("create %q: %s", s, err)
+ }
+ fd.Close()
+ }
+ if err = Chmod(dpath, 0); err != nil {
+ t.Fatalf("Chmod %q 0: %s", dpath, err)
+ }
+
+ // No error checking here: either RemoveAll
+ // will or won't be able to remove dpath;
+ // either way we want to see if it removes fpath
+ // and path/zzz. Reasons why RemoveAll might
+ // succeed in removing dpath as well include:
+ // * running as root
+ // * running on a file system without permissions (FAT)
+ RemoveAll(path)
+ Chmod(dpath, 0777)
+
+ for _, s := range []string{fpath, path + "/zzz"} {
+ if _, err = Lstat(s); err == nil {
+ t.Fatalf("Lstat %q succeeded after partial RemoveAll", s)
+ }
+ }
+ }
+ if err = RemoveAll(path); err != nil {
+ t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err)
+ }
+ if _, err = Lstat(path); err == nil {
+ t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path)
+ }
+}
+
+// Test RemoveAll on a large directory.
+func TestRemoveAllLarge(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+ t.Parallel()
+
+ tmpDir := t.TempDir()
+ path := filepath.Join(tmpDir, "_TestRemoveAllLarge_")
+
+ // Make directory with 1000 files and remove.
+ if err := MkdirAll(path, 0777); err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
+ for i := 0; i < 1000; i++ {
+ fpath := fmt.Sprintf("%s/file%d", path, i)
+ fd, err := Create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ fd.Close()
+ }
+ if err := RemoveAll(path); err != nil {
+ t.Fatalf("RemoveAll %q: %s", path, err)
+ }
+ if _, err := Lstat(path); err == nil {
+ t.Fatalf("Lstat %q succeeded after RemoveAll", path)
+ }
+}
+
+func TestRemoveAllLongPath(t *testing.T) {
+ switch runtime.GOOS {
+ case "aix", "darwin", "ios", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "illumos", "solaris":
+ break
+ default:
+ t.Skip("skipping for not implemented platforms")
+ }
+
+ prevDir, err := Getwd()
+ if err != nil {
+ t.Fatalf("Could not get wd: %s", err)
+ }
+
+ startPath, err := MkdirTemp("", "TestRemoveAllLongPath-")
+ if err != nil {
+ t.Fatalf("Could not create TempDir: %s", err)
+ }
+ defer RemoveAll(startPath)
+
+ err = Chdir(startPath)
+ if err != nil {
+ t.Fatalf("Could not chdir %s: %s", startPath, err)
+ }
+
+ // Removing paths with over 4096 chars commonly fails
+ for i := 0; i < 41; i++ {
+ name := strings.Repeat("a", 100)
+
+ err = Mkdir(name, 0755)
+ if err != nil {
+ t.Fatalf("Could not mkdir %s: %s", name, err)
+ }
+
+ err = Chdir(name)
+ if err != nil {
+ t.Fatalf("Could not chdir %s: %s", name, err)
+ }
+ }
+
+ err = Chdir(prevDir)
+ if err != nil {
+ t.Fatalf("Could not chdir %s: %s", prevDir, err)
+ }
+
+ err = RemoveAll(startPath)
+ if err != nil {
+ t.Errorf("RemoveAll could not remove long file path %s: %s", startPath, err)
+ }
+}
+
+func TestRemoveAllDot(t *testing.T) {
+ prevDir, err := Getwd()
+ if err != nil {
+ t.Fatalf("Could not get wd: %s", err)
+ }
+ tempDir, err := MkdirTemp("", "TestRemoveAllDot-")
+ if err != nil {
+ t.Fatalf("Could not create TempDir: %s", err)
+ }
+ defer RemoveAll(tempDir)
+
+ err = Chdir(tempDir)
+ if err != nil {
+ t.Fatalf("Could not chdir to tempdir: %s", err)
+ }
+
+ err = RemoveAll(".")
+ if err == nil {
+ t.Errorf("RemoveAll succeed to remove .")
+ }
+
+ err = Chdir(prevDir)
+ if err != nil {
+ t.Fatalf("Could not chdir %s: %s", prevDir, err)
+ }
+}
+
+func TestRemoveAllDotDot(t *testing.T) {
+ t.Parallel()
+
+ tempDir := t.TempDir()
+ subdir := filepath.Join(tempDir, "x")
+ subsubdir := filepath.Join(subdir, "y")
+ if err := MkdirAll(subsubdir, 0777); err != nil {
+ t.Fatal(err)
+ }
+ if err := RemoveAll(filepath.Join(subsubdir, "..")); err != nil {
+ t.Error(err)
+ }
+ for _, dir := range []string{subsubdir, subdir} {
+ if _, err := Stat(dir); err == nil {
+ t.Errorf("%s: exists after RemoveAll", dir)
+ }
+ }
+}
+
+// Issue #29178.
+func TestRemoveReadOnlyDir(t *testing.T) {
+ t.Parallel()
+
+ tempDir := t.TempDir()
+ subdir := filepath.Join(tempDir, "x")
+ if err := Mkdir(subdir, 0); err != nil {
+ t.Fatal(err)
+ }
+
+ // If an error occurs make it more likely that removing the
+ // temporary directory will succeed.
+ defer Chmod(subdir, 0777)
+
+ if err := RemoveAll(subdir); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := Stat(subdir); err == nil {
+ t.Error("subdirectory was not removed")
+ }
+}
+
+// Issue #29983.
+func TestRemoveAllButReadOnlyAndPathError(t *testing.T) {
+ switch runtime.GOOS {
+ case "js", "wasip1", "windows":
+ t.Skipf("skipping test on %s", runtime.GOOS)
+ }
+
+ if Getuid() == 0 {
+ t.Skip("skipping test when running as root")
+ }
+
+ t.Parallel()
+
+ tempDir := t.TempDir()
+ dirs := []string{
+ "a",
+ "a/x",
+ "a/x/1",
+ "b",
+ "b/y",
+ "b/y/2",
+ "c",
+ "c/z",
+ "c/z/3",
+ }
+ readonly := []string{
+ "b",
+ }
+ inReadonly := func(d string) bool {
+ for _, ro := range readonly {
+ if d == ro {
+ return true
+ }
+ dd, _ := filepath.Split(d)
+ if filepath.Clean(dd) == ro {
+ return true
+ }
+ }
+ return false
+ }
+
+ for _, dir := range dirs {
+ if err := Mkdir(filepath.Join(tempDir, dir), 0777); err != nil {
+ t.Fatal(err)
+ }
+ }
+ for _, dir := range readonly {
+ d := filepath.Join(tempDir, dir)
+ if err := Chmod(d, 0555); err != nil {
+ t.Fatal(err)
+ }
+
+ // Defer changing the mode back so that the deferred
+ // RemoveAll(tempDir) can succeed.
+ defer Chmod(d, 0777)
+ }
+
+ err := RemoveAll(tempDir)
+ if err == nil {
+ t.Fatal("RemoveAll succeeded unexpectedly")
+ }
+
+ // The error should be of type *PathError.
+ // see issue 30491 for details.
+ if pathErr, ok := err.(*PathError); ok {
+ want := filepath.Join(tempDir, "b", "y")
+ if pathErr.Path != want {
+ t.Errorf("RemoveAll(%q): err.Path=%q, want %q", tempDir, pathErr.Path, want)
+ }
+ } else {
+ t.Errorf("RemoveAll(%q): error has type %T, want *fs.PathError", tempDir, err)
+ }
+
+ for _, dir := range dirs {
+ _, err := Stat(filepath.Join(tempDir, dir))
+ if inReadonly(dir) {
+ if err != nil {
+ t.Errorf("file %q was deleted but should still exist", dir)
+ }
+ } else {
+ if err == nil {
+ t.Errorf("file %q still exists but should have been deleted", dir)
+ }
+ }
+ }
+}
+
+func TestRemoveUnreadableDir(t *testing.T) {
+ switch runtime.GOOS {
+ case "js":
+ t.Skipf("skipping test on %s", runtime.GOOS)
+ }
+
+ if Getuid() == 0 {
+ t.Skip("skipping test when running as root")
+ }
+
+ t.Parallel()
+
+ tempDir := t.TempDir()
+ target := filepath.Join(tempDir, "d0", "d1", "d2")
+ if err := MkdirAll(target, 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err := Chmod(target, 0300); err != nil {
+ t.Fatal(err)
+ }
+ if err := RemoveAll(filepath.Join(tempDir, "d0")); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// Issue 29921
+func TestRemoveAllWithMoreErrorThanReqSize(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+ t.Parallel()
+
+ tmpDir := t.TempDir()
+ path := filepath.Join(tmpDir, "_TestRemoveAllWithMoreErrorThanReqSize_")
+
+ // Make directory with 1025 read-only files.
+ if err := MkdirAll(path, 0777); err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
+ for i := 0; i < 1025; i++ {
+ fpath := filepath.Join(path, fmt.Sprintf("file%d", i))
+ fd, err := Create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ fd.Close()
+ }
+
+ // Make the parent directory read-only. On some platforms, this is what
+ // prevents Remove from removing the files within that directory.
+ if err := Chmod(path, 0555); err != nil {
+ t.Fatal(err)
+ }
+ defer Chmod(path, 0755)
+
+ // This call should not hang, even on a platform that disallows file deletion
+ // from read-only directories.
+ err := RemoveAll(path)
+
+ if Getuid() == 0 {
+ // On many platforms, root can remove files from read-only directories.
+ return
+ }
+ if err == nil {
+ if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" {
+ // Marking a directory as read-only in Windows does not prevent the RemoveAll
+ // from creating or removing files within it.
+ //
+ // For wasip1, there is no support for file permissions so we cannot prevent
+ // RemoveAll from removing the files.
+ return
+ }
+ t.Fatal("RemoveAll(<read-only directory>) = nil; want error")
+ }
+
+ dir, err := Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer dir.Close()
+
+ names, _ := dir.Readdirnames(1025)
+ if len(names) < 1025 {
+ t.Fatalf("RemoveAll(<read-only directory>) unexpectedly removed %d read-only files from that directory", 1025-len(names))
+ }
+}
+
+func TestRemoveAllNoFcntl(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+
+ const env = "GO_TEST_REMOVE_ALL_NO_FCNTL"
+ if dir := Getenv(env); dir != "" {
+ if err := RemoveAll(dir); err != nil {
+ t.Fatal(err)
+ }
+ return
+ }
+
+ // Only test on Linux so that we can assume we have strace.
+ // The code is OS-independent so if it passes on Linux
+ // it should pass on other Unix systems.
+ if runtime.GOOS != "linux" {
+ t.Skipf("skipping test on %s", runtime.GOOS)
+ }
+ if _, err := Stat("/bin/strace"); err != nil {
+ t.Skipf("skipping test because /bin/strace not found: %v", err)
+ }
+ me, err := Executable()
+ if err != nil {
+ t.Skipf("skipping because Executable failed: %v", err)
+ }
+
+ // Create 100 directories.
+ // The test is that we can remove them without calling fcntl
+ // on each one.
+ tmpdir := t.TempDir()
+ subdir := filepath.Join(tmpdir, "subdir")
+ if err := Mkdir(subdir, 0o755); err != nil {
+ t.Fatal(err)
+ }
+ for i := 0; i < 100; i++ {
+ subsubdir := filepath.Join(subdir, strconv.Itoa(i))
+ if err := Mkdir(filepath.Join(subdir, strconv.Itoa(i)), 0o755); err != nil {
+ t.Fatal(err)
+ }
+ if err := WriteFile(filepath.Join(subsubdir, "file"), nil, 0o644); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ cmd := testenv.Command(t, "/bin/strace", "-f", "-e", "fcntl", me, "-test.run=TestRemoveAllNoFcntl")
+ cmd = testenv.CleanCmdEnv(cmd)
+ cmd.Env = append(cmd.Env, env+"="+subdir)
+ out, err := cmd.CombinedOutput()
+ if len(out) > 0 {
+ t.Logf("%s", out)
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if got := bytes.Count(out, []byte("fcntl")); got >= 100 {
+ t.Errorf("found %d fcntl calls, want < 100", got)
+ }
+}
diff --git a/src/os/signal/doc.go b/src/os/signal/doc.go
new file mode 100644
index 0000000..a2a7525
--- /dev/null
+++ b/src/os/signal/doc.go
@@ -0,0 +1,232 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package signal implements access to incoming signals.
+
+Signals are primarily used on Unix-like systems. For the use of this
+package on Windows and Plan 9, see below.
+
+# Types of signals
+
+The signals SIGKILL and SIGSTOP may not be caught by a program, and
+therefore cannot be affected by this package.
+
+Synchronous signals are signals triggered by errors in program
+execution: SIGBUS, SIGFPE, and SIGSEGV. These are only considered
+synchronous when caused by program execution, not when sent using
+[os.Process.Kill] or the kill program or some similar mechanism. In
+general, except as discussed below, Go programs will convert a
+synchronous signal into a run-time panic.
+
+The remaining signals are asynchronous signals. They are not
+triggered by program errors, but are instead sent from the kernel or
+from some other program.
+
+Of the asynchronous signals, the SIGHUP signal is sent when a program
+loses its controlling terminal. The SIGINT signal is sent when the
+user at the controlling terminal presses the interrupt character,
+which by default is ^C (Control-C). The SIGQUIT signal is sent when
+the user at the controlling terminal presses the quit character, which
+by default is ^\ (Control-Backslash). In general you can cause a
+program to simply exit by pressing ^C, and you can cause it to exit
+with a stack dump by pressing ^\.
+
+# Default behavior of signals in Go programs
+
+By default, a synchronous signal is converted into a run-time panic. A
+SIGHUP, SIGINT, or SIGTERM signal causes the program to exit. A
+SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGSTKFLT, SIGEMT, or SIGSYS signal
+causes the program to exit with a stack dump. A SIGTSTP, SIGTTIN, or
+SIGTTOU signal gets the system default behavior (these signals are
+used by the shell for job control). The SIGPROF signal is handled
+directly by the Go runtime to implement runtime.CPUProfile. Other
+signals will be caught but no action will be taken.
+
+If the Go program is started with either SIGHUP or SIGINT ignored
+(signal handler set to SIG_IGN), they will remain ignored.
+
+If the Go program is started with a non-empty signal mask, that will
+generally be honored. However, some signals are explicitly unblocked:
+the synchronous signals, SIGILL, SIGTRAP, SIGSTKFLT, SIGCHLD, SIGPROF,
+and, on Linux, signals 32 (SIGCANCEL) and 33 (SIGSETXID)
+(SIGCANCEL and SIGSETXID are used internally by glibc). Subprocesses
+started by [os.Exec], or by [os/exec], will inherit the
+modified signal mask.
+
+# Changing the behavior of signals in Go programs
+
+The functions in this package allow a program to change the way Go
+programs handle signals.
+
+Notify disables the default behavior for a given set of asynchronous
+signals and instead delivers them over one or more registered
+channels. Specifically, it applies to the signals SIGHUP, SIGINT,
+SIGQUIT, SIGABRT, and SIGTERM. It also applies to the job control
+signals SIGTSTP, SIGTTIN, and SIGTTOU, in which case the system
+default behavior does not occur. It also applies to some signals that
+otherwise cause no action: SIGUSR1, SIGUSR2, SIGPIPE, SIGALRM,
+SIGCHLD, SIGCONT, SIGURG, SIGXCPU, SIGXFSZ, SIGVTALRM, SIGWINCH,
+SIGIO, SIGPWR, SIGSYS, SIGINFO, SIGTHR, SIGWAITING, SIGLWP, SIGFREEZE,
+SIGTHAW, SIGLOST, SIGXRES, SIGJVM1, SIGJVM2, and any real time signals
+used on the system. Note that not all of these signals are available
+on all systems.
+
+If the program was started with SIGHUP or SIGINT ignored, and Notify
+is called for either signal, a signal handler will be installed for
+that signal and it will no longer be ignored. If, later, Reset or
+Ignore is called for that signal, or Stop is called on all channels
+passed to Notify for that signal, the signal will once again be
+ignored. Reset will restore the system default behavior for the
+signal, while Ignore will cause the system to ignore the signal
+entirely.
+
+If the program is started with a non-empty signal mask, some signals
+will be explicitly unblocked as described above. If Notify is called
+for a blocked signal, it will be unblocked. If, later, Reset is
+called for that signal, or Stop is called on all channels passed to
+Notify for that signal, the signal will once again be blocked.
+
+# SIGPIPE
+
+When a Go program writes to a broken pipe, the kernel will raise a
+SIGPIPE signal.
+
+If the program has not called Notify to receive SIGPIPE signals, then
+the behavior depends on the file descriptor number. A write to a
+broken pipe on file descriptors 1 or 2 (standard output or standard
+error) will cause the program to exit with a SIGPIPE signal. A write
+to a broken pipe on some other file descriptor will take no action on
+the SIGPIPE signal, and the write will fail with an EPIPE error.
+
+If the program has called Notify to receive SIGPIPE signals, the file
+descriptor number does not matter. The SIGPIPE signal will be
+delivered to the Notify channel, and the write will fail with an EPIPE
+error.
+
+This means that, by default, command line programs will behave like
+typical Unix command line programs, while other programs will not
+crash with SIGPIPE when writing to a closed network connection.
+
+# Go programs that use cgo or SWIG
+
+In a Go program that includes non-Go code, typically C/C++ code
+accessed using cgo or SWIG, Go's startup code normally runs first. It
+configures the signal handlers as expected by the Go runtime, before
+the non-Go startup code runs. If the non-Go startup code wishes to
+install its own signal handlers, it must take certain steps to keep Go
+working well. This section documents those steps and the overall
+effect changes to signal handler settings by the non-Go code can have
+on Go programs. In rare cases, the non-Go code may run before the Go
+code, in which case the next section also applies.
+
+If the non-Go code called by the Go program does not change any signal
+handlers or masks, then the behavior is the same as for a pure Go
+program.
+
+If the non-Go code installs any signal handlers, it must use the
+SA_ONSTACK flag with sigaction. Failing to do so is likely to cause
+the program to crash if the signal is received. Go programs routinely
+run with a limited stack, and therefore set up an alternate signal
+stack.
+
+If the non-Go code installs a signal handler for any of the
+synchronous signals (SIGBUS, SIGFPE, SIGSEGV), then it should record
+the existing Go signal handler. If those signals occur while
+executing Go code, it should invoke the Go signal handler (whether the
+signal occurs while executing Go code can be determined by looking at
+the PC passed to the signal handler). Otherwise some Go run-time
+panics will not occur as expected.
+
+If the non-Go code installs a signal handler for any of the
+asynchronous signals, it may invoke the Go signal handler or not as it
+chooses. Naturally, if it does not invoke the Go signal handler, the
+Go behavior described above will not occur. This can be an issue with
+the SIGPROF signal in particular.
+
+The non-Go code should not change the signal mask on any threads
+created by the Go runtime. If the non-Go code starts new threads of
+its own, it may set the signal mask as it pleases.
+
+If the non-Go code starts a new thread, changes the signal mask, and
+then invokes a Go function in that thread, the Go runtime will
+automatically unblock certain signals: the synchronous signals,
+SIGILL, SIGTRAP, SIGSTKFLT, SIGCHLD, SIGPROF, SIGCANCEL, and
+SIGSETXID. When the Go function returns, the non-Go signal mask will
+be restored.
+
+If the Go signal handler is invoked on a non-Go thread not running Go
+code, the handler generally forwards the signal to the non-Go code, as
+follows. If the signal is SIGPROF, the Go handler does
+nothing. Otherwise, the Go handler removes itself, unblocks the
+signal, and raises it again, to invoke any non-Go handler or default
+system handler. If the program does not exit, the Go handler then
+reinstalls itself and continues execution of the program.
+
+If a SIGPIPE signal is received, the Go program will invoke the
+special handling described above if the SIGPIPE is received on a Go
+thread. If the SIGPIPE is received on a non-Go thread the signal will
+be forwarded to the non-Go handler, if any; if there is none the
+default system handler will cause the program to terminate.
+
+# Non-Go programs that call Go code
+
+When Go code is built with options like -buildmode=c-shared, it will
+be run as part of an existing non-Go program. The non-Go code may
+have already installed signal handlers when the Go code starts (that
+may also happen in unusual cases when using cgo or SWIG; in that case,
+the discussion here applies). For -buildmode=c-archive the Go runtime
+will initialize signals at global constructor time. For
+-buildmode=c-shared the Go runtime will initialize signals when the
+shared library is loaded.
+
+If the Go runtime sees an existing signal handler for the SIGCANCEL or
+SIGSETXID signals (which are used only on Linux), it will turn on
+the SA_ONSTACK flag and otherwise keep the signal handler.
+
+For the synchronous signals and SIGPIPE, the Go runtime will install a
+signal handler. It will save any existing signal handler. If a
+synchronous signal arrives while executing non-Go code, the Go runtime
+will invoke the existing signal handler instead of the Go signal
+handler.
+
+Go code built with -buildmode=c-archive or -buildmode=c-shared will
+not install any other signal handlers by default. If there is an
+existing signal handler, the Go runtime will turn on the SA_ONSTACK
+flag and otherwise keep the signal handler. If Notify is called for an
+asynchronous signal, a Go signal handler will be installed for that
+signal. If, later, Reset is called for that signal, the original
+handling for that signal will be reinstalled, restoring the non-Go
+signal handler if any.
+
+Go code built without -buildmode=c-archive or -buildmode=c-shared will
+install a signal handler for the asynchronous signals listed above,
+and save any existing signal handler. If a signal is delivered to a
+non-Go thread, it will act as described above, except that if there is
+an existing non-Go signal handler, that handler will be installed
+before raising the signal.
+
+# Windows
+
+On Windows a ^C (Control-C) or ^BREAK (Control-Break) normally cause
+the program to exit. If Notify is called for [os.Interrupt], ^C or ^BREAK
+will cause [os.Interrupt] to be sent on the channel, and the program will
+not exit. If Reset is called, or Stop is called on all channels passed
+to Notify, then the default behavior will be restored.
+
+Additionally, if Notify is called, and Windows sends CTRL_CLOSE_EVENT,
+CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT to the process, Notify will
+return syscall.SIGTERM. Unlike Control-C and Control-Break, Notify does
+not change process behavior when either CTRL_CLOSE_EVENT,
+CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT is received - the process will
+still get terminated unless it exits. But receiving syscall.SIGTERM will
+give the process an opportunity to clean up before termination.
+
+# Plan 9
+
+On Plan 9, signals have type syscall.Note, which is a string. Calling
+Notify with a syscall.Note will cause that value to be sent on the
+channel when that string is posted as a note.
+*/
+package signal
diff --git a/src/os/signal/example_test.go b/src/os/signal/example_test.go
new file mode 100644
index 0000000..ecefc75
--- /dev/null
+++ b/src/os/signal/example_test.go
@@ -0,0 +1,38 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package signal_test
+
+import (
+ "fmt"
+ "os"
+ "os/signal"
+)
+
+func ExampleNotify() {
+ // Set up channel on which to send signal notifications.
+ // We must use a buffered channel or risk missing the signal
+ // if we're not ready to receive when the signal is sent.
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+
+ // Block until a signal is received.
+ s := <-c
+ fmt.Println("Got signal:", s)
+}
+
+func ExampleNotify_allSignals() {
+ // Set up channel on which to send signal notifications.
+ // We must use a buffered channel or risk missing the signal
+ // if we're not ready to receive when the signal is sent.
+ c := make(chan os.Signal, 1)
+
+ // Passing no signals to Notify means that
+ // all signals will be sent to the channel.
+ signal.Notify(c)
+
+ // Block until any signal is received.
+ s := <-c
+ fmt.Println("Got signal:", s)
+}
diff --git a/src/os/signal/example_unix_test.go b/src/os/signal/example_unix_test.go
new file mode 100644
index 0000000..b7047ac
--- /dev/null
+++ b/src/os/signal/example_unix_test.go
@@ -0,0 +1,47 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package signal_test
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "os"
+ "os/signal"
+ "time"
+)
+
+// This example passes a context with a signal to tell a blocking function that
+// it should abandon its work after a signal is received.
+func ExampleNotifyContext() {
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
+ defer stop()
+
+ p, err := os.FindProcess(os.Getpid())
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // On a Unix-like system, pressing Ctrl+C on a keyboard sends a
+ // SIGINT signal to the process of the program in execution.
+ //
+ // This example simulates that by sending a SIGINT signal to itself.
+ if err := p.Signal(os.Interrupt); err != nil {
+ log.Fatal(err)
+ }
+
+ select {
+ case <-time.After(time.Second):
+ fmt.Println("missed signal")
+ case <-ctx.Done():
+ fmt.Println(ctx.Err()) // prints "context canceled"
+ stop() // stop receiving signal notifications as soon as possible.
+ }
+
+ // Output:
+ // context canceled
+}
diff --git a/src/os/signal/sig.s b/src/os/signal/sig.s
new file mode 100644
index 0000000..12833a8
--- /dev/null
+++ b/src/os/signal/sig.s
@@ -0,0 +1,8 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// The runtime package uses //go:linkname to push a few functions into this
+// package but we still need a .s file so the Go tool does not pass -complete
+// to the go tool compile so the latter does not complain about Go functions
+// with no bodies.
diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go
new file mode 100644
index 0000000..4250a7e
--- /dev/null
+++ b/src/os/signal/signal.go
@@ -0,0 +1,334 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package signal
+
+import (
+ "context"
+ "os"
+ "sync"
+)
+
+var handlers struct {
+ sync.Mutex
+ // Map a channel to the signals that should be sent to it.
+ m map[chan<- os.Signal]*handler
+ // Map a signal to the number of channels receiving it.
+ ref [numSig]int64
+ // Map channels to signals while the channel is being stopped.
+ // Not a map because entries live here only very briefly.
+ // We need a separate container because we need m to correspond to ref
+ // at all times, and we also need to keep track of the *handler
+ // value for a channel being stopped. See the Stop function.
+ stopping []stopping
+}
+
+type stopping struct {
+ c chan<- os.Signal
+ h *handler
+}
+
+type handler struct {
+ mask [(numSig + 31) / 32]uint32
+}
+
+func (h *handler) want(sig int) bool {
+ return (h.mask[sig/32]>>uint(sig&31))&1 != 0
+}
+
+func (h *handler) set(sig int) {
+ h.mask[sig/32] |= 1 << uint(sig&31)
+}
+
+func (h *handler) clear(sig int) {
+ h.mask[sig/32] &^= 1 << uint(sig&31)
+}
+
+// Stop relaying the signals, sigs, to any channels previously registered to
+// receive them and either reset the signal handlers to their original values
+// (action=disableSignal) or ignore the signals (action=ignoreSignal).
+func cancel(sigs []os.Signal, action func(int)) {
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ remove := func(n int) {
+ var zerohandler handler
+
+ for c, h := range handlers.m {
+ if h.want(n) {
+ handlers.ref[n]--
+ h.clear(n)
+ if h.mask == zerohandler.mask {
+ delete(handlers.m, c)
+ }
+ }
+ }
+
+ action(n)
+ }
+
+ if len(sigs) == 0 {
+ for n := 0; n < numSig; n++ {
+ remove(n)
+ }
+ } else {
+ for _, s := range sigs {
+ remove(signum(s))
+ }
+ }
+}
+
+// Ignore causes the provided signals to be ignored. If they are received by
+// the program, nothing will happen. Ignore undoes the effect of any prior
+// calls to Notify for the provided signals.
+// If no signals are provided, all incoming signals will be ignored.
+func Ignore(sig ...os.Signal) {
+ cancel(sig, ignoreSignal)
+}
+
+// Ignored reports whether sig is currently ignored.
+func Ignored(sig os.Signal) bool {
+ sn := signum(sig)
+ return sn >= 0 && signalIgnored(sn)
+}
+
+var (
+ // watchSignalLoopOnce guards calling the conditionally
+ // initialized watchSignalLoop. If watchSignalLoop is non-nil,
+ // it will be run in a goroutine lazily once Notify is invoked.
+ // See Issue 21576.
+ watchSignalLoopOnce sync.Once
+ watchSignalLoop func()
+)
+
+// Notify causes package signal to relay incoming signals to c.
+// If no signals are provided, all incoming signals will be relayed to c.
+// Otherwise, just the provided signals will.
+//
+// Package signal will not block sending to c: the caller must ensure
+// that c has sufficient buffer space to keep up with the expected
+// signal rate. For a channel used for notification of just one signal value,
+// a buffer of size 1 is sufficient.
+//
+// It is allowed to call Notify multiple times with the same channel:
+// each call expands the set of signals sent to that channel.
+// The only way to remove signals from the set is to call Stop.
+//
+// It is allowed to call Notify multiple times with different channels
+// and the same signals: each channel receives copies of incoming
+// signals independently.
+func Notify(c chan<- os.Signal, sig ...os.Signal) {
+ if c == nil {
+ panic("os/signal: Notify using nil channel")
+ }
+
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ h := handlers.m[c]
+ if h == nil {
+ if handlers.m == nil {
+ handlers.m = make(map[chan<- os.Signal]*handler)
+ }
+ h = new(handler)
+ handlers.m[c] = h
+ }
+
+ add := func(n int) {
+ if n < 0 {
+ return
+ }
+ if !h.want(n) {
+ h.set(n)
+ if handlers.ref[n] == 0 {
+ enableSignal(n)
+
+ // The runtime requires that we enable a
+ // signal before starting the watcher.
+ watchSignalLoopOnce.Do(func() {
+ if watchSignalLoop != nil {
+ go watchSignalLoop()
+ }
+ })
+ }
+ handlers.ref[n]++
+ }
+ }
+
+ if len(sig) == 0 {
+ for n := 0; n < numSig; n++ {
+ add(n)
+ }
+ } else {
+ for _, s := range sig {
+ add(signum(s))
+ }
+ }
+}
+
+// Reset undoes the effect of any prior calls to Notify for the provided
+// signals.
+// If no signals are provided, all signal handlers will be reset.
+func Reset(sig ...os.Signal) {
+ cancel(sig, disableSignal)
+}
+
+// Stop causes package signal to stop relaying incoming signals to c.
+// It undoes the effect of all prior calls to Notify using c.
+// When Stop returns, it is guaranteed that c will receive no more signals.
+func Stop(c chan<- os.Signal) {
+ handlers.Lock()
+
+ h := handlers.m[c]
+ if h == nil {
+ handlers.Unlock()
+ return
+ }
+ delete(handlers.m, c)
+
+ for n := 0; n < numSig; n++ {
+ if h.want(n) {
+ handlers.ref[n]--
+ if handlers.ref[n] == 0 {
+ disableSignal(n)
+ }
+ }
+ }
+
+ // Signals will no longer be delivered to the channel.
+ // We want to avoid a race for a signal such as SIGINT:
+ // it should be either delivered to the channel,
+ // or the program should take the default action (that is, exit).
+ // To avoid the possibility that the signal is delivered,
+ // and the signal handler invoked, and then Stop deregisters
+ // the channel before the process function below has a chance
+ // to send it on the channel, put the channel on a list of
+ // channels being stopped and wait for signal delivery to
+ // quiesce before fully removing it.
+
+ handlers.stopping = append(handlers.stopping, stopping{c, h})
+
+ handlers.Unlock()
+
+ signalWaitUntilIdle()
+
+ handlers.Lock()
+
+ for i, s := range handlers.stopping {
+ if s.c == c {
+ handlers.stopping = append(handlers.stopping[:i], handlers.stopping[i+1:]...)
+ break
+ }
+ }
+
+ handlers.Unlock()
+}
+
+// Wait until there are no more signals waiting to be delivered.
+// Defined by the runtime package.
+func signalWaitUntilIdle()
+
+func process(sig os.Signal) {
+ n := signum(sig)
+ if n < 0 {
+ return
+ }
+
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ for c, h := range handlers.m {
+ if h.want(n) {
+ // send but do not block for it
+ select {
+ case c <- sig:
+ default:
+ }
+ }
+ }
+
+ // Avoid the race mentioned in Stop.
+ for _, d := range handlers.stopping {
+ if d.h.want(n) {
+ select {
+ case d.c <- sig:
+ default:
+ }
+ }
+ }
+}
+
+// NotifyContext returns a copy of the parent context that is marked done
+// (its Done channel is closed) when one of the listed signals arrives,
+// when the returned stop function is called, or when the parent context's
+// Done channel is closed, whichever happens first.
+//
+// The stop function unregisters the signal behavior, which, like signal.Reset,
+// may restore the default behavior for a given signal. For example, the default
+// behavior of a Go program receiving os.Interrupt is to exit. Calling
+// NotifyContext(parent, os.Interrupt) will change the behavior to cancel
+// the returned context. Future interrupts received will not trigger the default
+// (exit) behavior until the returned stop function is called.
+//
+// The stop function releases resources associated with it, so code should
+// call stop as soon as the operations running in this Context complete and
+// signals no longer need to be diverted to the context.
+func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc) {
+ ctx, cancel := context.WithCancel(parent)
+ c := &signalCtx{
+ Context: ctx,
+ cancel: cancel,
+ signals: signals,
+ }
+ c.ch = make(chan os.Signal, 1)
+ Notify(c.ch, c.signals...)
+ if ctx.Err() == nil {
+ go func() {
+ select {
+ case <-c.ch:
+ c.cancel()
+ case <-c.Done():
+ }
+ }()
+ }
+ return c, c.stop
+}
+
+type signalCtx struct {
+ context.Context
+
+ cancel context.CancelFunc
+ signals []os.Signal
+ ch chan os.Signal
+}
+
+func (c *signalCtx) stop() {
+ c.cancel()
+ Stop(c.ch)
+}
+
+type stringer interface {
+ String() string
+}
+
+func (c *signalCtx) String() string {
+ var buf []byte
+ // We know that the type of c.Context is context.cancelCtx, and we know that the
+ // String method of cancelCtx returns a string that ends with ".WithCancel".
+ name := c.Context.(stringer).String()
+ name = name[:len(name)-len(".WithCancel")]
+ buf = append(buf, "signal.NotifyContext("+name...)
+ if len(c.signals) != 0 {
+ buf = append(buf, ", ["...)
+ for i, s := range c.signals {
+ buf = append(buf, s.String()...)
+ if i != len(c.signals)-1 {
+ buf = append(buf, ' ')
+ }
+ }
+ buf = append(buf, ']')
+ }
+ buf = append(buf, ')')
+ return string(buf)
+}
diff --git a/src/os/signal/signal_cgo_test.go b/src/os/signal/signal_cgo_test.go
new file mode 100644
index 0000000..ac59215
--- /dev/null
+++ b/src/os/signal/signal_cgo_test.go
@@ -0,0 +1,350 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (darwin || dragonfly || freebsd || (linux && !android) || netbsd || openbsd) && cgo
+
+// Note that this test does not work on Solaris: issue #22849.
+// Don't run the test on Android because at least some versions of the
+// C library do not define the posix_openpt function.
+
+package signal_test
+
+import (
+ "context"
+ "encoding/binary"
+ "fmt"
+ "internal/testpty"
+ "os"
+ "os/exec"
+ "os/signal"
+ "runtime"
+ "strconv"
+ "syscall"
+ "testing"
+ "time"
+ "unsafe"
+)
+
+const (
+ ptyFD = 3 // child end of pty.
+ controlFD = 4 // child end of control pipe.
+)
+
+// TestTerminalSignal tests that read from a pseudo-terminal does not return an
+// error if the process is SIGSTOP'd and put in the background during the read.
+//
+// This test simulates stopping a Go process running in a shell with ^Z and
+// then resuming with `fg`.
+//
+// This is a regression test for https://go.dev/issue/22838. On Darwin, PTY
+// reads return EINTR when this occurs, and Go should automatically retry.
+func TestTerminalSignal(t *testing.T) {
+ // This test simulates stopping a Go process running in a shell with ^Z
+ // and then resuming with `fg`. This sounds simple, but is actually
+ // quite complicated.
+ //
+ // In principle, what we are doing is:
+ // 1. Creating a new PTY parent/child FD pair.
+ // 2. Create a child that is in the foreground process group of the PTY, and read() from that process.
+ // 3. Stop the child with ^Z.
+ // 4. Take over as foreground process group of the PTY from the parent.
+ // 5. Make the child foreground process group again.
+ // 6. Continue the child.
+ //
+ // On Darwin, step 4 results in the read() returning EINTR once the
+ // process continues. internal/poll should automatically retry the
+ // read.
+ //
+ // These steps are complicated by the rules around foreground process
+ // groups. A process group cannot be foreground if it is "orphaned",
+ // unless it masks SIGTTOU. i.e., to be foreground the process group
+ // must have a parent process group in the same session or mask SIGTTOU
+ // (which we do). An orphaned process group cannot receive
+ // terminal-generated SIGTSTP at all.
+ //
+ // Achieving this requires three processes total:
+ // - Top-level process: this is the main test process and creates the
+ // pseudo-terminal.
+ // - GO_TEST_TERMINAL_SIGNALS=1: This process creates a new process
+ // group and session. The PTY is the controlling terminal for this
+ // session. This process masks SIGTTOU, making it eligible to be a
+ // foreground process group. This process will take over as foreground
+ // from subprocess 2 (step 4 above).
+ // - GO_TEST_TERMINAL_SIGNALS=2: This process create a child process
+ // group of subprocess 1, and is the original foreground process group
+ // for the PTY. This subprocess is the one that is SIGSTOP'd.
+
+ if runtime.GOOS == "dragonfly" {
+ t.Skip("skipping: wait hangs on dragonfly; see https://go.dev/issue/56132")
+ }
+
+ scale := 1
+ if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
+ if sc, err := strconv.Atoi(s); err == nil {
+ scale = sc
+ }
+ }
+ pause := time.Duration(scale) * 10 * time.Millisecond
+
+ lvl := os.Getenv("GO_TEST_TERMINAL_SIGNALS")
+ switch lvl {
+ case "":
+ // Main test process, run code below.
+ break
+ case "1":
+ runSessionLeader(pause)
+ panic("unreachable")
+ case "2":
+ runStoppingChild()
+ panic("unreachable")
+ default:
+ fmt.Fprintf(os.Stderr, "unknown subprocess level %s\n", lvl)
+ os.Exit(1)
+ }
+
+ t.Parallel()
+
+ pty, procTTYName, err := testpty.Open()
+ if err != nil {
+ ptyErr := err.(*testpty.PtyError)
+ if ptyErr.FuncName == "posix_openpt" && ptyErr.Errno == syscall.EACCES {
+ t.Skip("posix_openpt failed with EACCES, assuming chroot and skipping")
+ }
+ t.Fatal(err)
+ }
+ defer pty.Close()
+ procTTY, err := os.OpenFile(procTTYName, os.O_RDWR, 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer procTTY.Close()
+
+ // Control pipe. GO_TEST_TERMINAL_SIGNALS=2 send the PID of
+ // GO_TEST_TERMINAL_SIGNALS=3 here. After SIGSTOP, it also writes a
+ // byte to indicate that the foreground cycling is complete.
+ controlR, controlW, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 100*time.Second)
+ defer cancel()
+ cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestTerminalSignal")
+ cmd.Env = append(os.Environ(), "GO_TEST_TERMINAL_SIGNALS=1")
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout // for logging
+ cmd.Stderr = os.Stderr
+ cmd.ExtraFiles = []*os.File{procTTY, controlW}
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Setsid: true,
+ Setctty: true,
+ Ctty: ptyFD,
+ }
+
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := procTTY.Close(); err != nil {
+ t.Errorf("closing procTTY: %v", err)
+ }
+
+ if err := controlW.Close(); err != nil {
+ t.Errorf("closing controlW: %v", err)
+ }
+
+ // Wait for first child to send the second child's PID.
+ b := make([]byte, 8)
+ n, err := controlR.Read(b)
+ if err != nil {
+ t.Fatalf("error reading child pid: %v\n", err)
+ }
+ if n != 8 {
+ t.Fatalf("unexpected short read n = %d\n", n)
+ }
+ pid := binary.LittleEndian.Uint64(b[:])
+ process, err := os.FindProcess(int(pid))
+ if err != nil {
+ t.Fatalf("unable to find child process: %v", err)
+ }
+
+ // Wait for the third child to write a byte indicating that it is
+ // entering the read.
+ b = make([]byte, 1)
+ _, err = pty.Read(b)
+ if err != nil {
+ t.Fatalf("error reading from child: %v", err)
+ }
+
+ // Give the program time to enter the read call.
+ // It doesn't matter much if we occasionally don't wait long enough;
+ // we won't be testing what we want to test, but the overall test
+ // will pass.
+ time.Sleep(pause)
+
+ t.Logf("Sending ^Z...")
+
+ // Send a ^Z to stop the program.
+ if _, err := pty.Write([]byte{26}); err != nil {
+ t.Fatalf("writing ^Z to pty: %v", err)
+ }
+
+ // Wait for subprocess 1 to cycle the foreground process group.
+ if _, err := controlR.Read(b); err != nil {
+ t.Fatalf("error reading readiness: %v", err)
+ }
+
+ t.Logf("Sending SIGCONT...")
+
+ // Restart the stopped program.
+ if err := process.Signal(syscall.SIGCONT); err != nil {
+ t.Fatalf("Signal(SIGCONT) got err %v want nil", err)
+ }
+
+ // Write some data for the program to read, which should cause it to
+ // exit.
+ if _, err := pty.Write([]byte{'\n'}); err != nil {
+ t.Fatalf("writing %q to pty: %v", "\n", err)
+ }
+
+ t.Logf("Waiting for exit...")
+
+ if err = cmd.Wait(); err != nil {
+ t.Errorf("subprogram failed: %v", err)
+ }
+}
+
+// GO_TEST_TERMINAL_SIGNALS=1 subprocess above.
+func runSessionLeader(pause time.Duration) {
+ // "Attempts to use tcsetpgrp() from a process which is a
+ // member of a background process group on a fildes associated
+ // with its controlling terminal shall cause the process group
+ // to be sent a SIGTTOU signal. If the calling thread is
+ // blocking SIGTTOU signals or the process is ignoring SIGTTOU
+ // signals, the process shall be allowed to perform the
+ // operation, and no signal is sent."
+ // -https://pubs.opengroup.org/onlinepubs/9699919799/functions/tcsetpgrp.html
+ //
+ // We are changing the terminal to put us in the foreground, so
+ // we must ignore SIGTTOU. We are also an orphaned process
+ // group (see above), so we must mask SIGTTOU to be eligible to
+ // become foreground at all.
+ signal.Ignore(syscall.SIGTTOU)
+
+ pty := os.NewFile(ptyFD, "pty")
+ controlW := os.NewFile(controlFD, "control-pipe")
+
+ // Slightly shorter timeout than in the parent.
+ ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
+ defer cancel()
+ cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=TestTerminalSignal")
+ cmd.Env = append(os.Environ(), "GO_TEST_TERMINAL_SIGNALS=2")
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ cmd.ExtraFiles = []*os.File{pty}
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Foreground: true,
+ Ctty: ptyFD,
+ }
+ if err := cmd.Start(); err != nil {
+ fmt.Fprintf(os.Stderr, "error starting second subprocess: %v\n", err)
+ os.Exit(1)
+ }
+
+ fn := func() error {
+ var b [8]byte
+ binary.LittleEndian.PutUint64(b[:], uint64(cmd.Process.Pid))
+ _, err := controlW.Write(b[:])
+ if err != nil {
+ return fmt.Errorf("error writing child pid: %w", err)
+ }
+
+ // Wait for stop.
+ var status syscall.WaitStatus
+ var errno syscall.Errno
+ for {
+ _, _, errno = syscall.Syscall6(syscall.SYS_WAIT4, uintptr(cmd.Process.Pid), uintptr(unsafe.Pointer(&status)), syscall.WUNTRACED, 0, 0, 0)
+ if errno != syscall.EINTR {
+ break
+ }
+ }
+ if errno != 0 {
+ return fmt.Errorf("error waiting for stop: %w", errno)
+ }
+
+ if !status.Stopped() {
+ return fmt.Errorf("unexpected wait status: %v", status)
+ }
+
+ // Take TTY.
+ pgrp := int32(syscall.Getpgrp()) // assume that pid_t is int32
+ _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, ptyFD, syscall.TIOCSPGRP, uintptr(unsafe.Pointer(&pgrp)))
+ if errno != 0 {
+ return fmt.Errorf("error setting tty process group: %w", errno)
+ }
+
+ // Give the kernel time to potentially wake readers and have
+ // them return EINTR (darwin does this).
+ time.Sleep(pause)
+
+ // Give TTY back.
+ pid := int32(cmd.Process.Pid) // assume that pid_t is int32
+ _, _, errno = syscall.Syscall(syscall.SYS_IOCTL, ptyFD, syscall.TIOCSPGRP, uintptr(unsafe.Pointer(&pid)))
+ if errno != 0 {
+ return fmt.Errorf("error setting tty process group back: %w", errno)
+ }
+
+ // Report that we are done and SIGCONT can be sent. Note that
+ // the actual byte we send doesn't matter.
+ if _, err := controlW.Write(b[:1]); err != nil {
+ return fmt.Errorf("error writing readiness: %w", err)
+ }
+
+ return nil
+ }
+
+ err := fn()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "session leader error: %v\n", err)
+ cmd.Process.Kill()
+ // Wait for exit below.
+ }
+
+ werr := cmd.Wait()
+ if werr != nil {
+ fmt.Fprintf(os.Stderr, "error running second subprocess: %v\n", err)
+ }
+
+ if err != nil || werr != nil {
+ os.Exit(1)
+ }
+
+ os.Exit(0)
+}
+
+// GO_TEST_TERMINAL_SIGNALS=2 subprocess above.
+func runStoppingChild() {
+ pty := os.NewFile(ptyFD, "pty")
+
+ var b [1]byte
+ if _, err := pty.Write(b[:]); err != nil {
+ fmt.Fprintf(os.Stderr, "error writing byte to PTY: %v\n", err)
+ os.Exit(1)
+ }
+
+ _, err := pty.Read(b[:])
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ if b[0] == '\n' {
+ // This is what we expect
+ fmt.Println("read newline")
+ } else {
+ fmt.Fprintf(os.Stderr, "read 1 unexpected byte: %q\n", b)
+ os.Exit(1)
+ }
+ os.Exit(0)
+}
diff --git a/src/os/signal/signal_linux_test.go b/src/os/signal/signal_linux_test.go
new file mode 100644
index 0000000..f70f108
--- /dev/null
+++ b/src/os/signal/signal_linux_test.go
@@ -0,0 +1,42 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux
+
+package signal
+
+import (
+ "os"
+ "syscall"
+ "testing"
+ "time"
+)
+
+const prSetKeepCaps = 8
+
+// This test validates that syscall.AllThreadsSyscall() can reliably
+// reach all 'm' (threads) of the nocgo runtime even when one thread
+// is blocked waiting to receive signals from the kernel. This monitors
+// for a regression vs. the fix for #43149.
+func TestAllThreadsSyscallSignals(t *testing.T) {
+ if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, prSetKeepCaps, 0, 0); err == syscall.ENOTSUP {
+ t.Skip("AllThreadsSyscall disabled with cgo")
+ }
+
+ sig := make(chan os.Signal, 1)
+ Notify(sig, os.Interrupt)
+
+ for i := 0; i <= 100; i++ {
+ if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, prSetKeepCaps, uintptr(i&1), 0); errno != 0 {
+ t.Fatalf("[%d] failed to set KEEP_CAPS=%d: %v", i, i&1, errno)
+ }
+ }
+
+ select {
+ case <-time.After(10 * time.Millisecond):
+ case <-sig:
+ t.Fatal("unexpected signal")
+ }
+ Stop(sig)
+}
diff --git a/src/os/signal/signal_plan9.go b/src/os/signal/signal_plan9.go
new file mode 100644
index 0000000..7d48715
--- /dev/null
+++ b/src/os/signal/signal_plan9.go
@@ -0,0 +1,64 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package signal
+
+import (
+ "os"
+ "syscall"
+)
+
+var sigtab = make(map[os.Signal]int)
+
+// Defined by the runtime package.
+func signal_disable(uint32)
+func signal_enable(uint32)
+func signal_ignore(uint32)
+func signal_ignored(uint32) bool
+func signal_recv() string
+
+func init() {
+ watchSignalLoop = loop
+}
+
+func loop() {
+ for {
+ process(syscall.Note(signal_recv()))
+ }
+}
+
+const numSig = 256
+
+func signum(sig os.Signal) int {
+ switch sig := sig.(type) {
+ case syscall.Note:
+ n, ok := sigtab[sig]
+ if !ok {
+ n = len(sigtab) + 1
+ if n > numSig {
+ return -1
+ }
+ sigtab[sig] = n
+ }
+ return n
+ default:
+ return -1
+ }
+}
+
+func enableSignal(sig int) {
+ signal_enable(uint32(sig))
+}
+
+func disableSignal(sig int) {
+ signal_disable(uint32(sig))
+}
+
+func ignoreSignal(sig int) {
+ signal_ignore(uint32(sig))
+}
+
+func signalIgnored(sig int) bool {
+ return signal_ignored(uint32(sig))
+}
diff --git a/src/os/signal/signal_plan9_test.go b/src/os/signal/signal_plan9_test.go
new file mode 100644
index 0000000..8357199
--- /dev/null
+++ b/src/os/signal/signal_plan9_test.go
@@ -0,0 +1,167 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package signal
+
+import (
+ "internal/itoa"
+ "os"
+ "runtime"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) {
+ select {
+ case s := <-c:
+ if s != sig {
+ t.Fatalf("signal was %v, want %v", s, sig)
+ }
+ case <-time.After(1 * time.Second):
+ t.Fatalf("timeout waiting for %v", sig)
+ }
+}
+
+// Test that basic signal handling works.
+func TestSignal(t *testing.T) {
+ // Ask for hangup
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.Note("hangup"))
+ defer Stop(c)
+
+ // Send this process a hangup
+ t.Logf("hangup...")
+ postNote(syscall.Getpid(), "hangup")
+ waitSig(t, c, syscall.Note("hangup"))
+
+ // Ask for everything we can get.
+ c1 := make(chan os.Signal, 1)
+ Notify(c1)
+
+ // Send this process an alarm
+ t.Logf("alarm...")
+ postNote(syscall.Getpid(), "alarm")
+ waitSig(t, c1, syscall.Note("alarm"))
+
+ // Send two more hangups, to make sure that
+ // they get delivered on c1 and that not reading
+ // from c does not block everything.
+ t.Logf("hangup...")
+ postNote(syscall.Getpid(), "hangup")
+ waitSig(t, c1, syscall.Note("hangup"))
+ t.Logf("hangup...")
+ postNote(syscall.Getpid(), "hangup")
+ waitSig(t, c1, syscall.Note("hangup"))
+
+ // The first SIGHUP should be waiting for us on c.
+ waitSig(t, c, syscall.Note("hangup"))
+}
+
+func TestStress(t *testing.T) {
+ dur := 3 * time.Second
+ if testing.Short() {
+ dur = 100 * time.Millisecond
+ }
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+ done := make(chan bool)
+ finished := make(chan bool)
+ go func() {
+ sig := make(chan os.Signal, 1)
+ Notify(sig, syscall.Note("alarm"))
+ defer Stop(sig)
+ Loop:
+ for {
+ select {
+ case <-sig:
+ case <-done:
+ break Loop
+ }
+ }
+ finished <- true
+ }()
+ go func() {
+ Loop:
+ for {
+ select {
+ case <-done:
+ break Loop
+ default:
+ postNote(syscall.Getpid(), "alarm")
+ runtime.Gosched()
+ }
+ }
+ finished <- true
+ }()
+ time.Sleep(dur)
+ close(done)
+ <-finished
+ <-finished
+ // When run with 'go test -cpu=1,2,4' alarm from this test can slip
+ // into subsequent TestSignal() causing failure.
+ // Sleep for a while to reduce the possibility of the failure.
+ time.Sleep(10 * time.Millisecond)
+}
+
+// Test that Stop cancels the channel's registrations.
+func TestStop(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+ sigs := []string{
+ "alarm",
+ "hangup",
+ }
+
+ for _, sig := range sigs {
+ // Send the signal.
+ // If it's alarm, we should not see it.
+ // If it's hangup, maybe we'll die. Let the flag tell us what to do.
+ if sig != "hangup" {
+ postNote(syscall.Getpid(), sig)
+ }
+ time.Sleep(100 * time.Millisecond)
+
+ // Ask for signal
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.Note(sig))
+ defer Stop(c)
+
+ // Send this process that signal
+ postNote(syscall.Getpid(), sig)
+ waitSig(t, c, syscall.Note(sig))
+
+ Stop(c)
+ select {
+ case s := <-c:
+ t.Fatalf("unexpected signal %v", s)
+ case <-time.After(100 * time.Millisecond):
+ // nothing to read - good
+ }
+
+ // Send the signal.
+ // If it's alarm, we should not see it.
+ // If it's hangup, maybe we'll die. Let the flag tell us what to do.
+ if sig != "hangup" {
+ postNote(syscall.Getpid(), sig)
+ }
+
+ select {
+ case s := <-c:
+ t.Fatalf("unexpected signal %v", s)
+ case <-time.After(100 * time.Millisecond):
+ // nothing to read - good
+ }
+ }
+}
+
+func postNote(pid int, note string) error {
+ f, err := os.OpenFile("/proc/"+itoa.Itoa(pid)+"/note", os.O_WRONLY, 0)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ _, err = f.Write([]byte(note))
+ return err
+}
diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go
new file mode 100644
index 0000000..c7c42ed
--- /dev/null
+++ b/src/os/signal/signal_test.go
@@ -0,0 +1,932 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package signal
+
+import (
+ "bytes"
+ "context"
+ "flag"
+ "fmt"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "runtime"
+ "runtime/trace"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+)
+
+// settleTime is an upper bound on how long we expect signals to take to be
+// delivered. Lower values make the test faster, but also flakier — especially
+// on heavily loaded systems.
+//
+// The current value is set based on flakes observed in the Go builders.
+var settleTime = 100 * time.Millisecond
+
+// fatalWaitingTime is an absurdly long time to wait for signals to be
+// delivered but, using it, we (hopefully) eliminate test flakes on the
+// build servers. See #46736 for discussion.
+var fatalWaitingTime = 30 * time.Second
+
+func init() {
+ if testenv.Builder() == "solaris-amd64-oraclerel" {
+ // The solaris-amd64-oraclerel builder has been observed to time out in
+ // TestNohup even with a 250ms settle time.
+ //
+ // Use a much longer settle time on that builder to try to suss out whether
+ // the test is flaky due to builder slowness (which may mean we need a
+ // longer GO_TEST_TIMEOUT_SCALE) or due to a dropped signal (which may
+ // instead need a test-skip and upstream bug filed against the Solaris
+ // kernel).
+ //
+ // See https://golang.org/issue/33174.
+ settleTime = 5 * time.Second
+ } else if runtime.GOOS == "linux" && strings.HasPrefix(runtime.GOARCH, "ppc64") {
+ // Older linux kernels seem to have some hiccups delivering the signal
+ // in a timely manner on ppc64 and ppc64le. When running on a
+ // ppc64le/ubuntu 16.04/linux 4.4 host the time can vary quite
+ // substantially even on a idle system. 5 seconds is twice any value
+ // observed when running 10000 tests on such a system.
+ settleTime = 5 * time.Second
+ } else if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" {
+ if scale, err := strconv.Atoi(s); err == nil {
+ settleTime *= time.Duration(scale)
+ }
+ }
+}
+
+func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) {
+ t.Helper()
+ waitSig1(t, c, sig, false)
+}
+func waitSigAll(t *testing.T, c <-chan os.Signal, sig os.Signal) {
+ t.Helper()
+ waitSig1(t, c, sig, true)
+}
+
+func waitSig1(t *testing.T, c <-chan os.Signal, sig os.Signal, all bool) {
+ t.Helper()
+
+ // Sleep multiple times to give the kernel more tries to
+ // deliver the signal.
+ start := time.Now()
+ timer := time.NewTimer(settleTime / 10)
+ defer timer.Stop()
+ // If the caller notified for all signals on c, filter out SIGURG,
+ // which is used for runtime preemption and can come at unpredictable times.
+ // General user code should filter out all unexpected signals instead of just
+ // SIGURG, but since os/signal is tightly coupled to the runtime it seems
+ // appropriate to be stricter here.
+ for time.Since(start) < fatalWaitingTime {
+ select {
+ case s := <-c:
+ if s == sig {
+ return
+ }
+ if !all || s != syscall.SIGURG {
+ t.Fatalf("signal was %v, want %v", s, sig)
+ }
+ case <-timer.C:
+ timer.Reset(settleTime / 10)
+ }
+ }
+ t.Fatalf("timeout after %v waiting for %v", fatalWaitingTime, sig)
+}
+
+// quiesce waits until we can be reasonably confident that all pending signals
+// have been delivered by the OS.
+func quiesce() {
+ // The kernel will deliver a signal as a thread returns
+ // from a syscall. If the only active thread is sleeping,
+ // and the system is busy, the kernel may not get around
+ // to waking up a thread to catch the signal.
+ // We try splitting up the sleep to give the kernel
+ // many chances to deliver the signal.
+ start := time.Now()
+ for time.Since(start) < settleTime {
+ time.Sleep(settleTime / 10)
+ }
+}
+
+// Test that basic signal handling works.
+func TestSignal(t *testing.T) {
+ // Ask for SIGHUP
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGHUP)
+ defer Stop(c)
+
+ // Send this process a SIGHUP
+ t.Logf("sighup...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ waitSig(t, c, syscall.SIGHUP)
+
+ // Ask for everything we can get. The buffer size has to be
+ // more than 1, since the runtime might send SIGURG signals.
+ // Using 10 is arbitrary.
+ c1 := make(chan os.Signal, 10)
+ Notify(c1)
+ // Stop relaying the SIGURG signals. See #49724
+ Reset(syscall.SIGURG)
+ defer Stop(c1)
+
+ // Send this process a SIGWINCH
+ t.Logf("sigwinch...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
+ waitSigAll(t, c1, syscall.SIGWINCH)
+
+ // Send two more SIGHUPs, to make sure that
+ // they get delivered on c1 and that not reading
+ // from c does not block everything.
+ t.Logf("sighup...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ waitSigAll(t, c1, syscall.SIGHUP)
+ t.Logf("sighup...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ waitSigAll(t, c1, syscall.SIGHUP)
+
+ // The first SIGHUP should be waiting for us on c.
+ waitSig(t, c, syscall.SIGHUP)
+}
+
+func TestStress(t *testing.T) {
+ dur := 3 * time.Second
+ if testing.Short() {
+ dur = 100 * time.Millisecond
+ }
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+
+ sig := make(chan os.Signal, 1)
+ Notify(sig, syscall.SIGUSR1)
+
+ go func() {
+ stop := time.After(dur)
+ for {
+ select {
+ case <-stop:
+ // Allow enough time for all signals to be delivered before we stop
+ // listening for them.
+ quiesce()
+ Stop(sig)
+ // According to its documentation, “[w]hen Stop returns, it in
+ // guaranteed that c will receive no more signals.” So we can safely
+ // close sig here: if there is a send-after-close race here, that is a
+ // bug in Stop and we would like to detect it.
+ close(sig)
+ return
+
+ default:
+ syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
+ runtime.Gosched()
+ }
+ }
+ }()
+
+ for range sig {
+ // Receive signals until the sender closes sig.
+ }
+}
+
+func testCancel(t *testing.T, ignore bool) {
+ // Ask to be notified on c1 when a SIGWINCH is received.
+ c1 := make(chan os.Signal, 1)
+ Notify(c1, syscall.SIGWINCH)
+ defer Stop(c1)
+
+ // Ask to be notified on c2 when a SIGHUP is received.
+ c2 := make(chan os.Signal, 1)
+ Notify(c2, syscall.SIGHUP)
+ defer Stop(c2)
+
+ // Send this process a SIGWINCH and wait for notification on c1.
+ syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
+ waitSig(t, c1, syscall.SIGWINCH)
+
+ // Send this process a SIGHUP and wait for notification on c2.
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ waitSig(t, c2, syscall.SIGHUP)
+
+ // Ignore, or reset the signal handlers for, SIGWINCH and SIGHUP.
+ // Either way, this should undo both calls to Notify above.
+ if ignore {
+ Ignore(syscall.SIGWINCH, syscall.SIGHUP)
+ // Don't bother deferring a call to Reset: it is documented to undo Notify,
+ // but its documentation says nothing about Ignore, and (as of the time of
+ // writing) it empirically does not undo an Ignore.
+ } else {
+ Reset(syscall.SIGWINCH, syscall.SIGHUP)
+ }
+
+ // Send this process a SIGWINCH. It should be ignored.
+ syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
+
+ // If ignoring, Send this process a SIGHUP. It should be ignored.
+ if ignore {
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ }
+
+ quiesce()
+
+ select {
+ case s := <-c1:
+ t.Errorf("unexpected signal %v", s)
+ default:
+ // nothing to read - good
+ }
+
+ select {
+ case s := <-c2:
+ t.Errorf("unexpected signal %v", s)
+ default:
+ // nothing to read - good
+ }
+
+ // One or both of the signals may have been blocked for this process
+ // by the calling process.
+ // Discard any queued signals now to avoid interfering with other tests.
+ Notify(c1, syscall.SIGWINCH)
+ Notify(c2, syscall.SIGHUP)
+ quiesce()
+}
+
+// Test that Reset cancels registration for listed signals on all channels.
+func TestReset(t *testing.T) {
+ testCancel(t, false)
+}
+
+// Test that Ignore cancels registration for listed signals on all channels.
+func TestIgnore(t *testing.T) {
+ testCancel(t, true)
+}
+
+// Test that Ignored correctly detects changes to the ignored status of a signal.
+func TestIgnored(t *testing.T) {
+ // Ask to be notified on SIGWINCH.
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGWINCH)
+
+ // If we're being notified, then the signal should not be ignored.
+ if Ignored(syscall.SIGWINCH) {
+ t.Errorf("expected SIGWINCH to not be ignored.")
+ }
+ Stop(c)
+ Ignore(syscall.SIGWINCH)
+
+ // We're no longer paying attention to this signal.
+ if !Ignored(syscall.SIGWINCH) {
+ t.Errorf("expected SIGWINCH to be ignored when explicitly ignoring it.")
+ }
+
+ Reset()
+}
+
+var checkSighupIgnored = flag.Bool("check_sighup_ignored", false, "if true, TestDetectNohup will fail if SIGHUP is not ignored.")
+
+// Test that Ignored(SIGHUP) correctly detects whether it is being run under nohup.
+func TestDetectNohup(t *testing.T) {
+ if *checkSighupIgnored {
+ if !Ignored(syscall.SIGHUP) {
+ t.Fatal("SIGHUP is not ignored.")
+ } else {
+ t.Log("SIGHUP is ignored.")
+ }
+ } else {
+ defer Reset()
+ // Ugly: ask for SIGHUP so that child will not have no-hup set
+ // even if test is running under nohup environment.
+ // We have no intention of reading from c.
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGHUP)
+ if out, err := testenv.Command(t, os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput(); err == nil {
+ t.Errorf("ran test with -check_sighup_ignored and it succeeded: expected failure.\nOutput:\n%s", out)
+ }
+ Stop(c)
+
+ // Again, this time with nohup, assuming we can find it.
+ _, err := os.Stat("/usr/bin/nohup")
+ if err != nil {
+ t.Skip("cannot find nohup; skipping second half of test")
+ }
+ Ignore(syscall.SIGHUP)
+ os.Remove("nohup.out")
+ out, err := testenv.Command(t, "/usr/bin/nohup", os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput()
+
+ data, _ := os.ReadFile("nohup.out")
+ os.Remove("nohup.out")
+ if err != nil {
+ // nohup doesn't work on new LUCI darwin builders due to the
+ // type of launchd service the test run under. See
+ // https://go.dev/issue/63875.
+ if runtime.GOOS == "darwin" && strings.Contains(string(out), "nohup: can't detach from console: Inappropriate ioctl for device") {
+ t.Skip("Skipping nohup test due to darwin builder limitation. See https://go.dev/issue/63875.")
+ }
+
+ t.Errorf("ran test with -check_sighup_ignored under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", err, out, data)
+ }
+ }
+}
+
+var (
+ sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop")
+ dieFromSighup = flag.Bool("die_from_sighup", false, "wait to die from uncaught SIGHUP")
+)
+
+// Test that Stop cancels the channel's registrations.
+func TestStop(t *testing.T) {
+ sigs := []syscall.Signal{
+ syscall.SIGWINCH,
+ syscall.SIGHUP,
+ syscall.SIGUSR1,
+ }
+
+ for _, sig := range sigs {
+ sig := sig
+ t.Run(fmt.Sprint(sig), func(t *testing.T) {
+ // When calling Notify with a specific signal,
+ // independent signals should not interfere with each other,
+ // and we end up needing to wait for signals to quiesce a lot.
+ // Test the three different signals concurrently.
+ t.Parallel()
+
+ // If the signal is not ignored, send the signal before registering a
+ // channel to verify the behavior of the default Go handler.
+ // If it's SIGWINCH or SIGUSR1 we should not see it.
+ // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
+ mayHaveBlockedSignal := false
+ if !Ignored(sig) && (sig != syscall.SIGHUP || *sendUncaughtSighup == 1) {
+ syscall.Kill(syscall.Getpid(), sig)
+ quiesce()
+
+ // We don't know whether sig is blocked for this process; see
+ // https://golang.org/issue/38165. Assume that it could be.
+ mayHaveBlockedSignal = true
+ }
+
+ // Ask for signal
+ c := make(chan os.Signal, 1)
+ Notify(c, sig)
+
+ // Send this process the signal again.
+ syscall.Kill(syscall.Getpid(), sig)
+ waitSig(t, c, sig)
+
+ if mayHaveBlockedSignal {
+ // We may have received a queued initial signal in addition to the one
+ // that we sent after Notify. If so, waitSig may have observed that
+ // initial signal instead of the second one, and we may need to wait for
+ // the second signal to clear. Do that now.
+ quiesce()
+ select {
+ case <-c:
+ default:
+ }
+ }
+
+ // Stop watching for the signal and send it again.
+ // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
+ Stop(c)
+ if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 {
+ syscall.Kill(syscall.Getpid(), sig)
+ quiesce()
+
+ select {
+ case s := <-c:
+ t.Errorf("unexpected signal %v", s)
+ default:
+ // nothing to read - good
+ }
+
+ // If we're going to receive a signal, it has almost certainly been
+ // received by now. However, it may have been blocked for this process —
+ // we don't know. Explicitly unblock it and wait for it to clear now.
+ Notify(c, sig)
+ quiesce()
+ Stop(c)
+ }
+ })
+ }
+}
+
+// Test that when run under nohup, an uncaught SIGHUP does not kill the program.
+func TestNohup(t *testing.T) {
+ // When run without nohup, the test should crash on an uncaught SIGHUP.
+ // When run under nohup, the test should ignore uncaught SIGHUPs,
+ // because the runtime is not supposed to be listening for them.
+ // Either way, TestStop should still be able to catch them when it wants them
+ // and then when it stops wanting them, the original behavior should resume.
+ //
+ // send_uncaught_sighup=1 sends the SIGHUP before starting to listen for SIGHUPs.
+ // send_uncaught_sighup=2 sends the SIGHUP after no longer listening for SIGHUPs.
+ //
+ // Both should fail without nohup and succeed with nohup.
+
+ t.Run("uncaught", func(t *testing.T) {
+ // Ugly: ask for SIGHUP so that child will not have no-hup set
+ // even if test is running under nohup environment.
+ // We have no intention of reading from c.
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGHUP)
+ t.Cleanup(func() { Stop(c) })
+
+ var subTimeout time.Duration
+ if deadline, ok := t.Deadline(); ok {
+ subTimeout = time.Until(deadline)
+ subTimeout -= subTimeout / 10 // Leave 10% headroom for propagating output.
+ }
+ for i := 1; i <= 2; i++ {
+ i := i
+ t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+ t.Parallel()
+
+ args := []string{
+ "-test.v",
+ "-test.run=TestStop",
+ "-send_uncaught_sighup=" + strconv.Itoa(i),
+ "-die_from_sighup",
+ }
+ if subTimeout != 0 {
+ args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
+ }
+ out, err := testenv.Command(t, os.Args[0], args...).CombinedOutput()
+
+ if err == nil {
+ t.Errorf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, out)
+ } else {
+ t.Logf("test with -send_uncaught_sighup=%d failed as expected.\nError: %v\nOutput:\n%s", i, err, out)
+ }
+ })
+ }
+ })
+
+ t.Run("nohup", func(t *testing.T) {
+ // Skip the nohup test below when running in tmux on darwin, since nohup
+ // doesn't work correctly there. See issue #5135.
+ if runtime.GOOS == "darwin" && os.Getenv("TMUX") != "" {
+ t.Skip("Skipping nohup test due to running in tmux on darwin")
+ }
+
+ // Again, this time with nohup, assuming we can find it.
+ _, err := exec.LookPath("nohup")
+ if err != nil {
+ t.Skip("cannot find nohup; skipping second half of test")
+ }
+
+ var subTimeout time.Duration
+ if deadline, ok := t.Deadline(); ok {
+ subTimeout = time.Until(deadline)
+ subTimeout -= subTimeout / 10 // Leave 10% headroom for propagating output.
+ }
+ for i := 1; i <= 2; i++ {
+ i := i
+ t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+ t.Parallel()
+
+ // POSIX specifies that nohup writes to a file named nohup.out if standard
+ // output is a terminal. However, for an exec.Cmd, standard output is
+ // not a terminal — so we don't need to read or remove that file (and,
+ // indeed, cannot even create it if the current user is unable to write to
+ // GOROOT/src, such as when GOROOT is installed and owned by root).
+
+ args := []string{
+ os.Args[0],
+ "-test.v",
+ "-test.run=TestStop",
+ "-send_uncaught_sighup=" + strconv.Itoa(i),
+ }
+ if subTimeout != 0 {
+ args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
+ }
+ out, err := testenv.Command(t, "nohup", args...).CombinedOutput()
+
+ if err != nil {
+ // nohup doesn't work on new LUCI darwin builders due to the
+ // type of launchd service the test run under. See
+ // https://go.dev/issue/63875.
+ if runtime.GOOS == "darwin" && strings.Contains(string(out), "nohup: can't detach from console: Inappropriate ioctl for device") {
+ // TODO(go.dev/issue/63799): A false-positive in vet reports a
+ // t.Skip here as invalid. Switch back to t.Skip once fixed.
+ t.Logf("Skipping nohup test due to darwin builder limitation. See https://go.dev/issue/63875.")
+ return
+ }
+
+ t.Errorf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s", i, err, out)
+ } else {
+ t.Logf("ran test with -send_uncaught_sighup=%d under nohup.\nOutput:\n%s", i, out)
+ }
+ })
+ }
+ })
+}
+
+// Test that SIGCONT works (issue 8953).
+func TestSIGCONT(t *testing.T) {
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGCONT)
+ defer Stop(c)
+ syscall.Kill(syscall.Getpid(), syscall.SIGCONT)
+ waitSig(t, c, syscall.SIGCONT)
+}
+
+// Test race between stopping and receiving a signal (issue 14571).
+func TestAtomicStop(t *testing.T) {
+ if os.Getenv("GO_TEST_ATOMIC_STOP") != "" {
+ atomicStopTestProgram(t)
+ t.Fatal("atomicStopTestProgram returned")
+ }
+
+ testenv.MustHaveExec(t)
+
+ // Call Notify for SIGINT before starting the child process.
+ // That ensures that SIGINT is not ignored for the child.
+ // This is necessary because if SIGINT is ignored when a
+ // Go program starts, then it remains ignored, and closing
+ // the last notification channel for SIGINT will switch it
+ // back to being ignored. In that case the assumption of
+ // atomicStopTestProgram, that it will either die from SIGINT
+ // or have it be reported, breaks down, as there is a third
+ // option: SIGINT might be ignored.
+ cs := make(chan os.Signal, 1)
+ Notify(cs, syscall.SIGINT)
+ defer Stop(cs)
+
+ const execs = 10
+ for i := 0; i < execs; i++ {
+ timeout := "0"
+ if deadline, ok := t.Deadline(); ok {
+ timeout = time.Until(deadline).String()
+ }
+ cmd := testenv.Command(t, os.Args[0], "-test.run=TestAtomicStop", "-test.timeout="+timeout)
+ cmd.Env = append(os.Environ(), "GO_TEST_ATOMIC_STOP=1")
+ out, err := cmd.CombinedOutput()
+ if err == nil {
+ if len(out) > 0 {
+ t.Logf("iteration %d: output %s", i, out)
+ }
+ } else {
+ t.Logf("iteration %d: exit status %q: output: %s", i, err, out)
+ }
+
+ lost := bytes.Contains(out, []byte("lost signal"))
+ if lost {
+ t.Errorf("iteration %d: lost signal", i)
+ }
+
+ // The program should either die due to SIGINT,
+ // or exit with success without printing "lost signal".
+ if err == nil {
+ if len(out) > 0 && !lost {
+ t.Errorf("iteration %d: unexpected output", i)
+ }
+ } else {
+ if ee, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("iteration %d: error (%v) has type %T; expected exec.ExitError", i, err, err)
+ } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
+ t.Errorf("iteration %d: error.Sys (%v) has type %T; expected syscall.WaitStatus", i, ee.Sys(), ee.Sys())
+ } else if !ws.Signaled() || ws.Signal() != syscall.SIGINT {
+ t.Errorf("iteration %d: got exit status %v; expected SIGINT", i, ee)
+ }
+ }
+ }
+}
+
+// atomicStopTestProgram is run in a subprocess by TestAtomicStop.
+// It tries to trigger a signal delivery race. This function should
+// either catch a signal or die from it.
+func atomicStopTestProgram(t *testing.T) {
+ // This test won't work if SIGINT is ignored here.
+ if Ignored(syscall.SIGINT) {
+ fmt.Println("SIGINT is ignored")
+ os.Exit(1)
+ }
+
+ const tries = 10
+
+ timeout := 2 * time.Second
+ if deadline, ok := t.Deadline(); ok {
+ // Give each try an equal slice of the deadline, with one slice to spare for
+ // cleanup.
+ timeout = time.Until(deadline) / (tries + 1)
+ }
+
+ pid := syscall.Getpid()
+ printed := false
+ for i := 0; i < tries; i++ {
+ cs := make(chan os.Signal, 1)
+ Notify(cs, syscall.SIGINT)
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ Stop(cs)
+ }()
+
+ syscall.Kill(pid, syscall.SIGINT)
+
+ // At this point we should either die from SIGINT or
+ // get a notification on cs. If neither happens, we
+ // dropped the signal. It is given 2 seconds to
+ // deliver, as needed for gccgo on some loaded test systems.
+
+ select {
+ case <-cs:
+ case <-time.After(timeout):
+ if !printed {
+ fmt.Print("lost signal on tries:")
+ printed = true
+ }
+ fmt.Printf(" %d", i)
+ }
+
+ wg.Wait()
+ }
+ if printed {
+ fmt.Print("\n")
+ }
+
+ os.Exit(0)
+}
+
+func TestTime(t *testing.T) {
+ // Test that signal works fine when we are in a call to get time,
+ // which on some platforms is using VDSO. See issue #34391.
+ dur := 3 * time.Second
+ if testing.Short() {
+ dur = 100 * time.Millisecond
+ }
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+
+ sig := make(chan os.Signal, 1)
+ Notify(sig, syscall.SIGUSR1)
+
+ stop := make(chan struct{})
+ go func() {
+ for {
+ select {
+ case <-stop:
+ // Allow enough time for all signals to be delivered before we stop
+ // listening for them.
+ quiesce()
+ Stop(sig)
+ // According to its documentation, “[w]hen Stop returns, it in
+ // guaranteed that c will receive no more signals.” So we can safely
+ // close sig here: if there is a send-after-close race, that is a bug in
+ // Stop and we would like to detect it.
+ close(sig)
+ return
+
+ default:
+ syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
+ runtime.Gosched()
+ }
+ }
+ }()
+
+ done := make(chan struct{})
+ go func() {
+ for range sig {
+ // Receive signals until the sender closes sig.
+ }
+ close(done)
+ }()
+
+ t0 := time.Now()
+ for t1 := t0; t1.Sub(t0) < dur; t1 = time.Now() {
+ } // hammering on getting time
+
+ close(stop)
+ <-done
+}
+
+var (
+ checkNotifyContext = flag.Bool("check_notify_ctx", false, "if true, TestNotifyContext will fail if SIGINT is not received.")
+ ctxNotifyTimes = flag.Int("ctx_notify_times", 1, "number of times a SIGINT signal should be received")
+)
+
+func TestNotifyContextNotifications(t *testing.T) {
+ if *checkNotifyContext {
+ ctx, _ := NotifyContext(context.Background(), syscall.SIGINT)
+ // We want to make sure not to be calling Stop() internally on NotifyContext() when processing a received signal.
+ // Being able to wait for a number of received system signals allows us to do so.
+ var wg sync.WaitGroup
+ n := *ctxNotifyTimes
+ wg.Add(n)
+ for i := 0; i < n; i++ {
+ go func() {
+ syscall.Kill(syscall.Getpid(), syscall.SIGINT)
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+ <-ctx.Done()
+ fmt.Println("received SIGINT")
+ // Sleep to give time to simultaneous signals to reach the process.
+ // These signals must be ignored given stop() is not called on this code.
+ // We want to guarantee a SIGINT doesn't cause a premature termination of the program.
+ time.Sleep(settleTime)
+ return
+ }
+
+ t.Parallel()
+ testCases := []struct {
+ name string
+ n int // number of times a SIGINT should be notified.
+ }{
+ {"once", 1},
+ {"multiple", 10},
+ }
+ for _, tc := range testCases {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ var subTimeout time.Duration
+ if deadline, ok := t.Deadline(); ok {
+ timeout := time.Until(deadline)
+ if timeout < 2*settleTime {
+ t.Fatalf("starting test with less than %v remaining", 2*settleTime)
+ }
+ subTimeout = timeout - (timeout / 10) // Leave 10% headroom for cleaning up subprocess.
+ }
+
+ args := []string{
+ "-test.v",
+ "-test.run=TestNotifyContextNotifications$",
+ "-check_notify_ctx",
+ fmt.Sprintf("-ctx_notify_times=%d", tc.n),
+ }
+ if subTimeout != 0 {
+ args = append(args, fmt.Sprintf("-test.timeout=%v", subTimeout))
+ }
+ out, err := testenv.Command(t, os.Args[0], args...).CombinedOutput()
+ if err != nil {
+ t.Errorf("ran test with -check_notify_ctx_notification and it failed with %v.\nOutput:\n%s", err, out)
+ }
+ if want := []byte("received SIGINT\n"); !bytes.Contains(out, want) {
+ t.Errorf("got %q, wanted %q", out, want)
+ }
+ })
+ }
+}
+
+func TestNotifyContextStop(t *testing.T) {
+ Ignore(syscall.SIGHUP)
+ if !Ignored(syscall.SIGHUP) {
+ t.Errorf("expected SIGHUP to be ignored when explicitly ignoring it.")
+ }
+
+ parent, cancelParent := context.WithCancel(context.Background())
+ defer cancelParent()
+ c, stop := NotifyContext(parent, syscall.SIGHUP)
+ defer stop()
+
+ // If we're being notified, then the signal should not be ignored.
+ if Ignored(syscall.SIGHUP) {
+ t.Errorf("expected SIGHUP to not be ignored.")
+ }
+
+ if want, got := "signal.NotifyContext(context.Background.WithCancel, [hangup])", fmt.Sprint(c); want != got {
+ t.Errorf("c.String() = %q, wanted %q", got, want)
+ }
+
+ stop()
+ select {
+ case <-c.Done():
+ if got := c.Err(); got != context.Canceled {
+ t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
+ }
+ case <-time.After(time.Second):
+ t.Errorf("timed out waiting for context to be done after calling stop")
+ }
+}
+
+func TestNotifyContextCancelParent(t *testing.T) {
+ parent, cancelParent := context.WithCancel(context.Background())
+ defer cancelParent()
+ c, stop := NotifyContext(parent, syscall.SIGINT)
+ defer stop()
+
+ if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
+ t.Errorf("c.String() = %q, want %q", got, want)
+ }
+
+ cancelParent()
+ select {
+ case <-c.Done():
+ if got := c.Err(); got != context.Canceled {
+ t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
+ }
+ case <-time.After(time.Second):
+ t.Errorf("timed out waiting for parent context to be canceled")
+ }
+}
+
+func TestNotifyContextPrematureCancelParent(t *testing.T) {
+ parent, cancelParent := context.WithCancel(context.Background())
+ defer cancelParent()
+
+ cancelParent() // Prematurely cancel context before calling NotifyContext.
+ c, stop := NotifyContext(parent, syscall.SIGINT)
+ defer stop()
+
+ if want, got := "signal.NotifyContext(context.Background.WithCancel, [interrupt])", fmt.Sprint(c); want != got {
+ t.Errorf("c.String() = %q, want %q", got, want)
+ }
+
+ select {
+ case <-c.Done():
+ if got := c.Err(); got != context.Canceled {
+ t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
+ }
+ case <-time.After(time.Second):
+ t.Errorf("timed out waiting for parent context to be canceled")
+ }
+}
+
+func TestNotifyContextSimultaneousStop(t *testing.T) {
+ c, stop := NotifyContext(context.Background(), syscall.SIGINT)
+ defer stop()
+
+ if want, got := "signal.NotifyContext(context.Background, [interrupt])", fmt.Sprint(c); want != got {
+ t.Errorf("c.String() = %q, want %q", got, want)
+ }
+
+ var wg sync.WaitGroup
+ n := 10
+ wg.Add(n)
+ for i := 0; i < n; i++ {
+ go func() {
+ stop()
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+ select {
+ case <-c.Done():
+ if got := c.Err(); got != context.Canceled {
+ t.Errorf("c.Err() = %q, want %q", got, context.Canceled)
+ }
+ case <-time.After(time.Second):
+ t.Errorf("expected context to be canceled")
+ }
+}
+
+func TestNotifyContextStringer(t *testing.T) {
+ parent, cancelParent := context.WithCancel(context.Background())
+ defer cancelParent()
+ c, stop := NotifyContext(parent, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
+ defer stop()
+
+ want := `signal.NotifyContext(context.Background.WithCancel, [hangup interrupt terminated])`
+ if got := fmt.Sprint(c); got != want {
+ t.Errorf("c.String() = %q, want %q", got, want)
+ }
+}
+
+// #44193 test signal handling while stopping and starting the world.
+func TestSignalTrace(t *testing.T) {
+ done := make(chan struct{})
+ quit := make(chan struct{})
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGHUP)
+
+ // Source and sink for signals busy loop unsynchronized with
+ // trace starts and stops. We are ultimately validating that
+ // signals and runtime.(stop|start)TheWorldGC are compatible.
+ go func() {
+ defer close(done)
+ defer Stop(c)
+ pid := syscall.Getpid()
+ for {
+ select {
+ case <-quit:
+ return
+ default:
+ syscall.Kill(pid, syscall.SIGHUP)
+ }
+ waitSig(t, c, syscall.SIGHUP)
+ }
+ }()
+
+ for i := 0; i < 100; i++ {
+ buf := new(bytes.Buffer)
+ if err := trace.Start(buf); err != nil {
+ t.Fatalf("[%d] failed to start tracing: %v", i, err)
+ }
+ time.After(1 * time.Microsecond)
+ trace.Stop()
+ size := buf.Len()
+ if size == 0 {
+ t.Fatalf("[%d] trace is empty", i)
+ }
+ }
+ close(quit)
+ <-done
+}
diff --git a/src/os/signal/signal_unix.go b/src/os/signal/signal_unix.go
new file mode 100644
index 0000000..21dfa41
--- /dev/null
+++ b/src/os/signal/signal_unix.go
@@ -0,0 +1,62 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1 || windows
+
+package signal
+
+import (
+ "os"
+ "syscall"
+)
+
+// Defined by the runtime package.
+func signal_disable(uint32)
+func signal_enable(uint32)
+func signal_ignore(uint32)
+func signal_ignored(uint32) bool
+func signal_recv() uint32
+
+func loop() {
+ for {
+ process(syscall.Signal(signal_recv()))
+ }
+}
+
+func init() {
+ watchSignalLoop = loop
+}
+
+const (
+ numSig = 65 // max across all systems
+)
+
+func signum(sig os.Signal) int {
+ switch sig := sig.(type) {
+ case syscall.Signal:
+ i := int(sig)
+ if i < 0 || i >= numSig {
+ return -1
+ }
+ return i
+ default:
+ return -1
+ }
+}
+
+func enableSignal(sig int) {
+ signal_enable(uint32(sig))
+}
+
+func disableSignal(sig int) {
+ signal_disable(uint32(sig))
+}
+
+func ignoreSignal(sig int) {
+ signal_ignore(uint32(sig))
+}
+
+func signalIgnored(sig int) bool {
+ return signal_ignored(uint32(sig))
+}
diff --git a/src/os/signal/signal_windows_test.go b/src/os/signal/signal_windows_test.go
new file mode 100644
index 0000000..145a805
--- /dev/null
+++ b/src/os/signal/signal_windows_test.go
@@ -0,0 +1,98 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package signal
+
+import (
+ "internal/testenv"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func sendCtrlBreak(t *testing.T, pid int) {
+ d, e := syscall.LoadDLL("kernel32.dll")
+ if e != nil {
+ t.Fatalf("LoadDLL: %v\n", e)
+ }
+ p, e := d.FindProc("GenerateConsoleCtrlEvent")
+ if e != nil {
+ t.Fatalf("FindProc: %v\n", e)
+ }
+ r, _, e := p.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
+ if r == 0 {
+ t.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
+ }
+}
+
+func TestCtrlBreak(t *testing.T) {
+ // create source file
+ const source = `
+package main
+
+import (
+ "log"
+ "os"
+ "os/signal"
+ "time"
+)
+
+
+func main() {
+ c := make(chan os.Signal, 10)
+ signal.Notify(c)
+ select {
+ case s := <-c:
+ if s != os.Interrupt {
+ log.Fatalf("Wrong signal received: got %q, want %q\n", s, os.Interrupt)
+ }
+ case <-time.After(3 * time.Second):
+ log.Fatalf("Timeout waiting for Ctrl+Break\n")
+ }
+}
+`
+ tmp := t.TempDir()
+
+ // write ctrlbreak.go
+ name := filepath.Join(tmp, "ctlbreak")
+ src := name + ".go"
+ f, err := os.Create(src)
+ if err != nil {
+ t.Fatalf("Failed to create %v: %v", src, err)
+ }
+ defer f.Close()
+ f.Write([]byte(source))
+
+ // compile it
+ exe := name + ".exe"
+ defer os.Remove(exe)
+ o, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, src).CombinedOutput()
+ if err != nil {
+ t.Fatalf("Failed to compile: %v\n%v", err, string(o))
+ }
+
+ // run it
+ cmd := testenv.Command(t, exe)
+ var buf strings.Builder
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
+ }
+ err = cmd.Start()
+ if err != nil {
+ t.Fatalf("Start failed: %v", err)
+ }
+ go func() {
+ time.Sleep(1 * time.Second)
+ sendCtrlBreak(t, cmd.Process.Pid)
+ }()
+ err = cmd.Wait()
+ if err != nil {
+ t.Fatalf("Program exited with error: %v\n%v", err, buf.String())
+ }
+}
diff --git a/src/os/stat.go b/src/os/stat.go
new file mode 100644
index 0000000..af66838
--- /dev/null
+++ b/src/os/stat.go
@@ -0,0 +1,23 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "internal/testlog"
+
+// Stat returns a FileInfo describing the named file.
+// If there is an error, it will be of type *PathError.
+func Stat(name string) (FileInfo, error) {
+ testlog.Stat(name)
+ return statNolog(name)
+}
+
+// Lstat returns a FileInfo describing the named file.
+// If the file is a symbolic link, the returned FileInfo
+// describes the symbolic link. Lstat makes no attempt to follow the link.
+// If there is an error, it will be of type *PathError.
+func Lstat(name string) (FileInfo, error) {
+ testlog.Stat(name)
+ return lstatNolog(name)
+}
diff --git a/src/os/stat_aix.go b/src/os/stat_aix.go
new file mode 100644
index 0000000..a37c9fd
--- /dev/null
+++ b/src/os/stat_aix.go
@@ -0,0 +1,51 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = int64(fs.sys.Size)
+ fs.modTime = stTimespecToTime(fs.sys.Mtim)
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+func stTimespecToTime(ts syscall.StTimespec_t) time.Time {
+ return time.Unix(int64(ts.Sec), int64(ts.Nsec))
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return stTimespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
+}
diff --git a/src/os/stat_darwin.go b/src/os/stat_darwin.go
new file mode 100644
index 0000000..b92ffd4
--- /dev/null
+++ b/src/os/stat_darwin.go
@@ -0,0 +1,47 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = fs.sys.Size
+ fs.modTime = time.Unix(fs.sys.Mtimespec.Unix())
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK, syscall.S_IFWHT:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(fi.Sys().(*syscall.Stat_t).Atimespec.Unix())
+}
diff --git a/src/os/stat_dragonfly.go b/src/os/stat_dragonfly.go
new file mode 100644
index 0000000..316c26c
--- /dev/null
+++ b/src/os/stat_dragonfly.go
@@ -0,0 +1,47 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = fs.sys.Size
+ fs.modTime = time.Unix(fs.sys.Mtim.Unix())
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(fi.Sys().(*syscall.Stat_t).Atim.Unix())
+}
diff --git a/src/os/stat_freebsd.go b/src/os/stat_freebsd.go
new file mode 100644
index 0000000..919ee44
--- /dev/null
+++ b/src/os/stat_freebsd.go
@@ -0,0 +1,47 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = fs.sys.Size
+ fs.modTime = time.Unix(fs.sys.Mtimespec.Unix())
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(fi.Sys().(*syscall.Stat_t).Atimespec.Unix())
+}
diff --git a/src/os/stat_js.go b/src/os/stat_js.go
new file mode 100644
index 0000000..a137172
--- /dev/null
+++ b/src/os/stat_js.go
@@ -0,0 +1,50 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build js && wasm
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = fs.sys.Size
+ fs.modTime = time.Unix(fs.sys.Mtime, fs.sys.MtimeNsec)
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ st := fi.Sys().(*syscall.Stat_t)
+ return time.Unix(st.Atime, st.AtimeNsec)
+}
diff --git a/src/os/stat_linux.go b/src/os/stat_linux.go
new file mode 100644
index 0000000..316c26c
--- /dev/null
+++ b/src/os/stat_linux.go
@@ -0,0 +1,47 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = fs.sys.Size
+ fs.modTime = time.Unix(fs.sys.Mtim.Unix())
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(fi.Sys().(*syscall.Stat_t).Atim.Unix())
+}
diff --git a/src/os/stat_netbsd.go b/src/os/stat_netbsd.go
new file mode 100644
index 0000000..919ee44
--- /dev/null
+++ b/src/os/stat_netbsd.go
@@ -0,0 +1,47 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = fs.sys.Size
+ fs.modTime = time.Unix(fs.sys.Mtimespec.Unix())
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(fi.Sys().(*syscall.Stat_t).Atimespec.Unix())
+}
diff --git a/src/os/stat_openbsd.go b/src/os/stat_openbsd.go
new file mode 100644
index 0000000..316c26c
--- /dev/null
+++ b/src/os/stat_openbsd.go
@@ -0,0 +1,47 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = fs.sys.Size
+ fs.modTime = time.Unix(fs.sys.Mtim.Unix())
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(fi.Sys().(*syscall.Stat_t).Atim.Unix())
+}
diff --git a/src/os/stat_plan9.go b/src/os/stat_plan9.go
new file mode 100644
index 0000000..a5e9901
--- /dev/null
+++ b/src/os/stat_plan9.go
@@ -0,0 +1,114 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+const bitSize16 = 2
+
+func fileInfoFromStat(d *syscall.Dir) *fileStat {
+ fs := &fileStat{
+ name: d.Name,
+ size: d.Length,
+ modTime: time.Unix(int64(d.Mtime), 0),
+ sys: d,
+ }
+ fs.mode = FileMode(d.Mode & 0777)
+ if d.Mode&syscall.DMDIR != 0 {
+ fs.mode |= ModeDir
+ }
+ if d.Mode&syscall.DMAPPEND != 0 {
+ fs.mode |= ModeAppend
+ }
+ if d.Mode&syscall.DMEXCL != 0 {
+ fs.mode |= ModeExclusive
+ }
+ if d.Mode&syscall.DMTMP != 0 {
+ fs.mode |= ModeTemporary
+ }
+ // Consider all files not served by #M as device files.
+ if d.Type != 'M' {
+ fs.mode |= ModeDevice
+ }
+ // Consider all files served by #c as character device files.
+ if d.Type == 'c' {
+ fs.mode |= ModeCharDevice
+ }
+ return fs
+}
+
+// arg is an open *File or a path string.
+func dirstat(arg any) (*syscall.Dir, error) {
+ var name string
+ var err error
+
+ size := syscall.STATFIXLEN + 16*4
+
+ for i := 0; i < 2; i++ {
+ buf := make([]byte, bitSize16+size)
+
+ var n int
+ switch a := arg.(type) {
+ case *File:
+ name = a.name
+ if err := a.incref("fstat"); err != nil {
+ return nil, err
+ }
+ n, err = syscall.Fstat(a.fd, buf)
+ a.decref()
+ case string:
+ name = a
+ n, err = syscall.Stat(a, buf)
+ default:
+ panic("phase error in dirstat")
+ }
+
+ if n < bitSize16 {
+ return nil, &PathError{Op: "stat", Path: name, Err: err}
+ }
+
+ // Pull the real size out of the stat message.
+ size = int(uint16(buf[0]) | uint16(buf[1])<<8)
+
+ // If the stat message is larger than our buffer we will
+ // go around the loop and allocate one that is big enough.
+ if size <= n {
+ d, err := syscall.UnmarshalDir(buf[:n])
+ if err != nil {
+ return nil, &PathError{Op: "stat", Path: name, Err: err}
+ }
+ return d, nil
+ }
+
+ }
+
+ if err == nil {
+ err = syscall.ErrBadStat
+ }
+
+ return nil, &PathError{Op: "stat", Path: name, Err: err}
+}
+
+// statNolog implements Stat for Plan 9.
+func statNolog(name string) (FileInfo, error) {
+ d, err := dirstat(name)
+ if err != nil {
+ return nil, err
+ }
+ return fileInfoFromStat(d), nil
+}
+
+// lstatNolog implements Lstat for Plan 9.
+func lstatNolog(name string) (FileInfo, error) {
+ return statNolog(name)
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(int64(fi.Sys().(*syscall.Dir).Atime), 0)
+}
diff --git a/src/os/stat_solaris.go b/src/os/stat_solaris.go
new file mode 100644
index 0000000..4e00ecb
--- /dev/null
+++ b/src/os/stat_solaris.go
@@ -0,0 +1,57 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+// These constants aren't in the syscall package, which is frozen.
+// Values taken from golang.org/x/sys/unix.
+const (
+ _S_IFNAM = 0x5000
+ _S_IFDOOR = 0xd000
+ _S_IFPORT = 0xe000
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = fs.sys.Size
+ fs.modTime = time.Unix(fs.sys.Mtim.Unix())
+ fs.mode = FileMode(fs.sys.Mode & 0777)
+ switch fs.sys.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ case _S_IFNAM, _S_IFDOOR, _S_IFPORT:
+ fs.mode |= ModeIrregular
+ }
+ if fs.sys.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if fs.sys.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if fs.sys.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(fi.Sys().(*syscall.Stat_t).Atim.Unix())
+}
diff --git a/src/os/stat_test.go b/src/os/stat_test.go
new file mode 100644
index 0000000..9601969
--- /dev/null
+++ b/src/os/stat_test.go
@@ -0,0 +1,296 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "internal/testenv"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+// testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work.
+func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, fs.FileInfo)) {
+ // test os.Stat
+ sfi, err := os.Stat(path)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ statCheck(t, path, sfi)
+
+ // test os.Lstat
+ lsfi, err := os.Lstat(path)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ lstatCheck(t, path, lsfi)
+
+ if isLink {
+ if os.SameFile(sfi, lsfi) {
+ t.Errorf("stat and lstat of %q should not be the same", path)
+ }
+ } else {
+ if !os.SameFile(sfi, lsfi) {
+ t.Errorf("stat and lstat of %q should be the same", path)
+ }
+ }
+
+ // test os.File.Stat
+ f, err := os.Open(path)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer f.Close()
+
+ sfi2, err := f.Stat()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ statCheck(t, path, sfi2)
+
+ if !os.SameFile(sfi, sfi2) {
+ t.Errorf("stat of open %q file and stat of %q should be the same", path, path)
+ }
+
+ if isLink {
+ if os.SameFile(sfi2, lsfi) {
+ t.Errorf("stat of opened %q file and lstat of %q should not be the same", path, path)
+ }
+ } else {
+ if !os.SameFile(sfi2, lsfi) {
+ t.Errorf("stat of opened %q file and lstat of %q should be the same", path, path)
+ }
+ }
+
+ // test fs.FileInfo returned by os.Readdir
+ if len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) {
+ // skip os.Readdir test of directories with slash at the end
+ return
+ }
+ parentdir := filepath.Dir(path)
+ parent, err := os.Open(parentdir)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer parent.Close()
+
+ fis, err := parent.Readdir(-1)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ var lsfi2 fs.FileInfo
+ base := filepath.Base(path)
+ for _, fi2 := range fis {
+ if fi2.Name() == base {
+ lsfi2 = fi2
+ break
+ }
+ }
+ if lsfi2 == nil {
+ t.Errorf("failed to find %q in its parent", path)
+ return
+ }
+ lstatCheck(t, path, lsfi2)
+
+ if !os.SameFile(lsfi, lsfi2) {
+ t.Errorf("lstat of %q file in %q directory and %q should be the same", lsfi2.Name(), parentdir, path)
+ }
+}
+
+// testIsDir verifies that fi refers to directory.
+func testIsDir(t *testing.T, path string, fi fs.FileInfo) {
+ t.Helper()
+ if !fi.IsDir() {
+ t.Errorf("%q should be a directory", path)
+ }
+ if fi.Mode()&fs.ModeSymlink != 0 {
+ t.Errorf("%q should not be a symlink", path)
+ }
+}
+
+// testIsSymlink verifies that fi refers to symlink.
+func testIsSymlink(t *testing.T, path string, fi fs.FileInfo) {
+ t.Helper()
+ if fi.IsDir() {
+ t.Errorf("%q should not be a directory", path)
+ }
+ if fi.Mode()&fs.ModeSymlink == 0 {
+ t.Errorf("%q should be a symlink", path)
+ }
+}
+
+// testIsFile verifies that fi refers to file.
+func testIsFile(t *testing.T, path string, fi fs.FileInfo) {
+ t.Helper()
+ if fi.IsDir() {
+ t.Errorf("%q should not be a directory", path)
+ }
+ if fi.Mode()&fs.ModeSymlink != 0 {
+ t.Errorf("%q should not be a symlink", path)
+ }
+}
+
+func testDirStats(t *testing.T, path string) {
+ testStatAndLstat(t, path, false, testIsDir, testIsDir)
+}
+
+func testFileStats(t *testing.T, path string) {
+ testStatAndLstat(t, path, false, testIsFile, testIsFile)
+}
+
+func testSymlinkStats(t *testing.T, path string, isdir bool) {
+ if isdir {
+ testStatAndLstat(t, path, true, testIsDir, testIsSymlink)
+ } else {
+ testStatAndLstat(t, path, true, testIsFile, testIsSymlink)
+ }
+}
+
+func testSymlinkSameFile(t *testing.T, path, link string) {
+ pathfi, err := os.Stat(path)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ linkfi, err := os.Stat(link)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if !os.SameFile(pathfi, linkfi) {
+ t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", path, link)
+ }
+
+ linkfi, err = os.Lstat(link)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if os.SameFile(pathfi, linkfi) {
+ t.Errorf("os.Stat(%q) and os.Lstat(%q) are the same file", path, link)
+ }
+}
+
+func testSymlinkSameFileOpen(t *testing.T, link string) {
+ f, err := os.Open(link)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ defer f.Close()
+
+ fi, err := f.Stat()
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ fi2, err := os.Stat(link)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ if !os.SameFile(fi, fi2) {
+ t.Errorf("os.Open(%q).Stat() and os.Stat(%q) are not the same file", link, link)
+ }
+}
+
+func TestDirAndSymlinkStats(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+ t.Parallel()
+
+ tmpdir := t.TempDir()
+ dir := filepath.Join(tmpdir, "dir")
+ if err := os.Mkdir(dir, 0777); err != nil {
+ t.Fatal(err)
+ }
+ testDirStats(t, dir)
+
+ dirlink := filepath.Join(tmpdir, "link")
+ if err := os.Symlink(dir, dirlink); err != nil {
+ t.Fatal(err)
+ }
+ testSymlinkStats(t, dirlink, true)
+ testSymlinkSameFile(t, dir, dirlink)
+ testSymlinkSameFileOpen(t, dirlink)
+
+ linklink := filepath.Join(tmpdir, "linklink")
+ if err := os.Symlink(dirlink, linklink); err != nil {
+ t.Fatal(err)
+ }
+ testSymlinkStats(t, linklink, true)
+ testSymlinkSameFile(t, dir, linklink)
+ testSymlinkSameFileOpen(t, linklink)
+}
+
+func TestFileAndSymlinkStats(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+ t.Parallel()
+
+ tmpdir := t.TempDir()
+ file := filepath.Join(tmpdir, "file")
+ if err := os.WriteFile(file, []byte(""), 0644); err != nil {
+ t.Fatal(err)
+ }
+ testFileStats(t, file)
+
+ filelink := filepath.Join(tmpdir, "link")
+ if err := os.Symlink(file, filelink); err != nil {
+ t.Fatal(err)
+ }
+ testSymlinkStats(t, filelink, false)
+ testSymlinkSameFile(t, file, filelink)
+ testSymlinkSameFileOpen(t, filelink)
+
+ linklink := filepath.Join(tmpdir, "linklink")
+ if err := os.Symlink(filelink, linklink); err != nil {
+ t.Fatal(err)
+ }
+ testSymlinkStats(t, linklink, false)
+ testSymlinkSameFile(t, file, linklink)
+ testSymlinkSameFileOpen(t, linklink)
+}
+
+// see issue 27225 for details
+func TestSymlinkWithTrailingSlash(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+ t.Parallel()
+
+ tmpdir := t.TempDir()
+ dir := filepath.Join(tmpdir, "dir")
+ if err := os.Mkdir(dir, 0777); err != nil {
+ t.Fatal(err)
+ }
+ dirlink := filepath.Join(tmpdir, "link")
+ if err := os.Symlink(dir, dirlink); err != nil {
+ t.Fatal(err)
+ }
+ dirlinkWithSlash := dirlink + string(os.PathSeparator)
+
+ testDirStats(t, dirlinkWithSlash)
+
+ fi1, err := os.Stat(dir)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ fi2, err := os.Stat(dirlinkWithSlash)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if !os.SameFile(fi1, fi2) {
+ t.Errorf("os.Stat(%q) and os.Stat(%q) are not the same file", dir, dirlinkWithSlash)
+ }
+}
diff --git a/src/os/stat_unix.go b/src/os/stat_unix.go
new file mode 100644
index 0000000..431df33
--- /dev/null
+++ b/src/os/stat_unix.go
@@ -0,0 +1,52 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix || (js && wasm) || wasip1
+
+package os
+
+import (
+ "syscall"
+)
+
+// Stat returns the FileInfo structure describing file.
+// If there is an error, it will be of type *PathError.
+func (f *File) Stat() (FileInfo, error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ var fs fileStat
+ err := f.pfd.Fstat(&fs.sys)
+ if err != nil {
+ return nil, &PathError{Op: "stat", Path: f.name, Err: err}
+ }
+ fillFileStatFromSys(&fs, f.name)
+ return &fs, nil
+}
+
+// statNolog stats a file with no test logging.
+func statNolog(name string) (FileInfo, error) {
+ var fs fileStat
+ err := ignoringEINTR(func() error {
+ return syscall.Stat(name, &fs.sys)
+ })
+ if err != nil {
+ return nil, &PathError{Op: "stat", Path: name, Err: err}
+ }
+ fillFileStatFromSys(&fs, name)
+ return &fs, nil
+}
+
+// lstatNolog lstats a file with no test logging.
+func lstatNolog(name string) (FileInfo, error) {
+ var fs fileStat
+ err := ignoringEINTR(func() error {
+ return syscall.Lstat(name, &fs.sys)
+ })
+ if err != nil {
+ return nil, &PathError{Op: "lstat", Path: name, Err: err}
+ }
+ fillFileStatFromSys(&fs, name)
+ return &fs, nil
+}
diff --git a/src/os/stat_wasip1.go b/src/os/stat_wasip1.go
new file mode 100644
index 0000000..a4f0a20
--- /dev/null
+++ b/src/os/stat_wasip1.go
@@ -0,0 +1,40 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasip1
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func fillFileStatFromSys(fs *fileStat, name string) {
+ fs.name = basename(name)
+ fs.size = int64(fs.sys.Size)
+ fs.mode = FileMode(fs.sys.Mode)
+ fs.modTime = time.Unix(0, int64(fs.sys.Mtime))
+
+ switch fs.sys.Filetype {
+ case syscall.FILETYPE_BLOCK_DEVICE:
+ fs.mode |= ModeDevice
+ case syscall.FILETYPE_CHARACTER_DEVICE:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.FILETYPE_DIRECTORY:
+ fs.mode |= ModeDir
+ case syscall.FILETYPE_SOCKET_DGRAM:
+ fs.mode |= ModeSocket
+ case syscall.FILETYPE_SOCKET_STREAM:
+ fs.mode |= ModeSocket
+ case syscall.FILETYPE_SYMBOLIC_LINK:
+ fs.mode |= ModeSymlink
+ }
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ st := fi.Sys().(*syscall.Stat_t)
+ return time.Unix(0, int64(st.Atime))
+}
diff --git a/src/os/stat_windows.go b/src/os/stat_windows.go
new file mode 100644
index 0000000..033c3b9
--- /dev/null
+++ b/src/os/stat_windows.go
@@ -0,0 +1,136 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "internal/syscall/windows"
+ "syscall"
+ "unsafe"
+)
+
+// Stat returns the FileInfo structure describing file.
+// If there is an error, it will be of type *PathError.
+func (file *File) Stat() (FileInfo, error) {
+ if file == nil {
+ return nil, ErrInvalid
+ }
+ return statHandle(file.name, file.pfd.Sysfd)
+}
+
+// stat implements both Stat and Lstat of a file.
+func stat(funcname, name string, followSymlinks bool) (FileInfo, error) {
+ if len(name) == 0 {
+ return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
+ }
+ namep, err := syscall.UTF16PtrFromString(fixLongPath(name))
+ if err != nil {
+ return nil, &PathError{Op: funcname, Path: name, Err: err}
+ }
+
+ // Try GetFileAttributesEx first, because it is faster than CreateFile.
+ // See https://golang.org/issues/19922#issuecomment-300031421 for details.
+ var fa syscall.Win32FileAttributeData
+ err = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
+
+ // GetFileAttributesEx fails with ERROR_SHARING_VIOLATION error for
+ // files like c:\pagefile.sys. Use FindFirstFile for such files.
+ if err == windows.ERROR_SHARING_VIOLATION {
+ var fd syscall.Win32finddata
+ sh, err := syscall.FindFirstFile(namep, &fd)
+ if err != nil {
+ return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err}
+ }
+ syscall.FindClose(sh)
+ if fd.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+ // Not a symlink or mount point. FindFirstFile is good enough.
+ fs := newFileStatFromWin32finddata(&fd)
+ if err := fs.saveInfoFromPath(name); err != nil {
+ return nil, err
+ }
+ return fs, nil
+ }
+ }
+
+ if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+ // The file is definitely not a symlink, because it isn't any kind of reparse point.
+ // The information we got from GetFileAttributesEx is good enough for now.
+ fs := &fileStat{
+ FileAttributes: fa.FileAttributes,
+ CreationTime: fa.CreationTime,
+ LastAccessTime: fa.LastAccessTime,
+ LastWriteTime: fa.LastWriteTime,
+ FileSizeHigh: fa.FileSizeHigh,
+ FileSizeLow: fa.FileSizeLow,
+ }
+ if err := fs.saveInfoFromPath(name); err != nil {
+ return nil, err
+ }
+ return fs, nil
+ }
+
+ // Use CreateFile to determine whether the file is a symlink and, if so,
+ // save information about the link target.
+ // Set FILE_FLAG_BACKUP_SEMANTICS so that CreateFile will create the handle
+ // even if name refers to a directory.
+ h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
+ if err != nil {
+ // Since CreateFile failed, we can't determine whether name refers to a
+ // symlink, or some other kind of reparse point. Since we can't return a
+ // FileInfo with a known-accurate Mode, we must return an error.
+ return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
+ }
+
+ fi, err := statHandle(name, h)
+ syscall.CloseHandle(h)
+ if err == nil && followSymlinks && fi.(*fileStat).isSymlink() {
+ // To obtain information about the link target, we reopen the file without
+ // FILE_FLAG_OPEN_REPARSE_POINT and examine the resulting handle.
+ // (See https://devblogs.microsoft.com/oldnewthing/20100212-00/?p=14963.)
+ h, err = syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
+ if err != nil {
+ // name refers to a symlink, but we couldn't resolve the symlink target.
+ return nil, &PathError{Op: "CreateFile", Path: name, Err: err}
+ }
+ defer syscall.CloseHandle(h)
+ return statHandle(name, h)
+ }
+ return fi, err
+}
+
+func statHandle(name string, h syscall.Handle) (FileInfo, error) {
+ ft, err := syscall.GetFileType(h)
+ if err != nil {
+ return nil, &PathError{Op: "GetFileType", Path: name, Err: err}
+ }
+ switch ft {
+ case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR:
+ return &fileStat{name: basename(name), filetype: ft}, nil
+ }
+ fs, err := newFileStatFromGetFileInformationByHandle(name, h)
+ if err != nil {
+ return nil, err
+ }
+ fs.filetype = ft
+ return fs, err
+}
+
+// statNolog implements Stat for Windows.
+func statNolog(name string) (FileInfo, error) {
+ return stat("Stat", name, true)
+}
+
+// lstatNolog implements Lstat for Windows.
+func lstatNolog(name string) (FileInfo, error) {
+ followSymlinks := false
+ if name != "" && IsPathSeparator(name[len(name)-1]) {
+ // We try to implement POSIX semantics for Lstat path resolution
+ // (per https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/basedefs/V1_chap04.html#tag_04_12):
+ // symlinks before the last separator in the path must be resolved. Since
+ // the last separator in this case follows the last path element, we should
+ // follow symlinks in the last path element.
+ followSymlinks = true
+ }
+ return stat("Lstat", name, followSymlinks)
+}
diff --git a/src/os/sticky_bsd.go b/src/os/sticky_bsd.go
new file mode 100644
index 0000000..a6d9339
--- /dev/null
+++ b/src/os/sticky_bsd.go
@@ -0,0 +1,11 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || netbsd || openbsd || solaris || wasip1
+
+package os
+
+// According to sticky(8), neither open(2) nor mkdir(2) will create
+// a file with the sticky bit set.
+const supportsCreateWithStickyBit = false
diff --git a/src/os/sticky_notbsd.go b/src/os/sticky_notbsd.go
new file mode 100644
index 0000000..1d289b0
--- /dev/null
+++ b/src/os/sticky_notbsd.go
@@ -0,0 +1,9 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !aix && !darwin && !dragonfly && !freebsd && !js && !netbsd && !openbsd && !solaris && !wasip1
+
+package os
+
+const supportsCreateWithStickyBit = true
diff --git a/src/os/str.go b/src/os/str.go
new file mode 100644
index 0000000..242c945
--- /dev/null
+++ b/src/os/str.go
@@ -0,0 +1,39 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Simple conversions to avoid depending on strconv.
+
+package os
+
+// itox converts val (an int) to a hexadecimal string.
+func itox(val int) string {
+ if val < 0 {
+ return "-" + uitox(uint(-val))
+ }
+ return uitox(uint(val))
+}
+
+const hex = "0123456789abcdef"
+
+// uitox converts val (a uint) to a hexadecimal string.
+func uitox(val uint) string {
+ if val == 0 { // avoid string allocation
+ return "0x0"
+ }
+ var buf [20]byte // big enough for 64bit value base 16 + 0x
+ i := len(buf) - 1
+ for val >= 16 {
+ q := val / 16
+ buf[i] = hex[val%16]
+ i--
+ val = q
+ }
+ // val < 16
+ buf[i] = hex[val%16]
+ i--
+ buf[i] = 'x'
+ i--
+ buf[i] = '0'
+ return string(buf[i:])
+}
diff --git a/src/os/sys.go b/src/os/sys.go
new file mode 100644
index 0000000..28b0f6b
--- /dev/null
+++ b/src/os/sys.go
@@ -0,0 +1,10 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// Hostname returns the host name reported by the kernel.
+func Hostname() (name string, err error) {
+ return hostname()
+}
diff --git a/src/os/sys_aix.go b/src/os/sys_aix.go
new file mode 100644
index 0000000..53a40f2
--- /dev/null
+++ b/src/os/sys_aix.go
@@ -0,0 +1,26 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+// gethostname syscall cannot be used because it also returns the domain.
+// Therefore, hostname is retrieve with uname syscall and the Nodename field.
+
+func hostname() (name string, err error) {
+ var u syscall.Utsname
+ if errno := syscall.Uname(&u); errno != nil {
+ return "", NewSyscallError("uname", errno)
+ }
+ b := make([]byte, len(u.Nodename))
+ i := 0
+ for ; i < len(u.Nodename); i++ {
+ if u.Nodename[i] == 0 {
+ break
+ }
+ b[i] = byte(u.Nodename[i])
+ }
+ return string(b[:i]), nil
+}
diff --git a/src/os/sys_bsd.go b/src/os/sys_bsd.go
new file mode 100644
index 0000000..63120fb
--- /dev/null
+++ b/src/os/sys_bsd.go
@@ -0,0 +1,17 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build darwin || dragonfly || freebsd || (js && wasm) || netbsd || openbsd || wasip1
+
+package os
+
+import "syscall"
+
+func hostname() (name string, err error) {
+ name, err = syscall.Sysctl("kern.hostname")
+ if err != nil {
+ return "", NewSyscallError("sysctl kern.hostname", err)
+ }
+ return name, nil
+}
diff --git a/src/os/sys_js.go b/src/os/sys_js.go
new file mode 100644
index 0000000..4fd0e2d
--- /dev/null
+++ b/src/os/sys_js.go
@@ -0,0 +1,11 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build js && wasm
+
+package os
+
+// supportsCloseOnExec reports whether the platform supports the
+// O_CLOEXEC flag.
+const supportsCloseOnExec = false
diff --git a/src/os/sys_linux.go b/src/os/sys_linux.go
new file mode 100644
index 0000000..36a8a24
--- /dev/null
+++ b/src/os/sys_linux.go
@@ -0,0 +1,53 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "runtime"
+ "syscall"
+)
+
+func hostname() (name string, err error) {
+ // Try uname first, as it's only one system call and reading
+ // from /proc is not allowed on Android.
+ var un syscall.Utsname
+ err = syscall.Uname(&un)
+
+ var buf [512]byte // Enough for a DNS name.
+ for i, b := range un.Nodename[:] {
+ buf[i] = uint8(b)
+ if b == 0 {
+ name = string(buf[:i])
+ break
+ }
+ }
+ // If we got a name and it's not potentially truncated
+ // (Nodename is 65 bytes), return it.
+ if err == nil && len(name) > 0 && len(name) < 64 {
+ return name, nil
+ }
+ if runtime.GOOS == "android" {
+ if name != "" {
+ return name, nil
+ }
+ return "localhost", nil
+ }
+
+ f, err := Open("/proc/sys/kernel/hostname")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ n, err := f.Read(buf[:])
+ if err != nil {
+ return "", err
+ }
+
+ if n > 0 && buf[n-1] == '\n' {
+ n--
+ }
+ return string(buf[:n]), nil
+}
diff --git a/src/os/sys_plan9.go b/src/os/sys_plan9.go
new file mode 100644
index 0000000..40374eb
--- /dev/null
+++ b/src/os/sys_plan9.go
@@ -0,0 +1,24 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+func hostname() (name string, err error) {
+ f, err := Open("#c/sysname")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ var buf [128]byte
+ n, err := f.Read(buf[:len(buf)-1])
+
+ if err != nil {
+ return "", err
+ }
+ if n > 0 {
+ buf[n] = 0
+ }
+ return string(buf[0:n]), nil
+}
diff --git a/src/os/sys_solaris.go b/src/os/sys_solaris.go
new file mode 100644
index 0000000..917e8f2
--- /dev/null
+++ b/src/os/sys_solaris.go
@@ -0,0 +1,11 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+func hostname() (name string, err error) {
+ return syscall.Gethostname()
+}
diff --git a/src/os/sys_unix.go b/src/os/sys_unix.go
new file mode 100644
index 0000000..79005c2
--- /dev/null
+++ b/src/os/sys_unix.go
@@ -0,0 +1,14 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package os
+
+// supportsCloseOnExec reports whether the platform supports the
+// O_CLOEXEC flag.
+// On Darwin, the O_CLOEXEC flag was introduced in OS X 10.7 (Darwin 11.0.0).
+// See https://support.apple.com/kb/HT1633.
+// On FreeBSD, the O_CLOEXEC flag was introduced in version 8.3.
+const supportsCloseOnExec = true
diff --git a/src/os/sys_wasip1.go b/src/os/sys_wasip1.go
new file mode 100644
index 0000000..5a29aa5
--- /dev/null
+++ b/src/os/sys_wasip1.go
@@ -0,0 +1,11 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build wasip1
+
+package os
+
+// supportsCloseOnExec reports whether the platform supports the
+// O_CLOEXEC flag.
+const supportsCloseOnExec = false
diff --git a/src/os/sys_windows.go b/src/os/sys_windows.go
new file mode 100644
index 0000000..72ad90b
--- /dev/null
+++ b/src/os/sys_windows.go
@@ -0,0 +1,33 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "internal/syscall/windows"
+ "syscall"
+)
+
+func hostname() (name string, err error) {
+ // Use PhysicalDnsHostname to uniquely identify host in a cluster
+ const format = windows.ComputerNamePhysicalDnsHostname
+
+ n := uint32(64)
+ for {
+ b := make([]uint16, n)
+ err := windows.GetComputerNameEx(format, &b[0], &n)
+ if err == nil {
+ return syscall.UTF16ToString(b[:n]), nil
+ }
+ if err != syscall.ERROR_MORE_DATA {
+ return "", NewSyscallError("ComputerNameEx", err)
+ }
+
+ // If we received an ERROR_MORE_DATA, but n doesn't get larger,
+ // something has gone wrong and we may be in an infinite loop
+ if n <= uint32(len(b)) {
+ return "", NewSyscallError("ComputerNameEx", err)
+ }
+ }
+}
diff --git a/src/os/tempfile.go b/src/os/tempfile.go
new file mode 100644
index 0000000..99f65c6
--- /dev/null
+++ b/src/os/tempfile.go
@@ -0,0 +1,128 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "errors"
+ "internal/itoa"
+)
+
+// fastrand provided by runtime.
+// We generate random temporary file names so that there's a good
+// chance the file doesn't exist yet - keeps the number of tries in
+// TempFile to a minimum.
+func fastrand() uint32
+
+func nextRandom() string {
+ return itoa.Uitoa(uint(fastrand()))
+}
+
+// CreateTemp creates a new temporary file in the directory dir,
+// opens the file for reading and writing, and returns the resulting file.
+// The filename is generated by taking pattern and adding a random string to the end.
+// If pattern includes a "*", the random string replaces the last "*".
+// If dir is the empty string, CreateTemp uses the default directory for temporary files, as returned by TempDir.
+// Multiple programs or goroutines calling CreateTemp simultaneously will not choose the same file.
+// The caller can use the file's Name method to find the pathname of the file.
+// It is the caller's responsibility to remove the file when it is no longer needed.
+func CreateTemp(dir, pattern string) (*File, error) {
+ if dir == "" {
+ dir = TempDir()
+ }
+
+ prefix, suffix, err := prefixAndSuffix(pattern)
+ if err != nil {
+ return nil, &PathError{Op: "createtemp", Path: pattern, Err: err}
+ }
+ prefix = joinPath(dir, prefix)
+
+ try := 0
+ for {
+ name := prefix + nextRandom() + suffix
+ f, err := OpenFile(name, O_RDWR|O_CREATE|O_EXCL, 0600)
+ if IsExist(err) {
+ if try++; try < 10000 {
+ continue
+ }
+ return nil, &PathError{Op: "createtemp", Path: prefix + "*" + suffix, Err: ErrExist}
+ }
+ return f, err
+ }
+}
+
+var errPatternHasSeparator = errors.New("pattern contains path separator")
+
+// prefixAndSuffix splits pattern by the last wildcard "*", if applicable,
+// returning prefix as the part before "*" and suffix as the part after "*".
+func prefixAndSuffix(pattern string) (prefix, suffix string, err error) {
+ for i := 0; i < len(pattern); i++ {
+ if IsPathSeparator(pattern[i]) {
+ return "", "", errPatternHasSeparator
+ }
+ }
+ if pos := lastIndex(pattern, '*'); pos != -1 {
+ prefix, suffix = pattern[:pos], pattern[pos+1:]
+ } else {
+ prefix = pattern
+ }
+ return prefix, suffix, nil
+}
+
+// MkdirTemp creates a new temporary directory in the directory dir
+// and returns the pathname of the new directory.
+// The new directory's name is generated by adding a random string to the end of pattern.
+// If pattern includes a "*", the random string replaces the last "*" instead.
+// If dir is the empty string, MkdirTemp uses the default directory for temporary files, as returned by TempDir.
+// Multiple programs or goroutines calling MkdirTemp simultaneously will not choose the same directory.
+// It is the caller's responsibility to remove the directory when it is no longer needed.
+func MkdirTemp(dir, pattern string) (string, error) {
+ if dir == "" {
+ dir = TempDir()
+ }
+
+ prefix, suffix, err := prefixAndSuffix(pattern)
+ if err != nil {
+ return "", &PathError{Op: "mkdirtemp", Path: pattern, Err: err}
+ }
+ prefix = joinPath(dir, prefix)
+
+ try := 0
+ for {
+ name := prefix + nextRandom() + suffix
+ err := Mkdir(name, 0700)
+ if err == nil {
+ return name, nil
+ }
+ if IsExist(err) {
+ if try++; try < 10000 {
+ continue
+ }
+ return "", &PathError{Op: "mkdirtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist}
+ }
+ if IsNotExist(err) {
+ if _, err := Stat(dir); IsNotExist(err) {
+ return "", err
+ }
+ }
+ return "", err
+ }
+}
+
+func joinPath(dir, name string) string {
+ if len(dir) > 0 && IsPathSeparator(dir[len(dir)-1]) {
+ return dir + name
+ }
+ return dir + string(PathSeparator) + name
+}
+
+// lastIndex from the strings package.
+func lastIndex(s string, sep byte) int {
+ for i := len(s) - 1; i >= 0; i-- {
+ if s[i] == sep {
+ return i
+ }
+ }
+ return -1
+}
diff --git a/src/os/tempfile_test.go b/src/os/tempfile_test.go
new file mode 100644
index 0000000..82f0aab
--- /dev/null
+++ b/src/os/tempfile_test.go
@@ -0,0 +1,205 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "errors"
+ "io/fs"
+ . "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+func TestCreateTemp(t *testing.T) {
+ t.Parallel()
+
+ dir, err := MkdirTemp("", "TestCreateTempBadDir")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(dir)
+
+ nonexistentDir := filepath.Join(dir, "_not_exists_")
+ f, err := CreateTemp(nonexistentDir, "foo")
+ if f != nil || err == nil {
+ t.Errorf("CreateTemp(%q, `foo`) = %v, %v", nonexistentDir, f, err)
+ }
+}
+
+func TestCreateTempPattern(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct{ pattern, prefix, suffix string }{
+ {"tempfile_test", "tempfile_test", ""},
+ {"tempfile_test*", "tempfile_test", ""},
+ {"tempfile_test*xyz", "tempfile_test", "xyz"},
+ }
+ for _, test := range tests {
+ f, err := CreateTemp("", test.pattern)
+ if err != nil {
+ t.Errorf("CreateTemp(..., %q) error: %v", test.pattern, err)
+ continue
+ }
+ defer Remove(f.Name())
+ base := filepath.Base(f.Name())
+ f.Close()
+ if !(strings.HasPrefix(base, test.prefix) && strings.HasSuffix(base, test.suffix)) {
+ t.Errorf("CreateTemp pattern %q created bad name %q; want prefix %q & suffix %q",
+ test.pattern, base, test.prefix, test.suffix)
+ }
+ }
+}
+
+func TestCreateTempBadPattern(t *testing.T) {
+ t.Parallel()
+
+ tmpDir, err := MkdirTemp("", t.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(tmpDir)
+
+ const sep = string(PathSeparator)
+ tests := []struct {
+ pattern string
+ wantErr bool
+ }{
+ {"ioutil*test", false},
+ {"tempfile_test*foo", false},
+ {"tempfile_test" + sep + "foo", true},
+ {"tempfile_test*" + sep + "foo", true},
+ {"tempfile_test" + sep + "*foo", true},
+ {sep + "tempfile_test" + sep + "*foo", true},
+ {"tempfile_test*foo" + sep, true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.pattern, func(t *testing.T) {
+ tmpfile, err := CreateTemp(tmpDir, tt.pattern)
+ if tmpfile != nil {
+ defer tmpfile.Close()
+ }
+ if tt.wantErr {
+ if err == nil {
+ t.Errorf("CreateTemp(..., %#q) succeeded, expected error", tt.pattern)
+ }
+ if !errors.Is(err, ErrPatternHasSeparator) {
+ t.Errorf("CreateTemp(..., %#q): %v, expected ErrPatternHasSeparator", tt.pattern, err)
+ }
+ } else if err != nil {
+ t.Errorf("CreateTemp(..., %#q): %v", tt.pattern, err)
+ }
+ })
+ }
+}
+
+func TestMkdirTemp(t *testing.T) {
+ t.Parallel()
+
+ name, err := MkdirTemp("/_not_exists_", "foo")
+ if name != "" || err == nil {
+ t.Errorf("MkdirTemp(`/_not_exists_`, `foo`) = %v, %v", name, err)
+ }
+
+ tests := []struct {
+ pattern string
+ wantPrefix, wantSuffix string
+ }{
+ {"tempfile_test", "tempfile_test", ""},
+ {"tempfile_test*", "tempfile_test", ""},
+ {"tempfile_test*xyz", "tempfile_test", "xyz"},
+ }
+
+ dir := filepath.Clean(TempDir())
+
+ runTestMkdirTemp := func(t *testing.T, pattern, wantRePat string) {
+ name, err := MkdirTemp(dir, pattern)
+ if name == "" || err != nil {
+ t.Fatalf("MkdirTemp(dir, `tempfile_test`) = %v, %v", name, err)
+ }
+ defer Remove(name)
+
+ re := regexp.MustCompile(wantRePat)
+ if !re.MatchString(name) {
+ t.Errorf("MkdirTemp(%q, %q) created bad name\n\t%q\ndid not match pattern\n\t%q", dir, pattern, name, wantRePat)
+ }
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.pattern, func(t *testing.T) {
+ wantRePat := "^" + regexp.QuoteMeta(filepath.Join(dir, tt.wantPrefix)) + "[0-9]+" + regexp.QuoteMeta(tt.wantSuffix) + "$"
+ runTestMkdirTemp(t, tt.pattern, wantRePat)
+ })
+ }
+
+ // Separately testing "*xyz" (which has no prefix). That is when constructing the
+ // pattern to assert on, as in the previous loop, using filepath.Join for an empty
+ // prefix filepath.Join(dir, ""), produces the pattern:
+ // ^<DIR>[0-9]+xyz$
+ // yet we just want to match
+ // "^<DIR>/[0-9]+xyz"
+ t.Run("*xyz", func(t *testing.T) {
+ wantRePat := "^" + regexp.QuoteMeta(filepath.Join(dir)) + regexp.QuoteMeta(string(filepath.Separator)) + "[0-9]+xyz$"
+ runTestMkdirTemp(t, "*xyz", wantRePat)
+ })
+}
+
+// test that we return a nice error message if the dir argument to TempDir doesn't
+// exist (or that it's empty and TempDir doesn't exist)
+func TestMkdirTempBadDir(t *testing.T) {
+ t.Parallel()
+
+ dir, err := MkdirTemp("", "MkdirTempBadDir")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(dir)
+
+ badDir := filepath.Join(dir, "not-exist")
+ _, err = MkdirTemp(badDir, "foo")
+ if pe, ok := err.(*fs.PathError); !ok || !IsNotExist(err) || pe.Path != badDir {
+ t.Errorf("TempDir error = %#v; want PathError for path %q satisfying IsNotExist", err, badDir)
+ }
+}
+
+func TestMkdirTempBadPattern(t *testing.T) {
+ t.Parallel()
+
+ tmpDir, err := MkdirTemp("", t.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(tmpDir)
+
+ const sep = string(PathSeparator)
+ tests := []struct {
+ pattern string
+ wantErr bool
+ }{
+ {"ioutil*test", false},
+ {"tempfile_test*foo", false},
+ {"tempfile_test" + sep + "foo", true},
+ {"tempfile_test*" + sep + "foo", true},
+ {"tempfile_test" + sep + "*foo", true},
+ {sep + "tempfile_test" + sep + "*foo", true},
+ {"tempfile_test*foo" + sep, true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.pattern, func(t *testing.T) {
+ _, err := MkdirTemp(tmpDir, tt.pattern)
+ if tt.wantErr {
+ if err == nil {
+ t.Errorf("MkdirTemp(..., %#q) succeeded, expected error", tt.pattern)
+ }
+ if !errors.Is(err, ErrPatternHasSeparator) {
+ t.Errorf("MkdirTemp(..., %#q): %v, expected ErrPatternHasSeparator", tt.pattern, err)
+ }
+ } else if err != nil {
+ t.Errorf("MkdirTemp(..., %#q): %v", tt.pattern, err)
+ }
+ })
+ }
+}
diff --git a/src/os/testdata/dirfs/a b/src/os/testdata/dirfs/a
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/os/testdata/dirfs/a
diff --git a/src/os/testdata/dirfs/b b/src/os/testdata/dirfs/b
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/os/testdata/dirfs/b
diff --git a/src/os/testdata/dirfs/dir/x b/src/os/testdata/dirfs/dir/x
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/os/testdata/dirfs/dir/x
diff --git a/src/os/testdata/hello b/src/os/testdata/hello
new file mode 100644
index 0000000..e47c092
--- /dev/null
+++ b/src/os/testdata/hello
@@ -0,0 +1 @@
+Hello, Gophers!
diff --git a/src/os/testdata/issue37161/a b/src/os/testdata/issue37161/a
new file mode 100644
index 0000000..7898192
--- /dev/null
+++ b/src/os/testdata/issue37161/a
@@ -0,0 +1 @@
+a
diff --git a/src/os/testdata/issue37161/b b/src/os/testdata/issue37161/b
new file mode 100644
index 0000000..6178079
--- /dev/null
+++ b/src/os/testdata/issue37161/b
@@ -0,0 +1 @@
+b
diff --git a/src/os/testdata/issue37161/c b/src/os/testdata/issue37161/c
new file mode 100644
index 0000000..f2ad6c7
--- /dev/null
+++ b/src/os/testdata/issue37161/c
@@ -0,0 +1 @@
+c
diff --git a/src/os/timeout_test.go b/src/os/timeout_test.go
new file mode 100644
index 0000000..e0d2328
--- /dev/null
+++ b/src/os/timeout_test.go
@@ -0,0 +1,708 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !js && !plan9 && !wasip1 && !windows
+
+package os_test
+
+import (
+ "fmt"
+ "io"
+ "math/rand"
+ "os"
+ "os/signal"
+ "runtime"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func TestNonpollableDeadline(t *testing.T) {
+ // On BSD systems regular files seem to be pollable,
+ // so just run this test on Linux.
+ if runtime.GOOS != "linux" {
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+ t.Parallel()
+
+ f, err := os.CreateTemp("", "ostest")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ deadline := time.Now().Add(10 * time.Second)
+ if err := f.SetDeadline(deadline); err != os.ErrNoDeadline {
+ t.Errorf("SetDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
+ }
+ if err := f.SetReadDeadline(deadline); err != os.ErrNoDeadline {
+ t.Errorf("SetReadDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
+ }
+ if err := f.SetWriteDeadline(deadline); err != os.ErrNoDeadline {
+ t.Errorf("SetWriteDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline)
+ }
+}
+
+// noDeadline is a zero time.Time value, which cancels a deadline.
+var noDeadline time.Time
+
+var readTimeoutTests = []struct {
+ timeout time.Duration
+ xerrs [2]error // expected errors in transition
+}{
+ // Tests that read deadlines work, even if there's data ready
+ // to be read.
+ {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
+
+ {50 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestReadTimeout(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ if _, err := w.Write([]byte("READ TIMEOUT TEST")); err != nil {
+ t.Fatal(err)
+ }
+
+ for i, tt := range readTimeoutTests {
+ if err := r.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil {
+ t.Fatalf("#%d: %v", i, err)
+ }
+ var b [1]byte
+ for j, xerr := range tt.xerrs {
+ for {
+ n, err := r.Read(b[:])
+ if xerr != nil {
+ if !isDeadlineExceeded(err) {
+ t.Fatalf("#%d/%d: %v", i, j, err)
+ }
+ }
+ if err == nil {
+ time.Sleep(tt.timeout / 3)
+ continue
+ }
+ if n != 0 {
+ t.Fatalf("#%d/%d: read %d; want 0", i, j, n)
+ }
+ break
+ }
+ }
+ }
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestReadTimeoutMustNotReturn(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ max := time.NewTimer(100 * time.Millisecond)
+ defer max.Stop()
+ ch := make(chan error)
+ go func() {
+ if err := r.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
+ t.Error(err)
+ }
+ if err := r.SetWriteDeadline(time.Now().Add(-5 * time.Second)); err != nil {
+ t.Error(err)
+ }
+ if err := r.SetReadDeadline(noDeadline); err != nil {
+ t.Error(err)
+ }
+ var b [1]byte
+ _, err := r.Read(b[:])
+ ch <- err
+ }()
+
+ select {
+ case err := <-ch:
+ t.Fatalf("expected Read to not return, but it returned with %v", err)
+ case <-max.C:
+ w.Close()
+ err := <-ch // wait for tester goroutine to stop
+ if os.IsTimeout(err) {
+ t.Fatal(err)
+ }
+ }
+}
+
+var writeTimeoutTests = []struct {
+ timeout time.Duration
+ xerrs [2]error // expected errors in transition
+}{
+ // Tests that write deadlines work, even if there's buffer
+ // space available to write.
+ {-5 * time.Second, [2]error{os.ErrDeadlineExceeded, os.ErrDeadlineExceeded}},
+
+ {10 * time.Millisecond, [2]error{nil, os.ErrDeadlineExceeded}},
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestWriteTimeout(t *testing.T) {
+ t.Parallel()
+
+ for i, tt := range writeTimeoutTests {
+ t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ if err := w.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil {
+ t.Fatalf("%v", err)
+ }
+ for j, xerr := range tt.xerrs {
+ for {
+ n, err := w.Write([]byte("WRITE TIMEOUT TEST"))
+ if xerr != nil {
+ if !isDeadlineExceeded(err) {
+ t.Fatalf("%d: %v", j, err)
+ }
+ }
+ if err == nil {
+ time.Sleep(tt.timeout / 3)
+ continue
+ }
+ if n != 0 {
+ t.Fatalf("%d: wrote %d; want 0", j, n)
+ }
+ break
+ }
+ }
+ })
+ }
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestWriteTimeoutMustNotReturn(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ max := time.NewTimer(100 * time.Millisecond)
+ defer max.Stop()
+ ch := make(chan error)
+ go func() {
+ if err := w.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil {
+ t.Error(err)
+ }
+ if err := w.SetReadDeadline(time.Now().Add(-5 * time.Second)); err != nil {
+ t.Error(err)
+ }
+ if err := w.SetWriteDeadline(noDeadline); err != nil {
+ t.Error(err)
+ }
+ var b [1]byte
+ for {
+ if _, err := w.Write(b[:]); err != nil {
+ ch <- err
+ break
+ }
+ }
+ }()
+
+ select {
+ case err := <-ch:
+ t.Fatalf("expected Write to not return, but it returned with %v", err)
+ case <-max.C:
+ r.Close()
+ err := <-ch // wait for tester goroutine to stop
+ if os.IsTimeout(err) {
+ t.Fatal(err)
+ }
+ }
+}
+
+const (
+ // minDynamicTimeout is the minimum timeout to attempt for
+ // tests that automatically increase timeouts until success.
+ //
+ // Lower values may allow tests to succeed more quickly if the value is close
+ // to the true minimum, but may require more iterations (and waste more time
+ // and CPU power on failed attempts) if the timeout is too low.
+ minDynamicTimeout = 1 * time.Millisecond
+
+ // maxDynamicTimeout is the maximum timeout to attempt for
+ // tests that automatically increase timeouts until success.
+ //
+ // This should be a strict upper bound on the latency required to hit a
+ // timeout accurately, even on a slow or heavily-loaded machine. If a test
+ // would increase the timeout beyond this value, the test fails.
+ maxDynamicTimeout = 4 * time.Second
+)
+
+// timeoutUpperBound returns the maximum time that we expect a timeout of
+// duration d to take to return the caller.
+func timeoutUpperBound(d time.Duration) time.Duration {
+ switch runtime.GOOS {
+ case "openbsd", "netbsd":
+ // NetBSD and OpenBSD seem to be unable to reliably hit deadlines even when
+ // the absolute durations are long.
+ // In https://build.golang.org/log/c34f8685d020b98377dd4988cd38f0c5bd72267e,
+ // we observed that an openbsd-amd64-68 builder took 4.090948779s for a
+ // 2.983020682s timeout (37.1% overhead).
+ // (See https://go.dev/issue/50189 for further detail.)
+ // Give them lots of slop to compensate.
+ return d * 3 / 2
+ }
+ // Other platforms seem to hit their deadlines more reliably,
+ // at least when they are long enough to cover scheduling jitter.
+ return d * 11 / 10
+}
+
+// nextTimeout returns the next timeout to try after an operation took the given
+// actual duration with a timeout shorter than that duration.
+func nextTimeout(actual time.Duration) (next time.Duration, ok bool) {
+ if actual >= maxDynamicTimeout {
+ return maxDynamicTimeout, false
+ }
+ // Since the previous attempt took actual, we can't expect to beat that
+ // duration by any significant margin. Try the next attempt with an arbitrary
+ // factor above that, so that our growth curve is at least exponential.
+ next = actual * 5 / 4
+ if next > maxDynamicTimeout {
+ return maxDynamicTimeout, true
+ }
+ return next, true
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestReadTimeoutFluctuation(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ d := minDynamicTimeout
+ b := make([]byte, 256)
+ for {
+ t.Logf("SetReadDeadline(+%v)", d)
+ t0 := time.Now()
+ deadline := t0.Add(d)
+ if err = r.SetReadDeadline(deadline); err != nil {
+ t.Fatalf("SetReadDeadline(%v): %v", deadline, err)
+ }
+ var n int
+ n, err = r.Read(b)
+ t1 := time.Now()
+
+ if n != 0 || err == nil || !isDeadlineExceeded(err) {
+ t.Errorf("Read did not return (0, timeout): (%d, %v)", n, err)
+ }
+
+ actual := t1.Sub(t0)
+ if t1.Before(deadline) {
+ t.Errorf("Read took %s; expected at least %s", actual, d)
+ }
+ if t.Failed() {
+ return
+ }
+ if want := timeoutUpperBound(d); actual > want {
+ next, ok := nextTimeout(actual)
+ if !ok {
+ t.Fatalf("Read took %s; expected at most %v", actual, want)
+ }
+ // Maybe this machine is too slow to reliably schedule goroutines within
+ // the requested duration. Increase the timeout and try again.
+ t.Logf("Read took %s (expected %s); trying with longer timeout", actual, d)
+ d = next
+ continue
+ }
+
+ break
+ }
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestWriteTimeoutFluctuation(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ d := minDynamicTimeout
+ for {
+ t.Logf("SetWriteDeadline(+%v)", d)
+ t0 := time.Now()
+ deadline := t0.Add(d)
+ if err := w.SetWriteDeadline(deadline); err != nil {
+ t.Fatalf("SetWriteDeadline(%v): %v", deadline, err)
+ }
+ var n int64
+ var err error
+ for {
+ var dn int
+ dn, err = w.Write([]byte("TIMEOUT TRANSMITTER"))
+ n += int64(dn)
+ if err != nil {
+ break
+ }
+ }
+ t1 := time.Now()
+ // Inv: err != nil
+ if !isDeadlineExceeded(err) {
+ t.Fatalf("Write did not return (any, timeout): (%d, %v)", n, err)
+ }
+
+ actual := t1.Sub(t0)
+ if t1.Before(deadline) {
+ t.Errorf("Write took %s; expected at least %s", actual, d)
+ }
+ if t.Failed() {
+ return
+ }
+ if want := timeoutUpperBound(d); actual > want {
+ if n > 0 {
+ // SetWriteDeadline specifies a time “after which I/O operations fail
+ // instead of blocking”. However, the kernel's send buffer is not yet
+ // full, we may be able to write some arbitrary (but finite) number of
+ // bytes to it without blocking.
+ t.Logf("Wrote %d bytes into send buffer; retrying until buffer is full", n)
+ if d <= maxDynamicTimeout/2 {
+ // We don't know how long the actual write loop would have taken if
+ // the buffer were full, so just guess and double the duration so that
+ // the next attempt can make twice as much progress toward filling it.
+ d *= 2
+ }
+ } else if next, ok := nextTimeout(actual); !ok {
+ t.Fatalf("Write took %s; expected at most %s", actual, want)
+ } else {
+ // Maybe this machine is too slow to reliably schedule goroutines within
+ // the requested duration. Increase the timeout and try again.
+ t.Logf("Write took %s (expected %s); trying with longer timeout", actual, d)
+ d = next
+ }
+ continue
+ }
+
+ break
+ }
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestVariousDeadlines(t *testing.T) {
+ t.Parallel()
+ testVariousDeadlines(t)
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestVariousDeadlines1Proc(t *testing.T) {
+ // Cannot use t.Parallel - modifies global GOMAXPROCS.
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
+ testVariousDeadlines(t)
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestVariousDeadlines4Proc(t *testing.T) {
+ // Cannot use t.Parallel - modifies global GOMAXPROCS.
+ if testing.Short() {
+ t.Skip("skipping in short mode")
+ }
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+ testVariousDeadlines(t)
+}
+
+type neverEnding byte
+
+func (b neverEnding) Read(p []byte) (int, error) {
+ for i := range p {
+ p[i] = byte(b)
+ }
+ return len(p), nil
+}
+
+func testVariousDeadlines(t *testing.T) {
+ type result struct {
+ n int64
+ err error
+ d time.Duration
+ }
+
+ handler := func(w *os.File, pasvch chan result) {
+ // The writer, with no timeouts of its own,
+ // sending bytes to clients as fast as it can.
+ t0 := time.Now()
+ n, err := io.Copy(w, neverEnding('a'))
+ dt := time.Since(t0)
+ pasvch <- result{n, err, dt}
+ }
+
+ for _, timeout := range []time.Duration{
+ 1 * time.Nanosecond,
+ 2 * time.Nanosecond,
+ 5 * time.Nanosecond,
+ 50 * time.Nanosecond,
+ 100 * time.Nanosecond,
+ 200 * time.Nanosecond,
+ 500 * time.Nanosecond,
+ 750 * time.Nanosecond,
+ 1 * time.Microsecond,
+ 5 * time.Microsecond,
+ 25 * time.Microsecond,
+ 250 * time.Microsecond,
+ 500 * time.Microsecond,
+ 1 * time.Millisecond,
+ 5 * time.Millisecond,
+ 100 * time.Millisecond,
+ 250 * time.Millisecond,
+ 500 * time.Millisecond,
+ 1 * time.Second,
+ } {
+ numRuns := 3
+ if testing.Short() {
+ numRuns = 1
+ if timeout > 500*time.Microsecond {
+ continue
+ }
+ }
+ for run := 0; run < numRuns; run++ {
+ t.Run(fmt.Sprintf("%v-%d", timeout, run+1), func(t *testing.T) {
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ pasvch := make(chan result)
+ go handler(w, pasvch)
+
+ tooLong := 5 * time.Second
+ max := time.NewTimer(tooLong)
+ defer max.Stop()
+ actvch := make(chan result)
+ go func() {
+ t0 := time.Now()
+ if err := r.SetDeadline(t0.Add(timeout)); err != nil {
+ t.Error(err)
+ }
+ n, err := io.Copy(io.Discard, r)
+ dt := time.Since(t0)
+ r.Close()
+ actvch <- result{n, err, dt}
+ }()
+
+ select {
+ case res := <-actvch:
+ if !isDeadlineExceeded(err) {
+ t.Logf("good client timeout after %v, reading %d bytes", res.d, res.n)
+ } else {
+ t.Fatalf("client Copy = %d, %v; want timeout", res.n, res.err)
+ }
+ case <-max.C:
+ t.Fatalf("timeout (%v) waiting for client to timeout (%v) reading", tooLong, timeout)
+ }
+
+ select {
+ case res := <-pasvch:
+ t.Logf("writer in %v wrote %d: %v", res.d, res.n, res.err)
+ case <-max.C:
+ t.Fatalf("timeout waiting for writer to finish writing")
+ }
+ })
+ }
+ }
+}
+
+// There is a very similar copy of this in net/timeout_test.go.
+func TestReadWriteDeadlineRace(t *testing.T) {
+ t.Parallel()
+
+ N := 1000
+ if testing.Short() {
+ N = 50
+ }
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ var wg sync.WaitGroup
+ wg.Add(3)
+ go func() {
+ defer wg.Done()
+ tic := time.NewTicker(2 * time.Microsecond)
+ defer tic.Stop()
+ for i := 0; i < N; i++ {
+ if err := r.SetReadDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
+ break
+ }
+ if err := w.SetWriteDeadline(time.Now().Add(2 * time.Microsecond)); err != nil {
+ break
+ }
+ <-tic.C
+ }
+ }()
+ go func() {
+ defer wg.Done()
+ var b [1]byte
+ for i := 0; i < N; i++ {
+ _, err := r.Read(b[:])
+ if err != nil && !isDeadlineExceeded(err) {
+ t.Error("Read returned non-timeout error", err)
+ }
+ }
+ }()
+ go func() {
+ defer wg.Done()
+ var b [1]byte
+ for i := 0; i < N; i++ {
+ _, err := w.Write(b[:])
+ if err != nil && !isDeadlineExceeded(err) {
+ t.Error("Write returned non-timeout error", err)
+ }
+ }
+ }()
+ wg.Wait() // wait for tester goroutine to stop
+}
+
+// TestRacyRead tests that it is safe to mutate the input Read buffer
+// immediately after cancellation has occurred.
+func TestRacyRead(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ go io.Copy(w, rand.New(rand.NewSource(0)))
+
+ r.SetReadDeadline(time.Now().Add(time.Millisecond))
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ b1 := make([]byte, 1024)
+ b2 := make([]byte, 1024)
+ for j := 0; j < 100; j++ {
+ _, err := r.Read(b1)
+ copy(b1, b2) // Mutate b1 to trigger potential race
+ if err != nil {
+ if !isDeadlineExceeded(err) {
+ t.Error(err)
+ }
+ r.SetReadDeadline(time.Now().Add(time.Millisecond))
+ }
+ }
+ }()
+ }
+}
+
+// TestRacyWrite tests that it is safe to mutate the input Write buffer
+// immediately after cancellation has occurred.
+func TestRacyWrite(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ var wg sync.WaitGroup
+ defer wg.Wait()
+
+ go io.Copy(io.Discard, r)
+
+ w.SetWriteDeadline(time.Now().Add(time.Millisecond))
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ b1 := make([]byte, 1024)
+ b2 := make([]byte, 1024)
+ for j := 0; j < 100; j++ {
+ _, err := w.Write(b1)
+ copy(b1, b2) // Mutate b1 to trigger potential race
+ if err != nil {
+ if !isDeadlineExceeded(err) {
+ t.Error(err)
+ }
+ w.SetWriteDeadline(time.Now().Add(time.Millisecond))
+ }
+ }
+ }()
+ }
+}
+
+// Closing a TTY while reading from it should not hang. Issue 23943.
+func TestTTYClose(t *testing.T) {
+ // Ignore SIGTTIN in case we are running in the background.
+ signal.Ignore(syscall.SIGTTIN)
+ defer signal.Reset(syscall.SIGTTIN)
+
+ f, err := os.Open("/dev/tty")
+ if err != nil {
+ t.Skipf("skipping because opening /dev/tty failed: %v", err)
+ }
+
+ go func() {
+ var buf [1]byte
+ f.Read(buf[:])
+ }()
+
+ // Give the goroutine a chance to enter the read.
+ // It doesn't matter much if it occasionally fails to do so,
+ // we won't be testing what we want to test but the test will pass.
+ time.Sleep(time.Millisecond)
+
+ c := make(chan bool)
+ go func() {
+ defer close(c)
+ f.Close()
+ }()
+
+ select {
+ case <-c:
+ case <-time.After(time.Second):
+ t.Error("timed out waiting for close")
+ }
+
+ // On some systems the goroutines may now be hanging.
+ // There's not much we can do about that.
+}
diff --git a/src/os/types.go b/src/os/types.go
new file mode 100644
index 0000000..d8edd98
--- /dev/null
+++ b/src/os/types.go
@@ -0,0 +1,74 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "io/fs"
+ "syscall"
+)
+
+// Getpagesize returns the underlying system's memory page size.
+func Getpagesize() int { return syscall.Getpagesize() }
+
+// File represents an open file descriptor.
+type File struct {
+ *file // os specific
+}
+
+// A FileInfo describes a file and is returned by Stat and Lstat.
+type FileInfo = fs.FileInfo
+
+// A FileMode represents a file's mode and permission bits.
+// The bits have the same definition on all systems, so that
+// information about files can be moved from one system
+// to another portably. Not all bits apply to all systems.
+// The only required bit is ModeDir for directories.
+type FileMode = fs.FileMode
+
+// The defined file mode bits are the most significant bits of the FileMode.
+// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
+// The values of these bits should be considered part of the public API and
+// may be used in wire protocols or disk representations: they must not be
+// changed, although new bits might be added.
+const (
+ // The single letters are the abbreviations
+ // used by the String method's formatting.
+ ModeDir = fs.ModeDir // d: is a directory
+ ModeAppend = fs.ModeAppend // a: append-only
+ ModeExclusive = fs.ModeExclusive // l: exclusive use
+ ModeTemporary = fs.ModeTemporary // T: temporary file; Plan 9 only
+ ModeSymlink = fs.ModeSymlink // L: symbolic link
+ ModeDevice = fs.ModeDevice // D: device file
+ ModeNamedPipe = fs.ModeNamedPipe // p: named pipe (FIFO)
+ ModeSocket = fs.ModeSocket // S: Unix domain socket
+ ModeSetuid = fs.ModeSetuid // u: setuid
+ ModeSetgid = fs.ModeSetgid // g: setgid
+ ModeCharDevice = fs.ModeCharDevice // c: Unix character device, when ModeDevice is set
+ ModeSticky = fs.ModeSticky // t: sticky
+ ModeIrregular = fs.ModeIrregular // ?: non-regular file; nothing else is known about this file
+
+ // Mask for the type bits. For regular files, none will be set.
+ ModeType = fs.ModeType
+
+ ModePerm = fs.ModePerm // Unix permission bits, 0o777
+)
+
+func (fs *fileStat) Name() string { return fs.name }
+func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
+
+// SameFile reports whether fi1 and fi2 describe the same file.
+// For example, on Unix this means that the device and inode fields
+// of the two underlying structures are identical; on other systems
+// the decision may be based on the path names.
+// SameFile only applies to results returned by this package's Stat.
+// It returns false in other cases.
+func SameFile(fi1, fi2 FileInfo) bool {
+ fs1, ok1 := fi1.(*fileStat)
+ fs2, ok2 := fi2.(*fileStat)
+ if !ok1 || !ok2 {
+ return false
+ }
+ return sameFile(fs1, fs2)
+}
diff --git a/src/os/types_plan9.go b/src/os/types_plan9.go
new file mode 100644
index 0000000..adb4013
--- /dev/null
+++ b/src/os/types_plan9.go
@@ -0,0 +1,30 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
+type fileStat struct {
+ name string
+ size int64
+ mode FileMode
+ modTime time.Time
+ sys any
+}
+
+func (fs *fileStat) Size() int64 { return fs.size }
+func (fs *fileStat) Mode() FileMode { return fs.mode }
+func (fs *fileStat) ModTime() time.Time { return fs.modTime }
+func (fs *fileStat) Sys() any { return fs.sys }
+
+func sameFile(fs1, fs2 *fileStat) bool {
+ a := fs1.sys.(*syscall.Dir)
+ b := fs2.sys.(*syscall.Dir)
+ return a.Qid.Path == b.Qid.Path && a.Type == b.Type && a.Dev == b.Dev
+}
diff --git a/src/os/types_unix.go b/src/os/types_unix.go
new file mode 100644
index 0000000..1b90a5a
--- /dev/null
+++ b/src/os/types_unix.go
@@ -0,0 +1,30 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !windows && !plan9
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
+type fileStat struct {
+ name string
+ size int64
+ mode FileMode
+ modTime time.Time
+ sys syscall.Stat_t
+}
+
+func (fs *fileStat) Size() int64 { return fs.size }
+func (fs *fileStat) Mode() FileMode { return fs.mode }
+func (fs *fileStat) ModTime() time.Time { return fs.modTime }
+func (fs *fileStat) Sys() any { return &fs.sys }
+
+func sameFile(fs1, fs2 *fileStat) bool {
+ return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino
+}
diff --git a/src/os/types_windows.go b/src/os/types_windows.go
new file mode 100644
index 0000000..effb014
--- /dev/null
+++ b/src/os/types_windows.go
@@ -0,0 +1,262 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "internal/syscall/windows"
+ "sync"
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
+type fileStat struct {
+ name string
+
+ // from ByHandleFileInformation, Win32FileAttributeData and Win32finddata
+ FileAttributes uint32
+ CreationTime syscall.Filetime
+ LastAccessTime syscall.Filetime
+ LastWriteTime syscall.Filetime
+ FileSizeHigh uint32
+ FileSizeLow uint32
+
+ // from Win32finddata
+ ReparseTag uint32
+
+ // what syscall.GetFileType returns
+ filetype uint32
+
+ // used to implement SameFile
+ sync.Mutex
+ path string
+ vol uint32
+ idxhi uint32
+ idxlo uint32
+ appendNameToPath bool
+}
+
+// newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle
+// to gather all required information about the file handle h.
+func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) {
+ var d syscall.ByHandleFileInformation
+ err = syscall.GetFileInformationByHandle(h, &d)
+ if err != nil {
+ return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err}
+ }
+
+ var ti windows.FILE_ATTRIBUTE_TAG_INFO
+ err = windows.GetFileInformationByHandleEx(h, windows.FileAttributeTagInfo, (*byte)(unsafe.Pointer(&ti)), uint32(unsafe.Sizeof(ti)))
+ if err != nil {
+ if errno, ok := err.(syscall.Errno); ok && errno == windows.ERROR_INVALID_PARAMETER {
+ // It appears calling GetFileInformationByHandleEx with
+ // FILE_ATTRIBUTE_TAG_INFO fails on FAT file system with
+ // ERROR_INVALID_PARAMETER. Clear ti.ReparseTag in that
+ // instance to indicate no symlinks are possible.
+ ti.ReparseTag = 0
+ } else {
+ return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err}
+ }
+ }
+
+ return &fileStat{
+ name: basename(path),
+ FileAttributes: d.FileAttributes,
+ CreationTime: d.CreationTime,
+ LastAccessTime: d.LastAccessTime,
+ LastWriteTime: d.LastWriteTime,
+ FileSizeHigh: d.FileSizeHigh,
+ FileSizeLow: d.FileSizeLow,
+ vol: d.VolumeSerialNumber,
+ idxhi: d.FileIndexHigh,
+ idxlo: d.FileIndexLow,
+ ReparseTag: ti.ReparseTag,
+ // fileStat.path is used by os.SameFile to decide if it needs
+ // to fetch vol, idxhi and idxlo. But these are already set,
+ // so set fileStat.path to "" to prevent os.SameFile doing it again.
+ }, nil
+}
+
+// newFileStatFromWin32finddata copies all required information
+// from syscall.Win32finddata d into the newly created fileStat.
+func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
+ fs := &fileStat{
+ FileAttributes: d.FileAttributes,
+ CreationTime: d.CreationTime,
+ LastAccessTime: d.LastAccessTime,
+ LastWriteTime: d.LastWriteTime,
+ FileSizeHigh: d.FileSizeHigh,
+ FileSizeLow: d.FileSizeLow,
+ }
+ if d.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
+ // Per https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-win32_find_dataw:
+ // “If the dwFileAttributes member includes the FILE_ATTRIBUTE_REPARSE_POINT
+ // attribute, this member specifies the reparse point tag. Otherwise, this
+ // value is undefined and should not be used.”
+ fs.ReparseTag = d.Reserved0
+ }
+ return fs
+}
+
+func (fs *fileStat) isSymlink() bool {
+ // As of https://go.dev/cl/86556, we treat MOUNT_POINT reparse points as
+ // symlinks because otherwise certain directory junction tests in the
+ // path/filepath package would fail.
+ //
+ // However,
+ // https://learn.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions
+ // seems to suggest that directory junctions should be treated like hard
+ // links, not symlinks.
+ //
+ // TODO(bcmills): Get more input from Microsoft on what the behavior ought to
+ // be for MOUNT_POINT reparse points.
+
+ return fs.ReparseTag == syscall.IO_REPARSE_TAG_SYMLINK ||
+ fs.ReparseTag == windows.IO_REPARSE_TAG_MOUNT_POINT
+}
+
+func (fs *fileStat) Size() int64 {
+ return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
+}
+
+func (fs *fileStat) Mode() (m FileMode) {
+ if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
+ m |= 0444
+ } else {
+ m |= 0666
+ }
+ if fs.isSymlink() {
+ return m | ModeSymlink
+ }
+ if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ m |= ModeDir | 0111
+ }
+ switch fs.filetype {
+ case syscall.FILE_TYPE_PIPE:
+ m |= ModeNamedPipe
+ case syscall.FILE_TYPE_CHAR:
+ m |= ModeDevice | ModeCharDevice
+ }
+ if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 && m&ModeType == 0 {
+ if fs.ReparseTag == windows.IO_REPARSE_TAG_DEDUP {
+ // If the Data Deduplication service is enabled on Windows Server, its
+ // Optimization job may convert regular files to IO_REPARSE_TAG_DEDUP
+ // whenever that job runs.
+ //
+ // However, DEDUP reparse points remain similar in most respects to
+ // regular files: they continue to support random-access reads and writes
+ // of persistent data, and they shouldn't add unexpected latency or
+ // unavailability in the way that a network filesystem might.
+ //
+ // Go programs may use ModeIrregular to filter out unusual files (such as
+ // raw device files on Linux, POSIX FIFO special files, and so on), so
+ // to avoid files changing unpredictably from regular to irregular we will
+ // consider DEDUP files to be close enough to regular to treat as such.
+ } else {
+ m |= ModeIrregular
+ }
+ }
+ return m
+}
+
+func (fs *fileStat) ModTime() time.Time {
+ return time.Unix(0, fs.LastWriteTime.Nanoseconds())
+}
+
+// Sys returns syscall.Win32FileAttributeData for file fs.
+func (fs *fileStat) Sys() any {
+ return &syscall.Win32FileAttributeData{
+ FileAttributes: fs.FileAttributes,
+ CreationTime: fs.CreationTime,
+ LastAccessTime: fs.LastAccessTime,
+ LastWriteTime: fs.LastWriteTime,
+ FileSizeHigh: fs.FileSizeHigh,
+ FileSizeLow: fs.FileSizeLow,
+ }
+}
+
+func (fs *fileStat) loadFileId() error {
+ fs.Lock()
+ defer fs.Unlock()
+ if fs.path == "" {
+ // already done
+ return nil
+ }
+ var path string
+ if fs.appendNameToPath {
+ path = fs.path + `\` + fs.name
+ } else {
+ path = fs.path
+ }
+ pathp, err := syscall.UTF16PtrFromString(path)
+ if err != nil {
+ return err
+ }
+
+ // Per https://learn.microsoft.com/en-us/windows/win32/fileio/reparse-points-and-file-operations,
+ // “Applications that use the CreateFile function should specify the
+ // FILE_FLAG_OPEN_REPARSE_POINT flag when opening the file if it is a reparse
+ // point.”
+ //
+ // And per https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew,
+ // “If the file is not a reparse point, then this flag is ignored.”
+ //
+ // So we set FILE_FLAG_OPEN_REPARSE_POINT unconditionally, since we want
+ // information about the reparse point itself.
+ //
+ // If the file is a symlink, the symlink target should have already been
+ // resolved when the fileStat was created, so we don't need to worry about
+ // resolving symlink reparse points again here.
+ attrs := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS | syscall.FILE_FLAG_OPEN_REPARSE_POINT)
+
+ h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, attrs, 0)
+ if err != nil {
+ return err
+ }
+ defer syscall.CloseHandle(h)
+ var i syscall.ByHandleFileInformation
+ err = syscall.GetFileInformationByHandle(h, &i)
+ if err != nil {
+ return err
+ }
+ fs.path = ""
+ fs.vol = i.VolumeSerialNumber
+ fs.idxhi = i.FileIndexHigh
+ fs.idxlo = i.FileIndexLow
+ return nil
+}
+
+// saveInfoFromPath saves full path of the file to be used by os.SameFile later,
+// and set name from path.
+func (fs *fileStat) saveInfoFromPath(path string) error {
+ fs.path = path
+ if !isAbs(fs.path) {
+ var err error
+ fs.path, err = syscall.FullPath(fs.path)
+ if err != nil {
+ return &PathError{Op: "FullPath", Path: path, Err: err}
+ }
+ }
+ fs.name = basename(path)
+ return nil
+}
+
+func sameFile(fs1, fs2 *fileStat) bool {
+ e := fs1.loadFileId()
+ if e != nil {
+ return false
+ }
+ e = fs2.loadFileId()
+ if e != nil {
+ return false
+ }
+ return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
+}
diff --git a/src/os/user/cgo_listgroups_unix.go b/src/os/user/cgo_listgroups_unix.go
new file mode 100644
index 0000000..5963695
--- /dev/null
+++ b/src/os/user/cgo_listgroups_unix.go
@@ -0,0 +1,57 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (cgo || darwin) && !osusergo && (darwin || dragonfly || freebsd || (linux && !android) || netbsd || openbsd || (solaris && !illumos))
+
+package user
+
+import (
+ "fmt"
+ "strconv"
+ "unsafe"
+)
+
+const maxGroups = 2048
+
+func listGroups(u *User) ([]string, error) {
+ ug, err := strconv.Atoi(u.Gid)
+ if err != nil {
+ return nil, fmt.Errorf("user: list groups for %s: invalid gid %q", u.Username, u.Gid)
+ }
+ userGID := _C_gid_t(ug)
+ nameC := make([]byte, len(u.Username)+1)
+ copy(nameC, u.Username)
+
+ n := _C_int(256)
+ gidsC := make([]_C_gid_t, n)
+ rv := getGroupList((*_C_char)(unsafe.Pointer(&nameC[0])), userGID, &gidsC[0], &n)
+ if rv == -1 {
+ // Mac is the only Unix that does not set n properly when rv == -1, so
+ // we need to use different logic for Mac vs. the other OS's.
+ if err := groupRetry(u.Username, nameC, userGID, &gidsC, &n); err != nil {
+ return nil, err
+ }
+ }
+ gidsC = gidsC[:n]
+ gids := make([]string, 0, n)
+ for _, g := range gidsC[:n] {
+ gids = append(gids, strconv.Itoa(int(g)))
+ }
+ return gids, nil
+}
+
+// groupRetry retries getGroupList with much larger size for n. The result is
+// stored in gids.
+func groupRetry(username string, name []byte, userGID _C_gid_t, gids *[]_C_gid_t, n *_C_int) error {
+ // More than initial buffer, but now n contains the correct size.
+ if *n > maxGroups {
+ return fmt.Errorf("user: %q is a member of more than %d groups", username, maxGroups)
+ }
+ *gids = make([]_C_gid_t, *n)
+ rv := getGroupList((*_C_char)(unsafe.Pointer(&name[0])), userGID, &(*gids)[0], n)
+ if rv == -1 {
+ return fmt.Errorf("user: list groups for %s failed", username)
+ }
+ return nil
+}
diff --git a/src/os/user/cgo_lookup_cgo.go b/src/os/user/cgo_lookup_cgo.go
new file mode 100644
index 0000000..4f78dca
--- /dev/null
+++ b/src/os/user/cgo_lookup_cgo.go
@@ -0,0 +1,112 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build cgo && !osusergo && unix && !android && !darwin
+
+package user
+
+import (
+ "syscall"
+)
+
+/*
+#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS
+#cgo CFLAGS: -fno-stack-protector
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <string.h>
+
+static struct passwd mygetpwuid_r(int uid, char *buf, size_t buflen, int *found, int *perr) {
+ struct passwd pwd;
+ struct passwd *result;
+ memset (&pwd, 0, sizeof(pwd));
+ *perr = getpwuid_r(uid, &pwd, buf, buflen, &result);
+ *found = result != NULL;
+ return pwd;
+}
+
+static struct passwd mygetpwnam_r(const char *name, char *buf, size_t buflen, int *found, int *perr) {
+ struct passwd pwd;
+ struct passwd *result;
+ memset(&pwd, 0, sizeof(pwd));
+ *perr = getpwnam_r(name, &pwd, buf, buflen, &result);
+ *found = result != NULL;
+ return pwd;
+}
+
+static struct group mygetgrgid_r(int gid, char *buf, size_t buflen, int *found, int *perr) {
+ struct group grp;
+ struct group *result;
+ memset(&grp, 0, sizeof(grp));
+ *perr = getgrgid_r(gid, &grp, buf, buflen, &result);
+ *found = result != NULL;
+ return grp;
+}
+
+static struct group mygetgrnam_r(const char *name, char *buf, size_t buflen, int *found, int *perr) {
+ struct group grp;
+ struct group *result;
+ memset(&grp, 0, sizeof(grp));
+ *perr = getgrnam_r(name, &grp, buf, buflen, &result);
+ *found = result != NULL;
+ return grp;
+}
+*/
+import "C"
+
+type _C_char = C.char
+type _C_int = C.int
+type _C_gid_t = C.gid_t
+type _C_uid_t = C.uid_t
+type _C_size_t = C.size_t
+type _C_struct_group = C.struct_group
+type _C_struct_passwd = C.struct_passwd
+type _C_long = C.long
+
+func _C_pw_uid(p *_C_struct_passwd) _C_uid_t { return p.pw_uid }
+func _C_pw_uidp(p *_C_struct_passwd) *_C_uid_t { return &p.pw_uid }
+func _C_pw_gid(p *_C_struct_passwd) _C_gid_t { return p.pw_gid }
+func _C_pw_gidp(p *_C_struct_passwd) *_C_gid_t { return &p.pw_gid }
+func _C_pw_name(p *_C_struct_passwd) *_C_char { return p.pw_name }
+func _C_pw_gecos(p *_C_struct_passwd) *_C_char { return p.pw_gecos }
+func _C_pw_dir(p *_C_struct_passwd) *_C_char { return p.pw_dir }
+
+func _C_gr_gid(g *_C_struct_group) _C_gid_t { return g.gr_gid }
+func _C_gr_name(g *_C_struct_group) *_C_char { return g.gr_name }
+
+func _C_GoString(p *_C_char) string { return C.GoString(p) }
+
+func _C_getpwnam_r(name *_C_char, buf *_C_char, size _C_size_t) (pwd _C_struct_passwd, found bool, errno syscall.Errno) {
+ var f, e _C_int
+ pwd = C.mygetpwnam_r(name, buf, size, &f, &e)
+ return pwd, f != 0, syscall.Errno(e)
+}
+
+func _C_getpwuid_r(uid _C_uid_t, buf *_C_char, size _C_size_t) (pwd _C_struct_passwd, found bool, errno syscall.Errno) {
+ var f, e _C_int
+ pwd = C.mygetpwuid_r(_C_int(uid), buf, size, &f, &e)
+ return pwd, f != 0, syscall.Errno(e)
+}
+
+func _C_getgrnam_r(name *_C_char, buf *_C_char, size _C_size_t) (grp _C_struct_group, found bool, errno syscall.Errno) {
+ var f, e _C_int
+ grp = C.mygetgrnam_r(name, buf, size, &f, &e)
+ return grp, f != 0, syscall.Errno(e)
+}
+
+func _C_getgrgid_r(gid _C_gid_t, buf *_C_char, size _C_size_t) (grp _C_struct_group, found bool, errno syscall.Errno) {
+ var f, e _C_int
+ grp = C.mygetgrgid_r(_C_int(gid), buf, size, &f, &e)
+ return grp, f != 0, syscall.Errno(e)
+}
+
+const (
+ _C__SC_GETPW_R_SIZE_MAX = C._SC_GETPW_R_SIZE_MAX
+ _C__SC_GETGR_R_SIZE_MAX = C._SC_GETGR_R_SIZE_MAX
+)
+
+func _C_sysconf(key _C_int) _C_long { return C.sysconf(key) }
diff --git a/src/os/user/cgo_lookup_syscall.go b/src/os/user/cgo_lookup_syscall.go
new file mode 100644
index 0000000..321df65
--- /dev/null
+++ b/src/os/user/cgo_lookup_syscall.go
@@ -0,0 +1,65 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !osusergo && darwin
+
+package user
+
+import (
+ "internal/syscall/unix"
+ "syscall"
+)
+
+type _C_char = byte
+type _C_int = int32
+type _C_gid_t = uint32
+type _C_uid_t = uint32
+type _C_size_t = uintptr
+type _C_struct_group = unix.Group
+type _C_struct_passwd = unix.Passwd
+type _C_long = int64
+
+func _C_pw_uid(p *_C_struct_passwd) _C_uid_t { return p.Uid }
+func _C_pw_uidp(p *_C_struct_passwd) *_C_uid_t { return &p.Uid }
+func _C_pw_gid(p *_C_struct_passwd) _C_gid_t { return p.Gid }
+func _C_pw_gidp(p *_C_struct_passwd) *_C_gid_t { return &p.Gid }
+func _C_pw_name(p *_C_struct_passwd) *_C_char { return p.Name }
+func _C_pw_gecos(p *_C_struct_passwd) *_C_char { return p.Gecos }
+func _C_pw_dir(p *_C_struct_passwd) *_C_char { return p.Dir }
+
+func _C_gr_gid(g *_C_struct_group) _C_gid_t { return g.Gid }
+func _C_gr_name(g *_C_struct_group) *_C_char { return g.Name }
+
+func _C_GoString(p *_C_char) string { return unix.GoString(p) }
+
+func _C_getpwnam_r(name *_C_char, buf *_C_char, size _C_size_t) (pwd _C_struct_passwd, found bool, errno syscall.Errno) {
+ var result *_C_struct_passwd
+ errno = unix.Getpwnam(name, &pwd, buf, size, &result)
+ return pwd, result != nil, errno
+}
+
+func _C_getpwuid_r(uid _C_uid_t, buf *_C_char, size _C_size_t) (pwd _C_struct_passwd, found bool, errno syscall.Errno) {
+ var result *_C_struct_passwd
+ errno = unix.Getpwuid(uid, &pwd, buf, size, &result)
+ return pwd, result != nil, errno
+}
+
+func _C_getgrnam_r(name *_C_char, buf *_C_char, size _C_size_t) (grp _C_struct_group, found bool, errno syscall.Errno) {
+ var result *_C_struct_group
+ errno = unix.Getgrnam(name, &grp, buf, size, &result)
+ return grp, result != nil, errno
+}
+
+func _C_getgrgid_r(gid _C_gid_t, buf *_C_char, size _C_size_t) (grp _C_struct_group, found bool, errno syscall.Errno) {
+ var result *_C_struct_group
+ errno = unix.Getgrgid(gid, &grp, buf, size, &result)
+ return grp, result != nil, errno
+}
+
+const (
+ _C__SC_GETPW_R_SIZE_MAX = unix.SC_GETPW_R_SIZE_MAX
+ _C__SC_GETGR_R_SIZE_MAX = unix.SC_GETGR_R_SIZE_MAX
+)
+
+func _C_sysconf(key _C_int) _C_long { return unix.Sysconf(key) }
diff --git a/src/os/user/cgo_lookup_unix.go b/src/os/user/cgo_lookup_unix.go
new file mode 100644
index 0000000..3735971
--- /dev/null
+++ b/src/os/user/cgo_lookup_unix.go
@@ -0,0 +1,200 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (cgo || darwin) && !osusergo && unix && !android
+
+package user
+
+import (
+ "fmt"
+ "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+func current() (*User, error) {
+ return lookupUnixUid(syscall.Getuid())
+}
+
+func lookupUser(username string) (*User, error) {
+ var pwd _C_struct_passwd
+ var found bool
+ nameC := make([]byte, len(username)+1)
+ copy(nameC, username)
+
+ err := retryWithBuffer(userBuffer, func(buf []byte) syscall.Errno {
+ var errno syscall.Errno
+ pwd, found, errno = _C_getpwnam_r((*_C_char)(unsafe.Pointer(&nameC[0])),
+ (*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
+ return errno
+ })
+ if err != nil {
+ return nil, fmt.Errorf("user: lookup username %s: %v", username, err)
+ }
+ if !found {
+ return nil, UnknownUserError(username)
+ }
+ return buildUser(&pwd), err
+}
+
+func lookupUserId(uid string) (*User, error) {
+ i, e := strconv.Atoi(uid)
+ if e != nil {
+ return nil, e
+ }
+ return lookupUnixUid(i)
+}
+
+func lookupUnixUid(uid int) (*User, error) {
+ var pwd _C_struct_passwd
+ var found bool
+
+ err := retryWithBuffer(userBuffer, func(buf []byte) syscall.Errno {
+ var errno syscall.Errno
+ pwd, found, errno = _C_getpwuid_r(_C_uid_t(uid),
+ (*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
+ return errno
+ })
+ if err != nil {
+ return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err)
+ }
+ if !found {
+ return nil, UnknownUserIdError(uid)
+ }
+ return buildUser(&pwd), nil
+}
+
+func buildUser(pwd *_C_struct_passwd) *User {
+ u := &User{
+ Uid: strconv.FormatUint(uint64(_C_pw_uid(pwd)), 10),
+ Gid: strconv.FormatUint(uint64(_C_pw_gid(pwd)), 10),
+ Username: _C_GoString(_C_pw_name(pwd)),
+ Name: _C_GoString(_C_pw_gecos(pwd)),
+ HomeDir: _C_GoString(_C_pw_dir(pwd)),
+ }
+ // The pw_gecos field isn't quite standardized. Some docs
+ // say: "It is expected to be a comma separated list of
+ // personal data where the first item is the full name of the
+ // user."
+ u.Name, _, _ = strings.Cut(u.Name, ",")
+ return u
+}
+
+func lookupGroup(groupname string) (*Group, error) {
+ var grp _C_struct_group
+ var found bool
+
+ cname := make([]byte, len(groupname)+1)
+ copy(cname, groupname)
+
+ err := retryWithBuffer(groupBuffer, func(buf []byte) syscall.Errno {
+ var errno syscall.Errno
+ grp, found, errno = _C_getgrnam_r((*_C_char)(unsafe.Pointer(&cname[0])),
+ (*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
+ return errno
+ })
+ if err != nil {
+ return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err)
+ }
+ if !found {
+ return nil, UnknownGroupError(groupname)
+ }
+ return buildGroup(&grp), nil
+}
+
+func lookupGroupId(gid string) (*Group, error) {
+ i, e := strconv.Atoi(gid)
+ if e != nil {
+ return nil, e
+ }
+ return lookupUnixGid(i)
+}
+
+func lookupUnixGid(gid int) (*Group, error) {
+ var grp _C_struct_group
+ var found bool
+
+ err := retryWithBuffer(groupBuffer, func(buf []byte) syscall.Errno {
+ var errno syscall.Errno
+ grp, found, errno = _C_getgrgid_r(_C_gid_t(gid),
+ (*_C_char)(unsafe.Pointer(&buf[0])), _C_size_t(len(buf)))
+ return syscall.Errno(errno)
+ })
+ if err != nil {
+ return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err)
+ }
+ if !found {
+ return nil, UnknownGroupIdError(strconv.Itoa(gid))
+ }
+ return buildGroup(&grp), nil
+}
+
+func buildGroup(grp *_C_struct_group) *Group {
+ g := &Group{
+ Gid: strconv.Itoa(int(_C_gr_gid(grp))),
+ Name: _C_GoString(_C_gr_name(grp)),
+ }
+ return g
+}
+
+type bufferKind _C_int
+
+var (
+ userBuffer = bufferKind(_C__SC_GETPW_R_SIZE_MAX)
+ groupBuffer = bufferKind(_C__SC_GETGR_R_SIZE_MAX)
+)
+
+func (k bufferKind) initialSize() _C_size_t {
+ sz := _C_sysconf(_C_int(k))
+ if sz == -1 {
+ // DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
+ // Additionally, not all Linux systems have it, either. For
+ // example, the musl libc returns -1.
+ return 1024
+ }
+ if !isSizeReasonable(int64(sz)) {
+ // Truncate. If this truly isn't enough, retryWithBuffer will error on the first run.
+ return maxBufferSize
+ }
+ return _C_size_t(sz)
+}
+
+// retryWithBuffer repeatedly calls f(), increasing the size of the
+// buffer each time, until f succeeds, fails with a non-ERANGE error,
+// or the buffer exceeds a reasonable limit.
+func retryWithBuffer(startSize bufferKind, f func([]byte) syscall.Errno) error {
+ buf := make([]byte, startSize)
+ for {
+ errno := f(buf)
+ if errno == 0 {
+ return nil
+ } else if runtime.GOOS == "aix" && errno+1 == 0 {
+ // On AIX getpwuid_r appears to return -1,
+ // not ERANGE, on buffer overflow.
+ } else if errno != syscall.ERANGE {
+ return errno
+ }
+ newSize := len(buf) * 2
+ if !isSizeReasonable(int64(newSize)) {
+ return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize)
+ }
+ buf = make([]byte, newSize)
+ }
+}
+
+const maxBufferSize = 1 << 20
+
+func isSizeReasonable(sz int64) bool {
+ return sz > 0 && sz <= maxBufferSize
+}
+
+// Because we can't use cgo in tests:
+func structPasswdForNegativeTest() _C_struct_passwd {
+ sp := _C_struct_passwd{}
+ *_C_pw_uidp(&sp) = 1<<32 - 2
+ *_C_pw_gidp(&sp) = 1<<32 - 3
+ return sp
+}
diff --git a/src/os/user/cgo_unix_test.go b/src/os/user/cgo_unix_test.go
new file mode 100644
index 0000000..6d16aa2
--- /dev/null
+++ b/src/os/user/cgo_unix_test.go
@@ -0,0 +1,23 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (darwin || dragonfly || freebsd || (!android && linux) || netbsd || openbsd || solaris) && cgo && !osusergo
+
+package user
+
+import (
+ "testing"
+)
+
+// Issue 22739
+func TestNegativeUid(t *testing.T) {
+ sp := structPasswdForNegativeTest()
+ u := buildUser(&sp)
+ if g, w := u.Uid, "4294967294"; g != w {
+ t.Errorf("Uid = %q; want %q", g, w)
+ }
+ if g, w := u.Gid, "4294967293"; g != w {
+ t.Errorf("Gid = %q; want %q", g, w)
+ }
+}
diff --git a/src/os/user/cgo_user_test.go b/src/os/user/cgo_user_test.go
new file mode 100644
index 0000000..0458495
--- /dev/null
+++ b/src/os/user/cgo_user_test.go
@@ -0,0 +1,11 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build cgo && !osusergo
+
+package user
+
+func init() {
+ hasCgo = true
+}
diff --git a/src/os/user/getgrouplist_syscall.go b/src/os/user/getgrouplist_syscall.go
new file mode 100644
index 0000000..41b64fc
--- /dev/null
+++ b/src/os/user/getgrouplist_syscall.go
@@ -0,0 +1,19 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !osusergo && darwin
+
+package user
+
+import (
+ "internal/syscall/unix"
+)
+
+func getGroupList(name *_C_char, userGID _C_gid_t, gids *_C_gid_t, n *_C_int) _C_int {
+ err := unix.Getgrouplist(name, userGID, gids, n)
+ if err != nil {
+ return -1
+ }
+ return 0
+}
diff --git a/src/os/user/getgrouplist_unix.go b/src/os/user/getgrouplist_unix.go
new file mode 100644
index 0000000..fb482d3
--- /dev/null
+++ b/src/os/user/getgrouplist_unix.go
@@ -0,0 +1,22 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build cgo && !osusergo && (dragonfly || freebsd || (!android && linux) || netbsd || openbsd || (solaris && !illumos))
+
+package user
+
+/*
+#include <unistd.h>
+#include <sys/types.h>
+#include <grp.h>
+
+static int mygetgrouplist(const char* user, gid_t group, gid_t* groups, int* ngroups) {
+ return getgrouplist(user, group, groups, ngroups);
+}
+*/
+import "C"
+
+func getGroupList(name *_C_char, userGID _C_gid_t, gids *_C_gid_t, n *_C_int) _C_int {
+ return C.mygetgrouplist(name, userGID, gids, n)
+}
diff --git a/src/os/user/listgroups_stub.go b/src/os/user/listgroups_stub.go
new file mode 100644
index 0000000..aa7df93
--- /dev/null
+++ b/src/os/user/listgroups_stub.go
@@ -0,0 +1,19 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build android
+
+package user
+
+import (
+ "errors"
+)
+
+func init() {
+ groupListImplemented = false
+}
+
+func listGroups(*User) ([]string, error) {
+ return nil, errors.New("user: list groups not implemented")
+}
diff --git a/src/os/user/listgroups_unix.go b/src/os/user/listgroups_unix.go
new file mode 100644
index 0000000..67bd8a7
--- /dev/null
+++ b/src/os/user/listgroups_unix.go
@@ -0,0 +1,109 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ((darwin || dragonfly || freebsd || (js && wasm) || wasip1 || (!android && linux) || netbsd || openbsd || solaris) && ((!cgo && !darwin) || osusergo)) || aix || illumos
+
+package user
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strconv"
+)
+
+func listGroupsFromReader(u *User, r io.Reader) ([]string, error) {
+ if u.Username == "" {
+ return nil, errors.New("user: list groups: empty username")
+ }
+ primaryGid, err := strconv.Atoi(u.Gid)
+ if err != nil {
+ return nil, fmt.Errorf("user: list groups for %s: invalid gid %q", u.Username, u.Gid)
+ }
+
+ userCommas := []byte("," + u.Username + ",") // ,john,
+ userFirst := userCommas[1:] // john,
+ userLast := userCommas[:len(userCommas)-1] // ,john
+ userOnly := userCommas[1 : len(userCommas)-1] // john
+
+ // Add primary Gid first.
+ groups := []string{u.Gid}
+
+ rd := bufio.NewReader(r)
+ done := false
+ for !done {
+ line, err := rd.ReadBytes('\n')
+ if err != nil {
+ if err == io.EOF {
+ done = true
+ } else {
+ return groups, err
+ }
+ }
+
+ // Look for username in the list of users. If user is found,
+ // append the GID to the groups slice.
+
+ // There's no spec for /etc/passwd or /etc/group, but we try to follow
+ // the same rules as the glibc parser, which allows comments and blank
+ // space at the beginning of a line.
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 || line[0] == '#' ||
+ // If you search for a gid in a row where the group
+ // name (the first field) starts with "+" or "-",
+ // glibc fails to find the record, and so should we.
+ line[0] == '+' || line[0] == '-' {
+ continue
+ }
+
+ // Format of /etc/group is
+ // groupname:password:GID:user_list
+ // for example
+ // wheel:x:10:john,paul,jack
+ // tcpdump:x:72:
+ listIdx := bytes.LastIndexByte(line, ':')
+ if listIdx == -1 || listIdx == len(line)-1 {
+ // No commas, or empty group list.
+ continue
+ }
+ if bytes.Count(line[:listIdx], colon) != 2 {
+ // Incorrect number of colons.
+ continue
+ }
+ list := line[listIdx+1:]
+ // Check the list for user without splitting or copying.
+ if !(bytes.Equal(list, userOnly) || bytes.HasPrefix(list, userFirst) || bytes.HasSuffix(list, userLast) || bytes.Contains(list, userCommas)) {
+ continue
+ }
+
+ // groupname:password:GID
+ parts := bytes.Split(line[:listIdx], colon)
+ if len(parts) != 3 || len(parts[0]) == 0 {
+ continue
+ }
+ gid := string(parts[2])
+ // Make sure it's numeric and not the same as primary GID.
+ numGid, err := strconv.Atoi(gid)
+ if err != nil || numGid == primaryGid {
+ continue
+ }
+
+ groups = append(groups, gid)
+ }
+
+ return groups, nil
+}
+
+func listGroups(u *User) ([]string, error) {
+ f, err := os.Open(groupFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ return listGroupsFromReader(u, f)
+}
diff --git a/src/os/user/listgroups_unix_test.go b/src/os/user/listgroups_unix_test.go
new file mode 100644
index 0000000..ae50319
--- /dev/null
+++ b/src/os/user/listgroups_unix_test.go
@@ -0,0 +1,107 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ((darwin || dragonfly || freebsd || (js && wasm) || wasip1 || (!android && linux) || netbsd || openbsd || solaris) && ((!cgo && !darwin) || osusergo)) || aix || illumos
+
+package user
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+ "testing"
+)
+
+var testGroupFile = `# See the opendirectoryd(8) man page for additional
+# information about Open Directory.
+##
+nobody:*:-2:
+nogroup:*:-1:
+wheel:*:0:root
+emptyid:*::root
+invalidgid:*:notanumber:root
++plussign:*:20:root
+-minussign:*:21:root
+# Next line is invalid (empty group name)
+:*:22:root
+
+daemon:*:1:root
+ indented:*:7:root
+# comment:*:4:found
+ # comment:*:4:found
+kmem:*:2:root
+manymembers:x:777:jill,jody,john,jack,jov,user777
+` + largeGroup()
+
+func largeGroup() (res string) {
+ var b strings.Builder
+ b.WriteString("largegroup:x:1000:user1")
+ for i := 2; i <= 7500; i++ {
+ fmt.Fprintf(&b, ",user%d", i)
+ }
+ return b.String()
+}
+
+var listGroupsTests = []struct {
+ // input
+ in string
+ user string
+ gid string
+ // output
+ gids []string
+ err bool
+}{
+ {in: testGroupFile, user: "root", gid: "0", gids: []string{"0", "1", "2", "7"}},
+ {in: testGroupFile, user: "jill", gid: "33", gids: []string{"33", "777"}},
+ {in: testGroupFile, user: "jody", gid: "34", gids: []string{"34", "777"}},
+ {in: testGroupFile, user: "john", gid: "35", gids: []string{"35", "777"}},
+ {in: testGroupFile, user: "jov", gid: "37", gids: []string{"37", "777"}},
+ {in: testGroupFile, user: "user777", gid: "7", gids: []string{"7", "777", "1000"}},
+ {in: testGroupFile, user: "user1111", gid: "1111", gids: []string{"1111", "1000"}},
+ {in: testGroupFile, user: "user1000", gid: "1000", gids: []string{"1000"}},
+ {in: testGroupFile, user: "user7500", gid: "7500", gids: []string{"1000", "7500"}},
+ {in: testGroupFile, user: "no-such-user", gid: "2345", gids: []string{"2345"}},
+ {in: "", user: "no-such-user", gid: "2345", gids: []string{"2345"}},
+ // Error cases.
+ {in: "", user: "", gid: "2345", err: true},
+ {in: "", user: "joanna", gid: "bad", err: true},
+}
+
+func TestListGroups(t *testing.T) {
+ for _, tc := range listGroupsTests {
+ u := &User{Username: tc.user, Gid: tc.gid}
+ got, err := listGroupsFromReader(u, strings.NewReader(tc.in))
+ if tc.err {
+ if err == nil {
+ t.Errorf("listGroups(%q): got nil; want error", tc.user)
+ }
+ continue // no more checks
+ }
+ if err != nil {
+ t.Errorf("listGroups(%q): got %v error, want nil", tc.user, err)
+ continue // no more checks
+ }
+ checkSameIDs(t, got, tc.gids)
+ }
+}
+
+func checkSameIDs(t *testing.T, got, want []string) {
+ t.Helper()
+ if len(got) != len(want) {
+ t.Errorf("ID list mismatch: got %v; want %v", got, want)
+ return
+ }
+ sort.Strings(got)
+ sort.Strings(want)
+ mismatch := -1
+ for i, g := range want {
+ if got[i] != g {
+ mismatch = i
+ break
+ }
+ }
+ if mismatch != -1 {
+ t.Errorf("ID list mismatch (at index %d): got %v; want %v", mismatch, got, want)
+ }
+}
diff --git a/src/os/user/lookup.go b/src/os/user/lookup.go
new file mode 100644
index 0000000..ed33d0c
--- /dev/null
+++ b/src/os/user/lookup.go
@@ -0,0 +1,70 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import "sync"
+
+const (
+ userFile = "/etc/passwd"
+ groupFile = "/etc/group"
+)
+
+var colon = []byte{':'}
+
+// Current returns the current user.
+//
+// The first call will cache the current user information.
+// Subsequent calls will return the cached value and will not reflect
+// changes to the current user.
+func Current() (*User, error) {
+ cache.Do(func() { cache.u, cache.err = current() })
+ if cache.err != nil {
+ return nil, cache.err
+ }
+ u := *cache.u // copy
+ return &u, nil
+}
+
+// cache of the current user
+var cache struct {
+ sync.Once
+ u *User
+ err error
+}
+
+// Lookup looks up a user by username. If the user cannot be found, the
+// returned error is of type UnknownUserError.
+func Lookup(username string) (*User, error) {
+ if u, err := Current(); err == nil && u.Username == username {
+ return u, err
+ }
+ return lookupUser(username)
+}
+
+// LookupId looks up a user by userid. If the user cannot be found, the
+// returned error is of type UnknownUserIdError.
+func LookupId(uid string) (*User, error) {
+ if u, err := Current(); err == nil && u.Uid == uid {
+ return u, err
+ }
+ return lookupUserId(uid)
+}
+
+// LookupGroup looks up a group by name. If the group cannot be found, the
+// returned error is of type UnknownGroupError.
+func LookupGroup(name string) (*Group, error) {
+ return lookupGroup(name)
+}
+
+// LookupGroupId looks up a group by groupid. If the group cannot be found, the
+// returned error is of type UnknownGroupIdError.
+func LookupGroupId(gid string) (*Group, error) {
+ return lookupGroupId(gid)
+}
+
+// GroupIds returns the list of group IDs that the user is a member of.
+func (u *User) GroupIds() ([]string, error) {
+ return listGroups(u)
+}
diff --git a/src/os/user/lookup_android.go b/src/os/user/lookup_android.go
new file mode 100644
index 0000000..0ae31fd
--- /dev/null
+++ b/src/os/user/lookup_android.go
@@ -0,0 +1,25 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build android
+
+package user
+
+import "errors"
+
+func lookupUser(string) (*User, error) {
+ return nil, errors.New("user: Lookup not implemented on android")
+}
+
+func lookupUserId(string) (*User, error) {
+ return nil, errors.New("user: LookupId not implemented on android")
+}
+
+func lookupGroup(string) (*Group, error) {
+ return nil, errors.New("user: LookupGroup not implemented on android")
+}
+
+func lookupGroupId(string) (*Group, error) {
+ return nil, errors.New("user: LookupGroupId not implemented on android")
+}
diff --git a/src/os/user/lookup_plan9.go b/src/os/user/lookup_plan9.go
new file mode 100644
index 0000000..c2aabd5
--- /dev/null
+++ b/src/os/user/lookup_plan9.go
@@ -0,0 +1,67 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+ "fmt"
+ "os"
+ "syscall"
+)
+
+// Partial os/user support on Plan 9.
+// Supports Current(), but not Lookup()/LookupId().
+// The latter two would require parsing /adm/users.
+
+func init() {
+ userImplemented = false
+ groupImplemented = false
+ groupListImplemented = false
+}
+
+var (
+ // unused variables (in this implementation)
+ // modified during test to exercise code paths in the cgo implementation.
+ userBuffer = 0
+ groupBuffer = 0
+)
+
+func current() (*User, error) {
+ ubytes, err := os.ReadFile("/dev/user")
+ if err != nil {
+ return nil, fmt.Errorf("user: %s", err)
+ }
+
+ uname := string(ubytes)
+
+ u := &User{
+ Uid: uname,
+ Gid: uname,
+ Username: uname,
+ Name: uname,
+ HomeDir: os.Getenv("home"),
+ }
+
+ return u, nil
+}
+
+func lookupUser(username string) (*User, error) {
+ return nil, syscall.EPLAN9
+}
+
+func lookupUserId(uid string) (*User, error) {
+ return nil, syscall.EPLAN9
+}
+
+func lookupGroup(groupname string) (*Group, error) {
+ return nil, syscall.EPLAN9
+}
+
+func lookupGroupId(string) (*Group, error) {
+ return nil, syscall.EPLAN9
+}
+
+func listGroups(*User) ([]string, error) {
+ return nil, syscall.EPLAN9
+}
diff --git a/src/os/user/lookup_stubs.go b/src/os/user/lookup_stubs.go
new file mode 100644
index 0000000..89dfe45
--- /dev/null
+++ b/src/os/user/lookup_stubs.go
@@ -0,0 +1,83 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build (!cgo && !darwin && !windows && !plan9) || android || (osusergo && !windows && !plan9)
+
+package user
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strconv"
+)
+
+var (
+ // unused variables (in this implementation)
+ // modified during test to exercise code paths in the cgo implementation.
+ userBuffer = 0
+ groupBuffer = 0
+)
+
+func current() (*User, error) {
+ uid := currentUID()
+ // $USER and /etc/passwd may disagree; prefer the latter if we can get it.
+ // See issue 27524 for more information.
+ u, err := lookupUserId(uid)
+ if err == nil {
+ return u, nil
+ }
+
+ homeDir, _ := os.UserHomeDir()
+ u = &User{
+ Uid: uid,
+ Gid: currentGID(),
+ Username: os.Getenv("USER"),
+ Name: "", // ignored
+ HomeDir: homeDir,
+ }
+ // On Android, return a dummy user instead of failing.
+ switch runtime.GOOS {
+ case "android":
+ if u.Uid == "" {
+ u.Uid = "1"
+ }
+ if u.Username == "" {
+ u.Username = "android"
+ }
+ }
+ // cgo isn't available, but if we found the minimum information
+ // without it, use it:
+ if u.Uid != "" && u.Username != "" && u.HomeDir != "" {
+ return u, nil
+ }
+ var missing string
+ if u.Username == "" {
+ missing = "$USER"
+ }
+ if u.HomeDir == "" {
+ if missing != "" {
+ missing += ", "
+ }
+ missing += "$HOME"
+ }
+ return u, fmt.Errorf("user: Current requires cgo or %s set in environment", missing)
+}
+
+func currentUID() string {
+ if id := os.Getuid(); id >= 0 {
+ return strconv.Itoa(id)
+ }
+ // Note: Windows returns -1, but this file isn't used on
+ // Windows anyway, so this empty return path shouldn't be
+ // used.
+ return ""
+}
+
+func currentGID() string {
+ if id := os.Getgid(); id >= 0 {
+ return strconv.Itoa(id)
+ }
+ return ""
+}
diff --git a/src/os/user/lookup_unix.go b/src/os/user/lookup_unix.go
new file mode 100644
index 0000000..a430826
--- /dev/null
+++ b/src/os/user/lookup_unix.go
@@ -0,0 +1,234 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ((unix && !android) || (js && wasm) || wasip1) && ((!cgo && !darwin) || osusergo)
+
+package user
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+)
+
+// lineFunc returns a value, an error, or (nil, nil) to skip the row.
+type lineFunc func(line []byte) (v any, err error)
+
+// readColonFile parses r as an /etc/group or /etc/passwd style file, running
+// fn for each row. readColonFile returns a value, an error, or (nil, nil) if
+// the end of the file is reached without a match.
+//
+// readCols is the minimum number of colon-separated fields that will be passed
+// to fn; in a long line additional fields may be silently discarded.
+func readColonFile(r io.Reader, fn lineFunc, readCols int) (v any, err error) {
+ rd := bufio.NewReader(r)
+
+ // Read the file line-by-line.
+ for {
+ var isPrefix bool
+ var wholeLine []byte
+
+ // Read the next line. We do so in chunks (as much as reader's
+ // buffer is able to keep), check if we read enough columns
+ // already on each step and store final result in wholeLine.
+ for {
+ var line []byte
+ line, isPrefix, err = rd.ReadLine()
+
+ if err != nil {
+ // We should return (nil, nil) if EOF is reached
+ // without a match.
+ if err == io.EOF {
+ err = nil
+ }
+ return nil, err
+ }
+
+ // Simple common case: line is short enough to fit in a
+ // single reader's buffer.
+ if !isPrefix && len(wholeLine) == 0 {
+ wholeLine = line
+ break
+ }
+
+ wholeLine = append(wholeLine, line...)
+
+ // Check if we read the whole line (or enough columns)
+ // already.
+ if !isPrefix || bytes.Count(wholeLine, []byte{':'}) >= readCols {
+ break
+ }
+ }
+
+ // There's no spec for /etc/passwd or /etc/group, but we try to follow
+ // the same rules as the glibc parser, which allows comments and blank
+ // space at the beginning of a line.
+ wholeLine = bytes.TrimSpace(wholeLine)
+ if len(wholeLine) == 0 || wholeLine[0] == '#' {
+ continue
+ }
+ v, err = fn(wholeLine)
+ if v != nil || err != nil {
+ return
+ }
+
+ // If necessary, skip the rest of the line
+ for ; isPrefix; _, isPrefix, err = rd.ReadLine() {
+ if err != nil {
+ // We should return (nil, nil) if EOF is reached without a match.
+ if err == io.EOF {
+ err = nil
+ }
+ return nil, err
+ }
+ }
+ }
+}
+
+func matchGroupIndexValue(value string, idx int) lineFunc {
+ var leadColon string
+ if idx > 0 {
+ leadColon = ":"
+ }
+ substr := []byte(leadColon + value + ":")
+ return func(line []byte) (v any, err error) {
+ if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 {
+ return
+ }
+ // wheel:*:0:root
+ parts := strings.SplitN(string(line), ":", 4)
+ if len(parts) < 4 || parts[0] == "" || parts[idx] != value ||
+ // If the file contains +foo and you search for "foo", glibc
+ // returns an "invalid argument" error. Similarly, if you search
+ // for a gid for a row where the group name starts with "+" or "-",
+ // glibc fails to find the record.
+ parts[0][0] == '+' || parts[0][0] == '-' {
+ return
+ }
+ if _, err := strconv.Atoi(parts[2]); err != nil {
+ return nil, nil
+ }
+ return &Group{Name: parts[0], Gid: parts[2]}, nil
+ }
+}
+
+func findGroupId(id string, r io.Reader) (*Group, error) {
+ if v, err := readColonFile(r, matchGroupIndexValue(id, 2), 3); err != nil {
+ return nil, err
+ } else if v != nil {
+ return v.(*Group), nil
+ }
+ return nil, UnknownGroupIdError(id)
+}
+
+func findGroupName(name string, r io.Reader) (*Group, error) {
+ if v, err := readColonFile(r, matchGroupIndexValue(name, 0), 3); err != nil {
+ return nil, err
+ } else if v != nil {
+ return v.(*Group), nil
+ }
+ return nil, UnknownGroupError(name)
+}
+
+// returns a *User for a row if that row's has the given value at the
+// given index.
+func matchUserIndexValue(value string, idx int) lineFunc {
+ var leadColon string
+ if idx > 0 {
+ leadColon = ":"
+ }
+ substr := []byte(leadColon + value + ":")
+ return func(line []byte) (v any, err error) {
+ if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 6 {
+ return
+ }
+ // kevin:x:1005:1006::/home/kevin:/usr/bin/zsh
+ parts := strings.SplitN(string(line), ":", 7)
+ if len(parts) < 6 || parts[idx] != value || parts[0] == "" ||
+ parts[0][0] == '+' || parts[0][0] == '-' {
+ return
+ }
+ if _, err := strconv.Atoi(parts[2]); err != nil {
+ return nil, nil
+ }
+ if _, err := strconv.Atoi(parts[3]); err != nil {
+ return nil, nil
+ }
+ u := &User{
+ Username: parts[0],
+ Uid: parts[2],
+ Gid: parts[3],
+ Name: parts[4],
+ HomeDir: parts[5],
+ }
+ // The pw_gecos field isn't quite standardized. Some docs
+ // say: "It is expected to be a comma separated list of
+ // personal data where the first item is the full name of the
+ // user."
+ u.Name, _, _ = strings.Cut(u.Name, ",")
+ return u, nil
+ }
+}
+
+func findUserId(uid string, r io.Reader) (*User, error) {
+ i, e := strconv.Atoi(uid)
+ if e != nil {
+ return nil, errors.New("user: invalid userid " + uid)
+ }
+ if v, err := readColonFile(r, matchUserIndexValue(uid, 2), 6); err != nil {
+ return nil, err
+ } else if v != nil {
+ return v.(*User), nil
+ }
+ return nil, UnknownUserIdError(i)
+}
+
+func findUsername(name string, r io.Reader) (*User, error) {
+ if v, err := readColonFile(r, matchUserIndexValue(name, 0), 6); err != nil {
+ return nil, err
+ } else if v != nil {
+ return v.(*User), nil
+ }
+ return nil, UnknownUserError(name)
+}
+
+func lookupGroup(groupname string) (*Group, error) {
+ f, err := os.Open(groupFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return findGroupName(groupname, f)
+}
+
+func lookupGroupId(id string) (*Group, error) {
+ f, err := os.Open(groupFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return findGroupId(id, f)
+}
+
+func lookupUser(username string) (*User, error) {
+ f, err := os.Open(userFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return findUsername(username, f)
+}
+
+func lookupUserId(uid string) (*User, error) {
+ f, err := os.Open(userFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return findUserId(uid, f)
+}
diff --git a/src/os/user/lookup_unix_test.go b/src/os/user/lookup_unix_test.go
new file mode 100644
index 0000000..78b3392
--- /dev/null
+++ b/src/os/user/lookup_unix_test.go
@@ -0,0 +1,259 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix && !android && !cgo && !darwin
+
+package user
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+var groupTests = []struct {
+ in string
+ name string
+ gid string
+}{
+ {testGroupFile, "nobody", "-2"},
+ {testGroupFile, "kmem", "2"},
+ {testGroupFile, "notinthefile", ""},
+ {testGroupFile, "comment", ""},
+ {testGroupFile, "plussign", ""},
+ {testGroupFile, "+plussign", ""},
+ {testGroupFile, "-minussign", ""},
+ {testGroupFile, "minussign", ""},
+ {testGroupFile, "emptyid", ""},
+ {testGroupFile, "invalidgid", ""},
+ {testGroupFile, "indented", "7"},
+ {testGroupFile, "# comment", ""},
+ {testGroupFile, "largegroup", "1000"},
+ {testGroupFile, "manymembers", "777"},
+ {"", "emptyfile", ""},
+}
+
+func TestFindGroupName(t *testing.T) {
+ for _, tt := range groupTests {
+ got, err := findGroupName(tt.name, strings.NewReader(tt.in))
+ if tt.gid == "" {
+ if err == nil {
+ t.Errorf("findGroupName(%s): got nil error, expected err", tt.name)
+ continue
+ }
+ switch terr := err.(type) {
+ case UnknownGroupError:
+ if terr.Error() != "group: unknown group "+tt.name {
+ t.Errorf("findGroupName(%s): got %v, want %v", tt.name, terr, tt.name)
+ }
+ default:
+ t.Errorf("findGroupName(%s): got unexpected error %v", tt.name, terr)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("findGroupName(%s): got unexpected error %v", tt.name, err)
+ }
+ if got.Gid != tt.gid {
+ t.Errorf("findGroupName(%s): got gid %v, want %s", tt.name, got.Gid, tt.gid)
+ }
+ if got.Name != tt.name {
+ t.Errorf("findGroupName(%s): got name %s, want %s", tt.name, got.Name, tt.name)
+ }
+ }
+ }
+}
+
+var groupIdTests = []struct {
+ in string
+ gid string
+ name string
+}{
+ {testGroupFile, "-2", "nobody"},
+ {testGroupFile, "2", "kmem"},
+ {testGroupFile, "notinthefile", ""},
+ {testGroupFile, "comment", ""},
+ {testGroupFile, "7", "indented"},
+ {testGroupFile, "4", ""},
+ {testGroupFile, "20", ""}, // row starts with a plus
+ {testGroupFile, "21", ""}, // row starts with a minus
+ {"", "emptyfile", ""},
+}
+
+func TestFindGroupId(t *testing.T) {
+ for _, tt := range groupIdTests {
+ got, err := findGroupId(tt.gid, strings.NewReader(tt.in))
+ if tt.name == "" {
+ if err == nil {
+ t.Errorf("findGroupId(%s): got nil error, expected err", tt.gid)
+ continue
+ }
+ switch terr := err.(type) {
+ case UnknownGroupIdError:
+ if terr.Error() != "group: unknown groupid "+tt.gid {
+ t.Errorf("findGroupId(%s): got %v, want %v", tt.name, terr, tt.name)
+ }
+ default:
+ t.Errorf("findGroupId(%s): got unexpected error %v", tt.name, terr)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("findGroupId(%s): got unexpected error %v", tt.name, err)
+ }
+ if got.Gid != tt.gid {
+ t.Errorf("findGroupId(%s): got gid %v, want %s", tt.name, got.Gid, tt.gid)
+ }
+ if got.Name != tt.name {
+ t.Errorf("findGroupId(%s): got name %s, want %s", tt.name, got.Name, tt.name)
+ }
+ }
+ }
+}
+
+const testUserFile = ` # Example user file
+root:x:0:0:root:/root:/bin/bash
+daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
+bin:x:2:3:bin:/bin:/usr/sbin/nologin
+ indented:x:3:3:indented:/dev:/usr/sbin/nologin
+sync:x:4:65534:sync:/bin:/bin/sync
+negative:x:-5:60:games:/usr/games:/usr/sbin/nologin
+man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
+allfields:x:6:12:mansplit,man2,man3,man4:/home/allfields:/usr/sbin/nologin
++plussign:x:8:10:man:/var/cache/man:/usr/sbin/nologin
+-minussign:x:9:10:man:/var/cache/man:/usr/sbin/nologin
+
+malformed:x:27:12 # more:colons:after:comment
+
+struid:x:notanumber:12 # more:colons:after:comment
+
+# commented:x:28:12:commented:/var/cache/man:/usr/sbin/nologin
+ # commentindented:x:29:12:commentindented:/var/cache/man:/usr/sbin/nologin
+
+struid2:x:30:badgid:struid2name:/home/struid:/usr/sbin/nologin
+`
+
+var userIdTests = []struct {
+ in string
+ uid string
+ name string
+}{
+ {testUserFile, "-5", "negative"},
+ {testUserFile, "2", "bin"},
+ {testUserFile, "100", ""}, // not in the file
+ {testUserFile, "8", ""}, // plus sign, glibc doesn't find it
+ {testUserFile, "9", ""}, // minus sign, glibc doesn't find it
+ {testUserFile, "27", ""}, // malformed
+ {testUserFile, "28", ""}, // commented out
+ {testUserFile, "29", ""}, // commented out, indented
+ {testUserFile, "3", "indented"},
+ {testUserFile, "30", ""}, // the Gid is not valid, shouldn't match
+ {"", "1", ""},
+}
+
+func TestInvalidUserId(t *testing.T) {
+ _, err := findUserId("notanumber", strings.NewReader(""))
+ if err == nil {
+ t.Fatalf("findUserId('notanumber'): got nil error")
+ }
+ if want := "user: invalid userid notanumber"; err.Error() != want {
+ t.Errorf("findUserId('notanumber'): got %v, want %s", err, want)
+ }
+}
+
+func TestLookupUserId(t *testing.T) {
+ for _, tt := range userIdTests {
+ got, err := findUserId(tt.uid, strings.NewReader(tt.in))
+ if tt.name == "" {
+ if err == nil {
+ t.Errorf("findUserId(%s): got nil error, expected err", tt.uid)
+ continue
+ }
+ switch terr := err.(type) {
+ case UnknownUserIdError:
+ if want := "user: unknown userid " + tt.uid; terr.Error() != want {
+ t.Errorf("findUserId(%s): got %v, want %v", tt.name, terr, want)
+ }
+ default:
+ t.Errorf("findUserId(%s): got unexpected error %v", tt.name, terr)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("findUserId(%s): got unexpected error %v", tt.name, err)
+ }
+ if got.Uid != tt.uid {
+ t.Errorf("findUserId(%s): got uid %v, want %s", tt.name, got.Uid, tt.uid)
+ }
+ if got.Username != tt.name {
+ t.Errorf("findUserId(%s): got name %s, want %s", tt.name, got.Username, tt.name)
+ }
+ }
+ }
+}
+
+func TestLookupUserPopulatesAllFields(t *testing.T) {
+ u, err := findUsername("allfields", strings.NewReader(testUserFile))
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := &User{
+ Username: "allfields",
+ Uid: "6",
+ Gid: "12",
+ Name: "mansplit",
+ HomeDir: "/home/allfields",
+ }
+ if !reflect.DeepEqual(u, want) {
+ t.Errorf("findUsername: got %#v, want %#v", u, want)
+ }
+}
+
+var userTests = []struct {
+ in string
+ name string
+ uid string
+}{
+ {testUserFile, "negative", "-5"},
+ {testUserFile, "bin", "2"},
+ {testUserFile, "notinthefile", ""},
+ {testUserFile, "indented", "3"},
+ {testUserFile, "plussign", ""},
+ {testUserFile, "+plussign", ""},
+ {testUserFile, "minussign", ""},
+ {testUserFile, "-minussign", ""},
+ {testUserFile, " indented", ""},
+ {testUserFile, "commented", ""},
+ {testUserFile, "commentindented", ""},
+ {testUserFile, "malformed", ""},
+ {testUserFile, "# commented", ""},
+ {"", "emptyfile", ""},
+}
+
+func TestLookupUser(t *testing.T) {
+ for _, tt := range userTests {
+ got, err := findUsername(tt.name, strings.NewReader(tt.in))
+ if tt.uid == "" {
+ if err == nil {
+ t.Errorf("lookupUser(%s): got nil error, expected err", tt.uid)
+ continue
+ }
+ switch terr := err.(type) {
+ case UnknownUserError:
+ if want := "user: unknown user " + tt.name; terr.Error() != want {
+ t.Errorf("lookupUser(%s): got %v, want %v", tt.name, terr, want)
+ }
+ default:
+ t.Errorf("lookupUser(%s): got unexpected error %v", tt.name, terr)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("lookupUser(%s): got unexpected error %v", tt.name, err)
+ }
+ if got.Uid != tt.uid {
+ t.Errorf("lookupUser(%s): got uid %v, want %s", tt.name, got.Uid, tt.uid)
+ }
+ if got.Username != tt.name {
+ t.Errorf("lookupUser(%s): got name %s, want %s", tt.name, got.Username, tt.name)
+ }
+ }
+ }
+}
diff --git a/src/os/user/lookup_windows.go b/src/os/user/lookup_windows.go
new file mode 100644
index 0000000..e64b8ae
--- /dev/null
+++ b/src/os/user/lookup_windows.go
@@ -0,0 +1,392 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+ "fmt"
+ "internal/syscall/windows"
+ "internal/syscall/windows/registry"
+ "syscall"
+ "unsafe"
+)
+
+func isDomainJoined() (bool, error) {
+ var domain *uint16
+ var status uint32
+ err := syscall.NetGetJoinInformation(nil, &domain, &status)
+ if err != nil {
+ return false, err
+ }
+ syscall.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))
+ return status == syscall.NetSetupDomainName, nil
+}
+
+func lookupFullNameDomain(domainAndUser string) (string, error) {
+ return syscall.TranslateAccountName(domainAndUser,
+ syscall.NameSamCompatible, syscall.NameDisplay, 50)
+}
+
+func lookupFullNameServer(servername, username string) (string, error) {
+ s, e := syscall.UTF16PtrFromString(servername)
+ if e != nil {
+ return "", e
+ }
+ u, e := syscall.UTF16PtrFromString(username)
+ if e != nil {
+ return "", e
+ }
+ var p *byte
+ e = syscall.NetUserGetInfo(s, u, 10, &p)
+ if e != nil {
+ return "", e
+ }
+ defer syscall.NetApiBufferFree(p)
+ i := (*syscall.UserInfo10)(unsafe.Pointer(p))
+ return windows.UTF16PtrToString(i.FullName), nil
+}
+
+func lookupFullName(domain, username, domainAndUser string) (string, error) {
+ joined, err := isDomainJoined()
+ if err == nil && joined {
+ name, err := lookupFullNameDomain(domainAndUser)
+ if err == nil {
+ return name, nil
+ }
+ }
+ name, err := lookupFullNameServer(domain, username)
+ if err == nil {
+ return name, nil
+ }
+ // domain worked neither as a domain nor as a server
+ // could be domain server unavailable
+ // pretend username is fullname
+ return username, nil
+}
+
+// getProfilesDirectory retrieves the path to the root directory
+// where user profiles are stored.
+func getProfilesDirectory() (string, error) {
+ n := uint32(100)
+ for {
+ b := make([]uint16, n)
+ e := windows.GetProfilesDirectory(&b[0], &n)
+ if e == nil {
+ return syscall.UTF16ToString(b), nil
+ }
+ if e != syscall.ERROR_INSUFFICIENT_BUFFER {
+ return "", e
+ }
+ if n <= uint32(len(b)) {
+ return "", e
+ }
+ }
+}
+
+// lookupUsernameAndDomain obtains the username and domain for usid.
+func lookupUsernameAndDomain(usid *syscall.SID) (username, domain string, e error) {
+ username, domain, t, e := usid.LookupAccount("")
+ if e != nil {
+ return "", "", e
+ }
+ if t != syscall.SidTypeUser {
+ return "", "", fmt.Errorf("user: should be user account type, not %d", t)
+ }
+ return username, domain, nil
+}
+
+// findHomeDirInRegistry finds the user home path based on the uid.
+func findHomeDirInRegistry(uid string) (dir string, e error) {
+ k, e := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+uid, registry.QUERY_VALUE)
+ if e != nil {
+ return "", e
+ }
+ defer k.Close()
+ dir, _, e = k.GetStringValue("ProfileImagePath")
+ if e != nil {
+ return "", e
+ }
+ return dir, nil
+}
+
+// lookupGroupName accepts the name of a group and retrieves the group SID.
+func lookupGroupName(groupname string) (string, error) {
+ sid, _, t, e := syscall.LookupSID("", groupname)
+ if e != nil {
+ return "", e
+ }
+ // https://msdn.microsoft.com/en-us/library/cc245478.aspx#gt_0387e636-5654-4910-9519-1f8326cf5ec0
+ // SidTypeAlias should also be treated as a group type next to SidTypeGroup
+ // and SidTypeWellKnownGroup:
+ // "alias object -> resource group: A group object..."
+ //
+ // Tests show that "Administrators" can be considered of type SidTypeAlias.
+ if t != syscall.SidTypeGroup && t != syscall.SidTypeWellKnownGroup && t != syscall.SidTypeAlias {
+ return "", fmt.Errorf("lookupGroupName: should be group account type, not %d", t)
+ }
+ return sid.String()
+}
+
+// listGroupsForUsernameAndDomain accepts username and domain and retrieves
+// a SID list of the local groups where this user is a member.
+func listGroupsForUsernameAndDomain(username, domain string) ([]string, error) {
+ // Check if both the domain name and user should be used.
+ var query string
+ joined, err := isDomainJoined()
+ if err == nil && joined && len(domain) != 0 {
+ query = domain + `\` + username
+ } else {
+ query = username
+ }
+ q, err := syscall.UTF16PtrFromString(query)
+ if err != nil {
+ return nil, err
+ }
+ var p0 *byte
+ var entriesRead, totalEntries uint32
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370655(v=vs.85).aspx
+ // NetUserGetLocalGroups() would return a list of LocalGroupUserInfo0
+ // elements which hold the names of local groups where the user participates.
+ // The list does not follow any sorting order.
+ //
+ // If no groups can be found for this user, NetUserGetLocalGroups() should
+ // always return the SID of a single group called "None", which
+ // also happens to be the primary group for the local user.
+ err = windows.NetUserGetLocalGroups(nil, q, 0, windows.LG_INCLUDE_INDIRECT, &p0, windows.MAX_PREFERRED_LENGTH, &entriesRead, &totalEntries)
+ if err != nil {
+ return nil, err
+ }
+ defer syscall.NetApiBufferFree(p0)
+ if entriesRead == 0 {
+ return nil, fmt.Errorf("listGroupsForUsernameAndDomain: NetUserGetLocalGroups() returned an empty list for domain: %s, username: %s", domain, username)
+ }
+ entries := (*[1024]windows.LocalGroupUserInfo0)(unsafe.Pointer(p0))[:entriesRead:entriesRead]
+ var sids []string
+ for _, entry := range entries {
+ if entry.Name == nil {
+ continue
+ }
+ sid, err := lookupGroupName(windows.UTF16PtrToString(entry.Name))
+ if err != nil {
+ return nil, err
+ }
+ sids = append(sids, sid)
+ }
+ return sids, nil
+}
+
+func newUser(uid, gid, dir, username, domain string) (*User, error) {
+ domainAndUser := domain + `\` + username
+ name, e := lookupFullName(domain, username, domainAndUser)
+ if e != nil {
+ return nil, e
+ }
+ u := &User{
+ Uid: uid,
+ Gid: gid,
+ Username: domainAndUser,
+ Name: name,
+ HomeDir: dir,
+ }
+ return u, nil
+}
+
+var (
+ // unused variables (in this implementation)
+ // modified during test to exercise code paths in the cgo implementation.
+ userBuffer = 0
+ groupBuffer = 0
+)
+
+func current() (*User, error) {
+ t, e := syscall.OpenCurrentProcessToken()
+ if e != nil {
+ return nil, e
+ }
+ defer t.Close()
+ u, e := t.GetTokenUser()
+ if e != nil {
+ return nil, e
+ }
+ pg, e := t.GetTokenPrimaryGroup()
+ if e != nil {
+ return nil, e
+ }
+ uid, e := u.User.Sid.String()
+ if e != nil {
+ return nil, e
+ }
+ gid, e := pg.PrimaryGroup.String()
+ if e != nil {
+ return nil, e
+ }
+ dir, e := t.GetUserProfileDirectory()
+ if e != nil {
+ return nil, e
+ }
+ username, domain, e := lookupUsernameAndDomain(u.User.Sid)
+ if e != nil {
+ return nil, e
+ }
+ return newUser(uid, gid, dir, username, domain)
+}
+
+// lookupUserPrimaryGroup obtains the primary group SID for a user using this method:
+// https://support.microsoft.com/en-us/help/297951/how-to-use-the-primarygroupid-attribute-to-find-the-primary-group-for
+// The method follows this formula: domainRID + "-" + primaryGroupRID
+func lookupUserPrimaryGroup(username, domain string) (string, error) {
+ // get the domain RID
+ sid, _, t, e := syscall.LookupSID("", domain)
+ if e != nil {
+ return "", e
+ }
+ if t != syscall.SidTypeDomain {
+ return "", fmt.Errorf("lookupUserPrimaryGroup: should be domain account type, not %d", t)
+ }
+ domainRID, e := sid.String()
+ if e != nil {
+ return "", e
+ }
+ // If the user has joined a domain use the RID of the default primary group
+ // called "Domain Users":
+ // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
+ // SID: S-1-5-21domain-513
+ //
+ // The correct way to obtain the primary group of a domain user is
+ // probing the user primaryGroupID attribute in the server Active Directory:
+ // https://msdn.microsoft.com/en-us/library/ms679375(v=vs.85).aspx
+ //
+ // Note that the primary group of domain users should not be modified
+ // on Windows for performance reasons, even if it's possible to do that.
+ // The .NET Developer's Guide to Directory Services Programming - Page 409
+ // https://books.google.bg/books?id=kGApqjobEfsC&lpg=PA410&ots=p7oo-eOQL7&dq=primary%20group%20RID&hl=bg&pg=PA409#v=onepage&q&f=false
+ joined, err := isDomainJoined()
+ if err == nil && joined {
+ return domainRID + "-513", nil
+ }
+ // For non-domain users call NetUserGetInfo() with level 4, which
+ // in this case would not have any network overhead.
+ // The primary group should not change from RID 513 here either
+ // but the group will be called "None" instead:
+ // https://www.adampalmer.me/iodigitalsec/2013/08/10/windows-null-session-enumeration/
+ // "Group 'None' (RID: 513)"
+ u, e := syscall.UTF16PtrFromString(username)
+ if e != nil {
+ return "", e
+ }
+ d, e := syscall.UTF16PtrFromString(domain)
+ if e != nil {
+ return "", e
+ }
+ var p *byte
+ e = syscall.NetUserGetInfo(d, u, 4, &p)
+ if e != nil {
+ return "", e
+ }
+ defer syscall.NetApiBufferFree(p)
+ i := (*windows.UserInfo4)(unsafe.Pointer(p))
+ return fmt.Sprintf("%s-%d", domainRID, i.PrimaryGroupID), nil
+}
+
+func newUserFromSid(usid *syscall.SID) (*User, error) {
+ username, domain, e := lookupUsernameAndDomain(usid)
+ if e != nil {
+ return nil, e
+ }
+ gid, e := lookupUserPrimaryGroup(username, domain)
+ if e != nil {
+ return nil, e
+ }
+ uid, e := usid.String()
+ if e != nil {
+ return nil, e
+ }
+ // If this user has logged in at least once their home path should be stored
+ // in the registry under the specified SID. References:
+ // https://social.technet.microsoft.com/wiki/contents/articles/13895.how-to-remove-a-corrupted-user-profile-from-the-registry.aspx
+ // https://support.asperasoft.com/hc/en-us/articles/216127438-How-to-delete-Windows-user-profiles
+ //
+ // The registry is the most reliable way to find the home path as the user
+ // might have decided to move it outside of the default location,
+ // (e.g. C:\users). Reference:
+ // https://answers.microsoft.com/en-us/windows/forum/windows_7-security/how-do-i-set-a-home-directory-outside-cusers-for-a/aed68262-1bf4-4a4d-93dc-7495193a440f
+ dir, e := findHomeDirInRegistry(uid)
+ if e != nil {
+ // If the home path does not exist in the registry, the user might
+ // have not logged in yet; fall back to using getProfilesDirectory().
+ // Find the username based on a SID and append that to the result of
+ // getProfilesDirectory(). The domain is not relevant here.
+ dir, e = getProfilesDirectory()
+ if e != nil {
+ return nil, e
+ }
+ dir += `\` + username
+ }
+ return newUser(uid, gid, dir, username, domain)
+}
+
+func lookupUser(username string) (*User, error) {
+ sid, _, t, e := syscall.LookupSID("", username)
+ if e != nil {
+ return nil, e
+ }
+ if t != syscall.SidTypeUser {
+ return nil, fmt.Errorf("user: should be user account type, not %d", t)
+ }
+ return newUserFromSid(sid)
+}
+
+func lookupUserId(uid string) (*User, error) {
+ sid, e := syscall.StringToSid(uid)
+ if e != nil {
+ return nil, e
+ }
+ return newUserFromSid(sid)
+}
+
+func lookupGroup(groupname string) (*Group, error) {
+ sid, err := lookupGroupName(groupname)
+ if err != nil {
+ return nil, err
+ }
+ return &Group{Name: groupname, Gid: sid}, nil
+}
+
+func lookupGroupId(gid string) (*Group, error) {
+ sid, err := syscall.StringToSid(gid)
+ if err != nil {
+ return nil, err
+ }
+ groupname, _, t, err := sid.LookupAccount("")
+ if err != nil {
+ return nil, err
+ }
+ if t != syscall.SidTypeGroup && t != syscall.SidTypeWellKnownGroup && t != syscall.SidTypeAlias {
+ return nil, fmt.Errorf("lookupGroupId: should be group account type, not %d", t)
+ }
+ return &Group{Name: groupname, Gid: gid}, nil
+}
+
+func listGroups(user *User) ([]string, error) {
+ sid, err := syscall.StringToSid(user.Uid)
+ if err != nil {
+ return nil, err
+ }
+ username, domain, err := lookupUsernameAndDomain(sid)
+ if err != nil {
+ return nil, err
+ }
+ sids, err := listGroupsForUsernameAndDomain(username, domain)
+ if err != nil {
+ return nil, err
+ }
+ // Add the primary group of the user to the list if it is not already there.
+ // This is done only to comply with the POSIX concept of a primary group.
+ for _, sid := range sids {
+ if sid == user.Gid {
+ return sids, nil
+ }
+ }
+ return append(sids, user.Gid), nil
+}
diff --git a/src/os/user/user.go b/src/os/user/user.go
new file mode 100644
index 0000000..0307d2a
--- /dev/null
+++ b/src/os/user/user.go
@@ -0,0 +1,95 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+/*
+Package user allows user account lookups by name or id.
+
+For most Unix systems, this package has two internal implementations of
+resolving user and group ids to names, and listing supplementary group IDs.
+One is written in pure Go and parses /etc/passwd and /etc/group. The other
+is cgo-based and relies on the standard C library (libc) routines such as
+getpwuid_r, getgrnam_r, and getgrouplist.
+
+When cgo is available, and the required routines are implemented in libc
+for a particular platform, cgo-based (libc-backed) code is used.
+This can be overridden by using osusergo build tag, which enforces
+the pure Go implementation.
+*/
+package user
+
+import (
+ "strconv"
+)
+
+// These may be set to false in init() for a particular platform and/or
+// build flags to let the tests know to skip tests of some features.
+var (
+ userImplemented = true
+ groupImplemented = true
+ groupListImplemented = true
+)
+
+// User represents a user account.
+type User struct {
+ // Uid is the user ID.
+ // On POSIX systems, this is a decimal number representing the uid.
+ // On Windows, this is a security identifier (SID) in a string format.
+ // On Plan 9, this is the contents of /dev/user.
+ Uid string
+ // Gid is the primary group ID.
+ // On POSIX systems, this is a decimal number representing the gid.
+ // On Windows, this is a SID in a string format.
+ // On Plan 9, this is the contents of /dev/user.
+ Gid string
+ // Username is the login name.
+ Username string
+ // Name is the user's real or display name.
+ // It might be blank.
+ // On POSIX systems, this is the first (or only) entry in the GECOS field
+ // list.
+ // On Windows, this is the user's display name.
+ // On Plan 9, this is the contents of /dev/user.
+ Name string
+ // HomeDir is the path to the user's home directory (if they have one).
+ HomeDir string
+}
+
+// Group represents a grouping of users.
+//
+// On POSIX systems Gid contains a decimal number representing the group ID.
+type Group struct {
+ Gid string // group ID
+ Name string // group name
+}
+
+// UnknownUserIdError is returned by LookupId when a user cannot be found.
+type UnknownUserIdError int
+
+func (e UnknownUserIdError) Error() string {
+ return "user: unknown userid " + strconv.Itoa(int(e))
+}
+
+// UnknownUserError is returned by Lookup when
+// a user cannot be found.
+type UnknownUserError string
+
+func (e UnknownUserError) Error() string {
+ return "user: unknown user " + string(e)
+}
+
+// UnknownGroupIdError is returned by LookupGroupId when
+// a group cannot be found.
+type UnknownGroupIdError string
+
+func (e UnknownGroupIdError) Error() string {
+ return "group: unknown groupid " + string(e)
+}
+
+// UnknownGroupError is returned by LookupGroup when
+// a group cannot be found.
+type UnknownGroupError string
+
+func (e UnknownGroupError) Error() string {
+ return "group: unknown group " + string(e)
+}
diff --git a/src/os/user/user_test.go b/src/os/user/user_test.go
new file mode 100644
index 0000000..fa597b7
--- /dev/null
+++ b/src/os/user/user_test.go
@@ -0,0 +1,192 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+ "os"
+ "testing"
+)
+
+var (
+ hasCgo = false
+ hasUSER = os.Getenv("USER") != ""
+ hasHOME = os.Getenv("HOME") != ""
+)
+
+func checkUser(t *testing.T) {
+ t.Helper()
+ if !userImplemented {
+ t.Skip("user: not implemented; skipping tests")
+ }
+}
+
+func TestCurrent(t *testing.T) {
+ old := userBuffer
+ defer func() {
+ userBuffer = old
+ }()
+ userBuffer = 1 // force use of retry code
+ u, err := Current()
+ if err != nil {
+ if hasCgo || (hasUSER && hasHOME) {
+ t.Fatalf("Current: %v (got %#v)", err, u)
+ } else {
+ t.Skipf("skipping: %v", err)
+ }
+ }
+ if u.HomeDir == "" {
+ t.Errorf("didn't get a HomeDir")
+ }
+ if u.Username == "" {
+ t.Errorf("didn't get a username")
+ }
+}
+
+func BenchmarkCurrent(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Current()
+ }
+}
+
+func compare(t *testing.T, want, got *User) {
+ if want.Uid != got.Uid {
+ t.Errorf("got Uid=%q; want %q", got.Uid, want.Uid)
+ }
+ if want.Username != got.Username {
+ t.Errorf("got Username=%q; want %q", got.Username, want.Username)
+ }
+ if want.Name != got.Name {
+ t.Errorf("got Name=%q; want %q", got.Name, want.Name)
+ }
+ if want.HomeDir != got.HomeDir {
+ t.Errorf("got HomeDir=%q; want %q", got.HomeDir, want.HomeDir)
+ }
+ if want.Gid != got.Gid {
+ t.Errorf("got Gid=%q; want %q", got.Gid, want.Gid)
+ }
+}
+
+func TestLookup(t *testing.T) {
+ checkUser(t)
+
+ want, err := Current()
+ if err != nil {
+ if hasCgo || (hasUSER && hasHOME) {
+ t.Fatalf("Current: %v", err)
+ } else {
+ t.Skipf("skipping: %v", err)
+ }
+ }
+
+ // TODO: Lookup() has a fast path that calls Current() and returns if the
+ // usernames match, so this test does not exercise very much. It would be
+ // good to try and test finding a different user than the current user.
+ got, err := Lookup(want.Username)
+ if err != nil {
+ t.Fatalf("Lookup: %v", err)
+ }
+ compare(t, want, got)
+}
+
+func TestLookupId(t *testing.T) {
+ checkUser(t)
+
+ want, err := Current()
+ if err != nil {
+ if hasCgo || (hasUSER && hasHOME) {
+ t.Fatalf("Current: %v", err)
+ } else {
+ t.Skipf("skipping: %v", err)
+ }
+ }
+
+ got, err := LookupId(want.Uid)
+ if err != nil {
+ t.Fatalf("LookupId: %v", err)
+ }
+ compare(t, want, got)
+}
+
+func checkGroup(t *testing.T) {
+ t.Helper()
+ if !groupImplemented {
+ t.Skip("user: group not implemented; skipping test")
+ }
+}
+
+func TestLookupGroup(t *testing.T) {
+ old := groupBuffer
+ defer func() {
+ groupBuffer = old
+ }()
+ groupBuffer = 1 // force use of retry code
+ checkGroup(t)
+
+ user, err := Current()
+ if err != nil {
+ if hasCgo || (hasUSER && hasHOME) {
+ t.Fatalf("Current: %v", err)
+ } else {
+ t.Skipf("skipping: %v", err)
+ }
+ }
+
+ g1, err := LookupGroupId(user.Gid)
+ if err != nil {
+ // NOTE(rsc): Maybe the group isn't defined. That's fine.
+ // On my OS X laptop, rsc logs in with group 5000 even
+ // though there's no name for group 5000. Such is Unix.
+ t.Logf("LookupGroupId(%q): %v", user.Gid, err)
+ return
+ }
+ if g1.Gid != user.Gid {
+ t.Errorf("LookupGroupId(%q).Gid = %s; want %s", user.Gid, g1.Gid, user.Gid)
+ }
+
+ g2, err := LookupGroup(g1.Name)
+ if err != nil {
+ t.Fatalf("LookupGroup(%q): %v", g1.Name, err)
+ }
+ if g1.Gid != g2.Gid || g1.Name != g2.Name {
+ t.Errorf("LookupGroup(%q) = %+v; want %+v", g1.Name, g2, g1)
+ }
+}
+
+func checkGroupList(t *testing.T) {
+ t.Helper()
+ if !groupListImplemented {
+ t.Skip("user: group list not implemented; skipping test")
+ }
+}
+
+func TestGroupIds(t *testing.T) {
+ checkGroupList(t)
+
+ user, err := Current()
+ if err != nil {
+ if hasCgo || (hasUSER && hasHOME) {
+ t.Fatalf("Current: %v", err)
+ } else {
+ t.Skipf("skipping: %v", err)
+ }
+ }
+
+ gids, err := user.GroupIds()
+ if err != nil {
+ t.Fatalf("%+v.GroupIds(): %v", user, err)
+ }
+ if !containsID(gids, user.Gid) {
+ t.Errorf("%+v.GroupIds() = %v; does not contain user GID %s", user, gids, user.Gid)
+ }
+}
+
+func containsID(ids []string, id string) bool {
+ for _, x := range ids {
+ if x == id {
+ return true
+ }
+ }
+ return false
+}
diff --git a/src/os/wait6_dragonfly.go b/src/os/wait6_dragonfly.go
new file mode 100644
index 0000000..cc3af39
--- /dev/null
+++ b/src/os/wait6_dragonfly.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+const _P_PID = 0
+
+func wait6(idtype, id, options int) (status int, errno syscall.Errno) {
+ var status32 int32 // C.int
+ _, _, errno = syscall.Syscall6(syscall.SYS_WAIT6, uintptr(idtype), uintptr(id), uintptr(unsafe.Pointer(&status32)), uintptr(options), 0, 0)
+ return int(status32), errno
+}
diff --git a/src/os/wait6_freebsd64.go b/src/os/wait6_freebsd64.go
new file mode 100644
index 0000000..b2677c5
--- /dev/null
+++ b/src/os/wait6_freebsd64.go
@@ -0,0 +1,20 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build freebsd && (amd64 || arm64 || riscv64)
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+const _P_PID = 0
+
+func wait6(idtype, id, options int) (status int, errno syscall.Errno) {
+ var status32 int32 // C.int
+ _, _, errno = syscall.Syscall6(syscall.SYS_WAIT6, uintptr(idtype), uintptr(id), uintptr(unsafe.Pointer(&status32)), uintptr(options), 0, 0)
+ return int(status32), errno
+}
diff --git a/src/os/wait6_freebsd_386.go b/src/os/wait6_freebsd_386.go
new file mode 100644
index 0000000..30b01c5
--- /dev/null
+++ b/src/os/wait6_freebsd_386.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+const _P_PID = 0
+
+func wait6(idtype, id, options int) (status int, errno syscall.Errno) {
+ // freebsd32_wait6_args{ idtype, id1, id2, status, options, wrusage, info }
+ _, _, errno = syscall.Syscall9(syscall.SYS_WAIT6, uintptr(idtype), uintptr(id), 0, uintptr(unsafe.Pointer(&status)), uintptr(options), 0, 0, 0, 0)
+ return status, errno
+}
diff --git a/src/os/wait6_freebsd_arm.go b/src/os/wait6_freebsd_arm.go
new file mode 100644
index 0000000..0fd8af0
--- /dev/null
+++ b/src/os/wait6_freebsd_arm.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+const _P_PID = 0
+
+func wait6(idtype, id, options int) (status int, errno syscall.Errno) {
+ // freebsd32_wait6_args{ idtype, pad, id1, id2, status, options, wrusage, info }
+ _, _, errno = syscall.Syscall9(syscall.SYS_WAIT6, uintptr(idtype), 0, uintptr(id), 0, uintptr(unsafe.Pointer(&status)), uintptr(options), 0, 0, 0)
+ return status, errno
+}
diff --git a/src/os/wait6_netbsd.go b/src/os/wait6_netbsd.go
new file mode 100644
index 0000000..0bbb73d
--- /dev/null
+++ b/src/os/wait6_netbsd.go
@@ -0,0 +1,18 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+const _P_PID = 1 // not 0 as on FreeBSD and Dragonfly!
+
+func wait6(idtype, id, options int) (status int, errno syscall.Errno) {
+ var status32 int32 // C.int
+ _, _, errno = syscall.Syscall6(syscall.SYS_WAIT6, uintptr(idtype), uintptr(id), uintptr(unsafe.Pointer(&status32)), uintptr(options), 0, 0)
+ return int(status32), errno
+}
diff --git a/src/os/wait_unimp.go b/src/os/wait_unimp.go
new file mode 100644
index 0000000..810e35d
--- /dev/null
+++ b/src/os/wait_unimp.go
@@ -0,0 +1,21 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// aix, darwin, js/wasm, openbsd, solaris and wasip1/wasm don't implement
+// waitid/wait6.
+
+//go:build aix || darwin || (js && wasm) || openbsd || solaris || wasip1
+
+package os
+
+// blockUntilWaitable attempts to block until a call to p.Wait will
+// succeed immediately, and reports whether it has done so.
+// It does not actually call p.Wait.
+// This version is used on systems that do not implement waitid,
+// or where we have not implemented it yet. Note that this is racy:
+// a call to Process.Signal can in an extremely unlikely case send a
+// signal to the wrong process, see issue #13987.
+func (p *Process) blockUntilWaitable() (bool, error) {
+ return false, nil
+}
diff --git a/src/os/wait_wait6.go b/src/os/wait_wait6.go
new file mode 100644
index 0000000..1031428
--- /dev/null
+++ b/src/os/wait_wait6.go
@@ -0,0 +1,32 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build dragonfly || freebsd || netbsd
+
+package os
+
+import (
+ "runtime"
+ "syscall"
+)
+
+// blockUntilWaitable attempts to block until a call to p.Wait will
+// succeed immediately, and reports whether it has done so.
+// It does not actually call p.Wait.
+func (p *Process) blockUntilWaitable() (bool, error) {
+ var errno syscall.Errno
+ for {
+ _, errno = wait6(_P_PID, p.Pid, syscall.WEXITED|syscall.WNOWAIT)
+ if errno != syscall.EINTR {
+ break
+ }
+ }
+ runtime.KeepAlive(p)
+ if errno == syscall.ENOSYS {
+ return false, nil
+ } else if errno != 0 {
+ return false, NewSyscallError("wait6", errno)
+ }
+ return true, nil
+}
diff --git a/src/os/wait_waitid.go b/src/os/wait_waitid.go
new file mode 100644
index 0000000..c0503b2
--- /dev/null
+++ b/src/os/wait_waitid.go
@@ -0,0 +1,48 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// We used to used this code for Darwin, but according to issue #19314
+// waitid returns if the process is stopped, even when using WEXITED.
+
+//go:build linux
+
+package os
+
+import (
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
+const _P_PID = 1
+
+// blockUntilWaitable attempts to block until a call to p.Wait will
+// succeed immediately, and reports whether it has done so.
+// It does not actually call p.Wait.
+func (p *Process) blockUntilWaitable() (bool, error) {
+ // The waitid system call expects a pointer to a siginfo_t,
+ // which is 128 bytes on all Linux systems.
+ // On darwin/amd64, it requires 104 bytes.
+ // We don't care about the values it returns.
+ var siginfo [16]uint64
+ psig := &siginfo[0]
+ var e syscall.Errno
+ for {
+ _, _, e = syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(p.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0)
+ if e != syscall.EINTR {
+ break
+ }
+ }
+ runtime.KeepAlive(p)
+ if e != 0 {
+ // waitid has been available since Linux 2.6.9, but
+ // reportedly is not available in Ubuntu on Windows.
+ // See issue 16610.
+ if e == syscall.ENOSYS {
+ return false, nil
+ }
+ return false, NewSyscallError("waitid", e)
+ }
+ return true, nil
+}