summaryrefslogtreecommitdiff
path: root/src/pkg/os
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/os')
-rw-r--r--src/pkg/os/dir_unix.go2
-rw-r--r--src/pkg/os/doc.go3
-rw-r--r--src/pkg/os/env_unix_test.go2
-rw-r--r--src/pkg/os/error_unix.go2
-rw-r--r--src/pkg/os/exec/exec.go88
-rw-r--r--src/pkg/os/exec/exec_test.go151
-rw-r--r--src/pkg/os/exec/lp_unix.go2
-rw-r--r--src/pkg/os/exec/lp_unix_test.go2
-rw-r--r--src/pkg/os/exec/lp_windows_test.go474
-rw-r--r--src/pkg/os/exec_plan9.go9
-rw-r--r--src/pkg/os/exec_posix.go2
-rw-r--r--src/pkg/os/exec_unix.go5
-rw-r--r--src/pkg/os/file.go8
-rw-r--r--src/pkg/os/file_plan9.go33
-rw-r--r--src/pkg/os/file_posix.go7
-rw-r--r--src/pkg/os/file_unix.go59
-rw-r--r--src/pkg/os/file_windows.go15
-rw-r--r--src/pkg/os/getwd.go28
-rw-r--r--src/pkg/os/os_test.go156
-rw-r--r--src/pkg/os/os_unix_test.go40
-rw-r--r--src/pkg/os/path_test.go5
-rw-r--r--src/pkg/os/path_unix.go2
-rw-r--r--src/pkg/os/pipe_bsd.go2
-rw-r--r--src/pkg/os/signal/example_test.go4
-rw-r--r--src/pkg/os/signal/sig.s2
-rw-r--r--src/pkg/os/signal/signal_test.go2
-rw-r--r--src/pkg/os/signal/signal_unix.go2
-rw-r--r--src/pkg/os/signal/signal_windows_test.go11
-rw-r--r--src/pkg/os/stat_nacl.go62
-rw-r--r--src/pkg/os/stat_solaris.go61
-rw-r--r--src/pkg/os/sys_bsd.go2
-rw-r--r--src/pkg/os/sys_darwin.go31
-rw-r--r--src/pkg/os/sys_freebsd.go23
-rw-r--r--src/pkg/os/sys_nacl.go9
-rw-r--r--src/pkg/os/sys_solaris.go11
-rw-r--r--src/pkg/os/sys_unix.go11
-rw-r--r--src/pkg/os/user/lookup_unix.go2
37 files changed, 1004 insertions, 326 deletions
diff --git a/src/pkg/os/dir_unix.go b/src/pkg/os/dir_unix.go
index 9fa7ad664..d353e405e 100644
--- a/src/pkg/os/dir_unix.go
+++ b/src/pkg/os/dir_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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package os
diff --git a/src/pkg/os/doc.go b/src/pkg/os/doc.go
index a954e313d..389a8eb14 100644
--- a/src/pkg/os/doc.go
+++ b/src/pkg/os/doc.go
@@ -39,11 +39,14 @@ func (p *Process) Kill() error {
// Wait waits for the Process to exit, and then returns a
// ProcessState describing its status and an error, if any.
// Wait releases any resources associated with the Process.
+// On most operating systems, the Process must be a child
+// of the current process or an error will be returned.
func (p *Process) Wait() (*ProcessState, error) {
return p.wait()
}
// Signal sends a signal to the Process.
+// Sending Interrupt on Windows is not implemented.
func (p *Process) Signal(sig Signal) error {
return p.signal(sig)
}
diff --git a/src/pkg/os/env_unix_test.go b/src/pkg/os/env_unix_test.go
index e16d71a64..5ec07ee1b 100644
--- a/src/pkg/os/env_unix_test.go
+++ b/src/pkg/os/env_unix_test.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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package os_test
diff --git a/src/pkg/os/error_unix.go b/src/pkg/os/error_unix.go
index 6250349e5..f2aabbb45 100644
--- a/src/pkg/os/error_unix.go
+++ b/src/pkg/os/error_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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package os
diff --git a/src/pkg/os/exec/exec.go b/src/pkg/os/exec/exec.go
index 491cc242b..a70ed0d20 100644
--- a/src/pkg/os/exec/exec.go
+++ b/src/pkg/os/exec/exec.go
@@ -12,7 +12,10 @@ import (
"errors"
"io"
"os"
+ "path/filepath"
+ "runtime"
"strconv"
+ "strings"
"sync"
"syscall"
)
@@ -33,7 +36,8 @@ type Cmd struct {
// Path is the path of the command to run.
//
// This is the only field that must be set to a non-zero
- // value.
+ // value. If Path is relative, it is evaluated relative
+ // to Dir.
Path string
// Args holds command line arguments, including the command as Args[0].
@@ -84,7 +88,7 @@ type Cmd struct {
// available after a call to Wait or Run.
ProcessState *os.ProcessState
- err error // last error (from LookPath, stdin, stdout, stderr)
+ lookPathErr error // LookPath error, if any.
finished bool // when Wait was called
childFiles []*os.File
closeAfterStart []io.Closer
@@ -96,8 +100,7 @@ type Cmd struct {
// Command returns the Cmd struct to execute the named program with
// the given arguments.
//
-// It sets Path and Args in the returned structure and zeroes the
-// other fields.
+// It sets only the Path and Args in the returned structure.
//
// If name contains no path separators, Command uses LookPath to
// resolve the path to a complete name if possible. Otherwise it uses
@@ -107,19 +110,22 @@ type Cmd struct {
// followed by the elements of arg, so arg should not include the
// command name itself. For example, Command("echo", "hello")
func Command(name string, arg ...string) *Cmd {
- aname, err := LookPath(name)
- if err != nil {
- aname = name
- }
- return &Cmd{
- Path: aname,
+ cmd := &Cmd{
+ Path: name,
Args: append([]string{name}, arg...),
- err: err,
}
+ if filepath.Base(name) == name {
+ if lp, err := LookPath(name); err != nil {
+ cmd.lookPathErr = err
+ } else {
+ cmd.Path = lp
+ }
+ }
+ return cmd
}
// interfaceEqual protects against panics from doing equality tests on
-// two interfaces with non-comparable underlying types
+// two interfaces with non-comparable underlying types.
func interfaceEqual(a, b interface{}) bool {
defer func() {
recover()
@@ -233,12 +239,50 @@ func (c *Cmd) Run() error {
return c.Wait()
}
+// lookExtensions finds windows executable by its dir and path.
+// It uses LookPath to try appropriate extensions.
+// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`.
+func lookExtensions(path, dir string) (string, error) {
+ if filepath.Base(path) == path {
+ path = filepath.Join(".", path)
+ }
+ if dir == "" {
+ return LookPath(path)
+ }
+ if filepath.VolumeName(path) != "" {
+ return LookPath(path)
+ }
+ if len(path) > 1 && os.IsPathSeparator(path[0]) {
+ return LookPath(path)
+ }
+ dirandpath := filepath.Join(dir, path)
+ // We assume that LookPath will only add file extension.
+ lp, err := LookPath(dirandpath)
+ if err != nil {
+ return "", err
+ }
+ ext := strings.TrimPrefix(lp, dirandpath)
+ return path + ext, nil
+}
+
// Start starts the specified command but does not wait for it to complete.
+//
+// The Wait method will return the exit code and release associated resources
+// once the command exits.
func (c *Cmd) Start() error {
- if c.err != nil {
+ if c.lookPathErr != nil {
c.closeDescriptors(c.closeAfterStart)
c.closeDescriptors(c.closeAfterWait)
- return c.err
+ return c.lookPathErr
+ }
+ if runtime.GOOS == "windows" {
+ lp, err := lookExtensions(c.Path, c.Dir)
+ if err != nil {
+ c.closeDescriptors(c.closeAfterStart)
+ c.closeDescriptors(c.closeAfterWait)
+ return err
+ }
+ c.Path = lp
}
if c.Process != nil {
return errors.New("exec: already started")
@@ -300,6 +344,8 @@ func (e *ExitError) Error() string {
// 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.
+//
+// Wait releases any resources associated with the Cmd.
func (c *Cmd) Wait() error {
if c.Process == nil {
return errors.New("exec: not started")
@@ -383,15 +429,17 @@ func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
type closeOnce struct {
*os.File
- close sync.Once
- closeErr error
+ once sync.Once
+ err error
}
func (c *closeOnce) Close() error {
- c.close.Do(func() {
- c.closeErr = c.File.Close()
- })
- return c.closeErr
+ c.once.Do(c.close)
+ return c.err
+}
+
+func (c *closeOnce) close() {
+ c.err = c.File.Close()
}
// StdoutPipe returns a pipe that will be connected to the command's
diff --git a/src/pkg/os/exec/exec_test.go b/src/pkg/os/exec/exec_test.go
index c380d6506..6f77ac38a 100644
--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.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.
-package exec
+// Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec
+// circular dependency on non-cgo darwin.
+
+package exec_test
import (
"bufio"
@@ -10,10 +13,12 @@ import (
"fmt"
"io"
"io/ioutil"
+ "log"
"net"
"net/http"
"net/http/httptest"
"os"
+ "os/exec"
"path/filepath"
"runtime"
"strconv"
@@ -22,16 +27,19 @@ import (
"time"
)
-func helperCommand(s ...string) *Cmd {
+func helperCommand(t *testing.T, s ...string) *exec.Cmd {
+ if runtime.GOOS == "nacl" {
+ t.Skip("skipping on nacl")
+ }
cs := []string{"-test.run=TestHelperProcess", "--"}
cs = append(cs, s...)
- cmd := Command(os.Args[0], cs...)
+ cmd := exec.Command(os.Args[0], cs...)
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
return cmd
}
func TestEcho(t *testing.T) {
- bs, err := helperCommand("echo", "foo bar", "baz").Output()
+ bs, err := helperCommand(t, "echo", "foo bar", "baz").Output()
if err != nil {
t.Errorf("echo: %v", err)
}
@@ -40,10 +48,37 @@ func TestEcho(t *testing.T) {
}
}
+func TestCommandRelativeName(t *testing.T) {
+ // Run our own binary as a relative path
+ // (e.g. "_test/exec.test") our parent directory.
+ base := filepath.Base(os.Args[0]) // "exec.test"
+ dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test"
+ if dir == "." {
+ t.Skip("skipping; running test at root somehow")
+ }
+ parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec"
+ dirBase := filepath.Base(dir) // "_test"
+ if dirBase == "." {
+ t.Skipf("skipping; unexpected shallow dir of %q", dir)
+ }
+
+ cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo")
+ cmd.Dir = parentDir
+ cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
+
+ out, err := cmd.Output()
+ if err != nil {
+ t.Errorf("echo: %v", err)
+ }
+ if g, e := string(out), "foo\n"; g != e {
+ t.Errorf("echo: want %q, got %q", e, g)
+ }
+}
+
func TestCatStdin(t *testing.T) {
// Cat, testing stdin and stdout.
input := "Input string\nLine 2"
- p := helperCommand("cat")
+ p := helperCommand(t, "cat")
p.Stdin = strings.NewReader(input)
bs, err := p.Output()
if err != nil {
@@ -57,9 +92,9 @@ func TestCatStdin(t *testing.T) {
func TestCatGoodAndBadFile(t *testing.T) {
// Testing combined output and error values.
- bs, err := helperCommand("cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
- if _, ok := err.(*ExitError); !ok {
- t.Errorf("expected *ExitError from cat combined; got %T: %v", err, err)
+ bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
+ if _, ok := err.(*exec.ExitError); !ok {
+ t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err)
}
s := string(bs)
sp := strings.SplitN(s, "\n", 2)
@@ -77,7 +112,7 @@ func TestCatGoodAndBadFile(t *testing.T) {
func TestNoExistBinary(t *testing.T) {
// Can't run a non-existent binary
- err := Command("/no-exist-binary").Run()
+ err := exec.Command("/no-exist-binary").Run()
if err == nil {
t.Error("expected error from /no-exist-binary")
}
@@ -85,19 +120,19 @@ func TestNoExistBinary(t *testing.T) {
func TestExitStatus(t *testing.T) {
// Test that exit values are returned correctly
- cmd := helperCommand("exit", "42")
+ cmd := helperCommand(t, "exit", "42")
err := cmd.Run()
want := "exit status 42"
switch runtime.GOOS {
case "plan9":
want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid())
}
- if werr, ok := err.(*ExitError); ok {
+ if werr, ok := err.(*exec.ExitError); ok {
if s := werr.Error(); s != want {
t.Errorf("from exit 42 got exit %q, want %q", s, want)
}
} else {
- t.Fatalf("expected *ExitError from exit 42; got %T: %v", err, err)
+ t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err)
}
}
@@ -108,7 +143,7 @@ func TestPipes(t *testing.T) {
}
}
// Cat, testing stdin and stdout.
- c := helperCommand("pipetest")
+ c := helperCommand(t, "pipetest")
stdin, err := c.StdinPipe()
check("StdinPipe", err)
stdout, err := c.StdoutPipe()
@@ -161,7 +196,7 @@ func TestStdinClose(t *testing.T) {
t.Fatalf("%s: %v", what, err)
}
}
- cmd := helperCommand("stdinClose")
+ cmd := helperCommand(t, "stdinClose")
stdin, err := cmd.StdinPipe()
check("StdinPipe", err)
// Check that we can access methods of the underlying os.File.`
@@ -182,9 +217,9 @@ func TestStdinClose(t *testing.T) {
// Issue 5071
func TestPipeLookPathLeak(t *testing.T) {
- fd0 := numOpenFDS(t)
+ fd0, lsof0 := numOpenFDS(t)
for i := 0; i < 4; i++ {
- cmd := Command("something-that-does-not-exist-binary")
+ cmd := exec.Command("something-that-does-not-exist-binary")
cmd.StdoutPipe()
cmd.StderrPipe()
cmd.StdinPipe()
@@ -192,19 +227,30 @@ func TestPipeLookPathLeak(t *testing.T) {
t.Fatal("unexpected success")
}
}
- fdGrowth := numOpenFDS(t) - fd0
- if fdGrowth > 2 {
- t.Errorf("leaked %d fds; want ~0", fdGrowth)
+ 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) int {
- lsof, err := Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
+func numOpenFDS(t *testing.T) (n int, lsof []byte) {
+ lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
if err != nil {
t.Skip("skipping test; error finding or running lsof")
- return 0
}
- return bytes.Count(lsof, []byte("\n"))
+ return bytes.Count(lsof, []byte("\n")), lsof
}
var testedAlreadyLeaked = false
@@ -270,7 +316,7 @@ func TestExtraFilesFDShuffle(t *testing.T) {
// Moving this test case around within the overall tests may
// affect the FDs obtained and hence the checks to catch these cases.
npipes := 2
- c := helperCommand("extraFilesAndPipes", strconv.Itoa(npipes+1))
+ c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1))
rd, wr, _ := os.Pipe()
defer rd.Close()
if rd.Fd() != 3 {
@@ -370,11 +416,15 @@ func TestExtraFiles(t *testing.T) {
// Force TLS root certs to be loaded (which might involve
// cgo), to make sure none of that potential C code leaks fds.
- ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte("Hello"))
- }))
+ ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+ // quiet expected TLS handshake error "remote error: bad certificate"
+ ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
+ ts.StartTLS()
defer ts.Close()
- http.Get(ts.URL) // ignore result; just calling to force root cert loading
+ _, err = http.Get(ts.URL)
+ if err == nil {
+ t.Errorf("success trying to fetch %s; want an error", ts.URL)
+ }
tf, err := ioutil.TempFile("", "")
if err != nil {
@@ -393,7 +443,7 @@ func TestExtraFiles(t *testing.T) {
t.Fatalf("Seek: %v", err)
}
- c := helperCommand("read3")
+ c := helperCommand(t, "read3")
var stdout, stderr bytes.Buffer
c.Stdout = &stdout
c.Stderr = &stderr
@@ -425,7 +475,7 @@ func TestExtraFilesRace(t *testing.T) {
}
return f
}
- runCommand := func(c *Cmd, out chan<- string) {
+ runCommand := func(c *exec.Cmd, out chan<- string) {
bout, err := c.CombinedOutput()
if err != nil {
out <- "ERROR:" + err.Error()
@@ -436,10 +486,10 @@ func TestExtraFilesRace(t *testing.T) {
for i := 0; i < 10; i++ {
la := listen()
- ca := helperCommand("describefiles")
+ ca := helperCommand(t, "describefiles")
ca.ExtraFiles = []*os.File{listenerFile(la)}
lb := listen()
- cb := helperCommand("describefiles")
+ cb := helperCommand(t, "describefiles")
cb.ExtraFiles = []*os.File{listenerFile(lb)}
ares := make(chan string)
bres := make(chan string)
@@ -476,6 +526,8 @@ func TestHelperProcess(*testing.T) {
switch runtime.GOOS {
case "dragonfly", "freebsd", "netbsd", "openbsd":
ofcmd = "fstat"
+ case "plan9":
+ ofcmd = "/bin/cat"
}
args := os.Args
@@ -566,6 +618,14 @@ func TestHelperProcess(*testing.T) {
// the cloned file descriptors that result from opening
// /dev/urandom.
// http://golang.org/issue/3955
+ case "plan9":
+ // TODO(0intro): Determine why Plan 9 is leaking
+ // file descriptors.
+ // http://golang.org/issue/7118
+ case "solaris":
+ // TODO(aram): This fails on Solaris because libc opens
+ // its own files, as it sees fit. Darwin does the same,
+ // see: http://golang.org/issue/2603
default:
// Now verify that there are no other open fds.
var files []*os.File
@@ -577,7 +637,14 @@ func TestHelperProcess(*testing.T) {
}
if got := f.Fd(); got != wantfd {
fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd)
- out, _ := Command(ofcmd, "-p", fmt.Sprint(os.Getpid())).CombinedOutput()
+ var args []string
+ switch runtime.GOOS {
+ case "plan9":
+ args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())}
+ default:
+ args = []string{"-p", fmt.Sprint(os.Getpid())}
+ }
+ out, _ := exec.Command(ofcmd, args...).CombinedOutput()
fmt.Print(string(out))
os.Exit(1)
}
@@ -634,6 +701,24 @@ func TestHelperProcess(*testing.T) {
}
fmt.Fprintf(os.Stderr, "child: %s", response)
os.Exit(0)
+ case "exec":
+ cmd := exec.Command(args[1])
+ cmd.Dir = args[0]
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output))
+ os.Exit(1)
+ }
+ fmt.Printf("%s", string(output))
+ os.Exit(0)
+ case "lookpath":
+ p, err := exec.LookPath(args[0])
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err)
+ os.Exit(1)
+ }
+ fmt.Print(p)
+ os.Exit(0)
default:
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
os.Exit(2)
diff --git a/src/pkg/os/exec/lp_unix.go b/src/pkg/os/exec/lp_unix.go
index 7ff2d201b..3f895d5b3 100644
--- a/src/pkg/os/exec/lp_unix.go
+++ b/src/pkg/os/exec/lp_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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package exec
diff --git a/src/pkg/os/exec/lp_unix_test.go b/src/pkg/os/exec/lp_unix_test.go
index f1ab6deff..051db664a 100644
--- a/src/pkg/os/exec/lp_unix_test.go
+++ b/src/pkg/os/exec/lp_unix_test.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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package exec
diff --git a/src/pkg/os/exec/lp_windows_test.go b/src/pkg/os/exec/lp_windows_test.go
index 385dd331b..72df03ed2 100644
--- a/src/pkg/os/exec/lp_windows_test.go
+++ b/src/pkg/os/exec/lp_windows_test.go
@@ -13,10 +13,48 @@ import (
"strconv"
"strings"
"testing"
- "text/template"
)
+func installExe(t *testing.T, dest, src string) {
+ fsrc, err := os.Open(src)
+ if err != nil {
+ t.Fatal("os.Open failed: ", err)
+ }
+ defer fsrc.Close()
+ fdest, err := os.Create(dest)
+ if err != nil {
+ t.Fatal("os.Create failed: ", err)
+ }
+ defer fdest.Close()
+ _, err = io.Copy(fdest, fsrc)
+ if err != nil {
+ t.Fatal("io.Copy failed: ", err)
+ }
+}
+
+func installBat(t *testing.T, dest string) {
+ f, err := os.Create(dest)
+ if err != nil {
+ t.Fatalf("failed to create batch file: %v", err)
+ }
+ defer f.Close()
+ fmt.Fprintf(f, "@echo %s\n", dest)
+}
+
+func installProg(t *testing.T, dest, srcExe string) {
+ err := os.MkdirAll(filepath.Dir(dest), 0700)
+ if err != nil {
+ t.Fatal("os.MkdirAll failed: ", err)
+ }
+ if strings.ToLower(filepath.Ext(dest)) == ".bat" {
+ installBat(t, dest)
+ return
+ }
+ installExe(t, dest, srcExe)
+}
+
type lookPathTest struct {
+ rootDir string
PATH string
PATHEXT string
files []string
@@ -24,13 +62,97 @@ type lookPathTest struct {
fails bool // test is expected to fail
}
-// PrefixPATH returns p.PATH with every element prefixed by prefix.
-func (t lookPathTest) PrefixPATH(prefix string) string {
- a := strings.SplitN(t.PATH, ";", -1)
- for i := range a {
- a[i] = filepath.Join(prefix, a[i])
+func (test lookPathTest) runProg(t *testing.T, env []string, args ...string) (string, error) {
+ cmd := Command(args[0], args[1:]...)
+ cmd.Env = env
+ cmd.Dir = test.rootDir
+ args[0] = filepath.Base(args[0])
+ cmdText := fmt.Sprintf("%q command", strings.Join(args, " "))
+ out, err := cmd.CombinedOutput()
+ if (err != nil) != test.fails {
+ if test.fails {
+ t.Fatalf("test=%+v: %s succeeded, but expected to fail", test, cmdText)
+ }
+ t.Fatalf("test=%+v: %s failed, but expected to succeed: %v - %v", test, cmdText, err, string(out))
+ }
+ if err != nil {
+ return "", fmt.Errorf("test=%+v: %s failed: %v - %v", test, cmdText, err, string(out))
+ }
+ // normalise program output
+ p := string(out)
+ // trim terminating \r and \n that batch file outputs
+ for len(p) > 0 && (p[len(p)-1] == '\n' || p[len(p)-1] == '\r') {
+ p = p[:len(p)-1]
+ }
+ if !filepath.IsAbs(p) {
+ return p, nil
+ }
+ if p[:len(test.rootDir)] != test.rootDir {
+ t.Fatalf("test=%+v: %s output is wrong: %q must have %q prefix", test, cmdText, p, test.rootDir)
+ }
+ return p[len(test.rootDir)+1:], nil
+}
+
+func updateEnv(env []string, name, value string) []string {
+ for i, e := range env {
+ if strings.HasPrefix(strings.ToUpper(e), name+"=") {
+ env[i] = name + "=" + value
+ return env
+ }
+ }
+ return append(env, name+"="+value)
+}
+
+func createEnv(dir, PATH, PATHEXT string) []string {
+ env := os.Environ()
+ env = updateEnv(env, "PATHEXT", PATHEXT)
+ // Add dir in front of every directory in the PATH.
+ dirs := splitList(PATH)
+ for i := range dirs {
+ dirs[i] = filepath.Join(dir, dirs[i])
+ }
+ path := strings.Join(dirs, ";")
+ env = updateEnv(env, "PATH", path)
+ return env
+}
+
+// createFiles copies srcPath file into multiply files.
+// It uses dir as preifx for all destination files.
+func createFiles(t *testing.T, dir string, files []string, srcPath string) {
+ for _, f := range files {
+ installProg(t, filepath.Join(dir, f), srcPath)
+ }
+}
+
+func (test lookPathTest) run(t *testing.T, tmpdir, printpathExe string) {
+ test.rootDir = tmpdir
+ createFiles(t, test.rootDir, test.files, printpathExe)
+ env := createEnv(test.rootDir, test.PATH, test.PATHEXT)
+ // Run "cmd.exe /c test.searchFor" with new environment and
+ // work directory set. All candidates are copies of printpath.exe.
+ // These will output their program paths when run.
+ should, errCmd := test.runProg(t, env, "cmd", "/c", test.searchFor)
+ // Run the lookpath program with new environment and work directory set.
+ env = append(env, "GO_WANT_HELPER_PROCESS=1")
+ have, errLP := test.runProg(t, env, os.Args[0], "-test.run=TestHelperProcess", "--", "lookpath", test.searchFor)
+ // Compare results.
+ if errCmd == nil && errLP == nil {
+ // both succeeded
+ if should != have {
+ t.Fatalf("test=%+v failed: expected to find %q, but found %q", test, should, have)
+ }
+ return
+ }
+ if errCmd != nil && errLP != nil {
+ // both failed -> continue
+ return
+ }
+ if errCmd != nil {
+ t.Fatal(errCmd)
+ }
+ if errLP != nil {
+ t.Fatal(errLP)
}
- return strings.Join(a, ";")
}
var lookPathTests = []lookPathTest{
@@ -179,190 +301,250 @@ var lookPathTests = []lookPathTest{
},
}
-func updateEnv(env []string, name, value string) []string {
- for i, e := range env {
- if strings.HasPrefix(strings.ToUpper(e), name+"=") {
- env[i] = name + "=" + value
- return env
- }
- }
- return append(env, name+"="+value)
-}
-
-func installExe(t *testing.T, dest, src string) {
- fsrc, err := os.Open(src)
- if err != nil {
- t.Fatal("os.Open failed: ", err)
- }
- defer fsrc.Close()
- fdest, err := os.Create(dest)
+func TestLookPath(t *testing.T) {
+ tmp, err := ioutil.TempDir("", "TestLookPath")
if err != nil {
- t.Fatal("os.Create failed: ", err)
+ t.Fatal("TempDir failed: ", err)
}
- defer fdest.Close()
- _, err = io.Copy(fdest, fsrc)
- if err != nil {
- t.Fatal("io.Copy failed: ", err)
+ defer os.RemoveAll(tmp)
+
+ printpathExe := buildPrintPathExe(t, tmp)
+
+ // Run all tests.
+ for i, test := range lookPathTests {
+ dir := filepath.Join(tmp, "d"+strconv.Itoa(i))
+ err := os.Mkdir(dir, 0700)
+ if err != nil {
+ t.Fatal("Mkdir failed: ", err)
+ }
+ test.run(t, dir, printpathExe)
}
}
-func installBat(t *testing.T, dest string) {
- f, err := os.Create(dest)
- if err != nil {
- t.Fatalf("failed to create batch file: %v", err)
- }
- defer f.Close()
- fmt.Fprintf(f, "@echo %s\n", dest)
+type commandTest struct {
+ PATH string
+ files []string
+ dir string
+ arg0 string
+ want string
+ fails bool // test is expected to fail
}
-func installProg(t *testing.T, dest, srcExe string) {
- err := os.MkdirAll(filepath.Dir(dest), 0700)
+func (test commandTest) isSuccess(rootDir, output string, err error) error {
if err != nil {
- t.Fatal("os.MkdirAll failed: ", err)
+ return fmt.Errorf("test=%+v: exec: %v %v", test, err, output)
}
- if strings.ToLower(filepath.Ext(dest)) == ".bat" {
- installBat(t, dest)
- return
+ path := output
+ if path[:len(rootDir)] != rootDir {
+ return fmt.Errorf("test=%+v: %q must have %q prefix", test, path, rootDir)
}
- installExe(t, dest, srcExe)
+ path = path[len(rootDir)+1:]
+ if path != test.want {
+ return fmt.Errorf("test=%+v: want %q, got %q", test, test.want, path)
+ }
+ return nil
}
-func runProg(t *testing.T, test lookPathTest, env []string, dir string, args ...string) (string, error) {
- cmd := Command(args[0], args[1:]...)
+func (test commandTest) runOne(rootDir string, env []string, dir, arg0 string) error {
+ cmd := Command(os.Args[0], "-test.run=TestHelperProcess", "--", "exec", dir, arg0)
+ cmd.Dir = rootDir
cmd.Env = env
- cmd.Dir = dir
- args[0] = filepath.Base(args[0])
- cmdText := fmt.Sprintf("%q command", strings.Join(args, " "))
- out, err := cmd.CombinedOutput()
+ output, err := cmd.CombinedOutput()
+ err = test.isSuccess(rootDir, string(output), err)
if (err != nil) != test.fails {
if test.fails {
- t.Fatalf("test=%+v: %s succeeded, but expected to fail", test, cmdText)
+ return fmt.Errorf("test=%+v: succeeded, but expected to fail", test)
}
- t.Fatalf("test=%+v: %s failed, but expected to succeed: %v - %v", test, cmdText, err, string(out))
- }
- if err != nil {
- return "", fmt.Errorf("test=%+v: %s failed: %v - %v", test, cmdText, err, string(out))
- }
- // normalise program output
- p := string(out)
- // trim terminating \r and \n that batch file outputs
- for len(p) > 0 && (p[len(p)-1] == '\n' || p[len(p)-1] == '\r') {
- p = p[:len(p)-1]
- }
- if !filepath.IsAbs(p) {
- return p, nil
- }
- if p[:len(dir)] != dir {
- t.Fatalf("test=%+v: %s output is wrong: %q must have %q prefix", test, cmdText, p, dir)
+ return err
}
- return p[len(dir)+1:], nil
+ return nil
}
-func testLookPath(t *testing.T, test lookPathTest, tmpdir, lookpathExe, printpathExe string) {
- // Create files listed in test.files in tmp directory.
- for i := range test.files {
- installProg(t, filepath.Join(tmpdir, test.files[i]), printpathExe)
- }
- // Create environment with test.PATH and test.PATHEXT set.
- env := os.Environ()
- env = updateEnv(env, "PATH", test.PrefixPATH(tmpdir))
- env = updateEnv(env, "PATHEXT", test.PATHEXT)
- // Run "cmd.exe /c test.searchFor" with new environment and
- // work directory set. All candidates are copies of printpath.exe.
- // These will output their program paths when run.
- should, errCmd := runProg(t, test, env, tmpdir, "cmd", "/c", test.searchFor)
- // Run the lookpath program with new environment and work directory set.
- have, errLP := runProg(t, test, env, tmpdir, lookpathExe, test.searchFor)
- // Compare results.
- if errCmd == nil && errLP == nil {
- // both succeeded
- if should != have {
- // t.Fatalf("test=%+v failed: expected to find %v, but found %v", test, should, have)
- t.Fatalf("test=%+v failed: expected to find %q, but found %q", test, should, have)
- }
- return
- }
- if errCmd != nil && errLP != nil {
- // both failed -> continue
- return
- }
- if errCmd != nil {
- t.Fatal(errCmd)
- }
- if errLP != nil {
- t.Fatal(errLP)
+func (test commandTest) run(t *testing.T, rootDir, printpathExe string) {
+ createFiles(t, rootDir, test.files, printpathExe)
+ PATHEXT := `.COM;.EXE;.BAT`
+ env := createEnv(rootDir, test.PATH, PATHEXT)
+ env = append(env, "GO_WANT_HELPER_PROCESS=1")
+ err := test.runOne(rootDir, env, test.dir, test.arg0)
+ if err != nil {
+ t.Error(err)
}
}
-func buildExe(t *testing.T, templ, dir, name string) string {
- srcname := name + ".go"
- f, err := os.Create(filepath.Join(dir, srcname))
- if err != nil {
- t.Fatalf("failed to create source: %v", err)
- }
- err = template.Must(template.New("template").Parse(templ)).Execute(f, nil)
- f.Close()
- if err != nil {
- t.Fatalf("failed to execute template: %v", err)
- }
- outname := name + ".exe"
- cmd := Command("go", "build", "-o", outname, srcname)
- cmd.Dir = dir
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatalf("failed to build executable: %v - %v", err, string(out))
- }
- return filepath.Join(dir, outname)
+var commandTests = []commandTest{
+ // testing commands with no slash, like `a.exe`
+ {
+ // should find a.exe in current directory
+ files: []string{`a.exe`},
+ arg0: `a.exe`,
+ want: `a.exe`,
+ },
+ {
+ // like above, but add PATH in attempt to break the test
+ PATH: `p2;p`,
+ files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
+ arg0: `a.exe`,
+ want: `a.exe`,
+ },
+ {
+ // like above, but use "a" instead of "a.exe" for command
+ PATH: `p2;p`,
+ files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
+ arg0: `a`,
+ want: `a.exe`,
+ },
+ // testing commands with slash, like `.\a.exe`
+ {
+ // should find p\a.exe
+ files: []string{`p\a.exe`},
+ arg0: `p\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but adding `.` in front of executable should still be OK
+ files: []string{`p\a.exe`},
+ arg0: `.\p\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but with PATH added in attempt to break it
+ PATH: `p2`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ arg0: `p\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but make sure .exe is tried even for commands with slash
+ PATH: `p2`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ arg0: `p\a`,
+ want: `p\a.exe`,
+ },
+ // tests commands, like `a.exe`, with c.Dir set
+ {
+ // should not find a.exe in p, becasue LookPath(`a.exe`) will fail
+ files: []string{`p\a.exe`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `p\a.exe`,
+ fails: true,
+ },
+ {
+ // LookPath(`a.exe`) will find `.\a.exe`, but prefixing that with
+ // dir `p\a.exe` will refer to not existant file
+ files: []string{`a.exe`, `p\not_important_file`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `a.exe`,
+ fails: true,
+ },
+ {
+ // like above, but making test succeed by installing file
+ // in refered destination (so LookPath(`a.exe`) will still
+ // find `.\a.exe`, but we successfully execute `p\a.exe`)
+ files: []string{`a.exe`, `p\a.exe`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but add PATH in attempt to break the test
+ PATH: `p2;p`,
+ files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but use "a" instead of "a.exe" for command
+ PATH: `p2;p`,
+ files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `a`,
+ want: `p\a.exe`,
+ },
+ {
+ // finds `a.exe` in the PATH regardless of dir set
+ // because LookPath returns full path in that case
+ PATH: `p2;p`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `a.exe`,
+ want: `p2\a.exe`,
+ },
+ // tests commands, like `.\a.exe`, with c.Dir set
+ {
+ // should use dir when command is path, like ".\a.exe"
+ files: []string{`p\a.exe`},
+ dir: `p`,
+ arg0: `.\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but with PATH added in attempt to break it
+ PATH: `p2`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `.\a.exe`,
+ want: `p\a.exe`,
+ },
+ {
+ // like above, but make sure .exe is tried even for commands with slash
+ PATH: `p2`,
+ files: []string{`p\a.exe`, `p2\a.exe`},
+ dir: `p`,
+ arg0: `.\a`,
+ want: `p\a.exe`,
+ },
}
-func TestLookPath(t *testing.T) {
- tmp, err := ioutil.TempDir("", "TestLookPath")
+func TestCommand(t *testing.T) {
+ tmp, err := ioutil.TempDir("", "TestCommand")
if err != nil {
t.Fatal("TempDir failed: ", err)
}
defer os.RemoveAll(tmp)
- // Create a Go program that uses LookPath to find executable passed as command line parameter.
- lookpathExe := buildExe(t, lookpathSrc, tmp, "lookpath")
-
- // Create a Go program that prints its own path.
- printpathExe := buildExe(t, printpathSrc, tmp, "printpath")
+ printpathExe := buildPrintPathExe(t, tmp)
// Run all tests.
- for i, test := range lookPathTests {
+ for i, test := range commandTests {
dir := filepath.Join(tmp, "d"+strconv.Itoa(i))
err := os.Mkdir(dir, 0700)
if err != nil {
t.Fatal("Mkdir failed: ", err)
}
- testLookPath(t, test, dir, lookpathExe, printpathExe)
+ test.run(t, dir, printpathExe)
}
}
-const lookpathSrc = `
-package main
-
-import (
- "fmt"
- "os"
- "os/exec"
-)
-
-func main() {
- p, err := exec.LookPath(os.Args[1])
+// buildPrintPathExe creates a Go program that prints its own path.
+// dir is a temp directory where executable will be created.
+// The function returns full path to the created program.
+func buildPrintPathExe(t *testing.T, dir string) string {
+ const name = "printpath"
+ srcname := name + ".go"
+ err := ioutil.WriteFile(filepath.Join(dir, srcname), []byte(printpathSrc), 0644)
if err != nil {
- fmt.Printf("LookPath failed: %v\n", err)
- os.Exit(1)
+ t.Fatalf("failed to create source: %v", err)
+ }
+ if err != nil {
+ t.Fatalf("failed to execute template: %v", err)
+ }
+ outname := name + ".exe"
+ cmd := Command("go", "build", "-o", outname, srcname)
+ cmd.Dir = dir
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to build executable: %v - %v", err, string(out))
}
- fmt.Print(p)
+ return filepath.Join(dir, outname)
}
-`
const printpathSrc = `
package main
import (
- "fmt"
"os"
"syscall"
"unicode/utf16"
@@ -383,9 +565,9 @@ func getMyName() (string, error) {
func main() {
path, err := getMyName()
if err != nil {
- fmt.Printf("getMyName failed: %v\n", err)
+ os.Stderr.Write([]byte("getMyName failed: " + err.Error() + "\n"))
os.Exit(1)
}
- fmt.Print(path)
+ os.Stdout.Write([]byte(path))
}
`
diff --git a/src/pkg/os/exec_plan9.go b/src/pkg/os/exec_plan9.go
index 2bd5b6888..676be36ac 100644
--- a/src/pkg/os/exec_plan9.go
+++ b/src/pkg/os/exec_plan9.go
@@ -52,10 +52,6 @@ func (p *Process) signal(sig Signal) error {
if p.done() {
return errors.New("os: process already finished")
}
- if sig == Kill {
- // Special-case the kill signal since it doesn't use /proc/$pid/note.
- return p.Kill()
- }
if e := p.writeProcFile("note", sig.String()); e != nil {
return NewSyscallError("signal", e)
}
@@ -63,10 +59,7 @@ func (p *Process) signal(sig Signal) error {
}
func (p *Process) kill() error {
- if e := p.writeProcFile("ctl", "kill"); e != nil {
- return NewSyscallError("kill", e)
- }
- return nil
+ return p.signal(Kill)
}
func (p *Process) wait() (ps *ProcessState, err error) {
diff --git a/src/pkg/os/exec_posix.go b/src/pkg/os/exec_posix.go
index fb123aefb..fb9d291e6 100644
--- a/src/pkg/os/exec_posix.go
+++ b/src/pkg/os/exec_posix.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 darwin dragonfly freebsd linux netbsd openbsd windows
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
package os
diff --git a/src/pkg/os/exec_unix.go b/src/pkg/os/exec_unix.go
index 5572e628e..1b1e3350b 100644
--- a/src/pkg/os/exec_unix.go
+++ b/src/pkg/os/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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package os
@@ -38,6 +38,9 @@ func (p *Process) signal(sig Signal) error {
if p.done() {
return errors.New("os: process already finished")
}
+ if p.Pid == -1 {
+ return errors.New("os: process already released")
+ }
s, ok := sig.(syscall.Signal)
if !ok {
return errors.New("os: unsupported signal type")
diff --git a/src/pkg/os/file.go b/src/pkg/os/file.go
index 2dd1fcf28..b4a745801 100644
--- a/src/pkg/os/file.go
+++ b/src/pkg/os/file.go
@@ -140,6 +140,9 @@ func (f *File) Write(b []byte) (n int, err error) {
if n < 0 {
n = 0
}
+ if n != len(b) {
+ err = io.ErrShortWrite
+ }
epipecheck(f, e)
@@ -247,3 +250,8 @@ func Create(name string) (file *File, err error) {
// lstat is overridden in tests.
var lstat = Lstat
+
+// Rename renames (moves) a file. OS-specific restrictions might apply.
+func Rename(oldpath, newpath string) error {
+ return rename(oldpath, newpath)
+}
diff --git a/src/pkg/os/file_plan9.go b/src/pkg/os/file_plan9.go
index 708163ee1..a804b8197 100644
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -313,8 +313,33 @@ func Remove(name string) error {
return nil
}
-// Rename renames a file.
-func Rename(oldname, newname string) error {
+// HasPrefix from the strings package.
+func hasPrefix(s, prefix string) bool {
+ return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
+}
+
+// Variant of LastIndex from the strings package.
+func lastIndex(s string, sep byte) int {
+ for i := len(s) - 1; i >= 0; i-- {
+ if s[i] == sep {
+ return i
+ }
+ }
+ return -1
+}
+
+func rename(oldname, newname string) error {
+ dirname := oldname[:lastIndex(oldname, '/')+1]
+ if hasPrefix(newname, dirname) {
+ newname = newname[len(dirname):]
+ } else {
+ return &LinkError{"rename", oldname, newname, ErrInvalid}
+ }
+
+ // If newname still contains slashes after removing the oldname
+ // prefix, the rename is cross-directory and must be rejected.
+ // This case is caught by d.Marshal below.
+
var d syscall.Dir
d.Null()
@@ -323,10 +348,10 @@ func Rename(oldname, newname string) error {
buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
n, err := d.Marshal(buf[:])
if err != nil {
- return &PathError{"rename", oldname, err}
+ return &LinkError{"rename", oldname, newname, err}
}
if err = syscall.Wstat(oldname, buf[:n]); err != nil {
- return &PathError{"rename", oldname, err}
+ return &LinkError{"rename", oldname, newname, err}
}
return nil
}
diff --git a/src/pkg/os/file_posix.go b/src/pkg/os/file_posix.go
index a8bef359b..b3466b15c 100644
--- a/src/pkg/os/file_posix.go
+++ b/src/pkg/os/file_posix.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 darwin dragonfly freebsd linux netbsd openbsd windows
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
package os
@@ -48,8 +48,7 @@ func Readlink(name string) (string, error) {
}
}
-// Rename renames a file.
-func Rename(oldname, newname string) error {
+func rename(oldname, newname string) error {
e := syscall.Rename(oldname, newname)
if e != nil {
return &LinkError{"rename", oldname, newname, e}
@@ -145,7 +144,7 @@ func (f *File) Truncate(size int64) error {
// of recently written data to disk.
func (f *File) Sync() (err error) {
if f == nil {
- return syscall.EINVAL
+ return ErrInvalid
}
if e := syscall.Fsync(f.fd); e != nil {
return NewSyscallError("fsync", e)
diff --git a/src/pkg/os/file_unix.go b/src/pkg/os/file_unix.go
index ff1a597e7..76168339d 100644
--- a/src/pkg/os/file_unix.go
+++ b/src/pkg/os/file_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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package os
@@ -81,12 +81,7 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
// There's a race here with fork/exec, which we are
// content to live with. See ../syscall/exec_unix.go.
- // On OS X 10.6, the O_CLOEXEC flag is not respected.
- // On OS X 10.7, the O_CLOEXEC flag works.
- // Without a cheap & reliable way to detect 10.6 vs 10.7 at
- // runtime, we just always call syscall.CloseOnExec on Darwin.
- // Once >=10.7 is prevalent, this extra call can removed.
- if syscall.O_CLOEXEC == 0 || runtime.GOOS == "darwin" { // O_CLOEXEC not supported
+ if !supportsCloseOnExec {
syscall.CloseOnExec(r)
}
@@ -160,30 +155,48 @@ func (f *File) readdir(n int) (fi []FileInfo, err error) {
if dirname == "" {
dirname = "."
}
- dirname += "/"
names, err := f.Readdirnames(n)
- fi = make([]FileInfo, len(names))
- for i, filename := range names {
- fip, lerr := lstat(dirname + filename)
- if lerr != nil {
- fi[i] = &fileStat{name: filename}
+ fi = make([]FileInfo, 0, len(names))
+ for _, filename := range names {
+ fip, lerr := lstat(dirname + "/" + filename)
+ if IsNotExist(lerr) {
+ // File disappeared between readdir + stat.
+ // Just treat it as if it didn't exist.
continue
}
- fi[i] = fip
+ if lerr != nil {
+ return fi, lerr
+ }
+ fi = append(fi, fip)
}
return fi, 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 syscall.Read(f.fd, b)
}
// 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 0.
+// 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 syscall.Pread(f.fd, b, off)
}
@@ -191,13 +204,22 @@ func (f *File) pread(b []byte, off int64) (n int, err error) {
// It returns the number of bytes written and an error, if any.
func (f *File) write(b []byte) (n int, err error) {
for {
- m, err := syscall.Write(f.fd, b)
+ bcap := b
+ if needsMaxRW && len(bcap) > maxRW {
+ bcap = bcap[:maxRW]
+ }
+ m, err := 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(b) || err == syscall.EINTR {
+ 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
}
@@ -209,6 +231,9 @@ func (f *File) write(b []byte) (n int, err error) {
// 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 syscall.Pwrite(f.fd, b, off)
}
diff --git a/src/pkg/os/file_windows.go b/src/pkg/os/file_windows.go
index fab7de342..efe8bc03f 100644
--- a/src/pkg/os/file_windows.go
+++ b/src/pkg/os/file_windows.go
@@ -134,20 +134,19 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
if name == "" {
return nil, &PathError{"open", name, syscall.ENOENT}
}
- // TODO(brainman): not sure about my logic of assuming it is dir first, then fall back to file
- r, e := openDir(name)
- if e == nil {
+ r, errf := openFile(name, flag, perm)
+ if errf == nil {
+ return r, nil
+ }
+ r, errd := openDir(name)
+ if errd == nil {
if flag&O_WRONLY != 0 || flag&O_RDWR != 0 {
r.Close()
return nil, &PathError{"open", name, syscall.EISDIR}
}
return r, nil
}
- r, e = openFile(name, flag, perm)
- if e == nil {
- return r, nil
- }
- return nil, &PathError{"open", name, e}
+ return nil, &PathError{"open", name, errf}
}
// Close closes the File, rendering it unusable for I/O.
diff --git a/src/pkg/os/getwd.go b/src/pkg/os/getwd.go
index 8c5ff7fca..a72edeaee 100644
--- a/src/pkg/os/getwd.go
+++ b/src/pkg/os/getwd.go
@@ -22,7 +22,7 @@ var useSyscallwd = func(error) bool { return true }
// current directory. If the current directory can be
// reached via multiple paths (due to symbolic links),
// Getwd may return any one of them.
-func Getwd() (pwd string, err error) {
+func Getwd() (dir string, err error) {
// If the operating system provides a Getwd call, use it.
if syscall.ImplementsGetwd {
s, e := syscall.Getwd()
@@ -39,22 +39,22 @@ func Getwd() (pwd string, err error) {
// Clumsy but widespread kludge:
// if $PWD is set and matches ".", use it.
- pwd = Getenv("PWD")
- if len(pwd) > 0 && pwd[0] == '/' {
- d, err := Stat(pwd)
+ dir = Getenv("PWD")
+ if len(dir) > 0 && dir[0] == '/' {
+ d, err := Stat(dir)
if err == nil && SameFile(dot, d) {
- return pwd, nil
+ return dir, nil
}
}
// Apply same kludge but to cached dir instead of $PWD.
getwdCache.Lock()
- pwd = getwdCache.dir
+ dir = getwdCache.dir
getwdCache.Unlock()
- if len(pwd) > 0 {
- d, err := Stat(pwd)
+ if len(dir) > 0 {
+ d, err := Stat(dir)
if err == nil && SameFile(dot, d) {
- return pwd, nil
+ return dir, nil
}
}
@@ -71,8 +71,8 @@ func Getwd() (pwd string, err error) {
// General algorithm: find name in parent
// and then find name of parent. Each iteration
- // adds /name to the beginning of pwd.
- pwd = ""
+ // adds /name to the beginning of dir.
+ dir = ""
for parent := ".."; ; parent = "../" + parent {
if len(parent) >= 1024 { // Sanity check
return "", syscall.ENAMETOOLONG
@@ -91,7 +91,7 @@ func Getwd() (pwd string, err error) {
for _, name := range names {
d, _ := Lstat(parent + "/" + name)
if SameFile(d, dot) {
- pwd = "/" + name + pwd
+ dir = "/" + name + dir
goto Found
}
}
@@ -112,8 +112,8 @@ func Getwd() (pwd string, err error) {
// Save answer as hint to avoid the expensive path next time.
getwdCache.Lock()
- getwdCache.dir = pwd
+ getwdCache.dir = dir
getwdCache.Unlock()
- return pwd, nil
+ return dir, nil
}
diff --git a/src/pkg/os/os_test.go b/src/pkg/os/os_test.go
index 9462ebd42..16d5984e9 100644
--- a/src/pkg/os/os_test.go
+++ b/src/pkg/os/os_test.go
@@ -6,6 +6,7 @@ package os_test
import (
"bytes"
+ "errors"
"flag"
"fmt"
"io"
@@ -13,7 +14,9 @@ import (
. "os"
osexec "os/exec"
"path/filepath"
+ "reflect"
"runtime"
+ "sort"
"strings"
"syscall"
"testing"
@@ -382,6 +385,84 @@ func TestReaddirNValues(t *testing.T) {
}
}
+func touch(t *testing.T, name string) {
+ f, err := Create(name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestReaddirStatFailures(t *testing.T) {
+ switch runtime.GOOS {
+ case "windows", "plan9":
+ // Windows and Plan 9 already do this correctly,
+ // but are structured with different syscalls such
+ // that they don't use Lstat, so the hook below for
+ // testing it wouldn't work.
+ t.Skipf("skipping test on %v", runtime.GOOS)
+ }
+ dir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("TempDir: %v", err)
+ }
+ defer RemoveAll(dir)
+ touch(t, filepath.Join(dir, "good1"))
+ touch(t, filepath.Join(dir, "x")) // will disappear or have an error
+ touch(t, filepath.Join(dir, "good2"))
+ defer func() {
+ *LstatP = Lstat
+ }()
+ var xerr error // error to return for x
+ *LstatP = func(path string) (FileInfo, error) {
+ if xerr != nil && strings.HasSuffix(path, "x") {
+ return nil, xerr
+ }
+ return Lstat(path)
+ }
+ readDir := func() ([]FileInfo, error) {
+ d, err := Open(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer d.Close()
+ return d.Readdir(-1)
+ }
+ mustReadDir := func(testName string) []FileInfo {
+ fis, err := readDir()
+ if err != nil {
+ t.Fatalf("%s: Readdir: %v", testName, err)
+ }
+ return fis
+ }
+ names := func(fis []FileInfo) []string {
+ s := make([]string, len(fis))
+ for i, fi := range fis {
+ s[i] = fi.Name()
+ }
+ sort.Strings(s)
+ return s
+ }
+
+ if got, want := names(mustReadDir("inital readdir")),
+ []string{"good1", "good2", "x"}; !reflect.DeepEqual(got, want) {
+ t.Errorf("initial readdir got %q; want %q", got, want)
+ }
+
+ xerr = ErrNotExist
+ if got, want := names(mustReadDir("with x disappearing")),
+ []string{"good1", "good2"}; !reflect.DeepEqual(got, want) {
+ t.Errorf("with x disappearing, got %q; want %q", got, want)
+ }
+
+ xerr = errors.New("some real error")
+ if _, err := readDir(); err != xerr {
+ t.Errorf("with a non-ErrNotExist error, got error %v; want %v", err, xerr)
+ }
+}
+
func TestHardLink(t *testing.T) {
// Hardlinks are not supported under windows or Plan 9.
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
@@ -415,10 +496,10 @@ func TestHardLink(t *testing.T) {
}
}
-func TestSymLink(t *testing.T) {
- // Symlinks are not supported under windows or Plan 9.
- if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
- return
+func TestSymlink(t *testing.T) {
+ switch runtime.GOOS {
+ case "windows", "plan9", "nacl":
+ t.Skipf("skipping on %s", runtime.GOOS)
}
from, to := "symlinktestfrom", "symlinktestto"
Remove(from) // Just in case.
@@ -478,9 +559,9 @@ func TestSymLink(t *testing.T) {
}
func TestLongSymlink(t *testing.T) {
- // Symlinks are not supported under windows or Plan 9.
- if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
- return
+ switch runtime.GOOS {
+ case "windows", "plan9", "nacl":
+ t.Skipf("skipping on %s", runtime.GOOS)
}
s := "0123456789abcdef"
// Long, but not too long: a common limit is 255.
@@ -549,6 +630,10 @@ func exec(t *testing.T, dir, cmd string, args []string, expect string) {
}
func TestStartProcess(t *testing.T) {
+ if runtime.GOOS == "nacl" {
+ t.Skip("skipping on nacl")
+ }
+
var dir, cmd string
var args []string
if runtime.GOOS == "windows" {
@@ -622,8 +707,10 @@ func TestFTruncate(t *testing.T) {
checkSize(t, f, 1024)
f.Truncate(0)
checkSize(t, f, 0)
- f.Write([]byte("surprise!"))
- checkSize(t, f, 13+9) // wrote at offset past where hello, world was.
+ _, err := f.Write([]byte("surprise!"))
+ if err == nil {
+ checkSize(t, f, 13+9) // wrote at offset past where hello, world was.
+ }
}
func TestTruncate(t *testing.T) {
@@ -640,8 +727,10 @@ func TestTruncate(t *testing.T) {
checkSize(t, f, 1024)
Truncate(f.Name(), 0)
checkSize(t, f, 0)
- f.Write([]byte("surprise!"))
- checkSize(t, f, 13+9) // wrote at offset past where hello, world was.
+ _, err := f.Write([]byte("surprise!"))
+ if err == nil {
+ checkSize(t, f, 13+9) // wrote at offset past where hello, world was.
+ }
}
// Use TempDir() to make sure we're on a local file system,
@@ -676,13 +765,13 @@ func TestChtimes(t *testing.T) {
}
postStat := st
- /* Plan 9:
+ /* 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" {
+ if !pat.Before(at) && runtime.GOOS != "plan9" && runtime.GOOS != "nacl" {
t.Errorf("AccessTime didn't go backwards; was=%d, after=%d", at, pat)
}
@@ -884,8 +973,9 @@ func run(t *testing.T, cmd []string) string {
func TestHostname(t *testing.T) {
// There is no other way to fetch hostname on windows, but via winapi.
// On Plan 9 it is can be taken from #c/sysname as Hostname() does.
- if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
- return
+ switch runtime.GOOS {
+ case "windows", "plan9", "nacl":
+ t.Skipf("skipping on %s", runtime.GOOS)
}
// Check internal Hostname() against the output of /bin/hostname.
@@ -1144,6 +1234,10 @@ func TestReadAtEOF(t *testing.T) {
}
func testKillProcess(t *testing.T, processKiller func(p *Process)) {
+ if runtime.GOOS == "nacl" {
+ t.Skip("skipping on nacl")
+ }
+
dir, err := ioutil.TempDir("", "go-build")
if err != nil {
t.Fatalf("Failed to create temp directory: %v", err)
@@ -1211,3 +1305,35 @@ func TestKillFindProcess(t *testing.T) {
}
})
}
+
+var nilFileMethodTests = []struct {
+ name string
+ f func(*File) error
+}{
+ {"Chdir", func(f *File) error { return f.Chdir() }},
+ {"Close", func(f *File) error { return f.Close() }},
+ {"Chmod", func(f *File) error { return f.Chmod(0) }},
+ {"Chown", func(f *File) error { return f.Chown(0, 0) }},
+ {"Read", func(f *File) error { _, err := f.Read(make([]byte, 0)); return err }},
+ {"ReadAt", func(f *File) error { _, err := f.ReadAt(make([]byte, 0), 0); return err }},
+ {"Readdir", func(f *File) error { _, err := f.Readdir(1); return err }},
+ {"Readdirnames", func(f *File) error { _, err := f.Readdirnames(1); return err }},
+ {"Seek", func(f *File) error { _, err := f.Seek(0, 0); return err }},
+ {"Stat", func(f *File) error { _, err := f.Stat(); return err }},
+ {"Sync", func(f *File) error { return f.Sync() }},
+ {"Truncate", func(f *File) error { return f.Truncate(0) }},
+ {"Write", func(f *File) error { _, err := f.Write(make([]byte, 0)); return err }},
+ {"WriteAt", func(f *File) error { _, err := f.WriteAt(make([]byte, 0), 0); return err }},
+ {"WriteString", func(f *File) error { _, err := f.WriteString(""); return err }},
+}
+
+// Test that all File methods give ErrInvalid if the receiver is nil.
+func TestNilFileMethods(t *testing.T) {
+ for _, tt := range nilFileMethodTests {
+ var file *File
+ got := tt.f(file)
+ if got != ErrInvalid {
+ t.Errorf("%v should fail when f is nil; got %v", tt.name, got)
+ }
+ }
+}
diff --git a/src/pkg/os/os_unix_test.go b/src/pkg/os/os_unix_test.go
index b0fc0256d..21d40ccaf 100644
--- a/src/pkg/os/os_unix_test.go
+++ b/src/pkg/os/os_unix_test.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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package os_test
@@ -74,41 +74,3 @@ func TestChown(t *testing.T) {
checkUidGid(t, f.Name(), int(sys.Uid), gid)
}
}
-
-func TestReaddirWithBadLstat(t *testing.T) {
- handle, err := Open(sfdir)
- failfile := sfdir + "/" + sfname
- if err != nil {
- t.Fatalf("Couldn't open %s: %s", sfdir, err)
- }
-
- *LstatP = func(file string) (FileInfo, error) {
- if file == failfile {
- var fi FileInfo
- return fi, ErrInvalid
- }
- return Lstat(file)
- }
- defer func() { *LstatP = Lstat }()
-
- dirs, err := handle.Readdir(-1)
- if err != nil {
- t.Fatalf("Expected Readdir to return no error, got %v", err)
- }
- foundfail := false
- for _, dir := range dirs {
- if dir.Name() == sfname {
- foundfail = true
- if dir.Sys() != nil {
- t.Errorf("Expected Readdir for %s should not contain Sys", failfile)
- }
- } else {
- if dir.Sys() == nil {
- t.Errorf("Readdir for every file other than %s should contain Sys, but %s/%s didn't either", failfile, sfdir, dir.Name())
- }
- }
- }
- if !foundfail {
- t.Fatalf("Expected %s from Readdir, but didn't find it", failfile)
- }
-}
diff --git a/src/pkg/os/path_test.go b/src/pkg/os/path_test.go
index 27abf5982..3af21cde9 100644
--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -167,8 +167,9 @@ func TestRemoveAll(t *testing.T) {
}
func TestMkdirAllWithSymlink(t *testing.T) {
- if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
- t.Skip("Skipping test: symlinks don't exist under Windows/Plan 9")
+ switch runtime.GOOS {
+ case "nacl", "plan9", "windows":
+ t.Skipf("skipping on %s", runtime.GOOS)
}
tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-")
diff --git a/src/pkg/os/path_unix.go b/src/pkg/os/path_unix.go
index 3bf63bf80..0211107dd 100644
--- a/src/pkg/os/path_unix.go
+++ b/src/pkg/os/path_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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
package os
diff --git a/src/pkg/os/pipe_bsd.go b/src/pkg/os/pipe_bsd.go
index 73d35b4d5..3b81ed20f 100644
--- a/src/pkg/os/pipe_bsd.go
+++ b/src/pkg/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 darwin dragonfly freebsd netbsd openbsd
+// +build darwin dragonfly freebsd nacl netbsd openbsd solaris
package os
diff --git a/src/pkg/os/signal/example_test.go b/src/pkg/os/signal/example_test.go
index 600ed315d..079ee5070 100644
--- a/src/pkg/os/signal/example_test.go
+++ b/src/pkg/os/signal/example_test.go
@@ -1,3 +1,7 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
package signal_test
import (
diff --git a/src/pkg/os/signal/sig.s b/src/pkg/os/signal/sig.s
index 888823cf4..f860924aa 100644
--- a/src/pkg/os/signal/sig.s
+++ b/src/pkg/os/signal/sig.s
@@ -4,7 +4,7 @@
// Assembly to get into package runtime without using exported symbols.
-// +build amd64 arm 386
+// +build amd64 amd64p32 arm 386
#include "../../../cmd/ld/textflag.h"
diff --git a/src/pkg/os/signal/signal_test.go b/src/pkg/os/signal/signal_test.go
index 741f2a0ed..076fe3f93 100644
--- a/src/pkg/os/signal/signal_test.go
+++ b/src/pkg/os/signal/signal_test.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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package signal
diff --git a/src/pkg/os/signal/signal_unix.go b/src/pkg/os/signal/signal_unix.go
index 318488dc0..94b8ab3dd 100644
--- a/src/pkg/os/signal/signal_unix.go
+++ b/src/pkg/os/signal/signal_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 darwin dragonfly freebsd linux netbsd openbsd windows
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
package signal
diff --git a/src/pkg/os/signal/signal_windows_test.go b/src/pkg/os/signal/signal_windows_test.go
index 26712f35b..f3e6706b7 100644
--- a/src/pkg/os/signal/signal_windows_test.go
+++ b/src/pkg/os/signal/signal_windows_test.go
@@ -6,6 +6,7 @@ package signal
import (
"bytes"
+ "io/ioutil"
"os"
"os/exec"
"path/filepath"
@@ -55,9 +56,15 @@ func main() {
}
}
`
- name := filepath.Join(os.TempDir(), "ctlbreak")
+ tmp, err := ioutil.TempDir("", "TestCtrlBreak")
+ if err != nil {
+ t.Fatal("TempDir failed: ", err)
+ }
+ defer os.RemoveAll(tmp)
+
+ // write ctrlbreak.go
+ name := filepath.Join(tmp, "ctlbreak")
src := name + ".go"
- defer os.Remove(src)
f, err := os.Create(src)
if err != nil {
t.Fatalf("Failed to create %v: %v", src, err)
diff --git a/src/pkg/os/stat_nacl.go b/src/pkg/os/stat_nacl.go
new file mode 100644
index 000000000..a503b59fa
--- /dev/null
+++ b/src/pkg/os/stat_nacl.go
@@ -0,0 +1,62 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func sameFile(fs1, fs2 *fileStat) bool {
+ stat1 := fs1.sys.(*syscall.Stat_t)
+ stat2 := fs2.sys.(*syscall.Stat_t)
+ return stat1.Dev == stat2.Dev && stat1.Ino == stat2.Ino
+}
+
+func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo {
+ fs := &fileStat{
+ name: basename(name),
+ size: int64(st.Size),
+ modTime: timespecToTime(st.Mtime, st.MtimeNsec),
+ sys: st,
+ }
+ fs.mode = FileMode(st.Mode & 0777)
+ switch st.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if st.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if st.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if st.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+ return fs
+}
+
+func timespecToTime(sec, nsec int64) time.Time {
+ return time.Unix(sec, nsec)
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ st := fi.Sys().(*syscall.Stat_t)
+ return timespecToTime(st.Atime, st.AtimeNsec)
+}
diff --git a/src/pkg/os/stat_solaris.go b/src/pkg/os/stat_solaris.go
new file mode 100644
index 000000000..605c1d9b6
--- /dev/null
+++ b/src/pkg/os/stat_solaris.go
@@ -0,0 +1,61 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func sameFile(fs1, fs2 *fileStat) bool {
+ stat1 := fs1.sys.(*syscall.Stat_t)
+ stat2 := fs2.sys.(*syscall.Stat_t)
+ return stat1.Dev == stat2.Dev && stat1.Ino == stat2.Ino
+}
+
+func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo {
+ fs := &fileStat{
+ name: basename(name),
+ size: int64(st.Size),
+ modTime: timespecToTime(st.Mtim),
+ sys: st,
+ }
+ fs.mode = FileMode(st.Mode & 0777)
+ switch st.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK:
+ fs.mode |= ModeDevice
+ case syscall.S_IFCHR:
+ fs.mode |= ModeDevice | ModeCharDevice
+ case syscall.S_IFDIR:
+ fs.mode |= ModeDir
+ case syscall.S_IFIFO:
+ fs.mode |= ModeNamedPipe
+ case syscall.S_IFLNK:
+ fs.mode |= ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fs.mode |= ModeSocket
+ }
+ if st.Mode&syscall.S_ISGID != 0 {
+ fs.mode |= ModeSetgid
+ }
+ if st.Mode&syscall.S_ISUID != 0 {
+ fs.mode |= ModeSetuid
+ }
+ if st.Mode&syscall.S_ISVTX != 0 {
+ fs.mode |= ModeSticky
+ }
+ return fs
+}
+
+func timespecToTime(ts syscall.Timespec) time.Time {
+ return time.Unix(int64(ts.Sec), int64(ts.Nsec))
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
+}
diff --git a/src/pkg/os/sys_bsd.go b/src/pkg/os/sys_bsd.go
index 9ad2f8546..8ad5e2183 100644
--- a/src/pkg/os/sys_bsd.go
+++ b/src/pkg/os/sys_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 darwin dragonfly freebsd netbsd openbsd
+// +build darwin dragonfly freebsd nacl netbsd openbsd
// os code shared between *BSD systems including OS X (Darwin)
// and FreeBSD.
diff --git a/src/pkg/os/sys_darwin.go b/src/pkg/os/sys_darwin.go
new file mode 100644
index 000000000..7a8330abb
--- /dev/null
+++ b/src/pkg/os/sys_darwin.go
@@ -0,0 +1,31 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+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
+ }
+}
diff --git a/src/pkg/os/sys_freebsd.go b/src/pkg/os/sys_freebsd.go
new file mode 100644
index 000000000..273c2df1c
--- /dev/null
+++ b/src/pkg/os/sys_freebsd.go
@@ -0,0 +1,23 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+// supportsCloseOnExec reports whether the platform supports the
+// O_CLOEXEC flag.
+var supportsCloseOnExec bool
+
+func init() {
+ osrel, err := syscall.SysctlUint32("kern.osreldate")
+ if err != nil {
+ return
+ }
+ // The O_CLOEXEC flag was introduced in FreeBSD 8.3.
+ // See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html.
+ if osrel >= 803000 {
+ supportsCloseOnExec = true
+ }
+}
diff --git a/src/pkg/os/sys_nacl.go b/src/pkg/os/sys_nacl.go
new file mode 100644
index 000000000..07907c847
--- /dev/null
+++ b/src/pkg/os/sys_nacl.go
@@ -0,0 +1,9 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// supportsCloseOnExec reports whether the platform supports the
+// O_CLOEXEC flag.
+const supportsCloseOnExec = false
diff --git a/src/pkg/os/sys_solaris.go b/src/pkg/os/sys_solaris.go
new file mode 100644
index 000000000..917e8f2b0
--- /dev/null
+++ b/src/pkg/os/sys_solaris.go
@@ -0,0 +1,11 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+func hostname() (name string, err error) {
+ return syscall.Gethostname()
+}
diff --git a/src/pkg/os/sys_unix.go b/src/pkg/os/sys_unix.go
new file mode 100644
index 000000000..39c20dc73
--- /dev/null
+++ b/src/pkg/os/sys_unix.go
@@ -0,0 +1,11 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build dragonfly linux netbsd openbsd solaris
+
+package os
+
+// supportsCloseOnExec reports whether the platform supports the
+// O_CLOEXEC flag.
+const supportsCloseOnExec = true
diff --git a/src/pkg/os/user/lookup_unix.go b/src/pkg/os/user/lookup_unix.go
index 5459268fa..f2baf05bb 100644
--- a/src/pkg/os/user/lookup_unix.go
+++ b/src/pkg/os/user/lookup_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 darwin dragonfly freebsd linux netbsd openbsd
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
// +build cgo
package user