diff options
author | Ian Lance Taylor <ian@gcc.gnu.org> | 2015-10-31 00:59:47 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2015-10-31 00:59:47 +0000 |
commit | af146490bb04205107cb23e301ec7a8ff927b5fc (patch) | |
tree | 13beeaed3698c61903fe93fb1ce70bd9b18d4e7f /libgo/go/os | |
parent | 725e1be3406315d9bcc8195d7eef0a7082b3c7cc (diff) | |
download | gcc-af146490bb04205107cb23e301ec7a8ff927b5fc.zip gcc-af146490bb04205107cb23e301ec7a8ff927b5fc.tar.gz gcc-af146490bb04205107cb23e301ec7a8ff927b5fc.tar.bz2 |
runtime: Remove now unnecessary pad field from ParFor.
It is not needed due to the removal of the ctx field.
Reviewed-on: https://go-review.googlesource.com/16525
From-SVN: r229616
Diffstat (limited to 'libgo/go/os')
31 files changed, 1133 insertions, 229 deletions
diff --git a/libgo/go/os/env.go b/libgo/go/os/env.go index d0494a4..a4ede15 100644 --- a/libgo/go/os/env.go +++ b/libgo/go/os/env.go @@ -33,7 +33,7 @@ func ExpandEnv(s string) string { return Expand(s, Getenv) } -// isSpellSpecialVar reports whether the character identifies a special +// isShellSpecialVar reports whether the character identifies a special // shell variable such as $*. func isShellSpecialVar(c uint8) bool { switch c { @@ -48,7 +48,7 @@ func isAlphaNum(c uint8) bool { return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' } -// getName returns the name that begins the string and the number of bytes +// getShellName returns the name that begins the string and the number of bytes // consumed to extract it. If the name is enclosed in {}, it's part of a ${} // expansion and two more bytes are needed than the length of the name. func getShellName(s string) (string, int) { @@ -81,6 +81,15 @@ func Getenv(key string) string { return v } +// LookupEnv retrieves the value of the environment variable named +// by the key. If the variable is present in the environment the +// value (which may be empty) is returned and the boolean is true. +// Otherwise the returned value will be empty and the boolean will +// be false. +func LookupEnv(key string) (string, bool) { + return syscall.Getenv(key) +} + // Setenv sets the value of the environment variable named by the key. // It returns an error, if any. func Setenv(key, value string) error { diff --git a/libgo/go/os/env_test.go b/libgo/go/os/env_test.go index e618067..d1074cd 100644 --- a/libgo/go/os/env_test.go +++ b/libgo/go/os/env_test.go @@ -94,3 +94,20 @@ func TestUnsetenv(t *testing.T) { t.Fatal("Unsetenv didn't clear TestUnsetenv") } } + +func TestLookupEnv(t *testing.T) { + const smallpox = "SMALLPOX" // No one has smallpox. + value, ok := LookupEnv(smallpox) // Should not exist. + if ok || value != "" { + t.Fatalf("%s=%q", smallpox, value) + } + defer Unsetenv(smallpox) + err := Setenv(smallpox, "virus") + if err != nil { + t.Fatalf("failed to release smallpox virus") + } + value, ok = LookupEnv(smallpox) + if !ok { + t.Errorf("smallpox release failed; world remains safe but LookupEnv is broken") + } +} diff --git a/libgo/go/os/exec.go b/libgo/go/os/exec.go index 5aea309..15e95b9 100644 --- a/libgo/go/os/exec.go +++ b/libgo/go/os/exec.go @@ -13,8 +13,8 @@ import ( // Process stores the information about a process created by StartProcess. type Process struct { Pid int - handle uintptr - isdone uint32 // process has been successfully waited on, non zero if true + handle uintptr // handle is accessed atomically on Windows + isdone uint32 // process has been successfully waited on, non zero if true } func newProcess(pid int, handle uintptr) *Process { diff --git a/libgo/go/os/exec/exec.go b/libgo/go/os/exec/exec.go index 72b4905..8a84e26 100644 --- a/libgo/go/os/exec/exec.go +++ b/libgo/go/os/exec/exec.go @@ -32,6 +32,9 @@ func (e *Error) Error() string { } // Cmd represents an external command being prepared or run. +// +// A Cmd cannot be reused after calling its Run, Output or CombinedOutput +// methods. type Cmd struct { // Path is the path of the command to run. // @@ -80,8 +83,8 @@ type Cmd struct { // new process. It does not include standard input, standard output, or // standard error. If non-nil, entry i becomes file descriptor 3+i. // - // BUG: on OS X 10.6, child processes may sometimes inherit unwanted fds. - // http://golang.org/issue/2603 + // BUG(rsc): On OS X 10.6, child processes may sometimes inherit unwanted fds. + // https://golang.org/issue/2603 ExtraFiles []*os.File // SysProcAttr holds optional, operating system-specific attributes. @@ -154,6 +157,11 @@ func (c *Cmd) argv() []string { return []string{c.Path} } +// skipStdinCopyError optionally specifies a function which reports +// whether the provided the 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 + func (c *Cmd) stdin() (f *os.File, err error) { if c.Stdin == nil { f, err = os.Open(os.DevNull) @@ -177,6 +185,9 @@ func (c *Cmd) stdin() (f *os.File, err error) { c.closeAfterWait = append(c.closeAfterWait, pw) c.goroutine = append(c.goroutine, func() error { _, err := io.Copy(pw, c.Stdin) + if skip := skipStdinCopyError; skip != nil && skip(err) { + err = nil + } if err1 := pw.Close(); err == nil { err = err1 } @@ -219,6 +230,7 @@ func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) { c.closeAfterWait = append(c.closeAfterWait, pr) c.goroutine = append(c.goroutine, func() error { _, err := io.Copy(w, pr) + pr.Close() // in case io.Copy stopped due to write error return err }) return pw, nil @@ -352,6 +364,10 @@ 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. +// // Wait releases any resources associated with the Cmd. func (c *Cmd) Wait() error { if c.Process == nil { diff --git a/libgo/go/os/exec/exec_posix.go b/libgo/go/os/exec/exec_posix.go new file mode 100644 index 0000000..5e11137 --- /dev/null +++ b/libgo/go/os/exec/exec_posix.go @@ -0,0 +1,24 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !plan9 + +package exec + +import ( + "os" + "syscall" +) + +func init() { + skipStdinCopyError = func(err error) bool { + // Ignore EPIPE errors copying to stdin if the program + // completed successfully otherwise. + // See Issue 9173. + pe, ok := err.(*os.PathError) + return ok && + pe.Op == "write" && pe.Path == "|1" && + pe.Err == syscall.EPIPE + } +} diff --git a/libgo/go/os/exec/exec_test.go b/libgo/go/os/exec/exec_test.go index f9ffde6..f4c025e 100644 --- a/libgo/go/os/exec/exec_test.go +++ b/libgo/go/os/exec/exec_test.go @@ -11,6 +11,7 @@ import ( "bufio" "bytes" "fmt" + "internal/testenv" "io" "io/ioutil" "log" @@ -28,9 +29,8 @@ import ( ) func helperCommand(t *testing.T, s ...string) *exec.Cmd { - if runtime.GOOS == "nacl" { - t.Skip("skipping on nacl") - } + testenv.MustHaveExec(t) + cs := []string{"-test.run=TestHelperProcess", "--"} cs = append(cs, s...) cmd := exec.Command(os.Args[0], cs...) @@ -53,6 +53,8 @@ func TestEcho(t *testing.T) { } func TestCommandRelativeName(t *testing.T) { + testenv.MustHaveExec(t) + // Run our own binary as a relative path // (e.g. "_test/exec.test") our parent directory. base := filepath.Base(os.Args[0]) // "exec.test" @@ -250,6 +252,12 @@ func TestPipeLookPathLeak(t *testing.T) { } func numOpenFDS(t *testing.T) (n int, lsof []byte) { + if runtime.GOOS == "android" { + // Android's stock lsof does not obey the -p option, + // so extra filtering is needed. (golang.org/issue/10206) + return numOpenFDsAndroid(t) + } + lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output() if err != nil { t.Skip("skipping test; error finding or running lsof") @@ -257,6 +265,45 @@ func numOpenFDS(t *testing.T) (n int, lsof []byte) { return bytes.Count(lsof, []byte("\n")), lsof } +func numOpenFDsAndroid(t *testing.T) (n int, lsof []byte) { + raw, err := exec.Command("lsof").Output() + if err != nil { + t.Skip("skipping test; error finding or running lsof") + } + + // First find the PID column index by parsing the first line, and + // select lines containing pid in the column. + pid := []byte(strconv.Itoa(os.Getpid())) + pidCol := -1 + + s := bufio.NewScanner(bytes.NewReader(raw)) + for s.Scan() { + line := s.Bytes() + fields := bytes.Fields(line) + if pidCol < 0 { + for i, v := range fields { + if bytes.Equal(v, []byte("PID")) { + pidCol = i + break + } + } + lsof = append(lsof, line...) + continue + } + if bytes.Equal(fields[pidCol], pid) { + lsof = append(lsof, '\n') + lsof = append(lsof, line...) + } + } + if pidCol < 0 { + t.Fatal("error processing lsof output: unexpected header format") + } + if err := s.Err(); err != nil { + t.Fatalf("error processing lsof output: %v", err) + } + return bytes.Count(lsof, []byte("\n")), lsof +} + var testedAlreadyLeaked = false // basefds returns the number of expected file descriptors @@ -275,15 +322,15 @@ func closeUnexpectedFds(t *testing.T, m string) { } func TestExtraFilesFDShuffle(t *testing.T) { - t.Skip("flaky test; see http://golang.org/issue/5780") + t.Skip("flaky test; see https://golang.org/issue/5780") switch runtime.GOOS { case "darwin": - // TODO(cnicolaou): http://golang.org/issue/2603 + // TODO(cnicolaou): https://golang.org/issue/2603 // leads to leaked file descriptors in this test when it's // run from a builder. closeUnexpectedFds(t, "TestExtraFilesFDShuffle") case "netbsd": - // http://golang.org/issue/3955 + // https://golang.org/issue/3955 closeUnexpectedFds(t, "TestExtraFilesFDShuffle") case "windows": t.Skip("no operating system support; skipping") @@ -379,8 +426,9 @@ func TestExtraFilesFDShuffle(t *testing.T) { } func TestExtraFiles(t *testing.T) { - switch runtime.GOOS { - case "nacl", "windows": + testenv.MustHaveExec(t) + + if runtime.GOOS == "windows" { t.Skipf("skipping test on %q", runtime.GOOS) } @@ -608,21 +656,21 @@ func TestHelperProcess(*testing.T) { // file descriptors... case "darwin": // TODO(bradfitz): broken? Sometimes. - // http://golang.org/issue/2603 + // https://golang.org/issue/2603 // Skip this additional part of the test for now. case "netbsd": // TODO(jsing): This currently fails on NetBSD due to // the cloned file descriptors that result from opening // /dev/urandom. - // http://golang.org/issue/3955 + // https://golang.org/issue/3955 case "plan9": // TODO(0intro): Determine why Plan 9 is leaking // file descriptors. - // http://golang.org/issue/7118 + // https://golang.org/issue/7118 case "solaris": // TODO(aram): This fails on Solaris because libc opens // its own files, as it sees fit. Darwin does the same, - // see: http://golang.org/issue/2603 + // see: https://golang.org/issue/2603 default: // Now verify that there are no other open fds. var files []*os.File @@ -721,3 +769,54 @@ func TestHelperProcess(*testing.T) { os.Exit(2) } } + +// Issue 9173: ignore stdin pipe writes if the program completes successfully. +func TestIgnorePipeErrorOnSuccess(t *testing.T) { + testenv.MustHaveExec(t) + + // We really only care about testing this on Unixy things. + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skipf("skipping test on %q", runtime.GOOS) + } + + cmd := helperCommand(t, "echo", "foo") + var out bytes.Buffer + cmd.Stdin = strings.NewReader(strings.Repeat("x", 10<<20)) + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + t.Fatal(err) + } + if got, want := out.String(), "foo\n"; got != want { + t.Errorf("output = %q; want %q", got, want) + } +} + +type badWriter struct{} + +func (w *badWriter) Write(data []byte) (int, error) { + return 0, io.ErrUnexpectedEOF +} + +func TestClosePipeOnCopyError(t *testing.T) { + testenv.MustHaveExec(t) + + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Skipf("skipping test on %s - no yes command", runtime.GOOS) + } + cmd := exec.Command("yes") + cmd.Stdout = new(badWriter) + c := make(chan int, 1) + go func() { + err := cmd.Run() + if err == nil { + t.Errorf("yes completed successfully") + } + c <- 1 + }() + select { + case <-c: + // ok + case <-time.After(5 * time.Second): + t.Fatalf("yes got stuck writing to bad writer") + } +} diff --git a/libgo/go/os/exec_posix.go b/libgo/go/os/exec_posix.go index fb9d291..94dd04b 100644 --- a/libgo/go/os/exec_posix.go +++ b/libgo/go/os/exec_posix.go @@ -81,33 +81,6 @@ func (p *ProcessState) sysUsage() interface{} { return p.rusage } -// Convert i to decimal string. -func itod(i int) string { - if i == 0 { - return "0" - } - - u := uint64(i) - if i < 0 { - u = -u - } - - // Assemble decimal in reverse order. - var b [32]byte - bp := len(b) - for ; u > 0; u /= 10 { - bp-- - b[bp] = byte(u%10) + '0' - } - - if i < 0 { - bp-- - b[bp] = '-' - } - - return string(b[bp:]) -} - func (p *ProcessState) String() string { if p == nil { return "<nil>" @@ -116,13 +89,13 @@ func (p *ProcessState) String() string { res := "" switch { case status.Exited(): - res = "exit status " + itod(status.ExitStatus()) + res = "exit status " + itoa(status.ExitStatus()) case status.Signaled(): res = "signal: " + status.Signal().String() case status.Stopped(): res = "stop signal: " + status.StopSignal().String() if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != 0 { - res += " (trap " + itod(status.TrapCause()) + ")" + res += " (trap " + itoa(status.TrapCause()) + ")" } case status.Continued(): res = "continued" diff --git a/libgo/go/os/exec_windows.go b/libgo/go/os/exec_windows.go index 393393b..3264271 100644 --- a/libgo/go/os/exec_windows.go +++ b/libgo/go/os/exec_windows.go @@ -7,13 +7,15 @@ package os import ( "errors" "runtime" + "sync/atomic" "syscall" "time" "unsafe" ) func (p *Process) wait() (ps *ProcessState, err error) { - s, e := syscall.WaitForSingleObject(syscall.Handle(p.handle), syscall.INFINITE) + handle := atomic.LoadUintptr(&p.handle) + s, e := syscall.WaitForSingleObject(syscall.Handle(handle), syscall.INFINITE) switch s { case syscall.WAIT_OBJECT_0: break @@ -23,12 +25,12 @@ func (p *Process) wait() (ps *ProcessState, err error) { return nil, errors.New("os: unexpected result from WaitForSingleObject") } var ec uint32 - e = syscall.GetExitCodeProcess(syscall.Handle(p.handle), &ec) + e = syscall.GetExitCodeProcess(syscall.Handle(handle), &ec) if e != nil { return nil, NewSyscallError("GetExitCodeProcess", e) } var u syscall.Rusage - e = syscall.GetProcessTimes(syscall.Handle(p.handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime) + e = syscall.GetProcessTimes(syscall.Handle(handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime) if e != nil { return nil, NewSyscallError("GetProcessTimes", e) } @@ -53,7 +55,8 @@ func terminateProcess(pid, exitcode int) error { } func (p *Process) signal(sig Signal) error { - if p.handle == uintptr(syscall.InvalidHandle) { + handle := atomic.LoadUintptr(&p.handle) + if handle == uintptr(syscall.InvalidHandle) { return syscall.EINVAL } if p.done() { @@ -67,14 +70,15 @@ func (p *Process) signal(sig Signal) error { } func (p *Process) release() error { - if p.handle == uintptr(syscall.InvalidHandle) { + handle := atomic.LoadUintptr(&p.handle) + if handle == uintptr(syscall.InvalidHandle) { return syscall.EINVAL } - e := syscall.CloseHandle(syscall.Handle(p.handle)) + e := syscall.CloseHandle(syscall.Handle(handle)) if e != nil { return NewSyscallError("CloseHandle", e) } - p.handle = uintptr(syscall.InvalidHandle) + atomic.StoreUintptr(&p.handle, uintptr(syscall.InvalidHandle)) // no need for a finalizer anymore runtime.SetFinalizer(p, nil) return nil diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go index e12428c..8c0e3ff 100644 --- a/libgo/go/os/file.go +++ b/libgo/go/os/file.go @@ -192,7 +192,7 @@ func (f *File) Seek(offset int64, whence int) (ret int64, err error) { // WriteString is like Write, but writes the contents of string s rather than // a slice of bytes. -func (f *File) WriteString(s string) (ret int, err error) { +func (f *File) WriteString(s string) (n int, err error) { if f == nil { return 0, ErrInvalid } @@ -203,9 +203,16 @@ func (f *File) WriteString(s string) (ret int, err error) { // If there is an error, it will be of type *PathError. func Mkdir(name string, perm FileMode) error { e := syscall.Mkdir(name, syscallMode(perm)) + if e != nil { return &PathError{"mkdir", name, e} } + + // mkdir(2) itself won't handle the sticky bit on *BSD and Solaris + if !supportsCreateWithStickyBit && perm&ModeSticky != 0 { + Chmod(name, perm) + } + return nil } @@ -235,16 +242,16 @@ func (f *File) Chdir() error { // the returned file can be used for reading; the associated file // descriptor has mode O_RDONLY. // If there is an error, it will be of type *PathError. -func Open(name string) (file *File, err error) { +func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) } -// Create creates the named file mode 0666 (before umask), truncating -// it if it already exists. If successful, methods on the returned +// Create creates the named file with mode 0666 (before umask), truncating +// it if it already exists. If successful, methods on the returned // File can be used for I/O; the associated file descriptor has mode // O_RDWR. // If there is an error, it will be of type *PathError. -func Create(name string) (file *File, err error) { +func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) } @@ -252,6 +259,7 @@ func Create(name string) (file *File, err error) { var lstat = Lstat // Rename renames (moves) a file. OS-specific restrictions might apply. +// If there is an error, it will be of type *LinkError. func Rename(oldpath, newpath string) error { return rename(oldpath, newpath) } diff --git a/libgo/go/os/file_plan9.go b/libgo/go/os/file_plan9.go index 132594e..085ebc4 100644 --- a/libgo/go/os/file_plan9.go +++ b/libgo/go/os/file_plan9.go @@ -79,7 +79,7 @@ func syscallMode(i FileMode) (o uint32) { // (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful, // methods on the returned File can be used for I/O. // If there is an error, it will be of type *PathError. -func OpenFile(name string, flag int, perm FileMode) (file *File, err error) { +func OpenFile(name string, flag int, perm FileMode) (*File, error) { var ( fd int e error @@ -159,7 +159,7 @@ func (file *file) close() error { // Stat returns the FileInfo structure describing file. // If there is an error, it will be of type *PathError. -func (f *File) Stat() (fi FileInfo, err error) { +func (f *File) Stat() (FileInfo, error) { if f == nil { return nil, ErrInvalid } @@ -224,7 +224,7 @@ func (f *File) Chmod(mode FileMode) error { // Sync commits the current contents of the file to stable storage. // Typically, this means flushing the file system's in-memory copy // of recently written data to disk. -func (f *File) Sync() (err error) { +func (f *File) Sync() error { if f == nil { return ErrInvalid } @@ -319,7 +319,7 @@ func hasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[0:len(prefix)] == prefix } -// Variant of LastIndex from the strings package. +// LastIndexByte from the strings package. func lastIndex(s string, sep byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == sep { diff --git a/libgo/go/os/file_posix.go b/libgo/go/os/file_posix.go index fbb3b5e4..6d8076f 100644 --- a/libgo/go/os/file_posix.go +++ b/libgo/go/os/file_posix.go @@ -28,14 +28,6 @@ func Readlink(name string) (string, error) { } } -func rename(oldname, newname string) error { - e := syscall.Rename(oldname, newname) - if e != nil { - return &LinkError{"rename", oldname, newname, e} - } - return nil -} - // syscallMode returns the syscall-specific mode bits from Go's portable mode bits. func syscallMode(i FileMode) (o uint32) { o |= uint32(i.Perm()) @@ -122,7 +114,7 @@ func (f *File) Truncate(size int64) error { // Sync commits the current contents of the file to stable storage. // Typically, this means flushing the file system's in-memory copy // of recently written data to disk. -func (f *File) Sync() (err error) { +func (f *File) Sync() error { if f == nil { return ErrInvalid } diff --git a/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go index b25e62f..7a18987 100644 --- a/libgo/go/os/file_unix.go +++ b/libgo/go/os/file_unix.go @@ -12,6 +12,14 @@ import ( "syscall" ) +func rename(oldname, newname string) error { + e := syscall.Rename(oldname, newname) + if e != nil { + return &LinkError{"rename", oldname, newname, e} + } + return nil +} + // File represents an open file descriptor. type File struct { *file @@ -73,12 +81,24 @@ const DevNull = "/dev/null" // (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful, // methods on the returned File can be used for I/O. // If there is an error, it will be of type *PathError. -func OpenFile(name string, flag int, perm FileMode) (file *File, err error) { +func OpenFile(name string, flag int, perm FileMode) (*File, error) { + chmod := false + if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 { + if _, err := Stat(name); IsNotExist(err) { + chmod = true + } + } + r, e := syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm)) if e != nil { return nil, &PathError{"open", name, e} } + // open(2) itself won't handle the sticky bit on *BSD and Solaris + if chmod { + Chmod(name, perm) + } + // There's a race here with fork/exec, which we are // content to live with. See ../syscall/exec_unix.go. if !supportsCloseOnExec { @@ -126,12 +146,12 @@ func (file *file) close() error { // Stat returns the FileInfo structure describing file. // If there is an error, it will be of type *PathError. -func (f *File) Stat() (fi FileInfo, err error) { +func (f *File) Stat() (FileInfo, error) { if f == nil { return nil, ErrInvalid } var stat syscall.Stat_t - err = syscall.Fstat(f.fd, &stat) + err := syscall.Fstat(f.fd, &stat) if err != nil { return nil, &PathError{"stat", f.name, err} } @@ -140,9 +160,9 @@ func (f *File) Stat() (fi FileInfo, err error) { // Stat returns a FileInfo describing the named file. // If there is an error, it will be of type *PathError. -func Stat(name string) (fi FileInfo, err error) { +func Stat(name string) (FileInfo, error) { var stat syscall.Stat_t - err = syscall.Stat(name, &stat) + err := syscall.Stat(name, &stat) if err != nil { return nil, &PathError{"stat", name, err} } @@ -153,9 +173,9 @@ func Stat(name string) (fi FileInfo, err error) { // If the file is a symbolic link, the returned FileInfo // describes the symbolic link. Lstat makes no attempt to follow the link. // If there is an error, it will be of type *PathError. -func Lstat(name string) (fi FileInfo, err error) { +func Lstat(name string) (FileInfo, error) { var stat syscall.Stat_t - err = syscall.Lstat(name, &stat) + err := syscall.Lstat(name, &stat) if err != nil { return nil, &PathError{"lstat", name, err} } diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go index 7a86efa..78201f2c 100644 --- a/libgo/go/os/os_test.go +++ b/libgo/go/os/os_test.go @@ -9,6 +9,7 @@ import ( "errors" "flag" "fmt" + "internal/testenv" "io" "io/ioutil" . "os" @@ -21,7 +22,6 @@ import ( "sync" "syscall" "testing" - "text/template" "time" ) @@ -41,18 +41,33 @@ type sysDir struct { files []string } -var sysdir = func() (sd *sysDir) { +var sysdir = func() *sysDir { switch runtime.GOOS { case "android": - sd = &sysDir{ + return &sysDir{ "/system/etc", []string{ "audio_policy.conf", "system_fonts.xml", }, } + case "darwin": + switch runtime.GOARCH { + case "arm", "arm64": + wd, err := syscall.Getwd() + if err != nil { + wd = err.Error() + } + return &sysDir{ + filepath.Join(wd, "..", ".."), + []string{ + "ResourceRules.plist", + "Info.plist", + }, + } + } case "windows": - sd = &sysDir{ + return &sysDir{ Getenv("SystemRoot") + "\\system32\\drivers\\etc", []string{ "networks", @@ -61,24 +76,22 @@ var sysdir = func() (sd *sysDir) { }, } case "plan9": - sd = &sysDir{ + return &sysDir{ "/lib/ndb", []string{ "common", "local", }, } - default: - sd = &sysDir{ - "/etc", - []string{ - "group", - "hosts", - "passwd", - }, - } } - return + return &sysDir{ + "/etc", + []string{ + "group", + "hosts", + "passwd", + }, + } }() func size(name string, t *testing.T) int64 { @@ -112,15 +125,22 @@ func equal(name1, name2 string) (r bool) { return } -func newFile(testName string, t *testing.T) (f *File) { - // Use a local file system, not NFS. - // On Unix, override $TMPDIR in case the user - // has it set to an NFS-mounted directory. - dir := "" - if runtime.GOOS != "android" && runtime.GOOS != "windows" { - dir = "/tmp" +// localTmp returns a local temporary directory not on NFS. +func localTmp() string { + switch runtime.GOOS { + case "android", "windows": + return TempDir() + case "darwin": + switch runtime.GOARCH { + case "arm", "arm64": + return TempDir() + } } - f, err := ioutil.TempFile(dir, "_Go_"+testName) + return "/tmp" +} + +func newFile(testName string, t *testing.T) (f *File) { + f, err := ioutil.TempFile(localTmp(), "_Go_"+testName) if err != nil { t.Fatalf("TempFile %s: %s", testName, err) } @@ -128,14 +148,7 @@ func newFile(testName string, t *testing.T) (f *File) { } func newDir(testName string, t *testing.T) (name string) { - // Use a local file system, not NFS. - // On Unix, override $TMPDIR in case the user - // has it set to an NFS-mounted directory. - dir := "" - if runtime.GOOS != "android" && runtime.GOOS != "windows" { - dir = "/tmp" - } - name, err := ioutil.TempDir(dir, "_Go_"+testName) + name, err := ioutil.TempDir(localTmp(), "_Go_"+testName) if err != nil { t.Fatalf("TempDir %s: %s", testName, err) } @@ -310,6 +323,15 @@ func TestReaddirnamesOneAtATime(t *testing.T) { switch runtime.GOOS { case "android": dir = "/system/bin" + case "darwin": + switch runtime.GOARCH { + case "arm", "arm64": + wd, err := Getwd() + if err != nil { + t.Fatal(err) + } + dir = wd + } case "plan9": dir = "/bin" case "windows": @@ -489,11 +511,35 @@ func TestReaddirStatFailures(t *testing.T) { } } +// Readdir on a regular file should fail. +func TestReaddirOfFile(t *testing.T) { + f, err := ioutil.TempFile("", "_Go_ReaddirOfFile") + if err != nil { + t.Fatal(err) + } + defer Remove(f.Name()) + f.Write([]byte("foo")) + f.Close() + reg, err := Open(f.Name()) + if err != nil { + t.Fatal(err) + } + defer reg.Close() + + names, err := reg.Readdirnames(-1) + if err == nil { + t.Error("Readdirnames succeeded; want non-nil error") + } + if len(names) > 0 { + t.Errorf("unexpected dir names in regular file: %q", names) + } +} + func TestHardLink(t *testing.T) { - // Hardlinks are not supported under windows or Plan 9. if runtime.GOOS == "plan9" { - return + t.Skip("skipping on plan9, hardlinks not supported") } + defer chtmpdir(t)() from, to := "hardlinktestfrom", "hardlinktestto" Remove(from) // Just in case. file, err := Create(to) @@ -508,6 +554,14 @@ func TestHardLink(t *testing.T) { if err != nil { t.Fatalf("link %q, %q failed: %v", to, from, err) } + + none := "hardlinktestnone" + err = Link(none, none) + // Check the returned error is well-formed. + if lerr, ok := err.(*LinkError); !ok || lerr.Error() == "" { + t.Errorf("link %q, %q failed to return a valid error", none, none) + } + defer Remove(from) tostat, err := Stat(to) if err != nil { @@ -522,6 +576,31 @@ func TestHardLink(t *testing.T) { } } +// chtmpdir changes the working directory to a new temporary directory and +// provides a cleanup function. Used when PWD is read-only. +func chtmpdir(t *testing.T) func() { + if runtime.GOOS != "darwin" || (runtime.GOARCH != "arm" && runtime.GOARCH != "arm64") { + return func() {} // only needed on darwin/arm{,64} + } + oldwd, err := Getwd() + if err != nil { + t.Fatalf("chtmpdir: %v", err) + } + d, err := ioutil.TempDir("", "test") + if err != nil { + t.Fatalf("chtmpdir: %v", err) + } + if err := Chdir(d); err != nil { + t.Fatalf("chtmpdir: %v", err) + } + return func() { + if err := Chdir(oldwd); err != nil { + t.Fatalf("chtmpdir: %v", err) + } + RemoveAll(d) + } +} + func TestSymlink(t *testing.T) { switch runtime.GOOS { case "android", "nacl", "plan9": @@ -531,6 +610,7 @@ func TestSymlink(t *testing.T) { t.Skipf("skipping on %s", runtime.GOOS) } } + defer chtmpdir(t)() from, to := "symlinktestfrom", "symlinktestto" Remove(from) // Just in case. file, err := Create(to) @@ -597,6 +677,7 @@ func TestLongSymlink(t *testing.T) { t.Skipf("skipping on %s", runtime.GOOS) } } + defer chtmpdir(t)() s := "0123456789abcdef" // Long, but not too long: a common limit is 255. s = s + s + s + s + s + s + s + s + s + s + s + s + s + s + s @@ -617,14 +698,18 @@ func TestLongSymlink(t *testing.T) { } func TestRename(t *testing.T) { + defer chtmpdir(t)() from, to := "renamefrom", "renameto" - Remove(to) // Just in case. + // Ensure we are not testing the overwrite case here. + Remove(from) + Remove(to) + file, err := Create(from) if err != nil { - t.Fatalf("open %q failed: %v", to, err) + t.Fatalf("open %q failed: %v", from, err) } if err = file.Close(); err != nil { - t.Errorf("close %q failed: %v", to, err) + t.Errorf("close %q failed: %v", from, err) } err = Rename(from, to) if err != nil { @@ -637,6 +722,79 @@ func TestRename(t *testing.T) { } } +func TestRenameOverwriteDest(t *testing.T) { + if runtime.GOOS == "plan9" { + t.Skip("skipping on plan9") + } + defer chtmpdir(t)() + from, to := "renamefrom", "renameto" + // Just in case. + Remove(from) + Remove(to) + + toData := []byte("to") + fromData := []byte("from") + + err := ioutil.WriteFile(to, toData, 0777) + if err != nil { + t.Fatalf("write file %q failed: %v", to, err) + } + + err = ioutil.WriteFile(from, fromData, 0777) + if err != nil { + t.Fatalf("write file %q failed: %v", from, err) + } + err = Rename(from, to) + if err != nil { + t.Fatalf("rename %q, %q failed: %v", to, from, err) + } + defer Remove(to) + + _, err = Stat(from) + if err == nil { + t.Errorf("from file %q still exists", from) + } + if err != nil && !IsNotExist(err) { + t.Fatalf("stat from: %v", err) + } + toFi, err := Stat(to) + if err != nil { + t.Fatalf("stat %q failed: %v", to, err) + } + if toFi.Size() != int64(len(fromData)) { + t.Errorf(`"to" size = %d; want %d (old "from" size)`, toFi.Size(), len(fromData)) + } +} + +func TestRenameFailed(t *testing.T) { + defer chtmpdir(t)() + from, to := "renamefrom", "renameto" + // Ensure we are not testing the overwrite case here. + Remove(from) + Remove(to) + + err := Rename(from, to) + switch err := err.(type) { + case *LinkError: + if err.Op != "rename" { + t.Errorf("rename %q, %q: err.Op: want %q, got %q", from, to, "rename", err.Op) + } + if err.Old != from { + t.Errorf("rename %q, %q: err.Old: want %q, got %q", from, to, from, err.Old) + } + if err.New != to { + t.Errorf("rename %q, %q: err.New: want %q, got %q", from, to, to, err.New) + } + case nil: + t.Errorf("rename %q, %q: expected error, got nil", from, to) + + // cleanup whatever was placed in "renameto" + Remove(to) + default: + t.Errorf("rename %q, %q: expected %T, got %T %v", from, to, new(LinkError), err, err) + } +} + func exec(t *testing.T, dir, cmd string, args []string, expect string) { r, w, err := Pipe() if err != nil { @@ -664,18 +822,18 @@ func exec(t *testing.T, dir, cmd string, args []string, expect string) { } func TestStartProcess(t *testing.T) { - switch runtime.GOOS { - case "android", "nacl": - t.Skipf("skipping on %s", runtime.GOOS) - } + testenv.MustHaveExec(t) var dir, cmd string var args []string - if runtime.GOOS == "windows" { + switch runtime.GOOS { + case "android": + t.Skip("android doesn't have /bin/pwd") + case "windows": cmd = Getenv("COMSPEC") dir = Getenv("SystemRoot") args = []string{"/c", "cd"} - } else { + default: cmd = "/bin/pwd" dir = "/" args = []string{} @@ -847,6 +1005,19 @@ func TestChdirAndGetwd(t *testing.T) { dirs = []string{"/", "/system/bin"} case "plan9": dirs = []string{"/", "/usr"} + case "darwin": + switch runtime.GOARCH { + case "arm", "arm64": + d1, err := ioutil.TempDir("", "d1") + if err != nil { + t.Fatalf("TempDir: %v", err) + } + d2, err := ioutil.TempDir("", "d2") + if err != nil { + t.Fatalf("TempDir: %v", err) + } + dirs = []string{d1, d2} + } } oldwd := Getenv("PWD") for mode := 0; mode < 2; mode++ { @@ -892,6 +1063,64 @@ func TestChdirAndGetwd(t *testing.T) { fd.Close() } +// Test that Chdir+Getwd is program-wide. +func TestProgWideChdir(t *testing.T) { + const N = 10 + c := make(chan bool) + cpwd := make(chan string) + for i := 0; i < N; i++ { + go func(i int) { + // Lock half the goroutines in their own operating system + // thread to exercise more scheduler possibilities. + if i%2 == 1 { + // On Plan 9, after calling LockOSThread, the goroutines + // run on different processes which don't share the working + // directory. This used to be an issue because Go expects + // the working directory to be program-wide. + // See issue 9428. + runtime.LockOSThread() + } + <-c + pwd, err := Getwd() + if err != nil { + t.Errorf("Getwd on goroutine %d: %v", i, err) + return + } + cpwd <- pwd + }(i) + } + oldwd, err := Getwd() + if err != nil { + t.Fatalf("Getwd: %v", err) + } + d, err := ioutil.TempDir("", "test") + if err != nil { + t.Fatalf("TempDir: %v", err) + } + defer func() { + if err := Chdir(oldwd); err != nil { + t.Fatalf("Chdir: %v", err) + } + RemoveAll(d) + }() + if err := Chdir(d); err != nil { + t.Fatalf("Chdir: %v", err) + } + // OS X sets TMPDIR to a symbolic link. + // So we resolve our working directory again before the test. + d, err = Getwd() + if err != nil { + t.Fatalf("Getwd: %v", err) + } + close(c) + for i := 0; i < N; i++ { + pwd := <-cpwd + if pwd != d { + t.Errorf("Getwd returned %q; want %q", pwd, d) + } + } +} + func TestSeek(t *testing.T) { f := newFile("TestSeek", t) defer Remove(f.Name()) @@ -920,7 +1149,7 @@ func TestSeek(t *testing.T) { if off != tt.out || err != nil { if e, ok := err.(*PathError); ok && e.Err == syscall.EINVAL && tt.out > 1<<32 { // Reiserfs rejects the big seeks. - // http://code.google.com/p/go/issues/detail?id=91 + // https://golang.org/issue/91 break } t.Errorf("#%d: Seek(%v, %v) = %v, %v want %v, nil", i, tt.in, tt.whence, off, err, tt.out) @@ -1033,14 +1262,35 @@ func run(t *testing.T, cmd []string) string { return output } +func testWindowsHostname(t *testing.T) { + hostname, err := Hostname() + if err != nil { + t.Fatal(err) + } + cmd := osexec.Command("hostname") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Failed to execute hostname command: %v %s", err, out) + } + want := strings.Trim(string(out), "\r\n") + if hostname != want { + t.Fatalf("Hostname() = %q, want %q", hostname, want) + } +} + func TestHostname(t *testing.T) { // There is no other way to fetch hostname on windows, but via winapi. // On Plan 9 it can be taken from #c/sysname as Hostname() does. switch runtime.GOOS { - case "android", "nacl", "plan9", "windows": - t.Skipf("skipping on %s", runtime.GOOS) + case "android", "plan9": + t.Skipf("%s doesn't have /bin/hostname", runtime.GOOS) + case "windows": + testWindowsHostname(t) + return } + testenv.MustHaveExec(t) + // Check internal Hostname() against the output of /bin/hostname. // Allow that the internal Hostname returns a Fully Qualified Domain Name // and the /bin/hostname only returns the first component @@ -1115,6 +1365,7 @@ func writeFile(t *testing.T, fname string, flag int, text string) string { } func TestAppend(t *testing.T) { + defer chtmpdir(t)() const f = "append.txt" defer Remove(f) s := writeFile(t, f, O_CREATE|O_TRUNC|O_RDWR, "new") @@ -1178,6 +1429,7 @@ func TestNilProcessStateString(t *testing.T) { } func TestSameFile(t *testing.T) { + defer chtmpdir(t)() fa, err := Create("a") if err != nil { t.Fatalf("Create(a): %v", err) @@ -1297,45 +1549,11 @@ func TestReadAtEOF(t *testing.T) { } func testKillProcess(t *testing.T, processKiller func(p *Process)) { - t.Skip("gccgo does not have a go command") - switch runtime.GOOS { - case "android", "nacl": - t.Skipf("skipping on %s", runtime.GOOS) - } - - dir, err := ioutil.TempDir("", "go-build") - if err != nil { - t.Fatalf("Failed to create temp directory: %v", err) - } - defer RemoveAll(dir) - - src := filepath.Join(dir, "main.go") - f, err := Create(src) - if err != nil { - t.Fatalf("Failed to create %v: %v", src, err) - } - st := template.Must(template.New("source").Parse(` -package main -import "time" -func main() { - time.Sleep(time.Second) -} -`)) - err = st.Execute(f, nil) - if err != nil { - f.Close() - t.Fatalf("Failed to execute template: %v", err) - } - f.Close() - - exe := filepath.Join(dir, "main.exe") - output, err := osexec.Command("go", "build", "-o", exe, src).CombinedOutput() - if err != nil { - t.Fatalf("Failed to build exe %v: %v %v", exe, err, string(output)) - } + testenv.MustHaveExec(t) - cmd := osexec.Command(exe) - err = cmd.Start() + // Re-exec the test binary itself to emulate "sleep 1". + cmd := osexec.Command(Args[0], "-test.run", "TestSleep") + err := cmd.Start() if err != nil { t.Fatalf("Failed to start test process: %v", err) } @@ -1349,6 +1567,15 @@ func main() { } } +// TestSleep emulates "sleep 1". It is a helper for testKillProcess, so we +// don't have to rely on an external "sleep" command being available. +func TestSleep(t *testing.T) { + if testing.Short() { + t.Skip("Skipping in short mode") + } + time.Sleep(time.Second) +} + func TestKillStartProcess(t *testing.T) { testKillProcess(t, func(p *Process) { err := p.Kill() @@ -1359,14 +1586,13 @@ func TestKillStartProcess(t *testing.T) { } func TestGetppid(t *testing.T) { - switch runtime.GOOS { - case "nacl": - t.Skip("skipping on nacl") - case "plan9": + if runtime.GOOS == "plan9" { // TODO: golang.org/issue/8206 t.Skipf("skipping test on plan9; see issue 8206") } + testenv.MustHaveExec(t) + if Getenv("GO_WANT_HELPER_PROCESS") == "1" { fmt.Print(Getppid()) Exit(0) diff --git a/libgo/go/os/os_unix_test.go b/libgo/go/os/os_unix_test.go index 21d40cc..2adc3b5 100644 --- a/libgo/go/os/os_unix_test.go +++ b/libgo/go/os/os_unix_test.go @@ -13,6 +13,10 @@ import ( "testing" ) +func init() { + isReadonlyError = func(err error) bool { return err == syscall.EROFS } +} + func checkUidGid(t *testing.T, path string, uid, gid int) { dir, err := Stat(path) if err != nil { @@ -28,10 +32,10 @@ func checkUidGid(t *testing.T, path string, uid, gid int) { } func TestChown(t *testing.T) { - // Chown is not supported under windows os Plan 9. + // Chown is not supported under windows or Plan 9. // Plan9 provides a native ChownPlan9 version instead. if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { - return + 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 @@ -74,3 +78,109 @@ func TestChown(t *testing.T) { checkUidGid(t, f.Name(), int(sys.Uid), gid) } } + +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 + // basically useless. + f := newFile("TestFileChown", t) + defer Remove(f.Name()) + defer f.Close() + dir, err := f.Stat() + if err != nil { + t.Fatalf("stat %s: %s", f.Name(), err) + } + + // Can't change uid unless root, but can try + // changing the group id. First try our current group. + gid := Getgid() + t.Log("gid:", gid) + if err = f.Chown(-1, gid); err != nil { + t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) + } + sys := dir.Sys().(*syscall.Stat_t) + checkUidGid(t, f.Name(), int(sys.Uid), gid) + + // Then try all the auxiliary groups. + groups, err := Getgroups() + if err != nil { + t.Fatalf("getgroups: %s", err) + } + t.Log("groups: ", groups) + for _, g := range groups { + if err = f.Chown(-1, g); err != nil { + t.Fatalf("fchown %s -1 %d: %s", f.Name(), g, err) + } + checkUidGid(t, f.Name(), int(sys.Uid), g) + + // change back to gid to test fd.Chown + if err = f.Chown(-1, gid); err != nil { + t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) + } + checkUidGid(t, f.Name(), int(sys.Uid), gid) + } +} + +func TestLchown(t *testing.T) { + // 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 + // basically useless. + f := newFile("TestLchown", t) + defer Remove(f.Name()) + defer f.Close() + dir, err := f.Stat() + if err != nil { + t.Fatalf("stat %s: %s", f.Name(), err) + } + + linkname := f.Name() + "2" + if err := Link(f.Name(), linkname); err != nil { + t.Fatalf("link %s -> %s: %v", f.Name(), linkname, err) + } + defer Remove(linkname) + + f2, err := Open(linkname) + if err != nil { + t.Fatalf("open %s: %v", linkname, err) + } + defer f2.Close() + + // Can't change uid unless root, but can try + // changing the group id. First try our current group. + gid := Getgid() + t.Log("gid:", gid) + if err = Lchown(linkname, -1, gid); err != nil { + t.Fatalf("lchown %s -1 %d: %s", linkname, gid, err) + } + sys := dir.Sys().(*syscall.Stat_t) + checkUidGid(t, linkname, int(sys.Uid), gid) + + // Then try all the auxiliary groups. + groups, err := Getgroups() + if err != nil { + t.Fatalf("getgroups: %s", err) + } + t.Log("groups: ", groups) + for _, g := range groups { + if err = Lchown(linkname, -1, g); err != nil { + t.Fatalf("lchown %s -1 %d: %s", linkname, g, err) + } + checkUidGid(t, linkname, int(sys.Uid), g) + + // change back to gid to test fd.Chown + if err = f2.Chown(-1, gid); err != nil { + t.Fatalf("fchown %s -1 %d: %s", linkname, gid, err) + } + checkUidGid(t, linkname, int(sys.Uid), gid) + } +} diff --git a/libgo/go/os/path_plan9.go b/libgo/go/os/path_plan9.go index 64bad50..b09b53a 100644 --- a/libgo/go/os/path_plan9.go +++ b/libgo/go/os/path_plan9.go @@ -9,7 +9,7 @@ const ( PathListSeparator = '\000' // OS-specific path list separator ) -// IsPathSeparator returns true if c is a directory separator character. +// IsPathSeparator reports whether c is a directory separator character. func IsPathSeparator(c uint8) bool { return PathSeparator == c } diff --git a/libgo/go/os/path_test.go b/libgo/go/os/path_test.go index 6f24a43..f985381 100644 --- a/libgo/go/os/path_test.go +++ b/libgo/go/os/path_test.go @@ -13,6 +13,8 @@ import ( "testing" ) +var isReadonlyError = func(error) bool { return false } + func TestMkdirAll(t *testing.T) { tmpDir := TempDir() path := tmpDir + "/_TestMkdirAll_/dir/./dir2" @@ -205,16 +207,22 @@ func TestMkdirAllAtSlash(t *testing.T) { switch runtime.GOOS { case "android", "plan9", "windows": t.Skipf("skipping on %s", runtime.GOOS) + case "darwin": + switch runtime.GOARCH { + case "arm", "arm64": + t.Skipf("skipping on darwin/%s, mkdir returns EPERM", runtime.GOARCH) + } } RemoveAll("/_go_os_test") - err := MkdirAll("/_go_os_test/dir", 0777) + const dir = "/_go_os_test/dir" + err := MkdirAll(dir, 0777) if err != nil { pathErr, ok := err.(*PathError) // common for users not to be able to write to / - if ok && pathErr.Err == syscall.EACCES { - return + if ok && (pathErr.Err == syscall.EACCES || isReadonlyError(pathErr.Err)) { + t.Skipf("could not create %v: %v", dir, err) } - t.Fatalf(`MkdirAll "/_go_os_test/dir": %v`, err) + t.Fatalf(`MkdirAll "/_go_os_test/dir": %v, %s`, err, pathErr.Err) } RemoveAll("/_go_os_test") } diff --git a/libgo/go/os/path_unix.go b/libgo/go/os/path_unix.go index 0211107..36f8e61 100644 --- a/libgo/go/os/path_unix.go +++ b/libgo/go/os/path_unix.go @@ -11,7 +11,7 @@ const ( PathListSeparator = ':' // OS-specific path list separator ) -// IsPathSeparator returns true if c is a directory separator character. +// IsPathSeparator reports whether c is a directory separator character. func IsPathSeparator(c uint8) bool { return PathSeparator == c } diff --git a/libgo/go/os/path_windows.go b/libgo/go/os/path_windows.go index 61f2ca5..c96f137 100644 --- a/libgo/go/os/path_windows.go +++ b/libgo/go/os/path_windows.go @@ -9,7 +9,7 @@ const ( PathListSeparator = ';' // OS-specific path list separator ) -// IsPathSeparator returns true if c is a directory separator character. +// IsPathSeparator reports whether c is a directory separator character. func IsPathSeparator(c uint8) bool { // NOTE: Windows accept / as path separator. return c == '\\' || c == '/' diff --git a/libgo/go/os/proc.go b/libgo/go/os/proc.go index 774f099..33a8b26 100644 --- a/libgo/go/os/proc.go +++ b/libgo/go/os/proc.go @@ -44,6 +44,14 @@ func Getgroups() ([]int, error) { // Exit causes the current program to exit with the given status code. // Conventionally, code zero indicates success, non-zero an error. -// The program terminates immediately; deferred functions are -// not run. -func Exit(code int) { syscall.Exit(code) } +// The program terminates immediately; deferred functions are not run. +func Exit(code int) { + if code == 0 { + // Give race detector a chance to fail the program. + // Racy programs do not have the right to finish successfully. + runtime_beforeExit() + } + syscall.Exit(code) +} + +func runtime_beforeExit() // implemented in runtime diff --git a/libgo/go/os/signal/signal.go b/libgo/go/os/signal/signal.go index 3004275..1625786 100644 --- a/libgo/go/os/signal/signal.go +++ b/libgo/go/os/signal/signal.go @@ -5,8 +5,6 @@ // Package signal implements access to incoming signals. package signal -// BUG(rsc): This package is not yet implemented on Plan 9. - import ( "os" "sync" @@ -30,9 +28,55 @@ func (h *handler) set(sig int) { h.mask[sig/32] |= 1 << uint(sig&31) } +func (h *handler) clear(sig int) { + h.mask[sig/32] &^= 1 << uint(sig&31) +} + +// Stop relaying the signals, sigs, to any channels previously registered to +// receive them and either reset the signal handlers to their original values +// (action=disableSignal) or ignore the signals (action=ignoreSignal). +func cancel(sigs []os.Signal, action func(int)) { + handlers.Lock() + defer handlers.Unlock() + + remove := func(n int) { + var zerohandler handler + + for c, h := range handlers.m { + if h.want(n) { + handlers.ref[n]-- + h.clear(n) + if h.mask == zerohandler.mask { + delete(handlers.m, c) + } + } + } + + action(n) + } + + if len(sigs) == 0 { + for n := 0; n < numSig; n++ { + remove(n) + } + } else { + for _, s := range sigs { + remove(signum(s)) + } + } +} + +// Ignore causes the provided signals to be ignored. If they are received by +// the program, nothing will happen. Ignore undoes the effect of any prior +// calls to Notify for the provided signals. +// If no signals are provided, all incoming signals will be ignored. +func Ignore(sig ...os.Signal) { + cancel(sig, ignoreSignal) +} + // Notify causes package signal to relay incoming signals to c. -// If no signals are listed, all incoming signals will be relayed to c. -// Otherwise, just the listed signals will. +// If no signals are provided, all incoming signals will be relayed to c. +// Otherwise, just the provided signals will. // // Package signal will not block sending to c: the caller must ensure // that c has sufficient buffer space to keep up with the expected @@ -87,6 +131,13 @@ func Notify(c chan<- os.Signal, sig ...os.Signal) { } } +// Reset undoes the effect of any prior calls to Notify for the provided +// signals. +// If no signals are provided, all signal handlers will be reset. +func Reset(sig ...os.Signal) { + cancel(sig, disableSignal) +} + // Stop causes package signal to stop relaying incoming signals to c. // It undoes the effect of all prior calls to Notify using c. // When Stop returns, it is guaranteed that c will receive no more signals. diff --git a/libgo/go/os/signal/signal_plan9.go b/libgo/go/os/signal/signal_plan9.go new file mode 100644 index 0000000..b065ae5 --- /dev/null +++ b/libgo/go/os/signal/signal_plan9.go @@ -0,0 +1,60 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package signal + +import ( + "os" + "syscall" +) + +var sigtab = make(map[os.Signal]int) + +// In sig.s; jumps to runtime. +func signal_disable(uint32) +func signal_enable(uint32) +func signal_ignore(uint32) +func signal_recv() string + +func init() { + signal_enable(0) // first call - initialize + go loop() +} + +func loop() { + for { + process(syscall.Note(signal_recv())) + } +} + +const numSig = 256 + +func signum(sig os.Signal) int { + switch sig := sig.(type) { + case syscall.Note: + n, ok := sigtab[sig] + if !ok { + n = len(sigtab) + 1 + if n > numSig { + return -1 + } + sigtab[sig] = n + } + return n + default: + return -1 + } +} + +func enableSignal(sig int) { + signal_enable(uint32(sig)) +} + +func disableSignal(sig int) { + signal_disable(uint32(sig)) +} + +func ignoreSignal(sig int) { + signal_ignore(uint32(sig)) +} diff --git a/libgo/go/os/signal/signal_plan9_test.go b/libgo/go/os/signal/signal_plan9_test.go new file mode 100644 index 0000000..10bfdc3 --- /dev/null +++ b/libgo/go/os/signal/signal_plan9_test.go @@ -0,0 +1,181 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package signal + +import ( + "os" + "runtime" + "syscall" + "testing" + "time" +) + +func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) { + select { + case s := <-c: + if s != sig { + t.Fatalf("signal was %v, want %v", s, sig) + } + case <-time.After(1 * time.Second): + t.Fatalf("timeout waiting for %v", sig) + } +} + +// Test that basic signal handling works. +func TestSignal(t *testing.T) { + // Ask for hangup + c := make(chan os.Signal, 1) + Notify(c, syscall.Note("hangup")) + defer Stop(c) + + // Send this process a hangup + t.Logf("hangup...") + postNote(syscall.Getpid(), "hangup") + waitSig(t, c, syscall.Note("hangup")) + + // Ask for everything we can get. + c1 := make(chan os.Signal, 1) + Notify(c1) + + // Send this process an alarm + t.Logf("alarm...") + postNote(syscall.Getpid(), "alarm") + waitSig(t, c1, syscall.Note("alarm")) + + // Send two more hangups, to make sure that + // they get delivered on c1 and that not reading + // from c does not block everything. + t.Logf("hangup...") + postNote(syscall.Getpid(), "hangup") + waitSig(t, c1, syscall.Note("hangup")) + t.Logf("hangup...") + postNote(syscall.Getpid(), "hangup") + waitSig(t, c1, syscall.Note("hangup")) + + // The first SIGHUP should be waiting for us on c. + waitSig(t, c, syscall.Note("hangup")) +} + +func TestStress(t *testing.T) { + dur := 3 * time.Second + if testing.Short() { + dur = 100 * time.Millisecond + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4)) + done := make(chan bool) + finished := make(chan bool) + go func() { + sig := make(chan os.Signal, 1) + Notify(sig, syscall.Note("alarm")) + defer Stop(sig) + Loop: + for { + select { + case <-sig: + case <-done: + break Loop + } + } + finished <- true + }() + go func() { + Loop: + for { + select { + case <-done: + break Loop + default: + postNote(syscall.Getpid(), "alarm") + runtime.Gosched() + } + } + finished <- true + }() + time.Sleep(dur) + close(done) + <-finished + <-finished + // When run with 'go test -cpu=1,2,4' alarm from this test can slip + // into subsequent TestSignal() causing failure. + // Sleep for a while to reduce the possibility of the failure. + time.Sleep(10 * time.Millisecond) +} + +// Test that Stop cancels the channel's registrations. +func TestStop(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + sigs := []string{ + "alarm", + "hangup", + } + + for _, sig := range sigs { + // Send the signal. + // If it's alarm, we should not see it. + // If it's hangup, maybe we'll die. Let the flag tell us what to do. + if sig != "hangup" { + postNote(syscall.Getpid(), sig) + } + time.Sleep(100 * time.Millisecond) + + // Ask for signal + c := make(chan os.Signal, 1) + Notify(c, syscall.Note(sig)) + defer Stop(c) + + // Send this process that signal + postNote(syscall.Getpid(), sig) + waitSig(t, c, syscall.Note(sig)) + + Stop(c) + select { + case s := <-c: + t.Fatalf("unexpected signal %v", s) + case <-time.After(100 * time.Millisecond): + // nothing to read - good + } + + // Send the signal. + // If it's alarm, we should not see it. + // If it's hangup, maybe we'll die. Let the flag tell us what to do. + if sig != "hangup" { + postNote(syscall.Getpid(), sig) + } + + select { + case s := <-c: + t.Fatalf("unexpected signal %v", s) + case <-time.After(100 * time.Millisecond): + // nothing to read - good + } + } +} + +func itoa(val int) string { + if val < 0 { + return "-" + itoa(-val) + } + var buf [32]byte // big enough for int64 + i := len(buf) - 1 + for val >= 10 { + buf[i] = byte(val%10 + '0') + i-- + val /= 10 + } + buf[i] = byte(val + '0') + return string(buf[i:]) +} + +func postNote(pid int, note string) error { + f, err := os.OpenFile("/proc/"+itoa(pid)+"/note", os.O_WRONLY, 0) + if err != nil { + return err + } + defer f.Close() + _, err = f.Write([]byte(note)) + return err +} diff --git a/libgo/go/os/signal/signal_stub.go b/libgo/go/os/signal/signal_stub.go deleted file mode 100644 index d0a6935..0000000 --- a/libgo/go/os/signal/signal_stub.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build plan9 - -package signal - -import "os" - -const numSig = 0 - -func signum(sig os.Signal) int { return -1 } - -func disableSignal(int) {} - -func enableSignal(int) {} diff --git a/libgo/go/os/signal/signal_test.go b/libgo/go/os/signal/signal_test.go index 22337a7..a71633c 100644 --- a/libgo/go/os/signal/signal_test.go +++ b/libgo/go/os/signal/signal_test.go @@ -109,6 +109,72 @@ func TestStress(t *testing.T) { time.Sleep(10 * time.Millisecond) } +func testCancel(t *testing.T, ignore bool) { + // Send SIGWINCH. By default this signal should be ignored. + syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) + time.Sleep(100 * time.Millisecond) + + // Ask to be notified on c1 when a SIGWINCH is received. + c1 := make(chan os.Signal, 1) + Notify(c1, syscall.SIGWINCH) + defer Stop(c1) + + // Ask to be notified on c2 when a SIGHUP is received. + c2 := make(chan os.Signal, 1) + Notify(c2, syscall.SIGHUP) + defer Stop(c2) + + // Send this process a SIGWINCH and wait for notification on c1. + syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) + waitSig(t, c1, syscall.SIGWINCH) + + // Send this process a SIGHUP and wait for notification on c2. + syscall.Kill(syscall.Getpid(), syscall.SIGHUP) + waitSig(t, c2, syscall.SIGHUP) + + // Ignore, or reset the signal handlers for, SIGWINCH and SIGHUP. + if ignore { + Ignore(syscall.SIGWINCH, syscall.SIGHUP) + } else { + Reset(syscall.SIGWINCH, syscall.SIGHUP) + } + + // Send this process a SIGWINCH. It should be ignored. + syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) + + // If ignoring, Send this process a SIGHUP. It should be ignored. + if ignore { + syscall.Kill(syscall.Getpid(), syscall.SIGHUP) + } + + select { + case s := <-c1: + t.Fatalf("unexpected signal %v", s) + case <-time.After(100 * time.Millisecond): + // nothing to read - good + } + + select { + case s := <-c2: + t.Fatalf("unexpected signal %v", s) + case <-time.After(100 * time.Millisecond): + // nothing to read - good + } + + // Reset the signal handlers for all signals. + Reset() +} + +// Test that Reset cancels registration for listed signals on all channels. +func TestReset(t *testing.T) { + testCancel(t, false) +} + +// Test that Ignore cancels registration for listed signals on all channels. +func TestIgnore(t *testing.T) { + testCancel(t, true) +} + var sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop") // Test that Stop cancels the channel's registrations. diff --git a/libgo/go/os/signal/signal_unix.go b/libgo/go/os/signal/signal_unix.go index 94b8ab3..1bdf1d7 100644 --- a/libgo/go/os/signal/signal_unix.go +++ b/libgo/go/os/signal/signal_unix.go @@ -14,6 +14,7 @@ import ( // In assembly. func signal_disable(uint32) func signal_enable(uint32) +func signal_ignore(uint32) func signal_recv() uint32 func loop() { @@ -51,3 +52,7 @@ func enableSignal(sig int) { func disableSignal(sig int) { signal_disable(uint32(sig)) } + +func ignoreSignal(sig int) { + signal_ignore(uint32(sig)) +} diff --git a/libgo/go/os/stat_plan9.go b/libgo/go/os/stat_plan9.go index 25c9a8c..fa4bd83 100644 --- a/libgo/go/os/stat_plan9.go +++ b/libgo/go/os/stat_plan9.go @@ -9,6 +9,8 @@ import ( "time" ) +const _BIT16SZ = 2 + func sameFile(fs1, fs2 *fileStat) bool { a := fs1.sys.(*syscall.Dir) b := fs2.sys.(*syscall.Dir) @@ -41,16 +43,14 @@ func fileInfoFromStat(d *syscall.Dir) FileInfo { // arg is an open *File or a path string. func dirstat(arg interface{}) (*syscall.Dir, error) { var name string + var err error - // This is big enough for most stat messages - // and rounded to a multiple of 128 bytes. - size := (syscall.STATFIXLEN + 16*4 + 128) &^ 128 + size := syscall.STATFIXLEN + 16*4 for i := 0; i < 2; i++ { - buf := make([]byte, size) + buf := make([]byte, _BIT16SZ+size) var n int - var err error switch a := arg.(type) { case *File: name = a.name @@ -61,34 +61,36 @@ func dirstat(arg interface{}) (*syscall.Dir, error) { default: panic("phase error in dirstat") } - if err != nil { + + if n < _BIT16SZ { return nil, &PathError{"stat", name, err} } - if n < syscall.STATFIXLEN { - return nil, &PathError{"stat", name, syscall.ErrShortStat} - } // Pull the real size out of the stat message. size = int(uint16(buf[0]) | uint16(buf[1])<<8) // If the stat message is larger than our buffer we will // go around the loop and allocate one that is big enough. - if size > n { - continue + if size <= n { + d, err := syscall.UnmarshalDir(buf[:n]) + if err != nil { + return nil, &PathError{"stat", name, err} + } + return d, nil } - d, err := syscall.UnmarshalDir(buf[:n]) - if err != nil { - return nil, &PathError{"stat", name, err} - } - return d, nil } - return nil, &PathError{"stat", name, syscall.ErrBadStat} + + if err == nil { + err = syscall.ErrBadStat + } + + return nil, &PathError{"stat", name, err} } // Stat returns a FileInfo describing the named file. // If there is an error, it will be of type *PathError. -func Stat(name string) (fi FileInfo, err error) { +func Stat(name string) (FileInfo, error) { d, err := dirstat(name) if err != nil { return nil, err @@ -100,7 +102,7 @@ func Stat(name string) (fi FileInfo, err error) { // If the file is a symbolic link, the returned FileInfo // describes the symbolic link. Lstat makes no attempt to follow the link. // If there is an error, it will be of type *PathError. -func Lstat(name string) (fi FileInfo, err error) { +func Lstat(name string) (FileInfo, error) { return Stat(name) } diff --git a/libgo/go/os/sticky_bsd.go b/libgo/go/os/sticky_bsd.go new file mode 100644 index 0000000..6b54c75 --- /dev/null +++ b/libgo/go/os/sticky_bsd.go @@ -0,0 +1,11 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd solaris + +package os + +// According to sticky(8), neither open(2) nor mkdir(2) will create +// a file with the sticky bit set. +const supportsCreateWithStickyBit = false diff --git a/libgo/go/os/sticky_notbsd.go b/libgo/go/os/sticky_notbsd.go new file mode 100644 index 0000000..834e79b --- /dev/null +++ b/libgo/go/os/sticky_notbsd.go @@ -0,0 +1,14 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !darwin +// +build !dragonfly +// +build !freebsd +// +build !netbsd +// +build !openbsd +// +build !solaris + +package os + +const supportsCreateWithStickyBit = true diff --git a/libgo/go/os/str.go b/libgo/go/os/str.go index e3606b6..d3e03e9 100644 --- a/libgo/go/os/str.go +++ b/libgo/go/os/str.go @@ -2,21 +2,32 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build plan9 +// Simple converions to avoid depending on strconv. package os -func itoa(val int) string { // do it here rather than with fmt to avoid dependency +// Convert integer to decimal string +func itoa(val int) string { if val < 0 { - return "-" + itoa(-val) + return "-" + uitoa(uint(-val)) } - var buf [32]byte // big enough for int64 + return uitoa(uint(val)) +} + +// Convert unsigned integer to decimal string +func uitoa(val uint) string { + if val == 0 { // avoid string allocation + return "0" + } + var buf [20]byte // big enough for 64bit value base 10 i := len(buf) - 1 for val >= 10 { - buf[i] = byte(val%10 + '0') + q := val / 10 + buf[i] = byte('0' + val - q*10) i-- - val /= 10 + val = q } - buf[i] = byte(val + '0') + // val < 10 + buf[i] = byte('0' + val) return string(buf[i:]) } diff --git a/libgo/go/os/types.go b/libgo/go/os/types.go index 473d431..9d6f8e1 100644 --- a/libgo/go/os/types.go +++ b/libgo/go/os/types.go @@ -53,7 +53,7 @@ const ( // Mask for the type bits. For regular files, none will be set. ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice - ModePerm FileMode = 0777 // permission bits + ModePerm FileMode = 0777 // Unix permission bits ) func (m FileMode) String() string { diff --git a/libgo/go/os/user/lookup_unix.go b/libgo/go/os/user/lookup_unix.go index 64f4576..bebb9e8 100644 --- a/libgo/go/os/user/lookup_unix.go +++ b/libgo/go/os/user/lookup_unix.go @@ -16,6 +16,7 @@ import ( ) /* +#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS #include <unistd.h> #include <sys/types.h> #include <pwd.h> @@ -23,7 +24,12 @@ import ( static int mygetpwuid_r(int uid, struct passwd *pwd, char *buf, size_t buflen, struct passwd **result) { - return getpwuid_r(uid, pwd, buf, buflen, result); + return getpwuid_r(uid, pwd, buf, buflen, result); +} + +static int mygetpwnam_r(const char *name, struct passwd *pwd, + char *buf, size_t buflen, struct passwd **result) { + return getpwnam_r(name, pwd, buf, buflen, result); } */ @@ -62,9 +68,9 @@ func lookupUnix(uid int, username string, lookupByName bool) (*User, error) { const bufSize = 1024 buf := make([]byte, bufSize) if lookupByName { - p := syscall.StringBytePtr(username) + nameC := syscall.StringBytePtr(username) syscall.Entersyscall() - rv := libc_getpwnam_r(p, + rv := libc_getpwnam_r(nameC, &pwd, &buf[0], bufSize, |