diff options
Diffstat (limited to 'src/pkg/os')
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 |