diff options
author | Ian Lance Taylor <iant@golang.org> | 2020-12-23 09:57:37 -0800 |
---|---|---|
committer | Ian Lance Taylor <iant@golang.org> | 2020-12-30 15:13:24 -0800 |
commit | cfcbb4227fb20191e04eb8d7766ae6202f526afd (patch) | |
tree | e2effea96f6f204451779f044415c2385e45042b /libgo/go/os | |
parent | 0696141107d61483f38482b941549959a0d7f613 (diff) | |
download | gcc-cfcbb4227fb20191e04eb8d7766ae6202f526afd.zip gcc-cfcbb4227fb20191e04eb8d7766ae6202f526afd.tar.gz gcc-cfcbb4227fb20191e04eb8d7766ae6202f526afd.tar.bz2 |
libgo: update to Go1.16beta1 release
This does not yet include support for the //go:embed directive added
in this release.
* Makefile.am (check-runtime): Don't create check-runtime-dir.
(mostlyclean-local): Don't remove check-runtime-dir.
(check-go-tool, check-vet): Copy in go.mod and modules.txt.
(check-cgo-test, check-carchive-test): Add go.mod file.
* Makefile.in: Regenerate.
Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/280172
Diffstat (limited to 'libgo/go/os')
67 files changed, 1896 insertions, 561 deletions
diff --git a/libgo/go/os/dir.go b/libgo/go/os/dir.go index 1d7ced8..5306bcb 100644 --- a/libgo/go/os/dir.go +++ b/libgo/go/os/dir.go @@ -4,6 +4,19 @@ 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 @@ -19,11 +32,20 @@ package os // 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 } - return f.readdir(n) + _, _, 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 @@ -45,5 +67,59 @@ func (f *File) Readdirnames(n int) (names []string, err error) { if f == nil { return nil, ErrInvalid } - return f.readdirnames(n) + 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/libgo/go/os/dir_gccgo.go b/libgo/go/os/dir_gccgo.go index 171dde5..31ee262 100644 --- a/libgo/go/os/dir_gccgo.go +++ b/libgo/go/os/dir_gccgo.go @@ -5,9 +5,8 @@ package os import ( + "internal/poll" "io" - "runtime" - "sync/atomic" "syscall" "unsafe" ) @@ -16,8 +15,7 @@ import ( //extern pathconf func libc_pathconf(*byte, int32) int -//extern dup -func libc_dup(int32) int32 +func direntType(*syscall.Dirent) byte func clen(n []byte) int { for i := 0; i < len(n); i++ { @@ -28,95 +26,118 @@ func clen(n []byte) int { return len(n) } -var nameMax int32 - -func (file *File) readdirnames(n int) (names []string, err error) { - if file.dirinfo == nil { - p, err := syscall.BytePtrFromString(file.name) +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 { + fd, call, err := poll.DupCloseOnExec(int(f.pfd.Sysfd)) if err != nil { - return nil, err - } - - elen := int(atomic.LoadInt32(&nameMax)) - if elen == 0 { - syscall.Entersyscall() - plen := libc_pathconf(p, syscall.PC_NAME_MAX) - syscall.Exitsyscall() - if plen < 1024 { - plen = 1024 - } - var dummy syscall.Dirent - elen = int(unsafe.Offsetof(dummy.Name)) + plen + 1 - atomic.StoreInt32(&nameMax, int32(elen)) + return nil, nil, nil, NewSyscallError(call, err) } syscall.Entersyscall() - fd := libc_dup(int32(file.pfd.Sysfd)) + r := libc_fdopendir(int32(fd)) errno := syscall.GetErrno() syscall.Exitsyscall() - if fd < 0 { - return nil, &PathError{"dup", file.name, errno} - } - - syscall.Entersyscall() - r := libc_fdopendir(fd) - errno = syscall.GetErrno() - syscall.Exitsyscall() if r == nil { - return nil, &PathError{"fdopendir", file.name, errno} + return nil, nil, nil, &PathError{"fdopendir", f.name, errno} } - file.dirinfo = new(dirInfo) - file.dirinfo.buf = make([]byte, elen) - file.dirinfo.dir = r + f.dirinfo = &dirInfo{r} } - - entryDirent := (*syscall.Dirent)(unsafe.Pointer(&file.dirinfo.buf[0])) - - size := n - if size <= 0 { - size = 100 + dir := f.dirinfo.dir + + // 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 } - names = make([]string, 0, size) // Empty with room to grow. - for n != 0 { - var dirent *syscall.Dirent - pr := &dirent syscall.Entersyscall() - i := libc_readdir_r(file.dirinfo.dir, entryDirent, pr) + syscall.SetErrno(0) + dirent := libc_readdir(dir) + errno := syscall.GetErrno() syscall.Exitsyscall() - // On AIX when readdir_r hits EOF it sets dirent to nil and returns 9. - // https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.basetrf2/readdir_r.htm - if runtime.GOOS == "aix" && i == 9 && dirent == nil { - break - } - if i != 0 { - return names, NewSyscallError("readdir_r", i) - } + if dirent == nil { + if errno != 0 { + return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno} + } break // EOF } - bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0])) - var name = string(bytes[0:clen(bytes[:])]) - if name == "." || name == ".." { // Useless names + + // In some cases the actual name can be longer than + // the Name field. + name := (*[1 << 16]byte)(unsafe.Pointer(&dirent.Name[0]))[:] + for i, c := range name { + if c == 0 { + name = name[:i] + break + } + } + // Check for useless names before allocating a string. + if (len(name) == 1 && name[0] == '.') || (len(name) == 2 && name[0] == '.' && name[1] == '.') { continue } - names = append(names, name) - n-- - } - if n >= 0 && len(names) == 0 { - return names, io.EOF + if n > 0 { // see 'n == 0' comment above + n-- + } + if mode == readdirName { + names = append(names, string(name)) + } else if mode == readdirDirEntry { + var typ FileMode + switch direntType(dirent) { + case 'B': + typ = ModeDevice + case 'C': + typ = ModeDevice | ModeCharDevice + case 'D': + typ = ModeDir + case 'F': + typ = ModeNamedPipe + case 'L': + typ = ModeSymlink + case 'R': + typ = 0 + case 'S': + typ = ModeSocket + case 'U': + typ = ^FileMode(0) + } + + de, err := newUnixDirent(f.name, string(name), typ) + 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) + } } - return names, nil -} -func (f *File) seekInvalidate() { - if f.file.dirinfo != nil { - syscall.Entersyscall() - libc_closedir(f.file.dirinfo.dir) - syscall.Exitsyscall() - f.file.dirinfo = nil + if n > 0 && len(names)+len(dirents)+len(infos) == 0 { + return nil, nil, nil, io.EOF } + return names, dirents, infos, nil } diff --git a/libgo/go/os/dir_gccgo_c.c b/libgo/go/os/dir_gccgo_c.c new file mode 100644 index 0000000..66b9be1 --- /dev/null +++ b/libgo/go/os/dir_gccgo_c.c @@ -0,0 +1,58 @@ +/* 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. */ + +#include <dirent.h> + +#include "runtime.h" + +unsigned char direntType (struct dirent *) + __asm__ (GOSYM_PREFIX "os.direntType"); + +unsigned char +direntType (struct dirent *p __attribute__((unused))) +{ +#ifndef HAVE_STRUCT_DIRENT_D_TYPE + return 'U'; +#else + switch (p->d_type) + { +#ifdef DT_BLK + case DT_BLK: + return 'B'; +#endif +#ifdef DT_CHR + case DT_CHR: + return 'C'; +#endif +#ifdef DT_DBF + case DT_DBF: + // Database record file. + // Treat as regular file. + return 'R'; +#endif +#ifdef DT_DIR + case DT_DIR: + return 'D'; +#endif +#ifdef DT_FIFO + case DT_FIFO: + return 'F'; +#endif +#ifdef DT_LNK + case DT_LNK: + return 'L'; +#endif +#ifdef DT_REG + case DT_REG: + return 'R'; +#endif +#ifdef DT_SOCK + case DT_SOCK: + return 'S'; +#endif + default: + return 'U'; + } +#endif /* HAVE_DIRENT_D_TYPE */ +} diff --git a/libgo/go/os/dir_largefile.go b/libgo/go/os/dir_largefile.go index e7d30f8..1fc5ee0 100644 --- a/libgo/go/os/dir_largefile.go +++ b/libgo/go/os/dir_largefile.go @@ -11,5 +11,5 @@ package os import "syscall" -//extern readdir64_r -func libc_readdir_r(*syscall.DIR, *syscall.Dirent, **syscall.Dirent) syscall.Errno +//extern readdir64 +func libc_readdir(*syscall.DIR) *syscall.Dirent diff --git a/libgo/go/os/dir_plan9.go b/libgo/go/os/dir_plan9.go index 8195c02..8f6b0d6 100644 --- a/libgo/go/os/dir_plan9.go +++ b/libgo/go/os/dir_plan9.go @@ -9,7 +9,7 @@ import ( "syscall" ) -func (file *File) readdir(n int) ([]FileInfo, error) { +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) @@ -20,7 +20,6 @@ func (file *File) readdir(n int) ([]FileInfo, error) { size = 100 n = -1 } - fi := make([]FileInfo, 0, size) // Empty with room to grow. for n != 0 { // Refill the buffer if necessary. if d.bufp >= d.nbuf { @@ -33,10 +32,10 @@ func (file *File) readdir(n int) ([]FileInfo, error) { if err == io.EOF { break } - return fi, &PathError{"readdir", file.name, err} + return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: err} } if nb < syscall.STATFIXLEN { - return fi, &PathError{"readdir", file.name, syscall.ErrShortStat} + return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: syscall.ErrShortStat} } } @@ -44,30 +43,39 @@ func (file *File) readdir(n int) ([]FileInfo, error) { b := d.buf[d.bufp:] m := int(uint16(b[0])|uint16(b[1])<<8) + 2 if m < syscall.STATFIXLEN { - return fi, &PathError{"readdir", file.name, syscall.ErrShortStat} + return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: syscall.ErrShortStat} } dir, err := syscall.UnmarshalDir(b[:m]) if err != nil { - return fi, &PathError{"readdir", file.name, err} + return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: err} } - fi = append(fi, fileInfoFromStat(dir)) + 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(fi) == 0 { - return fi, io.EOF + if n > 0 && len(names)+len(dirents)+len(infos) == 0 { + return nil, nil, nil, io.EOF } - return fi, nil + return names, dirents, infos, nil } -func (file *File) readdirnames(n int) (names []string, err error) { - fi, err := file.Readdir(n) - names = make([]string, len(fi)) - for i := range fi { - names[i] = fi[i].Name() - } - return +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 } diff --git a/libgo/go/os/dir_regfile.go b/libgo/go/os/dir_regfile.go index b2e6623..3a0cd64 100644 --- a/libgo/go/os/dir_regfile.go +++ b/libgo/go/os/dir_regfile.go @@ -15,5 +15,5 @@ package os import "syscall" -//extern-sysinfo readdir_r -func libc_readdir_r(*syscall.DIR, *syscall.Dirent, **syscall.Dirent) syscall.Errno +//extern-sysinfo readdir +func libc_readdir(*syscall.DIR) *syscall.Dirent diff --git a/libgo/go/os/error.go b/libgo/go/os/error.go index 875cc97..704a6fb 100644 --- a/libgo/go/os/error.go +++ b/libgo/go/os/error.go @@ -7,6 +7,7 @@ package os import ( "internal/oserror" "internal/poll" + "io/fs" ) // Portable analogs of some common system call errors. @@ -16,20 +17,17 @@ import ( var ( // ErrInvalid indicates an invalid argument. // Methods on File will return this error when the receiver is nil. - ErrInvalid = errInvalid() // "invalid argument" + 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" - ErrPermission = errPermission() // "permission denied" - ErrExist = errExist() // "file already exists" - ErrNotExist = errNotExist() // "file does not exist" - ErrClosed = errClosed() // "file already closed" ErrNoDeadline = errNoDeadline() // "file type does not support deadline" ErrDeadlineExceeded = errDeadlineExceeded() // "i/o timeout" ) -func errInvalid() error { return oserror.ErrInvalid } -func errPermission() error { return oserror.ErrPermission } -func errExist() error { return oserror.ErrExist } -func errNotExist() error { return oserror.ErrNotExist } func errClosed() error { return oserror.ErrClosed } func errNoDeadline() error { return poll.ErrNoDeadline } @@ -47,21 +45,7 @@ type timeout interface { } // PathError records an error and the operation and file path that caused it. -type PathError struct { - Op string - Path string - Err error -} - -func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } - -func (e *PathError) Unwrap() error { return e.Err } - -// Timeout reports whether this error represents a timeout. -func (e *PathError) Timeout() bool { - t, ok := e.Err.(timeout) - return ok && t.Timeout() -} +type PathError = fs.PathError // SyscallError records an error from a specific system call. type SyscallError struct { @@ -92,6 +76,9 @@ func NewSyscallError(syscall string, err error) error { // 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, os.ErrExist). func IsExist(err error) bool { return underlyingErrorIs(err, ErrExist) } @@ -99,6 +86,9 @@ func IsExist(err error) bool { // 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, os.ErrNotExist). func IsNotExist(err error) bool { return underlyingErrorIs(err, ErrNotExist) } @@ -106,12 +96,21 @@ func IsNotExist(err error) bool { // 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, os.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() diff --git a/libgo/go/os/error_test.go b/libgo/go/os/error_test.go index 3d92157..6264ccc 100644 --- a/libgo/go/os/error_test.go +++ b/libgo/go/os/error_test.go @@ -7,14 +7,14 @@ package os_test import ( "errors" "fmt" - "io/ioutil" + "io/fs" "os" "path/filepath" "testing" ) func TestErrIsExist(t *testing.T) { - f, err := ioutil.TempFile("", "_Go_ErrIsExist") + f, err := os.CreateTemp("", "_Go_ErrIsExist") if err != nil { t.Fatalf("open ErrIsExist tempfile: %s", err) return @@ -27,7 +27,7 @@ func TestErrIsExist(t *testing.T) { t.Fatal("Open should have failed") return } - if s := checkErrorPredicate("os.IsExist", os.IsExist, err, os.ErrExist); s != "" { + if s := checkErrorPredicate("os.IsExist", os.IsExist, err, fs.ErrExist); s != "" { t.Fatal(s) return } @@ -39,7 +39,7 @@ func testErrNotExist(name string) string { f.Close() return "Open should have failed" } - if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, os.ErrNotExist); s != "" { + if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, fs.ErrNotExist); s != "" { return s } @@ -47,14 +47,14 @@ func testErrNotExist(name string) string { if err == nil { return "Chdir should have failed" } - if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, os.ErrNotExist); s != "" { + if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, fs.ErrNotExist); s != "" { return s } return "" } func TestErrIsNotExist(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "_Go_ErrIsNotExist") + tmpDir, err := os.MkdirTemp("", "_Go_ErrIsNotExist") if err != nil { t.Fatalf("create ErrIsNotExist tempdir: %s", err) return @@ -91,18 +91,18 @@ type isExistTest struct { } var isExistTests = []isExistTest{ - {&os.PathError{Err: os.ErrInvalid}, false, false}, - {&os.PathError{Err: os.ErrPermission}, false, false}, - {&os.PathError{Err: os.ErrExist}, true, false}, - {&os.PathError{Err: os.ErrNotExist}, false, true}, - {&os.PathError{Err: os.ErrClosed}, false, false}, - {&os.LinkError{Err: os.ErrInvalid}, false, false}, - {&os.LinkError{Err: os.ErrPermission}, false, false}, - {&os.LinkError{Err: os.ErrExist}, true, false}, - {&os.LinkError{Err: os.ErrNotExist}, false, true}, - {&os.LinkError{Err: os.ErrClosed}, false, false}, - {&os.SyscallError{Err: os.ErrNotExist}, false, true}, - {&os.SyscallError{Err: os.ErrExist}, true, false}, + {&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}, } @@ -111,14 +111,14 @@ func TestIsExist(t *testing.T) { 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, os.ErrExist); is != tt.is { - t.Errorf("errors.Is(%T %v, os.ErrExist) = %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, os.ErrNotExist); isnot != tt.isnot { - t.Errorf("errors.Is(%T %v, os.ErrNotExist) = %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) } } } @@ -130,8 +130,8 @@ type isPermissionTest struct { var isPermissionTests = []isPermissionTest{ {nil, false}, - {&os.PathError{Err: os.ErrPermission}, true}, - {&os.SyscallError{Err: os.ErrPermission}, true}, + {&fs.PathError{Err: fs.ErrPermission}, true}, + {&os.SyscallError{Err: fs.ErrPermission}, true}, } func TestIsPermission(t *testing.T) { @@ -139,19 +139,19 @@ func TestIsPermission(t *testing.T) { 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, os.ErrPermission); got != tt.want { - t.Errorf("errors.Is(%#v, os.ErrPermission) = %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) { - f, err := ioutil.TempFile("", "_Go_ErrPathNUL\x00") + f, err := os.CreateTemp("", "_Go_ErrPathNUL\x00") if err == nil { f.Close() t.Fatal("TempFile should have failed") } - f, err = ioutil.TempFile("", "_Go_ErrPathNUL") + f, err = os.CreateTemp("", "_Go_ErrPathNUL") if err != nil { t.Fatalf("open ErrPathNUL tempfile: %s", err) } @@ -170,8 +170,8 @@ func TestErrPathNUL(t *testing.T) { } func TestPathErrorUnwrap(t *testing.T) { - pe := &os.PathError{Err: os.ErrInvalid} - if !errors.Is(pe, os.ErrInvalid) { + pe := &fs.PathError{Err: fs.ErrInvalid} + if !errors.Is(pe, fs.ErrInvalid) { t.Error("errors.Is failed, wanted success") } } @@ -181,7 +181,7 @@ type myErrorIs struct{ error } func (e myErrorIs) Is(target error) bool { return target == e.error } func TestErrorIsMethods(t *testing.T) { - if os.IsPermission(myErrorIs{os.ErrPermission}) { - t.Error("os.IsPermission(err) = true when err.Is(os.ErrPermission), wanted false") + if os.IsPermission(myErrorIs{fs.ErrPermission}) { + t.Error("os.IsPermission(err) = true when err.Is(fs.ErrPermission), wanted false") } } diff --git a/libgo/go/os/error_unix_test.go b/libgo/go/os/error_unix_test.go index a01916e..dd9f317 100644 --- a/libgo/go/os/error_unix_test.go +++ b/libgo/go/os/error_unix_test.go @@ -7,14 +7,15 @@ package os_test import ( + "io/fs" "os" "syscall" ) func init() { isExistTests = append(isExistTests, - isExistTest{err: &os.PathError{Err: syscall.EEXIST}, is: true, isnot: false}, - isExistTest{err: &os.PathError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, + 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}, @@ -23,9 +24,9 @@ func init() { isExistTest{err: &os.SyscallError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, ) isPermissionTests = append(isPermissionTests, - isPermissionTest{err: &os.PathError{Err: syscall.EACCES}, want: true}, - isPermissionTest{err: &os.PathError{Err: syscall.EPERM}, want: true}, - isPermissionTest{err: &os.PathError{Err: syscall.EEXIST}, want: false}, + 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}, diff --git a/libgo/go/os/error_windows_test.go b/libgo/go/os/error_windows_test.go index 1635c10..b8191c5 100644 --- a/libgo/go/os/error_windows_test.go +++ b/libgo/go/os/error_windows_test.go @@ -7,6 +7,7 @@ package os_test import ( + "io/fs" "os" "syscall" ) @@ -15,24 +16,24 @@ func init() { const _ERROR_BAD_NETPATH = syscall.Errno(53) isExistTests = append(isExistTests, - isExistTest{err: &os.PathError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true}, + 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: &os.PathError{Err: _ERROR_BAD_NETPATH}, 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: &os.PathError{Err: syscall.ERROR_PATH_NOT_FOUND}, 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: &os.PathError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false}, + 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: &os.PathError{Err: syscall.ERROR_ACCESS_DENIED}, want: true}, + 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/libgo/go/os/example_test.go b/libgo/go/os/example_test.go index 822886f..3adce51 100644 --- a/libgo/go/os/example_test.go +++ b/libgo/go/os/example_test.go @@ -6,8 +6,10 @@ package os_test import ( "fmt" + "io/fs" "log" "os" + "path/filepath" "time" ) @@ -62,9 +64,9 @@ func ExampleFileMode() { fmt.Println("regular file") case mode.IsDir(): fmt.Println("directory") - case mode&os.ModeSymlink != 0: + case mode&fs.ModeSymlink != 0: fmt.Println("symbolic link") - case mode&os.ModeNamedPipe != 0: + case mode&fs.ModeNamedPipe != 0: fmt.Println("named pipe") } } @@ -143,3 +145,98 @@ 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) + } +} diff --git a/libgo/go/os/exec.go b/libgo/go/os/exec.go index cab6a73..edb773a 100644 --- a/libgo/go/os/exec.go +++ b/libgo/go/os/exec.go @@ -5,6 +5,7 @@ package os import ( + "errors" "internal/testlog" "runtime" "sync" @@ -13,6 +14,9 @@ import ( "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 diff --git a/libgo/go/os/exec/exec_plan9.go b/libgo/go/os/exec/exec_plan9.go index d90bd04..21ac7b7 100644 --- a/libgo/go/os/exec/exec_plan9.go +++ b/libgo/go/os/exec/exec_plan9.go @@ -4,14 +4,14 @@ package exec -import "os" +import "io/fs" func init() { skipStdinCopyError = func(err error) bool { // Ignore hungup errors copying to stdin if the program // completed successfully otherwise. // See Issue 35753. - pe, ok := err.(*os.PathError) + pe, ok := err.(*fs.PathError) return ok && pe.Op == "write" && pe.Path == "|1" && pe.Err.Error() == "i/o on hungup channel" diff --git a/libgo/go/os/exec/exec_test.go b/libgo/go/os/exec/exec_test.go index dafbc64..8b0c93f 100644 --- a/libgo/go/os/exec/exec_test.go +++ b/libgo/go/os/exec/exec_test.go @@ -15,7 +15,6 @@ import ( "internal/poll" "internal/testenv" "io" - "io/ioutil" "log" "net" "net/http" @@ -386,7 +385,7 @@ func TestPipeLookPathLeak(t *testing.T) { // Reading /proc/self/fd is more reliable than calling lsof, so try that // first. numOpenFDs := func() (int, []byte, error) { - fds, err := ioutil.ReadDir("/proc/self/fd") + fds, err := os.ReadDir("/proc/self/fd") if err != nil { return 0, nil, err } @@ -605,6 +604,10 @@ func TestExtraFiles(t *testing.T) { 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) + if runtime.GOOS == "windows" { t.Skipf("skipping test on %q", runtime.GOOS) } @@ -633,7 +636,7 @@ func TestExtraFiles(t *testing.T) { // 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(ioutil.Discard, "", 0) + ts.Config.ErrorLog = log.New(io.Discard, "", 0) ts.StartTLS() defer ts.Close() _, err = http.Get(ts.URL) @@ -641,7 +644,7 @@ func TestExtraFiles(t *testing.T) { t.Errorf("success trying to fetch %s; want an error", ts.URL) } - tf, err := ioutil.TempFile("", "") + tf, err := os.CreateTemp("", "") if err != nil { t.Fatalf("TempFile: %v", err) } @@ -687,6 +690,18 @@ func TestExtraFiles(t *testing.T) { 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.Bytes(), stderr.Bytes()) @@ -826,7 +841,7 @@ func TestHelperProcess(*testing.T) { } } case "stdinClose": - b, err := ioutil.ReadAll(os.Stdin) + b, err := io.ReadAll(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) diff --git a/libgo/go/os/exec/exec_unix.go b/libgo/go/os/exec/exec_unix.go index 9c3e17d..51c5242 100644 --- a/libgo/go/os/exec/exec_unix.go +++ b/libgo/go/os/exec/exec_unix.go @@ -7,7 +7,7 @@ package exec import ( - "os" + "io/fs" "syscall" ) @@ -16,7 +16,7 @@ func init() { // Ignore EPIPE errors copying to stdin if the program // completed successfully otherwise. // See Issue 9173. - pe, ok := err.(*os.PathError) + pe, ok := err.(*fs.PathError) return ok && pe.Op == "write" && pe.Path == "|1" && pe.Err == syscall.EPIPE diff --git a/libgo/go/os/exec/exec_windows.go b/libgo/go/os/exec/exec_windows.go index af8cd97..bb937f8 100644 --- a/libgo/go/os/exec/exec_windows.go +++ b/libgo/go/os/exec/exec_windows.go @@ -5,7 +5,7 @@ package exec import ( - "os" + "io/fs" "syscall" ) @@ -15,7 +15,7 @@ func init() { // to stdin if the program completed successfully otherwise. // See Issue 20445. const _ERROR_NO_DATA = syscall.Errno(0xe8) - pe, ok := err.(*os.PathError) + 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/libgo/go/os/exec/lp_plan9.go b/libgo/go/os/exec/lp_plan9.go index 5860cbc..e8826a5 100644 --- a/libgo/go/os/exec/lp_plan9.go +++ b/libgo/go/os/exec/lp_plan9.go @@ -6,6 +6,7 @@ package exec import ( "errors" + "io/fs" "os" "path/filepath" "strings" @@ -22,7 +23,7 @@ func findExecutable(file string) error { if m := d.Mode(); !m.IsDir() && m&0111 != 0 { return nil } - return os.ErrPermission + return fs.ErrPermission } // LookPath searches for an executable named file in the diff --git a/libgo/go/os/exec/lp_unix.go b/libgo/go/os/exec/lp_unix.go index 6a5e877..bb5a3ec 100644 --- a/libgo/go/os/exec/lp_unix.go +++ b/libgo/go/os/exec/lp_unix.go @@ -8,6 +8,7 @@ package exec import ( "errors" + "io/fs" "os" "path/filepath" "strings" @@ -24,7 +25,7 @@ func findExecutable(file string) error { if m := d.Mode(); !m.IsDir() && m&0111 != 0 { return nil } - return os.ErrPermission + return fs.ErrPermission } // LookPath searches for an executable named file in the diff --git a/libgo/go/os/exec/lp_unix_test.go b/libgo/go/os/exec/lp_unix_test.go index 5c240b3..23d1506 100644 --- a/libgo/go/os/exec/lp_unix_test.go +++ b/libgo/go/os/exec/lp_unix_test.go @@ -7,13 +7,12 @@ package exec import ( - "io/ioutil" "os" "testing" ) func TestLookPathUnixEmptyPath(t *testing.T) { - tmp, err := ioutil.TempDir("", "TestLookPathUnixEmptyPath") + tmp, err := os.MkdirTemp("", "TestLookPathUnixEmptyPath") if err != nil { t.Fatal("TempDir failed: ", err) } diff --git a/libgo/go/os/exec/lp_windows.go b/libgo/go/os/exec/lp_windows.go index 9ea3d76..e7a2cdf 100644 --- a/libgo/go/os/exec/lp_windows.go +++ b/libgo/go/os/exec/lp_windows.go @@ -6,6 +6,7 @@ package exec import ( "errors" + "io/fs" "os" "path/filepath" "strings" @@ -20,7 +21,7 @@ func chkStat(file string) error { return err } if d.IsDir() { - return os.ErrPermission + return fs.ErrPermission } return nil } @@ -47,7 +48,7 @@ func findExecutable(file string, exts []string) (string, error) { return f, nil } } - return "", os.ErrNotExist + return "", fs.ErrNotExist } // LookPath searches for an executable named file in the diff --git a/libgo/go/os/exec/read3.go b/libgo/go/os/exec/read3.go index 25d732a..8cc24da 100644 --- a/libgo/go/os/exec/read3.go +++ b/libgo/go/os/exec/read3.go @@ -15,7 +15,7 @@ package main import ( "fmt" "internal/poll" - "io/ioutil" + "io" "os" "os/exec" "runtime" @@ -24,7 +24,7 @@ import ( func main() { fd3 := os.NewFile(3, "fd3") - bs, err := ioutil.ReadAll(fd3) + bs, err := io.ReadAll(fd3) if err != nil { fmt.Printf("ReadAll from fd 3: %v\n", err) os.Exit(1) @@ -56,7 +56,7 @@ func main() { switch runtime.GOOS { case "plan9": args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())} - case "aix": + case "aix", "solaris", "illumos": args = []string{fmt.Sprint(os.Getpid())} default: args = []string{"-p", fmt.Sprint(os.Getpid())} @@ -71,6 +71,8 @@ func main() { ofcmd = "/bin/cat" case "aix": ofcmd = "procfiles" + case "solaris", "illumos": + ofcmd = "pfiles" } cmd := exec.Command(ofcmd, args...) diff --git a/libgo/go/os/exec_plan9.go b/libgo/go/os/exec_plan9.go index b0abf74..8580153 100644 --- a/libgo/go/os/exec_plan9.go +++ b/libgo/go/os/exec_plan9.go @@ -5,7 +5,6 @@ package os import ( - "errors" "runtime" "syscall" "time" @@ -34,7 +33,7 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e pid, h, e := syscall.StartProcess(name, argv, sysattr) if e != nil { - return nil, &PathError{"fork/exec", name, e} + return nil, &PathError{Op: "fork/exec", Path: name, Err: e} } return newProcess(pid, h), nil @@ -52,7 +51,7 @@ func (p *Process) writeProcFile(file string, data string) error { func (p *Process) signal(sig Signal) error { if p.done() { - return errors.New("os: process already finished") + return ErrProcessDone } if e := p.writeProcFile("note", sig.String()); e != nil { return NewSyscallError("signal", e) diff --git a/libgo/go/os/exec_posix.go b/libgo/go/os/exec_posix.go index dc9947c..aa8dfe0 100644 --- a/libgo/go/os/exec_posix.go +++ b/libgo/go/os/exec_posix.go @@ -56,7 +56,7 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e runtime.KeepAlive(attr) if e != nil { - return nil, &PathError{"fork/exec", name, e} + return nil, &PathError{Op: "fork/exec", Path: name, Err: e} } return newProcess(pid, h), nil diff --git a/libgo/go/os/exec_unix.go b/libgo/go/os/exec_unix.go index a99b45d..69eb615 100644 --- a/libgo/go/os/exec_unix.go +++ b/libgo/go/os/exec_unix.go @@ -59,8 +59,6 @@ func (p *Process) wait() (ps *ProcessState, err error) { return ps, nil } -var errFinished = errors.New("os: process already finished") - func (p *Process) signal(sig Signal) error { if p.Pid == -1 { return errors.New("os: process already released") @@ -71,7 +69,7 @@ func (p *Process) signal(sig Signal) error { p.sigMu.RLock() defer p.sigMu.RUnlock() if p.done() { - return errFinished + return ErrProcessDone } s, ok := sig.(syscall.Signal) if !ok { @@ -79,7 +77,7 @@ func (p *Process) signal(sig Signal) error { } if e := syscall.Kill(p.Pid, s); e != nil { if e == syscall.ESRCH { - return errFinished + return ErrProcessDone } return e } diff --git a/libgo/go/os/exec_unix_test.go b/libgo/go/os/exec_unix_test.go new file mode 100644 index 0000000..d942cdb --- /dev/null +++ b/libgo/go/os/exec_unix_test.go @@ -0,0 +1,29 @@ +// 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. + +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package os_test + +import ( + "internal/testenv" + . "os" + "testing" +) + +func TestErrProcessDone(t *testing.T) { + testenv.MustHaveGoBuild(t) + path, err := testenv.GoTool() + if err != nil { + t.Errorf("finding go tool: %v", err) + } + p, err := StartProcess(path, []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) + } +} diff --git a/libgo/go/os/exec_windows.go b/libgo/go/os/exec_windows.go index 24ddf89..5710401 100644 --- a/libgo/go/os/exec_windows.go +++ b/libgo/go/os/exec_windows.go @@ -61,7 +61,7 @@ func (p *Process) signal(sig Signal) error { return syscall.EINVAL } if p.done() { - return errors.New("os: process already finished") + return ErrProcessDone } if sig == Kill { err := terminateProcess(p.Pid, 1) diff --git a/libgo/go/os/executable_dragonfly.go b/libgo/go/os/executable_dragonfly.go new file mode 100644 index 0000000..19c2ae8 --- /dev/null +++ b/libgo/go/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/libgo/go/os/executable_freebsd.go b/libgo/go/os/executable_freebsd.go index ccaf8e6..95f1a93 100644 --- a/libgo/go/os/executable_freebsd.go +++ b/libgo/go/os/executable_freebsd.go @@ -1,33 +1,12 @@ -// Copyright 2016 The Go Authors. All rights reserved. +// 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" +// From FreeBSD's <sys/sysctl.h> +const ( + _CTL_KERN = 1 + _KERN_PROC = 14 + _KERN_PROC_PATHNAME = 12 ) - -func executable() (string, error) { - mib := [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* 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/libgo/go/os/executable_procfs.go b/libgo/go/os/executable_procfs.go index d2d2e5a..989a220 100644 --- a/libgo/go/os/executable_procfs.go +++ b/libgo/go/os/executable_procfs.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build hurd linux netbsd dragonfly js,wasm +// +build hurd linux netbsd js,wasm package os @@ -23,8 +23,6 @@ var executablePath, executablePathErr = func() (string, error) { procfn = "/proc/self/exe" case "netbsd": procfn = "/proc/curproc/exe" - case "dragonfly": - procfn = "/proc/curproc/file" } return Readlink(procfn) }() diff --git a/libgo/go/os/executable_sysctl.go b/libgo/go/os/executable_sysctl.go new file mode 100644 index 0000000..f9a4b18 --- /dev/null +++ b/libgo/go/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. + +// +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/libgo/go/os/export_test.go b/libgo/go/os/export_test.go index 812432c..f3cb1a2 100644 --- a/libgo/go/os/export_test.go +++ b/libgo/go/os/export_test.go @@ -9,3 +9,5 @@ package os var Atime = atime var LstatP = &lstat var ErrWriteAtInAppendMode = errWriteAtInAppendMode +var TestingForceReadDirLstat = &testingForceReadDirLstat +var ErrPatternHasSeparator = errPatternHasSeparator diff --git a/libgo/go/os/fifo_test.go b/libgo/go/os/fifo_test.go index 3041dcf..2439192 100644 --- a/libgo/go/os/fifo_test.go +++ b/libgo/go/os/fifo_test.go @@ -11,7 +11,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "os" "path/filepath" "runtime" @@ -31,7 +30,7 @@ func TestFifoEOF(t *testing.T) { t.Skip("skipping on OpenBSD; issue 25877") } - dir, err := ioutil.TempDir("", "TestFifoEOF") + dir, err := os.MkdirTemp("", "TestFifoEOF") if err != nil { t.Fatal(err) } diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go index a2b71cb..416bc0e 100644 --- a/libgo/go/os/file.go +++ b/libgo/go/os/file.go @@ -45,6 +45,7 @@ import ( "internal/poll" "internal/testlog" "io" + "io/fs" "runtime" "syscall" "time" @@ -127,7 +128,7 @@ func (f *File) ReadAt(b []byte, off int64) (n int, err error) { } if off < 0 { - return 0, &PathError{"readat", f.name, errors.New("negative offset")} + return 0, &PathError{Op: "readat", Path: f.name, Err: errors.New("negative offset")} } for len(b) > 0 { @@ -203,7 +204,7 @@ func (f *File) WriteAt(b []byte, off int64) (n int, err error) { } if off < 0 { - return 0, &PathError{"writeat", f.name, errors.New("negative offset")} + return 0, &PathError{Op: "writeat", Path: f.name, Err: errors.New("negative offset")} } for len(b) > 0 { @@ -253,12 +254,15 @@ func (f *File) WriteString(s string) (n int, err error) { // If there is an error, it will be of type *PathError. func Mkdir(name string, perm FileMode) error { if runtime.GOOS == "windows" && isWindowsNulName(name) { - return &PathError{"mkdir", name, syscall.ENOTDIR} + return &PathError{Op: "mkdir", Path: name, Err: syscall.ENOTDIR} } - e := syscall.Mkdir(fixLongPath(name), syscallMode(perm)) + longName := fixLongPath(name) + e := ignoringEINTR(func() error { + return syscall.Mkdir(longName, syscallMode(perm)) + }) if e != nil { - return &PathError{"mkdir", name, e} + return &PathError{Op: "mkdir", Path: name, Err: e} } // mkdir(2) itself won't handle the sticky bit on *BSD and Solaris @@ -288,7 +292,7 @@ func setStickyBit(name string) error { func Chdir(dir string) error { if e := syscall.Chdir(dir); e != nil { testlog.Open(dir) // observe likely non-existent directory - return &PathError{"chdir", dir, e} + return &PathError{Op: "chdir", Path: dir, Err: e} } if log := testlog.Logger(); log != nil { wd, err := Getwd() @@ -363,7 +367,7 @@ func (f *File) wrapErr(op string, err error) error { if err == poll.ErrFileClosing { err = ErrClosed } - return &PathError{op, f.name, err} + return &PathError{Op: op, Path: f.name, Err: err} } // TempDir returns the default directory to use for temporary files. @@ -402,7 +406,7 @@ func UserCacheDir() (string, error) { return "", errors.New("%LocalAppData% is not defined") } - case "darwin": + case "darwin", "ios": dir = Getenv("HOME") if dir == "" { return "", errors.New("$HOME is not defined") @@ -453,7 +457,7 @@ func UserConfigDir() (string, error) { return "", errors.New("%AppData% is not defined") } - case "darwin": + case "darwin", "ios": dir = Getenv("HOME") if dir == "" { return "", errors.New("$HOME is not defined") @@ -501,10 +505,8 @@ func UserHomeDir() (string, error) { switch runtime.GOOS { case "android": return "/sdcard", nil - case "darwin": - if runtime.GOARCH == "arm64" { - return "/", nil - } + case "ios": + return "/", nil } return "", errors.New(enverr + " is not defined") } @@ -605,3 +607,88 @@ func isWindowsNulName(name string) bool { } return true } + +// 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. DirFS is therefore not a general substitute for a chroot-style security +// mechanism when the directory tree contains arbitrary content. +func DirFS(dir string) fs.FS { + return dirFS(dir) +} + +type dirFS string + +func (dir dirFS) Open(name string) (fs.File, error) { + if !fs.ValidPath(name) { + return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid} + } + f, err := Open(string(dir) + "/" + name) + if err != nil { + return nil, err // nil fs.File + } + return f, 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. +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/libgo/go/os/file_plan9.go b/libgo/go/os/file_plan9.go index eb15890..bbc7328 100644 --- a/libgo/go/os/file_plan9.go +++ b/libgo/go/os/file_plan9.go @@ -29,8 +29,13 @@ type file struct { } // Fd returns the integer Plan 9 file descriptor referencing the open file. -// The file descriptor is valid only until f.Close is called or f is garbage collected. -// On Unix systems this will cause the SetDeadline methods to stop working. +// 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)) @@ -114,18 +119,18 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { if IsNotExist(e) && create { fd, e = syscall.Create(name, flag, syscallMode(perm)) if e != nil { - return nil, &PathError{"create", name, e} + return nil, &PathError{Op: "create", Path: name, Err: e} } } } if e != nil { - return nil, &PathError{"open", name, e} + return nil, &PathError{Op: "open", Path: name, Err: e} } if append { if _, e = syscall.Seek(fd, 0, io.SeekEnd); e != nil { - return nil, &PathError{"seek", name, e} + return nil, &PathError{Op: "seek", Path: name, Err: e} } } @@ -149,7 +154,7 @@ func (file *file) close() error { } var err error if e := syscall.Close(file.fd); e != nil { - err = &PathError{"close", file.name, e} + err = &PathError{Op: "close", Path: file.name, Err: e} } file.fd = badFd // so it can't be closed again @@ -186,10 +191,10 @@ func (f *File) Truncate(size int64) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"truncate", f.name, err} + return &PathError{Op: "truncate", Path: f.name, Err: err} } if err = syscall.Fwstat(f.fd, buf[:n]); err != nil { - return &PathError{"truncate", f.name, err} + return &PathError{Op: "truncate", Path: f.name, Err: err} } return nil } @@ -204,7 +209,7 @@ func (f *File) chmod(mode FileMode) error { odir, e := dirstat(f) if e != nil { - return &PathError{"chmod", f.name, e} + return &PathError{Op: "chmod", Path: f.name, Err: e} } d.Null() d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask @@ -212,10 +217,10 @@ func (f *File) chmod(mode FileMode) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"chmod", f.name, err} + return &PathError{Op: "chmod", Path: f.name, Err: err} } if err = syscall.Fwstat(f.fd, buf[:n]); err != nil { - return &PathError{"chmod", f.name, err} + return &PathError{Op: "chmod", Path: f.name, Err: err} } return nil } @@ -233,10 +238,10 @@ func (f *File) Sync() error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"sync", f.name, err} + return &PathError{Op: "sync", Path: f.name, Err: err} } if err = syscall.Fwstat(f.fd, buf[:n]); err != nil { - return &PathError{"sync", f.name, err} + return &PathError{Op: "sync", Path: f.name, Err: err} } return nil } @@ -309,10 +314,10 @@ func Truncate(name string, size int64) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"truncate", name, err} + return &PathError{Op: "truncate", Path: name, Err: err} } if err = syscall.Wstat(name, buf[:n]); err != nil { - return &PathError{"truncate", name, err} + return &PathError{Op: "truncate", Path: name, Err: err} } return nil } @@ -321,7 +326,7 @@ func Truncate(name string, size int64) error { // 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{"remove", name, e} + return &PathError{Op: "remove", Path: name, Err: e} } return nil } @@ -384,7 +389,7 @@ func chmod(name string, mode FileMode) error { odir, e := dirstat(name) if e != nil { - return &PathError{"chmod", name, e} + return &PathError{Op: "chmod", Path: name, Err: e} } d.Null() d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask @@ -392,10 +397,10 @@ func chmod(name string, mode FileMode) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"chmod", name, err} + return &PathError{Op: "chmod", Path: name, Err: err} } if err = syscall.Wstat(name, buf[:n]); err != nil { - return &PathError{"chmod", name, err} + return &PathError{Op: "chmod", Path: name, Err: err} } return nil } @@ -416,10 +421,10 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"chtimes", name, err} + return &PathError{Op: "chtimes", Path: name, Err: err} } if err = syscall.Wstat(name, buf[:n]); err != nil { - return &PathError{"chtimes", name, err} + return &PathError{Op: "chtimes", Path: name, Err: err} } return nil } @@ -453,7 +458,7 @@ func Symlink(oldname, newname string) error { // 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{"readlink", name, syscall.EPLAN9} + return "", &PathError{Op: "readlink", Path: name, Err: syscall.EPLAN9} } // Chown changes the numeric uid and gid of the named file. @@ -464,14 +469,14 @@ func Readlink(name string) (string, error) { // 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{"chown", name, syscall.EPLAN9} + 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{"lchown", name, syscall.EPLAN9} + return &PathError{Op: "lchown", Path: name, Err: syscall.EPLAN9} } // Chown changes the numeric uid and gid of the named file. @@ -480,7 +485,7 @@ func (f *File) Chown(uid, gid int) error { if f == nil { return ErrInvalid } - return &PathError{"chown", f.name, syscall.EPLAN9} + return &PathError{Op: "chown", Path: f.name, Err: syscall.EPLAN9} } func tempDir() string { @@ -500,7 +505,7 @@ func (f *File) Chdir() error { return err } if e := syscall.Fchdir(f.fd); e != nil { - return &PathError{"chdir", f.name, e} + return &PathError{Op: "chdir", Path: f.name, Err: e} } return nil } @@ -536,7 +541,7 @@ func (f *File) checkValid(op string) error { return ErrInvalid } if f.fd == badFd { - return &PathError{op, f.name, ErrClosed} + return &PathError{Op: op, Path: f.name, Err: ErrClosed} } return nil } @@ -558,3 +563,7 @@ func (c *rawConn) Write(f func(uintptr) bool) error { func newRawConn(file *File) (*rawConn, error) { return nil, syscall.EPLAN9 } + +func ignoringEINTR(fn func() error) error { + return fn() +} diff --git a/libgo/go/os/file_posix.go b/libgo/go/os/file_posix.go index ee0728b..95bf9e7 100644 --- a/libgo/go/os/file_posix.go +++ b/libgo/go/os/file_posix.go @@ -76,8 +76,12 @@ func syscallMode(i FileMode) (o uint32) { // See docs in file.go:Chmod. func chmod(name string, mode FileMode) error { - if e := syscall.Chmod(fixLongPath(name), syscallMode(mode)); e != nil { - return &PathError{"chmod", name, e} + 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 } @@ -101,8 +105,11 @@ func (f *File) chmod(mode FileMode) error { // 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 { - if e := syscall.Chown(name, uid, gid); e != nil { - return &PathError{"chown", name, e} + e := ignoringEINTR(func() error { + return syscall.Chown(name, uid, gid) + }) + if e != nil { + return &PathError{Op: "chown", Path: name, Err: e} } return nil } @@ -114,8 +121,11 @@ func Chown(name string, uid, gid int) error { // On Windows, it always returns the syscall.EWINDOWS error, wrapped // in *PathError. func Lchown(name string, uid, gid int) error { - if e := syscall.Lchown(name, uid, gid); e != nil { - return &PathError{"lchown", name, e} + e := ignoringEINTR(func() error { + return syscall.Lchown(name, uid, gid) + }) + if e != nil { + return &PathError{Op: "lchown", Path: name, Err: e} } return nil } @@ -172,7 +182,7 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { utimes[0] = syscall.NsecToTimespec(atime.UnixNano()) utimes[1] = syscall.NsecToTimespec(mtime.UnixNano()) if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil { - return &PathError{"chtimes", name, e} + return &PathError{Op: "chtimes", Path: name, Err: e} } return nil } @@ -222,3 +232,19 @@ func (f *File) checkValid(op string) error { } 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/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go index aed7713..8315887 100644 --- a/libgo/go/os/file_unix.go +++ b/libgo/go/os/file_unix.go @@ -9,7 +9,6 @@ package os import ( "internal/poll" "internal/syscall/unix" - "io" "runtime" "syscall" ) @@ -39,7 +38,9 @@ func rename(oldname, newname string) error { return &LinkError{"rename", oldname, newname, syscall.EEXIST} } } - err = syscall.Rename(oldname, newname) + err = ignoringEINTR(func() error { + return syscall.Rename(oldname, newname) + }) if err != nil { return &LinkError{"rename", oldname, newname, err} } @@ -60,8 +61,13 @@ type file struct { } // Fd returns the integer Unix file descriptor referencing the open file. -// The file descriptor is valid only until f.Close is called or f is garbage collected. -// On Unix systems this will cause the SetDeadline methods to stop working. +// 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)) @@ -127,9 +133,11 @@ func newFile(fd uintptr, name string, kind newFileKind) *File { // used with kqueue. if kind == kindOpenFile { switch runtime.GOOS { - case "darwin", "dragonfly", "freebsd", "netbsd", "openbsd": + case "darwin", "ios", "dragonfly", "freebsd", "netbsd", "openbsd": var st syscall.Stat_t - err := syscall.Fstat(fdi, &st) + err := ignoringEINTR(func() error { + return syscall.Fstat(fdi, &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 @@ -146,7 +154,7 @@ func newFile(fd uintptr, name string, kind newFileKind) *File { // 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" && typ == syscall.S_IFIFO { + if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && typ == syscall.S_IFIFO { pollable = false } } @@ -173,7 +181,6 @@ func newFile(fd uintptr, name string, kind newFileKind) *File { // Auxiliary information if the File describes a directory type dirInfo struct { - buf []byte // buffer for directory I/O dir *syscall.DIR // from opendir } @@ -222,7 +229,7 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { continue } - return nil, &PathError{"open", name, e} + return nil, &PathError{Op: "open", Path: name, Err: e} } // open(2) itself won't handle the sticky bit on *BSD and Solaris @@ -251,7 +258,7 @@ func (file *file) close() error { if e == poll.ErrFileClosing { e = ErrClosed } - err = &PathError{"close", file.name, e} + err = &PathError{Op: "close", Path: file.name, Err: e} } // no need for a finalizer anymore @@ -279,8 +286,11 @@ func (f *File) seek(offset int64, whence int) (ret int64, err error) { // 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 { - if e := syscall.Truncate(name, size); e != nil { - return &PathError{"truncate", name, e} + e := ignoringEINTR(func() error { + return syscall.Truncate(name, size) + }) + if e != nil { + return &PathError{Op: "truncate", Path: name, Err: e} } return nil } @@ -292,11 +302,15 @@ func Remove(name string) error { // whether name is a file or directory. // Try both: it is cheaper on average than // doing a Stat plus the right one. - e := syscall.Unlink(name) + e := ignoringEINTR(func() error { + return syscall.Unlink(name) + }) if e == nil { return nil } - e1 := syscall.Rmdir(name) + e1 := ignoringEINTR(func() error { + return syscall.Rmdir(name) + }) if e1 == nil { return nil } @@ -313,7 +327,7 @@ func Remove(name string) error { if e1 != syscall.ENOTDIR { e = e1 } - return &PathError{"remove", name, e} + return &PathError{Op: "remove", Path: name, Err: e} } func tempDir() string { @@ -331,7 +345,9 @@ func tempDir() string { // 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 := syscall.Link(oldname, newname) + e := ignoringEINTR(func() error { + return syscall.Link(oldname, newname) + }) if e != nil { return &LinkError{"link", oldname, newname, e} } @@ -341,55 +357,77 @@ func Link(oldname, newname string) error { // Symlink creates newname as a symbolic link to oldname. // If there is an error, it will be of type *LinkError. func Symlink(oldname, newname string) error { - e := syscall.Symlink(oldname, newname) + e := ignoringEINTR(func() error { + return syscall.Symlink(oldname, newname) + }) if e != nil { return &LinkError{"symlink", oldname, newname, e} } return nil } -func (f *File) readdir(n int) (fi []FileInfo, err error) { - dirname := f.name - if dirname == "" { - dirname = "." - } - names, err := f.Readdirnames(n) - fi = make([]FileInfo, 0, len(names)) - for _, filename := range names { - fip, lerr := lstat(dirname + "/" + filename) - if IsNotExist(lerr) { - // File disappeared between readdir + stat. - // Just treat it as if it didn't exist. - continue - } - if lerr != nil { - return fi, lerr - } - fi = append(fi, fip) - } - if len(fi) == 0 && err == nil && n > 0 { - // Per File.Readdir, the slice must be non-empty or err - // must be non-nil if n > 0. - err = io.EOF - } - return fi, err -} - // 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) - n, e := fixCount(syscall.Readlink(name, b)) + 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" && e == syscall.ERANGE { continue } if e != nil { - return "", &PathError{"readlink", name, e} + 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 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/libgo/go/os/getwd.go b/libgo/go/os/getwd.go index 6d25466..90604cf 100644 --- a/libgo/go/os/getwd.go +++ b/libgo/go/os/getwd.go @@ -15,10 +15,6 @@ var getwdCache struct { dir string } -// useSyscallwd determines whether to use the return value of -// syscall.Getwd based on its error. -var useSyscallwd = func(error) bool { return true } - // 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), @@ -45,10 +41,17 @@ func Getwd() (dir string, err error) { // If the operating system provides a Getwd call, use it. // Otherwise, we're trying to find our way back to ".". if syscall.ImplementsGetwd { - s, e := syscall.Getwd() - if useSyscallwd(e) { - return s, NewSyscallError("getwd", e) + 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. @@ -103,10 +106,10 @@ func Getwd() (dir string, err error) { Found: pd, err := fd.Stat() + fd.Close() if err != nil { return "", err } - fd.Close() if SameFile(pd, root) { break } diff --git a/libgo/go/os/getwd_darwin.go b/libgo/go/os/getwd_darwin.go deleted file mode 100644 index e51ffcd..0000000 --- a/libgo/go/os/getwd_darwin.go +++ /dev/null @@ -1,15 +0,0 @@ -// 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" - -func init() { - useSyscallwd = useSyscallwdDarwin -} - -func useSyscallwdDarwin(err error) bool { - return err != syscall.ENOTSUP -} diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go index 8a2f917..ccef141 100644 --- a/libgo/go/os/os_test.go +++ b/libgo/go/os/os_test.go @@ -11,7 +11,7 @@ import ( "fmt" "internal/testenv" "io" - "io/ioutil" + "os" . "os" osexec "os/exec" "path/filepath" @@ -23,6 +23,7 @@ import ( "sync" "syscall" "testing" + "testing/fstest" "time" ) @@ -50,7 +51,7 @@ var sysdir = func() *sysDir { "libpowermanager.so", }, } - case "darwin": + case "darwin", "ios": switch runtime.GOARCH { case "arm64": wd, err := syscall.Getwd() @@ -142,7 +143,7 @@ func localTmp() string { switch runtime.GOOS { case "android", "windows": return TempDir() - case "darwin": + case "darwin", "ios": switch runtime.GOARCH { case "arm64": return TempDir() @@ -152,7 +153,7 @@ func localTmp() string { } func newFile(testName string, t *testing.T) (f *File) { - f, err := ioutil.TempFile(localTmp(), "_Go_"+testName) + f, err := os.CreateTemp(localTmp(), "_Go_"+testName) if err != nil { t.Fatalf("TempFile %s: %s", testName, err) } @@ -160,7 +161,7 @@ func newFile(testName string, t *testing.T) (f *File) { } func newDir(testName string, t *testing.T) (name string) { - name, err := ioutil.TempDir(localTmp(), "_Go_"+testName) + name, err := os.MkdirTemp(localTmp(), "_Go_"+testName) if err != nil { t.Fatalf("TempDir %s: %s", testName, err) } @@ -307,25 +308,29 @@ func testReaddirnames(dir string, contents []string, t *testing.T) { defer file.Close() s, err2 := file.Readdirnames(-1) if err2 != nil { - t.Fatalf("readdirnames %q failed: %v", dir, err2) + t.Fatalf("Readdirnames %q failed: %v", dir, err2) } for _, m := range contents { found := false for _, n := range s { if n == "." || n == ".." { - t.Errorf("got %s in directory", n) + t.Errorf("got %q in directory", n) } - if equal(m, n) { - if found { - t.Error("present twice:", m) - } - found = true + 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, t *testing.T) { @@ -336,32 +341,98 @@ func testReaddir(dir string, contents []string, t *testing.T) { defer file.Close() s, err2 := file.Readdir(-1) if err2 != nil { - t.Fatalf("readdir %q failed: %v", dir, err2) + t.Fatalf("Readdir %q failed: %v", dir, err2) } for _, m := range contents { found := false for _, n := range s { - if equal(m, n.Name()) { - if found { - t.Error("present twice:", m) - } - found = true + 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 TestReaddirnames(t *testing.T) { +func testReadDir(dir string, contents []string, t *testing.T) { + 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) { testReaddirnames(".", dot, t) testReaddirnames(sysdir.name, sysdir.files, t) + testReaddirnames(t.TempDir(), nil, t) } -func TestReaddir(t *testing.T) { +func TestFileReaddir(t *testing.T) { testReaddir(".", dot, t) testReaddir(sysdir.name, sysdir.files, t) + testReaddir(t.TempDir(), nil, t) +} + +func TestFileReadDir(t *testing.T) { + testReadDir(".", dot, t) + testReadDir(sysdir.name, sysdir.files, t) + testReadDir(t.TempDir(), nil, t) } func benchmarkReaddirname(path string, b *testing.B) { @@ -398,6 +469,23 @@ func benchmarkReaddir(path string, b *testing.B) { 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) } @@ -406,6 +494,10 @@ 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++ { @@ -479,7 +571,7 @@ func TestReaddirnamesOneAtATime(t *testing.T) { switch runtime.GOOS { case "android": dir = "/system/bin" - case "darwin": + case "darwin", "ios": switch runtime.GOARCH { case "arm64": wd, err := Getwd() @@ -522,7 +614,7 @@ func TestReaddirNValues(t *testing.T) { if testing.Short() { t.Skip("test.short; skipping") } - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { t.Fatalf("TempDir: %v", err) } @@ -545,7 +637,8 @@ func TestReaddirNValues(t *testing.T) { } } - readDirExpect := func(n, want int, wantErr error) { + 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) @@ -555,7 +648,19 @@ func TestReaddirNValues(t *testing.T) { } } - readDirNamesExpect := func(n, want int, wantErr error) { + 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) @@ -565,7 +670,7 @@ func TestReaddirNValues(t *testing.T) { } } - for _, fn := range []func(int, int, error){readDirExpect, readDirNamesExpect} { + for _, fn := range []func(int, int, error){readdirExpect, readdirnamesExpect, readDirExpect} { // Test the slurp case openDir() fn(0, 105, nil) @@ -608,7 +713,7 @@ func TestReaddirStatFailures(t *testing.T) { // testing it wouldn't work. t.Skipf("skipping test on %v", runtime.GOOS) } - dir, err := ioutil.TempDir("", "") + dir, err := os.MkdirTemp("", "") if err != nil { t.Fatalf("TempDir: %v", err) } @@ -669,7 +774,7 @@ func TestReaddirStatFailures(t *testing.T) { // Readdir on a regular file should fail. func TestReaddirOfFile(t *testing.T) { - f, err := ioutil.TempFile("", "_Go_ReaddirOfFile") + f, err := os.CreateTemp("", "_Go_ReaddirOfFile") if err != nil { t.Fatal(err) } @@ -686,6 +791,10 @@ func TestReaddirOfFile(t *testing.T) { 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) } @@ -756,7 +865,7 @@ func chtmpdir(t *testing.T) func() { if err != nil { t.Fatalf("chtmpdir: %v", err) } - d, err := ioutil.TempDir("", "test") + d, err := os.MkdirTemp("", "test") if err != nil { t.Fatalf("chtmpdir: %v", err) } @@ -881,12 +990,12 @@ func TestRenameOverwriteDest(t *testing.T) { toData := []byte("to") fromData := []byte("from") - err := ioutil.WriteFile(to, toData, 0777) + err := os.WriteFile(to, toData, 0777) if err != nil { t.Fatalf("write file %q failed: %v", to, err) } - err = ioutil.WriteFile(from, fromData, 0777) + err = os.WriteFile(from, fromData, 0777) if err != nil { t.Fatalf("write file %q failed: %v", from, err) } @@ -1093,32 +1202,38 @@ func checkMode(t *testing.T, path string, mode FileMode) { if err != nil { t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err) } - if dir.Mode()&0777 != mode { + 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 under windows. - if runtime.GOOS == "windows" { - return - } f := newFile("TestChmod", t) defer Remove(f.Name()) defer f.Close() + // Creation mode is read write - if err := Chmod(f.Name(), 0456); err != nil { - t.Fatalf("chmod %s 0456: %s", f.Name(), err) + fm := FileMode(0456) + if runtime.GOOS == "windows" { + fm = FileMode(0444) // read-only file } - checkMode(t, f.Name(), 0456) + if err := Chmod(f.Name(), fm); err != nil { + t.Fatalf("chmod %s %#o: %s", f.Name(), fm, err) + } + checkMode(t, f.Name(), fm) - if err := f.Chmod(0123); err != nil { - t.Fatalf("chmod %s 0123: %s", f.Name(), err) + 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(), 0123) + 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) @@ -1224,7 +1339,7 @@ func testChtimes(t *testing.T, name string) { // the contents are accessed; also, it is set // whenever mtime is set. case "netbsd": - mounts, _ := ioutil.ReadFile("/proc/mounts") + mounts, _ := os.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 { @@ -1293,12 +1408,12 @@ func TestChdirAndGetwd(t *testing.T) { dirs = []string{"/system/bin"} case "plan9": dirs = []string{"/", "/usr"} - case "darwin": + case "darwin", "ios": switch runtime.GOARCH { case "arm64": dirs = nil for _, d := range []string{"d1", "d2"} { - dir, err := ioutil.TempDir("", d) + dir, err := os.MkdirTemp("", d) if err != nil { t.Fatalf("TempDir: %v", err) } @@ -1392,7 +1507,7 @@ func TestProgWideChdir(t *testing.T) { c <- true t.Fatalf("Getwd: %v", err) } - d, err := ioutil.TempDir("", "test") + d, err := os.MkdirTemp("", "test") if err != nil { c <- true t.Fatalf("TempDir: %v", err) @@ -1463,7 +1578,7 @@ func TestSeek(t *testing.T) { 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, _ := ioutil.ReadFile("/proc/mounts") + mounts, _ := os.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") @@ -1566,8 +1681,8 @@ func TestOpenError(t *testing.T) { func TestOpenNoName(t *testing.T) { f, err := Open("") if err == nil { - t.Fatal(`Open("") succeeded`) f.Close() + t.Fatal(`Open("") succeeded`) } } @@ -1745,7 +1860,7 @@ func TestWriteAt(t *testing.T) { t.Fatalf("WriteAt 7: %d, %v", n, err) } - b, err := ioutil.ReadFile(f.Name()) + b, err := os.ReadFile(f.Name()) if err != nil { t.Fatalf("ReadFile %s: %v", f.Name(), err) } @@ -1793,7 +1908,7 @@ func writeFile(t *testing.T, fname string, flag int, text string) string { t.Fatalf("WriteString: %d, %v", n, err) } f.Close() - data, err := ioutil.ReadFile(fname) + data, err := os.ReadFile(fname) if err != nil { t.Fatalf("ReadFile: %v", err) } @@ -1835,7 +1950,7 @@ func TestAppend(t *testing.T) { func TestStatDirWithTrailingSlash(t *testing.T) { // Create new temporary directory and arrange to clean it up. - path, err := ioutil.TempDir("", "_TestStatDirWithSlash_") + path, err := os.MkdirTemp("", "_TestStatDirWithSlash_") if err != nil { t.Fatalf("TempDir: %s", err) } @@ -1977,7 +2092,7 @@ func TestLargeWriteToConsole(t *testing.T) { func TestStatDirModeExec(t *testing.T) { const mode = 0111 - path, err := ioutil.TempDir("", "go-build") + path, err := os.MkdirTemp("", "go-build") if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } @@ -2046,7 +2161,7 @@ func TestStatStdin(t *testing.T) { func TestStatRelativeSymlink(t *testing.T) { testenv.MustHaveSymlink(t) - tmpdir, err := ioutil.TempDir("", "TestStatRelativeSymlink") + tmpdir, err := os.MkdirTemp("", "TestStatRelativeSymlink") if err != nil { t.Fatal(err) } @@ -2136,8 +2251,8 @@ func TestLongPath(t *testing.T) { t.Fatalf("MkdirAll failed: %v", err) } data := []byte("hello world\n") - if err := ioutil.WriteFile(sizedTempDir+"/foo.txt", data, 0644); err != nil { - t.Fatalf("ioutil.WriteFile() failed: %v", err) + if err := os.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) @@ -2321,7 +2436,7 @@ func TestRemoveAllRace(t *testing.T) { n := runtime.GOMAXPROCS(16) defer runtime.GOMAXPROCS(n) - root, err := ioutil.TempDir("", "issue") + root, err := os.MkdirTemp("", "issue") if err != nil { t.Fatal(err) } @@ -2427,7 +2542,7 @@ func testDoubleCloseError(t *testing.T, path string) { if err := file.Close(); err == nil { t.Error("second Close did not fail") } else if pe, ok := err.(*PathError); !ok { - t.Errorf("second Close returned unexpected error type %T; expected os.PathError", pe) + t.Errorf("second Close returned unexpected error type %T; expected fs.PathError", pe) } else if pe.Err != ErrClosed { t.Errorf("second Close returned %q, wanted %q", err, ErrClosed) } else { @@ -2572,3 +2687,28 @@ func TestOpenFileKeepsPermissions(t *testing.T) { t.Errorf("Stat after OpenFile is %v, should be writable", fi.Mode()) } } + +func TestDirFS(t *testing.T) { + if err := fstest.TestFS(DirFS("./signal"), "signal.go", "internal/pty/pty.go"); err != nil { + t.Fatal(err) + } +} + +func TestReadFileProc(t *testing.T) { + // 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) + } +} diff --git a/libgo/go/os/os_unix_test.go b/libgo/go/os/os_unix_test.go index 7b6fa0c..9a5d7bf 100644 --- a/libgo/go/os/os_unix_test.go +++ b/libgo/go/os/os_unix_test.go @@ -8,7 +8,7 @@ package os_test import ( "io" - "io/ioutil" + "os" . "os" "path/filepath" "runtime" @@ -190,7 +190,7 @@ func TestReaddirRemoveRace(t *testing.T) { } dir := newDir("TestReaddirRemoveRace", t) defer RemoveAll(dir) - if err := ioutil.WriteFile(filepath.Join(dir, "some-file"), []byte("hello"), 0644); err != nil { + if err := os.WriteFile(filepath.Join(dir, "some-file"), []byte("hello"), 0644); err != nil { t.Fatal(err) } d, err := Open(dir) diff --git a/libgo/go/os/os_windows_test.go b/libgo/go/os/os_windows_test.go index f03ec75..8d1d1f6 100644 --- a/libgo/go/os/os_windows_test.go +++ b/libgo/go/os/os_windows_test.go @@ -12,7 +12,7 @@ import ( "internal/syscall/windows/registry" "internal/testenv" "io" - "io/ioutil" + "io/fs" "os" osexec "os/exec" "path/filepath" @@ -30,7 +30,7 @@ import ( type syscallDescriptor = syscall.Handle func TestSameWindowsFile(t *testing.T) { - temp, err := ioutil.TempDir("", "TestSameWindowsFile") + temp, err := os.MkdirTemp("", "TestSameWindowsFile") if err != nil { t.Fatal(err) } @@ -89,7 +89,7 @@ type dirLinkTest struct { } func testDirLinks(t *testing.T, tests []dirLinkTest) { - tmpdir, err := ioutil.TempDir("", "testDirLinks") + tmpdir, err := os.MkdirTemp("", "testDirLinks") if err != nil { t.Fatal(err) } @@ -114,7 +114,7 @@ func testDirLinks(t *testing.T, tests []dirLinkTest) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644) + err = os.WriteFile(filepath.Join(dir, "abc"), []byte("abc"), 0644) if err != nil { t.Fatal(err) } @@ -126,7 +126,7 @@ func testDirLinks(t *testing.T, tests []dirLinkTest) { continue } - data, err := ioutil.ReadFile(filepath.Join(link, "abc")) + data, err := os.ReadFile(filepath.Join(link, "abc")) if err != nil { t.Errorf("failed to read abc file: %v", err) continue @@ -164,11 +164,11 @@ func testDirLinks(t *testing.T, tests []dirLinkTest) { t.Errorf("failed to lstat link %v: %v", link, err) continue } - if m := fi2.Mode(); m&os.ModeSymlink == 0 { + 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&os.ModeDir != 0 { + 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 } @@ -438,7 +438,7 @@ func TestNetworkSymbolicLink(t *testing.T) { const _NERR_ServerNotStarted = syscall.Errno(2114) - dir, err := ioutil.TempDir("", "TestNetworkSymbolicLink") + dir, err := os.MkdirTemp("", "TestNetworkSymbolicLink") if err != nil { t.Fatal(err) } @@ -599,7 +599,7 @@ func TestStatDir(t *testing.T) { } func TestOpenVolumeName(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "TestOpenVolumeName") + tmpdir, err := os.MkdirTemp("", "TestOpenVolumeName") if err != nil { t.Fatal(err) } @@ -618,7 +618,7 @@ func TestOpenVolumeName(t *testing.T) { want := []string{"file1", "file2", "file3", "gopher.txt"} sort.Strings(want) for _, name := range want { - err := ioutil.WriteFile(filepath.Join(tmpdir, name), nil, 0777) + err := os.WriteFile(filepath.Join(tmpdir, name), nil, 0777) if err != nil { t.Fatal(err) } @@ -642,7 +642,7 @@ func TestOpenVolumeName(t *testing.T) { } func TestDeleteReadOnly(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "TestDeleteReadOnly") + tmpdir, err := os.MkdirTemp("", "TestDeleteReadOnly") if err != nil { t.Fatal(err) } @@ -681,7 +681,7 @@ func TestStatSymlinkLoop(t *testing.T) { defer os.Remove("x") _, err = os.Stat("x") - if _, ok := err.(*os.PathError); !ok { + if _, ok := err.(*fs.PathError); !ok { t.Errorf("expected *PathError, got %T: %v\n", err, err) } } @@ -803,7 +803,7 @@ func compareCommandLineToArgvWithSyscall(t *testing.T, cmd string) { } func TestCmdArgs(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "TestCmdArgs") + tmpdir, err := os.MkdirTemp("", "TestCmdArgs") if err != nil { t.Fatal(err) } @@ -822,7 +822,7 @@ func main() { } ` src := filepath.Join(tmpdir, "main.go") - err = ioutil.WriteFile(src, []byte(prog), 0666) + err = os.WriteFile(src, []byte(prog), 0666) if err != nil { t.Fatal(err) } @@ -970,14 +970,14 @@ func TestSymlinkCreation(t *testing.T) { } t.Parallel() - temp, err := ioutil.TempDir("", "TestSymlinkCreation") + temp, err := os.MkdirTemp("", "TestSymlinkCreation") if err != nil { t.Fatal(err) } defer os.RemoveAll(temp) dummyFile := filepath.Join(temp, "file") - err = ioutil.WriteFile(dummyFile, []byte(""), 0644) + err = os.WriteFile(dummyFile, []byte(""), 0644) if err != nil { t.Fatal(err) } @@ -1206,7 +1206,7 @@ func mklinkd(t *testing.T, link, target string) { } func TestWindowsReadlink(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "TestWindowsReadlink") + tmpdir, err := os.MkdirTemp("", "TestWindowsReadlink") if err != nil { t.Fatal(err) } @@ -1271,7 +1271,7 @@ func TestWindowsReadlink(t *testing.T) { testReadlink(t, "reldirlink", "dir") file := filepath.Join(tmpdir, "file") - err = ioutil.WriteFile(file, []byte(""), 0666) + err = os.WriteFile(file, []byte(""), 0666) if err != nil { t.Fatal(err) } @@ -1291,9 +1291,9 @@ func TestWindowsReadlink(t *testing.T) { // os.Mkdir(os.DevNull) fails. func TestMkdirDevNull(t *testing.T) { err := os.Mkdir(os.DevNull, 777) - oserr, ok := err.(*os.PathError) + oserr, ok := err.(*fs.PathError) if !ok { - t.Fatalf("error (%T) is not *os.PathError", err) + t.Fatalf("error (%T) is not *fs.PathError", err) } errno, ok := oserr.Err.(syscall.Errno) if !ok { diff --git a/libgo/go/os/path.go b/libgo/go/os/path.go index ba43ea3..df87887 100644 --- a/libgo/go/os/path.go +++ b/libgo/go/os/path.go @@ -22,7 +22,7 @@ func MkdirAll(path string, perm FileMode) error { if dir.IsDir() { return nil } - return &PathError{"mkdir", path, syscall.ENOTDIR} + return &PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} } // Slow path: make sure parent exists and then call Mkdir for path. diff --git a/libgo/go/os/path_test.go b/libgo/go/os/path_test.go index d586daf..b79d958 100644 --- a/libgo/go/os/path_test.go +++ b/libgo/go/os/path_test.go @@ -6,7 +6,7 @@ package os_test import ( "internal/testenv" - "io/ioutil" + "os" . "os" "path/filepath" "runtime" @@ -78,7 +78,7 @@ func TestMkdirAll(t *testing.T) { func TestMkdirAllWithSymlink(t *testing.T) { testenv.MustHaveSymlink(t) - tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-") + tmpDir, err := os.MkdirTemp("", "TestMkdirAllWithSymlink-") if err != nil { t.Fatal(err) } @@ -107,7 +107,7 @@ func TestMkdirAllAtSlash(t *testing.T) { switch runtime.GOOS { case "android", "plan9", "windows": t.Skipf("skipping on %s", runtime.GOOS) - case "darwin": + case "darwin", "ios": switch runtime.GOARCH { case "arm64": t.Skipf("skipping on darwin/arm64, mkdir returns EPERM") diff --git a/libgo/go/os/path_windows_test.go b/libgo/go/os/path_windows_test.go index 862b404..869db8f 100644 --- a/libgo/go/os/path_windows_test.go +++ b/libgo/go/os/path_windows_test.go @@ -5,7 +5,6 @@ package os_test import ( - "io/ioutil" "os" "strings" "syscall" @@ -48,7 +47,7 @@ func TestFixLongPath(t *testing.T) { } func TestMkdirAllExtendedLength(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "TestMkdirAllExtendedLength") + tmpDir, err := os.MkdirTemp("", "TestMkdirAllExtendedLength") if err != nil { t.Fatal(err) } diff --git a/libgo/go/os/pipe2_illumos.go b/libgo/go/os/pipe2_illumos.go new file mode 100644 index 0000000..026ce62 --- /dev/null +++ b/libgo/go/os/pipe2_illumos.go @@ -0,0 +1,25 @@ +// 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. + +// +build illumos + +package os + +import ( + "internal/syscall/unix" + "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 := unix.Pipe2(p[0:], syscall.O_CLOEXEC) + if e != nil { + return nil, nil, NewSyscallError("pipe", e) + } + + return newFile(uintptr(p[0]), "|0", kindPipe), newFile(uintptr(p[1]), "|1", kindPipe), nil +} diff --git a/libgo/go/os/pipe_bsd.go b/libgo/go/os/pipe_bsd.go index 0d2d82f..115d6ba 100644 --- a/libgo/go/os/pipe_bsd.go +++ b/libgo/go/os/pipe_bsd.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build aix darwin dragonfly js,wasm solaris +// +build aix darwin dragonfly js,wasm solaris,!illumos package os diff --git a/libgo/go/os/pipe_test.go b/libgo/go/os/pipe_test.go index 429bd81..b98e538 100644 --- a/libgo/go/os/pipe_test.go +++ b/libgo/go/os/pipe_test.go @@ -13,7 +13,7 @@ import ( "fmt" "internal/testenv" "io" - "io/ioutil" + "io/fs" "os" osexec "os/exec" "os/signal" @@ -46,7 +46,7 @@ func TestEPIPE(t *testing.T) { if err == nil { t.Fatal("unexpected success of Write to broken pipe") } - if pe, ok := err.(*os.PathError); ok { + if pe, ok := err.(*fs.PathError); ok { err = pe.Err } if se, ok := err.(*os.SyscallError); ok { @@ -160,7 +160,7 @@ func testClosedPipeRace(t *testing.T, read bool) { // Get the amount we have to write to overload a pipe // with no reader. limit = 131073 - if b, err := ioutil.ReadFile("/proc/sys/fs/pipe-max-size"); err == nil { + 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 } @@ -202,10 +202,10 @@ func testClosedPipeRace(t *testing.T, read bool) { } if err == nil { t.Error("I/O on closed pipe unexpectedly succeeded") - } else if pe, ok := err.(*os.PathError); !ok { - t.Errorf("I/O on closed pipe returned unexpected error type %T; expected os.PathError", pe) - } else if pe.Err != os.ErrClosed { - t.Errorf("got error %q but expected %q", pe.Err, os.ErrClosed) + } 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) } @@ -233,7 +233,7 @@ func TestReadNonblockingFd(t *testing.T) { defer syscall.SetNonblock(fd, false) _, err := os.Stdin.Read(make([]byte, 1)) if err != nil { - if perr, ok := err.(*os.PathError); !ok || perr.Err != syscall.EAGAIN { + if perr, ok := err.(*fs.PathError); !ok || perr.Err != syscall.EAGAIN { t.Fatalf("read on nonblocking stdin got %q, should have gotten EAGAIN", err) } } @@ -308,10 +308,10 @@ func testCloseWithBlockingRead(t *testing.T, r, w *os.File) { if err == nil { t.Error("I/O on closed pipe unexpectedly succeeded") } - if pe, ok := err.(*os.PathError); ok { + if pe, ok := err.(*fs.PathError); ok { err = pe.Err } - if err != io.EOF && err != os.ErrClosed { + if err != io.EOF && err != fs.ErrClosed { t.Errorf("got %v, expected EOF or closed", err) } }(c2) diff --git a/libgo/go/os/proc.go b/libgo/go/os/proc.go index 7364d63..cbd5a6a 100644 --- a/libgo/go/os/proc.go +++ b/libgo/go/os/proc.go @@ -7,6 +7,7 @@ package os import ( + "internal/testlog" "runtime" "syscall" ) @@ -60,6 +61,13 @@ func Getgroups() ([]int, error) { // For portability, the status code should be in the range [0, 125]. func Exit(code int) { if code == 0 { + if 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") + } + // Give race detector a chance to fail the program. // Racy programs do not have the right to finish successfully. runtime_beforeExit() diff --git a/libgo/go/os/read_test.go b/libgo/go/os/read_test.go new file mode 100644 index 0000000..b42ff36 --- /dev/null +++ b/libgo/go/os/read_test.go @@ -0,0 +1,131 @@ +// 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) { + 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) { + 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.") + } + + // 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) { + 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 { + // This doesn't work in the gofrontend testsuite framework. + if runtime.Compiler == "gc" { + t.Fatalf("ReadDir %s: exec directory not found", dirname) + } + } +} diff --git a/libgo/go/os/readfrom_linux_test.go b/libgo/go/os/readfrom_linux_test.go index 00faf39..3704717 100644 --- a/libgo/go/os/readfrom_linux_test.go +++ b/libgo/go/os/readfrom_linux_test.go @@ -8,8 +8,8 @@ import ( "bytes" "internal/poll" "io" - "io/ioutil" "math/rand" + "os" . "os" "path/filepath" "strconv" @@ -173,7 +173,7 @@ func TestCopyFileRange(t *testing.T) { }) t.Run("Nil", func(t *testing.T) { var nilFile *File - anyFile, err := ioutil.TempFile("", "") + anyFile, err := os.CreateTemp("", "") if err != nil { t.Fatal(err) } diff --git a/libgo/go/os/removeall_at.go b/libgo/go/os/removeall_at.go index 8bbdcf0..f9eafa1 100644 --- a/libgo/go/os/removeall_at.go +++ b/libgo/go/os/removeall_at.go @@ -22,7 +22,7 @@ func removeAll(path string) error { // The rmdir system call does not permit removing ".", // so we don't permit it either. if endsWithDot(path) { - return &PathError{"RemoveAll", path, syscall.EINVAL} + return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL} } // Simple case: if Remove works, we're done. @@ -70,7 +70,7 @@ func removeAllFrom(parent *File, base string) error { // whose contents need to be removed. // Otherwise just return the error. if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES { - return &PathError{"unlinkat", base, err} + return &PathError{Op: "unlinkat", Path: base, Err: err} } // Is this a directory we need to recurse into? @@ -80,11 +80,11 @@ func removeAllFrom(parent *File, base string) error { if IsNotExist(statErr) { return nil } - return &PathError{"fstatat", base, statErr} + 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{"unlinkat", base, err} + return &PathError{Op: "unlinkat", Path: base, Err: err} } // Remove the directory's entries. @@ -99,7 +99,7 @@ func removeAllFrom(parent *File, base string) error { if IsNotExist(err) { return nil } - recurseErr = &PathError{"openfdat", base, err} + recurseErr = &PathError{Op: "openfdat", Path: base, Err: err} break } @@ -113,7 +113,7 @@ func removeAllFrom(parent *File, base string) error { if IsNotExist(readErr) { return nil } - return &PathError{"readdirnames", base, readErr} + return &PathError{Op: "readdirnames", Path: base, Err: readErr} } respSize = len(names) @@ -159,7 +159,7 @@ func removeAllFrom(parent *File, base string) error { if recurseErr != nil { return recurseErr } - return &PathError{"unlinkat", base, unlinkError} + return &PathError{Op: "unlinkat", Path: base, Err: unlinkError} } // openFdAt opens path relative to the directory in fd. diff --git a/libgo/go/os/removeall_noat.go b/libgo/go/os/removeall_noat.go index fb9b45f..a4cde5a 100644 --- a/libgo/go/os/removeall_noat.go +++ b/libgo/go/os/removeall_noat.go @@ -23,7 +23,7 @@ func removeAll(path string) error { // so we don't permit it to remain consistent with the // "at" implementation of RemoveAll. if endsWithDot(path) { - return &PathError{"RemoveAll", path, syscall.EINVAL} + return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL} } // Simple case: if Remove works, we're done. diff --git a/libgo/go/os/removeall_test.go b/libgo/go/os/removeall_test.go index 8a71f68..3a2f6e3 100644 --- a/libgo/go/os/removeall_test.go +++ b/libgo/go/os/removeall_test.go @@ -6,7 +6,7 @@ package os_test import ( "fmt" - "io/ioutil" + "os" . "os" "path/filepath" "runtime" @@ -15,7 +15,7 @@ import ( ) func TestRemoveAll(t *testing.T) { - tmpDir, err := ioutil.TempDir("", "TestRemoveAll-") + tmpDir, err := os.MkdirTemp("", "TestRemoveAll-") if err != nil { t.Fatal(err) } @@ -128,7 +128,7 @@ func TestRemoveAllLarge(t *testing.T) { t.Skip("skipping in short mode") } - tmpDir, err := ioutil.TempDir("", "TestRemoveAll-") + tmpDir, err := os.MkdirTemp("", "TestRemoveAll-") if err != nil { t.Fatal(err) } @@ -158,7 +158,7 @@ func TestRemoveAllLarge(t *testing.T) { func TestRemoveAllLongPath(t *testing.T) { switch runtime.GOOS { - case "aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "illumos", "solaris": + case "aix", "darwin", "ios", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "illumos", "solaris": break default: t.Skip("skipping for not implemented platforms") @@ -169,7 +169,7 @@ func TestRemoveAllLongPath(t *testing.T) { t.Fatalf("Could not get wd: %s", err) } - startPath, err := ioutil.TempDir("", "TestRemoveAllLongPath-") + startPath, err := os.MkdirTemp("", "TestRemoveAllLongPath-") if err != nil { t.Fatalf("Could not create TempDir: %s", err) } @@ -211,7 +211,7 @@ func TestRemoveAllDot(t *testing.T) { if err != nil { t.Fatalf("Could not get wd: %s", err) } - tempDir, err := ioutil.TempDir("", "TestRemoveAllDot-") + tempDir, err := os.MkdirTemp("", "TestRemoveAllDot-") if err != nil { t.Fatalf("Could not create TempDir: %s", err) } @@ -236,7 +236,7 @@ func TestRemoveAllDot(t *testing.T) { func TestRemoveAllDotDot(t *testing.T) { t.Parallel() - tempDir, err := ioutil.TempDir("", "TestRemoveAllDotDot-") + tempDir, err := os.MkdirTemp("", "TestRemoveAllDotDot-") if err != nil { t.Fatal(err) } @@ -261,7 +261,7 @@ func TestRemoveAllDotDot(t *testing.T) { func TestRemoveReadOnlyDir(t *testing.T) { t.Parallel() - tempDir, err := ioutil.TempDir("", "TestRemoveReadOnlyDir-") + tempDir, err := os.MkdirTemp("", "TestRemoveReadOnlyDir-") if err != nil { t.Fatal(err) } @@ -298,7 +298,7 @@ func TestRemoveAllButReadOnlyAndPathError(t *testing.T) { t.Parallel() - tempDir, err := ioutil.TempDir("", "TestRemoveAllButReadOnly-") + tempDir, err := os.MkdirTemp("", "TestRemoveAllButReadOnly-") if err != nil { t.Fatal(err) } @@ -355,11 +355,12 @@ func TestRemoveAllButReadOnlyAndPathError(t *testing.T) { // The error should be of type *PathError. // see issue 30491 for details. if pathErr, ok := err.(*PathError); ok { - if g, w := pathErr.Path, filepath.Join(tempDir, "b", "y"); g != w { - t.Errorf("got %q, expected pathErr.path %q", g, w) + 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("got %T, expected *os.PathError", err) + t.Errorf("RemoveAll(%q): error has type %T, want *fs.PathError", tempDir, err) } for _, dir := range dirs { @@ -388,7 +389,7 @@ func TestRemoveUnreadableDir(t *testing.T) { t.Parallel() - tempDir, err := ioutil.TempDir("", "TestRemoveAllButReadOnly-") + tempDir, err := os.MkdirTemp("", "TestRemoveAllButReadOnly-") if err != nil { t.Fatal(err) } @@ -412,7 +413,7 @@ func TestRemoveAllWithMoreErrorThanReqSize(t *testing.T) { t.Skip("skipping in short mode") } - tmpDir, err := ioutil.TempDir("", "TestRemoveAll-") + tmpDir, err := os.MkdirTemp("", "TestRemoveAll-") if err != nil { t.Fatal(err) } diff --git a/libgo/go/os/signal/example_unix_test.go b/libgo/go/os/signal/example_unix_test.go new file mode 100644 index 0000000..a0af37a --- /dev/null +++ b/libgo/go/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. + +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +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/libgo/go/os/signal/signal.go b/libgo/go/os/signal/signal.go index 8e31aa2..4250a7e 100644 --- a/libgo/go/os/signal/signal.go +++ b/libgo/go/os/signal/signal.go @@ -5,6 +5,7 @@ package signal import ( + "context" "os" "sync" ) @@ -257,3 +258,77 @@ func process(sig os.Signal) { } } } + +// 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/libgo/go/os/signal/signal_cgo_test.go b/libgo/go/os/signal/signal_cgo_test.go index a117221..a8a4856 100644 --- a/libgo/go/os/signal/signal_cgo_test.go +++ b/libgo/go/os/signal/signal_cgo_test.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "io" + "io/fs" "os" "os/exec" ptypkg "os/signal/internal/pty" @@ -127,7 +128,7 @@ func TestTerminalSignal(t *testing.T) { if len(line) > 0 || len(handled) > 0 { t.Logf("%q", append(handled, line...)) } - if perr, ok := err.(*os.PathError); ok { + if perr, ok := err.(*fs.PathError); ok { err = perr.Err } // EOF means pty is closed. diff --git a/libgo/go/os/signal/signal_test.go b/libgo/go/os/signal/signal_test.go index 98a1cc1..66b8327 100644 --- a/libgo/go/os/signal/signal_test.go +++ b/libgo/go/os/signal/signal_test.go @@ -8,10 +8,10 @@ package signal import ( "bytes" + "context" "flag" "fmt" "internal/testenv" - "io/ioutil" "os" "os/exec" "runtime" @@ -303,7 +303,7 @@ func TestDetectNohup(t *testing.T) { os.Remove("nohup.out") out, err := exec.Command("/usr/bin/nohup", os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput() - data, _ := ioutil.ReadFile("nohup.out") + data, _ := os.ReadFile("nohup.out") os.Remove("nohup.out") if err != nil { t.Errorf("ran test with -check_sighup_ignored under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", err, out, data) @@ -674,3 +674,164 @@ func TestTime(t *testing.T) { close(stop) <-done } + +func TestNotifyContext(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) + } + + syscall.Kill(syscall.Getpid(), syscall.SIGINT) + 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 SIGINT") + } +} + +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 TestNotifyContextSimultaneousNotifications(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() { + syscall.Kill(syscall.Getpid(), syscall.SIGINT) + 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 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) + } +} diff --git a/libgo/go/os/stat_plan9.go b/libgo/go/os/stat_plan9.go index b43339a..57ae6fb 100644 --- a/libgo/go/os/stat_plan9.go +++ b/libgo/go/os/stat_plan9.go @@ -11,7 +11,7 @@ import ( const bitSize16 = 2 -func fileInfoFromStat(d *syscall.Dir) FileInfo { +func fileInfoFromStat(d *syscall.Dir) *fileStat { fs := &fileStat{ name: d.Name, size: d.Length, @@ -65,7 +65,7 @@ func dirstat(arg interface{}) (*syscall.Dir, error) { } if n < bitSize16 { - return nil, &PathError{"stat", name, err} + return nil, &PathError{Op: "stat", Path: name, Err: err} } // Pull the real size out of the stat message. @@ -76,7 +76,7 @@ func dirstat(arg interface{}) (*syscall.Dir, error) { if size <= n { d, err := syscall.UnmarshalDir(buf[:n]) if err != nil { - return nil, &PathError{"stat", name, err} + return nil, &PathError{Op: "stat", Path: name, Err: err} } return d, nil } @@ -87,7 +87,7 @@ func dirstat(arg interface{}) (*syscall.Dir, error) { err = syscall.ErrBadStat } - return nil, &PathError{"stat", name, err} + return nil, &PathError{Op: "stat", Path: name, Err: err} } // statNolog implements Stat for Plan 9. diff --git a/libgo/go/os/stat_test.go b/libgo/go/os/stat_test.go index 60f3b4c..c409f0f 100644 --- a/libgo/go/os/stat_test.go +++ b/libgo/go/os/stat_test.go @@ -6,7 +6,7 @@ package os_test import ( "internal/testenv" - "io/ioutil" + "io/fs" "os" "path/filepath" "runtime" @@ -14,7 +14,7 @@ import ( ) // 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, os.FileInfo)) { +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 { @@ -70,7 +70,7 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh } } - // test os.FileInfo returned by os.Readdir + // 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 @@ -88,7 +88,7 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh t.Error(err) return } - var lsfi2 os.FileInfo + var lsfi2 fs.FileInfo base := filepath.Base(path) for _, fi2 := range fis { if fi2.Name() == base { @@ -108,34 +108,34 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh } // testIsDir verifies that fi refers to directory. -func testIsDir(t *testing.T, path string, fi os.FileInfo) { +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()&os.ModeSymlink != 0 { + 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 os.FileInfo) { +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()&os.ModeSymlink == 0 { + 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 os.FileInfo) { +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()&os.ModeSymlink != 0 { + if fi.Mode()&fs.ModeSymlink != 0 { t.Errorf("%q should not be a symlink", path) } } @@ -185,7 +185,7 @@ func testSymlinkSameFile(t *testing.T, path, link string) { func TestDirAndSymlinkStats(t *testing.T) { testenv.MustHaveSymlink(t) - tmpdir, err := ioutil.TempDir("", "TestDirAndSymlinkStats") + tmpdir, err := os.MkdirTemp("", "TestDirAndSymlinkStats") if err != nil { t.Fatal(err) } @@ -218,14 +218,14 @@ func TestDirAndSymlinkStats(t *testing.T) { func TestFileAndSymlinkStats(t *testing.T) { testenv.MustHaveSymlink(t) - tmpdir, err := ioutil.TempDir("", "TestFileAndSymlinkStats") + tmpdir, err := os.MkdirTemp("", "TestFileAndSymlinkStats") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpdir) file := filepath.Join(tmpdir, "file") - err = ioutil.WriteFile(file, []byte(""), 0644) + err = os.WriteFile(file, []byte(""), 0644) if err != nil { t.Fatal(err) } @@ -252,7 +252,7 @@ func TestFileAndSymlinkStats(t *testing.T) { func TestSymlinkWithTrailingSlash(t *testing.T) { testenv.MustHaveSymlink(t) - tmpdir, err := ioutil.TempDir("", "TestSymlinkWithTrailingSlash") + tmpdir, err := os.MkdirTemp("", "TestSymlinkWithTrailingSlash") if err != nil { t.Fatal(err) } diff --git a/libgo/go/os/stat_unix.go b/libgo/go/os/stat_unix.go index d489ad1..7873bf9 100644 --- a/libgo/go/os/stat_unix.go +++ b/libgo/go/os/stat_unix.go @@ -19,7 +19,7 @@ func (f *File) Stat() (FileInfo, error) { var fs fileStat err := f.pfd.Fstat(&fs.sys) if err != nil { - return nil, &PathError{"stat", f.name, err} + return nil, &PathError{Op: "stat", Path: f.name, Err: err} } fillFileStatFromSys(&fs, f.name) return &fs, nil @@ -28,9 +28,11 @@ func (f *File) Stat() (FileInfo, error) { // statNolog stats a file with no test logging. func statNolog(name string) (FileInfo, error) { var fs fileStat - err := syscall.Stat(name, &fs.sys) + err := ignoringEINTR(func() error { + return syscall.Stat(name, &fs.sys) + }) if err != nil { - return nil, &PathError{"stat", name, err} + return nil, &PathError{Op: "stat", Path: name, Err: err} } fillFileStatFromSys(&fs, name) return &fs, nil @@ -39,9 +41,11 @@ func statNolog(name string) (FileInfo, error) { // lstatNolog lstats a file with no test logging. func lstatNolog(name string) (FileInfo, error) { var fs fileStat - err := syscall.Lstat(name, &fs.sys) + err := ignoringEINTR(func() error { + return syscall.Lstat(name, &fs.sys) + }) if err != nil { - return nil, &PathError{"lstat", name, err} + return nil, &PathError{Op: "lstat", Path: name, Err: err} } fillFileStatFromSys(&fs, name) return &fs, nil diff --git a/libgo/go/os/tempfile.go b/libgo/go/os/tempfile.go new file mode 100644 index 0000000..2728485 --- /dev/null +++ b/libgo/go/os/tempfile.go @@ -0,0 +1,118 @@ +// 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" + "strings" +) + +// 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 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, TempFile 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: dir + string(PathSeparator) + 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 := strings.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, TempFile 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 +} diff --git a/libgo/go/os/tempfile_test.go b/libgo/go/os/tempfile_test.go new file mode 100644 index 0000000..e71a244 --- /dev/null +++ b/libgo/go/os/tempfile_test.go @@ -0,0 +1,193 @@ +// 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) { + 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) { + 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) { + 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) { + 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) { + 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 satisifying IsNotExist", err, badDir) + } +} + +func TestMkdirTempBadPattern(t *testing.T) { + 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/libgo/go/os/testdata/hello b/libgo/go/os/testdata/hello new file mode 100644 index 0000000..e47c092 --- /dev/null +++ b/libgo/go/os/testdata/hello @@ -0,0 +1 @@ +Hello, Gophers! diff --git a/libgo/go/os/timeout_test.go b/libgo/go/os/timeout_test.go index 99b94c2..0a39f46 100644 --- a/libgo/go/os/timeout_test.go +++ b/libgo/go/os/timeout_test.go @@ -11,7 +11,6 @@ package os_test import ( "fmt" "io" - "io/ioutil" "math/rand" "os" "os/signal" @@ -29,7 +28,7 @@ func TestNonpollableDeadline(t *testing.T) { t.Skipf("skipping on %s", runtime.GOOS) } - f, err := ioutil.TempFile("", "ostest") + f, err := os.CreateTemp("", "ostest") if err != nil { t.Fatal(err) } @@ -429,7 +428,7 @@ func testVariousDeadlines(t *testing.T) { if err := r.SetDeadline(t0.Add(timeout)); err != nil { t.Error(err) } - n, err := io.Copy(ioutil.Discard, r) + n, err := io.Copy(io.Discard, r) dt := time.Since(t0) r.Close() actvch <- result{n, err, dt} @@ -565,7 +564,7 @@ func TestRacyWrite(t *testing.T) { var wg sync.WaitGroup defer wg.Wait() - go io.Copy(ioutil.Discard, r) + go io.Copy(io.Discard, r) w.SetWriteDeadline(time.Now().Add(time.Millisecond)) for i := 0; i < 10; i++ { diff --git a/libgo/go/os/types.go b/libgo/go/os/types.go index 4b6c084..d8edd98 100644 --- a/libgo/go/os/types.go +++ b/libgo/go/os/types.go @@ -5,8 +5,8 @@ package os import ( + "io/fs" "syscall" - "time" ) // Getpagesize returns the underlying system's memory page size. @@ -18,21 +18,14 @@ type File struct { } // A FileInfo describes a file and is returned by Stat and Lstat. -type FileInfo interface { - Name() string // base name of the file - Size() int64 // length in bytes for regular files; system-dependent for others - Mode() FileMode // file mode bits - ModTime() time.Time // modification time - IsDir() bool // abbreviation for Mode().IsDir() - Sys() interface{} // underlying data source (can return nil) -} +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 uint32 +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. @@ -42,69 +35,26 @@ type FileMode uint32 const ( // The single letters are the abbreviations // used by the String method's formatting. - ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory - ModeAppend // a: append-only - ModeExclusive // l: exclusive use - ModeTemporary // T: temporary file; Plan 9 only - ModeSymlink // L: symbolic link - ModeDevice // D: device file - ModeNamedPipe // p: named pipe (FIFO) - ModeSocket // S: Unix domain socket - ModeSetuid // u: setuid - ModeSetgid // g: setgid - ModeCharDevice // c: Unix character device, when ModeDevice is set - ModeSticky // t: sticky - ModeIrregular // ?: non-regular file; nothing else is known about this file + 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 = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular + ModeType = fs.ModeType - ModePerm FileMode = 0777 // Unix permission bits + ModePerm = fs.ModePerm // Unix permission bits, 0o777 ) -func (m FileMode) String() string { - const str = "dalTLDpSugct?" - var buf [32]byte // Mode is uint32. - w := 0 - for i, c := range str { - if m&(1<<uint(32-1-i)) != 0 { - buf[w] = byte(c) - w++ - } - } - if w == 0 { - buf[w] = '-' - w++ - } - const rwx = "rwxrwxrwx" - for i, c := range rwx { - if m&(1<<uint(9-1-i)) != 0 { - buf[w] = byte(c) - } else { - buf[w] = '-' - } - w++ - } - return string(buf[:w]) -} - -// IsDir reports whether m describes a directory. -// That is, it tests for the ModeDir bit being set in m. -func (m FileMode) IsDir() bool { - return m&ModeDir != 0 -} - -// IsRegular reports whether m describes a regular file. -// That is, it tests that no mode type bits are set. -func (m FileMode) IsRegular() bool { - return m&ModeType == 0 -} - -// Perm returns the Unix permission bits in m. -func (m FileMode) Perm() FileMode { - return m & ModePerm -} - func (fs *fileStat) Name() string { return fs.name } func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() } diff --git a/libgo/go/os/types_windows.go b/libgo/go/os/types_windows.go index 3d1a667..59bf5ca 100644 --- a/libgo/go/os/types_windows.go +++ b/libgo/go/os/types_windows.go @@ -45,7 +45,7 @@ func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (f var d syscall.ByHandleFileInformation err = syscall.GetFileInformationByHandle(h, &d) if err != nil { - return nil, &PathError{"GetFileInformationByHandle", path, err} + return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err} } var ti windows.FILE_ATTRIBUTE_TAG_INFO @@ -58,7 +58,7 @@ func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (f // instance to indicate no symlinks are possible. ti.ReparseTag = 0 } else { - return nil, &PathError{"GetFileInformationByHandleEx", path, err} + return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err} } } @@ -197,7 +197,7 @@ func (fs *fileStat) saveInfoFromPath(path string) error { var err error fs.path, err = syscall.FullPath(fs.path) if err != nil { - return &PathError{"FullPath", path, err} + return &PathError{Op: "FullPath", Path: path, Err: err} } } fs.name = basename(path) diff --git a/libgo/go/os/user/lookup_plan9.go b/libgo/go/os/user/lookup_plan9.go index ea3ce0b..33ae3a6 100644 --- a/libgo/go/os/user/lookup_plan9.go +++ b/libgo/go/os/user/lookup_plan9.go @@ -6,7 +6,6 @@ package user import ( "fmt" - "io/ioutil" "os" "syscall" ) @@ -23,7 +22,7 @@ func init() { } func current() (*User, error) { - ubytes, err := ioutil.ReadFile(userFile) + ubytes, err := os.ReadFile(userFile) if err != nil { return nil, fmt.Errorf("user: %s", err) } |