diff options
author | Ian Lance Taylor <iant@golang.org> | 2018-01-09 01:23:08 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2018-01-09 01:23:08 +0000 |
commit | 1a2f01efa63036a5104f203a4789e682c0e0915d (patch) | |
tree | 373e15778dc8295354584e1f86915ae493b604ff /libgo/go/os | |
parent | 8799df67f2dab88f9fda11739c501780a85575e2 (diff) | |
download | gcc-1a2f01efa63036a5104f203a4789e682c0e0915d.zip gcc-1a2f01efa63036a5104f203a4789e682c0e0915d.tar.gz gcc-1a2f01efa63036a5104f203a4789e682c0e0915d.tar.bz2 |
libgo: update to Go1.10beta1
Update the Go library to the 1.10beta1 release.
Requires a few changes to the compiler for modifications to the map
runtime code, and to handle some nowritebarrier cases in the runtime.
Reviewed-on: https://go-review.googlesource.com/86455
gotools/:
* Makefile.am (go_cmd_vet_files): New variable.
(go_cmd_buildid_files, go_cmd_test2json_files): New variables.
(s-zdefaultcc): Change from constants to functions.
(noinst_PROGRAMS): Add vet, buildid, and test2json.
(cgo$(EXEEXT)): Link against $(LIBGOTOOL).
(vet$(EXEEXT)): New target.
(buildid$(EXEEXT)): New target.
(test2json$(EXEEXT)): New target.
(install-exec-local): Install all $(noinst_PROGRAMS).
(uninstall-local): Uninstasll all $(noinst_PROGRAMS).
(check-go-tool): Depend on $(noinst_PROGRAMS). Copy down
objabi.go.
(check-runtime): Depend on $(noinst_PROGRAMS).
(check-cgo-test, check-carchive-test): Likewise.
(check-vet): New target.
(check): Depend on check-vet. Look at cmd_vet-testlog.
(.PHONY): Add check-vet.
* Makefile.in: Rebuild.
From-SVN: r256365
Diffstat (limited to 'libgo/go/os')
-rw-r--r-- | libgo/go/os/env_test.go | 2 | ||||
-rw-r--r-- | libgo/go/os/error.go | 25 | ||||
-rw-r--r-- | libgo/go/os/exec/exec.go | 78 | ||||
-rw-r--r-- | libgo/go/os/exec_plan9.go | 7 | ||||
-rw-r--r-- | libgo/go/os/exec_posix.go | 7 | ||||
-rw-r--r-- | libgo/go/os/file.go | 49 | ||||
-rw-r--r-- | libgo/go/os/file_plan9.go | 26 | ||||
-rw-r--r-- | libgo/go/os/file_posix.go | 24 | ||||
-rw-r--r-- | libgo/go/os/file_unix.go | 40 | ||||
-rw-r--r-- | libgo/go/os/os_test.go | 66 | ||||
-rw-r--r-- | libgo/go/os/os_unix_test.go | 13 | ||||
-rw-r--r-- | libgo/go/os/path.go | 6 | ||||
-rw-r--r-- | libgo/go/os/path_test.go | 31 | ||||
-rw-r--r-- | libgo/go/os/pipe_bsd.go | 2 | ||||
-rw-r--r-- | libgo/go/os/pipe_freebsd.go | 18 | ||||
-rw-r--r-- | libgo/go/os/pipe_linux.go | 2 | ||||
-rw-r--r-- | libgo/go/os/signal/internal/pty/pty.go | 62 | ||||
-rw-r--r-- | libgo/go/os/signal/signal_cgo_test.go | 228 | ||||
-rw-r--r-- | libgo/go/os/signal/signal_test.go | 6 | ||||
-rw-r--r-- | libgo/go/os/sys_freebsd.go | 17 | ||||
-rw-r--r-- | libgo/go/os/timeout_test.go | 589 | ||||
-rw-r--r-- | libgo/go/os/user/cgo_lookup_unix.go | 15 | ||||
-rw-r--r-- | libgo/go/os/user/cgo_unix_test.go | 24 | ||||
-rw-r--r-- | libgo/go/os/user/listgroups_unix.go | 3 | ||||
-rw-r--r-- | libgo/go/os/wait_wait6.go | 5 |
25 files changed, 1189 insertions, 156 deletions
diff --git a/libgo/go/os/env_test.go b/libgo/go/os/env_test.go index e5749f0..16f1945 100644 --- a/libgo/go/os/env_test.go +++ b/libgo/go/os/env_test.go @@ -134,7 +134,7 @@ func TestLookupEnv(t *testing.T) { if err != nil { t.Fatalf("failed to release smallpox virus") } - value, ok = LookupEnv(smallpox) + _, ok = LookupEnv(smallpox) if !ok { t.Errorf("smallpox release failed; world remains safe but LookupEnv is broken") } diff --git a/libgo/go/os/error.go b/libgo/go/os/error.go index 7235bfb..b4242a4 100644 --- a/libgo/go/os/error.go +++ b/libgo/go/os/error.go @@ -6,6 +6,7 @@ package os import ( "errors" + "internal/poll" ) // Portable analogs of some common system call errors. @@ -15,8 +16,13 @@ var ( ErrExist = errors.New("file already exists") ErrNotExist = errors.New("file does not exist") ErrClosed = errors.New("file already closed") + ErrNoDeadline = poll.ErrNoDeadline ) +type timeout interface { + Timeout() bool +} + // PathError records an error and the operation and file path that caused it. type PathError struct { Op string @@ -26,6 +32,12 @@ type PathError struct { func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } +// Timeout reports whether this error represents a timeout. +func (e *PathError) Timeout() bool { + t, ok := e.Err.(timeout) + return ok && t.Timeout() +} + // SyscallError records an error from a specific system call. type SyscallError struct { Syscall string @@ -34,6 +46,12 @@ type SyscallError struct { func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() } +// Timeout reports whether this error represents a timeout. +func (e *SyscallError) Timeout() bool { + t, ok := e.Err.(timeout) + return ok && t.Timeout() +} + // NewSyscallError returns, as an error, a new SyscallError // with the given system call name and error details. // As a convenience, if err is nil, NewSyscallError returns nil. @@ -65,6 +83,13 @@ func IsPermission(err error) bool { return isPermission(err) } +// IsTimeout returns a boolean indicating whether the error is known +// to report that a timeout occurred. +func IsTimeout(err error) bool { + terr, ok := underlyingError(err).(timeout) + return ok && terr.Timeout() +} + // underlyingError returns the underlying error for known os error types. func underlyingError(err error) error { switch err := err.(type) { diff --git a/libgo/go/os/exec/exec.go b/libgo/go/os/exec/exec.go index 893d8ee..8a49fe3 100644 --- a/libgo/go/os/exec/exec.go +++ b/libgo/go/os/exec/exec.go @@ -77,9 +77,12 @@ type Cmd struct { Dir string // Stdin specifies the process's standard input. + // // If Stdin is nil, the process reads from the null device (os.DevNull). + // // If Stdin is an *os.File, the process's standard input is connected // directly to that file. + // // Otherwise, during the execution of the command a separate // goroutine reads from Stdin and delivers that data to the command // over a pipe. In this case, Wait does not complete until the goroutine @@ -92,8 +95,16 @@ type Cmd struct { // If either is nil, Run connects the corresponding file descriptor // to the null device (os.DevNull). // - // If Stdout and Stderr are the same writer, and have a type that can be compared with ==, - // at most one goroutine at a time will call Write. + // If either is an *os.File, the corresponding output from the process + // is connected directly to that file. + // + // Otherwise, during the execution of the command a separate goroutine + // reads from the process over a pipe and delivers that data to the + // corresponding Writer. In this case, Wait does not complete until the + // goroutine reaches EOF or encounters an error. + // + // If Stdout and Stderr are the same writer, and have a type that can + // be compared with ==, at most one goroutine at a time will call Write. Stdout io.Writer Stderr io.Writer @@ -190,7 +201,7 @@ func (c *Cmd) argv() []string { } // skipStdinCopyError optionally specifies a function which reports -// whether the provided the stdin copy error should be ignored. +// whether the provided stdin copy error should be ignored. // It is non-nil everywhere but Plan 9, which lacks EPIPE. See exec_posix.go. var skipStdinCopyError func(error) bool @@ -429,9 +440,8 @@ func (e *ExitError) Error() string { // error is of type *ExitError. Other error types may be // returned for I/O problems. // -// If c.Stdin is not an *os.File, Wait also waits for the I/O loop -// copying from c.Stdin into the process's standard input -// to complete. +// If any of c.Stdin, c.Stdout or c.Stderr are not an *os.File, Wait also waits +// for the respective I/O loop copying to or from the process to complete. // // Wait releases any resources associated with the Cmd. func (c *Cmd) Wait() error { @@ -527,16 +537,15 @@ func (c *Cmd) StdinPipe() (io.WriteCloser, error) { c.Stdin = pr c.closeAfterStart = append(c.closeAfterStart, pr) wc := &closeOnce{File: pw} - c.closeAfterWait = append(c.closeAfterWait, closerFunc(wc.safeClose)) + c.closeAfterWait = append(c.closeAfterWait, wc) return wc, nil } type closeOnce struct { *os.File - writers sync.RWMutex // coordinate safeClose and Write - once sync.Once - err error + once sync.Once + err error } func (c *closeOnce) Close() error { @@ -548,55 +557,6 @@ func (c *closeOnce) close() { c.err = c.File.Close() } -type closerFunc func() error - -func (f closerFunc) Close() error { return f() } - -// safeClose closes c being careful not to race with any calls to c.Write. -// See golang.org/issue/9307 and TestEchoFileRace in exec_test.go. -// In theory other calls could also be excluded (by writing appropriate -// wrappers like c.Write's implementation below), but since c is most -// commonly used as a WriteCloser, Write is the main one to worry about. -// See also #7970, for which this is a partial fix for this specific instance. -// The idea is that we return a WriteCloser, and so the caller can be -// relied upon not to call Write and Close simultaneously, but it's less -// obvious that cmd.Wait calls Close and that the caller must not call -// Write and cmd.Wait simultaneously. In fact that seems too onerous. -// So we change the use of Close in cmd.Wait to use safeClose, which will -// synchronize with any Write. -// -// It's important that we know this won't block forever waiting for the -// operations being excluded. At the point where this is called, -// the invoked command has exited and the parent copy of the read side -// of the pipe has also been closed, so there should really be no read side -// of the pipe left. Any active writes should return very shortly with an EPIPE, -// making it reasonable to wait for them. -// Technically it is possible that the child forked a sub-process or otherwise -// handed off the read side of the pipe before exiting and the current holder -// is not reading from the pipe, and the pipe is full, in which case the close here -// might block waiting for the write to complete. That's probably OK. -// It's a small enough problem to be outweighed by eliminating the race here. -func (c *closeOnce) safeClose() error { - c.writers.Lock() - err := c.Close() - c.writers.Unlock() - return err -} - -func (c *closeOnce) Write(b []byte) (int, error) { - c.writers.RLock() - n, err := c.File.Write(b) - c.writers.RUnlock() - return n, err -} - -func (c *closeOnce) WriteString(s string) (int, error) { - c.writers.RLock() - n, err := c.File.WriteString(s) - c.writers.RUnlock() - return n, err -} - // StdoutPipe returns a pipe that will be connected to the command's // standard output when the command starts. // diff --git a/libgo/go/os/exec_plan9.go b/libgo/go/os/exec_plan9.go index 676be36..6b4d28c 100644 --- a/libgo/go/os/exec_plan9.go +++ b/libgo/go/os/exec_plan9.go @@ -11,9 +11,10 @@ import ( "time" ) -// The only signal values guaranteed to be present on all systems -// are Interrupt (send the process an interrupt) and Kill (force -// the process to exit). +// The only signal values guaranteed to be present in the os package +// on all systems are Interrupt (send the process an interrupt) and +// Kill (force the process to exit). Interrupt is not implemented on +// Windows; using it with os.Process.Signal will return an error. var ( Interrupt Signal = syscall.Note("interrupt") Kill Signal = syscall.Note("kill") diff --git a/libgo/go/os/exec_posix.go b/libgo/go/os/exec_posix.go index 9e792b4..056f139 100644 --- a/libgo/go/os/exec_posix.go +++ b/libgo/go/os/exec_posix.go @@ -10,9 +10,10 @@ import ( "syscall" ) -// The only signal values guaranteed to be present on all systems -// are Interrupt (send the process an interrupt) and Kill (force -// the process to exit). +// The only signal values guaranteed to be present in the os package +// on all systems are Interrupt (send the process an interrupt) and +// Kill (force the process to exit). Interrupt is not implemented on +// Windows; using it with os.Process.Signal will return an error. var ( Interrupt Signal = syscall.SIGINT Kill Signal = syscall.SIGKILL diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go index 4b4d8fb..542b074 100644 --- a/libgo/go/os/file.go +++ b/libgo/go/os/file.go @@ -41,6 +41,7 @@ import ( "internal/poll" "io" "syscall" + "time" ) // Name returns the name of the file as presented to Open. @@ -61,12 +62,14 @@ var ( // Flags to OpenFile wrapping those of the underlying system. Not all // flags may be implemented on a given system. const ( + // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified. O_RDONLY int = syscall.O_RDONLY // open the file read-only. O_WRONLY int = syscall.O_WRONLY // open the file write-only. O_RDWR int = syscall.O_RDWR // open the file read-write. + // The remaining values may be or'ed in to control behavior. O_APPEND int = syscall.O_APPEND // append data to the file when writing. O_CREATE int = syscall.O_CREAT // create a new file if none exists. - O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist + O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist. O_SYNC int = syscall.O_SYNC // open for synchronous I/O. O_TRUNC int = syscall.O_TRUNC // if possible, truncate file when opened. ) @@ -316,3 +319,47 @@ func Chmod(name string, mode FileMode) error { return chmod(name, mode) } // Chmod changes the mode of the file to mode. // If there is an error, it will be of type *PathError. func (f *File) Chmod(mode FileMode) error { return f.chmod(mode) } + +// SetDeadline sets the read and write deadlines for a File. +// It is equivalent to calling both SetReadDeadline and SetWriteDeadline. +// +// Only some kinds of files support setting a deadline. Calls to SetDeadline +// for files that do not support deadlines will return ErrNoDeadline. +// On most systems ordinary files do not support deadlines, but pipes do. +// +// A deadline is an absolute time after which I/O operations fail with an +// error instead of blocking. The deadline applies to all future and pending +// I/O, not just the immediately following call to Read or Write. +// After a deadline has been exceeded, the connection can be refreshed +// by setting a deadline in the future. +// +// An error returned after a timeout fails will implement the +// Timeout method, and calling the Timeout method will return true. +// The PathError and SyscallError types implement the Timeout method. +// In general, call IsTimeout to test whether an error indicates a timeout. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful Read or Write calls. +// +// A zero value for t means I/O operations will not time out. +func (f *File) SetDeadline(t time.Time) error { + return f.setDeadline(t) +} + +// SetReadDeadline sets the deadline for future Read calls and any +// currently-blocked Read call. +// A zero value for t means Read will not time out. +// Not all files support setting deadlines; see SetDeadline. +func (f *File) SetReadDeadline(t time.Time) error { + return f.setReadDeadline(t) +} + +// SetWriteDeadline sets the deadline for any future Write calls and any +// currently-blocked Write call. +// Even if Write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means Write will not time out. +// Not all files support setting deadlines; see SetDeadline. +func (f *File) SetWriteDeadline(t time.Time) error { + return f.setWriteDeadline(t) +} diff --git a/libgo/go/os/file_plan9.go b/libgo/go/os/file_plan9.go index 0f4a736..e4f8fd9 100644 --- a/libgo/go/os/file_plan9.go +++ b/libgo/go/os/file_plan9.go @@ -5,6 +5,7 @@ package os import ( + "internal/poll" "io" "runtime" "syscall" @@ -28,6 +29,7 @@ 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. func (f *File) Fd() uintptr { if f == nil { return ^(uintptr(0)) @@ -491,6 +493,30 @@ func (f *File) Chdir() error { return nil } +// setDeadline sets the read and write deadline. +func (f *File) setDeadline(time.Time) error { + if err := f.checkValid("SetDeadline"); err != nil { + return err + } + return poll.ErrNoDeadline +} + +// setReadDeadline sets the read deadline. +func (f *File) setReadDeadline(time.Time) error { + if err := f.checkValid("SetReadDeadline"); err != nil { + return err + } + return poll.ErrNoDeadline +} + +// setWriteDeadline sets the write deadline. +func (f *File) setWriteDeadline(time.Time) error { + if err := f.checkValid("SetWriteDeadline"); err != nil { + return err + } + return poll.ErrNoDeadline +} + // checkValid checks whether f is valid for use. // If not, it returns an appropriate error, perhaps incorporating the operation name op. func (f *File) checkValid(op string) error { diff --git a/libgo/go/os/file_posix.go b/libgo/go/os/file_posix.go index 51cae9d..67da384 100644 --- a/libgo/go/os/file_posix.go +++ b/libgo/go/os/file_posix.go @@ -163,6 +163,30 @@ func (f *File) Chdir() error { return nil } +// setDeadline sets the read and write deadline. +func (f *File) setDeadline(t time.Time) error { + if err := f.checkValid("SetDeadline"); err != nil { + return err + } + return f.pfd.SetDeadline(t) +} + +// setReadDeadline sets the read deadline. +func (f *File) setReadDeadline(t time.Time) error { + if err := f.checkValid("SetReadDeadline"); err != nil { + return err + } + return f.pfd.SetReadDeadline(t) +} + +// setWriteDeadline sets the write deadline. +func (f *File) setWriteDeadline(t time.Time) error { + if err := f.checkValid("SetWriteDeadline"); err != nil { + return err + } + return f.pfd.SetWriteDeadline(t) +} + // checkValid checks whether f is valid for use. // If not, it returns an appropriate error, perhaps incorporating the operation name op. func (f *File) checkValid(op string) error { diff --git a/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go index 8199994..af9c37f9 100644 --- a/libgo/go/os/file_unix.go +++ b/libgo/go/os/file_unix.go @@ -45,14 +45,16 @@ func rename(oldname, newname string) error { // can overwrite this data, which could cause the finalizer // to close the wrong file descriptor. type file struct { - pfd poll.FD - name string - dirinfo *dirInfo // nil unless directory being read - nonblock bool // whether we set nonblocking mode + pfd poll.FD + name string + dirinfo *dirInfo // nil unless directory being read + nonblock bool // whether we set nonblocking mode + stdoutOrErr bool // whether this is stdout or stderr } // 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. func (f *File) Fd() uintptr { if f == nil { return ^(uintptr(0)) @@ -74,12 +76,22 @@ func (f *File) Fd() uintptr { // name. The returned value will be nil if fd is not a valid file // descriptor. func NewFile(fd uintptr, name string) *File { - return newFile(fd, name, false) + return newFile(fd, name, kindNewFile) } -// newFile is like NewFile, but if pollable is true it tries to add the -// file to the runtime poller. -func newFile(fd uintptr, name string, pollable bool) *File { +// newFileKind describes the kind of file to newFile. +type newFileKind int + +const ( + kindNewFile newFileKind = iota + kindOpenFile + kindPipe +) + +// newFile is like NewFile, but if called from OpenFile or Pipe +// (as passed in the kind parameter) it tries to add the file to +// the runtime poller. +func newFile(fd uintptr, name string, kind newFileKind) *File { fdi := int(fd) if fdi < 0 { return nil @@ -90,16 +102,18 @@ func newFile(fd uintptr, name string, pollable bool) *File { IsStream: true, ZeroReadIsEOF: true, }, - name: name, + name: name, + stdoutOrErr: fdi == 1 || fdi == 2, }} // Don't try to use kqueue with regular files on FreeBSD. // It crashes the system unpredictably while running all.bash. // Issue 19093. - if runtime.GOOS == "freebsd" { - pollable = false + if runtime.GOOS == "freebsd" && kind == kindOpenFile { + kind = kindNewFile } + pollable := kind == kindOpenFile || kind == kindPipe if err := f.pfd.Init("file", pollable); err != nil { // An error here indicates a failure to register // with the netpoll system. That can happen for @@ -129,7 +143,7 @@ type dirInfo struct { // output or standard error. See the SIGPIPE docs in os/signal, and // issue 11845. func epipecheck(file *File, e error) { - if e == syscall.EPIPE && (file.pfd.Sysfd == 1 || file.pfd.Sysfd == 2) { + if e == syscall.EPIPE && file.stdoutOrErr { sigpipe() } } @@ -180,7 +194,7 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) { syscall.CloseOnExec(r) } - return newFile(uintptr(r), name, true), nil + return newFile(uintptr(r), name, kindOpenFile), nil } // Close closes the File, rendering it unusable for I/O. diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go index 9033c4f..e1c5b45 100644 --- a/libgo/go/os/os_test.go +++ b/libgo/go/os/os_test.go @@ -719,6 +719,27 @@ func TestHardLink(t *testing.T) { if !SameFile(tostat, fromstat) { t.Errorf("link %q, %q did not create hard link", to, from) } + // We should not be able to perform the same Link() a second time + err = Link(to, from) + switch err := err.(type) { + case *LinkError: + if err.Op != "link" { + t.Errorf("Link(%q, %q) err.Op = %q; want %q", to, from, err.Op, "link") + } + if err.Old != to { + t.Errorf("Link(%q, %q) err.Old = %q; want %q", to, from, err.Old, to) + } + if err.New != from { + t.Errorf("Link(%q, %q) err.New = %q; want %q", to, from, err.New, from) + } + if !IsExist(err.Err) { + t.Errorf("Link(%q, %q) err.Err = %q; want %q", to, from, err.Err, "file exists error") + } + case nil: + t.Errorf("link %q, %q: expected error, got nil", from, to) + default: + t.Errorf("link %q, %q: expected %T, got %T %v", from, to, new(LinkError), err, err) + } } // chtmpdir changes the working directory to a new temporary directory and @@ -1012,9 +1033,14 @@ func TestStartProcess(t *testing.T) { dir = Getenv("SystemRoot") args = []string{"/c", "cd"} default: - cmd = "/bin/pwd" + var err error + cmd, err = osexec.LookPath("pwd") + if err != nil { + t.Fatalf("Can't find pwd: %v", err) + } dir = "/" args = []string{} + t.Logf("Testing with %v", cmd) } cmddir, cmdbase := filepath.Split(cmd) args = append([]string{cmdbase}, args...) @@ -1162,9 +1188,14 @@ func testChtimes(t *testing.T, name string) { // the contents are accessed; also, it is set // whenever mtime is set. case "netbsd": - t.Logf("AccessTime didn't go backwards; was=%d, after=%d (Ignoring. See NetBSD issue golang.org/issue/19293)", at, pat) + mounts, _ := ioutil.ReadFile("/proc/mounts") + if strings.Contains(string(mounts), "noatime") { + t.Logf("AccessTime didn't go backwards, but see a filesystem mounted noatime; ignoring. Issue 19293.") + } else { + t.Logf("AccessTime didn't go backwards; was=%v, after=%v (Ignoring on NetBSD, assuming noatime, Issue 19293)", at, pat) + } default: - t.Errorf("AccessTime didn't go backwards; was=%d, after=%d", at, pat) + t.Errorf("AccessTime didn't go backwards; was=%v, after=%v", at, pat) } } @@ -1211,9 +1242,9 @@ func TestChdirAndGetwd(t *testing.T) { if mode == 0 { err = Chdir(d) } else { - fd1, err := Open(d) - if err != nil { - t.Errorf("Open %s: %s", d, err) + fd1, err1 := Open(d) + if err1 != nil { + t.Errorf("Open %s: %s", d, err1) continue } err = fd1.Chdir() @@ -2204,22 +2235,24 @@ func TestPipeThreads(t *testing.T) { defer debug.SetMaxThreads(debug.SetMaxThreads(threads / 2)) - var wg sync.WaitGroup - wg.Add(threads) - c := make(chan bool, threads) + creading := make(chan bool, threads) + cdone := make(chan bool, threads) for i := 0; i < threads; i++ { go func(i int) { - defer wg.Done() var b [1]byte - c <- true + creading <- true if _, err := r[i].Read(b[:]); err != nil { t.Error(err) } + if err := r[i].Close(); err != nil { + t.Error(err) + } + cdone <- true }(i) } for i := 0; i < threads; i++ { - <-c + <-creading } // If we are still alive, it means that the 100 goroutines did @@ -2232,14 +2265,7 @@ func TestPipeThreads(t *testing.T) { if err := w[i].Close(); err != nil { t.Error(err) } - } - - wg.Wait() - - for i := 0; i < threads; i++ { - if err := r[i].Close(); err != nil { - t.Error(err) - } + <-cdone } } diff --git a/libgo/go/os/os_unix_test.go b/libgo/go/os/os_unix_test.go index e239835..56c885c 100644 --- a/libgo/go/os/os_unix_test.go +++ b/libgo/go/os/os_unix_test.go @@ -36,11 +36,6 @@ func checkUidGid(t *testing.T, path string, uid, gid int) { } func TestChown(t *testing.T) { - // Chown is not supported under windows or Plan 9. - // Plan9 provides a native ChownPlan9 version instead. - if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { - t.Skipf("%s does not support syscall.Chown", runtime.GOOS) - } // Use TempDir() to make sure we're on a local file system, // so that the group ids returned by Getgroups will be allowed // on the file. On NFS, the Getgroups groups are @@ -84,10 +79,6 @@ func TestChown(t *testing.T) { } func TestFileChown(t *testing.T) { - // Fchown is not supported under windows or Plan 9. - if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { - t.Skipf("%s does not support syscall.Fchown", runtime.GOOS) - } // Use TempDir() to make sure we're on a local file system, // so that the group ids returned by Getgroups will be allowed // on the file. On NFS, the Getgroups groups are @@ -131,10 +122,6 @@ func TestFileChown(t *testing.T) { } func TestLchown(t *testing.T) { - // Lchown is not supported under windows or Plan 9. - if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { - t.Skipf("%s does not support syscall.Lchown", runtime.GOOS) - } // Use TempDir() to make sure we're on a local file system, // so that the group ids returned by Getgroups will be allowed // on the file. On NFS, the Getgroups groups are diff --git a/libgo/go/os/path.go b/libgo/go/os/path.go index 146d7b6..17c49c8 100644 --- a/libgo/go/os/path.go +++ b/libgo/go/os/path.go @@ -6,6 +6,7 @@ package os import ( "io" + "runtime" "syscall" ) @@ -97,6 +98,11 @@ func RemoveAll(path string) error { // Remove contents & return first error. err = nil for { + if err == nil && (runtime.GOOS == "plan9" || runtime.GOOS == "nacl") { + // Reset read offset after removing directory entries. + // See golang.org/issue/22572. + fd.Seek(0, 0) + } names, err1 := fd.Readdirnames(100) for _, name := range names { err1 := RemoveAll(path + string(PathSeparator) + name) diff --git a/libgo/go/os/path_test.go b/libgo/go/os/path_test.go index 6f5bfa5..f58c7e7 100644 --- a/libgo/go/os/path_test.go +++ b/libgo/go/os/path_test.go @@ -5,6 +5,7 @@ package os_test import ( + "fmt" "internal/testenv" "io/ioutil" . "os" @@ -169,6 +170,36 @@ func TestRemoveAll(t *testing.T) { } } +// Test RemoveAll on a large directory. +func TestRemoveAllLarge(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + tmpDir := TempDir() + // Work directory. + path := tmpDir + "/_TestRemoveAllLarge_" + + // Make directory with 1000 files and remove. + if err := MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + for i := 0; i < 1000; i++ { + fpath := fmt.Sprintf("%s/file%d", path, i) + fd, err := Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + } + if err := RemoveAll(path); err != nil { + t.Fatalf("RemoveAll %q: %s", path, err) + } + if _, err := Lstat(path); err == nil { + t.Fatalf("Lstat %q succeeded after RemoveAll", path) + } +} + func TestMkdirAllWithSymlink(t *testing.T) { testenv.MustHaveSymlink(t) diff --git a/libgo/go/os/pipe_bsd.go b/libgo/go/os/pipe_bsd.go index ae153fa..8398a24 100644 --- a/libgo/go/os/pipe_bsd.go +++ b/libgo/go/os/pipe_bsd.go @@ -24,5 +24,5 @@ func Pipe() (r *File, w *File, err error) { syscall.CloseOnExec(p[1]) syscall.ForkLock.RUnlock() - return newFile(uintptr(p[0]), "|0", true), newFile(uintptr(p[1]), "|1", true), nil + return newFile(uintptr(p[0]), "|0", kindPipe), newFile(uintptr(p[1]), "|1", kindPipe), nil } diff --git a/libgo/go/os/pipe_freebsd.go b/libgo/go/os/pipe_freebsd.go index ea6622c..93bd869 100644 --- a/libgo/go/os/pipe_freebsd.go +++ b/libgo/go/os/pipe_freebsd.go @@ -13,22 +13,8 @@ func Pipe() (r *File, w *File, err error) { e := syscall.Pipe2(p[0:], syscall.O_CLOEXEC) if e != nil { - // Fallback support for FreeBSD 9, which lacks Pipe2. - // - // TODO: remove this for Go 1.10 when FreeBSD 9 - // is removed (Issue 19072). - - // See ../syscall/exec.go for description of lock. - syscall.ForkLock.RLock() - e := syscall.Pipe(p[0:]) - if e != nil { - syscall.ForkLock.RUnlock() - return nil, nil, NewSyscallError("pipe", e) - } - syscall.CloseOnExec(p[0]) - syscall.CloseOnExec(p[1]) - syscall.ForkLock.RUnlock() + return nil, nil, NewSyscallError("pipe", e) } - return newFile(uintptr(p[0]), "|0", true), newFile(uintptr(p[1]), "|1", true), nil + return newFile(uintptr(p[0]), "|0", kindPipe), newFile(uintptr(p[1]), "|1", kindPipe), nil } diff --git a/libgo/go/os/pipe_linux.go b/libgo/go/os/pipe_linux.go index 96f2ce9..acd7b88 100644 --- a/libgo/go/os/pipe_linux.go +++ b/libgo/go/os/pipe_linux.go @@ -29,5 +29,5 @@ func Pipe() (r *File, w *File, err error) { return nil, nil, NewSyscallError("pipe2", e) } - return newFile(uintptr(p[0]), "|0", true), newFile(uintptr(p[1]), "|1", true), nil + return newFile(uintptr(p[0]), "|0", kindPipe), newFile(uintptr(p[1]), "|1", kindPipe), nil } diff --git a/libgo/go/os/signal/internal/pty/pty.go b/libgo/go/os/signal/internal/pty/pty.go new file mode 100644 index 0000000..4728de7 --- /dev/null +++ b/libgo/go/os/signal/internal/pty/pty.go @@ -0,0 +1,62 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!android netbsd openbsd + +// Package pty is a simple pseudo-terminal package for Unix systems, +// implemented by calling C functions via cgo. +// This is only used for testing the os/signal package. +package pty + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +//extern posix_openpt +func posix_openpt(int32) int32 + +//extern grantpt +func grantpt(int32) int32 + +//extern unlockpt +func unlockpt(int32) int32 + +//extern ptsname +func ptsname(int32) *byte + +//extern close +func close(int32) int32 + +const _O_RDWR = 2 + +// Open returns a master pty and the name of the linked slave tty. +func Open() (master *os.File, slave string, err error) { + m := posix_openpt(_O_RDWR) + if m < 0 { + return nil, "", fmt.Errorf("posix_openpt: %v", syscall.GetErrno()) + } + if grantpt(m) < 0 { + errno := syscall.GetErrno() + close(m) + return nil, "", fmt.Errorf("grantpt: %v", errno) + } + if unlockpt(m) < 0 { + errno := syscall.GetErrno() + close(m) + return nil, "", fmt.Errorf("unlockpt: %v", errno) + } + p := ptsname(m) + s := (*[32000]byte)(unsafe.Pointer(p))[:] + for i, v := range s { + if v == 0 { + s = s[:i:i] + break + } + } + slave = string(s) + return os.NewFile(uintptr(m), "pty-master"), slave, nil +} diff --git a/libgo/go/os/signal/signal_cgo_test.go b/libgo/go/os/signal/signal_cgo_test.go new file mode 100644 index 0000000..27707fa --- /dev/null +++ b/libgo/go/os/signal/signal_cgo_test.go @@ -0,0 +1,228 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!android netbsd openbsd +// +build cgo + +// Note that this test does not work on Solaris: issue #22849. +// Don't run the test on Android because at least some versions of the +// C library do not define the posix_openpt function. + +package signal_test + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "os/signal/internal/pty" + "strconv" + "strings" + "syscall" + "testing" + "time" +) + +func TestTerminalSignal(t *testing.T) { + const enteringRead = "test program entering read" + if os.Getenv("GO_TEST_TERMINAL_SIGNALS") != "" { + var b [1]byte + fmt.Println(enteringRead) + n, err := os.Stdin.Read(b[:]) + if n == 1 { + if b[0] == '\n' { + // This is what we expect + fmt.Println("read newline") + } else { + fmt.Printf("read 1 byte: %q\n", b) + } + } else { + fmt.Printf("read %d bytes\n", n) + } + if err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(0) + } + + t.Parallel() + + // The test requires a shell that uses job control. + bash, err := exec.LookPath("bash") + if err != nil { + t.Skipf("could not find bash: %v", err) + } + + scale := 1 + if s := os.Getenv("GO_TEST_TIMEOUT_SCALE"); s != "" { + if sc, err := strconv.Atoi(s); err == nil { + scale = sc + } + } + pause := time.Duration(scale) * 10 * time.Millisecond + wait := time.Duration(scale) * 5 * time.Second + + // The test only fails when using a "slow device," in this + // case a pseudo-terminal. + + master, sname, err := pty.Open() + if err != nil { + t.Fatal(err) + } + defer master.Close() + slave, err := os.OpenFile(sname, os.O_RDWR, 0) + if err != nil { + t.Fatal(err) + } + defer slave.Close() + + // Start an interactive shell. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, bash, "--norc", "--noprofile", "-i") + cmd.Stdin = slave + cmd.Stdout = slave + cmd.Stderr = slave + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setsid: true, + Setctty: true, + Ctty: int(slave.Fd()), + } + + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + if err := slave.Close(); err != nil { + t.Errorf("closing slave: %v", err) + } + + progReady := make(chan bool) + sawPrompt := make(chan bool, 10) + const prompt = "prompt> " + + // Read data from master in the background. + go func() { + input := bufio.NewReader(master) + var line, handled []byte + for { + b, err := input.ReadByte() + if err != nil { + if len(line) > 0 || len(handled) > 0 { + t.Logf("%q", append(handled, line...)) + } + if perr, ok := err.(*os.PathError); ok { + err = perr.Err + } + // EOF means master is closed. + // EIO means child process is done. + // "file already closed" means deferred close of master has happened. + if err != io.EOF && err != syscall.EIO && !strings.Contains(err.Error(), "file already closed") { + t.Logf("error reading from master: %v", err) + } + return + } + + line = append(line, b) + + if b == '\n' { + t.Logf("%q", append(handled, line...)) + line = nil + handled = nil + continue + } + + if bytes.Contains(line, []byte(enteringRead)) { + close(progReady) + handled = append(handled, line...) + line = nil + } else if bytes.Contains(line, []byte(prompt)) && !bytes.Contains(line, []byte("PS1=")) { + sawPrompt <- true + handled = append(handled, line...) + line = nil + } + } + }() + + // Set the bash prompt so that we can see it. + if _, err := master.Write([]byte("PS1='" + prompt + "'\n")); err != nil { + t.Fatalf("setting prompt: %v", err) + } + select { + case <-sawPrompt: + case <-time.After(wait): + t.Fatal("timed out waiting for shell prompt") + } + + // Start a small program that reads from stdin + // (namely the code at the top of this function). + if _, err := master.Write([]byte("GO_TEST_TERMINAL_SIGNALS=1 " + os.Args[0] + " -test.run=TestTerminalSignal\n")); err != nil { + t.Fatal(err) + } + + // Wait for the program to print that it is starting. + select { + case <-progReady: + case <-time.After(wait): + t.Fatal("timed out waiting for program to start") + } + + // Give the program time to enter the read call. + // It doesn't matter much if we occasionally don't wait long enough; + // we won't be testing what we want to test, but the overall test + // will pass. + time.Sleep(pause) + + // Send a ^Z to stop the program. + if _, err := master.Write([]byte{26}); err != nil { + t.Fatalf("writing ^Z to pty: %v", err) + } + + // Wait for the program to stop and return to the shell. + select { + case <-sawPrompt: + case <-time.After(wait): + t.Fatal("timed out waiting for shell prompt") + } + + // Restart the stopped program. + if _, err := master.Write([]byte("fg\n")); err != nil { + t.Fatalf("writing %q to pty: %v", "fg", err) + } + + // Give the process time to restart. + // This is potentially racy: if the process does not restart + // quickly enough then the byte we send will go to bash rather + // than the program. Unfortunately there isn't anything we can + // look for to know that the program is running again. + // bash will print the program name, but that happens before it + // restarts the program. + time.Sleep(10 * pause) + + // Write some data for the program to read, + // which should cause it to exit. + if _, err := master.Write([]byte{'\n'}); err != nil { + t.Fatalf("writing %q to pty: %v", "\n", err) + } + + // Wait for the program to exit. + select { + case <-sawPrompt: + case <-time.After(wait): + t.Fatal("timed out waiting for shell prompt") + } + + // Exit the shell with the program's exit status. + if _, err := master.Write([]byte("exit $?\n")); err != nil { + t.Fatalf("writing %q to pty: %v", "exit", err) + } + + if err = cmd.Wait(); err != nil { + t.Errorf("subprogram failed: %v", err) + } +} diff --git a/libgo/go/os/signal/signal_test.go b/libgo/go/os/signal/signal_test.go index 10a4146..d27ff0b 100644 --- a/libgo/go/os/signal/signal_test.go +++ b/libgo/go/os/signal/signal_test.go @@ -321,7 +321,9 @@ func TestAtomicStop(t *testing.T) { cmd.Env = append(os.Environ(), "GO_TEST_ATOMIC_STOP=1") out, err := cmd.CombinedOutput() if err == nil { - t.Logf("iteration %d: output %s", i, out) + if len(out) > 0 { + t.Logf("iteration %d: output %s", i, out) + } } else { t.Logf("iteration %d: exit status %q: output: %s", i, err, out) } @@ -378,7 +380,7 @@ func atomicStopTestProgram() { case <-cs: case <-time.After(1 * time.Second): if !printed { - fmt.Print("lost signal on iterations:") + fmt.Print("lost signal on tries:") printed = true } fmt.Printf(" %d", i) diff --git a/libgo/go/os/sys_freebsd.go b/libgo/go/os/sys_freebsd.go index 273c2df..3ec49fa 100644 --- a/libgo/go/os/sys_freebsd.go +++ b/libgo/go/os/sys_freebsd.go @@ -4,20 +4,7 @@ package os -import "syscall" - // supportsCloseOnExec reports whether the platform supports the // O_CLOEXEC flag. -var supportsCloseOnExec bool - -func init() { - osrel, err := syscall.SysctlUint32("kern.osreldate") - if err != nil { - return - } - // The O_CLOEXEC flag was introduced in FreeBSD 8.3. - // See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html. - if osrel >= 803000 { - supportsCloseOnExec = true - } -} +// The O_CLOEXEC flag was introduced in FreeBSD 8.3. +const supportsCloseOnExec bool = true diff --git a/libgo/go/os/timeout_test.go b/libgo/go/os/timeout_test.go new file mode 100644 index 0000000..6f47ed0 --- /dev/null +++ b/libgo/go/os/timeout_test.go @@ -0,0 +1,589 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !nacl +// +build !plan9 +// +build !windows + +package os_test + +import ( + "fmt" + "internal/poll" + "io" + "io/ioutil" + "math/rand" + "os" + "runtime" + "sync" + "testing" + "time" +) + +func TestNonpollableDeadline(t *testing.T) { + // On BSD systems regular files seem to be pollable, + // so just run this test on Linux. + if runtime.GOOS != "linux" { + t.Skipf("skipping on %s", runtime.GOOS) + } + + f, err := ioutil.TempFile("", "ostest") + if err != nil { + t.Fatal(err) + } + defer os.Remove(f.Name()) + defer f.Close() + deadline := time.Now().Add(10 * time.Second) + if err := f.SetDeadline(deadline); err != os.ErrNoDeadline { + t.Errorf("SetDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline) + } + if err := f.SetReadDeadline(deadline); err != os.ErrNoDeadline { + t.Errorf("SetReadDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline) + } + if err := f.SetWriteDeadline(deadline); err != os.ErrNoDeadline { + t.Errorf("SetWriteDeadline on file returned %v, wanted %v", err, os.ErrNoDeadline) + } +} + +// noDeadline is a zero time.Time value, which cancels a deadline. +var noDeadline time.Time + +var readTimeoutTests = []struct { + timeout time.Duration + xerrs [2]error // expected errors in transition +}{ + // Tests that read deadlines work, even if there's data ready + // to be read. + {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + + {50 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, +} + +func TestReadTimeout(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + if _, err := w.Write([]byte("READ TIMEOUT TEST")); err != nil { + t.Fatal(err) + } + + for i, tt := range readTimeoutTests { + if err := r.SetReadDeadline(time.Now().Add(tt.timeout)); err != nil { + t.Fatalf("#%d: %v", i, err) + } + var b [1]byte + for j, xerr := range tt.xerrs { + for { + n, err := r.Read(b[:]) + if xerr != nil { + if !os.IsTimeout(err) { + t.Fatalf("#%d/%d: %v", i, j, err) + } + } + if err == nil { + time.Sleep(tt.timeout / 3) + continue + } + if n != 0 { + t.Fatalf("#%d/%d: read %d; want 0", i, j, n) + } + break + } + } + } +} + +func TestReadTimeoutMustNotReturn(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + max := time.NewTimer(100 * time.Millisecond) + defer max.Stop() + ch := make(chan error) + go func() { + if err := r.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) + } + if err := r.SetWriteDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) + } + if err := r.SetReadDeadline(noDeadline); err != nil { + t.Error(err) + } + var b [1]byte + _, err := r.Read(b[:]) + ch <- err + }() + + select { + case err := <-ch: + t.Fatalf("expected Read to not return, but it returned with %v", err) + case <-max.C: + w.Close() + err := <-ch // wait for tester goroutine to stop + if os.IsTimeout(err) { + t.Fatal(err) + } + } +} + +var writeTimeoutTests = []struct { + timeout time.Duration + xerrs [2]error // expected errors in transition +}{ + // Tests that write deadlines work, even if there's buffer + // space available to write. + {-5 * time.Second, [2]error{poll.ErrTimeout, poll.ErrTimeout}}, + + {10 * time.Millisecond, [2]error{nil, poll.ErrTimeout}}, +} + +func TestWriteTimeout(t *testing.T) { + t.Parallel() + + for i, tt := range writeTimeoutTests { + t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + if err := w.SetWriteDeadline(time.Now().Add(tt.timeout)); err != nil { + t.Fatalf("%v", err) + } + for j, xerr := range tt.xerrs { + for { + n, err := w.Write([]byte("WRITE TIMEOUT TEST")) + if xerr != nil { + if !os.IsTimeout(err) { + t.Fatalf("%d: %v", j, err) + } + } + if err == nil { + time.Sleep(tt.timeout / 3) + continue + } + if n != 0 { + t.Fatalf("%d: wrote %d; want 0", j, n) + } + break + } + } + }) + } +} + +func TestWriteTimeoutMustNotReturn(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + max := time.NewTimer(100 * time.Millisecond) + defer max.Stop() + ch := make(chan error) + go func() { + if err := w.SetDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) + } + if err := w.SetReadDeadline(time.Now().Add(-5 * time.Second)); err != nil { + t.Error(err) + } + if err := w.SetWriteDeadline(noDeadline); err != nil { + t.Error(err) + } + var b [1]byte + for { + if _, err := w.Write(b[:]); err != nil { + ch <- err + break + } + } + }() + + select { + case err := <-ch: + t.Fatalf("expected Write to not return, but it returned with %v", err) + case <-max.C: + r.Close() + err := <-ch // wait for tester goroutine to stop + if os.IsTimeout(err) { + t.Fatal(err) + } + } +} + +func timeoutReader(r *os.File, d, min, max time.Duration, ch chan<- error) { + var err error + defer func() { ch <- err }() + + t0 := time.Now() + if err = r.SetReadDeadline(time.Now().Add(d)); err != nil { + return + } + b := make([]byte, 256) + var n int + n, err = r.Read(b) + t1 := time.Now() + if n != 0 || err == nil || !os.IsTimeout(err) { + err = fmt.Errorf("Read did not return (0, timeout): (%d, %v)", n, err) + return + } + if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() { + err = fmt.Errorf("Read took %s; expected %s", dt, d) + return + } +} + +func TestReadTimeoutFluctuation(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + max := time.NewTimer(time.Second) + defer max.Stop() + ch := make(chan error) + go timeoutReader(r, 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch) + + select { + case <-max.C: + t.Fatal("Read took over 1s; expected 0.1s") + case err := <-ch: + if !os.IsTimeout(err) { + t.Fatal(err) + } + } +} + +func timeoutWriter(w *os.File, d, min, max time.Duration, ch chan<- error) { + var err error + defer func() { ch <- err }() + + t0 := time.Now() + if err = w.SetWriteDeadline(time.Now().Add(d)); err != nil { + return + } + var n int + for { + n, err = w.Write([]byte("TIMEOUT WRITER")) + if err != nil { + break + } + } + t1 := time.Now() + if err == nil || !os.IsTimeout(err) { + err = fmt.Errorf("Write did not return (any, timeout): (%d, %v)", n, err) + return + } + if dt := t1.Sub(t0); min > dt || dt > max && !testing.Short() { + err = fmt.Errorf("Write took %s; expected %s", dt, d) + return + } +} + +func TestWriteTimeoutFluctuation(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + d := time.Second + max := time.NewTimer(d) + defer max.Stop() + ch := make(chan error) + go timeoutWriter(w, 100*time.Millisecond, 50*time.Millisecond, 250*time.Millisecond, ch) + + select { + case <-max.C: + t.Fatalf("Write took over %v; expected 0.1s", d) + case err := <-ch: + if !os.IsTimeout(err) { + t.Fatal(err) + } + } +} + +func TestVariousDeadlines(t *testing.T) { + t.Parallel() + testVariousDeadlines(t) +} + +func TestVariousDeadlines1Proc(t *testing.T) { + // Cannot use t.Parallel - modifies global GOMAXPROCS. + if testing.Short() { + t.Skip("skipping in short mode") + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) + testVariousDeadlines(t) +} + +func TestVariousDeadlines4Proc(t *testing.T) { + // Cannot use t.Parallel - modifies global GOMAXPROCS. + if testing.Short() { + t.Skip("skipping in short mode") + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) + testVariousDeadlines(t) +} + +type neverEnding byte + +func (b neverEnding) Read(p []byte) (int, error) { + for i := range p { + p[i] = byte(b) + } + return len(p), nil +} + +func testVariousDeadlines(t *testing.T) { + type result struct { + n int64 + err error + d time.Duration + } + + handler := func(w *os.File, pasvch chan result) { + // The writer, with no timeouts of its own, + // sending bytes to clients as fast as it can. + t0 := time.Now() + n, err := io.Copy(w, neverEnding('a')) + dt := time.Since(t0) + pasvch <- result{n, err, dt} + } + + for _, timeout := range []time.Duration{ + 1 * time.Nanosecond, + 2 * time.Nanosecond, + 5 * time.Nanosecond, + 50 * time.Nanosecond, + 100 * time.Nanosecond, + 200 * time.Nanosecond, + 500 * time.Nanosecond, + 750 * time.Nanosecond, + 1 * time.Microsecond, + 5 * time.Microsecond, + 25 * time.Microsecond, + 250 * time.Microsecond, + 500 * time.Microsecond, + 1 * time.Millisecond, + 5 * time.Millisecond, + 100 * time.Millisecond, + 250 * time.Millisecond, + 500 * time.Millisecond, + 1 * time.Second, + } { + numRuns := 3 + if testing.Short() { + numRuns = 1 + if timeout > 500*time.Microsecond { + continue + } + } + for run := 0; run < numRuns; run++ { + t.Run(fmt.Sprintf("%v-%d", timeout, run+1), func(t *testing.T) { + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + pasvch := make(chan result) + go handler(w, pasvch) + + tooLong := 5 * time.Second + max := time.NewTimer(tooLong) + defer max.Stop() + actvch := make(chan result) + go func() { + t0 := time.Now() + if err := r.SetDeadline(t0.Add(timeout)); err != nil { + t.Error(err) + } + n, err := io.Copy(ioutil.Discard, r) + dt := time.Since(t0) + r.Close() + actvch <- result{n, err, dt} + }() + + select { + case res := <-actvch: + if os.IsTimeout(res.err) { + t.Logf("good client timeout after %v, reading %d bytes", res.d, res.n) + } else { + t.Fatalf("client Copy = %d, %v; want timeout", res.n, res.err) + } + case <-max.C: + t.Fatalf("timeout (%v) waiting for client to timeout (%v) reading", tooLong, timeout) + } + + select { + case res := <-pasvch: + t.Logf("writer in %v wrote %d: %v", res.d, res.n, res.err) + case <-max.C: + t.Fatalf("timeout waiting for writer to finish writing") + } + }) + } + } +} + +func TestReadWriteDeadlineRace(t *testing.T) { + t.Parallel() + + N := 1000 + if testing.Short() { + N = 50 + } + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + var wg sync.WaitGroup + wg.Add(3) + go func() { + defer wg.Done() + tic := time.NewTicker(2 * time.Microsecond) + defer tic.Stop() + for i := 0; i < N; i++ { + if err := r.SetReadDeadline(time.Now().Add(2 * time.Microsecond)); err != nil { + break + } + if err := w.SetWriteDeadline(time.Now().Add(2 * time.Microsecond)); err != nil { + break + } + <-tic.C + } + }() + go func() { + defer wg.Done() + var b [1]byte + for i := 0; i < N; i++ { + _, err := r.Read(b[:]) + if err != nil && !os.IsTimeout(err) { + t.Error("Read returned non-timeout error", err) + } + } + }() + go func() { + defer wg.Done() + var b [1]byte + for i := 0; i < N; i++ { + _, err := w.Write(b[:]) + if err != nil && !os.IsTimeout(err) { + t.Error("Write returned non-timeout error", err) + } + } + }() + wg.Wait() // wait for tester goroutine to stop +} + +// TestRacyRead tests that it is safe to mutate the input Read buffer +// immediately after cancelation has occurred. +func TestRacyRead(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + var wg sync.WaitGroup + defer wg.Wait() + + go io.Copy(w, rand.New(rand.NewSource(0))) + + r.SetReadDeadline(time.Now().Add(time.Millisecond)) + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + b1 := make([]byte, 1024) + b2 := make([]byte, 1024) + for j := 0; j < 100; j++ { + _, err := r.Read(b1) + copy(b1, b2) // Mutate b1 to trigger potential race + if err != nil { + if !os.IsTimeout(err) { + t.Error(err) + } + r.SetReadDeadline(time.Now().Add(time.Millisecond)) + } + } + }() + } +} + +// TestRacyWrite tests that it is safe to mutate the input Write buffer +// immediately after cancelation has occurred. +func TestRacyWrite(t *testing.T) { + t.Parallel() + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer r.Close() + defer w.Close() + + var wg sync.WaitGroup + defer wg.Wait() + + go io.Copy(ioutil.Discard, r) + + w.SetWriteDeadline(time.Now().Add(time.Millisecond)) + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + b1 := make([]byte, 1024) + b2 := make([]byte, 1024) + for j := 0; j < 100; j++ { + _, err := w.Write(b1) + copy(b1, b2) // Mutate b1 to trigger potential race + if err != nil { + if !os.IsTimeout(err) { + t.Error(err) + } + w.SetWriteDeadline(time.Now().Add(time.Millisecond)) + } + } + }() + } +} diff --git a/libgo/go/os/user/cgo_lookup_unix.go b/libgo/go/os/user/cgo_lookup_unix.go index 9670ada..6c815b4 100644 --- a/libgo/go/os/user/cgo_lookup_unix.go +++ b/libgo/go/os/user/cgo_lookup_unix.go @@ -18,6 +18,9 @@ import ( // bytePtrToString takes a NUL-terminated array of bytes and convert // it to a Go string. func bytePtrToString(p *byte) string { + if p == nil { + return "" + } a := (*[10000]byte)(unsafe.Pointer(p)) i := 0 for a[i] != 0 { @@ -99,8 +102,8 @@ func lookupUnixUid(uid int) (*User, error) { func buildUser(pwd *syscall.Passwd) *User { u := &User{ - Uid: strconv.Itoa(int(pwd.Pw_uid)), - Gid: strconv.Itoa(int(pwd.Pw_gid)), + Uid: strconv.FormatUint(uint64(pwd.Pw_uid), 10), + Gid: strconv.FormatUint(uint64(pwd.Pw_gid), 10), Username: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_name))), Name: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_gecos))), HomeDir: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_dir))), @@ -264,3 +267,11 @@ const maxBufferSize = 1 << 20 func isSizeReasonable(sz int64) bool { return sz > 0 && sz <= maxBufferSize } + +// Because we can't use cgo in tests: +func structPasswdForNegativeTest() syscall.Passwd { + sp := syscall.Passwd{} + sp.Pw_uid = 1<<32 - 2 + sp.Pw_gid = 1<<32 - 3 + return sp +} diff --git a/libgo/go/os/user/cgo_unix_test.go b/libgo/go/os/user/cgo_unix_test.go new file mode 100644 index 0000000..6741118 --- /dev/null +++ b/libgo/go/os/user/cgo_unix_test.go @@ -0,0 +1,24 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris +// +build cgo + +package user + +import ( + "testing" +) + +// Issue 22739 +func TestNegativeUid(t *testing.T) { + sp := structPasswdForNegativeTest() + u := buildUser(&sp) + if g, w := u.Uid, "4294967294"; g != w { + t.Errorf("Uid = %q; want %q", g, w) + } + if g, w := u.Gid, "4294967293"; g != w { + t.Errorf("Gid = %q; want %q", g, w) + } +} diff --git a/libgo/go/os/user/listgroups_unix.go b/libgo/go/os/user/listgroups_unix.go index 8b2b13d..b142e2b 100644 --- a/libgo/go/os/user/listgroups_unix.go +++ b/libgo/go/os/user/listgroups_unix.go @@ -15,9 +15,10 @@ import ( /* #include <unistd.h> #include <sys/types.h> -#include <stdlib.h> */ +const maxGroups = 2048 + func listGroups(u *User) ([]string, error) { ug, err := strconv.Atoi(u.Gid) if err != nil { diff --git a/libgo/go/os/wait_wait6.go b/libgo/go/os/wait_wait6.go index b309811..891f242 100644 --- a/libgo/go/os/wait_wait6.go +++ b/libgo/go/os/wait_wait6.go @@ -30,11 +30,6 @@ func (p *Process) blockUntilWaitable() (bool, error) { } runtime.KeepAlive(p) if errno != 0 { - // The wait6 system call is supported only on FreeBSD - // 9.3 and above, so it may return an ENOSYS error. - if errno == syscall.ENOSYS { - return false, nil - } return false, NewSyscallError("wait6", errno) } return true, nil |