summaryrefslogtreecommitdiff
path: root/src/os
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2014-09-08 00:08:51 -0400
committerRuss Cox <rsc@golang.org>2014-09-08 00:08:51 -0400
commit8528da672cc093d4dd06732819abc1f7b6b5a46e (patch)
tree334be80d4a4c85b77db6f6fdb67cbf0528cba5f5 /src/os
parent73bcb69f272cbf34ddcc9daa56427a8683b5a95d (diff)
downloadgo-8528da672cc093d4dd06732819abc1f7b6b5a46e.tar.gz
build: move package sources from src/pkg to src
Preparation was in CL 134570043. This CL contains only the effect of 'hg mv src/pkg/* src'. For more about the move, see golang.org/s/go14nopkg.
Diffstat (limited to 'src/os')
-rw-r--r--src/os/dir_plan9.go73
-rw-r--r--src/os/dir_unix.go58
-rw-r--r--src/os/dir_windows.go14
-rw-r--r--src/os/doc.go135
-rw-r--r--src/os/env.go103
-rw-r--r--src/os/env_test.go70
-rw-r--r--src/os/env_unix_test.go30
-rw-r--r--src/os/error.go65
-rw-r--r--src/os/error_plan9.go53
-rw-r--r--src/os/error_test.go132
-rw-r--r--src/os/error_unix.go45
-rw-r--r--src/os/error_windows.go45
-rw-r--r--src/os/error_windows_test.go47
-rw-r--r--src/os/exec.go70
-rw-r--r--src/os/exec/example_test.go75
-rw-r--r--src/os/exec/exec.go493
-rw-r--r--src/os/exec/exec_test.go726
-rw-r--r--src/os/exec/lp_plan9.go53
-rw-r--r--src/os/exec/lp_test.go33
-rw-r--r--src/os/exec/lp_unix.go60
-rw-r--r--src/os/exec/lp_unix_test.go55
-rw-r--r--src/os/exec/lp_windows.go123
-rw-r--r--src/os/exec/lp_windows_test.go573
-rw-r--r--src/os/exec_plan9.go137
-rw-r--r--src/os/exec_posix.go134
-rw-r--r--src/os/exec_unix.go73
-rw-r--r--src/os/exec_windows.go115
-rw-r--r--src/os/export_test.go10
-rw-r--r--src/os/file.go257
-rw-r--r--src/os/file_plan9.go468
-rw-r--r--src/os/file_posix.go149
-rw-r--r--src/os/file_unix.go338
-rw-r--r--src/os/file_windows.go595
-rw-r--r--src/os/getwd.go123
-rw-r--r--src/os/getwd_darwin.go15
-rw-r--r--src/os/os_test.go1405
-rw-r--r--src/os/os_unix_test.go76
-rw-r--r--src/os/os_windows_test.go81
-rw-r--r--src/os/path.go123
-rw-r--r--src/os/path_plan9.go15
-rw-r--r--src/os/path_test.go220
-rw-r--r--src/os/path_unix.go17
-rw-r--r--src/os/path_windows.go16
-rw-r--r--src/os/pipe_bsd.go28
-rw-r--r--src/os/pipe_linux.go33
-rw-r--r--src/os/proc.go36
-rw-r--r--src/os/signal/example_test.go23
-rw-r--r--src/os/signal/sig.s23
-rw-r--r--src/os/signal/signal.go131
-rw-r--r--src/os/signal/signal_stub.go17
-rw-r--r--src/os/signal/signal_test.go208
-rw-r--r--src/os/signal/signal_unix.go53
-rw-r--r--src/os/signal/signal_windows_test.go103
-rw-r--r--src/os/stat_darwin.go61
-rw-r--r--src/os/stat_dragonfly.go61
-rw-r--r--src/os/stat_freebsd.go61
-rw-r--r--src/os/stat_linux.go61
-rw-r--r--src/os/stat_nacl.go62
-rw-r--r--src/os/stat_netbsd.go61
-rw-r--r--src/os/stat_openbsd.go61
-rw-r--r--src/os/stat_plan9.go110
-rw-r--r--src/os/stat_solaris.go61
-rw-r--r--src/os/stat_windows.go170
-rw-r--r--src/os/str.go22
-rw-r--r--src/os/sys_bsd.go20
-rw-r--r--src/os/sys_darwin.go31
-rw-r--r--src/os/sys_freebsd.go23
-rw-r--r--src/os/sys_linux.go26
-rw-r--r--src/os/sys_nacl.go9
-rw-r--r--src/os/sys_plan9.go26
-rw-r--r--src/os/sys_solaris.go11
-rw-r--r--src/os/sys_unix.go11
-rw-r--r--src/os/sys_windows.go15
-rw-r--r--src/os/types.go118
-rw-r--r--src/os/types_notwin.go25
-rw-r--r--src/os/types_windows.go107
-rw-r--r--src/os/user/lookup.go22
-rw-r--r--src/os/user/lookup_plan9.go46
-rw-r--r--src/os/user/lookup_stubs.go28
-rw-r--r--src/os/user/lookup_unix.go112
-rw-r--r--src/os/user/lookup_windows.go149
-rw-r--r--src/os/user/user.go43
-rw-r--r--src/os/user/user_test.go89
83 files changed, 9855 insertions, 0 deletions
diff --git a/src/os/dir_plan9.go b/src/os/dir_plan9.go
new file mode 100644
index 000000000..8195c02a4
--- /dev/null
+++ b/src/os/dir_plan9.go
@@ -0,0 +1,73 @@
+// 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 (
+ "io"
+ "syscall"
+)
+
+func (file *File) readdir(n int) ([]FileInfo, error) {
+ // If this file has no dirinfo, create one.
+ if file.dirinfo == nil {
+ file.dirinfo = new(dirInfo)
+ }
+ d := file.dirinfo
+ size := n
+ if size <= 0 {
+ size = 100
+ n = -1
+ }
+ fi := make([]FileInfo, 0, size) // Empty with room to grow.
+ for n != 0 {
+ // Refill the buffer if necessary.
+ if d.bufp >= d.nbuf {
+ nb, err := file.Read(d.buf[:])
+
+ // Update the buffer state before checking for errors.
+ d.bufp, d.nbuf = 0, nb
+
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ return fi, &PathError{"readdir", file.name, err}
+ }
+ if nb < syscall.STATFIXLEN {
+ return fi, &PathError{"readdir", file.name, syscall.ErrShortStat}
+ }
+ }
+
+ // Get a record from the buffer.
+ b := d.buf[d.bufp:]
+ m := int(uint16(b[0])|uint16(b[1])<<8) + 2
+ if m < syscall.STATFIXLEN {
+ return fi, &PathError{"readdir", file.name, syscall.ErrShortStat}
+ }
+
+ dir, err := syscall.UnmarshalDir(b[:m])
+ if err != nil {
+ return fi, &PathError{"readdir", file.name, err}
+ }
+ fi = append(fi, fileInfoFromStat(dir))
+
+ d.bufp += m
+ n--
+ }
+
+ if n >= 0 && len(fi) == 0 {
+ return fi, io.EOF
+ }
+ return fi, nil
+}
+
+func (file *File) readdirnames(n int) (names []string, err error) {
+ fi, err := file.Readdir(n)
+ names = make([]string, len(fi))
+ for i := range fi {
+ names[i] = fi[i].Name()
+ }
+ return
+}
diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go
new file mode 100644
index 000000000..d353e405e
--- /dev/null
+++ b/src/os/dir_unix.go
@@ -0,0 +1,58 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package os
+
+import (
+ "io"
+ "syscall"
+)
+
+const (
+ blockSize = 4096
+)
+
+func (f *File) readdirnames(n int) (names []string, err error) {
+ // If this file has no dirinfo, create one.
+ if f.dirinfo == nil {
+ f.dirinfo = new(dirInfo)
+ // The buffer must be at least a block long.
+ f.dirinfo.buf = make([]byte, blockSize)
+ }
+ d := f.dirinfo
+
+ size := n
+ if size <= 0 {
+ size = 100
+ n = -1
+ }
+
+ names = make([]string, 0, size) // Empty with room to grow.
+ for n != 0 {
+ // Refill the buffer if necessary
+ if d.bufp >= d.nbuf {
+ d.bufp = 0
+ var errno error
+ d.nbuf, errno = syscall.ReadDirent(f.fd, d.buf)
+ if errno != nil {
+ return names, NewSyscallError("readdirent", errno)
+ }
+ if d.nbuf <= 0 {
+ break // EOF
+ }
+ }
+
+ // Drain the buffer
+ var nb, nc int
+ nb, nc, names = syscall.ParseDirent(d.buf[d.bufp:d.nbuf], n, names)
+ d.bufp += nb
+ n -= nc
+ }
+ if n >= 0 && len(names) == 0 {
+ return names, io.EOF
+ }
+ return names, nil
+}
diff --git a/src/os/dir_windows.go b/src/os/dir_windows.go
new file mode 100644
index 000000000..931316048
--- /dev/null
+++ b/src/os/dir_windows.go
@@ -0,0 +1,14 @@
+// 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
+
+func (file *File) readdirnames(n int) (names []string, err error) {
+ fis, err := file.Readdir(n)
+ names = make([]string, len(fis))
+ for i, fi := range fis {
+ names[i] = fi.Name()
+ }
+ return names, err
+}
diff --git a/src/os/doc.go b/src/os/doc.go
new file mode 100644
index 000000000..389a8eb14
--- /dev/null
+++ b/src/os/doc.go
@@ -0,0 +1,135 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "time"
+
+// FindProcess looks for a running process by its pid.
+// The Process it returns can be used to obtain information
+// about the underlying operating system process.
+func FindProcess(pid int) (p *Process, err error) {
+ return findProcess(pid)
+}
+
+// StartProcess starts a new process with the program, arguments and attributes
+// specified by name, argv and attr.
+//
+// StartProcess is a low-level interface. The os/exec package provides
+// higher-level interfaces.
+//
+// If there is an error, it will be of type *PathError.
+func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
+ return startProcess(name, argv, attr)
+}
+
+// Release releases any resources associated with the Process p,
+// rendering it unusable in the future.
+// Release only needs to be called if Wait is not.
+func (p *Process) Release() error {
+ return p.release()
+}
+
+// Kill causes the Process to exit immediately.
+func (p *Process) Kill() error {
+ return p.kill()
+}
+
+// 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)
+}
+
+// UserTime returns the user CPU time of the exited process and its children.
+func (p *ProcessState) UserTime() time.Duration {
+ return p.userTime()
+}
+
+// SystemTime returns the system CPU time of the exited process and its children.
+func (p *ProcessState) SystemTime() time.Duration {
+ return p.systemTime()
+}
+
+// Exited reports whether the program has exited.
+func (p *ProcessState) Exited() bool {
+ return p.exited()
+}
+
+// Success reports whether the program exited successfully,
+// such as with exit status 0 on Unix.
+func (p *ProcessState) Success() bool {
+ return p.success()
+}
+
+// Sys returns system-dependent exit information about
+// the process. Convert it to the appropriate underlying
+// type, such as syscall.WaitStatus on Unix, to access its contents.
+func (p *ProcessState) Sys() interface{} {
+ return p.sys()
+}
+
+// SysUsage returns system-dependent resource usage information about
+// the exited process. Convert it to the appropriate underlying
+// type, such as *syscall.Rusage on Unix, to access its contents.
+// (On Unix, *syscall.Rusage matches struct rusage as defined in the
+// getrusage(2) manual page.)
+func (p *ProcessState) SysUsage() interface{} {
+ return p.sysUsage()
+}
+
+// Hostname returns the host name reported by the kernel.
+func Hostname() (name string, err error) {
+ return hostname()
+}
+
+// Readdir reads the contents of the directory associated with file and
+// returns a slice of up to n FileInfo values, as would be returned
+// by Lstat, in directory order. Subsequent calls on the same file will yield
+// further FileInfos.
+//
+// If n > 0, Readdir returns at most n FileInfo structures. In this case, if
+// Readdir returns an empty slice, it will return a non-nil error
+// explaining why. At the end of a directory, the error is io.EOF.
+//
+// If n <= 0, Readdir returns all the FileInfo from the directory in
+// a single slice. In this case, if Readdir succeeds (reads all
+// the way to the end of the directory), it returns the slice and a
+// nil error. If it encounters an error before the end of the
+// directory, Readdir returns the FileInfo read until that point
+// and a non-nil error.
+func (f *File) Readdir(n int) (fi []FileInfo, err error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ return f.readdir(n)
+}
+
+// Readdirnames reads and returns a slice of names from the directory f.
+//
+// If n > 0, Readdirnames returns at most n names. In this case, if
+// Readdirnames returns an empty slice, it will return a non-nil error
+// explaining why. At the end of a directory, the error is io.EOF.
+//
+// If n <= 0, Readdirnames returns all the names from the directory in
+// a single slice. In this case, if Readdirnames succeeds (reads all
+// the way to the end of the directory), it returns the slice and a
+// nil error. If it encounters an error before the end of the
+// directory, Readdirnames returns the names read until that point and
+// a non-nil error.
+func (f *File) Readdirnames(n int) (names []string, err error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ return f.readdirnames(n)
+}
diff --git a/src/os/env.go b/src/os/env.go
new file mode 100644
index 000000000..db7fc72b8
--- /dev/null
+++ b/src/os/env.go
@@ -0,0 +1,103 @@
+// Copyright 2010 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.
+
+// General environment variables.
+
+package os
+
+import "syscall"
+
+// Expand replaces ${var} or $var in the string based on the mapping function.
+// For example, os.ExpandEnv(s) is equivalent to os.Expand(s, os.Getenv).
+func Expand(s string, mapping func(string) string) string {
+ buf := make([]byte, 0, 2*len(s))
+ // ${} is all ASCII, so bytes are fine for this operation.
+ i := 0
+ for j := 0; j < len(s); j++ {
+ if s[j] == '$' && j+1 < len(s) {
+ buf = append(buf, s[i:j]...)
+ name, w := getShellName(s[j+1:])
+ buf = append(buf, mapping(name)...)
+ j += w
+ i = j + 1
+ }
+ }
+ return string(buf) + s[i:]
+}
+
+// ExpandEnv replaces ${var} or $var in the string according to the values
+// of the current environment variables. References to undefined
+// variables are replaced by the empty string.
+func ExpandEnv(s string) string {
+ return Expand(s, Getenv)
+}
+
+// isSpellSpecialVar reports whether the character identifies a special
+// shell variable such as $*.
+func isShellSpecialVar(c uint8) bool {
+ switch c {
+ case '*', '#', '$', '@', '!', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ return true
+ }
+ return false
+}
+
+// isAlphaNum reports whether the byte is an ASCII letter, number, or underscore
+func isAlphaNum(c uint8) bool {
+ return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
+}
+
+// getName returns the name that begins the string and the number of bytes
+// consumed to extract it. If the name is enclosed in {}, it's part of a ${}
+// expansion and two more bytes are needed than the length of the name.
+func getShellName(s string) (string, int) {
+ switch {
+ case s[0] == '{':
+ if len(s) > 2 && isShellSpecialVar(s[1]) && s[2] == '}' {
+ return s[1:2], 3
+ }
+ // Scan to closing brace
+ for i := 1; i < len(s); i++ {
+ if s[i] == '}' {
+ return s[1:i], i + 1
+ }
+ }
+ return "", 1 // Bad syntax; just eat the brace.
+ case isShellSpecialVar(s[0]):
+ return s[0:1], 1
+ }
+ // Scan alphanumerics.
+ var i int
+ for i = 0; i < len(s) && isAlphaNum(s[i]); i++ {
+ }
+ return s[:i], i
+}
+
+// Getenv retrieves the value of the environment variable named by the key.
+// It returns the value, which will be empty if the variable is not present.
+func Getenv(key string) string {
+ v, _ := syscall.Getenv(key)
+ return v
+}
+
+// Setenv sets the value of the environment variable named by the key.
+// It returns an error, if any.
+func Setenv(key, value string) error {
+ err := syscall.Setenv(key, value)
+ if err != nil {
+ return NewSyscallError("setenv", err)
+ }
+ return nil
+}
+
+// Clearenv deletes all environment variables.
+func Clearenv() {
+ syscall.Clearenv()
+}
+
+// Environ returns a copy of strings representing the environment,
+// in the form "key=value".
+func Environ() []string {
+ return syscall.Environ()
+}
diff --git a/src/os/env_test.go b/src/os/env_test.go
new file mode 100644
index 000000000..991fa4d05
--- /dev/null
+++ b/src/os/env_test.go
@@ -0,0 +1,70 @@
+// Copyright 2010 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_test
+
+import (
+ . "os"
+ "reflect"
+ "testing"
+)
+
+// testGetenv gives us a controlled set of variables for testing Expand.
+func testGetenv(s string) string {
+ switch s {
+ case "*":
+ return "all the args"
+ case "#":
+ return "NARGS"
+ case "$":
+ return "PID"
+ case "1":
+ return "ARGUMENT1"
+ case "HOME":
+ return "/usr/gopher"
+ case "H":
+ return "(Value of H)"
+ case "home_1":
+ return "/usr/foo"
+ case "_":
+ return "underscore"
+ }
+ return ""
+}
+
+var expandTests = []struct {
+ in, out string
+}{
+ {"", ""},
+ {"$*", "all the args"},
+ {"$$", "PID"},
+ {"${*}", "all the args"},
+ {"$1", "ARGUMENT1"},
+ {"${1}", "ARGUMENT1"},
+ {"now is the time", "now is the time"},
+ {"$HOME", "/usr/gopher"},
+ {"$home_1", "/usr/foo"},
+ {"${HOME}", "/usr/gopher"},
+ {"${H}OME", "(Value of H)OME"},
+ {"A$$$#$1$H$home_1*B", "APIDNARGSARGUMENT1(Value of H)/usr/foo*B"},
+}
+
+func TestExpand(t *testing.T) {
+ for _, test := range expandTests {
+ result := Expand(test.in, testGetenv)
+ if result != test.out {
+ t.Errorf("Expand(%q)=%q; expected %q", test.in, result, test.out)
+ }
+ }
+}
+
+func TestConsistentEnviron(t *testing.T) {
+ e0 := Environ()
+ for i := 0; i < 10; i++ {
+ e1 := Environ()
+ if !reflect.DeepEqual(e0, e1) {
+ t.Fatalf("environment changed")
+ }
+ }
+}
diff --git a/src/os/env_unix_test.go b/src/os/env_unix_test.go
new file mode 100644
index 000000000..5ec07ee1b
--- /dev/null
+++ b/src/os/env_unix_test.go
@@ -0,0 +1,30 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package os_test
+
+import (
+ . "os"
+ "testing"
+)
+
+var setenvEinvalTests = []struct {
+ k, v string
+}{
+ {"", ""}, // empty key
+ {"k=v", ""}, // '=' in key
+ {"\x00", ""}, // '\x00' in key
+ {"k", "\x00"}, // '\x00' in value
+}
+
+func TestSetenvUnixEinval(t *testing.T) {
+ for _, tt := range setenvEinvalTests {
+ err := Setenv(tt.k, tt.v)
+ if err == nil {
+ t.Errorf(`Setenv(%q, %q) == nil, want error`, tt.k, tt.v)
+ }
+ }
+}
diff --git a/src/os/error.go b/src/os/error.go
new file mode 100644
index 000000000..8810e6930
--- /dev/null
+++ b/src/os/error.go
@@ -0,0 +1,65 @@
+// 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 (
+ "errors"
+)
+
+// Portable analogs of some common system call errors.
+var (
+ ErrInvalid = errors.New("invalid argument")
+ ErrPermission = errors.New("permission denied")
+ ErrExist = errors.New("file already exists")
+ ErrNotExist = errors.New("file does not exist")
+)
+
+// PathError records an error and the operation and file path that caused it.
+type PathError struct {
+ Op string
+ Path string
+ Err error
+}
+
+func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
+
+// SyscallError records an error from a specific system call.
+type SyscallError struct {
+ Syscall string
+ Err error
+}
+
+func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }
+
+// NewSyscallError returns, as an error, a new SyscallError
+// with the given system call name and error details.
+// As a convenience, if err is nil, NewSyscallError returns nil.
+func NewSyscallError(syscall string, err error) error {
+ if err == nil {
+ return nil
+ }
+ return &SyscallError{syscall, err}
+}
+
+// IsExist returns a boolean indicating whether the error is known to report
+// that a file or directory already exists. It is satisfied by ErrExist as
+// well as some syscall errors.
+func IsExist(err error) bool {
+ return isExist(err)
+}
+
+// IsNotExist returns a boolean indicating whether the error is known to
+// report that a file or directory does not exist. It is satisfied by
+// ErrNotExist as well as some syscall errors.
+func IsNotExist(err error) bool {
+ return isNotExist(err)
+}
+
+// IsPermission returns a boolean indicating whether the error is known to
+// report that permission is denied. It is satisfied by ErrPermission as well
+// as some syscall errors.
+func IsPermission(err error) bool {
+ return isPermission(err)
+}
diff --git a/src/os/error_plan9.go b/src/os/error_plan9.go
new file mode 100644
index 000000000..62d4e420e
--- /dev/null
+++ b/src/os/error_plan9.go
@@ -0,0 +1,53 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+func isExist(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return contains(err.Error(), " exists")
+}
+
+func isNotExist(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return contains(err.Error(), "does not exist") || contains(err.Error(), "not found")
+}
+
+func isPermission(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return contains(err.Error(), "permission denied")
+}
+
+// contains is a local version of strings.Contains. It knows len(sep) > 1.
+func contains(s, sep string) bool {
+ n := len(sep)
+ c := sep[0]
+ for i := 0; i+n <= len(s); i++ {
+ if s[i] == c && s[i:i+n] == sep {
+ return true
+ }
+ }
+ return false
+}
diff --git a/src/os/error_test.go b/src/os/error_test.go
new file mode 100644
index 000000000..02ed2351c
--- /dev/null
+++ b/src/os/error_test.go
@@ -0,0 +1,132 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestErrIsExist(t *testing.T) {
+ f, err := ioutil.TempFile("", "_Go_ErrIsExist")
+ if err != nil {
+ t.Fatalf("open ErrIsExist tempfile: %s", err)
+ return
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ f2, err := os.OpenFile(f.Name(), os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
+ if err == nil {
+ f2.Close()
+ t.Fatal("Open should have failed")
+ return
+ }
+ if s := checkErrorPredicate("os.IsExist", os.IsExist, err); s != "" {
+ t.Fatal(s)
+ return
+ }
+}
+
+func testErrNotExist(name string) string {
+ f, err := os.Open(name)
+ if err == nil {
+ f.Close()
+ return "Open should have failed"
+ }
+ if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err); s != "" {
+ return s
+ }
+
+ err = os.Chdir(name)
+ if err == nil {
+ return "Chdir should have failed"
+ }
+ if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err); s != "" {
+ return s
+ }
+ return ""
+}
+
+func TestErrIsNotExist(t *testing.T) {
+ tmpDir, err := ioutil.TempDir("", "_Go_ErrIsNotExist")
+ if err != nil {
+ t.Fatalf("create ErrIsNotExist tempdir: %s", err)
+ return
+ }
+ defer os.RemoveAll(tmpDir)
+
+ name := filepath.Join(tmpDir, "NotExists")
+ if s := testErrNotExist(name); s != "" {
+ t.Fatal(s)
+ return
+ }
+
+ name = filepath.Join(name, "NotExists2")
+ if s := testErrNotExist(name); s != "" {
+ t.Fatal(s)
+ return
+ }
+}
+
+func checkErrorPredicate(predName string, pred func(error) bool, err error) string {
+ if !pred(err) {
+ return fmt.Sprintf("%s does not work as expected for %#v", predName, err)
+ }
+ return ""
+}
+
+var isExistTests = []struct {
+ err error
+ is bool
+ isnot bool
+}{
+ {&os.PathError{Err: os.ErrInvalid}, false, false},
+ {&os.PathError{Err: os.ErrPermission}, false, false},
+ {&os.PathError{Err: os.ErrExist}, true, false},
+ {&os.PathError{Err: os.ErrNotExist}, false, true},
+ {&os.LinkError{Err: os.ErrInvalid}, false, false},
+ {&os.LinkError{Err: os.ErrPermission}, false, false},
+ {&os.LinkError{Err: os.ErrExist}, true, false},
+ {&os.LinkError{Err: os.ErrNotExist}, false, true},
+ {nil, false, false},
+}
+
+func TestIsExist(t *testing.T) {
+ for _, tt := range isExistTests {
+ if is := os.IsExist(tt.err); is != tt.is {
+ t.Errorf("os.IsExist(%T %v) = %v, want %v", tt.err, tt.err, is, tt.is)
+ }
+ if isnot := os.IsNotExist(tt.err); isnot != tt.isnot {
+ t.Errorf("os.IsNotExist(%T %v) = %v, want %v", tt.err, tt.err, isnot, tt.isnot)
+ }
+ }
+}
+
+func TestErrPathNUL(t *testing.T) {
+ f, err := ioutil.TempFile("", "_Go_ErrPathNUL\x00")
+ if err == nil {
+ f.Close()
+ t.Fatal("TempFile should have failed")
+ }
+ f, err = ioutil.TempFile("", "_Go_ErrPathNUL")
+ if err != nil {
+ t.Fatalf("open ErrPathNUL tempfile: %s", err)
+ }
+ defer os.Remove(f.Name())
+ defer f.Close()
+ f2, err := os.OpenFile(f.Name(), os.O_RDWR, 0600)
+ if err != nil {
+ t.Fatalf("open ErrPathNUL: %s", err)
+ }
+ f2.Close()
+ f2, err = os.OpenFile(f.Name()+"\x00", os.O_RDWR, 0600)
+ if err == nil {
+ f2.Close()
+ t.Fatal("Open should have failed")
+ }
+}
diff --git a/src/os/error_unix.go b/src/os/error_unix.go
new file mode 100644
index 000000000..f2aabbb45
--- /dev/null
+++ b/src/os/error_unix.go
@@ -0,0 +1,45 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package os
+
+import "syscall"
+
+func isExist(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return err == syscall.EEXIST || err == ErrExist
+}
+
+func isNotExist(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return err == syscall.ENOENT || err == ErrNotExist
+}
+
+func isPermission(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return err == syscall.EACCES || err == syscall.EPERM || err == ErrPermission
+}
diff --git a/src/os/error_windows.go b/src/os/error_windows.go
new file mode 100644
index 000000000..83db6c078
--- /dev/null
+++ b/src/os/error_windows.go
@@ -0,0 +1,45 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import "syscall"
+
+func isExist(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return err == syscall.ERROR_ALREADY_EXISTS ||
+ err == syscall.ERROR_FILE_EXISTS || err == ErrExist
+}
+
+func isNotExist(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return err == syscall.ERROR_FILE_NOT_FOUND ||
+ err == syscall.ERROR_PATH_NOT_FOUND || err == ErrNotExist
+}
+
+func isPermission(err error) bool {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
+ err = pe.Err
+ }
+ return err == syscall.ERROR_ACCESS_DENIED || err == ErrPermission
+}
diff --git a/src/os/error_windows_test.go b/src/os/error_windows_test.go
new file mode 100644
index 000000000..3e6504f8d
--- /dev/null
+++ b/src/os/error_windows_test.go
@@ -0,0 +1,47 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func TestErrIsExistAfterRename(t *testing.T) {
+ dir, err := ioutil.TempDir("", "go-build")
+ if err != nil {
+ t.Fatalf("Create temp directory: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ src := filepath.Join(dir, "src")
+ dest := filepath.Join(dir, "dest")
+
+ f, err := os.Create(src)
+ if err != nil {
+ t.Fatalf("Create file %v: %v", src, err)
+ }
+ f.Close()
+ err = os.Rename(src, dest)
+ if err != nil {
+ t.Fatalf("Rename %v to %v: %v", src, dest, err)
+ }
+
+ f, err = os.Create(src)
+ if err != nil {
+ t.Fatalf("Create file %v: %v", src, err)
+ }
+ f.Close()
+ err = os.Rename(src, dest)
+ if err == nil {
+ t.Fatal("Rename should have failed")
+ }
+ if s := checkErrorPredicate("os.IsExist", os.IsExist, err); s != "" {
+ t.Fatal(s)
+ return
+ }
+}
diff --git a/src/os/exec.go b/src/os/exec.go
new file mode 100644
index 000000000..5aea3098b
--- /dev/null
+++ b/src/os/exec.go
@@ -0,0 +1,70 @@
+// 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 (
+ "runtime"
+ "sync/atomic"
+ "syscall"
+)
+
+// Process stores the information about a process created by StartProcess.
+type Process struct {
+ Pid int
+ handle uintptr
+ isdone uint32 // process has been successfully waited on, non zero if true
+}
+
+func newProcess(pid int, handle uintptr) *Process {
+ p := &Process{Pid: pid, handle: handle}
+ runtime.SetFinalizer(p, (*Process).Release)
+ return p
+}
+
+func (p *Process) setDone() {
+ atomic.StoreUint32(&p.isdone, 1)
+}
+
+func (p *Process) done() bool {
+ return atomic.LoadUint32(&p.isdone) > 0
+}
+
+// ProcAttr holds the attributes that will be applied to a new process
+// started by StartProcess.
+type ProcAttr struct {
+ // If Dir is non-empty, the child changes into the directory before
+ // creating the process.
+ Dir string
+ // If Env is non-nil, it gives the environment variables for the
+ // new process in the form returned by Environ.
+ // If it is nil, the result of Environ will be used.
+ Env []string
+ // Files specifies the open files inherited by the new process. The
+ // first three entries correspond to standard input, standard output, and
+ // standard error. An implementation may support additional entries,
+ // depending on the underlying operating system. A nil entry corresponds
+ // to that file being closed when the process starts.
+ Files []*File
+
+ // Operating system-specific process creation attributes.
+ // Note that setting this field means that your program
+ // may not execute properly or even compile on some
+ // operating systems.
+ Sys *syscall.SysProcAttr
+}
+
+// A Signal represents an operating system signal.
+// The usual underlying implementation is operating system-dependent:
+// on Unix it is syscall.Signal.
+type Signal interface {
+ String() string
+ Signal() // to distinguish from other Stringers
+}
+
+// Getpid returns the process id of the caller.
+func Getpid() int { return syscall.Getpid() }
+
+// Getppid returns the process id of the caller's parent.
+func Getppid() int { return syscall.Getppid() }
diff --git a/src/os/exec/example_test.go b/src/os/exec/example_test.go
new file mode 100644
index 000000000..55eaac8ab
--- /dev/null
+++ b/src/os/exec/example_test.go
@@ -0,0 +1,75 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "log"
+ "os/exec"
+ "strings"
+)
+
+func ExampleLookPath() {
+ path, err := exec.LookPath("fortune")
+ if err != nil {
+ log.Fatal("installing fortune is in your future")
+ }
+ fmt.Printf("fortune is available at %s\n", path)
+}
+
+func ExampleCommand() {
+ cmd := exec.Command("tr", "a-z", "A-Z")
+ cmd.Stdin = strings.NewReader("some input")
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ err := cmd.Run()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("in all caps: %q\n", out.String())
+}
+
+func ExampleCmd_Output() {
+ out, err := exec.Command("date").Output()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("The date is %s\n", out)
+}
+
+func ExampleCmd_Start() {
+ cmd := exec.Command("sleep", "5")
+ err := cmd.Start()
+ if err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("Waiting for command to finish...")
+ err = cmd.Wait()
+ log.Printf("Command finished with error: %v", err)
+}
+
+func ExampleCmd_StdoutPipe() {
+ cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`)
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Fatal(err)
+ }
+ if err := cmd.Start(); err != nil {
+ log.Fatal(err)
+ }
+ var person struct {
+ Name string
+ Age int
+ }
+ if err := json.NewDecoder(stdout).Decode(&person); err != nil {
+ log.Fatal(err)
+ }
+ if err := cmd.Wait(); err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s is %d years old\n", person.Name, person.Age)
+}
diff --git a/src/os/exec/exec.go b/src/os/exec/exec.go
new file mode 100644
index 000000000..4aded4171
--- /dev/null
+++ b/src/os/exec/exec.go
@@ -0,0 +1,493 @@
+// 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 exec runs external commands. It wraps os.StartProcess to make it
+// easier to remap stdin and stdout, connect I/O with pipes, and do other
+// adjustments.
+package exec
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+)
+
+// Error records the name of a binary that failed to be executed
+// and the reason it failed.
+type Error struct {
+ Name string
+ Err error
+}
+
+func (e *Error) Error() string {
+ return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error()
+}
+
+// Cmd represents an external command being prepared or run.
+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. If Path is relative, it is evaluated relative
+ // to Dir.
+ Path string
+
+ // Args holds command line arguments, including the command as Args[0].
+ // If the Args field is empty or nil, Run uses {Path}.
+ //
+ // In typical use, both Path and Args are set by calling Command.
+ Args []string
+
+ // Env specifies the environment of the process.
+ // If Env is nil, Run uses the current process's environment.
+ Env []string
+
+ // Dir specifies the working directory of the command.
+ // If Dir is the empty string, Run runs the command in the
+ // calling process's current directory.
+ Dir string
+
+ // Stdin specifies the process's standard input. If Stdin is
+ // nil, the process reads from the null device (os.DevNull).
+ Stdin io.Reader
+
+ // Stdout and Stderr specify the process's standard output and error.
+ //
+ // If either is nil, Run connects the corresponding file descriptor
+ // to the null device (os.DevNull).
+ //
+ // If Stdout and Stderr are the same writer, at most one
+ // goroutine at a time will call Write.
+ Stdout io.Writer
+ Stderr io.Writer
+
+ // ExtraFiles specifies additional open files to be inherited by the
+ // new process. It does not include standard input, standard output, or
+ // standard error. If non-nil, entry i becomes file descriptor 3+i.
+ //
+ // BUG: on OS X 10.6, child processes may sometimes inherit unwanted fds.
+ // http://golang.org/issue/2603
+ ExtraFiles []*os.File
+
+ // SysProcAttr holds optional, operating system-specific attributes.
+ // Run passes it to os.StartProcess as the os.ProcAttr's Sys field.
+ SysProcAttr *syscall.SysProcAttr
+
+ // Process is the underlying process, once started.
+ Process *os.Process
+
+ // ProcessState contains information about an exited process,
+ // available after a call to Wait or Run.
+ ProcessState *os.ProcessState
+
+ lookPathErr error // LookPath error, if any.
+ finished bool // when Wait was called
+ childFiles []*os.File
+ closeAfterStart []io.Closer
+ closeAfterWait []io.Closer
+ goroutine []func() error
+ errch chan error // one send per goroutine
+}
+
+// Command returns the Cmd struct to execute the named program with
+// the given arguments.
+//
+// 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
+// name directly.
+//
+// The returned Cmd's Args field is constructed from the command name
+// 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 {
+ cmd := &Cmd{
+ Path: name,
+ Args: append([]string{name}, arg...),
+ }
+ 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.
+func interfaceEqual(a, b interface{}) bool {
+ defer func() {
+ recover()
+ }()
+ return a == b
+}
+
+func (c *Cmd) envv() []string {
+ if c.Env != nil {
+ return c.Env
+ }
+ return os.Environ()
+}
+
+func (c *Cmd) argv() []string {
+ if len(c.Args) > 0 {
+ return c.Args
+ }
+ return []string{c.Path}
+}
+
+func (c *Cmd) stdin() (f *os.File, err error) {
+ if c.Stdin == nil {
+ f, err = os.Open(os.DevNull)
+ if err != nil {
+ return
+ }
+ c.closeAfterStart = append(c.closeAfterStart, f)
+ return
+ }
+
+ if f, ok := c.Stdin.(*os.File); ok {
+ return f, nil
+ }
+
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return
+ }
+
+ c.closeAfterStart = append(c.closeAfterStart, pr)
+ c.closeAfterWait = append(c.closeAfterWait, pw)
+ c.goroutine = append(c.goroutine, func() error {
+ _, err := io.Copy(pw, c.Stdin)
+ if err1 := pw.Close(); err == nil {
+ err = err1
+ }
+ return err
+ })
+ return pr, nil
+}
+
+func (c *Cmd) stdout() (f *os.File, err error) {
+ return c.writerDescriptor(c.Stdout)
+}
+
+func (c *Cmd) stderr() (f *os.File, err error) {
+ if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
+ return c.childFiles[1], nil
+ }
+ return c.writerDescriptor(c.Stderr)
+}
+
+func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) {
+ if w == nil {
+ f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
+ if err != nil {
+ return
+ }
+ c.closeAfterStart = append(c.closeAfterStart, f)
+ return
+ }
+
+ if f, ok := w.(*os.File); ok {
+ return f, nil
+ }
+
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return
+ }
+
+ c.closeAfterStart = append(c.closeAfterStart, pw)
+ c.closeAfterWait = append(c.closeAfterWait, pr)
+ c.goroutine = append(c.goroutine, func() error {
+ _, err := io.Copy(w, pr)
+ return err
+ })
+ return pw, nil
+}
+
+func (c *Cmd) closeDescriptors(closers []io.Closer) {
+ for _, fd := range closers {
+ fd.Close()
+ }
+}
+
+// Run starts the specified command and waits for it to complete.
+//
+// The returned error is nil if the command runs, has no problems
+// copying stdin, stdout, and stderr, and exits with a zero exit
+// status.
+//
+// If the command fails to run or doesn't complete successfully, the
+// error is of type *ExitError. Other error types may be
+// returned for I/O problems.
+func (c *Cmd) Run() error {
+ if err := c.Start(); err != nil {
+ return err
+ }
+ 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.lookPathErr != nil {
+ c.closeDescriptors(c.closeAfterStart)
+ c.closeDescriptors(c.closeAfterWait)
+ 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")
+ }
+
+ type F func(*Cmd) (*os.File, error)
+ for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
+ fd, err := setupFd(c)
+ if err != nil {
+ c.closeDescriptors(c.closeAfterStart)
+ c.closeDescriptors(c.closeAfterWait)
+ return err
+ }
+ c.childFiles = append(c.childFiles, fd)
+ }
+ c.childFiles = append(c.childFiles, c.ExtraFiles...)
+
+ var err error
+ c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
+ Dir: c.Dir,
+ Files: c.childFiles,
+ Env: c.envv(),
+ Sys: c.SysProcAttr,
+ })
+ if err != nil {
+ c.closeDescriptors(c.closeAfterStart)
+ c.closeDescriptors(c.closeAfterWait)
+ return err
+ }
+
+ c.closeDescriptors(c.closeAfterStart)
+
+ c.errch = make(chan error, len(c.goroutine))
+ for _, fn := range c.goroutine {
+ go func(fn func() error) {
+ c.errch <- fn()
+ }(fn)
+ }
+
+ return nil
+}
+
+// An ExitError reports an unsuccessful exit by a command.
+type ExitError struct {
+ *os.ProcessState
+}
+
+func (e *ExitError) Error() string {
+ return e.ProcessState.String()
+}
+
+// Wait waits for the command to exit.
+// It must have been started by Start.
+//
+// The returned error is nil if the command runs, has no problems
+// copying stdin, stdout, and stderr, and exits with a zero exit
+// status.
+//
+// If the command fails to run or doesn't complete successfully, the
+// error is of type *ExitError. Other error types may be
+// returned for I/O problems.
+//
+// Wait releases any resources associated with the Cmd.
+func (c *Cmd) Wait() error {
+ if c.Process == nil {
+ return errors.New("exec: not started")
+ }
+ if c.finished {
+ return errors.New("exec: Wait was already called")
+ }
+ c.finished = true
+ state, err := c.Process.Wait()
+ c.ProcessState = state
+
+ var copyError error
+ for range c.goroutine {
+ if err := <-c.errch; err != nil && copyError == nil {
+ copyError = err
+ }
+ }
+
+ c.closeDescriptors(c.closeAfterWait)
+
+ if err != nil {
+ return err
+ } else if !state.Success() {
+ return &ExitError{state}
+ }
+
+ return copyError
+}
+
+// Output runs the command and returns its standard output.
+func (c *Cmd) Output() ([]byte, error) {
+ if c.Stdout != nil {
+ return nil, errors.New("exec: Stdout already set")
+ }
+ var b bytes.Buffer
+ c.Stdout = &b
+ err := c.Run()
+ return b.Bytes(), err
+}
+
+// CombinedOutput runs the command and returns its combined standard
+// output and standard error.
+func (c *Cmd) CombinedOutput() ([]byte, error) {
+ if c.Stdout != nil {
+ return nil, errors.New("exec: Stdout already set")
+ }
+ if c.Stderr != nil {
+ return nil, errors.New("exec: Stderr already set")
+ }
+ var b bytes.Buffer
+ c.Stdout = &b
+ c.Stderr = &b
+ err := c.Run()
+ return b.Bytes(), err
+}
+
+// StdinPipe returns a pipe that will be connected to the command's
+// standard input when the command starts.
+// The pipe will be closed automatically after Wait sees the command exit.
+// A caller need only call Close to force the pipe to close sooner.
+// For example, if the command being run will not exit until standard input
+// is closed, the caller must close the pipe.
+func (c *Cmd) StdinPipe() (io.WriteCloser, error) {
+ if c.Stdin != nil {
+ return nil, errors.New("exec: Stdin already set")
+ }
+ if c.Process != nil {
+ return nil, errors.New("exec: StdinPipe after process started")
+ }
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ c.Stdin = pr
+ c.closeAfterStart = append(c.closeAfterStart, pr)
+ wc := &closeOnce{File: pw}
+ c.closeAfterWait = append(c.closeAfterWait, wc)
+ return wc, nil
+}
+
+type closeOnce struct {
+ *os.File
+
+ once sync.Once
+ err error
+}
+
+func (c *closeOnce) Close() error {
+ 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
+// standard output when the command starts.
+//
+// Wait will close the pipe after seeing the command exit, so most callers
+// need not close the pipe themselves; however, an implication is that
+// it is incorrect to call Wait before all reads from the pipe have completed.
+// For the same reason, it is incorrect to call Run when using StdoutPipe.
+// See the example for idiomatic usage.
+func (c *Cmd) StdoutPipe() (io.ReadCloser, error) {
+ if c.Stdout != nil {
+ return nil, errors.New("exec: Stdout already set")
+ }
+ if c.Process != nil {
+ return nil, errors.New("exec: StdoutPipe after process started")
+ }
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ c.Stdout = pw
+ c.closeAfterStart = append(c.closeAfterStart, pw)
+ c.closeAfterWait = append(c.closeAfterWait, pr)
+ return pr, nil
+}
+
+// StderrPipe returns a pipe that will be connected to the command's
+// standard error when the command starts.
+//
+// Wait will close the pipe after seeing the command exit, so most callers
+// need not close the pipe themselves; however, an implication is that
+// it is incorrect to call Wait before all reads from the pipe have completed.
+// For the same reason, it is incorrect to use Run when using StderrPipe.
+// See the StdoutPipe example for idiomatic usage.
+func (c *Cmd) StderrPipe() (io.ReadCloser, error) {
+ if c.Stderr != nil {
+ return nil, errors.New("exec: Stderr already set")
+ }
+ if c.Process != nil {
+ return nil, errors.New("exec: StderrPipe after process started")
+ }
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ c.Stderr = pw
+ c.closeAfterStart = append(c.closeAfterStart, pw)
+ c.closeAfterWait = append(c.closeAfterWait, pr)
+ return pr, nil
+}
diff --git a/src/os/exec/exec_test.go b/src/os/exec/exec_test.go
new file mode 100644
index 000000000..6f77ac38a
--- /dev/null
+++ b/src/os/exec/exec_test.go
@@ -0,0 +1,726 @@
+// 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.
+
+// 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"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+)
+
+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 := exec.Command(os.Args[0], cs...)
+ cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
+ return cmd
+}
+
+func TestEcho(t *testing.T) {
+ bs, err := helperCommand(t, "echo", "foo bar", "baz").Output()
+ if err != nil {
+ t.Errorf("echo: %v", err)
+ }
+ if g, e := string(bs), "foo bar baz\n"; g != e {
+ t.Errorf("echo: want %q, got %q", e, g)
+ }
+}
+
+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(t, "cat")
+ p.Stdin = strings.NewReader(input)
+ bs, err := p.Output()
+ if err != nil {
+ t.Errorf("cat: %v", err)
+ }
+ s := string(bs)
+ if s != input {
+ t.Errorf("cat: want %q, got %q", input, s)
+ }
+}
+
+func TestCatGoodAndBadFile(t *testing.T) {
+ // Testing combined output and error values.
+ 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)
+ if len(sp) != 2 {
+ t.Fatalf("expected two lines from cat; got %q", s)
+ }
+ errLine, body := sp[0], sp[1]
+ if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") {
+ t.Errorf("expected stderr to complain about file; got %q", errLine)
+ }
+ if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") {
+ t.Errorf("expected test code; got %q (len %d)", body, len(body))
+ }
+}
+
+func TestNoExistBinary(t *testing.T) {
+ // Can't run a non-existent binary
+ err := exec.Command("/no-exist-binary").Run()
+ if err == nil {
+ t.Error("expected error from /no-exist-binary")
+ }
+}
+
+func TestExitStatus(t *testing.T) {
+ // Test that exit values are returned correctly
+ 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.(*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 *exec.ExitError from exit 42; got %T: %v", err, err)
+ }
+}
+
+func TestPipes(t *testing.T) {
+ check := func(what string, err error) {
+ if err != nil {
+ t.Fatalf("%s: %v", what, err)
+ }
+ }
+ // Cat, testing stdin and stdout.
+ c := helperCommand(t, "pipetest")
+ stdin, err := c.StdinPipe()
+ check("StdinPipe", err)
+ stdout, err := c.StdoutPipe()
+ check("StdoutPipe", err)
+ stderr, err := c.StderrPipe()
+ check("StderrPipe", err)
+
+ outbr := bufio.NewReader(stdout)
+ errbr := bufio.NewReader(stderr)
+ line := func(what string, br *bufio.Reader) string {
+ line, _, err := br.ReadLine()
+ if err != nil {
+ t.Fatalf("%s: %v", what, err)
+ }
+ return string(line)
+ }
+
+ err = c.Start()
+ check("Start", err)
+
+ _, err = stdin.Write([]byte("O:I am output\n"))
+ check("first stdin Write", err)
+ if g, e := line("first output line", outbr), "O:I am output"; g != e {
+ t.Errorf("got %q, want %q", g, e)
+ }
+
+ _, err = stdin.Write([]byte("E:I am error\n"))
+ check("second stdin Write", err)
+ if g, e := line("first error line", errbr), "E:I am error"; g != e {
+ t.Errorf("got %q, want %q", g, e)
+ }
+
+ _, err = stdin.Write([]byte("O:I am output2\n"))
+ check("third stdin Write 3", err)
+ if g, e := line("second output line", outbr), "O:I am output2"; g != e {
+ t.Errorf("got %q, want %q", g, e)
+ }
+
+ stdin.Close()
+ err = c.Wait()
+ check("Wait", err)
+}
+
+const stdinCloseTestString = "Some test string."
+
+// Issue 6270.
+func TestStdinClose(t *testing.T) {
+ check := func(what string, err error) {
+ if err != nil {
+ t.Fatalf("%s: %v", what, err)
+ }
+ }
+ cmd := helperCommand(t, "stdinClose")
+ stdin, err := cmd.StdinPipe()
+ check("StdinPipe", err)
+ // Check that we can access methods of the underlying os.File.`
+ if _, ok := stdin.(interface {
+ Fd() uintptr
+ }); !ok {
+ t.Error("can't access methods of underlying *os.File")
+ }
+ check("Start", cmd.Start())
+ go func() {
+ _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString))
+ check("Copy", err)
+ // Before the fix, this next line would race with cmd.Wait.
+ check("Close", stdin.Close())
+ }()
+ check("Wait", cmd.Wait())
+}
+
+// Issue 5071
+func TestPipeLookPathLeak(t *testing.T) {
+ fd0, lsof0 := numOpenFDS(t)
+ for i := 0; i < 4; i++ {
+ cmd := exec.Command("something-that-does-not-exist-binary")
+ cmd.StdoutPipe()
+ cmd.StderrPipe()
+ cmd.StdinPipe()
+ if err := cmd.Run(); err == nil {
+ t.Fatal("unexpected success")
+ }
+ }
+ for triesLeft := 3; triesLeft >= 0; triesLeft-- {
+ open, lsof := numOpenFDS(t)
+ fdGrowth := open - fd0
+ if fdGrowth > 2 {
+ if triesLeft > 0 {
+ // Work around what appears to be a race with Linux's
+ // proc filesystem (as used by lsof). It seems to only
+ // be eventually consistent. Give it awhile to settle.
+ // See golang.org/issue/7808
+ time.Sleep(100 * time.Millisecond)
+ continue
+ }
+ t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0)
+ }
+ break
+ }
+}
+
+func numOpenFDS(t *testing.T) (n int, lsof []byte) {
+ 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 bytes.Count(lsof, []byte("\n")), lsof
+}
+
+var testedAlreadyLeaked = false
+
+// basefds returns the number of expected file descriptors
+// to be present in a process at start.
+func basefds() uintptr {
+ n := os.Stderr.Fd() + 1
+
+ // Go runtime for 32-bit Plan 9 requires that /dev/bintime
+ // be kept open.
+ // See ../../runtime/time_plan9_386.c:/^runtime·nanotime
+ if runtime.GOOS == "plan9" && runtime.GOARCH == "386" {
+ n++
+ }
+ return n
+}
+
+func closeUnexpectedFds(t *testing.T, m string) {
+ for fd := basefds(); fd <= 101; fd++ {
+ err := os.NewFile(fd, "").Close()
+ if err == nil {
+ t.Logf("%s: Something already leaked - closed fd %d", m, fd)
+ }
+ }
+}
+
+func TestExtraFilesFDShuffle(t *testing.T) {
+ t.Skip("flaky test; see http://golang.org/issue/5780")
+ switch runtime.GOOS {
+ case "darwin":
+ // TODO(cnicolaou): http://golang.org/issue/2603
+ // leads to leaked file descriptors in this test when it's
+ // run from a builder.
+ closeUnexpectedFds(t, "TestExtraFilesFDShuffle")
+ case "netbsd":
+ // http://golang.org/issue/3955
+ closeUnexpectedFds(t, "TestExtraFilesFDShuffle")
+ case "windows":
+ t.Skip("no operating system support; skipping")
+ }
+
+ // syscall.StartProcess maps all the FDs passed to it in
+ // ProcAttr.Files (the concatenation of stdin,stdout,stderr and
+ // ExtraFiles) into consecutive FDs in the child, that is:
+ // Files{11, 12, 6, 7, 9, 3} should result in the file
+ // represented by FD 11 in the parent being made available as 0
+ // in the child, 12 as 1, etc.
+ //
+ // We want to test that FDs in the child do not get overwritten
+ // by one another as this shuffle occurs. The original implementation
+ // was buggy in that in some data dependent cases it would ovewrite
+ // stderr in the child with one of the ExtraFile members.
+ // Testing for this case is difficult because it relies on using
+ // the same FD values as that case. In particular, an FD of 3
+ // must be at an index of 4 or higher in ProcAttr.Files and
+ // the FD of the write end of the Stderr pipe (as obtained by
+ // StderrPipe()) must be the same as the size of ProcAttr.Files;
+ // therefore we test that the read end of this pipe (which is what
+ // is returned to the parent by StderrPipe() being one less than
+ // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles).
+ //
+ // 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(t, "extraFilesAndPipes", strconv.Itoa(npipes+1))
+ rd, wr, _ := os.Pipe()
+ defer rd.Close()
+ if rd.Fd() != 3 {
+ t.Errorf("bad test value for test pipe: fd %d", rd.Fd())
+ }
+ stderr, _ := c.StderrPipe()
+ wr.WriteString("_LAST")
+ wr.Close()
+
+ pipes := make([]struct {
+ r, w *os.File
+ }, npipes)
+ data := []string{"a", "b"}
+
+ for i := 0; i < npipes; i++ {
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatalf("unexpected error creating pipe: %s", err)
+ }
+ pipes[i].r = r
+ pipes[i].w = w
+ w.WriteString(data[i])
+ c.ExtraFiles = append(c.ExtraFiles, pipes[i].r)
+ defer func() {
+ r.Close()
+ w.Close()
+ }()
+ }
+ // Put fd 3 at the end.
+ c.ExtraFiles = append(c.ExtraFiles, rd)
+
+ stderrFd := int(stderr.(*os.File).Fd())
+ if stderrFd != ((len(c.ExtraFiles) + 3) - 1) {
+ t.Errorf("bad test value for stderr pipe")
+ }
+
+ expected := "child: " + strings.Join(data, "") + "_LAST"
+
+ err := c.Start()
+ if err != nil {
+ t.Fatalf("Run: %v", err)
+ }
+ ch := make(chan string, 1)
+ go func(ch chan string) {
+ buf := make([]byte, 512)
+ n, err := stderr.Read(buf)
+ if err != nil {
+ t.Fatalf("Read: %s", err)
+ ch <- err.Error()
+ } else {
+ ch <- string(buf[:n])
+ }
+ close(ch)
+ }(ch)
+ select {
+ case m := <-ch:
+ if m != expected {
+ t.Errorf("Read: '%s' not '%s'", m, expected)
+ }
+ case <-time.After(5 * time.Second):
+ t.Errorf("Read timedout")
+ }
+ c.Wait()
+}
+
+func TestExtraFiles(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skip("no operating system support; skipping")
+ }
+
+ // Ensure that file descriptors have not already been leaked into
+ // our environment.
+ if !testedAlreadyLeaked {
+ testedAlreadyLeaked = true
+ closeUnexpectedFds(t, "TestExtraFiles")
+ }
+
+ // Force network usage, to verify the epoll (or whatever) fd
+ // doesn't leak to the child,
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ln.Close()
+
+ // Make sure duplicated fds don't leak to the child.
+ f, err := ln.(*net.TCPListener).File()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer f.Close()
+ ln2, err := net.FileListener(f)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer ln2.Close()
+
+ // Force TLS root certs to be loaded (which might involve
+ // cgo), to make sure none of that potential C code leaks fds.
+ 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()
+ _, 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 {
+ t.Fatalf("TempFile: %v", err)
+ }
+ defer os.Remove(tf.Name())
+ defer tf.Close()
+
+ const text = "Hello, fd 3!"
+ _, err = tf.Write([]byte(text))
+ if err != nil {
+ t.Fatalf("Write: %v", err)
+ }
+ _, err = tf.Seek(0, os.SEEK_SET)
+ if err != nil {
+ t.Fatalf("Seek: %v", err)
+ }
+
+ c := helperCommand(t, "read3")
+ var stdout, stderr bytes.Buffer
+ c.Stdout = &stdout
+ c.Stderr = &stderr
+ c.ExtraFiles = []*os.File{tf}
+ err = c.Run()
+ if err != nil {
+ t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes())
+ }
+ if stdout.String() != text {
+ t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
+ }
+}
+
+func TestExtraFilesRace(t *testing.T) {
+ if runtime.GOOS == "windows" {
+ t.Skip("no operating system support; skipping")
+ }
+ listen := func() net.Listener {
+ ln, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ return ln
+ }
+ listenerFile := func(ln net.Listener) *os.File {
+ f, err := ln.(*net.TCPListener).File()
+ if err != nil {
+ t.Fatal(err)
+ }
+ return f
+ }
+ runCommand := func(c *exec.Cmd, out chan<- string) {
+ bout, err := c.CombinedOutput()
+ if err != nil {
+ out <- "ERROR:" + err.Error()
+ } else {
+ out <- string(bout)
+ }
+ }
+
+ for i := 0; i < 10; i++ {
+ la := listen()
+ ca := helperCommand(t, "describefiles")
+ ca.ExtraFiles = []*os.File{listenerFile(la)}
+ lb := listen()
+ cb := helperCommand(t, "describefiles")
+ cb.ExtraFiles = []*os.File{listenerFile(lb)}
+ ares := make(chan string)
+ bres := make(chan string)
+ go runCommand(ca, ares)
+ go runCommand(cb, bres)
+ if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
+ t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
+ }
+ if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
+ t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
+ }
+ la.Close()
+ lb.Close()
+ for _, f := range ca.ExtraFiles {
+ f.Close()
+ }
+ for _, f := range cb.ExtraFiles {
+ f.Close()
+ }
+
+ }
+}
+
+// TestHelperProcess isn't a real test. It's used as a helper process
+// for TestParameterRun.
+func TestHelperProcess(*testing.T) {
+ if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
+ return
+ }
+ defer os.Exit(0)
+
+ // Determine which command to use to display open files.
+ ofcmd := "lsof"
+ switch runtime.GOOS {
+ case "dragonfly", "freebsd", "netbsd", "openbsd":
+ ofcmd = "fstat"
+ case "plan9":
+ ofcmd = "/bin/cat"
+ }
+
+ args := os.Args
+ for len(args) > 0 {
+ if args[0] == "--" {
+ args = args[1:]
+ break
+ }
+ args = args[1:]
+ }
+ if len(args) == 0 {
+ fmt.Fprintf(os.Stderr, "No command\n")
+ os.Exit(2)
+ }
+
+ cmd, args := args[0], args[1:]
+ switch cmd {
+ case "echo":
+ iargs := []interface{}{}
+ for _, s := range args {
+ iargs = append(iargs, s)
+ }
+ fmt.Println(iargs...)
+ case "cat":
+ if len(args) == 0 {
+ io.Copy(os.Stdout, os.Stdin)
+ return
+ }
+ exit := 0
+ for _, fn := range args {
+ f, err := os.Open(fn)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ exit = 2
+ } else {
+ defer f.Close()
+ io.Copy(os.Stdout, f)
+ }
+ }
+ os.Exit(exit)
+ case "pipetest":
+ bufr := bufio.NewReader(os.Stdin)
+ for {
+ line, _, err := bufr.ReadLine()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ os.Exit(1)
+ }
+ if bytes.HasPrefix(line, []byte("O:")) {
+ os.Stdout.Write(line)
+ os.Stdout.Write([]byte{'\n'})
+ } else if bytes.HasPrefix(line, []byte("E:")) {
+ os.Stderr.Write(line)
+ os.Stderr.Write([]byte{'\n'})
+ } else {
+ os.Exit(1)
+ }
+ }
+ case "stdinClose":
+ b, err := ioutil.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+ if s := string(b); s != stdinCloseTestString {
+ fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
+ os.Exit(1)
+ }
+ os.Exit(0)
+ case "read3": // read fd 3
+ fd3 := os.NewFile(3, "fd3")
+ bs, err := ioutil.ReadAll(fd3)
+ if err != nil {
+ fmt.Printf("ReadAll from fd 3: %v", err)
+ os.Exit(1)
+ }
+ switch runtime.GOOS {
+ case "dragonfly":
+ // TODO(jsing): Determine why DragonFly is leaking
+ // file descriptors...
+ case "darwin":
+ // TODO(bradfitz): broken? Sometimes.
+ // http://golang.org/issue/2603
+ // Skip this additional part of the test for now.
+ case "netbsd":
+ // TODO(jsing): This currently fails on NetBSD due to
+ // the cloned file descriptors that result from opening
+ // /dev/urandom.
+ // http://golang.org/issue/3955
+ 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
+ for wantfd := basefds() + 1; wantfd <= 100; wantfd++ {
+ f, err := os.Open(os.Args[0])
+ if err != nil {
+ fmt.Printf("error opening file with expected fd %d: %v", wantfd, err)
+ os.Exit(1)
+ }
+ if got := f.Fd(); got != wantfd {
+ fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd)
+ 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)
+ }
+ files = append(files, f)
+ }
+ for _, f := range files {
+ f.Close()
+ }
+ }
+ // Referring to fd3 here ensures that it is not
+ // garbage collected, and therefore closed, while
+ // executing the wantfd loop above. It doesn't matter
+ // what we do with fd3 as long as we refer to it;
+ // closing it is the easy choice.
+ fd3.Close()
+ os.Stdout.Write(bs)
+ case "exit":
+ n, _ := strconv.Atoi(args[0])
+ os.Exit(n)
+ case "describefiles":
+ f := os.NewFile(3, fmt.Sprintf("fd3"))
+ ln, err := net.FileListener(f)
+ if err == nil {
+ fmt.Printf("fd3: listener %s\n", ln.Addr())
+ ln.Close()
+ }
+ os.Exit(0)
+ case "extraFilesAndPipes":
+ n, _ := strconv.Atoi(args[0])
+ pipes := make([]*os.File, n)
+ for i := 0; i < n; i++ {
+ pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i))
+ }
+ response := ""
+ for i, r := range pipes {
+ ch := make(chan string, 1)
+ go func(c chan string) {
+ buf := make([]byte, 10)
+ n, err := r.Read(buf)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i)
+ os.Exit(1)
+ }
+ c <- string(buf[:n])
+ close(c)
+ }(ch)
+ select {
+ case m := <-ch:
+ response = response + m
+ case <-time.After(5 * time.Second):
+ fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i)
+ os.Exit(1)
+ }
+ }
+ 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/os/exec/lp_plan9.go b/src/os/exec/lp_plan9.go
new file mode 100644
index 000000000..5aa8a54ed
--- /dev/null
+++ b/src/os/exec/lp_plan9.go
@@ -0,0 +1,53 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "errors"
+ "os"
+ "strings"
+)
+
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = errors.New("executable file not found in $path")
+
+func findExecutable(file string) error {
+ d, err := os.Stat(file)
+ if err != nil {
+ return err
+ }
+ if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
+ return nil
+ }
+ return os.ErrPermission
+}
+
+// LookPath searches for an executable binary named file
+// in the directories named by the path environment variable.
+// If file begins with "/", "#", "./", or "../", it is tried
+// directly and the path is not consulted.
+// The result may be an absolute path or a path relative to the current directory.
+func LookPath(file string) (string, error) {
+ // skip the path lookup for these prefixes
+ skip := []string{"/", "#", "./", "../"}
+
+ for _, p := range skip {
+ if strings.HasPrefix(file, p) {
+ err := findExecutable(file)
+ if err == nil {
+ return file, nil
+ }
+ return "", &Error{file, err}
+ }
+ }
+
+ path := os.Getenv("path")
+ for _, dir := range strings.Split(path, "\000") {
+ if err := findExecutable(dir + "/" + file); err == nil {
+ return dir + "/" + file, nil
+ }
+ }
+ return "", &Error{file, ErrNotFound}
+}
diff --git a/src/os/exec/lp_test.go b/src/os/exec/lp_test.go
new file mode 100644
index 000000000..77d8e848c
--- /dev/null
+++ b/src/os/exec/lp_test.go
@@ -0,0 +1,33 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "testing"
+)
+
+var nonExistentPaths = []string{
+ "some-non-existent-path",
+ "non-existent-path/slashed",
+}
+
+func TestLookPathNotFound(t *testing.T) {
+ for _, name := range nonExistentPaths {
+ path, err := LookPath(name)
+ if err == nil {
+ t.Fatalf("LookPath found %q in $PATH", name)
+ }
+ if path != "" {
+ t.Fatalf("LookPath path == %q when err != nil", path)
+ }
+ perr, ok := err.(*Error)
+ if !ok {
+ t.Fatal("LookPath error is not an exec.Error")
+ }
+ if perr.Name != name {
+ t.Fatalf("want Error name %q, got %q", name, perr.Name)
+ }
+ }
+}
diff --git a/src/os/exec/lp_unix.go b/src/os/exec/lp_unix.go
new file mode 100644
index 000000000..3f895d5b3
--- /dev/null
+++ b/src/os/exec/lp_unix.go
@@ -0,0 +1,60 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package exec
+
+import (
+ "errors"
+ "os"
+ "strings"
+)
+
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = errors.New("executable file not found in $PATH")
+
+func findExecutable(file string) error {
+ d, err := os.Stat(file)
+ if err != nil {
+ return err
+ }
+ if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
+ return nil
+ }
+ return os.ErrPermission
+}
+
+// LookPath searches for an executable binary named file
+// in the directories named by the PATH environment variable.
+// If file contains a slash, it is tried directly and the PATH is not consulted.
+// The result may be an absolute path or a path relative to the current directory.
+func LookPath(file string) (string, error) {
+ // NOTE(rsc): I wish we could use the Plan 9 behavior here
+ // (only bypass the path if file begins with / or ./ or ../)
+ // but that would not match all the Unix shells.
+
+ if strings.Contains(file, "/") {
+ err := findExecutable(file)
+ if err == nil {
+ return file, nil
+ }
+ return "", &Error{file, err}
+ }
+ pathenv := os.Getenv("PATH")
+ if pathenv == "" {
+ return "", &Error{file, ErrNotFound}
+ }
+ for _, dir := range strings.Split(pathenv, ":") {
+ if dir == "" {
+ // Unix shell semantics: path element "" means "."
+ dir = "."
+ }
+ path := dir + "/" + file
+ if err := findExecutable(path); err == nil {
+ return path, nil
+ }
+ }
+ return "", &Error{file, ErrNotFound}
+}
diff --git a/src/os/exec/lp_unix_test.go b/src/os/exec/lp_unix_test.go
new file mode 100644
index 000000000..051db664a
--- /dev/null
+++ b/src/os/exec/lp_unix_test.go
@@ -0,0 +1,55 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package exec
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestLookPathUnixEmptyPath(t *testing.T) {
+ tmp, err := ioutil.TempDir("", "TestLookPathUnixEmptyPath")
+ if err != nil {
+ t.Fatal("TempDir failed: ", err)
+ }
+ defer os.RemoveAll(tmp)
+ wd, err := os.Getwd()
+ if err != nil {
+ t.Fatal("Getwd failed: ", err)
+ }
+ err = os.Chdir(tmp)
+ if err != nil {
+ t.Fatal("Chdir failed: ", err)
+ }
+ defer os.Chdir(wd)
+
+ f, err := os.OpenFile("exec_me", os.O_CREATE|os.O_EXCL, 0700)
+ if err != nil {
+ t.Fatal("OpenFile failed: ", err)
+ }
+ err = f.Close()
+ if err != nil {
+ t.Fatal("Close failed: ", err)
+ }
+
+ pathenv := os.Getenv("PATH")
+ defer os.Setenv("PATH", pathenv)
+
+ err = os.Setenv("PATH", "")
+ if err != nil {
+ t.Fatal("Setenv failed: ", err)
+ }
+
+ path, err := LookPath("exec_me")
+ if err == nil {
+ t.Fatal("LookPath found exec_me in empty $PATH")
+ }
+ if path != "" {
+ t.Fatalf("LookPath path == %q when err != nil", path)
+ }
+}
diff --git a/src/os/exec/lp_windows.go b/src/os/exec/lp_windows.go
new file mode 100644
index 000000000..c3efd67e9
--- /dev/null
+++ b/src/os/exec/lp_windows.go
@@ -0,0 +1,123 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package exec
+
+import (
+ "errors"
+ "os"
+ "strings"
+)
+
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = errors.New("executable file not found in %PATH%")
+
+func chkStat(file string) error {
+ d, err := os.Stat(file)
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return os.ErrPermission
+ }
+ return nil
+}
+
+func hasExt(file string) bool {
+ i := strings.LastIndex(file, ".")
+ if i < 0 {
+ return false
+ }
+ return strings.LastIndexAny(file, `:\/`) < i
+}
+
+func findExecutable(file string, exts []string) (string, error) {
+ if len(exts) == 0 {
+ return file, chkStat(file)
+ }
+ if hasExt(file) {
+ if chkStat(file) == nil {
+ return file, nil
+ }
+ }
+ for _, e := range exts {
+ if f := file + e; chkStat(f) == nil {
+ return f, nil
+ }
+ }
+ return ``, os.ErrNotExist
+}
+
+// LookPath searches for an executable binary named file
+// in the directories named by the PATH environment variable.
+// If file contains a slash, it is tried directly and the PATH is not consulted.
+// LookPath also uses PATHEXT environment variable to match
+// a suitable candidate.
+// The result may be an absolute path or a path relative to the current directory.
+func LookPath(file string) (f string, err error) {
+ x := os.Getenv(`PATHEXT`)
+ if x == `` {
+ x = `.COM;.EXE;.BAT;.CMD`
+ }
+ exts := []string{}
+ for _, e := range strings.Split(strings.ToLower(x), `;`) {
+ if e == "" {
+ continue
+ }
+ if e[0] != '.' {
+ e = "." + e
+ }
+ exts = append(exts, e)
+ }
+ if strings.IndexAny(file, `:\/`) != -1 {
+ if f, err = findExecutable(file, exts); err == nil {
+ return
+ }
+ return ``, &Error{file, err}
+ }
+ if f, err = findExecutable(`.\`+file, exts); err == nil {
+ return
+ }
+ if pathenv := os.Getenv(`PATH`); pathenv != `` {
+ for _, dir := range splitList(pathenv) {
+ if f, err = findExecutable(dir+`\`+file, exts); err == nil {
+ return
+ }
+ }
+ }
+ return ``, &Error{file, ErrNotFound}
+}
+
+func splitList(path string) []string {
+ // The same implementation is used in SplitList in path/filepath;
+ // consider changing path/filepath when changing this.
+
+ if path == "" {
+ return []string{}
+ }
+
+ // Split path, respecting but preserving quotes.
+ list := []string{}
+ start := 0
+ quo := false
+ for i := 0; i < len(path); i++ {
+ switch c := path[i]; {
+ case c == '"':
+ quo = !quo
+ case c == os.PathListSeparator && !quo:
+ list = append(list, path[start:i])
+ start = i + 1
+ }
+ }
+ list = append(list, path[start:])
+
+ // Remove quotes.
+ for i, s := range list {
+ if strings.Contains(s, `"`) {
+ list[i] = strings.Replace(s, `"`, ``, -1)
+ }
+ }
+
+ return list
+}
diff --git a/src/os/exec/lp_windows_test.go b/src/os/exec/lp_windows_test.go
new file mode 100644
index 000000000..72df03ed2
--- /dev/null
+++ b/src/os/exec/lp_windows_test.go
@@ -0,0 +1,573 @@
+// 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 exec
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+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
+ searchFor string
+ fails bool // test is expected to fail
+}
+
+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)
+ }
+}
+
+var lookPathTests = []lookPathTest{
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`, `p2\a`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1.dir;p2.dir`,
+ files: []string{`p1.dir\a`, `p2.dir\a.exe`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a.exe`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\b.exe`},
+ searchFor: `b`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\b`, `p2\a`},
+ searchFor: `a`,
+ fails: true, // TODO(brainman): do not know why this fails
+ },
+ // If the command name specifies a path, the shell searches
+ // the specified path for an executable file matching
+ // the command name. If a match is found, the external
+ // command (the executable file) executes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `p2\a`,
+ },
+ // If the command name specifies a path, the shell searches
+ // the specified path for an executable file matching the command
+ // name. ... If no match is found, the shell reports an error
+ // and command processing completes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\b.exe`, `p2\a.exe`},
+ searchFor: `p2\b`,
+ fails: true,
+ },
+ // If the command name does not specify a path, the shell
+ // searches the current directory for an executable file
+ // matching the command name. If a match is found, the external
+ // command (the executable file) executes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`a`, `p1\a.exe`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ // The shell now searches each directory specified by the
+ // PATH environment variable, in the order listed, for an
+ // executable file matching the command name. If a match
+ // is found, the external command (the executable file) executes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ // The shell now searches each directory specified by the
+ // PATH environment variable, in the order listed, for an
+ // executable file matching the command name. If no match
+ // is found, the shell reports an error and command processing
+ // completes.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `b`,
+ fails: true,
+ },
+ // If the command name includes a file extension, the shell
+ // searches each directory for the exact file name specified
+ // by the command name.
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a.exe`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a.com`,
+ fails: true, // includes extension and not exact file name match
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1`,
+ files: []string{`p1\a.exe.exe`},
+ searchFor: `a.exe`,
+ },
+ {
+ PATHEXT: `.COM;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.exe`, `p2\a.exe`},
+ searchFor: `a.exe`,
+ },
+ // If the command name does not include a file extension, the shell
+ // adds the extensions listed in the PATHEXT environment variable,
+ // one by one, and searches the directory for that file name. Note
+ // that the shell tries all possible file extensions in a specific
+ // directory before moving on to search the next directory
+ // (if there is one).
+ {
+ PATHEXT: `.COM;.EXE`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.bat`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.bat`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM;.EXE;.BAT`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.bat`, `p1\a.exe`, `p2\a.bat`, `p2\a.exe`},
+ searchFor: `a`,
+ },
+ {
+ PATHEXT: `.COM`,
+ PATH: `p1;p2`,
+ files: []string{`p1\a.bat`, `p2\a.exe`},
+ searchFor: `a`,
+ fails: true, // tried all extensions in PATHEXT, but none matches
+ },
+}
+
+func TestLookPath(t *testing.T) {
+ tmp, err := ioutil.TempDir("", "TestLookPath")
+ if err != nil {
+ t.Fatal("TempDir 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)
+ }
+}
+
+type commandTest struct {
+ PATH string
+ files []string
+ dir string
+ arg0 string
+ want string
+ fails bool // test is expected to fail
+}
+
+func (test commandTest) isSuccess(rootDir, output string, err error) error {
+ if err != nil {
+ return fmt.Errorf("test=%+v: exec: %v %v", test, err, output)
+ }
+ path := output
+ if path[:len(rootDir)] != rootDir {
+ return fmt.Errorf("test=%+v: %q must have %q prefix", test, path, rootDir)
+ }
+ 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 (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
+ output, err := cmd.CombinedOutput()
+ err = test.isSuccess(rootDir, string(output), err)
+ if (err != nil) != test.fails {
+ if test.fails {
+ return fmt.Errorf("test=%+v: succeeded, but expected to fail", test)
+ }
+ return err
+ }
+ return nil
+}
+
+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)
+ }
+}
+
+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 TestCommand(t *testing.T) {
+ tmp, err := ioutil.TempDir("", "TestCommand")
+ if err != nil {
+ t.Fatal("TempDir failed: ", err)
+ }
+ defer os.RemoveAll(tmp)
+
+ printpathExe := buildPrintPathExe(t, tmp)
+
+ // Run all tests.
+ 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)
+ }
+ test.run(t, dir, printpathExe)
+ }
+}
+
+// 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 {
+ 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))
+ }
+ return filepath.Join(dir, outname)
+}
+
+const printpathSrc = `
+package main
+
+import (
+ "os"
+ "syscall"
+ "unicode/utf16"
+ "unsafe"
+)
+
+func getMyName() (string, error) {
+ var sysproc = syscall.MustLoadDLL("kernel32.dll").MustFindProc("GetModuleFileNameW")
+ b := make([]uint16, syscall.MAX_PATH)
+ r, _, err := sysproc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)))
+ n := uint32(r)
+ if n == 0 {
+ return "", err
+ }
+ return string(utf16.Decode(b[0:n])), nil
+}
+
+func main() {
+ path, err := getMyName()
+ if err != nil {
+ os.Stderr.Write([]byte("getMyName failed: " + err.Error() + "\n"))
+ os.Exit(1)
+ }
+ os.Stdout.Write([]byte(path))
+}
+`
diff --git a/src/os/exec_plan9.go b/src/os/exec_plan9.go
new file mode 100644
index 000000000..676be36ac
--- /dev/null
+++ b/src/os/exec_plan9.go
@@ -0,0 +1,137 @@
+// 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 (
+ "errors"
+ "runtime"
+ "syscall"
+ "time"
+)
+
+// The only signal values guaranteed to be present on all systems
+// are Interrupt (send the process an interrupt) and Kill (force
+// the process to exit).
+var (
+ Interrupt Signal = syscall.Note("interrupt")
+ Kill Signal = syscall.Note("kill")
+)
+
+func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
+ sysattr := &syscall.ProcAttr{
+ Dir: attr.Dir,
+ Env: attr.Env,
+ Sys: attr.Sys,
+ }
+
+ for _, f := range attr.Files {
+ sysattr.Files = append(sysattr.Files, f.Fd())
+ }
+
+ pid, h, e := syscall.StartProcess(name, argv, sysattr)
+ if e != nil {
+ return nil, &PathError{"fork/exec", name, e}
+ }
+
+ return newProcess(pid, h), nil
+}
+
+func (p *Process) writeProcFile(file string, data string) error {
+ f, e := OpenFile("/proc/"+itoa(p.Pid)+"/"+file, O_WRONLY, 0)
+ if e != nil {
+ return e
+ }
+ defer f.Close()
+ _, e = f.Write([]byte(data))
+ return e
+}
+
+func (p *Process) signal(sig Signal) error {
+ if p.done() {
+ return errors.New("os: process already finished")
+ }
+ if e := p.writeProcFile("note", sig.String()); e != nil {
+ return NewSyscallError("signal", e)
+ }
+ return nil
+}
+
+func (p *Process) kill() error {
+ return p.signal(Kill)
+}
+
+func (p *Process) wait() (ps *ProcessState, err error) {
+ var waitmsg syscall.Waitmsg
+
+ if p.Pid == -1 {
+ return nil, ErrInvalid
+ }
+ err = syscall.WaitProcess(p.Pid, &waitmsg)
+ if err != nil {
+ return nil, NewSyscallError("wait", err)
+ }
+
+ p.setDone()
+ ps = &ProcessState{
+ pid: waitmsg.Pid,
+ status: &waitmsg,
+ }
+ return ps, nil
+}
+
+func (p *Process) release() error {
+ // NOOP for Plan 9.
+ p.Pid = -1
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(p, nil)
+ return nil
+}
+
+func findProcess(pid int) (p *Process, err error) {
+ // NOOP for Plan 9.
+ return newProcess(pid, 0), nil
+}
+
+// ProcessState stores information about a process, as reported by Wait.
+type ProcessState struct {
+ pid int // The process's id.
+ status *syscall.Waitmsg // System-dependent status info.
+}
+
+// Pid returns the process id of the exited process.
+func (p *ProcessState) Pid() int {
+ return p.pid
+}
+
+func (p *ProcessState) exited() bool {
+ return p.status.Exited()
+}
+
+func (p *ProcessState) success() bool {
+ return p.status.ExitStatus() == 0
+}
+
+func (p *ProcessState) sys() interface{} {
+ return p.status
+}
+
+func (p *ProcessState) sysUsage() interface{} {
+ return p.status
+}
+
+func (p *ProcessState) userTime() time.Duration {
+ return time.Duration(p.status.Time[0]) * time.Millisecond
+}
+
+func (p *ProcessState) systemTime() time.Duration {
+ return time.Duration(p.status.Time[1]) * time.Millisecond
+}
+
+func (p *ProcessState) String() string {
+ if p == nil {
+ return "<nil>"
+ }
+ return "exit status: " + p.status.Msg
+}
diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go
new file mode 100644
index 000000000..fb9d291e6
--- /dev/null
+++ b/src/os/exec_posix.go
@@ -0,0 +1,134 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+
+package os
+
+import (
+ "syscall"
+)
+
+// The only signal values guaranteed to be present on all systems
+// are Interrupt (send the process an interrupt) and Kill (force
+// the process to exit).
+var (
+ Interrupt Signal = syscall.SIGINT
+ Kill Signal = syscall.SIGKILL
+)
+
+func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
+ // If there is no SysProcAttr (ie. no Chroot or changed
+ // UID/GID), double-check existence of the directory we want
+ // to chdir into. We can make the error clearer this way.
+ if attr != nil && attr.Sys == nil && attr.Dir != "" {
+ if _, err := Stat(attr.Dir); err != nil {
+ pe := err.(*PathError)
+ pe.Op = "chdir"
+ return nil, pe
+ }
+ }
+
+ sysattr := &syscall.ProcAttr{
+ Dir: attr.Dir,
+ Env: attr.Env,
+ Sys: attr.Sys,
+ }
+ if sysattr.Env == nil {
+ sysattr.Env = Environ()
+ }
+ for _, f := range attr.Files {
+ sysattr.Files = append(sysattr.Files, f.Fd())
+ }
+
+ pid, h, e := syscall.StartProcess(name, argv, sysattr)
+ if e != nil {
+ return nil, &PathError{"fork/exec", name, e}
+ }
+ return newProcess(pid, h), nil
+}
+
+func (p *Process) kill() error {
+ return p.Signal(Kill)
+}
+
+// ProcessState stores information about a process, as reported by Wait.
+type ProcessState struct {
+ pid int // The process's id.
+ status syscall.WaitStatus // System-dependent status info.
+ rusage *syscall.Rusage
+}
+
+// Pid returns the process id of the exited process.
+func (p *ProcessState) Pid() int {
+ return p.pid
+}
+
+func (p *ProcessState) exited() bool {
+ return p.status.Exited()
+}
+
+func (p *ProcessState) success() bool {
+ return p.status.ExitStatus() == 0
+}
+
+func (p *ProcessState) sys() interface{} {
+ return p.status
+}
+
+func (p *ProcessState) sysUsage() interface{} {
+ return p.rusage
+}
+
+// Convert i to decimal string.
+func itod(i int) string {
+ if i == 0 {
+ return "0"
+ }
+
+ u := uint64(i)
+ if i < 0 {
+ u = -u
+ }
+
+ // Assemble decimal in reverse order.
+ var b [32]byte
+ bp := len(b)
+ for ; u > 0; u /= 10 {
+ bp--
+ b[bp] = byte(u%10) + '0'
+ }
+
+ if i < 0 {
+ bp--
+ b[bp] = '-'
+ }
+
+ return string(b[bp:])
+}
+
+func (p *ProcessState) String() string {
+ if p == nil {
+ return "<nil>"
+ }
+ status := p.Sys().(syscall.WaitStatus)
+ res := ""
+ switch {
+ case status.Exited():
+ res = "exit status " + itod(status.ExitStatus())
+ case status.Signaled():
+ res = "signal: " + status.Signal().String()
+ case status.Stopped():
+ res = "stop signal: " + status.StopSignal().String()
+ if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != 0 {
+ res += " (trap " + itod(status.TrapCause()) + ")"
+ }
+ case status.Continued():
+ res = "continued"
+ }
+ if status.CoreDump() {
+ res += " (core dumped)"
+ }
+ return res
+}
diff --git a/src/os/exec_unix.go b/src/os/exec_unix.go
new file mode 100644
index 000000000..1b1e3350b
--- /dev/null
+++ b/src/os/exec_unix.go
@@ -0,0 +1,73 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package os
+
+import (
+ "errors"
+ "runtime"
+ "syscall"
+ "time"
+)
+
+func (p *Process) wait() (ps *ProcessState, err error) {
+ if p.Pid == -1 {
+ return nil, syscall.EINVAL
+ }
+ var status syscall.WaitStatus
+ var rusage syscall.Rusage
+ pid1, e := syscall.Wait4(p.Pid, &status, 0, &rusage)
+ if e != nil {
+ return nil, NewSyscallError("wait", e)
+ }
+ if pid1 != 0 {
+ p.setDone()
+ }
+ ps = &ProcessState{
+ pid: pid1,
+ status: status,
+ rusage: &rusage,
+ }
+ return ps, nil
+}
+
+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")
+ }
+ if e := syscall.Kill(p.Pid, s); e != nil {
+ return e
+ }
+ return nil
+}
+
+func (p *Process) release() error {
+ // NOOP for unix.
+ p.Pid = -1
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(p, nil)
+ return nil
+}
+
+func findProcess(pid int) (p *Process, err error) {
+ // NOOP for unix.
+ return newProcess(pid, 0), nil
+}
+
+func (p *ProcessState) userTime() time.Duration {
+ return time.Duration(p.rusage.Utime.Nano()) * time.Nanosecond
+}
+
+func (p *ProcessState) systemTime() time.Duration {
+ return time.Duration(p.rusage.Stime.Nano()) * time.Nanosecond
+}
diff --git a/src/os/exec_windows.go b/src/os/exec_windows.go
new file mode 100644
index 000000000..c4f3d4f85
--- /dev/null
+++ b/src/os/exec_windows.go
@@ -0,0 +1,115 @@
+// 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 (
+ "errors"
+ "runtime"
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+func (p *Process) wait() (ps *ProcessState, err error) {
+ s, e := syscall.WaitForSingleObject(syscall.Handle(p.handle), syscall.INFINITE)
+ switch s {
+ case syscall.WAIT_OBJECT_0:
+ break
+ case syscall.WAIT_FAILED:
+ return nil, NewSyscallError("WaitForSingleObject", e)
+ default:
+ return nil, errors.New("os: unexpected result from WaitForSingleObject")
+ }
+ var ec uint32
+ e = syscall.GetExitCodeProcess(syscall.Handle(p.handle), &ec)
+ if e != nil {
+ return nil, NewSyscallError("GetExitCodeProcess", e)
+ }
+ var u syscall.Rusage
+ e = syscall.GetProcessTimes(syscall.Handle(p.handle), &u.CreationTime, &u.ExitTime, &u.KernelTime, &u.UserTime)
+ if e != nil {
+ return nil, NewSyscallError("GetProcessTimes", e)
+ }
+ p.setDone()
+ // NOTE(brainman): It seems that sometimes process is not dead
+ // when WaitForSingleObject returns. But we do not know any
+ // other way to wait for it. Sleeping for a while seems to do
+ // the trick sometimes. So we will sleep and smell the roses.
+ defer time.Sleep(5 * time.Millisecond)
+ defer p.Release()
+ return &ProcessState{p.Pid, syscall.WaitStatus{ExitCode: ec}, &u}, nil
+}
+
+func terminateProcess(pid, exitcode int) error {
+ h, e := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, uint32(pid))
+ if e != nil {
+ return NewSyscallError("OpenProcess", e)
+ }
+ defer syscall.CloseHandle(h)
+ e = syscall.TerminateProcess(h, uint32(exitcode))
+ return NewSyscallError("TerminateProcess", e)
+}
+
+func (p *Process) signal(sig Signal) error {
+ if p.done() {
+ return errors.New("os: process already finished")
+ }
+ if sig == Kill {
+ return terminateProcess(p.Pid, 1)
+ }
+ // TODO(rsc): Handle Interrupt too?
+ return syscall.Errno(syscall.EWINDOWS)
+}
+
+func (p *Process) release() error {
+ if p.handle == uintptr(syscall.InvalidHandle) {
+ return syscall.EINVAL
+ }
+ e := syscall.CloseHandle(syscall.Handle(p.handle))
+ if e != nil {
+ return NewSyscallError("CloseHandle", e)
+ }
+ p.handle = uintptr(syscall.InvalidHandle)
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(p, nil)
+ return nil
+}
+
+func findProcess(pid int) (p *Process, err error) {
+ const da = syscall.STANDARD_RIGHTS_READ |
+ syscall.PROCESS_QUERY_INFORMATION | syscall.SYNCHRONIZE
+ h, e := syscall.OpenProcess(da, false, uint32(pid))
+ if e != nil {
+ return nil, NewSyscallError("OpenProcess", e)
+ }
+ return newProcess(pid, uintptr(h)), nil
+}
+
+func init() {
+ var argc int32
+ cmd := syscall.GetCommandLine()
+ argv, e := syscall.CommandLineToArgv(cmd, &argc)
+ if e != nil {
+ return
+ }
+ defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv))))
+ Args = make([]string, argc)
+ for i, v := range (*argv)[:argc] {
+ Args[i] = string(syscall.UTF16ToString((*v)[:]))
+ }
+}
+
+func ftToDuration(ft *syscall.Filetime) time.Duration {
+ n := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime) // in 100-nanosecond intervals
+ return time.Duration(n*100) * time.Nanosecond
+}
+
+func (p *ProcessState) userTime() time.Duration {
+ return ftToDuration(&p.rusage.UserTime)
+}
+
+func (p *ProcessState) systemTime() time.Duration {
+ return ftToDuration(&p.rusage.KernelTime)
+}
diff --git a/src/os/export_test.go b/src/os/export_test.go
new file mode 100644
index 000000000..9fa7936ae
--- /dev/null
+++ b/src/os/export_test.go
@@ -0,0 +1,10 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+// Export for testing.
+
+var Atime = atime
+var LstatP = &lstat
diff --git a/src/os/file.go b/src/os/file.go
new file mode 100644
index 000000000..b4a745801
--- /dev/null
+++ b/src/os/file.go
@@ -0,0 +1,257 @@
+// 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 provides a platform-independent interface to operating system
+// functionality. The design is Unix-like, although the error handling is
+// Go-like; failing calls return values of type error rather than error numbers.
+// Often, more information is available within the error. For example,
+// if a call that takes a file name fails, such as Open or Stat, the error
+// will include the failing file name when printed and will be of type
+// *PathError, which may be unpacked for more information.
+//
+// The os interface is intended to be uniform across all operating systems.
+// Features not generally available appear in the system-specific package syscall.
+//
+// Here is a simple example, opening a file and reading some of it.
+//
+// file, err := os.Open("file.go") // For read access.
+// if err != nil {
+// log.Fatal(err)
+// }
+//
+// If the open fails, the error string will be self-explanatory, like
+//
+// open file.go: no such file or directory
+//
+// The file's data can then be read into a slice of bytes. Read and
+// Write take their byte counts from the length of the argument slice.
+//
+// data := make([]byte, 100)
+// count, err := file.Read(data)
+// if err != nil {
+// log.Fatal(err)
+// }
+// fmt.Printf("read %d bytes: %q\n", count, data[:count])
+//
+package os
+
+import (
+ "io"
+ "syscall"
+)
+
+// Name returns the name of the file as presented to Open.
+func (f *File) Name() string { return f.name }
+
+// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
+// standard output, and standard error file descriptors.
+var (
+ Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
+ Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
+ Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
+)
+
+// Flags to Open wrapping those of the underlying system. Not all flags
+// may be implemented on a given system.
+const (
+ O_RDONLY int = syscall.O_RDONLY // open the file read-only.
+ O_WRONLY int = syscall.O_WRONLY // open the file write-only.
+ O_RDWR int = syscall.O_RDWR // open the file read-write.
+ O_APPEND int = syscall.O_APPEND // append data to the file when writing.
+ O_CREATE int = syscall.O_CREAT // create a new file if none exists.
+ O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist
+ O_SYNC int = syscall.O_SYNC // open for synchronous I/O.
+ O_TRUNC int = syscall.O_TRUNC // if possible, truncate file when opened.
+)
+
+// Seek whence values.
+const (
+ SEEK_SET int = 0 // seek relative to the origin of the file
+ SEEK_CUR int = 1 // seek relative to the current offset
+ SEEK_END int = 2 // seek relative to the end
+)
+
+// LinkError records an error during a link or symlink or rename
+// system call and the paths that caused it.
+type LinkError struct {
+ Op string
+ Old string
+ New string
+ Err error
+}
+
+func (e *LinkError) Error() string {
+ return e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error()
+}
+
+// Read reads up to len(b) bytes from the File.
+// It returns the number of bytes read and an error, if any.
+// EOF is signaled by a zero count with err set to io.EOF.
+func (f *File) Read(b []byte) (n int, err error) {
+ if f == nil {
+ return 0, ErrInvalid
+ }
+ n, e := f.read(b)
+ if n < 0 {
+ n = 0
+ }
+ if n == 0 && len(b) > 0 && e == nil {
+ return 0, io.EOF
+ }
+ if e != nil {
+ err = &PathError{"read", f.name, e}
+ }
+ return n, err
+}
+
+// ReadAt reads len(b) bytes from the File starting at byte offset off.
+// It returns the number of bytes read and the error, if any.
+// ReadAt always returns a non-nil error when n < len(b).
+// At end of file, that error is io.EOF.
+func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
+ if f == nil {
+ return 0, ErrInvalid
+ }
+ for len(b) > 0 {
+ m, e := f.pread(b, off)
+ if m == 0 && e == nil {
+ return n, io.EOF
+ }
+ if e != nil {
+ err = &PathError{"read", f.name, e}
+ break
+ }
+ n += m
+ b = b[m:]
+ off += int64(m)
+ }
+ return
+}
+
+// Write writes len(b) bytes to the File.
+// It returns the number of bytes written and an error, if any.
+// Write returns a non-nil error when n != len(b).
+func (f *File) Write(b []byte) (n int, err error) {
+ if f == nil {
+ return 0, ErrInvalid
+ }
+ n, e := f.write(b)
+ if n < 0 {
+ n = 0
+ }
+ if n != len(b) {
+ err = io.ErrShortWrite
+ }
+
+ epipecheck(f, e)
+
+ if e != nil {
+ err = &PathError{"write", f.name, e}
+ }
+ return n, err
+}
+
+// WriteAt writes len(b) bytes to the File starting at byte offset off.
+// It returns the number of bytes written and an error, if any.
+// WriteAt returns a non-nil error when n != len(b).
+func (f *File) WriteAt(b []byte, off int64) (n int, err error) {
+ if f == nil {
+ return 0, ErrInvalid
+ }
+ for len(b) > 0 {
+ m, e := f.pwrite(b, off)
+ if e != nil {
+ err = &PathError{"write", f.name, e}
+ break
+ }
+ n += m
+ b = b[m:]
+ off += int64(m)
+ }
+ return
+}
+
+// Seek sets the offset for the next Read or Write on file to offset, interpreted
+// according to whence: 0 means relative to the origin of the file, 1 means
+// relative to the current offset, and 2 means relative to the end.
+// It returns the new offset and an error, if any.
+func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
+ if f == nil {
+ return 0, ErrInvalid
+ }
+ r, e := f.seek(offset, whence)
+ if e == nil && f.dirinfo != nil && r != 0 {
+ e = syscall.EISDIR
+ }
+ if e != nil {
+ return 0, &PathError{"seek", f.name, e}
+ }
+ return r, nil
+}
+
+// WriteString is like Write, but writes the contents of string s rather than
+// a slice of bytes.
+func (f *File) WriteString(s string) (ret int, err error) {
+ if f == nil {
+ return 0, ErrInvalid
+ }
+ return f.Write([]byte(s))
+}
+
+// Mkdir creates a new directory with the specified name and permission bits.
+// If there is an error, it will be of type *PathError.
+func Mkdir(name string, perm FileMode) error {
+ e := syscall.Mkdir(name, syscallMode(perm))
+ if e != nil {
+ return &PathError{"mkdir", name, e}
+ }
+ return nil
+}
+
+// Chdir changes the current working directory to the named directory.
+// If there is an error, it will be of type *PathError.
+func Chdir(dir string) error {
+ if e := syscall.Chdir(dir); e != nil {
+ return &PathError{"chdir", dir, e}
+ }
+ return nil
+}
+
+// Chdir changes the current working directory to the file,
+// which must be a directory.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chdir() error {
+ if f == nil {
+ return ErrInvalid
+ }
+ if e := syscall.Fchdir(f.fd); e != nil {
+ return &PathError{"chdir", f.name, e}
+ }
+ return nil
+}
+
+// Open opens the named file for reading. If successful, methods on
+// the returned file can be used for reading; the associated file
+// descriptor has mode O_RDONLY.
+// If there is an error, it will be of type *PathError.
+func Open(name string) (file *File, err error) {
+ return OpenFile(name, O_RDONLY, 0)
+}
+
+// Create creates the named file mode 0666 (before umask), truncating
+// it if it already exists. If successful, methods on the returned
+// File can be used for I/O; the associated file descriptor has mode
+// O_RDWR.
+// If there is an error, it will be of type *PathError.
+func Create(name string) (file *File, err error) {
+ return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
+}
+
+// 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/os/file_plan9.go b/src/os/file_plan9.go
new file mode 100644
index 000000000..a804b8197
--- /dev/null
+++ b/src/os/file_plan9.go
@@ -0,0 +1,468 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "runtime"
+ "syscall"
+ "time"
+)
+
+// File represents an open file descriptor.
+type File struct {
+ *file
+}
+
+// file is the real representation of *File.
+// The extra level of indirection ensures that no clients of os
+// can overwrite this data, which could cause the finalizer
+// to close the wrong file descriptor.
+type file struct {
+ fd int
+ name string
+ dirinfo *dirInfo // nil unless directory being read
+}
+
+// Fd returns the integer Unix file descriptor referencing the open file.
+func (f *File) Fd() uintptr {
+ if f == nil {
+ return ^(uintptr(0))
+ }
+ return uintptr(f.fd)
+}
+
+// NewFile returns a new File with the given file descriptor and name.
+func NewFile(fd uintptr, name string) *File {
+ fdi := int(fd)
+ if fdi < 0 {
+ return nil
+ }
+ f := &File{&file{fd: fdi, name: name}}
+ runtime.SetFinalizer(f.file, (*file).close)
+ return f
+}
+
+// Auxiliary information if the File describes a directory
+type dirInfo struct {
+ buf [syscall.STATMAX]byte // buffer for directory I/O
+ nbuf int // length of buf; return value from Read
+ bufp int // location of next record in buf.
+}
+
+func epipecheck(file *File, e error) {
+}
+
+// DevNull is the name of the operating system's ``null device.''
+// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
+const DevNull = "/dev/null"
+
+// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
+func syscallMode(i FileMode) (o uint32) {
+ o |= uint32(i.Perm())
+ if i&ModeAppend != 0 {
+ o |= syscall.DMAPPEND
+ }
+ if i&ModeExclusive != 0 {
+ o |= syscall.DMEXCL
+ }
+ if i&ModeTemporary != 0 {
+ o |= syscall.DMTMP
+ }
+ return
+}
+
+// OpenFile is the generalized open call; most users will use Open
+// or Create instead. It opens the named file with specified flag
+// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful,
+// methods on the returned File can be used for I/O.
+// If there is an error, it will be of type *PathError.
+func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
+ var (
+ fd int
+ e error
+ create bool
+ excl bool
+ trunc bool
+ append bool
+ )
+
+ if flag&O_CREATE == O_CREATE {
+ flag = flag & ^O_CREATE
+ create = true
+ }
+ if flag&O_EXCL == O_EXCL {
+ excl = true
+ }
+ if flag&O_TRUNC == O_TRUNC {
+ trunc = true
+ }
+ // O_APPEND is emulated on Plan 9
+ if flag&O_APPEND == O_APPEND {
+ flag = flag &^ O_APPEND
+ append = true
+ }
+
+ if (create && trunc) || excl {
+ fd, e = syscall.Create(name, flag, syscallMode(perm))
+ } else {
+ fd, e = syscall.Open(name, flag)
+ if e != nil && create {
+ var e1 error
+ fd, e1 = syscall.Create(name, flag, syscallMode(perm))
+ if e1 == nil {
+ e = nil
+ }
+ }
+ }
+
+ if e != nil {
+ return nil, &PathError{"open", name, e}
+ }
+
+ if append {
+ if _, e = syscall.Seek(fd, 0, SEEK_END); e != nil {
+ return nil, &PathError{"seek", name, e}
+ }
+ }
+
+ return NewFile(uintptr(fd), name), nil
+}
+
+// Close closes the File, rendering it unusable for I/O.
+// It returns an error, if any.
+func (f *File) Close() error {
+ if f == nil {
+ return ErrInvalid
+ }
+ return f.file.close()
+}
+
+func (file *file) close() error {
+ if file == nil || file.fd < 0 {
+ return ErrInvalid
+ }
+ var err error
+ syscall.ForkLock.RLock()
+ if e := syscall.Close(file.fd); e != nil {
+ err = &PathError{"close", file.name, e}
+ }
+ syscall.ForkLock.RUnlock()
+ file.fd = -1 // so it can't be closed again
+
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(file, nil)
+ return err
+}
+
+// Stat returns the FileInfo structure describing file.
+// If there is an error, it will be of type *PathError.
+func (f *File) Stat() (fi FileInfo, err error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ d, err := dirstat(f)
+ if err != nil {
+ return nil, err
+ }
+ return fileInfoFromStat(d), nil
+}
+
+// Truncate changes the size of the file.
+// It does not change the I/O offset.
+// If there is an error, it will be of type *PathError.
+func (f *File) Truncate(size int64) error {
+ if f == nil {
+ return ErrInvalid
+ }
+
+ var d syscall.Dir
+ d.Null()
+ d.Length = size
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{"truncate", f.name, err}
+ }
+ if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
+ return &PathError{"truncate", f.name, err}
+ }
+ return nil
+}
+
+const chmodMask = uint32(syscall.DMAPPEND | syscall.DMEXCL | syscall.DMTMP | ModePerm)
+
+// Chmod changes the mode of the file to mode.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chmod(mode FileMode) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ var d syscall.Dir
+
+ odir, e := dirstat(f)
+ if e != nil {
+ return &PathError{"chmod", f.name, e}
+ }
+ d.Null()
+ d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{"chmod", f.name, err}
+ }
+ if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
+ return &PathError{"chmod", f.name, err}
+ }
+ return nil
+}
+
+// Sync commits the current contents of the file to stable storage.
+// Typically, this means flushing the file system's in-memory copy
+// of recently written data to disk.
+func (f *File) Sync() (err error) {
+ if f == nil {
+ return ErrInvalid
+ }
+ var d syscall.Dir
+ d.Null()
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return NewSyscallError("fsync", err)
+ }
+ if err = syscall.Fwstat(f.fd, buf[:n]); err != nil {
+ return NewSyscallError("fsync", err)
+ }
+ return nil
+}
+
+// read reads up to len(b) bytes from the File.
+// It returns the number of bytes read and an error, if any.
+func (f *File) read(b []byte) (n int, err error) {
+ return 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 nil.
+func (f *File) pread(b []byte, off int64) (n int, err error) {
+ return syscall.Pread(f.fd, b, off)
+}
+
+// write writes len(b) bytes to the File.
+// It returns the number of bytes written and an error, if any.
+// Since Plan 9 preserves message boundaries, never allow
+// a zero-byte write.
+func (f *File) write(b []byte) (n int, err error) {
+ if len(b) == 0 {
+ return 0, nil
+ }
+ return syscall.Write(f.fd, b)
+}
+
+// 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.
+// Since Plan 9 preserves message boundaries, never allow
+// a zero-byte write.
+func (f *File) pwrite(b []byte, off int64) (n int, err error) {
+ if len(b) == 0 {
+ return 0, nil
+ }
+ return syscall.Pwrite(f.fd, b, off)
+}
+
+// seek sets the offset for the next Read or Write on file to offset, interpreted
+// according to whence: 0 means relative to the origin of the file, 1 means
+// relative to the current offset, and 2 means relative to the end.
+// It returns the new offset and an error, if any.
+func (f *File) seek(offset int64, whence int) (ret int64, err error) {
+ return syscall.Seek(f.fd, offset, whence)
+}
+
+// Truncate changes the size of the named file.
+// If the file is a symbolic link, it changes the size of the link's target.
+// If there is an error, it will be of type *PathError.
+func Truncate(name string, size int64) error {
+ var d syscall.Dir
+
+ d.Null()
+ d.Length = size
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{"truncate", name, err}
+ }
+ if err = syscall.Wstat(name, buf[:n]); err != nil {
+ return &PathError{"truncate", name, err}
+ }
+ return nil
+}
+
+// Remove removes the named file or directory.
+// If there is an error, it will be of type *PathError.
+func Remove(name string) error {
+ if e := syscall.Remove(name); e != nil {
+ return &PathError{"remove", name, e}
+ }
+ return nil
+}
+
+// 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()
+ d.Name = newname
+
+ buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &LinkError{"rename", oldname, newname, err}
+ }
+ if err = syscall.Wstat(oldname, buf[:n]); err != nil {
+ return &LinkError{"rename", oldname, newname, err}
+ }
+ return nil
+}
+
+// Chmod changes the mode of the named file to mode.
+// If the file is a symbolic link, it changes the mode of the link's target.
+// If there is an error, it will be of type *PathError.
+func Chmod(name string, mode FileMode) error {
+ var d syscall.Dir
+
+ odir, e := dirstat(name)
+ if e != nil {
+ return &PathError{"chmod", name, e}
+ }
+ d.Null()
+ d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{"chmod", name, err}
+ }
+ if err = syscall.Wstat(name, buf[:n]); err != nil {
+ return &PathError{"chmod", name, err}
+ }
+ return nil
+}
+
+// Chtimes changes the access and modification times of the named
+// file, similar to the Unix utime() or utimes() functions.
+//
+// The underlying filesystem may truncate or round the values to a
+// less precise time unit.
+// If there is an error, it will be of type *PathError.
+func Chtimes(name string, atime time.Time, mtime time.Time) error {
+ var d syscall.Dir
+
+ d.Null()
+ d.Atime = uint32(atime.Unix())
+ d.Mtime = uint32(mtime.Unix())
+
+ var buf [syscall.STATFIXLEN]byte
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{"chtimes", name, err}
+ }
+ if err = syscall.Wstat(name, buf[:n]); err != nil {
+ return &PathError{"chtimes", name, err}
+ }
+ return nil
+}
+
+// Pipe returns a connected pair of Files; reads from r return bytes
+// written to w. It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]int
+
+ syscall.ForkLock.RLock()
+ if e := syscall.Pipe(p[0:]); e != nil {
+ syscall.ForkLock.RUnlock()
+ return nil, nil, NewSyscallError("pipe", e)
+ }
+ syscall.ForkLock.RUnlock()
+
+ return NewFile(uintptr(p[0]), "|0"), NewFile(uintptr(p[1]), "|1"), nil
+}
+
+// not supported on Plan 9
+
+// Link creates newname as a hard link to the oldname file.
+// If there is an error, it will be of type *LinkError.
+func Link(oldname, newname string) error {
+ return &LinkError{"link", oldname, newname, syscall.EPLAN9}
+}
+
+// Symlink creates newname as a symbolic link to oldname.
+// If there is an error, it will be of type *LinkError.
+func Symlink(oldname, newname string) error {
+ return &LinkError{"symlink", oldname, newname, syscall.EPLAN9}
+}
+
+// Readlink returns the destination of the named symbolic link.
+// If there is an error, it will be of type *PathError.
+func Readlink(name string) (string, error) {
+ return "", &PathError{"readlink", name, syscall.EPLAN9}
+}
+
+// Chown changes the numeric uid and gid of the named file.
+// If the file is a symbolic link, it changes the uid and gid of the link's target.
+// If there is an error, it will be of type *PathError.
+func Chown(name string, uid, gid int) error {
+ return &PathError{"chown", name, syscall.EPLAN9}
+}
+
+// Lchown changes the numeric uid and gid of the named file.
+// If the file is a symbolic link, it changes the uid and gid of the link itself.
+// If there is an error, it will be of type *PathError.
+func Lchown(name string, uid, gid int) error {
+ return &PathError{"lchown", name, syscall.EPLAN9}
+}
+
+// Chown changes the numeric uid and gid of the named file.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chown(uid, gid int) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ return &PathError{"chown", f.name, syscall.EPLAN9}
+}
+
+// TempDir returns the default directory to use for temporary files.
+func TempDir() string {
+ return "/tmp"
+}
diff --git a/src/os/file_posix.go b/src/os/file_posix.go
new file mode 100644
index 000000000..9cff7e5bc
--- /dev/null
+++ b/src/os/file_posix.go
@@ -0,0 +1,149 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func sigpipe() // implemented in package runtime
+
+// Readlink returns the destination of the named symbolic link.
+// If there is an error, it will be of type *PathError.
+func Readlink(name string) (string, error) {
+ for len := 128; ; len *= 2 {
+ b := make([]byte, len)
+ n, e := syscall.Readlink(name, b)
+ if e != nil {
+ return "", &PathError{"readlink", name, e}
+ }
+ if n < len {
+ return string(b[0:n]), nil
+ }
+ }
+}
+
+func rename(oldname, newname string) error {
+ e := syscall.Rename(oldname, newname)
+ if e != nil {
+ return &LinkError{"rename", oldname, newname, e}
+ }
+ return nil
+}
+
+// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
+func syscallMode(i FileMode) (o uint32) {
+ o |= uint32(i.Perm())
+ if i&ModeSetuid != 0 {
+ o |= syscall.S_ISUID
+ }
+ if i&ModeSetgid != 0 {
+ o |= syscall.S_ISGID
+ }
+ if i&ModeSticky != 0 {
+ o |= syscall.S_ISVTX
+ }
+ // No mapping for Go's ModeTemporary (plan9 only).
+ return
+}
+
+// Chmod changes the mode of the named file to mode.
+// If the file is a symbolic link, it changes the mode of the link's target.
+// If there is an error, it will be of type *PathError.
+func Chmod(name string, mode FileMode) error {
+ if e := syscall.Chmod(name, syscallMode(mode)); e != nil {
+ return &PathError{"chmod", name, e}
+ }
+ return nil
+}
+
+// Chmod changes the mode of the file to mode.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chmod(mode FileMode) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ if e := syscall.Fchmod(f.fd, syscallMode(mode)); e != nil {
+ return &PathError{"chmod", f.name, e}
+ }
+ return nil
+}
+
+// Chown changes the numeric uid and gid of the named file.
+// If the file is a symbolic link, it changes the uid and gid of the link's target.
+// If there is an error, it will be of type *PathError.
+func Chown(name string, uid, gid int) error {
+ if e := syscall.Chown(name, uid, gid); e != nil {
+ return &PathError{"chown", name, e}
+ }
+ return nil
+}
+
+// Lchown changes the numeric uid and gid of the named file.
+// If the file is a symbolic link, it changes the uid and gid of the link itself.
+// If there is an error, it will be of type *PathError.
+func Lchown(name string, uid, gid int) error {
+ if e := syscall.Lchown(name, uid, gid); e != nil {
+ return &PathError{"lchown", name, e}
+ }
+ return nil
+}
+
+// Chown changes the numeric uid and gid of the named file.
+// If there is an error, it will be of type *PathError.
+func (f *File) Chown(uid, gid int) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ if e := syscall.Fchown(f.fd, uid, gid); e != nil {
+ return &PathError{"chown", f.name, e}
+ }
+ return nil
+}
+
+// Truncate changes the size of the file.
+// It does not change the I/O offset.
+// If there is an error, it will be of type *PathError.
+func (f *File) Truncate(size int64) error {
+ if f == nil {
+ return ErrInvalid
+ }
+ if e := syscall.Ftruncate(f.fd, size); e != nil {
+ return &PathError{"truncate", f.name, e}
+ }
+ return nil
+}
+
+// Sync commits the current contents of the file to stable storage.
+// Typically, this means flushing the file system's in-memory copy
+// of recently written data to disk.
+func (f *File) Sync() (err error) {
+ if f == nil {
+ return ErrInvalid
+ }
+ if e := syscall.Fsync(f.fd); e != nil {
+ return NewSyscallError("fsync", e)
+ }
+ return nil
+}
+
+// Chtimes changes the access and modification times of the named
+// file, similar to the Unix utime() or utimes() functions.
+//
+// The underlying filesystem may truncate or round the values to a
+// less precise time unit.
+// If there is an error, it will be of type *PathError.
+func Chtimes(name string, atime time.Time, mtime time.Time) error {
+ var utimes [2]syscall.Timespec
+ utimes[0] = syscall.NsecToTimespec(atime.UnixNano())
+ utimes[1] = syscall.NsecToTimespec(mtime.UnixNano())
+ if e := syscall.UtimesNano(name, utimes[0:]); e != nil {
+ return &PathError{"chtimes", name, e}
+ }
+ return nil
+}
diff --git a/src/os/file_unix.go b/src/os/file_unix.go
new file mode 100644
index 000000000..bba0d9c0f
--- /dev/null
+++ b/src/os/file_unix.go
@@ -0,0 +1,338 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package os
+
+import (
+ "runtime"
+ "sync/atomic"
+ "syscall"
+)
+
+// File represents an open file descriptor.
+type File struct {
+ *file
+}
+
+// file is the real representation of *File.
+// The extra level of indirection ensures that no clients of os
+// can overwrite this data, which could cause the finalizer
+// to close the wrong file descriptor.
+type file struct {
+ fd int
+ name string
+ dirinfo *dirInfo // nil unless directory being read
+ nepipe int32 // number of consecutive EPIPE in Write
+}
+
+// Fd returns the integer Unix file descriptor referencing the open file.
+func (f *File) Fd() uintptr {
+ if f == nil {
+ return ^(uintptr(0))
+ }
+ return uintptr(f.fd)
+}
+
+// NewFile returns a new File with the given file descriptor and name.
+func NewFile(fd uintptr, name string) *File {
+ fdi := int(fd)
+ if fdi < 0 {
+ return nil
+ }
+ f := &File{&file{fd: fdi, name: name}}
+ runtime.SetFinalizer(f.file, (*file).close)
+ return f
+}
+
+// Auxiliary information if the File describes a directory
+type dirInfo struct {
+ buf []byte // buffer for directory I/O
+ nbuf int // length of buf; return value from Getdirentries
+ bufp int // location of next record in buf.
+}
+
+func epipecheck(file *File, e error) {
+ if e == syscall.EPIPE {
+ if atomic.AddInt32(&file.nepipe, 1) >= 10 {
+ sigpipe()
+ }
+ } else {
+ atomic.StoreInt32(&file.nepipe, 0)
+ }
+}
+
+// DevNull is the name of the operating system's ``null device.''
+// On Unix-like systems, it is "/dev/null"; on Windows, "NUL".
+const DevNull = "/dev/null"
+
+// OpenFile is the generalized open call; most users will use Open
+// or Create instead. It opens the named file with specified flag
+// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful,
+// methods on the returned File can be used for I/O.
+// If there is an error, it will be of type *PathError.
+func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
+ r, e := syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
+ if e != nil {
+ return nil, &PathError{"open", name, e}
+ }
+
+ // There's a race here with fork/exec, which we are
+ // content to live with. See ../syscall/exec_unix.go.
+ if !supportsCloseOnExec {
+ syscall.CloseOnExec(r)
+ }
+
+ return NewFile(uintptr(r), name), nil
+}
+
+// Close closes the File, rendering it unusable for I/O.
+// It returns an error, if any.
+func (f *File) Close() error {
+ if f == nil {
+ return ErrInvalid
+ }
+ return f.file.close()
+}
+
+func (file *file) close() error {
+ if file == nil || file.fd < 0 {
+ return syscall.EINVAL
+ }
+ var err error
+ if e := syscall.Close(file.fd); e != nil {
+ err = &PathError{"close", file.name, e}
+ }
+ file.fd = -1 // so it can't be closed again
+
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(file, nil)
+ return err
+}
+
+// Stat returns the FileInfo structure describing file.
+// If there is an error, it will be of type *PathError.
+func (f *File) Stat() (fi FileInfo, err error) {
+ if f == nil {
+ return nil, ErrInvalid
+ }
+ var stat syscall.Stat_t
+ err = syscall.Fstat(f.fd, &stat)
+ if err != nil {
+ return nil, &PathError{"stat", f.name, err}
+ }
+ return fileInfoFromStat(&stat, f.name), nil
+}
+
+// Stat returns a FileInfo describing the named file.
+// If there is an error, it will be of type *PathError.
+func Stat(name string) (fi FileInfo, err error) {
+ var stat syscall.Stat_t
+ err = syscall.Stat(name, &stat)
+ if err != nil {
+ return nil, &PathError{"stat", name, err}
+ }
+ return fileInfoFromStat(&stat, name), nil
+}
+
+// Lstat returns a FileInfo describing the named file.
+// If the file is a symbolic link, the returned FileInfo
+// describes the symbolic link. Lstat makes no attempt to follow the link.
+// If there is an error, it will be of type *PathError.
+func Lstat(name string) (fi FileInfo, err error) {
+ var stat syscall.Stat_t
+ err = syscall.Lstat(name, &stat)
+ if err != nil {
+ return nil, &PathError{"lstat", name, err}
+ }
+ return fileInfoFromStat(&stat, name), nil
+}
+
+func (f *File) readdir(n int) (fi []FileInfo, err error) {
+ dirname := f.name
+ if dirname == "" {
+ dirname = "."
+ }
+ names, err := f.Readdirnames(n)
+ 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
+ }
+ 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 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)
+}
+
+// write writes len(b) bytes to the File.
+// It returns the number of bytes written and an error, if any.
+func (f *File) write(b []byte) (n int, err error) {
+ for {
+ bcap := b
+ if needsMaxRW && len(bcap) > maxRW {
+ bcap = bcap[:maxRW]
+ }
+ m, err := syscall.Write(f.fd, bcap)
+ n += m
+
+ // If the syscall wrote some data but not all (short write)
+ // or it returned EINTR, then assume it stopped early for
+ // reasons that are uninteresting to the caller, and try again.
+ if 0 < m && m < len(bcap) || err == syscall.EINTR {
+ b = b[m:]
+ continue
+ }
+
+ if needsMaxRW && len(bcap) != len(b) && err == nil {
+ b = b[m:]
+ continue
+ }
+
+ return n, err
+ }
+}
+
+// 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)
+}
+
+// seek sets the offset for the next Read or Write on file to offset, interpreted
+// according to whence: 0 means relative to the origin of the file, 1 means
+// relative to the current offset, and 2 means relative to the end.
+// It returns the new offset and an error, if any.
+func (f *File) seek(offset int64, whence int) (ret int64, err error) {
+ return syscall.Seek(f.fd, offset, whence)
+}
+
+// Truncate changes the size of the named file.
+// If the file is a symbolic link, it changes the size of the link's target.
+// If there is an error, it will be of type *PathError.
+func Truncate(name string, size int64) error {
+ if e := syscall.Truncate(name, size); e != nil {
+ return &PathError{"truncate", name, e}
+ }
+ return nil
+}
+
+// Remove removes the named file or directory.
+// If there is an error, it will be of type *PathError.
+func Remove(name string) error {
+ // System call interface forces us to know
+ // whether name is a file or directory.
+ // Try both: it is cheaper on average than
+ // doing a Stat plus the right one.
+ e := syscall.Unlink(name)
+ if e == nil {
+ return nil
+ }
+ e1 := syscall.Rmdir(name)
+ if e1 == nil {
+ return nil
+ }
+
+ // Both failed: figure out which error to return.
+ // OS X and Linux differ on whether unlink(dir)
+ // returns EISDIR, so can't use that. However,
+ // both agree that rmdir(file) returns ENOTDIR,
+ // so we can use that to decide which error is real.
+ // Rmdir might also return ENOTDIR if given a bad
+ // file path, like /etc/passwd/foo, but in that case,
+ // both errors will be ENOTDIR, so it's okay to
+ // use the error from unlink.
+ if e1 != syscall.ENOTDIR {
+ e = e1
+ }
+ return &PathError{"remove", name, e}
+}
+
+// basename removes trailing slashes and the leading directory name from path name
+func basename(name string) string {
+ i := len(name) - 1
+ // Remove trailing slashes
+ for ; i > 0 && name[i] == '/'; i-- {
+ name = name[:i]
+ }
+ // Remove leading directory name
+ for i--; i >= 0; i-- {
+ if name[i] == '/' {
+ name = name[i+1:]
+ break
+ }
+ }
+
+ return name
+}
+
+// TempDir returns the default directory to use for temporary files.
+func TempDir() string {
+ dir := Getenv("TMPDIR")
+ if dir == "" {
+ if runtime.GOOS == "android" {
+ dir = "/data/local/tmp"
+ } else {
+ dir = "/tmp"
+ }
+ }
+ return dir
+}
+
+// Link creates newname as a hard link to the oldname file.
+// If there is an error, it will be of type *LinkError.
+func Link(oldname, newname string) error {
+ e := syscall.Link(oldname, newname)
+ if e != nil {
+ return &LinkError{"link", oldname, newname, e}
+ }
+ return nil
+}
+
+// Symlink creates newname as a symbolic link to oldname.
+// If there is an error, it will be of type *LinkError.
+func Symlink(oldname, newname string) error {
+ e := syscall.Symlink(oldname, newname)
+ if e != nil {
+ return &LinkError{"symlink", oldname, newname, e}
+ }
+ return nil
+}
diff --git a/src/os/file_windows.go b/src/os/file_windows.go
new file mode 100644
index 000000000..e78d4abf6
--- /dev/null
+++ b/src/os/file_windows.go
@@ -0,0 +1,595 @@
+// 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 (
+ "io"
+ "runtime"
+ "sync"
+ "syscall"
+ "unicode/utf16"
+ "unicode/utf8"
+ "unsafe"
+)
+
+// File represents an open file descriptor.
+type File struct {
+ *file
+}
+
+// file is the real representation of *File.
+// The extra level of indirection ensures that no clients of os
+// can overwrite this data, which could cause the finalizer
+// to close the wrong file descriptor.
+type file struct {
+ fd syscall.Handle
+ name string
+ dirinfo *dirInfo // nil unless directory being read
+ l sync.Mutex // used to implement windows pread/pwrite
+
+ // only for console io
+ isConsole bool
+ lastbits []byte // first few bytes of the last incomplete rune in last write
+ readbuf []rune // input console buffer
+}
+
+// Fd returns the Windows handle referencing the open file.
+func (file *File) Fd() uintptr {
+ if file == nil {
+ return uintptr(syscall.InvalidHandle)
+ }
+ return uintptr(file.fd)
+}
+
+// newFile returns a new File with the given file handle and name.
+// Unlike NewFile, it does not check that h is syscall.InvalidHandle.
+func newFile(h syscall.Handle, name string) *File {
+ f := &File{&file{fd: h, name: name}}
+ var m uint32
+ if syscall.GetConsoleMode(f.fd, &m) == nil {
+ f.isConsole = true
+ }
+ runtime.SetFinalizer(f.file, (*file).close)
+ return f
+}
+
+// NewFile returns a new File with the given file descriptor and name.
+func NewFile(fd uintptr, name string) *File {
+ h := syscall.Handle(fd)
+ if h == syscall.InvalidHandle {
+ return nil
+ }
+ return newFile(h, name)
+}
+
+// Auxiliary information if the File describes a directory
+type dirInfo struct {
+ data syscall.Win32finddata
+ needdata bool
+ path string
+ isempty bool // set if FindFirstFile returns ERROR_FILE_NOT_FOUND
+}
+
+func epipecheck(file *File, e error) {
+}
+
+const DevNull = "NUL"
+
+func (f *file) isdir() bool { return f != nil && f.dirinfo != nil }
+
+func openFile(name string, flag int, perm FileMode) (file *File, err error) {
+ r, e := syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
+ if e != nil {
+ return nil, e
+ }
+ return NewFile(uintptr(r), name), nil
+}
+
+func openDir(name string) (file *File, err error) {
+ maskp, e := syscall.UTF16PtrFromString(name + `\*`)
+ if e != nil {
+ return nil, e
+ }
+ d := new(dirInfo)
+ r, e := syscall.FindFirstFile(maskp, &d.data)
+ if e != nil {
+ // FindFirstFile returns ERROR_FILE_NOT_FOUND when
+ // no matching files can be found. Then, if directory
+ // exists, we should proceed.
+ if e != syscall.ERROR_FILE_NOT_FOUND {
+ return nil, e
+ }
+ var fa syscall.Win32FileAttributeData
+ namep, e := syscall.UTF16PtrFromString(name)
+ if e != nil {
+ return nil, e
+ }
+ e = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
+ if e != nil {
+ return nil, e
+ }
+ if fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 {
+ return nil, e
+ }
+ d.isempty = true
+ }
+ d.path = name
+ if !isAbs(d.path) {
+ d.path, e = syscall.FullPath(d.path)
+ if e != nil {
+ return nil, e
+ }
+ }
+ f := newFile(r, name)
+ f.dirinfo = d
+ return f, nil
+}
+
+// OpenFile is the generalized open call; most users will use Open
+// or Create instead. It opens the named file with specified flag
+// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful,
+// methods on the returned File can be used for I/O.
+// If there is an error, it will be of type *PathError.
+func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
+ if name == "" {
+ return nil, &PathError{"open", name, syscall.ENOENT}
+ }
+ 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
+ }
+ return nil, &PathError{"open", name, errf}
+}
+
+// Close closes the File, rendering it unusable for I/O.
+// It returns an error, if any.
+func (file *File) Close() error {
+ if file == nil {
+ return ErrInvalid
+ }
+ return file.file.close()
+}
+
+func (file *file) close() error {
+ if file == nil {
+ return syscall.EINVAL
+ }
+ if file.isdir() && file.dirinfo.isempty {
+ // "special" empty directories
+ return nil
+ }
+ if file.fd == syscall.InvalidHandle {
+ return syscall.EINVAL
+ }
+ var e error
+ if file.isdir() {
+ e = syscall.FindClose(syscall.Handle(file.fd))
+ } else {
+ e = syscall.CloseHandle(syscall.Handle(file.fd))
+ }
+ var err error
+ if e != nil {
+ err = &PathError{"close", file.name, e}
+ }
+ file.fd = syscall.InvalidHandle // so it can't be closed again
+
+ // no need for a finalizer anymore
+ runtime.SetFinalizer(file, nil)
+ return err
+}
+
+func (file *File) readdir(n int) (fi []FileInfo, err error) {
+ if file == nil {
+ return nil, syscall.EINVAL
+ }
+ if !file.isdir() {
+ return nil, &PathError{"Readdir", file.name, syscall.ENOTDIR}
+ }
+ if !file.dirinfo.isempty && file.fd == syscall.InvalidHandle {
+ return nil, syscall.EINVAL
+ }
+ wantAll := n <= 0
+ size := n
+ if wantAll {
+ n = -1
+ size = 100
+ }
+ fi = make([]FileInfo, 0, size) // Empty with room to grow.
+ d := &file.dirinfo.data
+ for n != 0 && !file.dirinfo.isempty {
+ if file.dirinfo.needdata {
+ e := syscall.FindNextFile(syscall.Handle(file.fd), d)
+ if e != nil {
+ if e == syscall.ERROR_NO_MORE_FILES {
+ break
+ } else {
+ err = &PathError{"FindNextFile", file.name, e}
+ if !wantAll {
+ fi = nil
+ }
+ return
+ }
+ }
+ }
+ file.dirinfo.needdata = true
+ name := string(syscall.UTF16ToString(d.FileName[0:]))
+ if name == "." || name == ".." { // Useless names
+ continue
+ }
+ f := &fileStat{
+ name: name,
+ sys: syscall.Win32FileAttributeData{
+ FileAttributes: d.FileAttributes,
+ CreationTime: d.CreationTime,
+ LastAccessTime: d.LastAccessTime,
+ LastWriteTime: d.LastWriteTime,
+ FileSizeHigh: d.FileSizeHigh,
+ FileSizeLow: d.FileSizeLow,
+ },
+ path: file.dirinfo.path + `\` + name,
+ }
+ n--
+ fi = append(fi, f)
+ }
+ if !wantAll && len(fi) == 0 {
+ return fi, io.EOF
+ }
+ return fi, nil
+}
+
+// readConsole reads utf16 characters from console File,
+// encodes them into utf8 and stores them in buffer b.
+// It returns the number of utf8 bytes read and an error, if any.
+func (f *File) readConsole(b []byte) (n int, err error) {
+ if len(b) == 0 {
+ return 0, nil
+ }
+ if len(f.readbuf) == 0 {
+ // syscall.ReadConsole seems to fail, if given large buffer.
+ // So limit the buffer to 16000 characters.
+ numBytes := len(b)
+ if numBytes > 16000 {
+ numBytes = 16000
+ }
+ // get more input data from os
+ wchars := make([]uint16, numBytes)
+ var p *uint16
+ if len(b) > 0 {
+ p = &wchars[0]
+ }
+ var nw uint32
+ err := syscall.ReadConsole(f.fd, p, uint32(len(wchars)), &nw, nil)
+ if err != nil {
+ return 0, err
+ }
+ f.readbuf = utf16.Decode(wchars[:nw])
+ }
+ for i, r := range f.readbuf {
+ if utf8.RuneLen(r) > len(b) {
+ f.readbuf = f.readbuf[i:]
+ return n, nil
+ }
+ nr := utf8.EncodeRune(b, r)
+ b = b[nr:]
+ n += nr
+ }
+ f.readbuf = nil
+ return n, nil
+}
+
+// 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) {
+ f.l.Lock()
+ defer f.l.Unlock()
+ if f.isConsole {
+ return f.readConsole(b)
+ }
+ 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.
+func (f *File) pread(b []byte, off int64) (n int, err error) {
+ f.l.Lock()
+ defer f.l.Unlock()
+ curoffset, e := syscall.Seek(f.fd, 0, 1)
+ if e != nil {
+ return 0, e
+ }
+ defer syscall.Seek(f.fd, curoffset, 0)
+ o := syscall.Overlapped{
+ OffsetHigh: uint32(off >> 32),
+ Offset: uint32(off),
+ }
+ var done uint32
+ e = syscall.ReadFile(syscall.Handle(f.fd), b, &done, &o)
+ if e != nil {
+ if e == syscall.ERROR_HANDLE_EOF {
+ // end of file
+ return 0, nil
+ }
+ return 0, e
+ }
+ return int(done), nil
+}
+
+// writeConsole writes len(b) bytes to the console File.
+// It returns the number of bytes written and an error, if any.
+func (f *File) writeConsole(b []byte) (n int, err error) {
+ n = len(b)
+ runes := make([]rune, 0, 256)
+ if len(f.lastbits) > 0 {
+ b = append(f.lastbits, b...)
+ f.lastbits = nil
+
+ }
+ for len(b) >= utf8.UTFMax || utf8.FullRune(b) {
+ r, l := utf8.DecodeRune(b)
+ runes = append(runes, r)
+ b = b[l:]
+ }
+ if len(b) > 0 {
+ f.lastbits = make([]byte, len(b))
+ copy(f.lastbits, b)
+ }
+ // syscall.WriteConsole seems to fail, if given large buffer.
+ // So limit the buffer to 16000 characters. This number was
+ // discovered by experimenting with syscall.WriteConsole.
+ const maxWrite = 16000
+ for len(runes) > 0 {
+ m := len(runes)
+ if m > maxWrite {
+ m = maxWrite
+ }
+ chunk := runes[:m]
+ runes = runes[m:]
+ uint16s := utf16.Encode(chunk)
+ for len(uint16s) > 0 {
+ var written uint32
+ err = syscall.WriteConsole(f.fd, &uint16s[0], uint32(len(uint16s)), &written, nil)
+ if err != nil {
+ return 0, nil
+ }
+ uint16s = uint16s[written:]
+ }
+ }
+ return n, nil
+}
+
+// write writes len(b) bytes to the File.
+// It returns the number of bytes written and an error, if any.
+func (f *File) write(b []byte) (n int, err error) {
+ f.l.Lock()
+ defer f.l.Unlock()
+ if f.isConsole {
+ return f.writeConsole(b)
+ }
+ return syscall.Write(f.fd, b)
+}
+
+// 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) {
+ f.l.Lock()
+ defer f.l.Unlock()
+ curoffset, e := syscall.Seek(f.fd, 0, 1)
+ if e != nil {
+ return 0, e
+ }
+ defer syscall.Seek(f.fd, curoffset, 0)
+ o := syscall.Overlapped{
+ OffsetHigh: uint32(off >> 32),
+ Offset: uint32(off),
+ }
+ var done uint32
+ e = syscall.WriteFile(syscall.Handle(f.fd), b, &done, &o)
+ if e != nil {
+ return 0, e
+ }
+ return int(done), nil
+}
+
+// seek sets the offset for the next Read or Write on file to offset, interpreted
+// according to whence: 0 means relative to the origin of the file, 1 means
+// relative to the current offset, and 2 means relative to the end.
+// It returns the new offset and an error, if any.
+func (f *File) seek(offset int64, whence int) (ret int64, err error) {
+ f.l.Lock()
+ defer f.l.Unlock()
+ return syscall.Seek(f.fd, offset, whence)
+}
+
+// Truncate changes the size of the named file.
+// If the file is a symbolic link, it changes the size of the link's target.
+func Truncate(name string, size int64) error {
+ f, e := OpenFile(name, O_WRONLY|O_CREATE, 0666)
+ if e != nil {
+ return e
+ }
+ defer f.Close()
+ e1 := f.Truncate(size)
+ if e1 != nil {
+ return e1
+ }
+ return nil
+}
+
+// Remove removes the named file or directory.
+// If there is an error, it will be of type *PathError.
+func Remove(name string) error {
+ p, e := syscall.UTF16PtrFromString(name)
+ if e != nil {
+ return &PathError{"remove", name, e}
+ }
+
+ // Go file interface forces us to know whether
+ // name is a file or directory. Try both.
+ e = syscall.DeleteFile(p)
+ if e == nil {
+ return nil
+ }
+ e1 := syscall.RemoveDirectory(p)
+ if e1 == nil {
+ return nil
+ }
+
+ // Both failed: figure out which error to return.
+ if e1 != e {
+ a, e2 := syscall.GetFileAttributes(p)
+ if e2 != nil {
+ e = e2
+ } else {
+ if a&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ e = e1
+ }
+ }
+ }
+ return &PathError{"remove", name, e}
+}
+
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]syscall.Handle
+
+ // See ../syscall/exec.go for description of lock.
+ syscall.ForkLock.RLock()
+ e := syscall.Pipe(p[0:])
+ if e != nil {
+ syscall.ForkLock.RUnlock()
+ return nil, nil, NewSyscallError("pipe", e)
+ }
+ syscall.CloseOnExec(p[0])
+ syscall.CloseOnExec(p[1])
+ syscall.ForkLock.RUnlock()
+
+ return NewFile(uintptr(p[0]), "|0"), NewFile(uintptr(p[1]), "|1"), nil
+}
+
+// TempDir returns the default directory to use for temporary files.
+func TempDir() string {
+ const pathSep = '\\'
+ dirw := make([]uint16, syscall.MAX_PATH)
+ n, _ := syscall.GetTempPath(uint32(len(dirw)), &dirw[0])
+ if n > uint32(len(dirw)) {
+ dirw = make([]uint16, n)
+ n, _ = syscall.GetTempPath(uint32(len(dirw)), &dirw[0])
+ if n > uint32(len(dirw)) {
+ n = 0
+ }
+ }
+ if n > 0 && dirw[n-1] == pathSep {
+ n--
+ }
+ return string(utf16.Decode(dirw[0:n]))
+}
+
+// Link creates newname as a hard link to the oldname file.
+// If there is an error, it will be of type *LinkError.
+func Link(oldname, newname string) error {
+ n, err := syscall.UTF16PtrFromString(newname)
+ if err != nil {
+ return &LinkError{"link", oldname, newname, err}
+ }
+ o, err := syscall.UTF16PtrFromString(oldname)
+ if err != nil {
+ return &LinkError{"link", oldname, newname, err}
+ }
+
+ e := syscall.CreateHardLink(n, o, 0)
+ if e != nil {
+ return &LinkError{"link", oldname, newname, err}
+ }
+ return nil
+}
+
+// Symlink creates newname as a symbolic link to oldname.
+// If there is an error, it will be of type *LinkError.
+func Symlink(oldname, newname string) error {
+ // CreateSymbolicLink is not supported before Windows Vista
+ if syscall.LoadCreateSymbolicLink() != nil {
+ return &LinkError{"symlink", oldname, newname, syscall.EWINDOWS}
+ }
+
+ // '/' does not work in link's content
+ oldname = fromSlash(oldname)
+
+ // need the exact location of the oldname when its relative to determine if its a directory
+ destpath := oldname
+ if !isAbs(oldname) {
+ destpath = dirname(newname) + `\` + oldname
+ }
+
+ fi, err := Lstat(destpath)
+ isdir := err == nil && fi.IsDir()
+
+ n, err := syscall.UTF16PtrFromString(newname)
+ if err != nil {
+ return &LinkError{"symlink", oldname, newname, err}
+ }
+ o, err := syscall.UTF16PtrFromString(oldname)
+ if err != nil {
+ return &LinkError{"symlink", oldname, newname, err}
+ }
+
+ var flags uint32
+ if isdir {
+ flags |= syscall.SYMBOLIC_LINK_FLAG_DIRECTORY
+ }
+ err = syscall.CreateSymbolicLink(n, o, flags)
+ if err != nil {
+ return &LinkError{"symlink", oldname, newname, err}
+ }
+ return nil
+}
+
+func fromSlash(path string) string {
+ // Replace each '/' with '\\' if present
+ var pathbuf []byte
+ var lastSlash int
+ for i, b := range path {
+ if b == '/' {
+ if pathbuf == nil {
+ pathbuf = make([]byte, len(path))
+ }
+ copy(pathbuf[lastSlash:], path[lastSlash:i])
+ pathbuf[i] = '\\'
+ lastSlash = i + 1
+ }
+ }
+ if pathbuf == nil {
+ return path
+ }
+
+ copy(pathbuf[lastSlash:], path[lastSlash:])
+ return string(pathbuf)
+}
+
+func dirname(path string) string {
+ vol := volumeName(path)
+ i := len(path) - 1
+ for i >= len(vol) && !IsPathSeparator(path[i]) {
+ i--
+ }
+ dir := path[len(vol) : i+1]
+ last := len(dir) - 1
+ if last > 0 && IsPathSeparator(dir[last]) {
+ dir = dir[:last]
+ }
+ if dir == "" {
+ dir = "."
+ }
+ return vol + dir
+}
diff --git a/src/os/getwd.go b/src/os/getwd.go
new file mode 100644
index 000000000..d5da53b34
--- /dev/null
+++ b/src/os/getwd.go
@@ -0,0 +1,123 @@
+// 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 (
+ "runtime"
+ "sync"
+ "syscall"
+)
+
+var getwdCache struct {
+ sync.Mutex
+ dir string
+}
+
+// useSyscallwd determines whether to use the return value of
+// syscall.Getwd based on its error.
+var useSyscallwd = func(error) bool { return true }
+
+// Getwd returns a rooted path name corresponding to the
+// 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() (dir string, err error) {
+ if runtime.GOOS == "windows" {
+ return syscall.Getwd()
+ }
+
+ // Clumsy but widespread kludge:
+ // if $PWD is set and matches ".", use it.
+ dot, err := Stat(".")
+ if err != nil {
+ return "", err
+ }
+ dir = Getenv("PWD")
+ if len(dir) > 0 && dir[0] == '/' {
+ d, err := Stat(dir)
+ if err == nil && SameFile(dot, d) {
+ return dir, nil
+ }
+ }
+
+ // If the operating system provides a Getwd call, use it.
+ // Otherwise, we're trying to find our way back to ".".
+ if syscall.ImplementsGetwd {
+ s, e := syscall.Getwd()
+ if useSyscallwd(e) {
+ return s, NewSyscallError("getwd", e)
+ }
+ }
+
+ // Apply same kludge but to cached dir instead of $PWD.
+ getwdCache.Lock()
+ dir = getwdCache.dir
+ getwdCache.Unlock()
+ if len(dir) > 0 {
+ d, err := Stat(dir)
+ if err == nil && SameFile(dot, d) {
+ return dir, nil
+ }
+ }
+
+ // Root is a special case because it has no parent
+ // and ends in a slash.
+ root, err := Stat("/")
+ if err != nil {
+ // Can't stat root - no hope.
+ return "", err
+ }
+ if SameFile(root, dot) {
+ return "/", nil
+ }
+
+ // General algorithm: find name in parent
+ // and then find name of parent. Each iteration
+ // adds /name to the beginning of dir.
+ dir = ""
+ for parent := ".."; ; parent = "../" + parent {
+ if len(parent) >= 1024 { // Sanity check
+ return "", syscall.ENAMETOOLONG
+ }
+ fd, err := Open(parent)
+ if err != nil {
+ return "", err
+ }
+
+ for {
+ names, err := fd.Readdirnames(100)
+ if err != nil {
+ fd.Close()
+ return "", err
+ }
+ for _, name := range names {
+ d, _ := Lstat(parent + "/" + name)
+ if SameFile(d, dot) {
+ dir = "/" + name + dir
+ goto Found
+ }
+ }
+ }
+
+ Found:
+ pd, err := fd.Stat()
+ if err != nil {
+ return "", err
+ }
+ fd.Close()
+ if SameFile(pd, root) {
+ break
+ }
+ // Set up for next round.
+ dot = pd
+ }
+
+ // Save answer as hint to avoid the expensive path next time.
+ getwdCache.Lock()
+ getwdCache.dir = dir
+ getwdCache.Unlock()
+
+ return dir, nil
+}
diff --git a/src/os/getwd_darwin.go b/src/os/getwd_darwin.go
new file mode 100644
index 000000000..e51ffcd5e
--- /dev/null
+++ b/src/os/getwd_darwin.go
@@ -0,0 +1,15 @@
+// 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"
+
+func init() {
+ useSyscallwd = useSyscallwdDarwin
+}
+
+func useSyscallwdDarwin(err error) bool {
+ return err != syscall.ENOTSUP
+}
diff --git a/src/os/os_test.go b/src/os/os_test.go
new file mode 100644
index 000000000..0224c9b01
--- /dev/null
+++ b/src/os/os_test.go
@@ -0,0 +1,1405 @@
+// 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_test
+
+import (
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ . "os"
+ osexec "os/exec"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "sort"
+ "strings"
+ "syscall"
+ "testing"
+ "text/template"
+ "time"
+)
+
+var supportsSymlinks = true
+
+var dot = []string{
+ "dir_unix.go",
+ "env.go",
+ "error.go",
+ "file.go",
+ "os_test.go",
+ "types.go",
+ "stat_darwin.go",
+ "stat_linux.go",
+}
+
+type sysDir struct {
+ name string
+ files []string
+}
+
+var sysdir = func() (sd *sysDir) {
+ switch runtime.GOOS {
+ case "android":
+ sd = &sysDir{
+ "/system/etc",
+ []string{
+ "audio_policy.conf",
+ "system_fonts.xml",
+ },
+ }
+ case "windows":
+ sd = &sysDir{
+ Getenv("SystemRoot") + "\\system32\\drivers\\etc",
+ []string{
+ "networks",
+ "protocol",
+ "services",
+ },
+ }
+ case "plan9":
+ sd = &sysDir{
+ "/lib/ndb",
+ []string{
+ "common",
+ "local",
+ },
+ }
+ default:
+ sd = &sysDir{
+ "/etc",
+ []string{
+ "group",
+ "hosts",
+ "passwd",
+ },
+ }
+ }
+ return
+}()
+
+func size(name string, t *testing.T) int64 {
+ file, err := Open(name)
+ if err != nil {
+ t.Fatal("open failed:", err)
+ }
+ defer file.Close()
+ var buf [100]byte
+ len := 0
+ for {
+ n, e := file.Read(buf[0:])
+ len += n
+ if e == io.EOF {
+ break
+ }
+ if e != nil {
+ t.Fatal("read failed:", err)
+ }
+ }
+ return int64(len)
+}
+
+func equal(name1, name2 string) (r bool) {
+ switch runtime.GOOS {
+ case "windows":
+ r = strings.ToLower(name1) == strings.ToLower(name2)
+ default:
+ r = name1 == name2
+ }
+ return
+}
+
+func newFile(testName string, t *testing.T) (f *File) {
+ // Use a local file system, not NFS.
+ // On Unix, override $TMPDIR in case the user
+ // has it set to an NFS-mounted directory.
+ dir := ""
+ if runtime.GOOS != "android" && runtime.GOOS != "windows" {
+ dir = "/tmp"
+ }
+ f, err := ioutil.TempFile(dir, "_Go_"+testName)
+ if err != nil {
+ t.Fatalf("open %s: %s", testName, err)
+ }
+ return
+}
+
+var sfdir = sysdir.name
+var sfname = sysdir.files[0]
+
+func TestStat(t *testing.T) {
+ path := sfdir + "/" + sfname
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatal("stat failed:", err)
+ }
+ if !equal(sfname, dir.Name()) {
+ t.Error("name should be ", sfname, "; is", dir.Name())
+ }
+ filesize := size(path, t)
+ if dir.Size() != filesize {
+ t.Error("size should be", filesize, "; is", dir.Size())
+ }
+}
+
+func TestFstat(t *testing.T) {
+ path := sfdir + "/" + sfname
+ file, err1 := Open(path)
+ if err1 != nil {
+ t.Fatal("open failed:", err1)
+ }
+ defer file.Close()
+ dir, err2 := file.Stat()
+ if err2 != nil {
+ t.Fatal("fstat failed:", err2)
+ }
+ if !equal(sfname, dir.Name()) {
+ t.Error("name should be ", sfname, "; is", dir.Name())
+ }
+ filesize := size(path, t)
+ if dir.Size() != filesize {
+ t.Error("size should be", filesize, "; is", dir.Size())
+ }
+}
+
+func TestLstat(t *testing.T) {
+ path := sfdir + "/" + sfname
+ dir, err := Lstat(path)
+ if err != nil {
+ t.Fatal("lstat failed:", err)
+ }
+ if !equal(sfname, dir.Name()) {
+ t.Error("name should be ", sfname, "; is", dir.Name())
+ }
+ filesize := size(path, t)
+ if dir.Size() != filesize {
+ t.Error("size should be", filesize, "; is", dir.Size())
+ }
+}
+
+// Read with length 0 should not return EOF.
+func TestRead0(t *testing.T) {
+ path := sfdir + "/" + sfname
+ f, err := Open(path)
+ if err != nil {
+ t.Fatal("open failed:", err)
+ }
+ defer f.Close()
+
+ b := make([]byte, 0)
+ n, err := f.Read(b)
+ if n != 0 || err != nil {
+ t.Errorf("Read(0) = %d, %v, want 0, nil", n, err)
+ }
+ b = make([]byte, 100)
+ n, err = f.Read(b)
+ if n <= 0 || err != nil {
+ t.Errorf("Read(100) = %d, %v, want >0, nil", n, err)
+ }
+}
+
+func testReaddirnames(dir string, contents []string, t *testing.T) {
+ file, err := Open(dir)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", dir, err)
+ }
+ defer file.Close()
+ s, err2 := file.Readdirnames(-1)
+ if err2 != nil {
+ t.Fatalf("readdirnames %q failed: %v", dir, err2)
+ }
+ for _, m := range contents {
+ found := false
+ for _, n := range s {
+ if n == "." || n == ".." {
+ t.Errorf("got %s in directory", n)
+ }
+ if equal(m, n) {
+ if found {
+ t.Error("present twice:", m)
+ }
+ found = true
+ }
+ }
+ if !found {
+ t.Error("could not find", m)
+ }
+ }
+}
+
+func testReaddir(dir string, contents []string, t *testing.T) {
+ file, err := Open(dir)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", dir, err)
+ }
+ defer file.Close()
+ s, err2 := file.Readdir(-1)
+ if err2 != nil {
+ t.Fatalf("readdir %q failed: %v", dir, err2)
+ }
+ for _, m := range contents {
+ found := false
+ for _, n := range s {
+ if equal(m, n.Name()) {
+ if found {
+ t.Error("present twice:", m)
+ }
+ found = true
+ }
+ }
+ if !found {
+ t.Error("could not find", m)
+ }
+ }
+}
+
+func TestReaddirnames(t *testing.T) {
+ testReaddirnames(".", dot, t)
+ testReaddirnames(sysdir.name, sysdir.files, t)
+}
+
+func TestReaddir(t *testing.T) {
+ testReaddir(".", dot, t)
+ testReaddir(sysdir.name, sysdir.files, t)
+}
+
+// Read the directory one entry at a time.
+func smallReaddirnames(file *File, length int, t *testing.T) []string {
+ names := make([]string, length)
+ count := 0
+ for {
+ d, err := file.Readdirnames(1)
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatalf("readdirnames %q failed: %v", file.Name(), err)
+ }
+ if len(d) == 0 {
+ t.Fatalf("readdirnames %q returned empty slice and no error", file.Name())
+ }
+ names[count] = d[0]
+ count++
+ }
+ return names[0:count]
+}
+
+// Check that reading a directory one entry at a time gives the same result
+// as reading it all at once.
+func TestReaddirnamesOneAtATime(t *testing.T) {
+ // big directory that doesn't change often.
+ dir := "/usr/bin"
+ switch runtime.GOOS {
+ case "android":
+ dir = "/system/bin"
+ case "plan9":
+ dir = "/bin"
+ case "windows":
+ dir = Getenv("SystemRoot") + "\\system32"
+ }
+ file, err := Open(dir)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", dir, err)
+ }
+ defer file.Close()
+ all, err1 := file.Readdirnames(-1)
+ if err1 != nil {
+ t.Fatalf("readdirnames %q failed: %v", dir, err1)
+ }
+ file1, err2 := Open(dir)
+ if err2 != nil {
+ t.Fatalf("open %q failed: %v", dir, err2)
+ }
+ defer file1.Close()
+ small := smallReaddirnames(file1, len(all)+100, t) // +100 in case we screw up
+ if len(small) < len(all) {
+ t.Fatalf("len(small) is %d, less than %d", len(small), len(all))
+ }
+ for i, n := range all {
+ if small[i] != n {
+ t.Errorf("small read %q mismatch: %v", small[i], n)
+ }
+ }
+}
+
+func TestReaddirNValues(t *testing.T) {
+ if testing.Short() {
+ t.Skip("test.short; skipping")
+ }
+ dir, err := ioutil.TempDir("", "")
+ if err != nil {
+ t.Fatalf("TempDir: %v", err)
+ }
+ defer RemoveAll(dir)
+ for i := 1; i <= 105; i++ {
+ f, err := Create(filepath.Join(dir, fmt.Sprintf("%d", i)))
+ if err != nil {
+ t.Fatalf("Create: %v", err)
+ }
+ f.Write([]byte(strings.Repeat("X", i)))
+ f.Close()
+ }
+
+ var d *File
+ openDir := func() {
+ var err error
+ d, err = Open(dir)
+ if err != nil {
+ t.Fatalf("Open directory: %v", err)
+ }
+ }
+
+ readDirExpect := func(n, want int, wantErr error) {
+ fi, err := d.Readdir(n)
+ if err != wantErr {
+ t.Fatalf("Readdir of %d got error %v, want %v", n, err, wantErr)
+ }
+ if g, e := len(fi), want; g != e {
+ t.Errorf("Readdir of %d got %d files, want %d", n, g, e)
+ }
+ }
+
+ readDirNamesExpect := func(n, want int, wantErr error) {
+ fi, err := d.Readdirnames(n)
+ if err != wantErr {
+ t.Fatalf("Readdirnames of %d got error %v, want %v", n, err, wantErr)
+ }
+ if g, e := len(fi), want; g != e {
+ t.Errorf("Readdirnames of %d got %d files, want %d", n, g, e)
+ }
+ }
+
+ for _, fn := range []func(int, int, error){readDirExpect, readDirNamesExpect} {
+ // Test the slurp case
+ openDir()
+ fn(0, 105, nil)
+ fn(0, 0, nil)
+ d.Close()
+
+ // Slurp with -1 instead
+ openDir()
+ fn(-1, 105, nil)
+ fn(-2, 0, nil)
+ fn(0, 0, nil)
+ d.Close()
+
+ // Test the bounded case
+ openDir()
+ fn(1, 1, nil)
+ fn(2, 2, nil)
+ fn(105, 102, nil) // and tests buffer >100 case
+ fn(3, 0, io.EOF)
+ d.Close()
+ }
+}
+
+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 == "plan9" {
+ return
+ }
+ from, to := "hardlinktestfrom", "hardlinktestto"
+ Remove(from) // Just in case.
+ file, err := Create(to)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", to, err)
+ }
+ defer Remove(to)
+ if err = file.Close(); err != nil {
+ t.Errorf("close %q failed: %v", to, err)
+ }
+ err = Link(to, from)
+ if err != nil {
+ t.Fatalf("link %q, %q failed: %v", to, from, err)
+ }
+ defer Remove(from)
+ tostat, err := Stat(to)
+ if err != nil {
+ t.Fatalf("stat %q failed: %v", to, err)
+ }
+ fromstat, err := Stat(from)
+ if err != nil {
+ t.Fatalf("stat %q failed: %v", from, err)
+ }
+ if !SameFile(tostat, fromstat) {
+ t.Errorf("link %q, %q did not create hard link", to, from)
+ }
+}
+
+func TestSymlink(t *testing.T) {
+ switch runtime.GOOS {
+ case "android", "nacl", "plan9":
+ t.Skipf("skipping on %s", runtime.GOOS)
+ case "windows":
+ if !supportsSymlinks {
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+ }
+ from, to := "symlinktestfrom", "symlinktestto"
+ Remove(from) // Just in case.
+ file, err := Create(to)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", to, err)
+ }
+ defer Remove(to)
+ if err = file.Close(); err != nil {
+ t.Errorf("close %q failed: %v", to, err)
+ }
+ err = Symlink(to, from)
+ if err != nil {
+ t.Fatalf("symlink %q, %q failed: %v", to, from, err)
+ }
+ defer Remove(from)
+ tostat, err := Lstat(to)
+ if err != nil {
+ t.Fatalf("stat %q failed: %v", to, err)
+ }
+ if tostat.Mode()&ModeSymlink != 0 {
+ t.Fatalf("stat %q claims to have found a symlink", to)
+ }
+ fromstat, err := Stat(from)
+ if err != nil {
+ t.Fatalf("stat %q failed: %v", from, err)
+ }
+ if !SameFile(tostat, fromstat) {
+ t.Errorf("symlink %q, %q did not create symlink", to, from)
+ }
+ fromstat, err = Lstat(from)
+ if err != nil {
+ t.Fatalf("lstat %q failed: %v", from, err)
+ }
+ if fromstat.Mode()&ModeSymlink == 0 {
+ t.Fatalf("symlink %q, %q did not create symlink", to, from)
+ }
+ fromstat, err = Stat(from)
+ if err != nil {
+ t.Fatalf("stat %q failed: %v", from, err)
+ }
+ if fromstat.Mode()&ModeSymlink != 0 {
+ t.Fatalf("stat %q did not follow symlink", from)
+ }
+ s, err := Readlink(from)
+ if err != nil {
+ t.Fatalf("readlink %q failed: %v", from, err)
+ }
+ if s != to {
+ t.Fatalf("after symlink %q != %q", s, to)
+ }
+ file, err = Open(from)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", from, err)
+ }
+ file.Close()
+}
+
+func TestLongSymlink(t *testing.T) {
+ switch runtime.GOOS {
+ case "plan9", "nacl":
+ t.Skipf("skipping on %s", runtime.GOOS)
+ case "windows":
+ if !supportsSymlinks {
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+ }
+ s := "0123456789abcdef"
+ // Long, but not too long: a common limit is 255.
+ s = s + s + s + s + s + s + s + s + s + s + s + s + s + s + s
+ from := "longsymlinktestfrom"
+ Remove(from) // Just in case.
+ err := Symlink(s, from)
+ if err != nil {
+ t.Fatalf("symlink %q, %q failed: %v", s, from, err)
+ }
+ defer Remove(from)
+ r, err := Readlink(from)
+ if err != nil {
+ t.Fatalf("readlink %q failed: %v", from, err)
+ }
+ if r != s {
+ t.Fatalf("after symlink %q != %q", r, s)
+ }
+}
+
+func TestRename(t *testing.T) {
+ from, to := "renamefrom", "renameto"
+ Remove(to) // Just in case.
+ file, err := Create(from)
+ if err != nil {
+ t.Fatalf("open %q failed: %v", to, err)
+ }
+ if err = file.Close(); err != nil {
+ t.Errorf("close %q failed: %v", to, err)
+ }
+ err = Rename(from, to)
+ if err != nil {
+ t.Fatalf("rename %q, %q failed: %v", to, from, err)
+ }
+ defer Remove(to)
+ _, err = Stat(to)
+ if err != nil {
+ t.Errorf("stat %q failed: %v", to, err)
+ }
+}
+
+func exec(t *testing.T, dir, cmd string, args []string, expect string) {
+ r, w, err := Pipe()
+ if err != nil {
+ t.Fatalf("Pipe: %v", err)
+ }
+ defer r.Close()
+ attr := &ProcAttr{Dir: dir, Files: []*File{nil, w, Stderr}}
+ p, err := StartProcess(cmd, args, attr)
+ if err != nil {
+ t.Fatalf("StartProcess: %v", err)
+ }
+ w.Close()
+
+ var b bytes.Buffer
+ io.Copy(&b, r)
+ output := b.String()
+
+ fi1, _ := Stat(strings.TrimSpace(output))
+ fi2, _ := Stat(expect)
+ if !SameFile(fi1, fi2) {
+ t.Errorf("exec %q returned %q wanted %q",
+ strings.Join(append([]string{cmd}, args...), " "), output, expect)
+ }
+ p.Wait()
+}
+
+func TestStartProcess(t *testing.T) {
+ switch runtime.GOOS {
+ case "android", "nacl":
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+
+ var dir, cmd string
+ var args []string
+ if runtime.GOOS == "windows" {
+ cmd = Getenv("COMSPEC")
+ dir = Getenv("SystemRoot")
+ args = []string{"/c", "cd"}
+ } else {
+ cmd = "/bin/pwd"
+ dir = "/"
+ args = []string{}
+ }
+ cmddir, cmdbase := filepath.Split(cmd)
+ args = append([]string{cmdbase}, args...)
+ // Test absolute executable path.
+ exec(t, dir, cmd, args, dir)
+ // Test relative executable path.
+ exec(t, cmddir, cmdbase, args, cmddir)
+}
+
+func checkMode(t *testing.T, path string, mode FileMode) {
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err)
+ }
+ if dir.Mode()&0777 != mode {
+ t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode)
+ }
+}
+
+func TestChmod(t *testing.T) {
+ // Chmod is not supported under windows.
+ if runtime.GOOS == "windows" {
+ return
+ }
+ f := newFile("TestChmod", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ if err := Chmod(f.Name(), 0456); err != nil {
+ t.Fatalf("chmod %s 0456: %s", f.Name(), err)
+ }
+ checkMode(t, f.Name(), 0456)
+
+ if err := f.Chmod(0123); err != nil {
+ t.Fatalf("chmod %s 0123: %s", f.Name(), err)
+ }
+ checkMode(t, f.Name(), 0123)
+}
+
+func checkSize(t *testing.T, f *File, size int64) {
+ dir, err := f.Stat()
+ if err != nil {
+ t.Fatalf("Stat %q (looking for size %d): %s", f.Name(), size, err)
+ }
+ if dir.Size() != size {
+ t.Errorf("Stat %q: size %d want %d", f.Name(), dir.Size(), size)
+ }
+}
+
+func TestFTruncate(t *testing.T) {
+ f := newFile("TestFTruncate", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ checkSize(t, f, 0)
+ f.Write([]byte("hello, world\n"))
+ checkSize(t, f, 13)
+ f.Truncate(10)
+ checkSize(t, f, 10)
+ f.Truncate(1024)
+ checkSize(t, f, 1024)
+ f.Truncate(0)
+ checkSize(t, f, 0)
+ _, 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) {
+ f := newFile("TestTruncate", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ checkSize(t, f, 0)
+ f.Write([]byte("hello, world\n"))
+ checkSize(t, f, 13)
+ Truncate(f.Name(), 10)
+ checkSize(t, f, 10)
+ Truncate(f.Name(), 1024)
+ checkSize(t, f, 1024)
+ Truncate(f.Name(), 0)
+ checkSize(t, f, 0)
+ _, 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,
+// so that timings are not distorted by latency and caching.
+// On NFS, timings can be off due to caching of meta-data on
+// NFS servers (Issue 848).
+func TestChtimes(t *testing.T) {
+ f := newFile("TestChtimes", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ f.Write([]byte("hello, world\n"))
+ f.Close()
+
+ st, err := Stat(f.Name())
+ if err != nil {
+ t.Fatalf("Stat %s: %s", f.Name(), err)
+ }
+ preStat := st
+
+ // Move access and modification time back a second
+ at := Atime(preStat)
+ mt := preStat.ModTime()
+ err = Chtimes(f.Name(), at.Add(-time.Second), mt.Add(-time.Second))
+ if err != nil {
+ t.Fatalf("Chtimes %s: %s", f.Name(), err)
+ }
+
+ st, err = Stat(f.Name())
+ if err != nil {
+ t.Fatalf("second Stat %s: %s", f.Name(), err)
+ }
+ postStat := st
+
+ /* Plan 9, NaCl:
+ Mtime is the time of the last change of content. Similarly, atime is set whenever the
+ contents are accessed; also, it is set whenever mtime is set.
+ */
+ pat := Atime(postStat)
+ pmt := postStat.ModTime()
+ if !pat.Before(at) && runtime.GOOS != "plan9" && runtime.GOOS != "nacl" {
+ t.Errorf("AccessTime didn't go backwards; was=%d, after=%d", at, pat)
+ }
+
+ if !pmt.Before(mt) {
+ t.Errorf("ModTime didn't go backwards; was=%d, after=%d", mt, pmt)
+ }
+}
+
+func TestChdirAndGetwd(t *testing.T) {
+ // TODO(brainman): file.Chdir() is not implemented on windows.
+ if runtime.GOOS == "windows" {
+ return
+ }
+ fd, err := Open(".")
+ if err != nil {
+ t.Fatalf("Open .: %s", err)
+ }
+ // These are chosen carefully not to be symlinks on a Mac
+ // (unlike, say, /var, /etc), except /tmp, which we handle below.
+ dirs := []string{"/", "/usr/bin", "/tmp"}
+ // /usr/bin does not usually exist on Plan 9 or Android.
+ switch runtime.GOOS {
+ case "android":
+ dirs = []string{"/", "/system/bin"}
+ case "plan9":
+ dirs = []string{"/", "/usr"}
+ }
+ oldwd := Getenv("PWD")
+ for mode := 0; mode < 2; mode++ {
+ for _, d := range dirs {
+ if mode == 0 {
+ err = Chdir(d)
+ } else {
+ fd1, err := Open(d)
+ if err != nil {
+ t.Errorf("Open %s: %s", d, err)
+ continue
+ }
+ err = fd1.Chdir()
+ fd1.Close()
+ }
+ if d == "/tmp" {
+ Setenv("PWD", "/tmp")
+ }
+ pwd, err1 := Getwd()
+ Setenv("PWD", oldwd)
+ err2 := fd.Chdir()
+ if err2 != nil {
+ // We changed the current directory and cannot go back.
+ // Don't let the tests continue; they'll scribble
+ // all over some other directory.
+ fmt.Fprintf(Stderr, "fchdir back to dot failed: %s\n", err2)
+ Exit(1)
+ }
+ if err != nil {
+ fd.Close()
+ t.Fatalf("Chdir %s: %s", d, err)
+ }
+ if err1 != nil {
+ fd.Close()
+ t.Fatalf("Getwd in %s: %s", d, err1)
+ }
+ if pwd != d {
+ fd.Close()
+ t.Fatalf("Getwd returned %q want %q", pwd, d)
+ }
+ }
+ }
+ fd.Close()
+}
+
+func TestSeek(t *testing.T) {
+ f := newFile("TestSeek", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ type test struct {
+ in int64
+ whence int
+ out int64
+ }
+ var tests = []test{
+ {0, 1, int64(len(data))},
+ {0, 0, 0},
+ {5, 0, 5},
+ {0, 2, int64(len(data))},
+ {0, 0, 0},
+ {-1, 2, int64(len(data)) - 1},
+ {1 << 33, 0, 1 << 33},
+ {1 << 33, 2, 1<<33 + int64(len(data))},
+ }
+ for i, tt := range tests {
+ off, err := f.Seek(tt.in, tt.whence)
+ if off != tt.out || err != nil {
+ if e, ok := err.(*PathError); ok && e.Err == syscall.EINVAL && tt.out > 1<<32 {
+ // Reiserfs rejects the big seeks.
+ // http://code.google.com/p/go/issues/detail?id=91
+ break
+ }
+ t.Errorf("#%d: Seek(%v, %v) = %v, %v want %v, nil", i, tt.in, tt.whence, off, err, tt.out)
+ }
+ }
+}
+
+type openErrorTest struct {
+ path string
+ mode int
+ error error
+}
+
+var openErrorTests = []openErrorTest{
+ {
+ sfdir + "/no-such-file",
+ O_RDONLY,
+ syscall.ENOENT,
+ },
+ {
+ sfdir,
+ O_WRONLY,
+ syscall.EISDIR,
+ },
+ {
+ sfdir + "/" + sfname + "/no-such-file",
+ O_WRONLY,
+ syscall.ENOTDIR,
+ },
+}
+
+func TestOpenError(t *testing.T) {
+ for _, tt := range openErrorTests {
+ f, err := OpenFile(tt.path, tt.mode, 0)
+ if err == nil {
+ t.Errorf("Open(%q, %d) succeeded", tt.path, tt.mode)
+ f.Close()
+ continue
+ }
+ perr, ok := err.(*PathError)
+ if !ok {
+ t.Errorf("Open(%q, %d) returns error of %T type; want *PathError", tt.path, tt.mode, err)
+ }
+ if perr.Err != tt.error {
+ if runtime.GOOS == "plan9" {
+ syscallErrStr := perr.Err.Error()
+ expectedErrStr := strings.Replace(tt.error.Error(), "file ", "", 1)
+ if !strings.HasSuffix(syscallErrStr, expectedErrStr) {
+ // Some Plan 9 file servers incorrectly return
+ // EACCES rather than EISDIR when a directory is
+ // opened for write.
+ if tt.error == syscall.EISDIR && strings.HasSuffix(syscallErrStr, syscall.EACCES.Error()) {
+ continue
+ }
+ t.Errorf("Open(%q, %d) = _, %q; want suffix %q", tt.path, tt.mode, syscallErrStr, expectedErrStr)
+ }
+ continue
+ }
+ if runtime.GOOS == "dragonfly" {
+ // DragonFly incorrectly returns EACCES rather
+ // EISDIR when a directory is opened for write.
+ if tt.error == syscall.EISDIR && perr.Err == syscall.EACCES {
+ continue
+ }
+ }
+ t.Errorf("Open(%q, %d) = _, %q; want %q", tt.path, tt.mode, perr.Err.Error(), tt.error.Error())
+ }
+ }
+}
+
+func TestOpenNoName(t *testing.T) {
+ f, err := Open("")
+ if err == nil {
+ t.Fatal(`Open("") succeeded`)
+ f.Close()
+ }
+}
+
+func run(t *testing.T, cmd []string) string {
+ // Run /bin/hostname and collect output.
+ r, w, err := Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ p, err := StartProcess("/bin/hostname", []string{"hostname"}, &ProcAttr{Files: []*File{nil, w, Stderr}})
+ if err != nil {
+ t.Fatal(err)
+ }
+ w.Close()
+
+ var b bytes.Buffer
+ io.Copy(&b, r)
+ _, err = p.Wait()
+ if err != nil {
+ t.Fatalf("run hostname Wait: %v", err)
+ }
+ err = p.Kill()
+ if err == nil {
+ t.Errorf("expected an error from Kill running 'hostname'")
+ }
+ output := b.String()
+ if n := len(output); n > 0 && output[n-1] == '\n' {
+ output = output[0 : n-1]
+ }
+ if output == "" {
+ t.Fatalf("%v produced no output", cmd)
+ }
+
+ return output
+}
+
+func TestHostname(t *testing.T) {
+ // There is no other way to fetch hostname on windows, but via winapi.
+ // On Plan 9 it can be taken from #c/sysname as Hostname() does.
+ switch runtime.GOOS {
+ case "android", "nacl", "plan9", "windows":
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+
+ // Check internal Hostname() against the output of /bin/hostname.
+ // Allow that the internal Hostname returns a Fully Qualified Domain Name
+ // and the /bin/hostname only returns the first component
+ hostname, err := Hostname()
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ want := run(t, []string{"/bin/hostname"})
+ if hostname != want {
+ i := strings.Index(hostname, ".")
+ if i < 0 || hostname[0:i] != want {
+ t.Errorf("Hostname() = %q, want %q", hostname, want)
+ }
+ }
+}
+
+func TestReadAt(t *testing.T) {
+ f := newFile("TestReadAt", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ b := make([]byte, 5)
+ n, err := f.ReadAt(b, 7)
+ if err != nil || n != len(b) {
+ t.Fatalf("ReadAt 7: %d, %v", n, err)
+ }
+ if string(b) != "world" {
+ t.Fatalf("ReadAt 7: have %q want %q", string(b), "world")
+ }
+}
+
+func TestWriteAt(t *testing.T) {
+ f := newFile("TestWriteAt", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ const data = "hello, world\n"
+ io.WriteString(f, data)
+
+ n, err := f.WriteAt([]byte("WORLD"), 7)
+ if err != nil || n != 5 {
+ t.Fatalf("WriteAt 7: %d, %v", n, err)
+ }
+
+ b, err := ioutil.ReadFile(f.Name())
+ if err != nil {
+ t.Fatalf("ReadFile %s: %v", f.Name(), err)
+ }
+ if string(b) != "hello, WORLD\n" {
+ t.Fatalf("after write: have %q want %q", string(b), "hello, WORLD\n")
+ }
+}
+
+func writeFile(t *testing.T, fname string, flag int, text string) string {
+ f, err := OpenFile(fname, flag, 0666)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+ n, err := io.WriteString(f, text)
+ if err != nil {
+ t.Fatalf("WriteString: %d, %v", n, err)
+ }
+ f.Close()
+ data, err := ioutil.ReadFile(fname)
+ if err != nil {
+ t.Fatalf("ReadFile: %v", err)
+ }
+ return string(data)
+}
+
+func TestAppend(t *testing.T) {
+ const f = "append.txt"
+ defer Remove(f)
+ s := writeFile(t, f, O_CREATE|O_TRUNC|O_RDWR, "new")
+ if s != "new" {
+ t.Fatalf("writeFile: have %q want %q", s, "new")
+ }
+ s = writeFile(t, f, O_APPEND|O_RDWR, "|append")
+ if s != "new|append" {
+ t.Fatalf("writeFile: have %q want %q", s, "new|append")
+ }
+ s = writeFile(t, f, O_CREATE|O_APPEND|O_RDWR, "|append")
+ if s != "new|append|append" {
+ t.Fatalf("writeFile: have %q want %q", s, "new|append|append")
+ }
+ err := Remove(f)
+ if err != nil {
+ t.Fatalf("Remove: %v", err)
+ }
+ s = writeFile(t, f, O_CREATE|O_APPEND|O_RDWR, "new&append")
+ if s != "new&append" {
+ t.Fatalf("writeFile: after append have %q want %q", s, "new&append")
+ }
+ s = writeFile(t, f, O_CREATE|O_RDWR, "old")
+ if s != "old&append" {
+ t.Fatalf("writeFile: after create have %q want %q", s, "old&append")
+ }
+ s = writeFile(t, f, O_CREATE|O_TRUNC|O_RDWR, "new")
+ if s != "new" {
+ t.Fatalf("writeFile: after truncate have %q want %q", s, "new")
+ }
+}
+
+func TestStatDirWithTrailingSlash(t *testing.T) {
+ // Create new temporary directory and arrange to clean it up.
+ path, err := ioutil.TempDir("", "/_TestStatDirWithSlash_")
+ if err != nil {
+ t.Fatalf("TempDir: %s", err)
+ }
+ defer RemoveAll(path)
+
+ // Stat of path should succeed.
+ _, err = Stat(path)
+ if err != nil {
+ t.Fatalf("stat %s failed: %s", path, err)
+ }
+
+ // Stat of path+"/" should succeed too.
+ path += "/"
+ _, err = Stat(path)
+ if err != nil {
+ t.Fatalf("stat %s failed: %s", path, err)
+ }
+}
+
+func TestNilProcessStateString(t *testing.T) {
+ var ps *ProcessState
+ s := ps.String()
+ if s != "<nil>" {
+ t.Errorf("(*ProcessState)(nil).String() = %q, want %q", s, "<nil>")
+ }
+}
+
+func TestSameFile(t *testing.T) {
+ fa, err := Create("a")
+ if err != nil {
+ t.Fatalf("Create(a): %v", err)
+ }
+ defer Remove(fa.Name())
+ fa.Close()
+ fb, err := Create("b")
+ if err != nil {
+ t.Fatalf("Create(b): %v", err)
+ }
+ defer Remove(fb.Name())
+ fb.Close()
+
+ ia1, err := Stat("a")
+ if err != nil {
+ t.Fatalf("Stat(a): %v", err)
+ }
+ ia2, err := Stat("a")
+ if err != nil {
+ t.Fatalf("Stat(a): %v", err)
+ }
+ if !SameFile(ia1, ia2) {
+ t.Errorf("files should be same")
+ }
+
+ ib, err := Stat("b")
+ if err != nil {
+ t.Fatalf("Stat(b): %v", err)
+ }
+ if SameFile(ia1, ib) {
+ t.Errorf("files should be different")
+ }
+}
+
+func TestDevNullFile(t *testing.T) {
+ f, err := Open(DevNull)
+ if err != nil {
+ t.Fatalf("Open(%s): %v", DevNull, err)
+ }
+ defer f.Close()
+ fi, err := f.Stat()
+ if err != nil {
+ t.Fatalf("Stat(%s): %v", DevNull, err)
+ }
+ name := filepath.Base(DevNull)
+ if fi.Name() != name {
+ t.Fatalf("wrong file name have %v want %v", fi.Name(), name)
+ }
+ if fi.Size() != 0 {
+ t.Fatalf("wrong file size have %d want 0", fi.Size())
+ }
+}
+
+var testLargeWrite = flag.Bool("large_write", false, "run TestLargeWriteToConsole test that floods console with output")
+
+func TestLargeWriteToConsole(t *testing.T) {
+ if !*testLargeWrite {
+ t.Skip("skipping console-flooding test; enable with -large_write")
+ }
+ b := make([]byte, 32000)
+ for i := range b {
+ b[i] = '.'
+ }
+ b[len(b)-1] = '\n'
+ n, err := Stdout.Write(b)
+ if err != nil {
+ t.Fatalf("Write to os.Stdout failed: %v", err)
+ }
+ if n != len(b) {
+ t.Errorf("Write to os.Stdout should return %d; got %d", len(b), n)
+ }
+ n, err = Stderr.Write(b)
+ if err != nil {
+ t.Fatalf("Write to os.Stderr failed: %v", err)
+ }
+ if n != len(b) {
+ t.Errorf("Write to os.Stderr should return %d; got %d", len(b), n)
+ }
+}
+
+func TestStatDirModeExec(t *testing.T) {
+ const mode = 0111
+
+ path, err := ioutil.TempDir("", "go-build")
+ if err != nil {
+ t.Fatalf("Failed to create temp directory: %v", err)
+ }
+ defer RemoveAll(path)
+
+ if err := Chmod(path, 0777); err != nil {
+ t.Fatalf("Chmod %q 0777: %v", path, err)
+ }
+
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err)
+ }
+ if dir.Mode()&mode != mode {
+ t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode()&mode, mode)
+ }
+}
+
+func TestReadAtEOF(t *testing.T) {
+ f := newFile("TestReadAtEOF", t)
+ defer Remove(f.Name())
+ defer f.Close()
+
+ _, err := f.ReadAt(make([]byte, 10), 0)
+ switch err {
+ case io.EOF:
+ // all good
+ case nil:
+ t.Fatalf("ReadAt succeeded")
+ default:
+ t.Fatalf("ReadAt failed: %s", err)
+ }
+}
+
+func testKillProcess(t *testing.T, processKiller func(p *Process)) {
+ switch runtime.GOOS {
+ case "android", "nacl":
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+
+ dir, err := ioutil.TempDir("", "go-build")
+ if err != nil {
+ t.Fatalf("Failed to create temp directory: %v", err)
+ }
+ defer RemoveAll(dir)
+
+ src := filepath.Join(dir, "main.go")
+ f, err := Create(src)
+ if err != nil {
+ t.Fatalf("Failed to create %v: %v", src, err)
+ }
+ st := template.Must(template.New("source").Parse(`
+package main
+import "time"
+func main() {
+ time.Sleep(time.Second)
+}
+`))
+ err = st.Execute(f, nil)
+ if err != nil {
+ f.Close()
+ t.Fatalf("Failed to execute template: %v", err)
+ }
+ f.Close()
+
+ exe := filepath.Join(dir, "main.exe")
+ output, err := osexec.Command("go", "build", "-o", exe, src).CombinedOutput()
+ if err != nil {
+ t.Fatalf("Failed to build exe %v: %v %v", exe, err, string(output))
+ }
+
+ cmd := osexec.Command(exe)
+ err = cmd.Start()
+ if err != nil {
+ t.Fatalf("Failed to start test process: %v", err)
+ }
+ go func() {
+ time.Sleep(100 * time.Millisecond)
+ processKiller(cmd.Process)
+ }()
+ err = cmd.Wait()
+ if err == nil {
+ t.Errorf("Test process succeeded, but expected to fail")
+ }
+}
+
+func TestKillStartProcess(t *testing.T) {
+ testKillProcess(t, func(p *Process) {
+ err := p.Kill()
+ if err != nil {
+ t.Fatalf("Failed to kill test process: %v", err)
+ }
+ })
+}
+
+func TestGetppid(t *testing.T) {
+ switch runtime.GOOS {
+ case "nacl":
+ t.Skip("skipping on nacl")
+ case "plan9":
+ // TODO: golang.org/issue/8206
+ t.Skipf("skipping test on plan9; see issue 8206")
+ }
+
+ if Getenv("GO_WANT_HELPER_PROCESS") == "1" {
+ fmt.Print(Getppid())
+ Exit(0)
+ }
+
+ cmd := osexec.Command(Args[0], "-test.run=TestGetppid")
+ cmd.Env = append(Environ(), "GO_WANT_HELPER_PROCESS=1")
+
+ // verify that Getppid() from the forked process reports our process id
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("Failed to spawn child process: %v %q", err, string(output))
+ }
+
+ childPpid := string(output)
+ ourPid := fmt.Sprintf("%d", Getpid())
+ if childPpid != ourPid {
+ t.Fatalf("Child process reports parent process id '%v', expected '%v'", childPpid, ourPid)
+ }
+}
+
+func TestKillFindProcess(t *testing.T) {
+ testKillProcess(t, func(p *Process) {
+ p2, err := FindProcess(p.Pid)
+ if err != nil {
+ t.Fatalf("Failed to find test process: %v", err)
+ }
+ err = p2.Kill()
+ if err != nil {
+ t.Fatalf("Failed to kill test process: %v", err)
+ }
+ })
+}
+
+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/os/os_unix_test.go b/src/os/os_unix_test.go
new file mode 100644
index 000000000..21d40ccaf
--- /dev/null
+++ b/src/os/os_unix_test.go
@@ -0,0 +1,76 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package os_test
+
+import (
+ . "os"
+ "runtime"
+ "syscall"
+ "testing"
+)
+
+func checkUidGid(t *testing.T, path string, uid, gid int) {
+ dir, err := Stat(path)
+ if err != nil {
+ t.Fatalf("Stat %q (looking for uid/gid %d/%d): %s", path, uid, gid, err)
+ }
+ sys := dir.Sys().(*syscall.Stat_t)
+ if int(sys.Uid) != uid {
+ t.Errorf("Stat %q: uid %d want %d", path, sys.Uid, uid)
+ }
+ if int(sys.Gid) != gid {
+ t.Errorf("Stat %q: gid %d want %d", path, sys.Gid, gid)
+ }
+}
+
+func TestChown(t *testing.T) {
+ // Chown is not supported under windows os Plan 9.
+ // Plan9 provides a native ChownPlan9 version instead.
+ if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
+ return
+ }
+ // Use TempDir() to make sure we're on a local file system,
+ // so that the group ids returned by Getgroups will be allowed
+ // on the file. On NFS, the Getgroups groups are
+ // basically useless.
+ f := newFile("TestChown", t)
+ defer Remove(f.Name())
+ defer f.Close()
+ dir, err := f.Stat()
+ if err != nil {
+ t.Fatalf("stat %s: %s", f.Name(), err)
+ }
+
+ // Can't change uid unless root, but can try
+ // changing the group id. First try our current group.
+ gid := Getgid()
+ t.Log("gid:", gid)
+ if err = Chown(f.Name(), -1, gid); err != nil {
+ t.Fatalf("chown %s -1 %d: %s", f.Name(), gid, err)
+ }
+ sys := dir.Sys().(*syscall.Stat_t)
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+
+ // Then try all the auxiliary groups.
+ groups, err := Getgroups()
+ if err != nil {
+ t.Fatalf("getgroups: %s", err)
+ }
+ t.Log("groups: ", groups)
+ for _, g := range groups {
+ if err = Chown(f.Name(), -1, g); err != nil {
+ t.Fatalf("chown %s -1 %d: %s", f.Name(), g, err)
+ }
+ checkUidGid(t, f.Name(), int(sys.Uid), g)
+
+ // change back to gid to test fd.Chown
+ if err = f.Chown(-1, gid); err != nil {
+ t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err)
+ }
+ checkUidGid(t, f.Name(), int(sys.Uid), gid)
+ }
+}
diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go
new file mode 100644
index 000000000..fd96713ea
--- /dev/null
+++ b/src/os/os_windows_test.go
@@ -0,0 +1,81 @@
+package os_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "syscall"
+ "testing"
+)
+
+func init() {
+ tmpdir, err := ioutil.TempDir("", "symtest")
+ if err != nil {
+ panic("failed to create temp directory: " + err.Error())
+ }
+ defer os.RemoveAll(tmpdir)
+
+ err = os.Symlink("target", filepath.Join(tmpdir, "symlink"))
+ if err == nil {
+ return
+ }
+
+ err = err.(*os.LinkError).Err
+ switch err {
+ case syscall.EWINDOWS, syscall.ERROR_PRIVILEGE_NOT_HELD:
+ supportsSymlinks = false
+ }
+}
+
+func TestSameWindowsFile(t *testing.T) {
+ temp, err := ioutil.TempDir("", "TestSameWindowsFile")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(temp)
+
+ wd, err := os.Getwd()
+ if err != nil {
+ t.Fatal(err)
+ }
+ err = os.Chdir(temp)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Chdir(wd)
+
+ f, err := os.Create("a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+
+ ia1, err := os.Stat("a")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ path, err := filepath.Abs("a")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ia2, err := os.Stat(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !os.SameFile(ia1, ia2) {
+ t.Errorf("files should be same")
+ }
+
+ p := filepath.VolumeName(path) + filepath.Base(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ia3, err := os.Stat(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !os.SameFile(ia1, ia3) {
+ t.Errorf("files should be same")
+ }
+}
diff --git a/src/os/path.go b/src/os/path.go
new file mode 100644
index 000000000..02a77ec80
--- /dev/null
+++ b/src/os/path.go
@@ -0,0 +1,123 @@
+// 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 (
+ "io"
+ "syscall"
+)
+
+// MkdirAll creates a directory named path,
+// along with any necessary parents, and returns nil,
+// or else returns an error.
+// The permission bits perm are used for all
+// directories that MkdirAll creates.
+// If path is already a directory, MkdirAll does nothing
+// and returns nil.
+func MkdirAll(path string, perm FileMode) error {
+ // If path exists, stop with success or error.
+ dir, err := Stat(path)
+ if err == nil {
+ if dir.IsDir() {
+ return nil
+ }
+ return &PathError{"mkdir", path, syscall.ENOTDIR}
+ }
+
+ // Doesn't already exist; make sure parent does.
+ i := len(path)
+ for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator.
+ i--
+ }
+
+ j := i
+ for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element.
+ j--
+ }
+
+ if j > 1 {
+ // Create parent
+ err = MkdirAll(path[0:j-1], perm)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Now parent exists, try to create.
+ err = Mkdir(path, perm)
+ if err != nil {
+ // Handle arguments like "foo/." by
+ // double-checking that directory doesn't exist.
+ dir, err1 := Lstat(path)
+ if err1 == nil && dir.IsDir() {
+ return nil
+ }
+ return err
+ }
+ return nil
+}
+
+// RemoveAll removes path and any children it contains.
+// It removes everything it can but returns the first error
+// it encounters. If the path does not exist, RemoveAll
+// returns nil (no error).
+func RemoveAll(path string) error {
+ // Simple case: if Remove works, we're done.
+ err := Remove(path)
+ if err == nil {
+ return nil
+ }
+
+ // Otherwise, is this a directory we need to recurse into?
+ dir, serr := Lstat(path)
+ if serr != nil {
+ if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
+ return nil
+ }
+ return serr
+ }
+ if !dir.IsDir() {
+ // Not a directory; return the error from Remove.
+ return err
+ }
+
+ // Directory.
+ fd, err := Open(path)
+ if err != nil {
+ return err
+ }
+
+ // Remove contents & return first error.
+ err = nil
+ for {
+ names, err1 := fd.Readdirnames(100)
+ for _, name := range names {
+ err1 := RemoveAll(path + string(PathSeparator) + name)
+ if err == nil {
+ err = err1
+ }
+ }
+ if err1 == io.EOF {
+ break
+ }
+ // If Readdirnames returned an error, use it.
+ if err == nil {
+ err = err1
+ }
+ if len(names) == 0 {
+ break
+ }
+ }
+
+ // Close directory, because windows won't remove opened directory.
+ fd.Close()
+
+ // Remove directory.
+ err1 := Remove(path)
+ if err == nil {
+ err = err1
+ }
+ return err
+}
diff --git a/src/os/path_plan9.go b/src/os/path_plan9.go
new file mode 100644
index 000000000..64bad500a
--- /dev/null
+++ b/src/os/path_plan9.go
@@ -0,0 +1,15 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+const (
+ PathSeparator = '/' // OS-specific path separator
+ PathListSeparator = '\000' // OS-specific path list separator
+)
+
+// IsPathSeparator returns true if c is a directory separator character.
+func IsPathSeparator(c uint8) bool {
+ return PathSeparator == c
+}
diff --git a/src/os/path_test.go b/src/os/path_test.go
new file mode 100644
index 000000000..6f24a4313
--- /dev/null
+++ b/src/os/path_test.go
@@ -0,0 +1,220 @@
+// 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_test
+
+import (
+ "io/ioutil"
+ . "os"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "testing"
+)
+
+func TestMkdirAll(t *testing.T) {
+ tmpDir := TempDir()
+ path := tmpDir + "/_TestMkdirAll_/dir/./dir2"
+ err := MkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
+ defer RemoveAll(tmpDir + "/_TestMkdirAll_")
+
+ // Already exists, should succeed.
+ err = MkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q (second time): %s", path, err)
+ }
+
+ // Make file.
+ fpath := path + "/file"
+ f, err := Create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ defer f.Close()
+
+ // Can't make directory named after file.
+ err = MkdirAll(fpath, 0777)
+ if err == nil {
+ t.Fatalf("MkdirAll %q: no error", fpath)
+ }
+ perr, ok := err.(*PathError)
+ if !ok {
+ t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err)
+ }
+ if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
+ t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
+ }
+
+ // Can't make subdirectory of file.
+ ffpath := fpath + "/subdir"
+ err = MkdirAll(ffpath, 0777)
+ if err == nil {
+ t.Fatalf("MkdirAll %q: no error", ffpath)
+ }
+ perr, ok = err.(*PathError)
+ if !ok {
+ t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err)
+ }
+ if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
+ t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
+ }
+
+ if runtime.GOOS == "windows" {
+ path := tmpDir + `\_TestMkdirAll_\dir\.\dir2\`
+ err := MkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
+ }
+}
+
+func TestRemoveAll(t *testing.T) {
+ tmpDir := TempDir()
+ // Work directory.
+ path := tmpDir + "/_TestRemoveAll_"
+ fpath := path + "/file"
+ dpath := path + "/dir"
+
+ // Make directory with 1 file and remove.
+ if err := MkdirAll(path, 0777); err != nil {
+ t.Fatalf("MkdirAll %q: %s", path, err)
+ }
+ fd, err := Create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ fd.Close()
+ if err = RemoveAll(path); err != nil {
+ t.Fatalf("RemoveAll %q (first): %s", path, err)
+ }
+ if _, err = Lstat(path); err == nil {
+ t.Fatalf("Lstat %q succeeded after RemoveAll (first)", path)
+ }
+
+ // Make directory with file and subdirectory and remove.
+ if err = MkdirAll(dpath, 0777); err != nil {
+ t.Fatalf("MkdirAll %q: %s", dpath, err)
+ }
+ fd, err = Create(fpath)
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ fd.Close()
+ fd, err = Create(dpath + "/file")
+ if err != nil {
+ t.Fatalf("create %q: %s", fpath, err)
+ }
+ fd.Close()
+ if err = RemoveAll(path); err != nil {
+ t.Fatalf("RemoveAll %q (second): %s", path, err)
+ }
+ if _, err := Lstat(path); err == nil {
+ t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path)
+ }
+
+ // Determine if we should run the following test.
+ testit := true
+ if runtime.GOOS == "windows" {
+ // Chmod is not supported under windows.
+ testit = false
+ } else {
+ // Test fails as root.
+ testit = Getuid() != 0
+ }
+ if testit {
+ // Make directory with file and subdirectory and trigger error.
+ if err = MkdirAll(dpath, 0777); err != nil {
+ t.Fatalf("MkdirAll %q: %s", dpath, err)
+ }
+
+ for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} {
+ fd, err = Create(s)
+ if err != nil {
+ t.Fatalf("create %q: %s", s, err)
+ }
+ fd.Close()
+ }
+ if err = Chmod(dpath, 0); err != nil {
+ t.Fatalf("Chmod %q 0: %s", dpath, err)
+ }
+
+ // No error checking here: either RemoveAll
+ // will or won't be able to remove dpath;
+ // either way we want to see if it removes fpath
+ // and path/zzz. Reasons why RemoveAll might
+ // succeed in removing dpath as well include:
+ // * running as root
+ // * running on a file system without permissions (FAT)
+ RemoveAll(path)
+ Chmod(dpath, 0777)
+
+ for _, s := range []string{fpath, path + "/zzz"} {
+ if _, err = Lstat(s); err == nil {
+ t.Fatalf("Lstat %q succeeded after partial RemoveAll", s)
+ }
+ }
+ }
+ if err = RemoveAll(path); err != nil {
+ t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err)
+ }
+ if _, err = Lstat(path); err == nil {
+ t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path)
+ }
+}
+
+func TestMkdirAllWithSymlink(t *testing.T) {
+ switch runtime.GOOS {
+ case "nacl", "plan9":
+ t.Skipf("skipping on %s", runtime.GOOS)
+ case "windows":
+ if !supportsSymlinks {
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+ }
+
+ tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(tmpDir)
+
+ dir := tmpDir + "/dir"
+ err = Mkdir(dir, 0755)
+ if err != nil {
+ t.Fatalf("Mkdir %s: %s", dir, err)
+ }
+
+ link := tmpDir + "/link"
+ err = Symlink("dir", link)
+ if err != nil {
+ t.Fatalf("Symlink %s: %s", link, err)
+ }
+
+ path := link + "/foo"
+ err = MkdirAll(path, 0755)
+ if err != nil {
+ t.Errorf("MkdirAll %q: %s", path, err)
+ }
+}
+
+func TestMkdirAllAtSlash(t *testing.T) {
+ switch runtime.GOOS {
+ case "android", "plan9", "windows":
+ t.Skipf("skipping on %s", runtime.GOOS)
+ }
+ RemoveAll("/_go_os_test")
+ err := MkdirAll("/_go_os_test/dir", 0777)
+ if err != nil {
+ pathErr, ok := err.(*PathError)
+ // common for users not to be able to write to /
+ if ok && pathErr.Err == syscall.EACCES {
+ return
+ }
+ t.Fatalf(`MkdirAll "/_go_os_test/dir": %v`, err)
+ }
+ RemoveAll("/_go_os_test")
+}
diff --git a/src/os/path_unix.go b/src/os/path_unix.go
new file mode 100644
index 000000000..0211107dd
--- /dev/null
+++ b/src/os/path_unix.go
@@ -0,0 +1,17 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+
+package os
+
+const (
+ PathSeparator = '/' // OS-specific path separator
+ PathListSeparator = ':' // OS-specific path list separator
+)
+
+// IsPathSeparator returns true if c is a directory separator character.
+func IsPathSeparator(c uint8) bool {
+ return PathSeparator == c
+}
diff --git a/src/os/path_windows.go b/src/os/path_windows.go
new file mode 100644
index 000000000..61f2ca59f
--- /dev/null
+++ b/src/os/path_windows.go
@@ -0,0 +1,16 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+const (
+ PathSeparator = '\\' // OS-specific path separator
+ PathListSeparator = ';' // OS-specific path list separator
+)
+
+// IsPathSeparator returns true if c is a directory separator character.
+func IsPathSeparator(c uint8) bool {
+ // NOTE: Windows accept / as path separator.
+ return c == '\\' || c == '/'
+}
diff --git a/src/os/pipe_bsd.go b/src/os/pipe_bsd.go
new file mode 100644
index 000000000..3b81ed20f
--- /dev/null
+++ b/src/os/pipe_bsd.go
@@ -0,0 +1,28 @@
+// 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.
+
+// +build darwin dragonfly freebsd nacl netbsd openbsd solaris
+
+package os
+
+import "syscall"
+
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]int
+
+ // See ../syscall/exec.go for description of lock.
+ syscall.ForkLock.RLock()
+ e := syscall.Pipe(p[0:])
+ if e != nil {
+ syscall.ForkLock.RUnlock()
+ return nil, nil, NewSyscallError("pipe", e)
+ }
+ syscall.CloseOnExec(p[0])
+ syscall.CloseOnExec(p[1])
+ syscall.ForkLock.RUnlock()
+
+ return NewFile(uintptr(p[0]), "|0"), NewFile(uintptr(p[1]), "|1"), nil
+}
diff --git a/src/os/pipe_linux.go b/src/os/pipe_linux.go
new file mode 100644
index 000000000..9bafad84f
--- /dev/null
+++ b/src/os/pipe_linux.go
@@ -0,0 +1,33 @@
+// 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"
+
+// Pipe returns a connected pair of Files; reads from r return bytes written to w.
+// It returns the files and an error, if any.
+func Pipe() (r *File, w *File, err error) {
+ var p [2]int
+
+ e := syscall.Pipe2(p[0:], syscall.O_CLOEXEC)
+ // pipe2 was added in 2.6.27 and our minimum requirement is 2.6.23, so it
+ // might not be implemented.
+ if e == syscall.ENOSYS {
+ // See ../syscall/exec.go for description of lock.
+ syscall.ForkLock.RLock()
+ e = syscall.Pipe(p[0:])
+ if e != nil {
+ syscall.ForkLock.RUnlock()
+ return nil, nil, NewSyscallError("pipe", e)
+ }
+ syscall.CloseOnExec(p[0])
+ syscall.CloseOnExec(p[1])
+ syscall.ForkLock.RUnlock()
+ } else if e != nil {
+ return nil, nil, NewSyscallError("pipe2", e)
+ }
+
+ return NewFile(uintptr(p[0]), "|0"), NewFile(uintptr(p[1]), "|1"), nil
+}
diff --git a/src/os/proc.go b/src/os/proc.go
new file mode 100644
index 000000000..38c436ec5
--- /dev/null
+++ b/src/os/proc.go
@@ -0,0 +1,36 @@
+// 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.
+
+// Process etc.
+
+package os
+
+import "syscall"
+
+// Args hold the command-line arguments, starting with the program name.
+var Args []string
+
+// Getuid returns the numeric user id of the caller.
+func Getuid() int { return syscall.Getuid() }
+
+// Geteuid returns the numeric effective user id of the caller.
+func Geteuid() int { return syscall.Geteuid() }
+
+// Getgid returns the numeric group id of the caller.
+func Getgid() int { return syscall.Getgid() }
+
+// Getegid returns the numeric effective group id of the caller.
+func Getegid() int { return syscall.Getegid() }
+
+// Getgroups returns a list of the numeric ids of groups that the caller belongs to.
+func Getgroups() ([]int, error) {
+ gids, e := syscall.Getgroups()
+ return gids, NewSyscallError("getgroups", e)
+}
+
+// Exit causes the current program to exit with the given status code.
+// Conventionally, code zero indicates success, non-zero an error.
+// The program terminates immediately; deferred functions are
+// not run.
+func Exit(code int) { syscall.Exit(code) }
diff --git a/src/os/signal/example_test.go b/src/os/signal/example_test.go
new file mode 100644
index 000000000..079ee5070
--- /dev/null
+++ b/src/os/signal/example_test.go
@@ -0,0 +1,23 @@
+// 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 (
+ "fmt"
+ "os"
+ "os/signal"
+)
+
+func ExampleNotify() {
+ // Set up channel on which to send signal notifications.
+ // We must use a buffered channel or risk missing the signal
+ // if we're not ready to receive when the signal is sent.
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt, os.Kill)
+
+ // Block until a signal is received.
+ s := <-c
+ fmt.Println("Got signal:", s)
+}
diff --git a/src/os/signal/sig.s b/src/os/signal/sig.s
new file mode 100644
index 000000000..d54c284b5
--- /dev/null
+++ b/src/os/signal/sig.s
@@ -0,0 +1,23 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Assembly to get into package runtime without using exported symbols.
+
+// +build amd64 amd64p32 arm 386
+
+#include "textflag.h"
+
+#ifdef GOARCH_arm
+#define JMP B
+#endif
+
+TEXT ·signal_disable(SB),NOSPLIT,$0
+ JMP runtime·signal_disable(SB)
+
+TEXT ·signal_enable(SB),NOSPLIT,$0
+ JMP runtime·signal_enable(SB)
+
+TEXT ·signal_recv(SB),NOSPLIT,$0
+ JMP runtime·signal_recv(SB)
+
diff --git a/src/os/signal/signal.go b/src/os/signal/signal.go
new file mode 100644
index 000000000..300427549
--- /dev/null
+++ b/src/os/signal/signal.go
@@ -0,0 +1,131 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package signal implements access to incoming signals.
+package signal
+
+// BUG(rsc): This package is not yet implemented on Plan 9.
+
+import (
+ "os"
+ "sync"
+)
+
+var handlers struct {
+ sync.Mutex
+ m map[chan<- os.Signal]*handler
+ ref [numSig]int64
+}
+
+type handler struct {
+ mask [(numSig + 31) / 32]uint32
+}
+
+func (h *handler) want(sig int) bool {
+ return (h.mask[sig/32]>>uint(sig&31))&1 != 0
+}
+
+func (h *handler) set(sig int) {
+ h.mask[sig/32] |= 1 << uint(sig&31)
+}
+
+// Notify causes package signal to relay incoming signals to c.
+// If no signals are listed, all incoming signals will be relayed to c.
+// Otherwise, just the listed signals will.
+//
+// Package signal will not block sending to c: the caller must ensure
+// that c has sufficient buffer space to keep up with the expected
+// signal rate. For a channel used for notification of just one signal value,
+// a buffer of size 1 is sufficient.
+//
+// It is allowed to call Notify multiple times with the same channel:
+// each call expands the set of signals sent to that channel.
+// The only way to remove signals from the set is to call Stop.
+//
+// It is allowed to call Notify multiple times with different channels
+// and the same signals: each channel receives copies of incoming
+// signals independently.
+func Notify(c chan<- os.Signal, sig ...os.Signal) {
+ if c == nil {
+ panic("os/signal: Notify using nil channel")
+ }
+
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ h := handlers.m[c]
+ if h == nil {
+ if handlers.m == nil {
+ handlers.m = make(map[chan<- os.Signal]*handler)
+ }
+ h = new(handler)
+ handlers.m[c] = h
+ }
+
+ add := func(n int) {
+ if n < 0 {
+ return
+ }
+ if !h.want(n) {
+ h.set(n)
+ if handlers.ref[n] == 0 {
+ enableSignal(n)
+ }
+ handlers.ref[n]++
+ }
+ }
+
+ if len(sig) == 0 {
+ for n := 0; n < numSig; n++ {
+ add(n)
+ }
+ } else {
+ for _, s := range sig {
+ add(signum(s))
+ }
+ }
+}
+
+// Stop causes package signal to stop relaying incoming signals to c.
+// It undoes the effect of all prior calls to Notify using c.
+// When Stop returns, it is guaranteed that c will receive no more signals.
+func Stop(c chan<- os.Signal) {
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ h := handlers.m[c]
+ if h == nil {
+ return
+ }
+ delete(handlers.m, c)
+
+ for n := 0; n < numSig; n++ {
+ if h.want(n) {
+ handlers.ref[n]--
+ if handlers.ref[n] == 0 {
+ disableSignal(n)
+ }
+ }
+ }
+}
+
+func process(sig os.Signal) {
+ n := signum(sig)
+ if n < 0 {
+ return
+ }
+
+ handlers.Lock()
+ defer handlers.Unlock()
+
+ for c, h := range handlers.m {
+ if h.want(n) {
+ // send but do not block for it
+ select {
+ case c <- sig:
+ default:
+ }
+ }
+ }
+}
diff --git a/src/os/signal/signal_stub.go b/src/os/signal/signal_stub.go
new file mode 100644
index 000000000..d0a6935ff
--- /dev/null
+++ b/src/os/signal/signal_stub.go
@@ -0,0 +1,17 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build plan9
+
+package signal
+
+import "os"
+
+const numSig = 0
+
+func signum(sig os.Signal) int { return -1 }
+
+func disableSignal(int) {}
+
+func enableSignal(int) {}
diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go
new file mode 100644
index 000000000..076fe3f93
--- /dev/null
+++ b/src/os/signal/signal_test.go
@@ -0,0 +1,208 @@
+// 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.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd solaris
+
+package signal
+
+import (
+ "flag"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "runtime"
+ "strconv"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func waitSig(t *testing.T, c <-chan os.Signal, sig os.Signal) {
+ select {
+ case s := <-c:
+ if s != sig {
+ t.Fatalf("signal was %v, want %v", s, sig)
+ }
+ case <-time.After(1 * time.Second):
+ t.Fatalf("timeout waiting for %v", sig)
+ }
+}
+
+// Test that basic signal handling works.
+func TestSignal(t *testing.T) {
+ // Ask for SIGHUP
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGHUP)
+ defer Stop(c)
+
+ // Send this process a SIGHUP
+ t.Logf("sighup...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ waitSig(t, c, syscall.SIGHUP)
+
+ // Ask for everything we can get.
+ c1 := make(chan os.Signal, 1)
+ Notify(c1)
+
+ // Send this process a SIGWINCH
+ t.Logf("sigwinch...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGWINCH)
+ waitSig(t, c1, syscall.SIGWINCH)
+
+ // Send two more SIGHUPs, to make sure that
+ // they get delivered on c1 and that not reading
+ // from c does not block everything.
+ t.Logf("sighup...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ waitSig(t, c1, syscall.SIGHUP)
+ t.Logf("sighup...")
+ syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
+ waitSig(t, c1, syscall.SIGHUP)
+
+ // The first SIGHUP should be waiting for us on c.
+ waitSig(t, c, syscall.SIGHUP)
+}
+
+func TestStress(t *testing.T) {
+ dur := 3 * time.Second
+ if testing.Short() {
+ dur = 100 * time.Millisecond
+ }
+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
+ done := make(chan bool)
+ finished := make(chan bool)
+ go func() {
+ sig := make(chan os.Signal, 1)
+ Notify(sig, syscall.SIGUSR1)
+ defer Stop(sig)
+ Loop:
+ for {
+ select {
+ case <-sig:
+ case <-done:
+ break Loop
+ }
+ }
+ finished <- true
+ }()
+ go func() {
+ Loop:
+ for {
+ select {
+ case <-done:
+ break Loop
+ default:
+ syscall.Kill(syscall.Getpid(), syscall.SIGUSR1)
+ runtime.Gosched()
+ }
+ }
+ finished <- true
+ }()
+ time.Sleep(dur)
+ close(done)
+ <-finished
+ <-finished
+ // When run with 'go test -cpu=1,2,4' SIGUSR1 from this test can slip
+ // into subsequent TestSignal() causing failure.
+ // Sleep for a while to reduce the possibility of the failure.
+ time.Sleep(10 * time.Millisecond)
+}
+
+var sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop")
+
+// Test that Stop cancels the channel's registrations.
+func TestStop(t *testing.T) {
+ sigs := []syscall.Signal{
+ syscall.SIGWINCH,
+ syscall.SIGHUP,
+ }
+
+ for _, sig := range sigs {
+ // Send the signal.
+ // If it's SIGWINCH, we should not see it.
+ // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
+ if sig != syscall.SIGHUP || *sendUncaughtSighup == 1 {
+ syscall.Kill(syscall.Getpid(), sig)
+ }
+ time.Sleep(10 * time.Millisecond)
+
+ // Ask for signal
+ c := make(chan os.Signal, 1)
+ Notify(c, sig)
+ defer Stop(c)
+
+ // Send this process that signal
+ syscall.Kill(syscall.Getpid(), sig)
+ waitSig(t, c, sig)
+
+ Stop(c)
+ select {
+ case s := <-c:
+ t.Fatalf("unexpected signal %v", s)
+ case <-time.After(10 * time.Millisecond):
+ // nothing to read - good
+ }
+
+ // Send the signal.
+ // If it's SIGWINCH, we should not see it.
+ // If it's SIGHUP, maybe we'll die. Let the flag tell us what to do.
+ if sig != syscall.SIGHUP || *sendUncaughtSighup == 2 {
+ syscall.Kill(syscall.Getpid(), sig)
+ }
+
+ select {
+ case s := <-c:
+ t.Fatalf("unexpected signal %v", s)
+ case <-time.After(10 * time.Millisecond):
+ // nothing to read - good
+ }
+ }
+}
+
+// Test that when run under nohup, an uncaught SIGHUP does not kill the program,
+// but a
+func TestNohup(t *testing.T) {
+ // Ugly: ask for SIGHUP so that child will not have no-hup set
+ // even if test is running under nohup environment.
+ // We have no intention of reading from c.
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGHUP)
+
+ // When run without nohup, the test should crash on an uncaught SIGHUP.
+ // When run under nohup, the test should ignore uncaught SIGHUPs,
+ // because the runtime is not supposed to be listening for them.
+ // Either way, TestStop should still be able to catch them when it wants them
+ // and then when it stops wanting them, the original behavior should resume.
+ //
+ // send_uncaught_sighup=1 sends the SIGHUP before starting to listen for SIGHUPs.
+ // send_uncaught_sighup=2 sends the SIGHUP after no longer listening for SIGHUPs.
+ //
+ // Both should fail without nohup and succeed with nohup.
+
+ for i := 1; i <= 2; i++ {
+ out, err := exec.Command(os.Args[0], "-test.run=TestStop", "-send_uncaught_sighup="+strconv.Itoa(i)).CombinedOutput()
+ if err == nil {
+ t.Fatalf("ran test with -send_uncaught_sighup=%d and it succeeded: expected failure.\nOutput:\n%s", i, out)
+ }
+ }
+
+ Stop(c)
+
+ // Again, this time with nohup, assuming we can find it.
+ _, err := os.Stat("/usr/bin/nohup")
+ if err != nil {
+ t.Skip("cannot find nohup; skipping second half of test")
+ }
+
+ for i := 1; i <= 2; i++ {
+ os.Remove("nohup.out")
+ out, err := exec.Command("/usr/bin/nohup", os.Args[0], "-test.run=TestStop", "-send_uncaught_sighup="+strconv.Itoa(i)).CombinedOutput()
+
+ data, _ := ioutil.ReadFile("nohup.out")
+ os.Remove("nohup.out")
+ if err != nil {
+ t.Fatalf("ran test with -send_uncaught_sighup=%d under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", i, err, out, data)
+ }
+ }
+}
diff --git a/src/os/signal/signal_unix.go b/src/os/signal/signal_unix.go
new file mode 100644
index 000000000..94b8ab3dd
--- /dev/null
+++ b/src/os/signal/signal_unix.go
@@ -0,0 +1,53 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+
+package signal
+
+import (
+ "os"
+ "syscall"
+)
+
+// In assembly.
+func signal_disable(uint32)
+func signal_enable(uint32)
+func signal_recv() uint32
+
+func loop() {
+ for {
+ process(syscall.Signal(signal_recv()))
+ }
+}
+
+func init() {
+ signal_enable(0) // first call - initialize
+ go loop()
+}
+
+const (
+ numSig = 65 // max across all systems
+)
+
+func signum(sig os.Signal) int {
+ switch sig := sig.(type) {
+ case syscall.Signal:
+ i := int(sig)
+ if i < 0 || i >= numSig {
+ return -1
+ }
+ return i
+ default:
+ return -1
+ }
+}
+
+func enableSignal(sig int) {
+ signal_enable(uint32(sig))
+}
+
+func disableSignal(sig int) {
+ signal_disable(uint32(sig))
+}
diff --git a/src/os/signal/signal_windows_test.go b/src/os/signal/signal_windows_test.go
new file mode 100644
index 000000000..f3e6706b7
--- /dev/null
+++ b/src/os/signal/signal_windows_test.go
@@ -0,0 +1,103 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package signal
+
+import (
+ "bytes"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "syscall"
+ "testing"
+ "time"
+)
+
+func sendCtrlBreak(t *testing.T, pid int) {
+ d, e := syscall.LoadDLL("kernel32.dll")
+ if e != nil {
+ t.Fatalf("LoadDLL: %v\n", e)
+ }
+ p, e := d.FindProc("GenerateConsoleCtrlEvent")
+ if e != nil {
+ t.Fatalf("FindProc: %v\n", e)
+ }
+ r, _, e := p.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
+ if r == 0 {
+ t.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
+ }
+}
+
+func TestCtrlBreak(t *testing.T) {
+ // create source file
+ const source = `
+package main
+
+import (
+ "log"
+ "os"
+ "os/signal"
+ "time"
+)
+
+
+func main() {
+ c := make(chan os.Signal, 10)
+ signal.Notify(c)
+ select {
+ case s := <-c:
+ if s != os.Interrupt {
+ log.Fatalf("Wrong signal received: got %q, want %q\n", s, os.Interrupt)
+ }
+ case <-time.After(3 * time.Second):
+ log.Fatalf("Timeout waiting for Ctrl+Break\n")
+ }
+}
+`
+ 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"
+ f, err := os.Create(src)
+ if err != nil {
+ t.Fatalf("Failed to create %v: %v", src, err)
+ }
+ defer f.Close()
+ f.Write([]byte(source))
+
+ // compile it
+ exe := name + ".exe"
+ defer os.Remove(exe)
+ o, err := exec.Command("go", "build", "-o", exe, src).CombinedOutput()
+ if err != nil {
+ t.Fatalf("Failed to compile: %v\n%v", err, string(o))
+ }
+
+ // run it
+ cmd := exec.Command(exe)
+ var b bytes.Buffer
+ cmd.Stdout = &b
+ cmd.Stderr = &b
+ cmd.SysProcAttr = &syscall.SysProcAttr{
+ CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
+ }
+ err = cmd.Start()
+ if err != nil {
+ t.Fatalf("Start failed: %v", err)
+ }
+ go func() {
+ time.Sleep(1 * time.Second)
+ sendCtrlBreak(t, cmd.Process.Pid)
+ }()
+ err = cmd.Wait()
+ if err != nil {
+ t.Fatalf("Program exited with error: %v\n%v", err, string(b.Bytes()))
+ }
+}
diff --git a/src/os/stat_darwin.go b/src/os/stat_darwin.go
new file mode 100644
index 000000000..0eea52201
--- /dev/null
+++ b/src/os/stat_darwin.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.Mtimespec),
+ sys: st,
+ }
+ fs.mode = FileMode(st.Mode & 0777)
+ switch st.Mode & syscall.S_IFMT {
+ case syscall.S_IFBLK, syscall.S_IFWHT:
+ 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).Atimespec)
+}
diff --git a/src/os/stat_dragonfly.go b/src/os/stat_dragonfly.go
new file mode 100644
index 000000000..605c1d9b6
--- /dev/null
+++ b/src/os/stat_dragonfly.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/os/stat_freebsd.go b/src/os/stat_freebsd.go
new file mode 100644
index 000000000..2ffb60fe2
--- /dev/null
+++ b/src/os/stat_freebsd.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.Mtimespec),
+ 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).Atimespec)
+}
diff --git a/src/os/stat_linux.go b/src/os/stat_linux.go
new file mode 100644
index 000000000..605c1d9b6
--- /dev/null
+++ b/src/os/stat_linux.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/os/stat_nacl.go b/src/os/stat_nacl.go
new file mode 100644
index 000000000..a503b59fa
--- /dev/null
+++ b/src/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/os/stat_netbsd.go b/src/os/stat_netbsd.go
new file mode 100644
index 000000000..2ffb60fe2
--- /dev/null
+++ b/src/os/stat_netbsd.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.Mtimespec),
+ 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).Atimespec)
+}
diff --git a/src/os/stat_openbsd.go b/src/os/stat_openbsd.go
new file mode 100644
index 000000000..605c1d9b6
--- /dev/null
+++ b/src/os/stat_openbsd.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/os/stat_plan9.go b/src/os/stat_plan9.go
new file mode 100644
index 000000000..25c9a8c14
--- /dev/null
+++ b/src/os/stat_plan9.go
@@ -0,0 +1,110 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+ "syscall"
+ "time"
+)
+
+func sameFile(fs1, fs2 *fileStat) bool {
+ a := fs1.sys.(*syscall.Dir)
+ b := fs2.sys.(*syscall.Dir)
+ return a.Qid.Path == b.Qid.Path && a.Type == b.Type && a.Dev == b.Dev
+}
+
+func fileInfoFromStat(d *syscall.Dir) FileInfo {
+ fs := &fileStat{
+ name: d.Name,
+ size: int64(d.Length),
+ modTime: time.Unix(int64(d.Mtime), 0),
+ sys: d,
+ }
+ fs.mode = FileMode(d.Mode & 0777)
+ if d.Mode&syscall.DMDIR != 0 {
+ fs.mode |= ModeDir
+ }
+ if d.Mode&syscall.DMAPPEND != 0 {
+ fs.mode |= ModeAppend
+ }
+ if d.Mode&syscall.DMEXCL != 0 {
+ fs.mode |= ModeExclusive
+ }
+ if d.Mode&syscall.DMTMP != 0 {
+ fs.mode |= ModeTemporary
+ }
+ return fs
+}
+
+// arg is an open *File or a path string.
+func dirstat(arg interface{}) (*syscall.Dir, error) {
+ var name string
+
+ // This is big enough for most stat messages
+ // and rounded to a multiple of 128 bytes.
+ size := (syscall.STATFIXLEN + 16*4 + 128) &^ 128
+
+ for i := 0; i < 2; i++ {
+ buf := make([]byte, size)
+
+ var n int
+ var err error
+ switch a := arg.(type) {
+ case *File:
+ name = a.name
+ n, err = syscall.Fstat(a.fd, buf)
+ case string:
+ name = a
+ n, err = syscall.Stat(a, buf)
+ default:
+ panic("phase error in dirstat")
+ }
+ if err != nil {
+ return nil, &PathError{"stat", name, err}
+ }
+ if n < syscall.STATFIXLEN {
+ return nil, &PathError{"stat", name, syscall.ErrShortStat}
+ }
+
+ // Pull the real size out of the stat message.
+ size = int(uint16(buf[0]) | uint16(buf[1])<<8)
+
+ // If the stat message is larger than our buffer we will
+ // go around the loop and allocate one that is big enough.
+ if size > n {
+ continue
+ }
+
+ d, err := syscall.UnmarshalDir(buf[:n])
+ if err != nil {
+ return nil, &PathError{"stat", name, err}
+ }
+ return d, nil
+ }
+ return nil, &PathError{"stat", name, syscall.ErrBadStat}
+}
+
+// Stat returns a FileInfo describing the named file.
+// If there is an error, it will be of type *PathError.
+func Stat(name string) (fi FileInfo, err error) {
+ d, err := dirstat(name)
+ if err != nil {
+ return nil, err
+ }
+ return fileInfoFromStat(d), nil
+}
+
+// Lstat returns a FileInfo describing the named file.
+// If the file is a symbolic link, the returned FileInfo
+// describes the symbolic link. Lstat makes no attempt to follow the link.
+// If there is an error, it will be of type *PathError.
+func Lstat(name string) (fi FileInfo, err error) {
+ return Stat(name)
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(int64(fi.Sys().(*syscall.Dir).Atime), 0)
+}
diff --git a/src/os/stat_solaris.go b/src/os/stat_solaris.go
new file mode 100644
index 000000000..605c1d9b6
--- /dev/null
+++ b/src/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/os/stat_windows.go b/src/os/stat_windows.go
new file mode 100644
index 000000000..f396c1db3
--- /dev/null
+++ b/src/os/stat_windows.go
@@ -0,0 +1,170 @@
+// 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"
+ "unsafe"
+)
+
+// Stat returns the FileInfo structure describing file.
+// If there is an error, it will be of type *PathError.
+func (file *File) Stat() (fi FileInfo, err error) {
+ if file == nil {
+ return nil, ErrInvalid
+ }
+ if file == nil || file.fd < 0 {
+ return nil, syscall.EINVAL
+ }
+ if file.isdir() {
+ // I don't know any better way to do that for directory
+ return Stat(file.name)
+ }
+ if file.name == DevNull {
+ return &devNullStat, nil
+ }
+ var d syscall.ByHandleFileInformation
+ e := syscall.GetFileInformationByHandle(syscall.Handle(file.fd), &d)
+ if e != nil {
+ return nil, &PathError{"GetFileInformationByHandle", file.name, e}
+ }
+ return &fileStat{
+ name: basename(file.name),
+ sys: syscall.Win32FileAttributeData{
+ FileAttributes: d.FileAttributes,
+ CreationTime: d.CreationTime,
+ LastAccessTime: d.LastAccessTime,
+ LastWriteTime: d.LastWriteTime,
+ FileSizeHigh: d.FileSizeHigh,
+ FileSizeLow: d.FileSizeLow,
+ },
+ vol: d.VolumeSerialNumber,
+ idxhi: d.FileIndexHigh,
+ idxlo: d.FileIndexLow,
+ }, nil
+}
+
+// Stat returns a FileInfo structure describing the named file.
+// If there is an error, it will be of type *PathError.
+func Stat(name string) (fi FileInfo, err error) {
+ for {
+ fi, err = Lstat(name)
+ if err != nil {
+ return
+ }
+ if fi.Mode()&ModeSymlink == 0 {
+ return
+ }
+ name, err = Readlink(name)
+ if err != nil {
+ return
+ }
+ }
+ return fi, err
+}
+
+// Lstat returns the FileInfo structure describing the named file.
+// If the file is a symbolic link, the returned FileInfo
+// describes the symbolic link. Lstat makes no attempt to follow the link.
+// If there is an error, it will be of type *PathError.
+func Lstat(name string) (fi FileInfo, err error) {
+ if len(name) == 0 {
+ return nil, &PathError{"Lstat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
+ }
+ if name == DevNull {
+ return &devNullStat, nil
+ }
+ fs := &fileStat{name: basename(name)}
+ namep, e := syscall.UTF16PtrFromString(name)
+ if e != nil {
+ return nil, &PathError{"Lstat", name, e}
+ }
+ e = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fs.sys)))
+ if e != nil {
+ return nil, &PathError{"GetFileAttributesEx", name, e}
+ }
+ fs.path = name
+ if !isAbs(fs.path) {
+ fs.path, e = syscall.FullPath(fs.path)
+ if e != nil {
+ return nil, e
+ }
+ }
+ return fs, nil
+}
+
+// basename removes trailing slashes and the leading
+// directory name and drive letter from path name.
+func basename(name string) string {
+ // Remove drive letter
+ if len(name) == 2 && name[1] == ':' {
+ name = "."
+ } else if len(name) > 2 && name[1] == ':' {
+ name = name[2:]
+ }
+ i := len(name) - 1
+ // Remove trailing slashes
+ for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i-- {
+ name = name[:i]
+ }
+ // Remove leading directory name
+ for i--; i >= 0; i-- {
+ if name[i] == '/' || name[i] == '\\' {
+ name = name[i+1:]
+ break
+ }
+ }
+ return name
+}
+
+func isAbs(path string) (b bool) {
+ v := volumeName(path)
+ if v == "" {
+ return false
+ }
+ path = path[len(v):]
+ if path == "" {
+ return false
+ }
+ return IsPathSeparator(path[0])
+}
+
+func volumeName(path string) (v string) {
+ if len(path) < 2 {
+ return ""
+ }
+ // with drive letter
+ c := path[0]
+ if path[1] == ':' &&
+ ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
+ 'A' <= c && c <= 'Z') {
+ return path[:2]
+ }
+ // is it UNC
+ if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) &&
+ !IsPathSeparator(path[2]) && path[2] != '.' {
+ // first, leading `\\` and next shouldn't be `\`. its server name.
+ for n := 3; n < l-1; n++ {
+ // second, next '\' shouldn't be repeated.
+ if IsPathSeparator(path[n]) {
+ n++
+ // third, following something characters. its share name.
+ if !IsPathSeparator(path[n]) {
+ if path[n] == '.' {
+ break
+ }
+ for ; n < l; n++ {
+ if IsPathSeparator(path[n]) {
+ break
+ }
+ }
+ return path[:n]
+ }
+ break
+ }
+ }
+ }
+ return ""
+}
diff --git a/src/os/str.go b/src/os/str.go
new file mode 100644
index 000000000..e3606b61e
--- /dev/null
+++ b/src/os/str.go
@@ -0,0 +1,22 @@
+// 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.
+
+// +build plan9
+
+package os
+
+func itoa(val int) string { // do it here rather than with fmt to avoid dependency
+ if val < 0 {
+ return "-" + itoa(-val)
+ }
+ var buf [32]byte // big enough for int64
+ i := len(buf) - 1
+ for val >= 10 {
+ buf[i] = byte(val%10 + '0')
+ i--
+ val /= 10
+ }
+ buf[i] = byte(val + '0')
+ return string(buf[i:])
+}
diff --git a/src/os/sys_bsd.go b/src/os/sys_bsd.go
new file mode 100644
index 000000000..8ad5e2183
--- /dev/null
+++ b/src/os/sys_bsd.go
@@ -0,0 +1,20 @@
+// 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.
+
+// +build darwin dragonfly freebsd nacl netbsd openbsd
+
+// os code shared between *BSD systems including OS X (Darwin)
+// and FreeBSD.
+
+package os
+
+import "syscall"
+
+func hostname() (name string, err error) {
+ name, err = syscall.Sysctl("kern.hostname")
+ if err != nil {
+ return "", NewSyscallError("sysctl kern.hostname", err)
+ }
+ return name, nil
+}
diff --git a/src/os/sys_darwin.go b/src/os/sys_darwin.go
new file mode 100644
index 000000000..7a8330abb
--- /dev/null
+++ b/src/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/os/sys_freebsd.go b/src/os/sys_freebsd.go
new file mode 100644
index 000000000..273c2df1c
--- /dev/null
+++ b/src/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/os/sys_linux.go b/src/os/sys_linux.go
new file mode 100644
index 000000000..76cdf5043
--- /dev/null
+++ b/src/os/sys_linux.go
@@ -0,0 +1,26 @@
+// 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.
+
+// Linux-specific
+
+package os
+
+func hostname() (name string, err error) {
+ f, err := Open("/proc/sys/kernel/hostname")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ var buf [512]byte // Enough for a DNS name.
+ n, err := f.Read(buf[0:])
+ if err != nil {
+ return "", err
+ }
+
+ if n > 0 && buf[n-1] == '\n' {
+ n--
+ }
+ return string(buf[0:n]), nil
+}
diff --git a/src/os/sys_nacl.go b/src/os/sys_nacl.go
new file mode 100644
index 000000000..07907c847
--- /dev/null
+++ b/src/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/os/sys_plan9.go b/src/os/sys_plan9.go
new file mode 100644
index 000000000..07a7905f4
--- /dev/null
+++ b/src/os/sys_plan9.go
@@ -0,0 +1,26 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Plan 9-specific
+
+package os
+
+func hostname() (name string, err error) {
+ f, err := Open("#c/sysname")
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+
+ var buf [128]byte
+ n, err := f.Read(buf[:len(buf)-1])
+
+ if err != nil {
+ return "", err
+ }
+ if n > 0 {
+ buf[n] = 0
+ }
+ return string(buf[0:n]), nil
+}
diff --git a/src/os/sys_solaris.go b/src/os/sys_solaris.go
new file mode 100644
index 000000000..917e8f2b0
--- /dev/null
+++ b/src/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/os/sys_unix.go b/src/os/sys_unix.go
new file mode 100644
index 000000000..39c20dc73
--- /dev/null
+++ b/src/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/os/sys_windows.go b/src/os/sys_windows.go
new file mode 100644
index 000000000..92617de5e
--- /dev/null
+++ b/src/os/sys_windows.go
@@ -0,0 +1,15 @@
+// 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"
+
+func hostname() (name string, err error) {
+ s, e := syscall.ComputerName()
+ if e != nil {
+ return "", NewSyscallError("ComputerName", e)
+ }
+ return s, nil
+}
diff --git a/src/os/types.go b/src/os/types.go
new file mode 100644
index 000000000..473d431d4
--- /dev/null
+++ b/src/os/types.go
@@ -0,0 +1,118 @@
+// 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"
+)
+
+// Getpagesize returns the underlying system's memory page size.
+func Getpagesize() int { return syscall.Getpagesize() }
+
+// A FileInfo describes a file and is returned by Stat and Lstat.
+type FileInfo interface {
+ Name() string // base name of the file
+ Size() int64 // length in bytes for regular files; system-dependent for others
+ Mode() FileMode // file mode bits
+ ModTime() time.Time // modification time
+ IsDir() bool // abbreviation for Mode().IsDir()
+ Sys() interface{} // underlying data source (can return nil)
+}
+
+// A FileMode represents a file's mode and permission bits.
+// The bits have the same definition on all systems, so that
+// information about files can be moved from one system
+// to another portably. Not all bits apply to all systems.
+// The only required bit is ModeDir for directories.
+type FileMode uint32
+
+// The defined file mode bits are the most significant bits of the FileMode.
+// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
+// The values of these bits should be considered part of the public API and
+// may be used in wire protocols or disk representations: they must not be
+// changed, although new bits might be added.
+const (
+ // The single letters are the abbreviations
+ // used by the String method's formatting.
+ ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory
+ ModeAppend // a: append-only
+ ModeExclusive // l: exclusive use
+ ModeTemporary // T: temporary file (not backed up)
+ ModeSymlink // L: symbolic link
+ ModeDevice // D: device file
+ ModeNamedPipe // p: named pipe (FIFO)
+ ModeSocket // S: Unix domain socket
+ ModeSetuid // u: setuid
+ ModeSetgid // g: setgid
+ ModeCharDevice // c: Unix character device, when ModeDevice is set
+ ModeSticky // t: sticky
+
+ // Mask for the type bits. For regular files, none will be set.
+ ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
+
+ ModePerm FileMode = 0777 // permission bits
+)
+
+func (m FileMode) String() string {
+ const str = "dalTLDpSugct"
+ var buf [32]byte // Mode is uint32.
+ w := 0
+ for i, c := range str {
+ if m&(1<<uint(32-1-i)) != 0 {
+ buf[w] = byte(c)
+ w++
+ }
+ }
+ if w == 0 {
+ buf[w] = '-'
+ w++
+ }
+ const rwx = "rwxrwxrwx"
+ for i, c := range rwx {
+ if m&(1<<uint(9-1-i)) != 0 {
+ buf[w] = byte(c)
+ } else {
+ buf[w] = '-'
+ }
+ w++
+ }
+ return string(buf[:w])
+}
+
+// IsDir reports whether m describes a directory.
+// That is, it tests for the ModeDir bit being set in m.
+func (m FileMode) IsDir() bool {
+ return m&ModeDir != 0
+}
+
+// IsRegular reports whether m describes a regular file.
+// That is, it tests that no mode type bits are set.
+func (m FileMode) IsRegular() bool {
+ return m&ModeType == 0
+}
+
+// Perm returns the Unix permission bits in m.
+func (m FileMode) Perm() FileMode {
+ return m & ModePerm
+}
+
+func (fs *fileStat) Name() string { return fs.name }
+func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
+
+// SameFile reports whether fi1 and fi2 describe the same file.
+// For example, on Unix this means that the device and inode fields
+// of the two underlying structures are identical; on other systems
+// the decision may be based on the path names.
+// SameFile only applies to results returned by this package's Stat.
+// It returns false in other cases.
+func SameFile(fi1, fi2 FileInfo) bool {
+ fs1, ok1 := fi1.(*fileStat)
+ fs2, ok2 := fi2.(*fileStat)
+ if !ok1 || !ok2 {
+ return false
+ }
+ return sameFile(fs1, fs2)
+}
diff --git a/src/os/types_notwin.go b/src/os/types_notwin.go
new file mode 100644
index 000000000..ea1a07393
--- /dev/null
+++ b/src/os/types_notwin.go
@@ -0,0 +1,25 @@
+// 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.
+
+// +build !windows
+
+package os
+
+import (
+ "time"
+)
+
+// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
+type fileStat struct {
+ name string
+ size int64
+ mode FileMode
+ modTime time.Time
+ sys interface{}
+}
+
+func (fs *fileStat) Size() int64 { return fs.size }
+func (fs *fileStat) Mode() FileMode { return fs.mode }
+func (fs *fileStat) ModTime() time.Time { return fs.modTime }
+func (fs *fileStat) Sys() interface{} { return fs.sys }
diff --git a/src/os/types_windows.go b/src/os/types_windows.go
new file mode 100644
index 000000000..7b2e54698
--- /dev/null
+++ b/src/os/types_windows.go
@@ -0,0 +1,107 @@
+// 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 (
+ "sync"
+ "syscall"
+ "time"
+)
+
+// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
+type fileStat struct {
+ name string
+ sys syscall.Win32FileAttributeData
+
+ // used to implement SameFile
+ sync.Mutex
+ path string
+ vol uint32
+ idxhi uint32
+ idxlo uint32
+}
+
+func (fs *fileStat) Size() int64 {
+ return int64(fs.sys.FileSizeHigh)<<32 + int64(fs.sys.FileSizeLow)
+}
+
+func (fs *fileStat) Mode() (m FileMode) {
+ if fs == &devNullStat {
+ return ModeDevice | ModeCharDevice | 0666
+ }
+ if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ m |= ModeDir | 0111
+ }
+ if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
+ m |= 0444
+ } else {
+ m |= 0666
+ }
+ if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
+ m |= ModeSymlink
+ }
+ return m
+}
+
+func (fs *fileStat) ModTime() time.Time {
+ return time.Unix(0, fs.sys.LastWriteTime.Nanoseconds())
+}
+
+// Sys returns syscall.Win32FileAttributeData for file fs.
+func (fs *fileStat) Sys() interface{} { return &fs.sys }
+
+func (fs *fileStat) loadFileId() error {
+ fs.Lock()
+ defer fs.Unlock()
+ if fs.path == "" {
+ // already done
+ return nil
+ }
+ pathp, err := syscall.UTF16PtrFromString(fs.path)
+ if err != nil {
+ return err
+ }
+ h, err := syscall.CreateFile(pathp, 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
+ if err != nil {
+ return err
+ }
+ defer syscall.CloseHandle(h)
+ var i syscall.ByHandleFileInformation
+ err = syscall.GetFileInformationByHandle(syscall.Handle(h), &i)
+ if err != nil {
+ return err
+ }
+ fs.path = ""
+ fs.vol = i.VolumeSerialNumber
+ fs.idxhi = i.FileIndexHigh
+ fs.idxlo = i.FileIndexLow
+ return nil
+}
+
+// devNullStat is fileStat structure describing DevNull file ("NUL").
+var devNullStat = fileStat{
+ name: DevNull,
+ // hopefully this will work for SameFile
+ vol: 0,
+ idxhi: 0,
+ idxlo: 0,
+}
+
+func sameFile(fs1, fs2 *fileStat) bool {
+ e := fs1.loadFileId()
+ if e != nil {
+ return false
+ }
+ e = fs2.loadFileId()
+ if e != nil {
+ return false
+ }
+ return fs1.vol == fs2.vol && fs1.idxhi == fs2.idxhi && fs1.idxlo == fs2.idxlo
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(0, fi.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
+}
diff --git a/src/os/user/lookup.go b/src/os/user/lookup.go
new file mode 100644
index 000000000..09f00c7bd
--- /dev/null
+++ b/src/os/user/lookup.go
@@ -0,0 +1,22 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package user
+
+// Current returns the current user.
+func Current() (*User, error) {
+ return current()
+}
+
+// Lookup looks up a user by username. If the user cannot be found, the
+// returned error is of type UnknownUserError.
+func Lookup(username string) (*User, error) {
+ return lookup(username)
+}
+
+// LookupId looks up a user by userid. If the user cannot be found, the
+// returned error is of type UnknownUserIdError.
+func LookupId(uid string) (*User, error) {
+ return lookupId(uid)
+}
diff --git a/src/os/user/lookup_plan9.go b/src/os/user/lookup_plan9.go
new file mode 100644
index 000000000..f7ef3482b
--- /dev/null
+++ b/src/os/user/lookup_plan9.go
@@ -0,0 +1,46 @@
+// 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 user
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "syscall"
+)
+
+// Partial os/user support on Plan 9.
+// Supports Current(), but not Lookup()/LookupId().
+// The latter two would require parsing /adm/users.
+const (
+ userFile = "/dev/user"
+)
+
+func current() (*User, error) {
+ ubytes, err := ioutil.ReadFile(userFile)
+ if err != nil {
+ return nil, fmt.Errorf("user: %s", err)
+ }
+
+ uname := string(ubytes)
+
+ u := &User{
+ Uid: uname,
+ Gid: uname,
+ Username: uname,
+ Name: uname,
+ HomeDir: os.Getenv("home"),
+ }
+
+ return u, nil
+}
+
+func lookup(username string) (*User, error) {
+ return nil, syscall.EPLAN9
+}
+
+func lookupId(uid string) (*User, error) {
+ return nil, syscall.EPLAN9
+}
diff --git a/src/os/user/lookup_stubs.go b/src/os/user/lookup_stubs.go
new file mode 100644
index 000000000..4fb0e3c6e
--- /dev/null
+++ b/src/os/user/lookup_stubs.go
@@ -0,0 +1,28 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !cgo,!windows,!plan9 android
+
+package user
+
+import (
+ "fmt"
+ "runtime"
+)
+
+func init() {
+ implemented = false
+}
+
+func current() (*User, error) {
+ return nil, fmt.Errorf("user: Current not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func lookup(username string) (*User, error) {
+ return nil, fmt.Errorf("user: Lookup not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
+
+func lookupId(uid string) (*User, error) {
+ return nil, fmt.Errorf("user: LookupId not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
diff --git a/src/os/user/lookup_unix.go b/src/os/user/lookup_unix.go
new file mode 100644
index 000000000..0871473df
--- /dev/null
+++ b/src/os/user/lookup_unix.go
@@ -0,0 +1,112 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
+// +build cgo
+
+package user
+
+import (
+ "fmt"
+ "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+ "unsafe"
+)
+
+/*
+#include <unistd.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <stdlib.h>
+
+static int mygetpwuid_r(int uid, struct passwd *pwd,
+ char *buf, size_t buflen, struct passwd **result) {
+ return getpwuid_r(uid, pwd, buf, buflen, result);
+}
+*/
+import "C"
+
+func current() (*User, error) {
+ return lookupUnix(syscall.Getuid(), "", false)
+}
+
+func lookup(username string) (*User, error) {
+ return lookupUnix(-1, username, true)
+}
+
+func lookupId(uid string) (*User, error) {
+ i, e := strconv.Atoi(uid)
+ if e != nil {
+ return nil, e
+ }
+ return lookupUnix(i, "", false)
+}
+
+func lookupUnix(uid int, username string, lookupByName bool) (*User, error) {
+ var pwd C.struct_passwd
+ var result *C.struct_passwd
+
+ var bufSize C.long
+ if runtime.GOOS == "dragonfly" || runtime.GOOS == "freebsd" {
+ // DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX
+ // and just return -1. So just use the same
+ // size that Linux returns.
+ bufSize = 1024
+ } else {
+ bufSize = C.sysconf(C._SC_GETPW_R_SIZE_MAX)
+ if bufSize <= 0 || bufSize > 1<<20 {
+ return nil, fmt.Errorf("user: unreasonable _SC_GETPW_R_SIZE_MAX of %d", bufSize)
+ }
+ }
+ buf := C.malloc(C.size_t(bufSize))
+ defer C.free(buf)
+ var rv C.int
+ if lookupByName {
+ nameC := C.CString(username)
+ defer C.free(unsafe.Pointer(nameC))
+ rv = C.getpwnam_r(nameC,
+ &pwd,
+ (*C.char)(buf),
+ C.size_t(bufSize),
+ &result)
+ if rv != 0 {
+ return nil, fmt.Errorf("user: lookup username %s: %s", username, syscall.Errno(rv))
+ }
+ if result == nil {
+ return nil, UnknownUserError(username)
+ }
+ } else {
+ // mygetpwuid_r is a wrapper around getpwuid_r to
+ // to avoid using uid_t because C.uid_t(uid) for
+ // unknown reasons doesn't work on linux.
+ rv = C.mygetpwuid_r(C.int(uid),
+ &pwd,
+ (*C.char)(buf),
+ C.size_t(bufSize),
+ &result)
+ if rv != 0 {
+ return nil, fmt.Errorf("user: lookup userid %d: %s", uid, syscall.Errno(rv))
+ }
+ if result == nil {
+ return nil, UnknownUserIdError(uid)
+ }
+ }
+ u := &User{
+ Uid: strconv.Itoa(int(pwd.pw_uid)),
+ Gid: strconv.Itoa(int(pwd.pw_gid)),
+ Username: C.GoString(pwd.pw_name),
+ Name: C.GoString(pwd.pw_gecos),
+ HomeDir: C.GoString(pwd.pw_dir),
+ }
+ // The pw_gecos field isn't quite standardized. Some docs
+ // say: "It is expected to be a comma separated list of
+ // personal data where the first item is the full name of the
+ // user."
+ if i := strings.Index(u.Name, ","); i >= 0 {
+ u.Name = u.Name[:i]
+ }
+ return u, nil
+}
diff --git a/src/os/user/lookup_windows.go b/src/os/user/lookup_windows.go
new file mode 100644
index 000000000..99c325ff0
--- /dev/null
+++ b/src/os/user/lookup_windows.go
@@ -0,0 +1,149 @@
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+func isDomainJoined() (bool, error) {
+ var domain *uint16
+ var status uint32
+ err := syscall.NetGetJoinInformation(nil, &domain, &status)
+ if err != nil {
+ return false, err
+ }
+ syscall.NetApiBufferFree((*byte)(unsafe.Pointer(domain)))
+ return status == syscall.NetSetupDomainName, nil
+}
+
+func lookupFullNameDomain(domainAndUser string) (string, error) {
+ return syscall.TranslateAccountName(domainAndUser,
+ syscall.NameSamCompatible, syscall.NameDisplay, 50)
+}
+
+func lookupFullNameServer(servername, username string) (string, error) {
+ s, e := syscall.UTF16PtrFromString(servername)
+ if e != nil {
+ return "", e
+ }
+ u, e := syscall.UTF16PtrFromString(username)
+ if e != nil {
+ return "", e
+ }
+ var p *byte
+ e = syscall.NetUserGetInfo(s, u, 10, &p)
+ if e != nil {
+ return "", e
+ }
+ defer syscall.NetApiBufferFree(p)
+ i := (*syscall.UserInfo10)(unsafe.Pointer(p))
+ if i.FullName == nil {
+ return "", nil
+ }
+ name := syscall.UTF16ToString((*[1024]uint16)(unsafe.Pointer(i.FullName))[:])
+ return name, nil
+}
+
+func lookupFullName(domain, username, domainAndUser string) (string, error) {
+ joined, err := isDomainJoined()
+ if err == nil && joined {
+ name, err := lookupFullNameDomain(domainAndUser)
+ if err == nil {
+ return name, nil
+ }
+ }
+ name, err := lookupFullNameServer(domain, username)
+ if err == nil {
+ return name, nil
+ }
+ // domain worked neigher as a domain nor as a server
+ // could be domain server unavailable
+ // pretend username is fullname
+ return username, nil
+}
+
+func newUser(usid *syscall.SID, gid, dir string) (*User, error) {
+ username, domain, t, e := usid.LookupAccount("")
+ if e != nil {
+ return nil, e
+ }
+ if t != syscall.SidTypeUser {
+ return nil, fmt.Errorf("user: should be user account type, not %d", t)
+ }
+ domainAndUser := domain + `\` + username
+ uid, e := usid.String()
+ if e != nil {
+ return nil, e
+ }
+ name, e := lookupFullName(domain, username, domainAndUser)
+ if e != nil {
+ return nil, e
+ }
+ u := &User{
+ Uid: uid,
+ Gid: gid,
+ Username: domainAndUser,
+ Name: name,
+ HomeDir: dir,
+ }
+ return u, nil
+}
+
+func current() (*User, error) {
+ t, e := syscall.OpenCurrentProcessToken()
+ if e != nil {
+ return nil, e
+ }
+ defer t.Close()
+ u, e := t.GetTokenUser()
+ if e != nil {
+ return nil, e
+ }
+ pg, e := t.GetTokenPrimaryGroup()
+ if e != nil {
+ return nil, e
+ }
+ gid, e := pg.PrimaryGroup.String()
+ if e != nil {
+ return nil, e
+ }
+ dir, e := t.GetUserProfileDirectory()
+ if e != nil {
+ return nil, e
+ }
+ return newUser(u.User.Sid, gid, dir)
+}
+
+// BUG(brainman): Lookup and LookupId functions do not set
+// Gid and HomeDir fields in the User struct returned on windows.
+
+func newUserFromSid(usid *syscall.SID) (*User, error) {
+ // TODO(brainman): do not know where to get gid and dir fields
+ gid := "unknown"
+ dir := "Unknown directory"
+ return newUser(usid, gid, dir)
+}
+
+func lookup(username string) (*User, error) {
+ sid, _, t, e := syscall.LookupSID("", username)
+ if e != nil {
+ return nil, e
+ }
+ if t != syscall.SidTypeUser {
+ return nil, fmt.Errorf("user: should be user account type, not %d", t)
+ }
+ return newUserFromSid(sid)
+}
+
+func lookupId(uid string) (*User, error) {
+ sid, e := syscall.StringToSid(uid)
+ if e != nil {
+ return nil, e
+ }
+ return newUserFromSid(sid)
+}
diff --git a/src/os/user/user.go b/src/os/user/user.go
new file mode 100644
index 000000000..e8680fe54
--- /dev/null
+++ b/src/os/user/user.go
@@ -0,0 +1,43 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package user allows user account lookups by name or id.
+package user
+
+import (
+ "strconv"
+)
+
+var implemented = true // set to false by lookup_stubs.go's init
+
+// User represents a user account.
+//
+// On posix systems Uid and Gid contain a decimal number
+// representing uid and gid. On windows Uid and Gid
+// contain security identifier (SID) in a string format.
+// On Plan 9, Uid, Gid, Username, and Name will be the
+// contents of /dev/user.
+type User struct {
+ Uid string // user id
+ Gid string // primary group id
+ Username string
+ Name string
+ HomeDir string
+}
+
+// UnknownUserIdError is returned by LookupId when
+// a user cannot be found.
+type UnknownUserIdError int
+
+func (e UnknownUserIdError) Error() string {
+ return "user: unknown userid " + strconv.Itoa(int(e))
+}
+
+// UnknownUserError is returned by Lookup when
+// a user cannot be found.
+type UnknownUserError string
+
+func (e UnknownUserError) Error() string {
+ return "user: unknown user " + string(e)
+}
diff --git a/src/os/user/user_test.go b/src/os/user/user_test.go
new file mode 100644
index 000000000..9d9420e80
--- /dev/null
+++ b/src/os/user/user_test.go
@@ -0,0 +1,89 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package user
+
+import (
+ "runtime"
+ "testing"
+)
+
+func check(t *testing.T) {
+ if !implemented {
+ t.Skip("user: not implemented; skipping tests")
+ }
+}
+
+func TestCurrent(t *testing.T) {
+ check(t)
+
+ u, err := Current()
+ if err != nil {
+ t.Fatalf("Current: %v", err)
+ }
+ if u.HomeDir == "" {
+ t.Errorf("didn't get a HomeDir")
+ }
+ if u.Username == "" {
+ t.Errorf("didn't get a username")
+ }
+}
+
+func compare(t *testing.T, want, got *User) {
+ if want.Uid != got.Uid {
+ t.Errorf("got Uid=%q; want %q", got.Uid, want.Uid)
+ }
+ if want.Username != got.Username {
+ t.Errorf("got Username=%q; want %q", got.Username, want.Username)
+ }
+ if want.Name != got.Name {
+ t.Errorf("got Name=%q; want %q", got.Name, want.Name)
+ }
+ // TODO(brainman): fix it once we know how.
+ if runtime.GOOS == "windows" {
+ t.Skip("skipping Gid and HomeDir comparisons")
+ }
+ if want.Gid != got.Gid {
+ t.Errorf("got Gid=%q; want %q", got.Gid, want.Gid)
+ }
+ if want.HomeDir != got.HomeDir {
+ t.Errorf("got HomeDir=%q; want %q", got.HomeDir, want.HomeDir)
+ }
+}
+
+func TestLookup(t *testing.T) {
+ check(t)
+
+ if runtime.GOOS == "plan9" {
+ t.Skipf("Lookup not implemented on %q", runtime.GOOS)
+ }
+
+ want, err := Current()
+ if err != nil {
+ t.Fatalf("Current: %v", err)
+ }
+ got, err := Lookup(want.Username)
+ if err != nil {
+ t.Fatalf("Lookup: %v", err)
+ }
+ compare(t, want, got)
+}
+
+func TestLookupId(t *testing.T) {
+ check(t)
+
+ if runtime.GOOS == "plan9" {
+ t.Skipf("LookupId not implemented on %q", runtime.GOOS)
+ }
+
+ want, err := Current()
+ if err != nil {
+ t.Fatalf("Current: %v", err)
+ }
+ got, err := LookupId(want.Uid)
+ if err != nil {
+ t.Fatalf("LookupId: %v", err)
+ }
+ compare(t, want, got)
+}