diff options
author | Ian Lance Taylor <iant@golang.org> | 2017-09-14 17:11:35 +0000 |
---|---|---|
committer | Ian Lance Taylor <ian@gcc.gnu.org> | 2017-09-14 17:11:35 +0000 |
commit | bc998d034f45d1828a8663b2eed928faf22a7d01 (patch) | |
tree | 8d262a22ca7318f4bcd64269fe8fe9e45bcf8d0f /libgo/go/os | |
parent | a41a6142df74219f596e612d3a7775f68ca6e96f (diff) | |
download | gcc-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')
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 |