aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/os
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2017-09-14 17:11:35 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2017-09-14 17:11:35 +0000
commitbc998d034f45d1828a8663b2eed928faf22a7d01 (patch)
tree8d262a22ca7318f4bcd64269fe8fe9e45bcf8d0f /libgo/go/os
parenta41a6142df74219f596e612d3a7775f68ca6e96f (diff)
downloadgcc-bc998d034f45d1828a8663b2eed928faf22a7d01.zip
gcc-bc998d034f45d1828a8663b2eed928faf22a7d01.tar.gz
gcc-bc998d034f45d1828a8663b2eed928faf22a7d01.tar.bz2
libgo: update to go1.9
Reviewed-on: https://go-review.googlesource.com/63753 From-SVN: r252767
Diffstat (limited to 'libgo/go/os')
-rw-r--r--libgo/go/os/error_posix.go18
-rw-r--r--libgo/go/os/example_test.go16
-rw-r--r--libgo/go/os/exec/env_test.go39
-rw-r--r--libgo/go/os/exec/exec.go67
-rw-r--r--libgo/go/os/exec/exec_posix_test.go83
-rw-r--r--libgo/go/os/exec/exec_test.go156
-rw-r--r--libgo/go/os/exec/exec_unix.go (renamed from libgo/go/os/exec/exec_posix.go)2
-rw-r--r--libgo/go/os/exec/exec_windows.go23
-rw-r--r--libgo/go/os/exec_windows.go80
-rw-r--r--libgo/go/os/executable.go3
-rw-r--r--libgo/go/os/executable_path.go40
-rw-r--r--libgo/go/os/executable_procfs.go4
-rw-r--r--libgo/go/os/executable_test.go9
-rw-r--r--libgo/go/os/export_windows_test.go6
-rw-r--r--libgo/go/os/file.go99
-rw-r--r--libgo/go/os/file_plan9.go54
-rw-r--r--libgo/go/os/file_posix.go60
-rw-r--r--libgo/go/os/file_unix.go155
-rw-r--r--libgo/go/os/os_test.go379
-rw-r--r--libgo/go/os/pipe_bsd.go4
-rw-r--r--libgo/go/os/pipe_freebsd.go34
-rw-r--r--libgo/go/os/pipe_linux.go2
-rw-r--r--libgo/go/os/pipe_test.go111
-rw-r--r--libgo/go/os/proc.go11
-rw-r--r--libgo/go/os/signal/doc.go9
-rw-r--r--libgo/go/os/signal/signal.go59
-rw-r--r--libgo/go/os/signal/signal_test.go91
-rw-r--r--libgo/go/os/stat_unix.go2
-rw-r--r--libgo/go/os/sys_darwin.go26
-rw-r--r--libgo/go/os/types.go2
-rw-r--r--libgo/go/os/types_unix.go2
-rw-r--r--libgo/go/os/types_windows.go36
-rw-r--r--libgo/go/os/user/cgo_lookup_unix.go266
-rw-r--r--libgo/go/os/user/lookup.go22
-rw-r--r--libgo/go/os/user/lookup_android.go13
-rw-r--r--libgo/go/os/user/lookup_stubs.go38
-rw-r--r--libgo/go/os/user/lookup_unix.go361
-rw-r--r--libgo/go/os/user/lookup_unix_test.go276
-rw-r--r--libgo/go/os/user/user_test.go12
-rw-r--r--libgo/go/os/wait_unimp.go2
-rw-r--r--libgo/go/os/wait_waitid.go5
41 files changed, 2127 insertions, 550 deletions
diff --git a/libgo/go/os/error_posix.go b/libgo/go/os/error_posix.go
new file mode 100644
index 0000000..2049e44
--- /dev/null
+++ b/libgo/go/os/error_posix.go
@@ -0,0 +1,18 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+
+package os
+
+import "syscall"
+
+// wrapSyscallError takes an error and a syscall name. If the error is
+// a syscall.Errno, it wraps it in a os.SyscallError using the syscall name.
+func wrapSyscallError(name string, err error) error {
+ if _, ok := err.(syscall.Errno); ok {
+ err = NewSyscallError(name, err)
+ }
+ return err
+}
diff --git a/libgo/go/os/example_test.go b/libgo/go/os/example_test.go
index 07f9c76..5749194 100644
--- a/libgo/go/os/example_test.go
+++ b/libgo/go/os/example_test.go
@@ -21,6 +21,20 @@ func ExampleOpenFile() {
}
}
+func ExampleOpenFile_append() {
+ // If the file doesn't exist, create it, or append to the file
+ f, err := os.OpenFile("access.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if _, err := f.Write([]byte("appended some data\n")); err != nil {
+ log.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ log.Fatal(err)
+ }
+}
+
func ExampleChmod() {
if err := os.Chmod("some-filename", 0644); err != nil {
log.Fatal(err)
@@ -36,7 +50,7 @@ func ExampleChtimes() {
}
func ExampleFileMode() {
- fi, err := os.Stat("some-filename")
+ fi, err := os.Lstat("some-filename")
if err != nil {
log.Fatal(err)
}
diff --git a/libgo/go/os/exec/env_test.go b/libgo/go/os/exec/env_test.go
new file mode 100644
index 0000000..b5ac398
--- /dev/null
+++ b/libgo/go/os/exec/env_test.go
@@ -0,0 +1,39 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestDedupEnv(t *testing.T) {
+ tests := []struct {
+ noCase bool
+ in []string
+ want []string
+ }{
+ {
+ noCase: true,
+ in: []string{"k1=v1", "k2=v2", "K1=v3"},
+ want: []string{"K1=v3", "k2=v2"},
+ },
+ {
+ noCase: false,
+ in: []string{"k1=v1", "K1=V2", "k1=v3"},
+ want: []string{"k1=v3", "K1=V2"},
+ },
+ {
+ in: []string{"=a", "=b", "foo", "bar"},
+ want: []string{"=b", "foo", "bar"},
+ },
+ }
+ for _, tt := range tests {
+ got := dedupEnvCase(tt.noCase, tt.in)
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("Dedup(%v, %q) = %q; want %q", tt.noCase, tt.in, got, tt.want)
+ }
+ }
+}
diff --git a/libgo/go/os/exec/exec.go b/libgo/go/os/exec/exec.go
index c4c5168..893d8ee 100644
--- a/libgo/go/os/exec/exec.go
+++ b/libgo/go/os/exec/exec.go
@@ -6,6 +6,15 @@
// easier to remap stdin and stdout, connect I/O with pipes, and do other
// adjustments.
//
+// Unlike the "system" library call from C and other languages, the
+// os/exec package intentionally does not invoke the system shell and
+// does not expand any glob patterns or handle other expansions,
+// pipelines, or redirections typically done by shells. The package
+// behaves more like C's "exec" family of functions. To expand glob
+// patterns, either call the shell directly, taking care to escape any
+// dangerous input, or use the path/filepath package's Glob function.
+// To expand environment variables, use package os's ExpandEnv.
+//
// Note that the examples in this package assume a Unix system.
// They may not run on Windows, and they do not run in the Go Playground
// used by golang.org and godoc.org.
@@ -55,7 +64,11 @@ type Cmd struct {
Args []string
// Env specifies the environment of the process.
- // If Env is nil, Run uses the current process's environment.
+ // Each entry is of the form "key=value".
+ // If Env is nil, the new process uses the current process's
+ // environment.
+ // If Env contains duplicate environment keys, only the last
+ // value in the slice for each duplicate key is used.
Env []string
// Dir specifies the working directory of the command.
@@ -79,17 +92,14 @@ 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, at most one
- // goroutine at a time will call Write.
+ // If Stdout and Stderr are the same writer, and have a type that can be compared with ==,
+ // at most one goroutine at a time will call Write.
Stdout io.Writer
Stderr io.Writer
// ExtraFiles specifies additional open files to be inherited by the
// new process. It does not include standard input, standard output, or
// standard error. If non-nil, entry i becomes file descriptor 3+i.
- //
- // 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.
@@ -270,9 +280,8 @@ func (c *Cmd) closeDescriptors(closers []io.Closer) {
// copying stdin, stdout, and stderr, and exits with a zero exit
// status.
//
-// If the command fails to run or doesn't complete successfully, the
-// error is of type *ExitError. Other error types may be
-// returned for I/O problems.
+// If the command starts but does not complete successfully, the error is of
+// type *ExitError. Other error types may be returned for other situations.
func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
@@ -354,7 +363,7 @@ func (c *Cmd) Start() error {
c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
Dir: c.Dir,
Files: c.childFiles,
- Env: c.envv(),
+ Env: dedupEnv(c.envv()),
Sys: c.SysProcAttr,
})
if err != nil {
@@ -407,8 +416,10 @@ func (e *ExitError) Error() string {
return e.ProcessState.String()
}
-// Wait waits for the command to exit.
-// It must have been started by Start.
+// Wait waits for the command to exit and waits for any copying to
+// stdin or copying from stdout or stderr to complete.
+//
+// The command must have been started by Start.
//
// The returned error is nil if the command runs, has no problems
// copying stdin, stdout, and stderr, and exits with a zero exit
@@ -712,3 +723,35 @@ func minInt(a, b int) int {
}
return b
}
+
+// dedupEnv returns a copy of env with any duplicates removed, in favor of
+// later values.
+// Items not of the normal environment "key=value" form are preserved unchanged.
+func dedupEnv(env []string) []string {
+ return dedupEnvCase(runtime.GOOS == "windows", env)
+}
+
+// dedupEnvCase is dedupEnv with a case option for testing.
+// If caseInsensitive is true, the case of keys is ignored.
+func dedupEnvCase(caseInsensitive bool, env []string) []string {
+ out := make([]string, 0, len(env))
+ saw := map[string]int{} // key => index into out
+ for _, kv := range env {
+ eq := strings.Index(kv, "=")
+ if eq < 0 {
+ out = append(out, kv)
+ continue
+ }
+ k := kv[:eq]
+ if caseInsensitive {
+ k = strings.ToLower(k)
+ }
+ if dupIdx, isDup := saw[k]; isDup {
+ out[dupIdx] = kv
+ continue
+ }
+ saw[k] = len(out)
+ out = append(out, kv)
+ }
+ return out
+}
diff --git a/libgo/go/os/exec/exec_posix_test.go b/libgo/go/os/exec/exec_posix_test.go
new file mode 100644
index 0000000..865b6c3
--- /dev/null
+++ b/libgo/go/os/exec/exec_posix_test.go
@@ -0,0 +1,83 @@
+// 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 netbsd openbsd solaris
+
+package exec_test
+
+import (
+ "os/user"
+ "strconv"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func TestCredentialNoSetGroups(t *testing.T) {
+ u, err := user.Current()
+ if err != nil {
+ t.Fatalf("error getting current user: %v", err)
+ }
+
+ uid, err := strconv.Atoi(u.Uid)
+ if err != nil {
+ t.Fatalf("error converting Uid=%s to integer: %v", u.Uid, err)
+ }
+
+ gid, err := strconv.Atoi(u.Gid)
+ if err != nil {
+ t.Fatalf("error converting Gid=%s to integer: %v", u.Gid, err)
+ }
+
+ // If NoSetGroups is true, setgroups isn't called and cmd.Run should succeed
+ cmd := helperCommand(t, "echo", "foo")
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ Credential: &syscall.Credential{
+ Uid: uint32(uid),
+ Gid: uint32(gid),
+ NoSetGroups: true,
+ },
+ }
+
+ if err = cmd.Run(); err != nil {
+ t.Errorf("Failed to run command: %v", err)
+ }
+}
+
+// For issue #19314: make sure that SIGSTOP does not cause the process
+// to appear done.
+func TestWaitid(t *testing.T) {
+ t.Parallel()
+
+ cmd := helperCommand(t, "sleep")
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ // The sleeps here are unnecessary in the sense that the test
+ // should still pass, but they are useful to make it more
+ // likely that we are testing the expected state of the child.
+ time.Sleep(100 * time.Millisecond)
+
+ if err := cmd.Process.Signal(syscall.SIGSTOP); err != nil {
+ cmd.Process.Kill()
+ t.Fatal(err)
+ }
+
+ ch := make(chan error)
+ go func() {
+ ch <- cmd.Wait()
+ }()
+
+ time.Sleep(100 * time.Millisecond)
+
+ if err := cmd.Process.Signal(syscall.SIGCONT); err != nil {
+ t.Error(err)
+ syscall.Kill(cmd.Process.Pid, syscall.SIGCONT)
+ }
+
+ cmd.Process.Kill()
+
+ <-ch
+}
diff --git a/libgo/go/os/exec/exec_test.go b/libgo/go/os/exec/exec_test.go
index f136351..a877d8a 100644
--- a/libgo/go/os/exec/exec_test.go
+++ b/libgo/go/os/exec/exec_test.go
@@ -12,6 +12,7 @@ import (
"bytes"
"context"
"fmt"
+ "internal/poll"
"internal/testenv"
"io"
"io/ioutil"
@@ -292,8 +293,51 @@ func TestStdinCloseRace(t *testing.T) {
// Issue 5071
func TestPipeLookPathLeak(t *testing.T) {
- fd0, lsof0 := numOpenFDS(t)
- for i := 0; i < 4; i++ {
+ // If we are reading from /proc/self/fd we (should) get an exact result.
+ tolerance := 0
+
+ // Reading /proc/self/fd is more reliable than calling lsof, so try that
+ // first.
+ numOpenFDs := func() (int, []byte, error) {
+ fds, err := ioutil.ReadDir("/proc/self/fd")
+ if err != nil {
+ return 0, nil, err
+ }
+ return len(fds), nil, nil
+ }
+ want, before, err := numOpenFDs()
+ if err != nil {
+ // We encountered a problem reading /proc/self/fd (we might be on
+ // a platform that doesn't have it). Fall back onto lsof.
+ t.Logf("using lsof because: %v", err)
+ numOpenFDs = func() (int, []byte, error) {
+ // Android's stock lsof does not obey the -p option,
+ // so extra filtering is needed.
+ // https://golang.org/issue/10206
+ if runtime.GOOS == "android" {
+ // numOpenFDsAndroid handles errors itself and
+ // might skip or fail the test.
+ n, lsof := numOpenFDsAndroid(t)
+ return n, lsof, nil
+ }
+ lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
+ return bytes.Count(lsof, []byte("\n")), lsof, err
+ }
+
+ // lsof may see file descriptors associated with the fork itself,
+ // so we allow some extra margin if we have to use it.
+ // https://golang.org/issue/19243
+ tolerance = 5
+
+ // Retry reading the number of open file descriptors.
+ want, before, err = numOpenFDs()
+ if err != nil {
+ t.Log(err)
+ t.Skipf("skipping test; error finding or running lsof")
+ }
+ }
+
+ for i := 0; i < 6; i++ {
cmd := exec.Command("something-that-does-not-exist-binary")
cmd.StdoutPipe()
cmd.StderrPipe()
@@ -302,36 +346,20 @@ func TestPipeLookPathLeak(t *testing.T) {
t.Fatal("unexpected success")
}
}
- for triesLeft := 3; triesLeft >= 0; triesLeft-- {
- open, lsof := numOpenFDS(t)
- fdGrowth := open - fd0
- if fdGrowth > 2 {
- if triesLeft > 0 {
- // Work around what appears to be a race with Linux's
- // proc filesystem (as used by lsof). It seems to only
- // be eventually consistent. Give it awhile to settle.
- // See golang.org/issue/7808
- time.Sleep(100 * time.Millisecond)
- continue
- }
- t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0)
- }
- break
- }
-}
-
-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()
+ got, after, err := numOpenFDs()
if err != nil {
- t.Skip("skipping test; error finding or running lsof")
+ // numOpenFDs has already succeeded once, it should work here.
+ t.Errorf("unexpected failure: %v", err)
+ }
+ if got-want > tolerance {
+ t.Errorf("number of open file descriptors changed: got %v, want %v", got, want)
+ if before != nil {
+ t.Errorf("before:\n%v\n", before)
+ }
+ if after != nil {
+ t.Errorf("after:\n%v\n", after)
+ }
}
- return bytes.Count(lsof, []byte("\n")), lsof
}
func numOpenFDsAndroid(t *testing.T) (n int, lsof []byte) {
@@ -377,12 +405,16 @@ var testedAlreadyLeaked = false
// basefds returns the number of expected file descriptors
// to be present in a process at start.
+// stdin, stdout, stderr, epoll/kqueue
func basefds() uintptr {
return os.Stderr.Fd() + 1
}
func closeUnexpectedFds(t *testing.T, m string) {
for fd := basefds(); fd <= 101; fd++ {
+ if fd == poll.PollDescriptor() {
+ continue
+ }
err := os.NewFile(fd, "").Close()
if err == nil {
t.Logf("%s: Something already leaked - closed fd %d", m, fd)
@@ -665,6 +697,11 @@ func TestHelperProcess(*testing.T) {
iargs = append(iargs, s)
}
fmt.Println(iargs...)
+ case "echoenv":
+ for _, s := range args {
+ fmt.Println(os.Getenv(s))
+ }
+ os.Exit(0)
case "cat":
if len(args) == 0 {
io.Copy(os.Stdout, os.Stdin)
@@ -740,6 +777,9 @@ func TestHelperProcess(*testing.T) {
// Now verify that there are no other open fds.
var files []*os.File
for wantfd := basefds() + 1; wantfd <= 100; wantfd++ {
+ if wantfd == poll.PollDescriptor() {
+ continue
+ }
f, err := os.Open(os.Args[0])
if err != nil {
fmt.Printf("error opening file with expected fd %d: %v", wantfd, err)
@@ -832,31 +872,50 @@ func TestHelperProcess(*testing.T) {
case "stderrfail":
fmt.Fprintf(os.Stderr, "some stderr text\n")
os.Exit(1)
+ case "sleep":
+ time.Sleep(3 * time.Second)
+ os.Exit(0)
default:
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
os.Exit(2)
}
}
+type delayedInfiniteReader struct{}
+
+func (delayedInfiniteReader) Read(b []byte) (int, error) {
+ time.Sleep(100 * time.Millisecond)
+ for i := range b {
+ b[i] = 'x'
+ }
+ return len(b), nil
+}
+
// Issue 9173: ignore stdin pipe writes if the program completes successfully.
func TestIgnorePipeErrorOnSuccess(t *testing.T) {
testenv.MustHaveExec(t)
- // We really only care about testing this on Unixy things.
- if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
+ // We really only care about testing this on Unixy and Windowsy things.
+ if 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)
+ testWith := func(r io.Reader) func(*testing.T) {
+ return func(t *testing.T) {
+ cmd := helperCommand(t, "echo", "foo")
+ var out bytes.Buffer
+ cmd.Stdin = r
+ cmd.Stdout = &out
+ if err := cmd.Run(); err != nil {
+ t.Fatal(err)
+ }
+ if got, want := out.String(), "foo\n"; got != want {
+ t.Errorf("output = %q; want %q", got, want)
+ }
+ }
}
+ t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20))))
+ t.Run("Infinite", testWith(delayedInfiniteReader{}))
}
type badWriter struct{}
@@ -1012,3 +1071,18 @@ func TestContextCancel(t *testing.T) {
t.Logf("exit status: %v", err)
}
}
+
+// test that environment variables are de-duped.
+func TestDedupEnvEcho(t *testing.T) {
+ testenv.MustHaveExec(t)
+
+ cmd := helperCommand(t, "echoenv", "FOO")
+ cmd.Env = append(cmd.Env, "FOO=bad", "FOO=good")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got, want := strings.TrimSpace(string(out)), "good"; got != want {
+ t.Errorf("output = %q; want %q", got, want)
+ }
+}
diff --git a/libgo/go/os/exec/exec_posix.go b/libgo/go/os/exec/exec_unix.go
index 5e11137..9c3e17d 100644
--- a/libgo/go/os/exec/exec_posix.go
+++ b/libgo/go/os/exec/exec_unix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build !plan9
+// +build !plan9,!windows
package exec
diff --git a/libgo/go/os/exec/exec_windows.go b/libgo/go/os/exec/exec_windows.go
new file mode 100644
index 0000000..af8cd97
--- /dev/null
+++ b/libgo/go/os/exec/exec_windows.go
@@ -0,0 +1,23 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "os"
+ "syscall"
+)
+
+func init() {
+ skipStdinCopyError = func(err error) bool {
+ // Ignore ERROR_BROKEN_PIPE and ERROR_NO_DATA errors copying
+ // to stdin if the program completed successfully otherwise.
+ // See Issue 20445.
+ const _ERROR_NO_DATA = syscall.Errno(0xe8)
+ pe, ok := err.(*os.PathError)
+ return ok &&
+ pe.Op == "write" && pe.Path == "|1" &&
+ (pe.Err == syscall.ERROR_BROKEN_PIPE || pe.Err == _ERROR_NO_DATA)
+ }
+}
diff --git a/libgo/go/os/exec_windows.go b/libgo/go/os/exec_windows.go
index d89db20..d5d553a 100644
--- a/libgo/go/os/exec_windows.go
+++ b/libgo/go/os/exec_windows.go
@@ -97,17 +97,79 @@ func findProcess(pid int) (p *Process, err error) {
}
func init() {
- var argc int32
- cmd := syscall.GetCommandLine()
- argv, e := syscall.CommandLineToArgv(cmd, &argc)
- if e != nil {
- return
+ p := syscall.GetCommandLine()
+ cmd := syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(p))[:])
+ if len(cmd) == 0 {
+ arg0, _ := Executable()
+ Args = []string{arg0}
+ } else {
+ Args = commandLineToArgv(cmd)
}
- defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv))))
- Args = make([]string, argc)
- for i, v := range (*argv)[:argc] {
- Args[i] = syscall.UTF16ToString((*v)[:])
+}
+
+// appendBSBytes appends n '\\' bytes to b and returns the resulting slice.
+func appendBSBytes(b []byte, n int) []byte {
+ for ; n > 0; n-- {
+ b = append(b, '\\')
+ }
+ return b
+}
+
+// readNextArg splits command line string cmd into next
+// argument and command line remainder.
+func readNextArg(cmd string) (arg []byte, rest string) {
+ var b []byte
+ var inquote bool
+ var nslash int
+ for ; len(cmd) > 0; cmd = cmd[1:] {
+ c := cmd[0]
+ switch c {
+ case ' ', '\t':
+ if !inquote {
+ return appendBSBytes(b, nslash), cmd[1:]
+ }
+ case '"':
+ b = appendBSBytes(b, nslash/2)
+ if nslash%2 == 0 {
+ // use "Prior to 2008" rule from
+ // http://daviddeley.com/autohotkey/parameters/parameters.htm
+ // section 5.2 to deal with double double quotes
+ if inquote && len(cmd) > 1 && cmd[1] == '"' {
+ b = append(b, c)
+ cmd = cmd[1:]
+ }
+ inquote = !inquote
+ } else {
+ b = append(b, c)
+ }
+ nslash = 0
+ continue
+ case '\\':
+ nslash++
+ continue
+ }
+ b = appendBSBytes(b, nslash)
+ nslash = 0
+ b = append(b, c)
+ }
+ return appendBSBytes(b, nslash), ""
+}
+
+// commandLineToArgv splits a command line into individual argument
+// strings, following the Windows conventions documented
+// at http://daviddeley.com/autohotkey/parameters/parameters.htm#WINARGV
+func commandLineToArgv(cmd string) []string {
+ var args []string
+ for len(cmd) > 0 {
+ if cmd[0] == ' ' || cmd[0] == '\t' {
+ cmd = cmd[1:]
+ continue
+ }
+ var arg []byte
+ arg, cmd = readNextArg(cmd)
+ args = append(args, string(arg))
}
+ return args
}
func ftToDuration(ft *syscall.Filetime) time.Duration {
diff --git a/libgo/go/os/executable.go b/libgo/go/os/executable.go
index 8c21246..17eed10bc 100644
--- a/libgo/go/os/executable.go
+++ b/libgo/go/os/executable.go
@@ -16,8 +16,7 @@ package os
// The main use case is finding resources located relative to an
// executable.
//
-// Executable is not supported on nacl or OpenBSD (unless procfs is
-// mounted.)
+// Executable is not supported on nacl.
func Executable() (string, error) {
return executable()
}
diff --git a/libgo/go/os/executable_path.go b/libgo/go/os/executable_path.go
index 117320d..7b8b836 100644
--- a/libgo/go/os/executable_path.go
+++ b/libgo/go/os/executable_path.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix
+// +build aix openbsd
package os
@@ -12,18 +12,19 @@ package os
var initWd, errWd = Getwd()
func executable() (string, error) {
- var err error
var exePath string
if len(Args) == 0 || Args[0] == "" {
return "", ErrNotExist
}
- // Args[0] is an absolute path : this is the executable
if IsPathSeparator(Args[0][0]) {
+ // Args[0] is an absolute path, so it is the executable.
+ // Note that we only need to worry about Unix paths here.
exePath = Args[0]
} else {
for i := 1; i < len(Args[0]); i++ {
- // Args[0] is a relative path : append current directory
if IsPathSeparator(Args[0][i]) {
+ // Args[0] is a relative path: prepend the
+ // initial working directory.
if errWd != nil {
return "", errWd
}
@@ -33,18 +34,15 @@ func executable() (string, error) {
}
}
if exePath != "" {
- err = isExecutable(exePath)
- if err == nil {
- return exePath, nil
+ if err := isExecutable(exePath); err != nil {
+ return "", err
}
- // File does not exist or is not executable,
- // this is an unexpected situation !
- return "", err
+ return exePath, nil
}
- // Search for executable in $PATH
+ // Search for executable in $PATH.
for _, dir := range splitPathList(Getenv("PATH")) {
if len(dir) == 0 {
- continue
+ dir = "."
}
if !IsPathSeparator(dir[0]) {
if errWd != nil {
@@ -53,12 +51,11 @@ func executable() (string, error) {
dir = initWd + string(PathSeparator) + dir
}
exePath = dir + string(PathSeparator) + Args[0]
- err = isExecutable(exePath)
- if err == nil {
+ switch isExecutable(exePath) {
+ case nil:
return exePath, nil
- }
- if err == ErrPermission {
- return "", err
+ case ErrPermission:
+ return "", ErrPermission
}
}
return "", ErrNotExist
@@ -74,15 +71,18 @@ func isExecutable(path string) error {
if !mode.IsRegular() {
return ErrPermission
}
- if (mode & 0111) != 0 {
- return nil
+ if (mode & 0111) == 0 {
+ return ErrPermission
}
- return ErrPermission
+ return nil
}
// splitPathList splits a path list.
// This is based on genSplit from strings/strings.go
func splitPathList(pathList string) []string {
+ if pathList == "" {
+ return nil
+ }
n := 1
for i := 0; i < len(pathList); i++ {
if pathList[i] == PathListSeparator {
diff --git a/libgo/go/os/executable_procfs.go b/libgo/go/os/executable_procfs.go
index 69a70e1..b5fae59 100644
--- a/libgo/go/os/executable_procfs.go
+++ b/libgo/go/os/executable_procfs.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build linux netbsd openbsd dragonfly nacl
+// +build linux netbsd dragonfly nacl
package os
@@ -23,8 +23,6 @@ var executablePath, executablePathErr = func() (string, error) {
procfn = "/proc/self/exe"
case "netbsd":
procfn = "/proc/curproc/exe"
- case "openbsd":
- procfn = "/proc/curproc/file"
case "dragonfly":
procfn = "/proc/curproc/file"
}
diff --git a/libgo/go/os/executable_test.go b/libgo/go/os/executable_test.go
index a4d8909..7800844 100644
--- a/libgo/go/os/executable_test.go
+++ b/libgo/go/os/executable_test.go
@@ -20,10 +20,6 @@ func TestExecutable(t *testing.T) {
testenv.MustHaveExec(t) // will also execlude nacl, which doesn't support Executable anyway
ep, err := os.Executable()
if err != nil {
- switch goos := runtime.GOOS; goos {
- case "openbsd": // procfs is not mounted by default
- t.Skipf("Executable failed on %s: %v, expected", goos, err)
- }
t.Fatalf("Executable failed: %v", err)
}
// we want fn to be of the form "dir/prog"
@@ -32,6 +28,7 @@ func TestExecutable(t *testing.T) {
if err != nil {
t.Fatalf("filepath.Rel: %v", err)
}
+
cmd := &osexec.Cmd{}
// make child start with a relative program path
cmd.Dir = dir
@@ -39,6 +36,10 @@ func TestExecutable(t *testing.T) {
// forge argv[0] for child, so that we can verify we could correctly
// get real path of the executable without influenced by argv[0].
cmd.Args = []string{"-", "-test.run=XXXX"}
+ if runtime.GOOS == "openbsd" {
+ // OpenBSD relies on argv[0]
+ cmd.Args[0] = fn
+ }
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=1", executable_EnvVar))
out, err := cmd.CombinedOutput()
if err != nil {
diff --git a/libgo/go/os/export_windows_test.go b/libgo/go/os/export_windows_test.go
index 3bb2d20..f36fadb 100644
--- a/libgo/go/os/export_windows_test.go
+++ b/libgo/go/os/export_windows_test.go
@@ -7,7 +7,7 @@ package os
// Export for testing.
var (
- FixLongPath = fixLongPath
- NewConsoleFile = newConsoleFile
- ReadConsoleFunc = &readConsole
+ FixLongPath = fixLongPath
+ NewConsoleFile = newConsoleFile
+ CommandLineToArgv = commandLineToArgv
)
diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go
index d45a00b..4b4d8fb 100644
--- a/libgo/go/os/file.go
+++ b/libgo/go/os/file.go
@@ -37,6 +37,8 @@
package os
import (
+ "errors"
+ "internal/poll"
"io"
"syscall"
)
@@ -99,13 +101,7 @@ func (f *File) Read(b []byte) (n int, err error) {
return 0, err
}
n, e := f.read(b)
- if n == 0 && len(b) > 0 && e == nil {
- return 0, io.EOF
- }
- if e != nil {
- err = &PathError{"read", f.name, e}
- }
- return n, err
+ return n, f.wrapErr("read", e)
}
// ReadAt reads len(b) bytes from the File starting at byte offset off.
@@ -116,13 +112,15 @@ func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
if err := f.checkValid("read"); err != nil {
return 0, err
}
+
+ if off < 0 {
+ return 0, &PathError{"readat", f.name, errors.New("negative offset")}
+ }
+
for len(b) > 0 {
m, e := f.pread(b, off)
- if m == 0 && e == nil {
- return n, io.EOF
- }
if e != nil {
- err = &PathError{"read", f.name, e}
+ err = f.wrapErr("read", e)
break
}
n += m
@@ -150,8 +148,9 @@ func (f *File) Write(b []byte) (n int, err error) {
epipecheck(f, e)
if e != nil {
- err = &PathError{"write", f.name, e}
+ err = f.wrapErr("write", e)
}
+
return n, err
}
@@ -162,10 +161,15 @@ func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
if err := f.checkValid("write"); err != nil {
return 0, err
}
+
+ if off < 0 {
+ return 0, &PathError{"writeat", f.name, errors.New("negative offset")}
+ }
+
for len(b) > 0 {
m, e := f.pwrite(b, off)
if e != nil {
- err = &PathError{"write", f.name, e}
+ err = f.wrapErr("write", e)
break
}
n += m
@@ -189,7 +193,7 @@ func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
e = syscall.EISDIR
}
if e != nil {
- return 0, &PathError{"seek", f.name, e}
+ return 0, f.wrapErr("seek", e)
}
return r, nil
}
@@ -226,19 +230,6 @@ func Chdir(dir string) error {
return nil
}
-// Chdir changes the current working directory to the file,
-// which must be a directory.
-// If there is an error, it will be of type *PathError.
-func (f *File) Chdir() error {
- if err := f.checkValid("chdir"); err != nil {
- return err
- }
- if e := syscall.Fchdir(f.fd); e != nil {
- return &PathError{"chdir", f.name, e}
- }
- return nil
-}
-
// Open opens the named file for reading. If successful, methods on
// the returned file can be used for reading; the associated file
// descriptor has mode O_RDONLY.
@@ -276,14 +267,52 @@ func fixCount(n int, err error) (int, error) {
return n, err
}
-// checkValid checks whether f is valid for use.
-// If not, it returns an appropriate error, perhaps incorporating the operation name op.
-func (f *File) checkValid(op string) error {
- if f == nil {
- return ErrInvalid
+// wrapErr wraps an error that occurred during an operation on an open file.
+// It passes io.EOF through unchanged, otherwise converts
+// poll.ErrFileClosing to ErrClosed and wraps the error in a PathError.
+func (f *File) wrapErr(op string, err error) error {
+ if err == nil || err == io.EOF {
+ return err
}
- if f.fd == badFd {
- return &PathError{op, f.name, ErrClosed}
+ if err == poll.ErrFileClosing {
+ err = ErrClosed
}
- return nil
+ return &PathError{op, f.name, err}
+}
+
+// TempDir returns the default directory to use for temporary files.
+//
+// On Unix systems, it returns $TMPDIR if non-empty, else /tmp.
+// On Windows, it uses GetTempPath, returning the first non-empty
+// value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory.
+// On Plan 9, it returns /tmp.
+//
+// The directory is neither guaranteed to exist nor have accessible
+// permissions.
+func TempDir() string {
+ return tempDir()
}
+
+// Chmod changes the mode of the named file to mode.
+// If the file is a symbolic link, it changes the mode of the link's target.
+// If there is an error, it will be of type *PathError.
+//
+// A different subset of the mode bits are used, depending on the
+// operating system.
+//
+// On Unix, the mode's permission bits, ModeSetuid, ModeSetgid, and
+// ModeSticky are used.
+//
+// On Windows, the mode must be non-zero but otherwise only the 0200
+// bit (owner writable) of mode is used; it controls whether the
+// file's read-only attribute is set or cleared. attribute. The other
+// bits are currently unused. Use mode 0400 for a read-only file and
+// 0600 for a readable+writable file.
+//
+// On Plan 9, the mode's permission bits, ModeAppend, ModeExclusive,
+// and ModeTemporary are used.
+func Chmod(name string, mode FileMode) error { return chmod(name, mode) }
+
+// Chmod changes the mode of the file to mode.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chmod(mode FileMode) error { return f.chmod(mode) }
diff --git a/libgo/go/os/file_plan9.go b/libgo/go/os/file_plan9.go
index 5276a7e..0f4a736 100644
--- a/libgo/go/os/file_plan9.go
+++ b/libgo/go/os/file_plan9.go
@@ -35,7 +35,9 @@ func (f *File) Fd() uintptr {
return uintptr(f.fd)
}
-// NewFile returns a new File with the given file descriptor and name.
+// NewFile returns a new File with the given file descriptor and
+// name. The returned value will be nil if fd is not a valid file
+// descriptor.
func NewFile(fd uintptr, name string) *File {
fdi := int(fd)
if fdi < 0 {
@@ -194,9 +196,7 @@ func (f *File) Truncate(size int64) error {
const chmodMask = uint32(syscall.DMAPPEND | syscall.DMEXCL | syscall.DMTMP | ModePerm)
-// 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 {
+func (f *File) chmod(mode FileMode) error {
if f == nil {
return ErrInvalid
}
@@ -244,14 +244,22 @@ func (f *File) Sync() error {
// read reads up to len(b) bytes from the File.
// It returns the number of bytes read and an error, if any.
func (f *File) read(b []byte) (n int, err error) {
- return fixCount(syscall.Read(f.fd, b))
+ n, e := fixCount(syscall.Read(f.fd, b))
+ if n == 0 && len(b) > 0 && e == nil {
+ return 0, io.EOF
+ }
+ return n, e
}
// pread reads len(b) bytes from the File starting at byte offset off.
// It returns the number of bytes read and the error, if any.
// EOF is signaled by a zero count with err set to nil.
func (f *File) pread(b []byte, off int64) (n int, err error) {
- return fixCount(syscall.Pread(f.fd, b, off))
+ n, e := fixCount(syscall.Pread(f.fd, b, off))
+ if n == 0 && len(b) > 0 && e == nil {
+ return 0, io.EOF
+ }
+ return n, e
}
// write writes len(b) bytes to the File.
@@ -365,10 +373,8 @@ func rename(oldname, newname string) error {
return nil
}
-// Chmod changes the mode of the named file to mode.
-// If the file is a symbolic link, it changes the mode of the link's target.
-// If there is an error, it will be of type *PathError.
-func Chmod(name string, mode FileMode) error {
+// See docs in file.go:Chmod.
+func chmod(name string, mode FileMode) error {
var d syscall.Dir
odir, e := dirstat(name)
@@ -468,7 +474,31 @@ func (f *File) Chown(uid, gid int) error {
return &PathError{"chown", f.name, syscall.EPLAN9}
}
-// TempDir returns the default directory to use for temporary files.
-func TempDir() string {
+func tempDir() string {
return "/tmp"
}
+
+// Chdir changes the current working directory to the file,
+// which must be a directory.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chdir() error {
+ if err := f.checkValid("chdir"); err != nil {
+ return err
+ }
+ if e := syscall.Fchdir(f.fd); e != nil {
+ return &PathError{"chdir", f.name, e}
+ }
+ return nil
+}
+
+// checkValid checks whether f is valid for use.
+// If not, it returns an appropriate error, perhaps incorporating the operation name op.
+func (f *File) checkValid(op string) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ if f.fd == badFd {
+ return &PathError{op, f.name, ErrClosed}
+ }
+ return nil
+}
diff --git a/libgo/go/os/file_posix.go b/libgo/go/os/file_posix.go
index 6634112..51cae9d 100644
--- a/libgo/go/os/file_posix.go
+++ b/libgo/go/os/file_posix.go
@@ -48,24 +48,21 @@ func syscallMode(i FileMode) (o uint32) {
return
}
-// Chmod changes the mode of the named file to mode.
-// If the file is a symbolic link, it changes the mode of the link's target.
-// If there is an error, it will be of type *PathError.
-func Chmod(name string, mode FileMode) error {
- if e := syscall.Chmod(name, syscallMode(mode)); e != nil {
+// See docs in file.go:Chmod.
+func chmod(name string, mode FileMode) error {
+ if e := syscall.Chmod(fixLongPath(name), syscallMode(mode)); e != nil {
return &PathError{"chmod", name, e}
}
return nil
}
-// 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 {
+// See docs in file.go:(*File).Chmod.
+func (f *File) chmod(mode FileMode) error {
if err := f.checkValid("chmod"); err != nil {
return err
}
- if e := syscall.Fchmod(f.fd, syscallMode(mode)); e != nil {
- return &PathError{"chmod", f.name, e}
+ if e := f.pfd.Fchmod(syscallMode(mode)); e != nil {
+ return f.wrapErr("chmod", e)
}
return nil
}
@@ -73,6 +70,9 @@ func (f *File) Chmod(mode FileMode) error {
// Chown changes the numeric uid and gid of the named file.
// If the file is a symbolic link, it changes the uid and gid of the link's target.
// If there is an error, it will be of type *PathError.
+//
+// On Windows, it always returns the syscall.EWINDOWS error, wrapped
+// in *PathError.
func Chown(name string, uid, gid int) error {
if e := syscall.Chown(name, uid, gid); e != nil {
return &PathError{"chown", name, e}
@@ -83,6 +83,9 @@ func Chown(name string, uid, gid int) error {
// Lchown changes the numeric uid and gid of the named file.
// If the file is a symbolic link, it changes the uid and gid of the link itself.
// If there is an error, it will be of type *PathError.
+//
+// On Windows, it always returns the syscall.EWINDOWS error, wrapped
+// in *PathError.
func Lchown(name string, uid, gid int) error {
if e := syscall.Lchown(name, uid, gid); e != nil {
return &PathError{"lchown", name, e}
@@ -92,12 +95,15 @@ func Lchown(name string, uid, gid int) error {
// Chown changes the numeric uid and gid of the named file.
// If there is an error, it will be of type *PathError.
+//
+// On Windows, it always returns the syscall.EWINDOWS error, wrapped
+// in *PathError.
func (f *File) Chown(uid, gid int) error {
if err := f.checkValid("chown"); err != nil {
return err
}
- if e := syscall.Fchown(f.fd, uid, gid); e != nil {
- return &PathError{"chown", f.name, e}
+ if e := f.pfd.Fchown(uid, gid); e != nil {
+ return f.wrapErr("chown", e)
}
return nil
}
@@ -109,8 +115,8 @@ func (f *File) Truncate(size int64) error {
if err := f.checkValid("truncate"); err != nil {
return err
}
- if e := syscall.Ftruncate(f.fd, size); e != nil {
- return &PathError{"truncate", f.name, e}
+ if e := f.pfd.Ftruncate(size); e != nil {
+ return f.wrapErr("truncate", e)
}
return nil
}
@@ -122,8 +128,8 @@ func (f *File) Sync() error {
if err := f.checkValid("sync"); err != nil {
return err
}
- if e := syscall.Fsync(f.fd); e != nil {
- return &PathError{"sync", f.name, e}
+ if e := f.pfd.Fsync(); e != nil {
+ return f.wrapErr("sync", e)
}
return nil
}
@@ -143,3 +149,25 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error {
}
return nil
}
+
+// Chdir changes the current working directory to the file,
+// which must be a directory.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chdir() error {
+ if err := f.checkValid("chdir"); err != nil {
+ return err
+ }
+ if e := f.pfd.Fchdir(); e != nil {
+ return f.wrapErr("chdir", e)
+ }
+ return nil
+}
+
+// checkValid checks whether f is valid for use.
+// If not, it returns an appropriate error, perhaps incorporating the operation name op.
+func (f *File) checkValid(op string) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ return nil
+}
diff --git a/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go
index 1bba4ed..8199994 100644
--- a/libgo/go/os/file_unix.go
+++ b/libgo/go/os/file_unix.go
@@ -7,6 +7,7 @@
package os
import (
+ "internal/poll"
"runtime"
"syscall"
)
@@ -19,11 +20,22 @@ func fixLongPath(path string) string {
func rename(oldname, newname string) error {
fi, err := Lstat(newname)
if err == nil && fi.IsDir() {
+ // There are two independent errors this function can return:
+ // one for a bad oldname, and one for a bad newname.
+ // At this point we've determined the newname is bad.
+ // But just in case oldname is also bad, prioritize returning
+ // the oldname error because that's what we did historically.
+ if _, err := Lstat(oldname); err != nil {
+ if pe, ok := err.(*PathError); ok {
+ err = pe.Err
+ }
+ return &LinkError{"rename", oldname, newname, err}
+ }
return &LinkError{"rename", oldname, newname, syscall.EEXIST}
}
- e := syscall.Rename(oldname, newname)
- if e != nil {
- return &LinkError{"rename", oldname, newname, e}
+ err = syscall.Rename(oldname, newname)
+ if err != nil {
+ return &LinkError{"rename", oldname, newname, err}
}
return nil
}
@@ -33,9 +45,10 @@ func rename(oldname, newname string) error {
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
- fd int
- name string
- dirinfo *dirInfo // nil unless directory being read
+ pfd poll.FD
+ name string
+ dirinfo *dirInfo // nil unless directory being read
+ nonblock bool // whether we set nonblocking mode
}
// Fd returns the integer Unix file descriptor referencing the open file.
@@ -44,16 +57,64 @@ func (f *File) Fd() uintptr {
if f == nil {
return ^(uintptr(0))
}
- return uintptr(f.fd)
+
+ // If we put the file descriptor into nonblocking mode,
+ // then set it to blocking mode before we return it,
+ // because historically we have always returned a descriptor
+ // opened in blocking mode. The File will continue to work,
+ // but any blocking operation will tie up a thread.
+ if f.nonblock {
+ syscall.SetNonblock(f.pfd.Sysfd, false)
+ }
+
+ return uintptr(f.pfd.Sysfd)
}
-// NewFile returns a new File with the given file descriptor and name.
+// NewFile returns a new File with the given file descriptor and
+// name. The returned value will be nil if fd is not a valid file
+// descriptor.
func NewFile(fd uintptr, name string) *File {
+ return newFile(fd, name, false)
+}
+
+// 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 {
fdi := int(fd)
if fdi < 0 {
return nil
}
- f := &File{&file{fd: fdi, name: name}}
+ f := &File{&file{
+ pfd: poll.FD{
+ Sysfd: fdi,
+ IsStream: true,
+ ZeroReadIsEOF: true,
+ },
+ name: name,
+ }}
+
+ // 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 err := f.pfd.Init("file", pollable); err != nil {
+ // An error here indicates a failure to register
+ // with the netpoll system. That can happen for
+ // a file descriptor that is not supported by
+ // epoll/kqueue; for example, disk files on
+ // GNU/Linux systems. We assume that any real error
+ // will show up in later I/O.
+ } else if pollable {
+ // We successfully registered with netpoll, so put
+ // the file into nonblocking mode.
+ if err := syscall.SetNonblock(fdi, true); err == nil {
+ f.nonblock = true
+ }
+ }
+
runtime.SetFinalizer(f.file, (*file).close)
return f
}
@@ -68,7 +129,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.fd == 1 || file.fd == 2) {
+ if e == syscall.EPIPE && (file.pfd.Sysfd == 1 || file.pfd.Sysfd == 2) {
sigpipe()
}
}
@@ -119,7 +180,7 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) {
syscall.CloseOnExec(r)
}
- return NewFile(uintptr(r), name), nil
+ return newFile(uintptr(r), name, true), nil
}
// Close closes the File, rendering it unusable for I/O.
@@ -132,11 +193,14 @@ func (f *File) Close() error {
}
func (file *file) close() error {
- if file == nil || file.fd == badFd {
+ if file == nil {
return syscall.EINVAL
}
var err error
- if e := syscall.Close(file.fd); e != nil {
+ if e := file.pfd.Close(); e != nil {
+ if e == poll.ErrFileClosing {
+ e = ErrClosed
+ }
err = &PathError{"close", file.name, e}
}
@@ -151,76 +215,42 @@ func (file *file) close() error {
}
}
- file.fd = -1 // so it can't be closed again
-
// no need for a finalizer anymore
runtime.SetFinalizer(file, nil)
return err
}
-// Darwin and FreeBSD can't read or write 2GB+ at a time,
-// even on 64-bit systems. See golang.org/issue/7812.
-// Use 1GB instead of, say, 2GB-1, to keep subsequent
-// reads aligned.
-const (
- needsMaxRW = runtime.GOOS == "darwin" || runtime.GOOS == "freebsd"
- maxRW = 1 << 30
-)
-
// read reads up to len(b) bytes from the File.
// It returns the number of bytes read and an error, if any.
func (f *File) read(b []byte) (n int, err error) {
- if needsMaxRW && len(b) > maxRW {
- b = b[:maxRW]
- }
- return fixCount(syscall.Read(f.fd, b))
+ n, err = f.pfd.Read(b)
+ runtime.KeepAlive(f)
+ return n, err
}
// pread reads len(b) bytes from the File starting at byte offset off.
// It returns the number of bytes read and the error, if any.
// EOF is signaled by a zero count with err set to nil.
func (f *File) pread(b []byte, off int64) (n int, err error) {
- if needsMaxRW && len(b) > maxRW {
- b = b[:maxRW]
- }
- return fixCount(syscall.Pread(f.fd, b, off))
+ n, err = f.pfd.Pread(b, off)
+ runtime.KeepAlive(f)
+ return n, err
}
// write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
func (f *File) write(b []byte) (n int, err error) {
- for {
- bcap := b
- if needsMaxRW && len(bcap) > maxRW {
- bcap = bcap[:maxRW]
- }
- m, err := fixCount(syscall.Write(f.fd, bcap))
- n += m
-
- // If the syscall wrote some data but not all (short write)
- // or it returned EINTR, then assume it stopped early for
- // reasons that are uninteresting to the caller, and try again.
- if 0 < m && m < len(bcap) || err == syscall.EINTR {
- b = b[m:]
- continue
- }
-
- if needsMaxRW && len(bcap) != len(b) && err == nil {
- b = b[m:]
- continue
- }
-
- return n, err
- }
+ n, err = f.pfd.Write(b)
+ runtime.KeepAlive(f)
+ return n, err
}
// pwrite writes len(b) bytes to the File starting at byte offset off.
// It returns the number of bytes written and an error, if any.
func (f *File) pwrite(b []byte, off int64) (n int, err error) {
- if needsMaxRW && len(b) > maxRW {
- b = b[:maxRW]
- }
- return fixCount(syscall.Pwrite(f.fd, b, off))
+ n, err = f.pfd.Pwrite(b, off)
+ runtime.KeepAlive(f)
+ return n, err
}
// seek sets the offset for the next Read or Write on file to offset, interpreted
@@ -228,7 +258,9 @@ func (f *File) pwrite(b []byte, off int64) (n int, err error) {
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
func (f *File) seek(offset int64, whence int) (ret int64, err error) {
- return syscall.Seek(f.fd, offset, whence)
+ ret, err = f.pfd.Seek(offset, whence)
+ runtime.KeepAlive(f)
+ return ret, err
}
// Truncate changes the size of the named file.
@@ -272,8 +304,7 @@ func Remove(name string) error {
return &PathError{"remove", name, e}
}
-// TempDir returns the default directory to use for temporary files.
-func TempDir() string {
+func tempDir() string {
dir := Getenv("TMPDIR")
if dir == "" {
if runtime.GOOS == "android" {
diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go
index dcc8d76..0f1617a 100644
--- a/libgo/go/os/os_test.go
+++ b/libgo/go/os/os_test.go
@@ -17,6 +17,7 @@ import (
"path/filepath"
"reflect"
"runtime"
+ "runtime/debug"
"sort"
"strings"
"sync"
@@ -52,15 +53,12 @@ var sysdir = func() *sysDir {
case "darwin":
switch runtime.GOARCH {
case "arm", "arm64":
- /// At this point the test harness has not had a chance
- // to move us into the ./src/os directory, so the
- // current working directory is the root of the app.
wd, err := syscall.Getwd()
if err != nil {
wd = err.Error()
}
return &sysDir{
- wd,
+ filepath.Join(wd, "..", ".."),
[]string{
"ResourceRules.plist",
"Info.plist",
@@ -110,7 +108,7 @@ func size(name string, t *testing.T) int64 {
break
}
if e != nil {
- t.Fatal("read failed:", err)
+ t.Fatal("read failed:", e)
}
}
return int64(len)
@@ -174,6 +172,45 @@ func TestStat(t *testing.T) {
}
}
+func TestStatError(t *testing.T) {
+ defer chtmpdir(t)()
+
+ path := "no-such-file"
+ Remove(path) // Just in case
+
+ fi, err := Stat(path)
+ if err == nil {
+ t.Fatal("got nil, want error")
+ }
+ if fi != nil {
+ t.Errorf("got %v, want nil", fi)
+ }
+ if perr, ok := err.(*PathError); !ok {
+ t.Errorf("got %T, want %T", err, perr)
+ }
+
+ testenv.MustHaveSymlink(t)
+
+ link := "symlink"
+ Remove(link) // Just in case
+ err = Symlink(path, link)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer Remove(link)
+
+ fi, err = Stat(link)
+ if err == nil {
+ t.Fatal("got nil, want error")
+ }
+ if fi != nil {
+ t.Errorf("got %v, want nil", fi)
+ }
+ if perr, ok := err.(*PathError); !ok {
+ t.Errorf("got %T, want %T", err, perr)
+ }
+}
+
func TestFstat(t *testing.T) {
path := sfdir + "/" + sfname
file, err1 := Open(path)
@@ -359,6 +396,50 @@ func BenchmarkReaddir(b *testing.B) {
benchmarkReaddir(".", b)
}
+func benchmarkStat(b *testing.B, path string) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := Stat(path)
+ if err != nil {
+ b.Fatalf("Stat(%q) failed: %v", path, err)
+ }
+ }
+}
+
+func benchmarkLstat(b *testing.B, path string) {
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ _, err := Lstat(path)
+ if err != nil {
+ b.Fatalf("Lstat(%q) failed: %v", path, err)
+ }
+ }
+}
+
+func BenchmarkStatDot(b *testing.B) {
+ benchmarkStat(b, ".")
+}
+
+func BenchmarkStatFile(b *testing.B) {
+ benchmarkStat(b, filepath.Join(runtime.GOROOT(), "src/os/os_test.go"))
+}
+
+func BenchmarkStatDir(b *testing.B) {
+ benchmarkStat(b, filepath.Join(runtime.GOROOT(), "src/os"))
+}
+
+func BenchmarkLstatDot(b *testing.B) {
+ benchmarkLstat(b, ".")
+}
+
+func BenchmarkLstatFile(b *testing.B) {
+ benchmarkLstat(b, filepath.Join(runtime.GOROOT(), "src/os/os_test.go"))
+}
+
+func BenchmarkLstatDir(b *testing.B) {
+ benchmarkLstat(b, filepath.Join(runtime.GOROOT(), "src/os"))
+}
+
// Read the directory one entry at a time.
func smallReaddirnames(file *File, length int, t *testing.T) []string {
names := make([]string, length)
@@ -673,55 +754,58 @@ func TestSymlink(t *testing.T) {
Remove(from) // Just in case.
file, err := Create(to)
if err != nil {
- t.Fatalf("open %q failed: %v", to, err)
+ t.Fatalf("Create(%q) failed: %v", to, err)
}
defer Remove(to)
if err = file.Close(); err != nil {
- t.Errorf("close %q failed: %v", to, err)
+ t.Errorf("Close(%q) failed: %v", to, err)
}
err = Symlink(to, from)
if err != nil {
- t.Fatalf("symlink %q, %q failed: %v", to, from, err)
+ t.Fatalf("Symlink(%q, %q) failed: %v", to, from, err)
}
defer Remove(from)
tostat, err := Lstat(to)
if err != nil {
- t.Fatalf("stat %q failed: %v", to, err)
+ t.Fatalf("Lstat(%q) failed: %v", to, err)
}
if tostat.Mode()&ModeSymlink != 0 {
- t.Fatalf("stat %q claims to have found a symlink", to)
+ t.Fatalf("Lstat(%q).Mode()&ModeSymlink = %v, want 0", to, tostat.Mode()&ModeSymlink)
}
fromstat, err := Stat(from)
if err != nil {
- t.Fatalf("stat %q failed: %v", from, err)
+ t.Fatalf("Stat(%q) failed: %v", from, err)
}
if !SameFile(tostat, fromstat) {
- t.Errorf("symlink %q, %q did not create symlink", to, from)
+ t.Errorf("Symlink(%q, %q) did not create symlink", to, from)
}
fromstat, err = Lstat(from)
if err != nil {
- t.Fatalf("lstat %q failed: %v", from, err)
+ t.Fatalf("Lstat(%q) failed: %v", from, err)
}
if fromstat.Mode()&ModeSymlink == 0 {
- t.Fatalf("symlink %q, %q did not create symlink", to, from)
+ t.Fatalf("Lstat(%q).Mode()&ModeSymlink = 0, want %v", from, ModeSymlink)
}
fromstat, err = Stat(from)
if err != nil {
- t.Fatalf("stat %q failed: %v", from, err)
+ t.Fatalf("Stat(%q) failed: %v", from, err)
+ }
+ if fromstat.Name() != from {
+ t.Errorf("Stat(%q).Name() = %q, want %q", from, fromstat.Name(), from)
}
if fromstat.Mode()&ModeSymlink != 0 {
- t.Fatalf("stat %q did not follow symlink", from)
+ t.Fatalf("Stat(%q).Mode()&ModeSymlink = %v, want 0", from, fromstat.Mode()&ModeSymlink)
}
s, err := Readlink(from)
if err != nil {
- t.Fatalf("readlink %q failed: %v", from, err)
+ t.Fatalf("Readlink(%q) failed: %v", from, err)
}
if s != to {
- t.Fatalf("after symlink %q != %q", s, to)
+ t.Fatalf("Readlink(%q) = %q, want %q", from, s, to)
}
file, err = Open(from)
if err != nil {
- t.Fatalf("open %q failed: %v", from, err)
+ t.Fatalf("Open(%q) failed: %v", from, err)
}
file.Close()
}
@@ -844,6 +928,18 @@ func TestRenameFailed(t *testing.T) {
}
}
+func TestRenameNotExisting(t *testing.T) {
+ defer chtmpdir(t)()
+ from, to := "doesnt-exist", "dest"
+
+ Mkdir(to, 0777)
+ defer Remove(to)
+
+ if err := Rename(from, to); !IsNotExist(err) {
+ t.Errorf("Rename(%q, %q) = %v; want an IsNotExist error", from, to, err)
+ }
+}
+
func TestRenameToDirFailed(t *testing.T) {
defer chtmpdir(t)()
from, to := "renamefrom", "renameto"
@@ -1054,14 +1150,22 @@ func testChtimes(t *testing.T, name string) {
}
postStat := st
- /* Plan 9, NaCl:
- Mtime is the time of the last change of content. Similarly, atime is set whenever the
- contents are accessed; also, it is set whenever mtime is set.
- */
pat := Atime(postStat)
pmt := postStat.ModTime()
- if !pat.Before(at) && runtime.GOOS != "plan9" && runtime.GOOS != "nacl" {
- t.Errorf("AccessTime didn't go backwards; was=%d, after=%d", at, pat)
+ if !pat.Before(at) {
+ switch runtime.GOOS {
+ case "plan9", "nacl":
+ // Ignore.
+ // Plan 9, NaCl:
+ // Mtime is the time of the last change of
+ // content. Similarly, atime is set whenever
+ // the contents are accessed; also, it is set
+ // whenever mtime is set.
+ case "netbsd":
+ t.Logf("AccessTime didn't go backwards; was=%d, after=%d (Ignoring. See NetBSD issue golang.org/issue/19293)", at, pat)
+ default:
+ t.Errorf("AccessTime didn't go backwards; was=%d, after=%d", at, pat)
+ }
}
if !pmt.Before(mt) {
@@ -1239,6 +1343,32 @@ func TestSeek(t *testing.T) {
}
}
+func TestSeekError(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9", "nacl":
+ t.Skipf("skipping test on %v", runtime.GOOS)
+ }
+
+ r, w, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, err = r.Seek(0, 0)
+ if err == nil {
+ t.Fatal("Seek on pipe should fail")
+ }
+ if perr, ok := err.(*PathError); !ok || perr.Err != syscall.ESPIPE {
+ t.Errorf("Seek returned error %v, want &PathError{Err: syscall.ESPIPE}", err)
+ }
+ _, err = w.Seek(0, 0)
+ if err == nil {
+ t.Fatal("Seek on pipe should fail")
+ }
+ if perr, ok := err.(*PathError); !ok || perr.Err != syscall.ESPIPE {
+ t.Errorf("Seek returned error %v, want &PathError{Err: syscall.ESPIPE}", err)
+ }
+}
+
type openErrorTest struct {
path string
mode int
@@ -1443,6 +1573,26 @@ func TestReadAtOffset(t *testing.T) {
}
}
+// Verify that ReadAt doesn't allow negative offset.
+func TestReadAtNegativeOffset(t *testing.T) {
+ f := newFile("TestReadAtNegativeOffset", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ f.Seek(0, 0)
+ b := make([]byte, 5)
+
+ n, err := f.ReadAt(b, -10)
+
+ const wantsub = "negative offset"
+ if !strings.Contains(fmt.Sprint(err), wantsub) || n != 0 {
+ t.Errorf("ReadAt(-10) = %v, %v; want 0, ...%q...", n, err, wantsub)
+ }
+}
+
func TestWriteAt(t *testing.T) {
f := newFile("TestWriteAt", t)
defer Remove(f.Name())
@@ -1465,6 +1615,20 @@ func TestWriteAt(t *testing.T) {
}
}
+// Verify that WriteAt doesn't allow negative offset.
+func TestWriteAtNegativeOffset(t *testing.T) {
+ f := newFile("TestWriteAtNegativeOffset", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ n, err := f.WriteAt([]byte("WORLD"), -10)
+
+ const wantsub = "negative offset"
+ if !strings.Contains(fmt.Sprint(err), wantsub) || n != 0 {
+ t.Errorf("WriteAt(-10) = %v, %v; want 0, ...%q...", n, err, wantsub)
+ }
+}
+
func writeFile(t *testing.T, fname string, flag int, text string) string {
f, err := OpenFile(fname, flag, 0666)
if err != nil {
@@ -1667,6 +1831,17 @@ func TestStatStdin(t *testing.T) {
Exit(0)
}
+ fi, err := Stdin.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+ switch mode := fi.Mode(); {
+ case mode&ModeCharDevice != 0:
+ case mode&ModeNamedPipe != 0:
+ default:
+ t.Fatalf("unexpected Stdin mode (%v), want ModeCharDevice or ModeNamedPipe", mode)
+ }
+
var cmd *osexec.Cmd
if runtime.GOOS == "windows" {
cmd = osexec.Command("cmd", "/c", "echo output | "+Args[0]+" -test.run=TestStatStdin")
@@ -1686,6 +1861,60 @@ func TestStatStdin(t *testing.T) {
}
}
+func TestStatRelativeSymlink(t *testing.T) {
+ testenv.MustHaveSymlink(t)
+
+ tmpdir, err := ioutil.TempDir("", "TestStatRelativeSymlink")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(tmpdir)
+
+ target := filepath.Join(tmpdir, "target")
+ f, err := Create(target)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+
+ st, err := f.Stat()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ link := filepath.Join(tmpdir, "link")
+ err = Symlink(filepath.Base(target), link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ st1, err := Stat(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !SameFile(st, st1) {
+ t.Error("Stat doesn't follow relative symlink")
+ }
+
+ if runtime.GOOS == "windows" {
+ Remove(link)
+ err = Symlink(target[len(filepath.VolumeName(target)):], link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ st1, err := Stat(link)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !SameFile(st, st1) {
+ t.Error("Stat doesn't follow relative symlink")
+ }
+ }
+}
+
func TestReadAtEOF(t *testing.T) {
f := newFile("TestReadAtEOF", t)
defer Remove(f.Name())
@@ -1759,6 +1988,10 @@ func TestLongPath(t *testing.T) {
if dir.Size() != filesize || filesize != wantSize {
t.Errorf("Size(%q) is %d, len(ReadFile()) is %d, want %d", path, dir.Size(), filesize, wantSize)
}
+ err = Chmod(path, dir.Mode())
+ if err != nil {
+ t.Fatalf("Chmod(%q) failed: %v", path, err)
+ }
}
if err := Truncate(sizedTempDir+"/bar.txt", 0); err != nil {
t.Fatalf("Truncate failed: %v", err)
@@ -1927,3 +2160,99 @@ func TestRemoveAllRace(t *testing.T) {
close(hold) // let workers race to remove root
wg.Wait()
}
+
+// Test that reading from a pipe doesn't use up a thread.
+func TestPipeThreads(t *testing.T) {
+ switch runtime.GOOS {
+ case "freebsd":
+ t.Skip("skipping on FreeBSD; issue 19093")
+ case "solaris":
+ t.Skip("skipping on Solaris; issue 19111")
+ case "windows":
+ t.Skip("skipping on Windows; issue 19098")
+ case "plan9":
+ t.Skip("skipping on Plan 9; does not support runtime poller")
+ }
+
+ threads := 100
+
+ // OpenBSD has a low default for max number of files.
+ if runtime.GOOS == "openbsd" {
+ threads = 50
+ }
+
+ r := make([]*File, threads)
+ w := make([]*File, threads)
+ for i := 0; i < threads; i++ {
+ rp, wp, err := Pipe()
+ if err != nil {
+ for j := 0; j < i; j++ {
+ r[j].Close()
+ w[j].Close()
+ }
+ t.Fatal(err)
+ }
+ r[i] = rp
+ w[i] = wp
+ }
+
+ defer debug.SetMaxThreads(debug.SetMaxThreads(threads / 2))
+
+ var wg sync.WaitGroup
+ wg.Add(threads)
+ c := make(chan bool, threads)
+ for i := 0; i < threads; i++ {
+ go func(i int) {
+ defer wg.Done()
+ var b [1]byte
+ c <- true
+ if _, err := r[i].Read(b[:]); err != nil {
+ t.Error(err)
+ }
+ }(i)
+ }
+
+ for i := 0; i < threads; i++ {
+ <-c
+ }
+
+ // If we are still alive, it means that the 100 goroutines did
+ // not require 100 threads.
+
+ for i := 0; i < threads; i++ {
+ if _, err := w[i].Write([]byte{0}); err != nil {
+ t.Error(err)
+ }
+ if err := w[i].Close(); err != nil {
+ t.Error(err)
+ }
+ }
+
+ wg.Wait()
+
+ for i := 0; i < threads; i++ {
+ if err := r[i].Close(); err != nil {
+ t.Error(err)
+ }
+ }
+}
+
+func TestDoubleCloseError(t *testing.T) {
+ path := sfdir + "/" + sfname
+ file, err := Open(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := file.Close(); err != nil {
+ t.Fatalf("unexpected error from Close: %v", err)
+ }
+ if err := file.Close(); err == nil {
+ t.Error("second Close did not fail")
+ } else if pe, ok := err.(*PathError); !ok {
+ t.Errorf("second Close returned unexpected error type %T; expected os.PathError", pe)
+ } else if pe.Err != ErrClosed {
+ t.Errorf("second Close returned %q, wanted %q", err, ErrClosed)
+ } else {
+ t.Logf("second close returned expected error %q", err)
+ }
+}
diff --git a/libgo/go/os/pipe_bsd.go b/libgo/go/os/pipe_bsd.go
index ebe198b..ae153fa 100644
--- a/libgo/go/os/pipe_bsd.go
+++ b/libgo/go/os/pipe_bsd.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd nacl netbsd openbsd solaris
+// +build aix darwin dragonfly nacl netbsd openbsd solaris
package os
@@ -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"), NewFile(uintptr(p[1]), "|1"), nil
+ return newFile(uintptr(p[0]), "|0", true), newFile(uintptr(p[1]), "|1", true), nil
}
diff --git a/libgo/go/os/pipe_freebsd.go b/libgo/go/os/pipe_freebsd.go
new file mode 100644
index 0000000..ea6622c
--- /dev/null
+++ b/libgo/go/os/pipe_freebsd.go
@@ -0,0 +1,34 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]int
+
+ e := syscall.Pipe2(p[0:], syscall.O_CLOEXEC)
+ if e != nil {
+ // 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 newFile(uintptr(p[0]), "|0", true), newFile(uintptr(p[1]), "|1", true), nil
+}
diff --git a/libgo/go/os/pipe_linux.go b/libgo/go/os/pipe_linux.go
index 9bafad8..96f2ce9 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"), NewFile(uintptr(p[1]), "|1"), nil
+ return newFile(uintptr(p[0]), "|0", true), newFile(uintptr(p[1]), "|1", true), nil
}
diff --git a/libgo/go/os/pipe_test.go b/libgo/go/os/pipe_test.go
index 74cce80..9d79d84 100644
--- a/libgo/go/os/pipe_test.go
+++ b/libgo/go/os/pipe_test.go
@@ -10,11 +10,16 @@ package os_test
import (
"fmt"
"internal/testenv"
+ "io/ioutil"
"os"
osexec "os/exec"
"os/signal"
+ "runtime"
+ "strconv"
+ "strings"
"syscall"
"testing"
+ "time"
)
func TestEPIPE(t *testing.T) {
@@ -82,7 +87,7 @@ func TestStdPipe(t *testing.T) {
t.Errorf("unexpected SIGPIPE signal for descriptor %d sig %t", dest, sig)
}
} else {
- t.Errorf("unexpected exit status %v for descriptor %ds sig %t", err, dest, sig)
+ t.Errorf("unexpected exit status %v for descriptor %d sig %t", err, dest, sig)
}
}
}
@@ -111,3 +116,107 @@ func TestStdPipeHelper(t *testing.T) {
// For descriptor 3, a normal exit is expected.
os.Exit(0)
}
+
+func testClosedPipeRace(t *testing.T, read bool) {
+ switch runtime.GOOS {
+ case "freebsd":
+ t.Skip("FreeBSD does not use the poller; issue 19093")
+ }
+
+ limit := 1
+ if !read {
+ // Get the amount we have to write to overload a pipe
+ // with no reader.
+ limit = 65537
+ if b, err := ioutil.ReadFile("/proc/sys/fs/pipe-max-size"); err == nil {
+ if i, err := strconv.Atoi(strings.TrimSpace(string(b))); err == nil {
+ limit = i + 1
+ }
+ }
+ t.Logf("using pipe write limit of %d", limit)
+ }
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ // Close the read end of the pipe in a goroutine while we are
+ // writing to the write end, or vice-versa.
+ go func() {
+ // Give the main goroutine a chance to enter the Read or
+ // Write call. This is sloppy but the test will pass even
+ // if we close before the read/write.
+ time.Sleep(20 * time.Millisecond)
+
+ var err error
+ if read {
+ err = r.Close()
+ } else {
+ err = w.Close()
+ }
+ if err != nil {
+ t.Error(err)
+ }
+ }()
+
+ b := make([]byte, limit)
+ if read {
+ _, err = r.Read(b[:])
+ } else {
+ _, err = w.Write(b[:])
+ }
+ if err == nil {
+ t.Error("I/O on closed pipe unexpectedly succeeded")
+ } else if pe, ok := err.(*os.PathError); !ok {
+ t.Errorf("I/O on closed pipe returned unexpected error type %T; expected os.PathError", pe)
+ } else if pe.Err != os.ErrClosed {
+ t.Errorf("got error %q but expected %q", pe.Err, os.ErrClosed)
+ } else {
+ t.Logf("I/O returned expected error %q", err)
+ }
+}
+
+func TestClosedPipeRaceRead(t *testing.T) {
+ testClosedPipeRace(t, true)
+}
+
+func TestClosedPipeRaceWrite(t *testing.T) {
+ testClosedPipeRace(t, false)
+}
+
+// Issue 20915: Reading on nonblocking fd should not return "waiting
+// for unsupported file type." Currently it returns EAGAIN; it is
+// possible that in the future it will simply wait for data.
+func TestReadNonblockingFd(t *testing.T) {
+ if os.Getenv("GO_WANT_READ_NONBLOCKING_FD") == "1" {
+ fd := int(os.Stdin.Fd())
+ syscall.SetNonblock(fd, true)
+ defer syscall.SetNonblock(fd, false)
+ _, err := os.Stdin.Read(make([]byte, 1))
+ if err != nil {
+ if perr, ok := err.(*os.PathError); !ok || perr.Err != syscall.EAGAIN {
+ t.Fatalf("read on nonblocking stdin got %q, should have gotten EAGAIN", err)
+ }
+ }
+ os.Exit(0)
+ }
+
+ testenv.MustHaveExec(t)
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+ cmd := osexec.Command(os.Args[0], "-test.run="+t.Name())
+ cmd.Env = append(os.Environ(), "GO_WANT_READ_NONBLOCKING_FD=1")
+ cmd.Stdin = r
+ output, err := cmd.CombinedOutput()
+ t.Logf("%s", output)
+ if err != nil {
+ t.Errorf("child process failed: %v", err)
+ }
+}
diff --git a/libgo/go/os/proc.go b/libgo/go/os/proc.go
index 33a8b26..804128a 100644
--- a/libgo/go/os/proc.go
+++ b/libgo/go/os/proc.go
@@ -25,18 +25,29 @@ func init() {
func runtime_args() []string // in package runtime
// Getuid returns the numeric user id of the caller.
+//
+// On Windows, it returns -1.
func Getuid() int { return syscall.Getuid() }
// Geteuid returns the numeric effective user id of the caller.
+//
+// On Windows, it returns -1.
func Geteuid() int { return syscall.Geteuid() }
// Getgid returns the numeric group id of the caller.
+//
+// On Windows, it returns -1.
func Getgid() int { return syscall.Getgid() }
// Getegid returns the numeric effective group id of the caller.
+//
+// On Windows, it returns -1.
func Getegid() int { return syscall.Getegid() }
// Getgroups returns a list of the numeric ids of groups that the caller belongs to.
+//
+// On Windows, it returns syscall.EWINDOWS. See the os/user package
+// for a possible alternative.
func Getgroups() ([]int, error) {
gids, e := syscall.Getgroups()
return gids, NewSyscallError("getgroups", e)
diff --git a/libgo/go/os/signal/doc.go b/libgo/go/os/signal/doc.go
index 73b01a2..16f49c7 100644
--- a/libgo/go/os/signal/doc.go
+++ b/libgo/go/os/signal/doc.go
@@ -181,10 +181,11 @@ If the Go runtime sees an existing signal handler for the SIGCANCEL or
SIGSETXID signals (which are used only on GNU/Linux), it will turn on
the SA_ONSTACK flag and otherwise keep the signal handler.
-For the synchronous signals, the Go runtime will install a signal
-handler. It will save any existing signal handler. If a synchronous
-signal arrives while executing non-Go code, the Go runtime will invoke
-the existing signal handler instead of the Go signal handler.
+For the synchronous signals and SIGPIPE, the Go runtime will install a
+signal handler. It will save any existing signal handler. If a
+synchronous signal arrives while executing non-Go code, the Go runtime
+will invoke the existing signal handler instead of the Go signal
+handler.
Go code built with -buildmode=c-archive or -buildmode=c-shared will
not install any other signal handlers by default. If there is an
diff --git a/libgo/go/os/signal/signal.go b/libgo/go/os/signal/signal.go
index c1376da..e5a21e8 100644
--- a/libgo/go/os/signal/signal.go
+++ b/libgo/go/os/signal/signal.go
@@ -11,8 +11,21 @@ import (
var handlers struct {
sync.Mutex
- m map[chan<- os.Signal]*handler
+ // Map a channel to the signals that should be sent to it.
+ m map[chan<- os.Signal]*handler
+ // Map a signal to the number of channels receiving it.
ref [numSig]int64
+ // Map channels to signals while the channel is being stopped.
+ // Not a map because entries live here only very briefly.
+ // We need a separate container because we need m to correspond to ref
+ // at all times, and we also need to keep track of the *handler
+ // value for a channel being stopped. See the Stop function.
+ stopping []stopping
+}
+
+type stopping struct {
+ c chan<- os.Signal
+ h *handler
}
type handler struct {
@@ -142,10 +155,10 @@ func Reset(sig ...os.Signal) {
// When Stop returns, it is guaranteed that c will receive no more signals.
func Stop(c chan<- os.Signal) {
handlers.Lock()
- defer handlers.Unlock()
h := handlers.m[c]
if h == nil {
+ handlers.Unlock()
return
}
delete(handlers.m, c)
@@ -158,8 +171,40 @@ func Stop(c chan<- os.Signal) {
}
}
}
+
+ // Signals will no longer be delivered to the channel.
+ // We want to avoid a race for a signal such as SIGINT:
+ // it should be either delivered to the channel,
+ // or the program should take the default action (that is, exit).
+ // To avoid the possibility that the signal is delivered,
+ // and the signal handler invoked, and then Stop deregisters
+ // the channel before the process function below has a chance
+ // to send it on the channel, put the channel on a list of
+ // channels being stopped and wait for signal delivery to
+ // quiesce before fully removing it.
+
+ handlers.stopping = append(handlers.stopping, stopping{c, h})
+
+ handlers.Unlock()
+
+ signalWaitUntilIdle()
+
+ handlers.Lock()
+
+ for i, s := range handlers.stopping {
+ if s.c == c {
+ handlers.stopping = append(handlers.stopping[:i], handlers.stopping[i+1:]...)
+ break
+ }
+ }
+
+ handlers.Unlock()
}
+// Wait until there are no more signals waiting to be delivered.
+// Defined by the runtime package.
+func signalWaitUntilIdle()
+
func process(sig os.Signal) {
n := signum(sig)
if n < 0 {
@@ -178,4 +223,14 @@ func process(sig os.Signal) {
}
}
}
+
+ // Avoid the race mentioned in Stop.
+ for _, d := range handlers.stopping {
+ if d.h.want(n) {
+ select {
+ case d.c <- sig:
+ default:
+ }
+ }
+ }
}
diff --git a/libgo/go/os/signal/signal_test.go b/libgo/go/os/signal/signal_test.go
index c8409e7..10a4146 100644
--- a/libgo/go/os/signal/signal_test.go
+++ b/libgo/go/os/signal/signal_test.go
@@ -7,12 +7,16 @@
package signal
import (
+ "bytes"
"flag"
+ "fmt"
+ "internal/testenv"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strconv"
+ "sync"
"syscall"
"testing"
"time"
@@ -301,3 +305,90 @@ func TestSIGCONT(t *testing.T) {
syscall.Kill(syscall.Getpid(), syscall.SIGCONT)
waitSig(t, c, syscall.SIGCONT)
}
+
+// Test race between stopping and receiving a signal (issue 14571).
+func TestAtomicStop(t *testing.T) {
+ if os.Getenv("GO_TEST_ATOMIC_STOP") != "" {
+ atomicStopTestProgram()
+ t.Fatal("atomicStopTestProgram returned")
+ }
+
+ testenv.MustHaveExec(t)
+
+ const execs = 10
+ for i := 0; i < execs; i++ {
+ cmd := exec.Command(os.Args[0], "-test.run=TestAtomicStop")
+ 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)
+ } else {
+ t.Logf("iteration %d: exit status %q: output: %s", i, err, out)
+ }
+
+ lost := bytes.Contains(out, []byte("lost signal"))
+ if lost {
+ t.Errorf("iteration %d: lost signal", i)
+ }
+
+ // The program should either die due to SIGINT,
+ // or exit with success without printing "lost signal".
+ if err == nil {
+ if len(out) > 0 && !lost {
+ t.Errorf("iteration %d: unexpected output", i)
+ }
+ } else {
+ if ee, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("iteration %d: error (%v) has type %T; expected exec.ExitError", i, err, err)
+ } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
+ t.Errorf("iteration %d: error.Sys (%v) has type %T; expected syscall.WaitStatus", i, ee.Sys(), ee.Sys())
+ } else if !ws.Signaled() || ws.Signal() != syscall.SIGINT {
+ t.Errorf("iteration %d: got exit status %v; expected SIGINT", i, ee)
+ }
+ }
+ }
+}
+
+// atomicStopTestProgram is run in a subprocess by TestAtomicStop.
+// It tries to trigger a signal delivery race. This function should
+// either catch a signal or die from it.
+func atomicStopTestProgram() {
+ const tries = 10
+ pid := syscall.Getpid()
+ printed := false
+ for i := 0; i < tries; i++ {
+ cs := make(chan os.Signal, 1)
+ Notify(cs, syscall.SIGINT)
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ Stop(cs)
+ }()
+
+ syscall.Kill(pid, syscall.SIGINT)
+
+ // At this point we should either die from SIGINT or
+ // get a notification on cs. If neither happens, we
+ // dropped the signal. Give it a second to deliver,
+ // which is far far longer than it should require.
+
+ select {
+ case <-cs:
+ case <-time.After(1 * time.Second):
+ if !printed {
+ fmt.Print("lost signal on iterations:")
+ printed = true
+ }
+ fmt.Printf(" %d", i)
+ }
+
+ wg.Wait()
+ }
+ if printed {
+ fmt.Print("\n")
+ }
+
+ os.Exit(0)
+}
diff --git a/libgo/go/os/stat_unix.go b/libgo/go/os/stat_unix.go
index 043aefe..7855fba 100644
--- a/libgo/go/os/stat_unix.go
+++ b/libgo/go/os/stat_unix.go
@@ -17,7 +17,7 @@ func (f *File) Stat() (FileInfo, error) {
return nil, ErrInvalid
}
var fs fileStat
- err := syscall.Fstat(f.fd, &fs.sys)
+ err := f.pfd.Fstat(&fs.sys)
if err != nil {
return nil, &PathError{"stat", f.name, err}
}
diff --git a/libgo/go/os/sys_darwin.go b/libgo/go/os/sys_darwin.go
index 7a8330a..11d678e 100644
--- a/libgo/go/os/sys_darwin.go
+++ b/libgo/go/os/sys_darwin.go
@@ -4,28 +4,8 @@
package os
-import "syscall"
-
// supportsCloseOnExec reports whether the platform supports the
// O_CLOEXEC flag.
-var supportsCloseOnExec bool
-
-func init() {
- // Seems like kern.osreldate is veiled on latest OS X. We use
- // kern.osrelease instead.
- osver, err := syscall.Sysctl("kern.osrelease")
- if err != nil {
- return
- }
- var i int
- for i = range osver {
- if osver[i] != '.' {
- continue
- }
- }
- // The O_CLOEXEC flag was introduced in OS X 10.7 (Darwin
- // 11.0.0). See http://support.apple.com/kb/HT1633.
- if i > 2 || i == 2 && osver[0] >= '1' && osver[1] >= '1' {
- supportsCloseOnExec = true
- }
-}
+// The O_CLOEXEC flag was introduced in OS X 10.7 (Darwin 11.0.0).
+// See http://support.apple.com/kb/HT1633.
+const supportsCloseOnExec = true
diff --git a/libgo/go/os/types.go b/libgo/go/os/types.go
index c565483..db78487 100644
--- a/libgo/go/os/types.go
+++ b/libgo/go/os/types.go
@@ -45,7 +45,7 @@ const (
ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory
ModeAppend // a: append-only
ModeExclusive // l: exclusive use
- ModeTemporary // T: temporary file (not backed up)
+ ModeTemporary // T: temporary file; Plan 9 only
ModeSymlink // L: symbolic link
ModeDevice // D: device file
ModeNamedPipe // p: named pipe (FIFO)
diff --git a/libgo/go/os/types_unix.go b/libgo/go/os/types_unix.go
index 1f61481..c0259ae 100644
--- a/libgo/go/os/types_unix.go
+++ b/libgo/go/os/types_unix.go
@@ -29,5 +29,3 @@ func (fs *fileStat) Sys() interface{} { return &fs.sys }
func sameFile(fs1, fs2 *fileStat) bool {
return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino
}
-
-const badFd = -1
diff --git a/libgo/go/os/types_windows.go b/libgo/go/os/types_windows.go
index ad4e863..01d6b62 100644
--- a/libgo/go/os/types_windows.go
+++ b/libgo/go/os/types_windows.go
@@ -12,16 +12,17 @@ import (
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
type fileStat struct {
- name string
- sys syscall.Win32FileAttributeData
- pipe bool
+ name string
+ sys syscall.Win32FileAttributeData
+ filetype uint32 // what syscall.GetFileType returns
// used to implement SameFile
sync.Mutex
- path string
- vol uint32
- idxhi uint32
- idxlo uint32
+ path string
+ vol uint32
+ idxhi uint32
+ idxlo uint32
+ appendNameToPath bool
}
func (fs *fileStat) Size() int64 {
@@ -32,19 +33,22 @@ func (fs *fileStat) Mode() (m FileMode) {
if fs == &devNullStat {
return ModeDevice | ModeCharDevice | 0666
}
- if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
- m |= ModeDir | 0111
- }
if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
m |= 0444
} else {
m |= 0666
}
if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
- m |= ModeSymlink
+ return m | ModeSymlink
+ }
+ if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ m |= ModeDir | 0111
}
- if fs.pipe {
+ switch fs.filetype {
+ case syscall.FILE_TYPE_PIPE:
m |= ModeNamedPipe
+ case syscall.FILE_TYPE_CHAR:
+ m |= ModeCharDevice
}
return m
}
@@ -63,7 +67,13 @@ func (fs *fileStat) loadFileId() error {
// already done
return nil
}
- pathp, err := syscall.UTF16PtrFromString(fs.path)
+ var path string
+ if fs.appendNameToPath {
+ path = fs.path + `\` + fs.name
+ } else {
+ path = fs.path
+ }
+ pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return err
}
diff --git a/libgo/go/os/user/cgo_lookup_unix.go b/libgo/go/os/user/cgo_lookup_unix.go
new file mode 100644
index 0000000..8881366
--- /dev/null
+++ b/libgo/go/os/user/cgo_lookup_unix.go
@@ -0,0 +1,266 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
+// +build cgo
+
+package user
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+// bytePtrToString takes a NUL-terminated array of bytes and convert
+// it to a Go string.
+func bytePtrToString(p *byte) string {
+ a := (*[10000]byte)(unsafe.Pointer(p))
+ i := 0
+ for a[i] != 0 {
+ i++
+ }
+ return string(a[:i])
+}
+
+func current() (*User, error) {
+ return lookupUnixUid(syscall.Getuid())
+}
+
+func lookupUser(username string) (*User, error) {
+ var pwd syscall.Passwd
+ var result *syscall.Passwd
+ p := syscall.StringBytePtr(username)
+
+ buf := alloc(userBuffer)
+ defer buf.free()
+
+ err := retryWithBuffer(buf, func() syscall.Errno {
+ syscall.Entersyscall()
+ rv := libc_getpwnam_r(p,
+ &pwd,
+ buf.ptr,
+ buf.size,
+ &result)
+ syscall.Exitsyscall()
+ if rv != 0 {
+ return syscall.GetErrno()
+ }
+ return 0
+ })
+ if err != nil {
+ return nil, fmt.Errorf("user: lookup username %s: %v", username, err)
+ }
+ if result == nil {
+ return nil, UnknownUserError(username)
+ }
+ return buildUser(&pwd), err
+}
+
+func lookupUserId(uid string) (*User, error) {
+ i, e := strconv.Atoi(uid)
+ if e != nil {
+ return nil, e
+ }
+ return lookupUnixUid(i)
+}
+
+func lookupUnixUid(uid int) (*User, error) {
+ var pwd syscall.Passwd
+ var result *syscall.Passwd
+
+ buf := alloc(userBuffer)
+ defer buf.free()
+
+ err := retryWithBuffer(buf, func() syscall.Errno {
+ syscall.Entersyscall()
+ rv := libc_getpwuid_r(syscall.Uid_t(uid),
+ &pwd,
+ buf.ptr,
+ buf.size,
+ &result)
+ syscall.Exitsyscall()
+ if rv != 0 {
+ return syscall.GetErrno()
+ }
+ return 0
+ })
+ if err != nil {
+ return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err)
+ }
+ if result == nil {
+ return nil, UnknownUserIdError(uid)
+ }
+ return buildUser(&pwd), nil
+}
+
+func buildUser(pwd *syscall.Passwd) *User {
+ u := &User{
+ Uid: strconv.Itoa(int(pwd.Pw_uid)),
+ Gid: strconv.Itoa(int(pwd.Pw_gid)),
+ Username: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_name))),
+ Name: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_gecos))),
+ HomeDir: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_dir))),
+ }
+ // The pw_gecos field isn't quite standardized. Some docs
+ // say: "It is expected to be a comma separated list of
+ // personal data where the first item is the full name of the
+ // user."
+ if i := strings.Index(u.Name, ","); i >= 0 {
+ u.Name = u.Name[:i]
+ }
+ return u
+}
+
+func currentGroup() (*Group, error) {
+ return lookupUnixGid(syscall.Getgid())
+}
+
+func lookupGroup(groupname string) (*Group, error) {
+ var grp syscall.Group
+ var result *syscall.Group
+
+ buf := alloc(groupBuffer)
+ defer buf.free()
+ p := syscall.StringBytePtr(groupname)
+
+ err := retryWithBuffer(buf, func() syscall.Errno {
+ syscall.Entersyscall()
+ rv := libc_getgrnam_r(p,
+ &grp,
+ buf.ptr,
+ buf.size,
+ &result)
+ syscall.Exitsyscall()
+ if rv != 0 {
+ return syscall.GetErrno()
+ }
+ return 0
+ })
+ if err != nil {
+ return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err)
+ }
+ if result == nil {
+ return nil, UnknownGroupError(groupname)
+ }
+ return buildGroup(&grp), nil
+}
+
+func lookupGroupId(gid string) (*Group, error) {
+ i, e := strconv.Atoi(gid)
+ if e != nil {
+ return nil, e
+ }
+ return lookupUnixGid(i)
+}
+
+func lookupUnixGid(gid int) (*Group, error) {
+ var grp syscall.Group
+ var result *syscall.Group
+
+ buf := alloc(groupBuffer)
+ defer buf.free()
+
+ err := retryWithBuffer(buf, func() syscall.Errno {
+ syscall.Entersyscall()
+ rv := libc_getgrgid_r(syscall.Gid_t(gid),
+ &grp,
+ buf.ptr,
+ buf.size,
+ &result)
+ syscall.Exitsyscall()
+ if rv != 0 {
+ return syscall.GetErrno()
+ }
+ return 0
+ })
+ if err != nil {
+ return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err)
+ }
+ if result == nil {
+ return nil, UnknownGroupIdError(strconv.Itoa(gid))
+ }
+ return buildGroup(&grp), nil
+}
+
+func buildGroup(grp *syscall.Group) *Group {
+ g := &Group{
+ Gid: strconv.Itoa(int(grp.Gr_gid)),
+ Name: bytePtrToString((*byte)(unsafe.Pointer(grp.Gr_name))),
+ }
+ return g
+}
+
+type bufferKind int
+
+const (
+ userBuffer = bufferKind(syscall.SC_GETPW_R_SIZE_MAX)
+ groupBuffer = bufferKind(syscall.SC_GETGR_R_SIZE_MAX)
+)
+
+func (k bufferKind) initialSize() syscall.Size_t {
+ sz, _ := syscall.Sysconf(int(k))
+ if sz == -1 {
+ // DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
+ // Additionally, not all Linux systems have it, either. For
+ // example, the musl libc returns -1.
+ return 1024
+ }
+ if !isSizeReasonable(int64(sz)) {
+ // Truncate. If this truly isn't enough, retryWithBuffer will error on the first run.
+ return maxBufferSize
+ }
+ return syscall.Size_t(sz)
+}
+
+type memBuffer struct {
+ ptr *byte
+ size syscall.Size_t
+}
+
+func alloc(kind bufferKind) *memBuffer {
+ sz := kind.initialSize()
+ b := make([]byte, sz)
+ return &memBuffer{
+ ptr: &b[0],
+ size: sz,
+ }
+}
+
+func (mb *memBuffer) resize(newSize syscall.Size_t) {
+ b := make([]byte, newSize)
+ mb.ptr = &b[0]
+ mb.size = newSize
+}
+
+func (mb *memBuffer) free() {
+ mb.ptr = nil
+}
+
+// retryWithBuffer repeatedly calls f(), increasing the size of the
+// buffer each time, until f succeeds, fails with a non-ERANGE error,
+// or the buffer exceeds a reasonable limit.
+func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error {
+ for {
+ errno := f()
+ if errno == 0 {
+ return nil
+ } else if errno != syscall.ERANGE {
+ return errno
+ }
+ newSize := buf.size * 2
+ if !isSizeReasonable(int64(newSize)) {
+ return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize)
+ }
+ buf.resize(newSize)
+ }
+}
+
+const maxBufferSize = 1 << 20
+
+func isSizeReasonable(sz int64) bool {
+ return sz > 0 && sz <= maxBufferSize
+}
diff --git a/libgo/go/os/user/lookup.go b/libgo/go/os/user/lookup.go
index 3b4421b..2243a25 100644
--- a/libgo/go/os/user/lookup.go
+++ b/libgo/go/os/user/lookup.go
@@ -4,20 +4,40 @@
package user
+import "sync"
+
// Current returns the current user.
func Current() (*User, error) {
- return current()
+ cache.Do(func() { cache.u, cache.err = current() })
+ if cache.err != nil {
+ return nil, cache.err
+ }
+ u := *cache.u // copy
+ return &u, nil
+}
+
+// cache of the current user
+var cache struct {
+ sync.Once
+ u *User
+ err error
}
// Lookup looks up a user by username. If the user cannot be found, the
// returned error is of type UnknownUserError.
func Lookup(username string) (*User, error) {
+ if u, err := Current(); err == nil && u.Username == username {
+ return u, err
+ }
return lookupUser(username)
}
// LookupId looks up a user by userid. If the user cannot be found, the
// returned error is of type UnknownUserIdError.
func LookupId(uid string) (*User, error) {
+ if u, err := Current(); err == nil && u.Uid == uid {
+ return u, err
+ }
return lookupUserId(uid)
}
diff --git a/libgo/go/os/user/lookup_android.go b/libgo/go/os/user/lookup_android.go
index b1be3dc..8ca30b8 100644
--- a/libgo/go/os/user/lookup_android.go
+++ b/libgo/go/os/user/lookup_android.go
@@ -8,15 +8,6 @@ package user
import "errors"
-func init() {
- userImplemented = false
- groupImplemented = false
-}
-
-func current() (*User, error) {
- return nil, errors.New("user: Current not implemented on android")
-}
-
func lookupUser(string) (*User, error) {
return nil, errors.New("user: Lookup not implemented on android")
}
@@ -32,7 +23,3 @@ func lookupGroup(string) (*Group, error) {
func lookupGroupId(string) (*Group, error) {
return nil, errors.New("user: LookupGroupId not implemented on android")
}
-
-func listGroups(*User) ([]string, error) {
- return nil, errors.New("user: GroupIds not implemented on android")
-}
diff --git a/libgo/go/os/user/lookup_stubs.go b/libgo/go/os/user/lookup_stubs.go
index ebf24f7..d23870f 100644
--- a/libgo/go/os/user/lookup_stubs.go
+++ b/libgo/go/os/user/lookup_stubs.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build !cgo,!windows,!plan9,!android
+// +build !cgo,!windows,!plan9 android
package user
@@ -15,7 +15,6 @@ import (
)
func init() {
- userImplemented = false
groupImplemented = false
}
@@ -27,7 +26,9 @@ func current() (*User, error) {
Name: "", // ignored
HomeDir: os.Getenv("HOME"),
}
- if runtime.GOOS == "nacl" {
+ // On NaCL and Android, return a dummy user instead of failing.
+ switch runtime.GOOS {
+ case "nacl":
if u.Uid == "" {
u.Uid = "1"
}
@@ -35,7 +36,17 @@ func current() (*User, error) {
u.Username = "nacl"
}
if u.HomeDir == "" {
- u.HomeDir = "/home/nacl"
+ u.HomeDir = "/"
+ }
+ case "android":
+ if u.Uid == "" {
+ u.Uid = "1"
+ }
+ if u.Username == "" {
+ u.Username = "android"
+ }
+ if u.HomeDir == "" {
+ u.HomeDir = "/sdcard"
}
}
// cgo isn't available, but if we found the minimum information
@@ -46,23 +57,10 @@ func current() (*User, error) {
return u, fmt.Errorf("user: Current not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
-func lookupUser(username string) (*User, error) {
- return nil, errors.New("user: Lookup requires cgo")
-}
-
-func lookupUserId(uid string) (*User, error) {
- return nil, errors.New("user: LookupId requires cgo")
-}
-
-func lookupGroup(groupname string) (*Group, error) {
- return nil, errors.New("user: LookupGroup requires cgo")
-}
-
-func lookupGroupId(string) (*Group, error) {
- return nil, errors.New("user: LookupGroupId requires cgo")
-}
-
func listGroups(*User) ([]string, error) {
+ if runtime.GOOS == "android" {
+ return nil, errors.New("user: GroupIds not implemented on Android")
+ }
return nil, errors.New("user: GroupIds requires cgo")
}
diff --git a/libgo/go/os/user/lookup_unix.go b/libgo/go/os/user/lookup_unix.go
index 9670ada..5f34ba8 100644
--- a/libgo/go/os/user/lookup_unix.go
+++ b/libgo/go/os/user/lookup_unix.go
@@ -1,266 +1,197 @@
-// Copyright 2011 The Go Authors. All rights reserved.
+// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris
-// +build cgo
+// +build darwin dragonfly freebsd !android,linux nacl netbsd openbsd solaris
+// +build !cgo
package user
import (
- "fmt"
+ "bufio"
+ "bytes"
+ "errors"
+ "io"
+ "os"
"strconv"
"strings"
- "syscall"
- "unsafe"
)
-// bytePtrToString takes a NUL-terminated array of bytes and convert
-// it to a Go string.
-func bytePtrToString(p *byte) string {
- a := (*[10000]byte)(unsafe.Pointer(p))
- i := 0
- for a[i] != 0 {
- i++
- }
- return string(a[:i])
-}
+const groupFile = "/etc/group"
+const userFile = "/etc/passwd"
-func current() (*User, error) {
- return lookupUnixUid(syscall.Getuid())
-}
+var colon = []byte{':'}
-func lookupUser(username string) (*User, error) {
- var pwd syscall.Passwd
- var result *syscall.Passwd
- p := syscall.StringBytePtr(username)
+func init() {
+ groupImplemented = false
+}
- buf := alloc(userBuffer)
- defer buf.free()
+// lineFunc returns a value, an error, or (nil, nil) to skip the row.
+type lineFunc func(line []byte) (v interface{}, err error)
- err := retryWithBuffer(buf, func() syscall.Errno {
- syscall.Entersyscall()
- rv := libc_getpwnam_r(p,
- &pwd,
- buf.ptr,
- buf.size,
- &result)
- syscall.Exitsyscall()
- if rv != 0 {
- return syscall.GetErrno()
+// readColonFile parses r as an /etc/group or /etc/passwd style file, running
+// fn for each row. readColonFile returns a value, an error, or (nil, nil) if
+// the end of the file is reached without a match.
+func readColonFile(r io.Reader, fn lineFunc) (v interface{}, err error) {
+ bs := bufio.NewScanner(r)
+ for bs.Scan() {
+ line := bs.Bytes()
+ // There's no spec for /etc/passwd or /etc/group, but we try to follow
+ // the same rules as the glibc parser, which allows comments and blank
+ // space at the beginning of a line.
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 || line[0] == '#' {
+ continue
+ }
+ v, err = fn(line)
+ if v != nil || err != nil {
+ return
}
- return 0
- })
- if err != nil {
- return nil, fmt.Errorf("user: lookup username %s: %v", username, err)
- }
- if result == nil {
- return nil, UnknownUserError(username)
}
- return buildUser(&pwd), err
+ return nil, bs.Err()
}
-func lookupUserId(uid string) (*User, error) {
- i, e := strconv.Atoi(uid)
- if e != nil {
- return nil, e
+func matchGroupIndexValue(value string, idx int) lineFunc {
+ var leadColon string
+ if idx > 0 {
+ leadColon = ":"
}
- return lookupUnixUid(i)
-}
-
-func lookupUnixUid(uid int) (*User, error) {
- var pwd syscall.Passwd
- var result *syscall.Passwd
-
- buf := alloc(userBuffer)
- defer buf.free()
-
- err := retryWithBuffer(buf, func() syscall.Errno {
- syscall.Entersyscall()
- rv := libc_getpwuid_r(syscall.Uid_t(uid),
- &pwd,
- buf.ptr,
- buf.size,
- &result)
- syscall.Exitsyscall()
- if rv != 0 {
- return syscall.GetErrno()
+ substr := []byte(leadColon + value + ":")
+ return func(line []byte) (v interface{}, err error) {
+ if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 3 {
+ return
}
- return 0
- })
- if err != nil {
- return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err)
- }
- if result == nil {
- return nil, UnknownUserIdError(uid)
+ // wheel:*:0:root
+ parts := strings.SplitN(string(line), ":", 4)
+ if len(parts) < 4 || parts[0] == "" || parts[idx] != value ||
+ // If the file contains +foo and you search for "foo", glibc
+ // returns an "invalid argument" error. Similarly, if you search
+ // for a gid for a row where the group name starts with "+" or "-",
+ // glibc fails to find the record.
+ parts[0][0] == '+' || parts[0][0] == '-' {
+ return
+ }
+ if _, err := strconv.Atoi(parts[2]); err != nil {
+ return nil, nil
+ }
+ return &Group{Name: parts[0], Gid: parts[2]}, nil
}
- return buildUser(&pwd), nil
}
-func buildUser(pwd *syscall.Passwd) *User {
- u := &User{
- Uid: strconv.Itoa(int(pwd.Pw_uid)),
- Gid: strconv.Itoa(int(pwd.Pw_gid)),
- Username: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_name))),
- Name: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_gecos))),
- HomeDir: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_dir))),
- }
- // The pw_gecos field isn't quite standardized. Some docs
- // say: "It is expected to be a comma separated list of
- // personal data where the first item is the full name of the
- // user."
- if i := strings.Index(u.Name, ","); i >= 0 {
- u.Name = u.Name[:i]
+func findGroupId(id string, r io.Reader) (*Group, error) {
+ if v, err := readColonFile(r, matchGroupIndexValue(id, 2)); err != nil {
+ return nil, err
+ } else if v != nil {
+ return v.(*Group), nil
}
- return u
+ return nil, UnknownGroupIdError(id)
}
-func currentGroup() (*Group, error) {
- return lookupUnixGid(syscall.Getgid())
+func findGroupName(name string, r io.Reader) (*Group, error) {
+ if v, err := readColonFile(r, matchGroupIndexValue(name, 0)); err != nil {
+ return nil, err
+ } else if v != nil {
+ return v.(*Group), nil
+ }
+ return nil, UnknownGroupError(name)
}
-func lookupGroup(groupname string) (*Group, error) {
- var grp syscall.Group
- var result *syscall.Group
-
- buf := alloc(groupBuffer)
- defer buf.free()
- p := syscall.StringBytePtr(groupname)
-
- err := retryWithBuffer(buf, func() syscall.Errno {
- syscall.Entersyscall()
- rv := libc_getgrnam_r(p,
- &grp,
- buf.ptr,
- buf.size,
- &result)
- syscall.Exitsyscall()
- if rv != 0 {
- return syscall.GetErrno()
- }
- return 0
- })
- if err != nil {
- return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err)
+// returns a *User for a row if that row's has the given value at the
+// given index.
+func matchUserIndexValue(value string, idx int) lineFunc {
+ var leadColon string
+ if idx > 0 {
+ leadColon = ":"
}
- if result == nil {
- return nil, UnknownGroupError(groupname)
+ substr := []byte(leadColon + value + ":")
+ return func(line []byte) (v interface{}, err error) {
+ if !bytes.Contains(line, substr) || bytes.Count(line, colon) < 6 {
+ return
+ }
+ // kevin:x:1005:1006::/home/kevin:/usr/bin/zsh
+ parts := strings.SplitN(string(line), ":", 7)
+ if len(parts) < 6 || parts[idx] != value || parts[0] == "" ||
+ parts[0][0] == '+' || parts[0][0] == '-' {
+ return
+ }
+ if _, err := strconv.Atoi(parts[2]); err != nil {
+ return nil, nil
+ }
+ if _, err := strconv.Atoi(parts[3]); err != nil {
+ return nil, nil
+ }
+ u := &User{
+ Username: parts[0],
+ Uid: parts[2],
+ Gid: parts[3],
+ Name: parts[4],
+ HomeDir: parts[5],
+ }
+ // The pw_gecos field isn't quite standardized. Some docs
+ // say: "It is expected to be a comma separated list of
+ // personal data where the first item is the full name of the
+ // user."
+ if i := strings.Index(u.Name, ","); i >= 0 {
+ u.Name = u.Name[:i]
+ }
+ return u, nil
}
- return buildGroup(&grp), nil
}
-func lookupGroupId(gid string) (*Group, error) {
- i, e := strconv.Atoi(gid)
+func findUserId(uid string, r io.Reader) (*User, error) {
+ i, e := strconv.Atoi(uid)
if e != nil {
- return nil, e
+ return nil, errors.New("user: invalid userid " + uid)
}
- return lookupUnixGid(i)
-}
-
-func lookupUnixGid(gid int) (*Group, error) {
- var grp syscall.Group
- var result *syscall.Group
-
- buf := alloc(groupBuffer)
- defer buf.free()
-
- err := retryWithBuffer(buf, func() syscall.Errno {
- syscall.Entersyscall()
- rv := libc_getgrgid_r(syscall.Gid_t(gid),
- &grp,
- buf.ptr,
- buf.size,
- &result)
- syscall.Exitsyscall()
- if rv != 0 {
- return syscall.GetErrno()
- }
- return 0
- })
- if err != nil {
- return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err)
+ if v, err := readColonFile(r, matchUserIndexValue(uid, 2)); err != nil {
+ return nil, err
+ } else if v != nil {
+ return v.(*User), nil
}
- if result == nil {
- return nil, UnknownGroupIdError(strconv.Itoa(gid))
- }
- return buildGroup(&grp), nil
+ return nil, UnknownUserIdError(i)
}
-func buildGroup(grp *syscall.Group) *Group {
- g := &Group{
- Gid: strconv.Itoa(int(grp.Gr_gid)),
- Name: bytePtrToString((*byte)(unsafe.Pointer(grp.Gr_name))),
+func findUsername(name string, r io.Reader) (*User, error) {
+ if v, err := readColonFile(r, matchUserIndexValue(name, 0)); err != nil {
+ return nil, err
+ } else if v != nil {
+ return v.(*User), nil
}
- return g
+ return nil, UnknownUserError(name)
}
-type bufferKind int
-
-const (
- userBuffer = bufferKind(syscall.SC_GETPW_R_SIZE_MAX)
- groupBuffer = bufferKind(syscall.SC_GETGR_R_SIZE_MAX)
-)
-
-func (k bufferKind) initialSize() syscall.Size_t {
- sz, _ := syscall.Sysconf(int(k))
- if sz == -1 {
- // DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX.
- // Additionally, not all Linux systems have it, either. For
- // example, the musl libc returns -1.
- return 1024
- }
- if !isSizeReasonable(int64(sz)) {
- // Truncate. If this truly isn't enough, retryWithBuffer will error on the first run.
- return maxBufferSize
+func lookupGroup(groupname string) (*Group, error) {
+ f, err := os.Open(groupFile)
+ if err != nil {
+ return nil, err
}
- return syscall.Size_t(sz)
+ defer f.Close()
+ return findGroupName(groupname, f)
}
-type memBuffer struct {
- ptr *byte
- size syscall.Size_t
-}
-
-func alloc(kind bufferKind) *memBuffer {
- sz := kind.initialSize()
- b := make([]byte, sz)
- return &memBuffer{
- ptr: &b[0],
- size: sz,
+func lookupGroupId(id string) (*Group, error) {
+ f, err := os.Open(groupFile)
+ if err != nil {
+ return nil, err
}
+ defer f.Close()
+ return findGroupId(id, f)
}
-func (mb *memBuffer) resize(newSize syscall.Size_t) {
- b := make([]byte, newSize)
- mb.ptr = &b[0]
- mb.size = newSize
-}
-
-func (mb *memBuffer) free() {
- mb.ptr = nil
-}
-
-// retryWithBuffer repeatedly calls f(), increasing the size of the
-// buffer each time, until f succeeds, fails with a non-ERANGE error,
-// or the buffer exceeds a reasonable limit.
-func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error {
- for {
- errno := f()
- if errno == 0 {
- return nil
- } else if errno != syscall.ERANGE {
- return errno
- }
- newSize := buf.size * 2
- if !isSizeReasonable(int64(newSize)) {
- return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize)
- }
- buf.resize(newSize)
+func lookupUser(username string) (*User, error) {
+ f, err := os.Open(userFile)
+ if err != nil {
+ return nil, err
}
+ defer f.Close()
+ return findUsername(username, f)
}
-const maxBufferSize = 1 << 20
-
-func isSizeReasonable(sz int64) bool {
- return sz > 0 && sz <= maxBufferSize
+func lookupUserId(uid string) (*User, error) {
+ f, err := os.Open(userFile)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return findUserId(uid, f)
}
diff --git a/libgo/go/os/user/lookup_unix_test.go b/libgo/go/os/user/lookup_unix_test.go
new file mode 100644
index 0000000..02c88ab
--- /dev/null
+++ b/libgo/go/os/user/lookup_unix_test.go
@@ -0,0 +1,276 @@
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd !android,linux nacl netbsd openbsd solaris
+// +build !cgo
+
+package user
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+const testGroupFile = `# See the opendirectoryd(8) man page for additional
+# information about Open Directory.
+##
+nobody:*:-2:
+nogroup:*:-1:
+wheel:*:0:root
+emptyid:*::root
+invalidgid:*:notanumber:root
++plussign:*:20:root
+-minussign:*:21:root
+
+daemon:*:1:root
+ indented:*:7:
+# comment:*:4:found
+ # comment:*:4:found
+kmem:*:2:root
+`
+
+var groupTests = []struct {
+ in string
+ name string
+ gid string
+}{
+ {testGroupFile, "nobody", "-2"},
+ {testGroupFile, "kmem", "2"},
+ {testGroupFile, "notinthefile", ""},
+ {testGroupFile, "comment", ""},
+ {testGroupFile, "plussign", ""},
+ {testGroupFile, "+plussign", ""},
+ {testGroupFile, "-minussign", ""},
+ {testGroupFile, "minussign", ""},
+ {testGroupFile, "emptyid", ""},
+ {testGroupFile, "invalidgid", ""},
+ {testGroupFile, "indented", "7"},
+ {testGroupFile, "# comment", ""},
+ {"", "emptyfile", ""},
+}
+
+func TestFindGroupName(t *testing.T) {
+ for _, tt := range groupTests {
+ got, err := findGroupName(tt.name, strings.NewReader(tt.in))
+ if tt.gid == "" {
+ if err == nil {
+ t.Errorf("findGroupName(%s): got nil error, expected err", tt.name)
+ continue
+ }
+ switch terr := err.(type) {
+ case UnknownGroupError:
+ if terr.Error() != "group: unknown group "+tt.name {
+ t.Errorf("findGroupName(%s): got %v, want %v", tt.name, terr, tt.name)
+ }
+ default:
+ t.Errorf("findGroupName(%s): got unexpected error %v", tt.name, terr)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("findGroupName(%s): got unexpected error %v", tt.name, err)
+ }
+ if got.Gid != tt.gid {
+ t.Errorf("findGroupName(%s): got gid %v, want %s", tt.name, got.Gid, tt.gid)
+ }
+ if got.Name != tt.name {
+ t.Errorf("findGroupName(%s): got name %s, want %s", tt.name, got.Name, tt.name)
+ }
+ }
+ }
+}
+
+var groupIdTests = []struct {
+ in string
+ gid string
+ name string
+}{
+ {testGroupFile, "-2", "nobody"},
+ {testGroupFile, "2", "kmem"},
+ {testGroupFile, "notinthefile", ""},
+ {testGroupFile, "comment", ""},
+ {testGroupFile, "7", "indented"},
+ {testGroupFile, "4", ""},
+ {testGroupFile, "20", ""}, // row starts with a plus
+ {testGroupFile, "21", ""}, // row starts with a minus
+ {"", "emptyfile", ""},
+}
+
+func TestFindGroupId(t *testing.T) {
+ for _, tt := range groupIdTests {
+ got, err := findGroupId(tt.gid, strings.NewReader(tt.in))
+ if tt.name == "" {
+ if err == nil {
+ t.Errorf("findGroupId(%s): got nil error, expected err", tt.gid)
+ continue
+ }
+ switch terr := err.(type) {
+ case UnknownGroupIdError:
+ if terr.Error() != "group: unknown groupid "+tt.gid {
+ t.Errorf("findGroupId(%s): got %v, want %v", tt.name, terr, tt.name)
+ }
+ default:
+ t.Errorf("findGroupId(%s): got unexpected error %v", tt.name, terr)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("findGroupId(%s): got unexpected error %v", tt.name, err)
+ }
+ if got.Gid != tt.gid {
+ t.Errorf("findGroupId(%s): got gid %v, want %s", tt.name, got.Gid, tt.gid)
+ }
+ if got.Name != tt.name {
+ t.Errorf("findGroupId(%s): got name %s, want %s", tt.name, got.Name, tt.name)
+ }
+ }
+ }
+}
+
+const testUserFile = ` # Example user file
+root:x:0:0:root:/root:/bin/bash
+daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
+bin:x:2:3:bin:/bin:/usr/sbin/nologin
+ indented:x:3:3:indented:/dev:/usr/sbin/nologin
+sync:x:4:65534:sync:/bin:/bin/sync
+negative:x:-5:60:games:/usr/games:/usr/sbin/nologin
+man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
+allfields:x:6:12:mansplit,man2,man3,man4:/home/allfields:/usr/sbin/nologin
++plussign:x:8:10:man:/var/cache/man:/usr/sbin/nologin
+-minussign:x:9:10:man:/var/cache/man:/usr/sbin/nologin
+
+malformed:x:27:12 # more:colons:after:comment
+
+struid:x:notanumber:12 # more:colons:after:comment
+
+# commented:x:28:12:commented:/var/cache/man:/usr/sbin/nologin
+ # commentindented:x:29:12:commentindented:/var/cache/man:/usr/sbin/nologin
+
+struid2:x:30:badgid:struid2name:/home/struid:/usr/sbin/nologin
+`
+
+var userIdTests = []struct {
+ in string
+ uid string
+ name string
+}{
+ {testUserFile, "-5", "negative"},
+ {testUserFile, "2", "bin"},
+ {testUserFile, "100", ""}, // not in the file
+ {testUserFile, "8", ""}, // plus sign, glibc doesn't find it
+ {testUserFile, "9", ""}, // minus sign, glibc doesn't find it
+ {testUserFile, "27", ""}, // malformed
+ {testUserFile, "28", ""}, // commented out
+ {testUserFile, "29", ""}, // commented out, indented
+ {testUserFile, "3", "indented"},
+ {testUserFile, "30", ""}, // the Gid is not valid, shouldn't match
+ {"", "1", ""},
+}
+
+func TestInvalidUserId(t *testing.T) {
+ _, err := findUserId("notanumber", strings.NewReader(""))
+ if err == nil {
+ t.Fatalf("findUserId('notanumber'): got nil error")
+ }
+ if want := "user: invalid userid notanumber"; err.Error() != want {
+ t.Errorf("findUserId('notanumber'): got %v, want %s", err, want)
+ }
+}
+
+func TestLookupUserId(t *testing.T) {
+ for _, tt := range userIdTests {
+ got, err := findUserId(tt.uid, strings.NewReader(tt.in))
+ if tt.name == "" {
+ if err == nil {
+ t.Errorf("findUserId(%s): got nil error, expected err", tt.uid)
+ continue
+ }
+ switch terr := err.(type) {
+ case UnknownUserIdError:
+ if want := "user: unknown userid " + tt.uid; terr.Error() != want {
+ t.Errorf("findUserId(%s): got %v, want %v", tt.name, terr, want)
+ }
+ default:
+ t.Errorf("findUserId(%s): got unexpected error %v", tt.name, terr)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("findUserId(%s): got unexpected error %v", tt.name, err)
+ }
+ if got.Uid != tt.uid {
+ t.Errorf("findUserId(%s): got uid %v, want %s", tt.name, got.Uid, tt.uid)
+ }
+ if got.Username != tt.name {
+ t.Errorf("findUserId(%s): got name %s, want %s", tt.name, got.Username, tt.name)
+ }
+ }
+ }
+}
+
+func TestLookupUserPopulatesAllFields(t *testing.T) {
+ u, err := findUsername("allfields", strings.NewReader(testUserFile))
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := &User{
+ Username: "allfields",
+ Uid: "6",
+ Gid: "12",
+ Name: "mansplit",
+ HomeDir: "/home/allfields",
+ }
+ if !reflect.DeepEqual(u, want) {
+ t.Errorf("findUsername: got %#v, want %#v", u, want)
+ }
+}
+
+var userTests = []struct {
+ in string
+ name string
+ uid string
+}{
+ {testUserFile, "negative", "-5"},
+ {testUserFile, "bin", "2"},
+ {testUserFile, "notinthefile", ""},
+ {testUserFile, "indented", "3"},
+ {testUserFile, "plussign", ""},
+ {testUserFile, "+plussign", ""},
+ {testUserFile, "minussign", ""},
+ {testUserFile, "-minussign", ""},
+ {testUserFile, " indented", ""},
+ {testUserFile, "commented", ""},
+ {testUserFile, "commentindented", ""},
+ {testUserFile, "malformed", ""},
+ {testUserFile, "# commented", ""},
+ {"", "emptyfile", ""},
+}
+
+func TestLookupUser(t *testing.T) {
+ for _, tt := range userTests {
+ got, err := findUsername(tt.name, strings.NewReader(tt.in))
+ if tt.uid == "" {
+ if err == nil {
+ t.Errorf("lookupUser(%s): got nil error, expected err", tt.uid)
+ continue
+ }
+ switch terr := err.(type) {
+ case UnknownUserError:
+ if want := "user: unknown user " + tt.name; terr.Error() != want {
+ t.Errorf("lookupUser(%s): got %v, want %v", tt.name, terr, want)
+ }
+ default:
+ t.Errorf("lookupUser(%s): got unexpected error %v", tt.name, terr)
+ }
+ } else {
+ if err != nil {
+ t.Fatalf("lookupUser(%s): got unexpected error %v", tt.name, err)
+ }
+ if got.Uid != tt.uid {
+ t.Errorf("lookupUser(%s): got uid %v, want %s", tt.name, got.Uid, tt.uid)
+ }
+ if got.Username != tt.name {
+ t.Errorf("lookupUser(%s): got name %s, want %s", tt.name, got.Username, tt.name)
+ }
+ }
+ }
+}
diff --git a/libgo/go/os/user/user_test.go b/libgo/go/os/user/user_test.go
index 9d8d94d..b3aeed8 100644
--- a/libgo/go/os/user/user_test.go
+++ b/libgo/go/os/user/user_test.go
@@ -16,9 +16,6 @@ func checkUser(t *testing.T) {
}
func TestCurrent(t *testing.T) {
- if runtime.GOOS == "android" {
- t.Skipf("skipping on %s", runtime.GOOS)
- }
u, err := Current()
if err != nil {
t.Fatalf("Current: %v (got %#v)", err, u)
@@ -31,6 +28,12 @@ func TestCurrent(t *testing.T) {
}
}
+func BenchmarkCurrent(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Current()
+ }
+}
+
func compare(t *testing.T, want, got *User) {
if want.Uid != got.Uid {
t.Errorf("got Uid=%q; want %q", got.Uid, want.Uid)
@@ -64,6 +67,9 @@ func TestLookup(t *testing.T) {
if err != nil {
t.Fatalf("Current: %v", err)
}
+ // TODO: Lookup() has a fast path that calls Current() and returns if the
+ // usernames match, so this test does not exercise very much. It would be
+ // good to try and test finding a different user than the current user.
got, err := Lookup(want.Username)
if err != nil {
t.Fatalf("Lookup: %v", err)
diff --git a/libgo/go/os/wait_unimp.go b/libgo/go/os/wait_unimp.go
index 0378b83..98243b5 100644
--- a/libgo/go/os/wait_unimp.go
+++ b/libgo/go/os/wait_unimp.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix dragonfly nacl netbsd openbsd solaris
+// +build aix darwin dragonfly nacl netbsd openbsd solaris
package os
diff --git a/libgo/go/os/wait_waitid.go b/libgo/go/os/wait_waitid.go
index 3337395..5a62b27 100644
--- a/libgo/go/os/wait_waitid.go
+++ b/libgo/go/os/wait_waitid.go
@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build darwin linux
+// We used to used this code for Darwin, but according to issue #19314
+// waitid returns if the process is stopped, even when using WEXITED.
+
+// +build linux
package os