summaryrefslogtreecommitdiff
path: root/src/pkg/os
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/os')
-rw-r--r--src/pkg/os/dir_plan9.go259
-rw-r--r--src/pkg/os/doc.go4
-rw-r--r--src/pkg/os/env.go2
-rw-r--r--src/pkg/os/env_unix_test.go30
-rw-r--r--src/pkg/os/error.go2
-rw-r--r--src/pkg/os/error_plan9.go21
-rw-r--r--src/pkg/os/error_posix.go21
-rw-r--r--src/pkg/os/error_test.go51
-rw-r--r--src/pkg/os/error_windows.go21
-rw-r--r--src/pkg/os/error_windows_test.go47
-rw-r--r--src/pkg/os/exec.go19
-rw-r--r--src/pkg/os/exec/exec.go10
-rw-r--r--src/pkg/os/exec/exec_test.go134
-rw-r--r--src/pkg/os/exec/lp_plan9.go3
-rw-r--r--src/pkg/os/exec/lp_unix.go3
-rw-r--r--src/pkg/os/exec/lp_unix_test.go55
-rw-r--r--src/pkg/os/exec/lp_windows.go35
-rw-r--r--src/pkg/os/exec_plan9.go58
-rw-r--r--src/pkg/os/exec_posix.go17
-rw-r--r--src/pkg/os/exec_unix.go4
-rw-r--r--src/pkg/os/exec_windows.go4
-rw-r--r--src/pkg/os/file.go4
-rw-r--r--src/pkg/os/file_plan9.go144
-rw-r--r--src/pkg/os/file_posix.go21
-rw-r--r--src/pkg/os/file_unix.go32
-rw-r--r--src/pkg/os/file_windows.go188
-rw-r--r--src/pkg/os/getwd.go23
-rw-r--r--src/pkg/os/os_test.go78
-rw-r--r--src/pkg/os/path_plan9.go4
-rw-r--r--src/pkg/os/path_test.go15
-rw-r--r--src/pkg/os/pipe_bsd.go28
-rw-r--r--src/pkg/os/pipe_linux.go33
-rw-r--r--src/pkg/os/proc.go2
-rw-r--r--src/pkg/os/signal/example_test.go19
-rw-r--r--src/pkg/os/signal/signal_test.go41
-rw-r--r--src/pkg/os/signal/signal_windows_test.go77
-rw-r--r--src/pkg/os/stat_darwin.go6
-rw-r--r--src/pkg/os/stat_freebsd.go6
-rw-r--r--src/pkg/os/stat_linux.go6
-rw-r--r--src/pkg/os/stat_netbsd.go10
-rw-r--r--src/pkg/os/stat_openbsd.go6
-rw-r--r--src/pkg/os/stat_plan9.go52
-rw-r--r--src/pkg/os/stat_windows.go149
-rw-r--r--src/pkg/os/types.go27
-rw-r--r--src/pkg/os/types_notwin.go25
-rw-r--r--src/pkg/os/types_windows.go104
-rw-r--r--src/pkg/os/user/lookup.go22
-rw-r--r--src/pkg/os/user/lookup_stubs.go6
-rw-r--r--src/pkg/os/user/lookup_unix.go21
-rw-r--r--src/pkg/os/user/lookup_windows.go25
-rw-r--r--src/pkg/os/user/user_test.go36
51 files changed, 1320 insertions, 690 deletions
diff --git a/src/pkg/os/dir_plan9.go b/src/pkg/os/dir_plan9.go
index 7fa4c7f44..8195c02a4 100644
--- a/src/pkg/os/dir_plan9.go
+++ b/src/pkg/os/dir_plan9.go
@@ -5,15 +5,11 @@
package os
import (
- "errors"
"io"
"syscall"
)
-var errShortStat = errors.New("short stat message")
-var errBadStat = errors.New("bad stat message format")
-
-func (file *File) readdir(n int) (fi []FileInfo, err error) {
+func (file *File) readdir(n int) ([]FileInfo, error) {
// If this file has no dirinfo, create one.
if file.dirinfo == nil {
file.dirinfo = new(dirInfo)
@@ -24,44 +20,47 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) {
size = 100
n = -1
}
- result := make([]FileInfo, 0, size) // Empty with room to grow.
+ fi := make([]FileInfo, 0, size) // Empty with room to grow.
for n != 0 {
- // Refill the buffer if necessary
+ // Refill the buffer if necessary.
if d.bufp >= d.nbuf {
- d.bufp = 0
- var e error
- d.nbuf, e = file.Read(d.buf[:])
- if e != nil && e != io.EOF {
- return result, &PathError{"readdir", file.name, e}
- }
- if e == io.EOF {
- break
+ 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 d.nbuf < syscall.STATFIXLEN {
- return result, &PathError{"readdir", file.name, errShortStat}
+ if nb < syscall.STATFIXLEN {
+ return fi, &PathError{"readdir", file.name, syscall.ErrShortStat}
}
}
- // Get a record from buffer
- m, _ := gbit16(d.buf[d.bufp:])
- m += 2
+ // 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 result, &PathError{"readdir", file.name, errShortStat}
+ return fi, &PathError{"readdir", file.name, syscall.ErrShortStat}
}
- dir, e := UnmarshalDir(d.buf[d.bufp : d.bufp+int(m)])
- if e != nil {
- return result, &PathError{"readdir", file.name, e}
+
+ dir, err := syscall.UnmarshalDir(b[:m])
+ if err != nil {
+ return fi, &PathError{"readdir", file.name, err}
}
- result = append(result, fileInfoFromStat(dir))
+ fi = append(fi, fileInfoFromStat(dir))
- d.bufp += int(m)
+ d.bufp += m
n--
}
- if n >= 0 && len(result) == 0 {
- return result, io.EOF
+ if n >= 0 && len(fi) == 0 {
+ return fi, io.EOF
}
- return result, nil
+ return fi, nil
}
func (file *File) readdirnames(n int) (names []string, err error) {
@@ -72,205 +71,3 @@ func (file *File) readdirnames(n int) (names []string, err error) {
}
return
}
-
-type Dir struct {
- // system-modified data
- Type uint16 // server type
- Dev uint32 // server subtype
- // file data
- Qid Qid // unique id from server
- Mode uint32 // permissions
- Atime uint32 // last read time
- Mtime uint32 // last write time
- Length uint64 // file length
- Name string // last element of path
- Uid string // owner name
- Gid string // group name
- Muid string // last modifier name
-}
-
-type Qid struct {
- Path uint64 // the file server's unique identification for the file
- Vers uint32 // version number for given Path
- Type uint8 // the type of the file (syscall.QTDIR for example)
-}
-
-var nullDir = Dir{
- ^uint16(0),
- ^uint32(0),
- Qid{^uint64(0), ^uint32(0), ^uint8(0)},
- ^uint32(0),
- ^uint32(0),
- ^uint32(0),
- ^uint64(0),
- "",
- "",
- "",
- "",
-}
-
-// Null assigns members of d with special "don't care" values indicating
-// they should not be written by syscall.Wstat.
-func (d *Dir) Null() {
- *d = nullDir
-}
-
-// pdir appends a 9P Stat message based on the contents of Dir d to a byte slice b.
-func pdir(b []byte, d *Dir) []byte {
- n := len(b)
- b = pbit16(b, 0) // length, filled in later
- b = pbit16(b, d.Type)
- b = pbit32(b, d.Dev)
- b = pqid(b, d.Qid)
- b = pbit32(b, d.Mode)
- b = pbit32(b, d.Atime)
- b = pbit32(b, d.Mtime)
- b = pbit64(b, d.Length)
- b = pstring(b, d.Name)
- b = pstring(b, d.Uid)
- b = pstring(b, d.Gid)
- b = pstring(b, d.Muid)
- pbit16(b[0:n], uint16(len(b)-(n+2)))
- return b
-}
-
-// UnmarshalDir reads a 9P Stat message from a 9P protocol message stored in b,
-// returning the corresponding Dir struct.
-func UnmarshalDir(b []byte) (d *Dir, err error) {
- n := uint16(0)
- n, b = gbit16(b)
-
- if int(n) != len(b) {
- return nil, errBadStat
- }
-
- d = new(Dir)
- d.Type, b = gbit16(b)
- d.Dev, b = gbit32(b)
- d.Qid, b = gqid(b)
- d.Mode, b = gbit32(b)
- d.Atime, b = gbit32(b)
- d.Mtime, b = gbit32(b)
- d.Length, b = gbit64(b)
- d.Name, b = gstring(b)
- d.Uid, b = gstring(b)
- d.Gid, b = gstring(b)
- d.Muid, b = gstring(b)
-
- if len(b) != 0 {
- return nil, errBadStat
- }
-
- return d, nil
-}
-
-// gqid reads the qid part of a 9P Stat message from a 9P protocol message stored in b,
-// returning the corresponding Qid struct and the remaining slice of b.
-func gqid(b []byte) (Qid, []byte) {
- var q Qid
- q.Path, b = gbit64(b)
- q.Vers, b = gbit32(b)
- q.Type, b = gbit8(b)
- return q, b
-}
-
-// pqid appends a Qid struct q to a 9P message b.
-func pqid(b []byte, q Qid) []byte {
- b = pbit64(b, q.Path)
- b = pbit32(b, q.Vers)
- b = pbit8(b, q.Type)
- return b
-}
-
-// gbit8 reads a byte-sized numeric value from a 9P protocol message stored in b,
-// returning the value and the remaining slice of b.
-func gbit8(b []byte) (uint8, []byte) {
- return uint8(b[0]), b[1:]
-}
-
-// gbit16 reads a 16-bit numeric value from a 9P protocol message stored in b,
-// returning the value and the remaining slice of b.
-func gbit16(b []byte) (uint16, []byte) {
- return uint16(b[0]) | uint16(b[1])<<8, b[2:]
-}
-
-// gbit32 reads a 32-bit numeric value from a 9P protocol message stored in b,
-// returning the value and the remaining slice of b.
-func gbit32(b []byte) (uint32, []byte) {
- return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24, b[4:]
-}
-
-// gbit64 reads a 64-bit numeric value from a 9P protocol message stored in b,
-// returning the value and the remaining slice of b.
-func gbit64(b []byte) (uint64, []byte) {
- lo, b := gbit32(b)
- hi, b := gbit32(b)
- return uint64(hi)<<32 | uint64(lo), b
-}
-
-// gstring reads a string from a 9P protocol message stored in b,
-// returning the value as a Go string and the remaining slice of b.
-func gstring(b []byte) (string, []byte) {
- n, b := gbit16(b)
- return string(b[0:n]), b[n:]
-}
-
-// pbit8 appends a byte-sized numeric value x to a 9P message b.
-func pbit8(b []byte, x uint8) []byte {
- n := len(b)
- if n+1 > cap(b) {
- nb := make([]byte, n, 100+2*cap(b))
- copy(nb, b)
- b = nb
- }
- b = b[0 : n+1]
- b[n] = x
- return b
-}
-
-// pbit16 appends a 16-bit numeric value x to a 9P message b.
-func pbit16(b []byte, x uint16) []byte {
- n := len(b)
- if n+2 > cap(b) {
- nb := make([]byte, n, 100+2*cap(b))
- copy(nb, b)
- b = nb
- }
- b = b[0 : n+2]
- b[n] = byte(x)
- b[n+1] = byte(x >> 8)
- return b
-}
-
-// pbit32 appends a 32-bit numeric value x to a 9P message b.
-func pbit32(b []byte, x uint32) []byte {
- n := len(b)
- if n+4 > cap(b) {
- nb := make([]byte, n, 100+2*cap(b))
- copy(nb, b)
- b = nb
- }
- b = b[0 : n+4]
- b[n] = byte(x)
- b[n+1] = byte(x >> 8)
- b[n+2] = byte(x >> 16)
- b[n+3] = byte(x >> 24)
- return b
-}
-
-// pbit64 appends a 64-bit numeric value x to a 9P message b.
-func pbit64(b []byte, x uint64) []byte {
- b = pbit32(b, uint32(x))
- b = pbit32(b, uint32(x>>32))
- return b
-}
-
-// pstring appends a Go string s to a 9P message b.
-func pstring(b []byte, s string) []byte {
- if len(s) >= 1<<16 {
- panic(errors.New("string too long"))
- }
- b = pbit16(b, uint16(len(s)))
- b = append(b, s...)
- return b
-}
diff --git a/src/pkg/os/doc.go b/src/pkg/os/doc.go
index 6a531e0d7..2cc17530c 100644
--- a/src/pkg/os/doc.go
+++ b/src/pkg/os/doc.go
@@ -79,6 +79,8 @@ func (p *ProcessState) Sys() interface{} {
// 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()
}
@@ -89,7 +91,7 @@ func Hostname() (name string, err error) {
}
// Readdir reads the contents of the directory associated with file and
-// returns an array of up to n FileInfo values, as would be returned
+// 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.
//
diff --git a/src/pkg/os/env.go b/src/pkg/os/env.go
index eb265f241..db7fc72b8 100644
--- a/src/pkg/os/env.go
+++ b/src/pkg/os/env.go
@@ -9,7 +9,7 @@ package os
import "syscall"
// Expand replaces ${var} or $var in the string based on the mapping function.
-// Invocations of undefined variables are replaced with the empty string.
+// 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.
diff --git a/src/pkg/os/env_unix_test.go b/src/pkg/os/env_unix_test.go
new file mode 100644
index 000000000..7eb4dc0ff
--- /dev/null
+++ b/src/pkg/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 freebsd linux netbsd openbsd
+
+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/pkg/os/error.go b/src/pkg/os/error.go
index b88e49400..a7977ff19 100644
--- a/src/pkg/os/error.go
+++ b/src/pkg/os/error.go
@@ -43,7 +43,7 @@ func NewSyscallError(syscall string, err error) error {
return &SyscallError{syscall, err}
}
-// IsExist returns whether the error is known to report that a file or directory
+// IsExist returns 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)
diff --git a/src/pkg/os/error_plan9.go b/src/pkg/os/error_plan9.go
index 3c9dfb0b1..85260c82a 100644
--- a/src/pkg/os/error_plan9.go
+++ b/src/pkg/os/error_plan9.go
@@ -5,21 +5,36 @@
package os
func isExist(err error) bool {
- if pe, ok := err.(*PathError); ok {
+ 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 {
- if pe, ok := err.(*PathError); ok {
+ 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")
}
func isPermission(err error) bool {
- if pe, ok := err.(*PathError); ok {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
err = pe.Err
}
return contains(err.Error(), "permission denied")
diff --git a/src/pkg/os/error_posix.go b/src/pkg/os/error_posix.go
index 1685c1f21..81b626aec 100644
--- a/src/pkg/os/error_posix.go
+++ b/src/pkg/os/error_posix.go
@@ -9,21 +9,36 @@ package os
import "syscall"
func isExist(err error) bool {
- if pe, ok := err.(*PathError); ok {
+ 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 {
- if pe, ok := err.(*PathError); ok {
+ 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 {
- if pe, ok := err.(*PathError); ok {
+ 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/pkg/os/error_test.go b/src/pkg/os/error_test.go
index 42f846fa3..02ed2351c 100644
--- a/src/pkg/os/error_test.go
+++ b/src/pkg/os/error_test.go
@@ -79,3 +79,54 @@ func checkErrorPredicate(predName string, pred func(error) bool, err error) stri
}
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/pkg/os/error_windows.go b/src/pkg/os/error_windows.go
index fbb0d4f3f..83db6c078 100644
--- a/src/pkg/os/error_windows.go
+++ b/src/pkg/os/error_windows.go
@@ -7,7 +7,12 @@ package os
import "syscall"
func isExist(err error) bool {
- if pe, ok := err.(*PathError); ok {
+ switch pe := err.(type) {
+ case nil:
+ return false
+ case *PathError:
+ err = pe.Err
+ case *LinkError:
err = pe.Err
}
return err == syscall.ERROR_ALREADY_EXISTS ||
@@ -15,7 +20,12 @@ func isExist(err error) bool {
}
func isNotExist(err error) bool {
- if pe, ok := err.(*PathError); ok {
+ 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 ||
@@ -23,7 +33,12 @@ func isNotExist(err error) bool {
}
func isPermission(err error) bool {
- if pe, ok := err.(*PathError); ok {
+ 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/pkg/os/error_windows_test.go b/src/pkg/os/error_windows_test.go
new file mode 100644
index 000000000..3e6504f8d
--- /dev/null
+++ b/src/pkg/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/pkg/os/exec.go b/src/pkg/os/exec.go
index 531b87ca5..5aea3098b 100644
--- a/src/pkg/os/exec.go
+++ b/src/pkg/os/exec.go
@@ -6,6 +6,7 @@ package os
import (
"runtime"
+ "sync/atomic"
"syscall"
)
@@ -13,7 +14,7 @@ import (
type Process struct {
Pid int
handle uintptr
- done bool // process has been successfully waited on
+ isdone uint32 // process has been successfully waited on, non zero if true
}
func newProcess(pid int, handle uintptr) *Process {
@@ -22,6 +23,14 @@ func newProcess(pid int, handle uintptr) *Process {
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 {
@@ -54,14 +63,6 @@ type Signal interface {
Signal() // to distinguish from other Stringers
}
-// 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
-)
-
// Getpid returns the process id of the caller.
func Getpid() int { return syscall.Getpid() }
diff --git a/src/pkg/os/exec/exec.go b/src/pkg/os/exec/exec.go
index 9a8e18170..8368491b0 100644
--- a/src/pkg/os/exec/exec.go
+++ b/src/pkg/os/exec/exec.go
@@ -16,7 +16,7 @@ import (
"syscall"
)
-// Error records the name of a binary that failed to be be executed
+// Error records the name of a binary that failed to be executed
// and the reason it failed.
type Error struct {
Name string
@@ -37,7 +37,7 @@ type Cmd struct {
// 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
@@ -143,6 +143,9 @@ func (c *Cmd) argv() []string {
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
}
@@ -182,6 +185,9 @@ func (c *Cmd) stderr() (f *os.File, err error) {
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
}
diff --git a/src/pkg/os/exec/exec_test.go b/src/pkg/os/exec/exec_test.go
index 52f4bce3a..611ac0267 100644
--- a/src/pkg/os/exec/exec_test.go
+++ b/src/pkg/os/exec/exec_test.go
@@ -14,6 +14,7 @@ import (
"net/http"
"net/http/httptest"
"os"
+ "path/filepath"
"runtime"
"strconv"
"strings"
@@ -83,10 +84,16 @@ func TestNoExistBinary(t *testing.T) {
func TestExitStatus(t *testing.T) {
// Test that exit values are returned correctly
- err := helperCommand("exit", "42").Run()
+ cmd := helperCommand("exit", "42")
+ err := cmd.Run()
+ want := "exit status 42"
+ switch runtime.GOOS {
+ case "plan9":
+ want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid())
+ }
if werr, ok := err.(*ExitError); ok {
- if s, e := werr.Error(), "exit status 42"; s != e {
- t.Errorf("from exit 42 got exit %q, want %q", s, e)
+ if s := werr.Error(); s != want {
+ t.Errorf("from exit 42 got exit %q, want %q", s, want)
}
} else {
t.Fatalf("expected *ExitError from exit 42; got %T: %v", err, err)
@@ -144,18 +151,36 @@ func TestPipes(t *testing.T) {
check("Wait", err)
}
+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 TestExtraFiles(t *testing.T) {
if runtime.GOOS == "windows" {
- t.Logf("no operating system support; skipping")
- return
+ t.Skip("no operating system support; skipping")
}
// Ensure that file descriptors have not already been leaked into
// our environment.
- for fd := os.Stderr.Fd() + 1; fd <= 101; fd++ {
- err := os.NewFile(fd, "").Close()
- if err == nil {
- t.Logf("Something already leaked - closed fd %d", fd)
+ if !testedAlreadyLeaked {
+ testedAlreadyLeaked = true
+ for fd := basefds(); fd <= 101; fd++ {
+ err := os.NewFile(fd, "").Close()
+ if err == nil {
+ t.Logf("Something already leaked - closed fd %d", fd)
+ }
}
}
@@ -167,6 +192,18 @@ func TestExtraFiles(t *testing.T) {
}
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.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -193,13 +230,65 @@ func TestExtraFiles(t *testing.T) {
}
c := helperCommand("read3")
+ var stdout, stderr bytes.Buffer
+ c.Stdout = &stdout
+ c.Stderr = &stderr
c.ExtraFiles = []*os.File{tf}
- bs, err := c.CombinedOutput()
+ err = c.Run()
if err != nil {
- t.Fatalf("CombinedOutput: %v; output %q", err, bs)
+ t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes())
}
- if string(bs) != text {
- t.Errorf("got %q; want %q", string(bs), text)
+ 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 *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("describefiles")
+ ca.ExtraFiles = []*os.File{listenerFile(la)}
+ lb := listen()
+ cb := helperCommand("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()
}
}
@@ -287,10 +376,15 @@ func TestHelperProcess(*testing.T) {
// 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
default:
// Now verify that there are no other open fds.
var files []*os.File
- for wantfd := os.Stderr.Fd() + 2; wantfd <= 100; wantfd++ {
+ 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)
@@ -314,10 +408,20 @@ func TestHelperProcess(*testing.T) {
// what we do with fd3 as long as we refer to it;
// closing it is the easy choice.
fd3.Close()
- os.Stderr.Write(bs)
+ os.Stdout.Write(bs)
case "exit":
n, _ := strconv.Atoi(args[0])
os.Exit(n)
+ case "describefiles":
+ for fd := uintptr(3); fd < 25; fd++ {
+ f := os.NewFile(fd, fmt.Sprintf("fd-%d", fd))
+ ln, err := net.FileListener(f)
+ if err == nil {
+ fmt.Printf("fd%d: listener %s\n", fd, ln.Addr())
+ ln.Close()
+ }
+ }
+ os.Exit(0)
default:
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
os.Exit(2)
diff --git a/src/pkg/os/exec/lp_plan9.go b/src/pkg/os/exec/lp_plan9.go
index 0e229e03e..6846a35c8 100644
--- a/src/pkg/os/exec/lp_plan9.go
+++ b/src/pkg/os/exec/lp_plan9.go
@@ -8,7 +8,6 @@ import (
"errors"
"os"
"strings"
- "syscall"
)
// ErrNotFound is the error resulting if a path search failed to find an executable file.
@@ -22,7 +21,7 @@ func findExecutable(file string) error {
if m := d.Mode(); !m.IsDir() && m&0111 != 0 {
return nil
}
- return syscall.EPERM
+ return os.ErrPermission
}
// LookPath searches for an executable binary named file
diff --git a/src/pkg/os/exec/lp_unix.go b/src/pkg/os/exec/lp_unix.go
index 216322199..1d1ec07da 100644
--- a/src/pkg/os/exec/lp_unix.go
+++ b/src/pkg/os/exec/lp_unix.go
@@ -42,6 +42,9 @@ func LookPath(file string) (string, error) {
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 "."
diff --git a/src/pkg/os/exec/lp_unix_test.go b/src/pkg/os/exec/lp_unix_test.go
new file mode 100644
index 000000000..625d78486
--- /dev/null
+++ b/src/pkg/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 freebsd linux netbsd openbsd
+
+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/pkg/os/exec/lp_windows.go b/src/pkg/os/exec/lp_windows.go
index d8351d7e6..7c7289bce 100644
--- a/src/pkg/os/exec/lp_windows.go
+++ b/src/pkg/os/exec/lp_windows.go
@@ -72,7 +72,7 @@ func LookPath(file string) (f string, err error) {
return
}
if pathenv := os.Getenv(`PATH`); pathenv != `` {
- for _, dir := range strings.Split(pathenv, `;`) {
+ for _, dir := range splitList(pathenv) {
if f, err = findExecutable(dir+`\`+file, exts); err == nil {
return
}
@@ -80,3 +80,36 @@ func LookPath(file string) (f string, err error) {
}
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/pkg/os/exec_plan9.go b/src/pkg/os/exec_plan9.go
index 41cc8c26f..2bd5b6888 100644
--- a/src/pkg/os/exec_plan9.go
+++ b/src/pkg/os/exec_plan9.go
@@ -11,6 +11,14 @@ import (
"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,
@@ -30,35 +38,35 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e
return newProcess(pid, h), nil
}
-// Plan9Note implements the Signal interface on Plan 9.
-type Plan9Note string
-
-func (note Plan9Note) String() string {
- return string(note)
+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 {
+ if p.done() {
return errors.New("os: process already finished")
}
-
- f, e := OpenFile("/proc/"+itoa(p.Pid)+"/note", O_WRONLY, 0)
- if e != nil {
+ if sig == Kill {
+ // Special-case the kill signal since it doesn't use /proc/$pid/note.
+ return p.Kill()
+ }
+ if e := p.writeProcFile("note", sig.String()); e != nil {
return NewSyscallError("signal", e)
}
- defer f.Close()
- _, e = f.Write([]byte(sig.String()))
- return e
+ return nil
}
func (p *Process) kill() error {
- f, e := OpenFile("/proc/"+itoa(p.Pid)+"/ctl", O_WRONLY, 0)
- if e != nil {
+ if e := p.writeProcFile("ctl", "kill"); e != nil {
return NewSyscallError("kill", e)
}
- defer f.Close()
- _, e = f.Write([]byte("kill"))
- return e
+ return nil
}
func (p *Process) wait() (ps *ProcessState, err error) {
@@ -67,20 +75,12 @@ func (p *Process) wait() (ps *ProcessState, err error) {
if p.Pid == -1 {
return nil, ErrInvalid
}
-
- for true {
- err = syscall.Await(&waitmsg)
-
- if err != nil {
- return nil, NewSyscallError("wait", err)
- }
-
- if waitmsg.Pid == p.Pid {
- p.done = true
- break
- }
+ err = syscall.WaitProcess(p.Pid, &waitmsg)
+ if err != nil {
+ return nil, NewSyscallError("wait", err)
}
+ p.setDone()
ps = &ProcessState{
pid: waitmsg.Pid,
status: &waitmsg,
diff --git a/src/pkg/os/exec_posix.go b/src/pkg/os/exec_posix.go
index 70351cfb3..f7b10f3c6 100644
--- a/src/pkg/os/exec_posix.go
+++ b/src/pkg/os/exec_posix.go
@@ -10,10 +10,19 @@ 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) {
- // Double-check existence of the directory we want
+ // 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.Dir != "" {
+ if attr != nil && attr.Sys == nil && attr.Dir != "" {
if _, err := Stat(attr.Dir); err != nil {
pe := err.(*PathError)
pe.Op = "chdir"
@@ -109,9 +118,9 @@ func (p *ProcessState) String() string {
case status.Exited():
res = "exit status " + itod(status.ExitStatus())
case status.Signaled():
- res = "signal " + itod(int(status.Signal()))
+ res = "signal: " + status.Signal().String()
case status.Stopped():
- res = "stop signal " + itod(int(status.StopSignal()))
+ res = "stop signal: " + status.StopSignal().String()
if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != 0 {
res += " (trap " + itod(status.TrapCause()) + ")"
}
diff --git a/src/pkg/os/exec_unix.go b/src/pkg/os/exec_unix.go
index ecfe5353b..fa3ba8a19 100644
--- a/src/pkg/os/exec_unix.go
+++ b/src/pkg/os/exec_unix.go
@@ -24,7 +24,7 @@ func (p *Process) wait() (ps *ProcessState, err error) {
return nil, NewSyscallError("wait", e)
}
if pid1 != 0 {
- p.done = true
+ p.setDone()
}
ps = &ProcessState{
pid: pid1,
@@ -35,7 +35,7 @@ func (p *Process) wait() (ps *ProcessState, err error) {
}
func (p *Process) signal(sig Signal) error {
- if p.done {
+ if p.done() {
return errors.New("os: process already finished")
}
s, ok := sig.(syscall.Signal)
diff --git a/src/pkg/os/exec_windows.go b/src/pkg/os/exec_windows.go
index 5beca4a65..4aa2ade63 100644
--- a/src/pkg/os/exec_windows.go
+++ b/src/pkg/os/exec_windows.go
@@ -32,7 +32,7 @@ func (p *Process) wait() (ps *ProcessState, err error) {
if e != nil {
return nil, NewSyscallError("GetProcessTimes", e)
}
- p.done = true
+ 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
@@ -43,7 +43,7 @@ func (p *Process) wait() (ps *ProcessState, err error) {
}
func (p *Process) signal(sig Signal) error {
- if p.done {
+ if p.done() {
return errors.New("os: process already finished")
}
if sig == Kill {
diff --git a/src/pkg/os/file.go b/src/pkg/os/file.go
index 4acf35d67..32cac6d89 100644
--- a/src/pkg/os/file.go
+++ b/src/pkg/os/file.go
@@ -9,7 +9,7 @@
// 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.
//
@@ -185,7 +185,7 @@ func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
}
// WriteString is like Write, but writes the contents of string s rather than
-// an array of bytes.
+// a slice of bytes.
func (f *File) WriteString(s string) (ret int, err error) {
if f == nil {
return 0, ErrInvalid
diff --git a/src/pkg/os/file_plan9.go b/src/pkg/os/file_plan9.go
index cb0e9ef92..d6d39a899 100644
--- a/src/pkg/os/file_plan9.go
+++ b/src/pkg/os/file_plan9.go
@@ -5,14 +5,11 @@
package os
import (
- "errors"
"runtime"
"syscall"
"time"
)
-var ErrPlan9 = errors.New("unimplemented on Plan 9")
-
// File represents an open file descriptor.
type File struct {
*file
@@ -107,7 +104,6 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
append = true
}
- syscall.ForkLock.RLock()
if (create && trunc) || excl {
fd, e = syscall.Create(name, flag, syscallMode(perm))
} else {
@@ -120,7 +116,6 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
}
}
}
- syscall.ForkLock.RUnlock()
if e != nil {
return nil, &PathError{"open", name, e}
@@ -137,8 +132,8 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
// Close closes the File, rendering it unusable for I/O.
// It returns an error, if any.
-func (file *File) Close() error {
- return file.file.close()
+func (f *File) Close() error {
+ return f.file.close()
}
func (file *file) close() error {
@@ -159,8 +154,8 @@ func (file *file) close() error {
}
// Stat returns the FileInfo structure describing file.
-// It returns the FileInfo and an error, if any.
-func (f *File) Stat() (FileInfo, error) {
+// If there is an error, it will be of type *PathError.
+func (f *File) Stat() (fi FileInfo, err error) {
d, err := dirstat(f)
if err != nil {
return nil, err
@@ -170,14 +165,20 @@ func (f *File) Stat() (FileInfo, error) {
// 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 {
- var d Dir
- d.Null()
+ var d syscall.Dir
- d.Length = uint64(size)
+ d.Null()
+ d.Length = size
- if e := syscall.Fwstat(f.fd, pdir(nil, &d)); e != nil {
- return &PathError{"truncate", f.name, e}
+ 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
}
@@ -187,7 +188,7 @@ const chmodMask = uint32(syscall.DMAPPEND | syscall.DMEXCL | syscall.DMTMP | Mod
// 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 {
- var d Dir
+ var d syscall.Dir
odir, e := dirstat(f)
if e != nil {
@@ -195,8 +196,14 @@ func (f *File) Chmod(mode FileMode) error {
}
d.Null()
d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask
- if e := syscall.Fwstat(f.fd, pdir(nil, &d)); e != nil {
- return &PathError{"chmod", f.name, e}
+
+ 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
}
@@ -208,12 +215,16 @@ func (f *File) Sync() (err error) {
if f == nil {
return ErrInvalid
}
-
- var d Dir
+ var d syscall.Dir
d.Null()
- if e := syscall.Fwstat(f.fd, pdir(nil, &d)); e != nil {
- return NewSyscallError("fsync", e)
+ 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
}
@@ -233,13 +244,23 @@ func (f *File) pread(b []byte, off int64) (n int, err error) {
// 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)
}
@@ -255,13 +276,18 @@ func (f *File) seek(offset int64, whence int) (ret int64, err error) {
// 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 Dir
- d.Null()
+ var d syscall.Dir
- d.Length = uint64(size)
+ d.Null()
+ d.Length = size
- if e := syscall.Wstat(name, pdir(nil, &d)); e != nil {
- return &PathError{"truncate", name, e}
+ 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
}
@@ -277,21 +303,27 @@ func Remove(name string) error {
// Rename renames a file.
func Rename(oldname, newname string) error {
- var d Dir
- d.Null()
+ var d syscall.Dir
+ d.Null()
d.Name = newname
- if e := syscall.Wstat(oldname, pdir(nil, &d)); e != nil {
- return &PathError{"rename", oldname, e}
+ buf := make([]byte, syscall.STATFIXLEN+len(d.Name))
+ n, err := d.Marshal(buf[:])
+ if err != nil {
+ return &PathError{"rename", oldname, err}
+ }
+ if err = syscall.Wstat(oldname, buf[:n]); err != nil {
+ return &PathError{"rename", oldname, 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 Dir
+ var d syscall.Dir
odir, e := dirstat(name)
if e != nil {
@@ -299,8 +331,14 @@ func Chmod(name string, mode FileMode) error {
}
d.Null()
d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask
- if e := syscall.Wstat(name, pdir(nil, &d)); e != nil {
- return &PathError{"chmod", name, e}
+
+ 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
}
@@ -310,19 +348,27 @@ func Chmod(name string, mode FileMode) error {
//
// 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 Dir
- d.Null()
+ var d syscall.Dir
+ d.Null()
d.Atime = uint32(atime.Unix())
d.Mtime = uint32(mtime.Unix())
- if e := syscall.Wstat(name, pdir(nil, &d)); e != nil {
- return &PathError{"chtimes", name, e}
+ 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
@@ -338,32 +384,42 @@ func Pipe() (r *File, w *File, err error) {
// not supported on Plan 9
-// Link creates a hard link.
+// 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, ErrPlan9}
+ 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, ErrPlan9}
+ 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 "", ErrPlan9
+ 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 ErrPlan9
+ 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 ErrPlan9
+ 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 {
- return ErrPlan9
+ return &PathError{"chown", f.name, syscall.EPLAN9}
}
// TempDir returns the default directory to use for temporary files.
diff --git a/src/pkg/os/file_posix.go b/src/pkg/os/file_posix.go
index 073bd56a4..b979fed97 100644
--- a/src/pkg/os/file_posix.go
+++ b/src/pkg/os/file_posix.go
@@ -13,17 +13,6 @@ import (
func sigpipe() // implemented in package runtime
-func epipecheck(file *File, e error) {
- if e == syscall.EPIPE {
- file.nepipe++
- if file.nepipe >= 10 {
- sigpipe()
- }
- } else {
- file.nepipe = 0
- }
-}
-
// 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 {
@@ -164,12 +153,10 @@ func (f *File) Sync() (err error) {
// 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.Timeval
- atime_ns := atime.Unix()*1e9 + int64(atime.Nanosecond())
- mtime_ns := mtime.Unix()*1e9 + int64(mtime.Nanosecond())
- utimes[0] = syscall.NsecToTimeval(atime_ns)
- utimes[1] = syscall.NsecToTimeval(mtime_ns)
- if e := syscall.Utimes(name, utimes[0:]); e != nil {
+ 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/pkg/os/file_unix.go b/src/pkg/os/file_unix.go
index 6271c3189..4f59c94cb 100644
--- a/src/pkg/os/file_unix.go
+++ b/src/pkg/os/file_unix.go
@@ -8,6 +8,7 @@ package os
import (
"runtime"
+ "sync/atomic"
"syscall"
)
@@ -24,7 +25,7 @@ type file struct {
fd int
name string
dirinfo *dirInfo // nil unless directory being read
- nepipe int // number of consecutive EPIPE in Write
+ nepipe int32 // number of consecutive EPIPE in Write
}
// Fd returns the integer Unix file descriptor referencing the open file.
@@ -53,6 +54,16 @@ type dirInfo struct {
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"
@@ -263,25 +274,6 @@ func basename(name string) string {
return name
}
-// 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
-}
-
// TempDir returns the default directory to use for temporary files.
func TempDir() string {
dir := Getenv("TMPDIR")
diff --git a/src/pkg/os/file_windows.go b/src/pkg/os/file_windows.go
index 88fa77bb8..2eba7a475 100644
--- a/src/pkg/os/file_windows.go
+++ b/src/pkg/os/file_windows.go
@@ -10,6 +10,8 @@ import (
"sync"
"syscall"
"unicode/utf16"
+ "unicode/utf8"
+ "unsafe"
)
// File represents an open file descriptor.
@@ -25,8 +27,12 @@ type file struct {
fd syscall.Handle
name string
dirinfo *dirInfo // nil unless directory being read
- nepipe int // number of consecutive EPIPE in Write
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.
@@ -37,15 +43,25 @@ func (file *File) Fd() uintptr {
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
}
- f := &File{&file{fd: h, name: name}}
- runtime.SetFinalizer(f.file, (*file).close)
- return f
+ return newFile(h, name)
}
// Auxiliary information if the File describes a directory
@@ -53,6 +69,10 @@ 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"
@@ -62,30 +82,45 @@ 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, &PathError{"open", name, e}
- }
-
- // There's a race here with fork/exec, which we are
- // content to live with. See ../syscall/exec.go
- if syscall.O_CLOEXEC == 0 { // O_CLOEXEC not supported
- syscall.CloseOnExec(r)
+ 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(syscall.StringToUTF16Ptr(name+`\*`), &d.data)
+ r, e := syscall.FindFirstFile(maskp, &d.data)
if e != nil {
- return nil, &PathError{"open", name, e}
+ // 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) {
cwd, _ := Getwd()
d.path = cwd + `\` + d.path
}
- f := NewFile(uintptr(r), name)
+ f := newFile(r, name)
f.dirinfo = d
return f, nil
}
@@ -112,7 +147,7 @@ func OpenFile(name string, flag int, perm FileMode) (file *File, err error) {
if e == nil {
return r, nil
}
- return nil, e
+ return nil, &PathError{"open", name, e}
}
// Close closes the File, rendering it unusable for I/O.
@@ -122,7 +157,14 @@ func (file *File) Close() error {
}
func (file *file) close() error {
- if file == nil || file.fd == syscall.InvalidHandle {
+ 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
@@ -143,12 +185,15 @@ func (file *file) close() error {
}
func (file *File) readdir(n int) (fi []FileInfo, err error) {
- if file == nil || file.fd == syscall.InvalidHandle {
+ 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 {
@@ -157,7 +202,7 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) {
}
fi = make([]FileInfo, 0, size) // Empty with room to grow.
d := &file.dirinfo.data
- for n != 0 {
+ for n != 0 && !file.dirinfo.isempty {
if file.dirinfo.needdata {
e := syscall.FindNextFile(syscall.Handle(file.fd), d)
if e != nil {
@@ -178,11 +223,16 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) {
continue
}
f := &fileStat{
- name: name,
- size: mkSize(d.FileSizeHigh, d.FileSizeLow),
- modTime: mkModTime(d.LastWriteTime),
- mode: mkMode(d.FileAttributes),
- sys: mkSys(file.dirinfo.path+`\`+name, d.LastAccessTime, d.CreationTime),
+ 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)
@@ -193,11 +243,48 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) {
return fi, nil
}
+// readConsole reads utf16 charcters 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 {
+ // get more input data from os
+ wchars := make([]uint16, len(b))
+ 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)
}
@@ -224,11 +311,57 @@ func (f *File) pread(b []byte, off int64) (n int, err error) {
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)
}
@@ -282,11 +415,14 @@ func Truncate(name string, size int64) error {
// Remove removes the named file or directory.
// If there is an error, it will be of type *PathError.
func Remove(name string) error {
- p := &syscall.StringToUTF16(name)[0]
+ 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)
+ e = syscall.DeleteFile(p)
if e == nil {
return nil
}
diff --git a/src/pkg/os/getwd.go b/src/pkg/os/getwd.go
index 81d8fed92..1b2212306 100644
--- a/src/pkg/os/getwd.go
+++ b/src/pkg/os/getwd.go
@@ -5,9 +5,15 @@
package os
import (
+ "sync"
"syscall"
)
+var getwdCache struct {
+ sync.Mutex
+ dir string
+}
+
// 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),
@@ -35,6 +41,17 @@ func Getwd() (pwd string, err error) {
}
}
+ // Apply same kludge but to cached dir instead of $PWD.
+ getwdCache.Lock()
+ pwd = getwdCache.dir
+ getwdCache.Unlock()
+ if len(pwd) > 0 {
+ d, err := Stat(pwd)
+ if err == nil && SameFile(dot, d) {
+ return pwd, nil
+ }
+ }
+
// Root is a special case because it has no parent
// and ends in a slash.
root, err := Stat("/")
@@ -88,5 +105,11 @@ func Getwd() (pwd string, err error) {
// Set up for next round.
dot = pd
}
+
+ // Save answer as hint to avoid the expensive path next time.
+ getwdCache.Lock()
+ getwdCache.dir = pwd
+ getwdCache.Unlock()
+
return pwd, nil
}
diff --git a/src/pkg/os/os_test.go b/src/pkg/os/os_test.go
index dec80cc09..29706015d 100644
--- a/src/pkg/os/os_test.go
+++ b/src/pkg/os/os_test.go
@@ -6,6 +6,7 @@ package os_test
import (
"bytes"
+ "flag"
"fmt"
"io"
"io/ioutil"
@@ -40,7 +41,6 @@ var sysdir = func() (sd *sysDir) {
sd = &sysDir{
Getenv("SystemRoot") + "\\system32\\drivers\\etc",
[]string{
- "hosts",
"networks",
"protocol",
"services",
@@ -69,10 +69,10 @@ var sysdir = func() (sd *sysDir) {
func size(name string, t *testing.T) int64 {
file, err := Open(name)
- defer file.Close()
if err != nil {
t.Fatal("open failed:", err)
}
+ defer file.Close()
var buf [100]byte
len := 0
for {
@@ -134,10 +134,10 @@ func TestStat(t *testing.T) {
func TestFstat(t *testing.T) {
path := sfdir + "/" + sfname
file, err1 := Open(path)
- defer file.Close()
if err1 != nil {
t.Fatal("open failed:", err1)
}
+ defer file.Close()
dir, err2 := file.Stat()
if err2 != nil {
t.Fatal("fstat failed:", err2)
@@ -189,10 +189,10 @@ func TestRead0(t *testing.T) {
func testReaddirnames(dir string, contents []string, t *testing.T) {
file, err := Open(dir)
- defer file.Close()
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)
@@ -218,10 +218,10 @@ func testReaddirnames(dir string, contents []string, t *testing.T) {
func testReaddir(dir string, contents []string, t *testing.T) {
file, err := Open(dir)
- defer file.Close()
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)
@@ -285,10 +285,10 @@ func TestReaddirnamesOneAtATime(t *testing.T) {
dir = "/bin"
}
file, err := Open(dir)
- defer file.Close()
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)
@@ -310,8 +310,7 @@ func TestReaddirnamesOneAtATime(t *testing.T) {
func TestReaddirNValues(t *testing.T) {
if testing.Short() {
- t.Logf("test.short; skipping")
- return
+ t.Skip("test.short; skipping")
}
dir, err := ioutil.TempDir("", "")
if err != nil {
@@ -535,8 +534,10 @@ func exec(t *testing.T, dir, cmd string, args []string, expect string) {
var b bytes.Buffer
io.Copy(&b, r)
output := b.String()
- // Accept /usr prefix because Solaris /bin is symlinked to /usr/bin.
- if output != expect && output != "/usr"+expect {
+
+ 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)
}
@@ -544,15 +545,13 @@ func exec(t *testing.T, dir, cmd string, args []string, expect string) {
}
func TestStartProcess(t *testing.T) {
- var dir, cmd, le string
+ var dir, cmd string
var args []string
if runtime.GOOS == "windows" {
- le = "\r\n"
cmd = Getenv("COMSPEC")
dir = Getenv("SystemRoot")
args = []string{"/c", "cd"}
} else {
- le = "\n"
cmd = "/bin/pwd"
dir = "/"
args = []string{}
@@ -560,9 +559,9 @@ func TestStartProcess(t *testing.T) {
cmddir, cmdbase := filepath.Split(cmd)
args = append([]string{cmdbase}, args...)
// Test absolute executable path.
- exec(t, dir, cmd, args, dir+le)
+ exec(t, dir, cmd, args, dir)
// Test relative executable path.
- exec(t, cmddir, cmdbase, args, filepath.Clean(cmddir)+le)
+ exec(t, cmddir, cmdbase, args, cmddir)
}
func checkMode(t *testing.T, path string, mode FileMode) {
@@ -1066,3 +1065,52 @@ func TestDevNullFile(t *testing.T) {
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)
+ }
+}
diff --git a/src/pkg/os/path_plan9.go b/src/pkg/os/path_plan9.go
index 3121b7bc7..64bad500a 100644
--- a/src/pkg/os/path_plan9.go
+++ b/src/pkg/os/path_plan9.go
@@ -5,8 +5,8 @@
package os
const (
- PathSeparator = '/' // OS-specific path separator
- PathListSeparator = 0 // OS-specific path list separator
+ PathSeparator = '/' // OS-specific path separator
+ PathListSeparator = '\000' // OS-specific path list separator
)
// IsPathSeparator returns true if c is a directory separator character.
diff --git a/src/pkg/os/path_test.go b/src/pkg/os/path_test.go
index c1e3fb354..16c4120dc 100644
--- a/src/pkg/os/path_test.go
+++ b/src/pkg/os/path_test.go
@@ -5,6 +5,7 @@
package os_test
import (
+ "io/ioutil"
. "os"
"path/filepath"
"runtime"
@@ -167,24 +168,26 @@ func TestRemoveAll(t *testing.T) {
func TestMkdirAllWithSymlink(t *testing.T) {
if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
- t.Log("Skipping test: symlinks don't exist under Windows/Plan 9")
- return
+ t.Skip("Skipping test: symlinks don't exist under Windows/Plan 9")
}
- tmpDir := TempDir()
+ tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer RemoveAll(tmpDir)
+
dir := tmpDir + "/dir"
- err := Mkdir(dir, 0755)
+ err = Mkdir(dir, 0755)
if err != nil {
t.Fatalf("Mkdir %s: %s", dir, err)
}
- defer RemoveAll(dir)
link := tmpDir + "/link"
err = Symlink("dir", link)
if err != nil {
t.Fatalf("Symlink %s: %s", link, err)
}
- defer RemoveAll(link)
path := link + "/foo"
err = MkdirAll(path, 0755)
diff --git a/src/pkg/os/pipe_bsd.go b/src/pkg/os/pipe_bsd.go
new file mode 100644
index 000000000..a2ce9a39f
--- /dev/null
+++ b/src/pkg/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 freebsd netbsd openbsd
+
+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/pkg/os/pipe_linux.go b/src/pkg/os/pipe_linux.go
new file mode 100644
index 000000000..9bafad84f
--- /dev/null
+++ b/src/pkg/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/pkg/os/proc.go b/src/pkg/os/proc.go
index 61545f445..38c436ec5 100644
--- a/src/pkg/os/proc.go
+++ b/src/pkg/os/proc.go
@@ -31,4 +31,6 @@ func Getgroups() ([]int, error) {
// 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/pkg/os/signal/example_test.go b/src/pkg/os/signal/example_test.go
new file mode 100644
index 000000000..600ed315d
--- /dev/null
+++ b/src/pkg/os/signal/example_test.go
@@ -0,0 +1,19 @@
+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/pkg/os/signal/signal_test.go b/src/pkg/os/signal/signal_test.go
index 3494f8c34..509b273aa 100644
--- a/src/pkg/os/signal/signal_test.go
+++ b/src/pkg/os/signal/signal_test.go
@@ -8,6 +8,7 @@ package signal
import (
"os"
+ "runtime"
"syscall"
"testing"
"time"
@@ -58,3 +59,43 @@ func TestSignal(t *testing.T) {
// 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)
+ 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
+}
diff --git a/src/pkg/os/signal/signal_windows_test.go b/src/pkg/os/signal/signal_windows_test.go
index 8d807ff7b..26712f35b 100644
--- a/src/pkg/os/signal/signal_windows_test.go
+++ b/src/pkg/os/signal/signal_windows_test.go
@@ -5,16 +5,16 @@
package signal
import (
- "flag"
+ "bytes"
"os"
+ "os/exec"
+ "path/filepath"
"syscall"
"testing"
"time"
)
-var runCtrlBreakTest = flag.Bool("run_ctlbrk_test", false, "force to run Ctrl+Break test")
-
-func sendCtrlBreak(t *testing.T) {
+func sendCtrlBreak(t *testing.T, pid int) {
d, e := syscall.LoadDLL("kernel32.dll")
if e != nil {
t.Fatalf("LoadDLL: %v\n", e)
@@ -23,29 +23,74 @@ func sendCtrlBreak(t *testing.T) {
if e != nil {
t.Fatalf("FindProc: %v\n", e)
}
- r, _, e := p.Call(0, 0)
+ r, _, e := p.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid))
if r == 0 {
t.Fatalf("GenerateConsoleCtrlEvent: %v\n", e)
}
}
func TestCtrlBreak(t *testing.T) {
- if !*runCtrlBreakTest {
- t.Logf("test disabled; use -run_ctlbrk_test to enable")
- return
- }
- go func() {
- time.Sleep(1 * time.Second)
- sendCtrlBreak(t)
- }()
+ // create source file
+ const source = `
+package main
+
+import (
+ "log"
+ "os"
+ "os/signal"
+ "time"
+)
+
+
+func main() {
c := make(chan os.Signal, 10)
- Notify(c)
+ signal.Notify(c)
select {
case s := <-c:
if s != os.Interrupt {
- t.Fatalf("Wrong signal received: got %q, want %q\n", s, os.Interrupt)
+ log.Fatalf("Wrong signal received: got %q, want %q\n", s, os.Interrupt)
}
case <-time.After(3 * time.Second):
- t.Fatalf("Timeout waiting for Ctrl+Break\n")
+ log.Fatalf("Timeout waiting for Ctrl+Break\n")
+ }
+}
+`
+ name := filepath.Join(os.TempDir(), "ctlbreak")
+ src := name + ".go"
+ defer os.Remove(src)
+ f, err := os.Create(src)
+ if err != nil {
+ t.Fatalf("Failed to create %v: %v", src, err)
+ }
+ 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/pkg/os/stat_darwin.go b/src/pkg/os/stat_darwin.go
index 2e5967d5c..0eea52201 100644
--- a/src/pkg/os/stat_darwin.go
+++ b/src/pkg/os/stat_darwin.go
@@ -9,9 +9,9 @@ import (
"time"
)
-func sameFile(sys1, sys2 interface{}) bool {
- stat1 := sys1.(*syscall.Stat_t)
- stat2 := sys2.(*syscall.Stat_t)
+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
}
diff --git a/src/pkg/os/stat_freebsd.go b/src/pkg/os/stat_freebsd.go
index 6ba84f438..2ffb60fe2 100644
--- a/src/pkg/os/stat_freebsd.go
+++ b/src/pkg/os/stat_freebsd.go
@@ -9,9 +9,9 @@ import (
"time"
)
-func sameFile(sys1, sys2 interface{}) bool {
- stat1 := sys1.(*syscall.Stat_t)
- stat2 := sys2.(*syscall.Stat_t)
+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
}
diff --git a/src/pkg/os/stat_linux.go b/src/pkg/os/stat_linux.go
index 00506b2b6..605c1d9b6 100644
--- a/src/pkg/os/stat_linux.go
+++ b/src/pkg/os/stat_linux.go
@@ -9,9 +9,9 @@ import (
"time"
)
-func sameFile(sys1, sys2 interface{}) bool {
- stat1 := sys1.(*syscall.Stat_t)
- stat2 := sys2.(*syscall.Stat_t)
+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
}
diff --git a/src/pkg/os/stat_netbsd.go b/src/pkg/os/stat_netbsd.go
index 00506b2b6..2ffb60fe2 100644
--- a/src/pkg/os/stat_netbsd.go
+++ b/src/pkg/os/stat_netbsd.go
@@ -9,9 +9,9 @@ import (
"time"
)
-func sameFile(sys1, sys2 interface{}) bool {
- stat1 := sys1.(*syscall.Stat_t)
- stat2 := sys2.(*syscall.Stat_t)
+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
}
@@ -19,7 +19,7 @@ func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo {
fs := &fileStat{
name: basename(name),
size: int64(st.Size),
- modTime: timespecToTime(st.Mtim),
+ modTime: timespecToTime(st.Mtimespec),
sys: st,
}
fs.mode = FileMode(st.Mode & 0777)
@@ -57,5 +57,5 @@ func timespecToTime(ts syscall.Timespec) time.Time {
// For testing.
func atime(fi FileInfo) time.Time {
- return timespecToTime(fi.Sys().(*syscall.Stat_t).Atim)
+ return timespecToTime(fi.Sys().(*syscall.Stat_t).Atimespec)
}
diff --git a/src/pkg/os/stat_openbsd.go b/src/pkg/os/stat_openbsd.go
index 00506b2b6..605c1d9b6 100644
--- a/src/pkg/os/stat_openbsd.go
+++ b/src/pkg/os/stat_openbsd.go
@@ -9,9 +9,9 @@ import (
"time"
)
-func sameFile(sys1, sys2 interface{}) bool {
- stat1 := sys1.(*syscall.Stat_t)
- stat2 := sys2.(*syscall.Stat_t)
+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
}
diff --git a/src/pkg/os/stat_plan9.go b/src/pkg/os/stat_plan9.go
index a7990a359..25c9a8c14 100644
--- a/src/pkg/os/stat_plan9.go
+++ b/src/pkg/os/stat_plan9.go
@@ -9,13 +9,13 @@ import (
"time"
)
-func sameFile(sys1, sys2 interface{}) bool {
- a := sys1.(*Dir)
- b := sys2.(*Dir)
+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 *Dir) FileInfo {
+func fileInfoFromStat(d *syscall.Dir) FileInfo {
fs := &fileStat{
name: d.Name,
size: int64(d.Length),
@@ -38,8 +38,8 @@ func fileInfoFromStat(d *Dir) FileInfo {
return fs
}
-// arg is an open *File or a path string.
-func dirstat(arg interface{}) (d *Dir, err error) {
+// 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
@@ -50,41 +50,45 @@ func dirstat(arg interface{}) (d *Dir, err error) {
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(name, buf)
+ 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, errShortStat}
+ return nil, &PathError{"stat", name, syscall.ErrShortStat}
}
// Pull the real size out of the stat message.
- s, _ := gbit16(buf)
- size = int(s)
+ 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 {
- d, err = UnmarshalDir(buf[:n])
- if err != nil {
- return nil, &PathError{"stat", name, err}
- }
- return
+ 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, errBadStat}
+ return nil, &PathError{"stat", name, syscall.ErrBadStat}
}
-// Stat returns a FileInfo structure describing the named file.
+// Stat returns a FileInfo describing the named file.
// If there is an error, it will be of type *PathError.
-func Stat(name string) (FileInfo, error) {
+func Stat(name string) (fi FileInfo, err error) {
d, err := dirstat(name)
if err != nil {
return nil, err
@@ -92,15 +96,15 @@ func Stat(name string) (FileInfo, error) {
return fileInfoFromStat(d), nil
}
-// Lstat returns the FileInfo structure describing the named file.
-// If the file is a symbolic link (though Plan 9 does not have symbolic links),
-// the returned FileInfo describes the symbolic link. Lstat makes no attempt to follow the link.
+// 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) (FileInfo, error) {
+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().(*Dir).Atime), 0)
+ return time.Unix(int64(fi.Sys().(*syscall.Dir).Atime), 0)
}
diff --git a/src/pkg/os/stat_windows.go b/src/pkg/os/stat_windows.go
index 75351c805..8394c2b32 100644
--- a/src/pkg/os/stat_windows.go
+++ b/src/pkg/os/stat_windows.go
@@ -5,9 +5,7 @@
package os
import (
- "sync"
"syscall"
- "time"
"unsafe"
)
@@ -22,7 +20,7 @@ func (file *File) Stat() (fi FileInfo, err error) {
return Stat(file.name)
}
if file.name == DevNull {
- return statDevNull()
+ return &devNullStat, nil
}
var d syscall.ByHandleFileInformation
e := syscall.GetFileInformationByHandle(syscall.Handle(file.fd), &d)
@@ -30,11 +28,18 @@ func (file *File) Stat() (fi FileInfo, err error) {
return nil, &PathError{"GetFileInformationByHandle", file.name, e}
}
return &fileStat{
- name: basename(file.name),
- size: mkSize(d.FileSizeHigh, d.FileSizeLow),
- modTime: mkModTime(d.LastWriteTime),
- mode: mkMode(d.FileAttributes),
- sys: mkSysFromFI(&d),
+ 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
}
@@ -45,25 +50,23 @@ func Stat(name string) (fi FileInfo, err error) {
return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
}
if name == DevNull {
- return statDevNull()
+ return &devNullStat, nil
+ }
+ fs := &fileStat{name: basename(name)}
+ namep, e := syscall.UTF16PtrFromString(name)
+ if e != nil {
+ return nil, &PathError{"Stat", name, e}
}
- var d syscall.Win32FileAttributeData
- e := syscall.GetFileAttributesEx(syscall.StringToUTF16Ptr(name), syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&d)))
+ e = syscall.GetFileAttributesEx(namep, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fs.sys)))
if e != nil {
return nil, &PathError{"GetFileAttributesEx", name, e}
}
- path := name
- if !isAbs(path) {
+ fs.path = name
+ if !isAbs(fs.path) {
cwd, _ := Getwd()
- path = cwd + `\` + path
+ fs.path = cwd + `\` + fs.path
}
- return &fileStat{
- name: basename(name),
- size: mkSize(d.FileSizeHigh, d.FileSizeLow),
- modTime: mkModTime(d.LastWriteTime),
- mode: mkMode(d.FileAttributes),
- sys: mkSys(path, d.LastAccessTime, d.CreationTime),
- }, nil
+ return fs, nil
}
// Lstat returns the FileInfo structure describing the named file.
@@ -75,22 +78,6 @@ func Lstat(name string) (fi FileInfo, err error) {
return Stat(name)
}
-// statDevNull return FileInfo structure describing DevNull file ("NUL").
-// It creates invented data, since none of windows api will return
-// that information.
-func statDevNull() (fi FileInfo, err error) {
- return &fileStat{
- name: DevNull,
- mode: ModeDevice | ModeCharDevice | 0666,
- sys: &winSys{
- // hopefully this will work for SameFile
- vol: 0,
- idxhi: 0,
- idxlo: 0,
- },
- }, nil
-}
-
// basename removes trailing slashes and the leading
// directory name and drive letter from path name.
func basename(name string) string {
@@ -168,91 +155,3 @@ func volumeName(path string) (v string) {
}
return ""
}
-
-type winSys struct {
- sync.Mutex
- path string
- atime, ctime syscall.Filetime
- vol, idxhi, idxlo uint32
-}
-
-func mkSize(hi, lo uint32) int64 {
- return int64(hi)<<32 + int64(lo)
-}
-
-func mkModTime(mtime syscall.Filetime) time.Time {
- return time.Unix(0, mtime.Nanoseconds())
-}
-
-func mkMode(fa uint32) (m FileMode) {
- if fa&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
- m |= ModeDir
- }
- if fa&syscall.FILE_ATTRIBUTE_READONLY != 0 {
- m |= 0444
- } else {
- m |= 0666
- }
- return m
-}
-
-func mkSys(path string, atime, ctime syscall.Filetime) *winSys {
- return &winSys{
- path: path,
- atime: atime,
- ctime: ctime,
- }
-}
-
-func mkSysFromFI(i *syscall.ByHandleFileInformation) *winSys {
- return &winSys{
- atime: i.LastAccessTime,
- ctime: i.CreationTime,
- vol: i.VolumeSerialNumber,
- idxhi: i.FileIndexHigh,
- idxlo: i.FileIndexLow,
- }
-}
-
-func (s *winSys) loadFileId() error {
- if s.path == "" {
- // already done
- return nil
- }
- s.Lock()
- defer s.Unlock()
- h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
- if e != nil {
- return e
- }
- defer syscall.CloseHandle(h)
- var i syscall.ByHandleFileInformation
- e = syscall.GetFileInformationByHandle(syscall.Handle(h), &i)
- if e != nil {
- return e
- }
- s.path = ""
- s.vol = i.VolumeSerialNumber
- s.idxhi = i.FileIndexHigh
- s.idxlo = i.FileIndexLow
- return nil
-}
-
-func sameFile(sys1, sys2 interface{}) bool {
- s1 := sys1.(*winSys)
- s2 := sys2.(*winSys)
- e := s1.loadFileId()
- if e != nil {
- panic(e)
- }
- e = s2.loadFileId()
- if e != nil {
- panic(e)
- }
- return s1.vol == s2.vol && s1.idxhi == s2.idxhi && s1.idxlo == s2.idxlo
-}
-
-// For testing.
-func atime(fi FileInfo) time.Time {
- return time.Unix(0, fi.Sys().(*winSys).atime.Nanoseconds())
-}
diff --git a/src/pkg/os/types.go b/src/pkg/os/types.go
index 0c95c9cec..473d431d4 100644
--- a/src/pkg/os/types.go
+++ b/src/pkg/os/types.go
@@ -12,7 +12,7 @@ import (
// 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
+// 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
@@ -88,26 +88,19 @@ 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
}
-// 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) Name() string { return fs.name }
-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) IsDir() bool { return fs.mode.IsDir() }
-func (fs *fileStat) Sys() interface{} { return fs.sys }
+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
@@ -121,5 +114,5 @@ func SameFile(fi1, fi2 FileInfo) bool {
if !ok1 || !ok2 {
return false
}
- return sameFile(fs1.sys, fs2.sys)
+ return sameFile(fs1, fs2)
}
diff --git a/src/pkg/os/types_notwin.go b/src/pkg/os/types_notwin.go
new file mode 100644
index 000000000..ea1a07393
--- /dev/null
+++ b/src/pkg/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/pkg/os/types_windows.go b/src/pkg/os/types_windows.go
new file mode 100644
index 000000000..38901681e
--- /dev/null
+++ b/src/pkg/os/types_windows.go
@@ -0,0 +1,104 @@
+// 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
+ }
+ 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/pkg/os/user/lookup.go b/src/pkg/os/user/lookup.go
new file mode 100644
index 000000000..09f00c7bd
--- /dev/null
+++ b/src/pkg/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/pkg/os/user/lookup_stubs.go b/src/pkg/os/user/lookup_stubs.go
index 415f869f2..ad06907b5 100644
--- a/src/pkg/os/user/lookup_stubs.go
+++ b/src/pkg/os/user/lookup_stubs.go
@@ -15,14 +15,14 @@ func init() {
implemented = false
}
-func Current() (*User, error) {
+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) {
+func lookup(username string) (*User, error) {
return nil, fmt.Errorf("user: Lookup not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}
-func LookupId(string) (*User, error) {
+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/pkg/os/user/lookup_unix.go b/src/pkg/os/user/lookup_unix.go
index 241957c33..609542263 100644
--- a/src/pkg/os/user/lookup_unix.go
+++ b/src/pkg/os/user/lookup_unix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build darwin freebsd linux
+// +build darwin freebsd linux netbsd openbsd
// +build cgo
package user
@@ -29,28 +29,23 @@ static int mygetpwuid_r(int uid, struct passwd *pwd,
*/
import "C"
-// Current returns the current user.
-func Current() (*User, error) {
- return lookup(syscall.Getuid(), "", false)
+func current() (*User, error) {
+ return lookupUnix(syscall.Getuid(), "", false)
}
-// 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(-1, username, true)
+func lookup(username string) (*User, error) {
+ return lookupUnix(-1, username, true)
}
-// 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) {
+func lookupId(uid string) (*User, error) {
i, e := strconv.Atoi(uid)
if e != nil {
return nil, e
}
- return lookup(i, "", false)
+ return lookupUnix(i, "", false)
}
-func lookup(uid int, username string, lookupByName bool) (*User, error) {
+func lookupUnix(uid int, username string, lookupByName bool) (*User, error) {
var pwd C.struct_passwd
var result *C.struct_passwd
diff --git a/src/pkg/os/user/lookup_windows.go b/src/pkg/os/user/lookup_windows.go
index 993687115..a0a8a4ec1 100644
--- a/src/pkg/os/user/lookup_windows.go
+++ b/src/pkg/os/user/lookup_windows.go
@@ -16,13 +16,21 @@ func lookupFullName(domain, username, domainAndUser string) (string, error) {
syscall.NameSamCompatible, syscall.NameDisplay, 50)
if e != nil {
// domain lookup failed, perhaps this pc is not part of domain
- d := syscall.StringToUTF16Ptr(domain)
- u := syscall.StringToUTF16Ptr(username)
- var p *byte
- e := syscall.NetUserGetInfo(d, u, 10, &p)
+ d, e := syscall.UTF16PtrFromString(domain)
+ if e != nil {
+ return "", e
+ }
+ u, e := syscall.UTF16PtrFromString(username)
if e != nil {
return "", e
}
+ var p *byte
+ e = syscall.NetUserGetInfo(d, u, 10, &p)
+ if e != nil {
+ // path executed when a domain user is disconnected from the domain
+ // pretend username is fullname
+ return username, nil
+ }
defer syscall.NetApiBufferFree(p)
i := (*syscall.UserInfo10)(unsafe.Pointer(p))
if i.FullName == nil {
@@ -60,8 +68,7 @@ func newUser(usid *syscall.SID, gid, dir string) (*User, error) {
return u, nil
}
-// Current returns the current user.
-func Current() (*User, error) {
+func current() (*User, error) {
t, e := syscall.OpenCurrentProcessToken()
if e != nil {
return nil, e
@@ -95,8 +102,7 @@ func newUserFromSid(usid *syscall.SID) (*User, error) {
return newUser(usid, gid, dir)
}
-// Lookup looks up a user by username.
-func Lookup(username string) (*User, error) {
+func lookup(username string) (*User, error) {
sid, _, t, e := syscall.LookupSID("", username)
if e != nil {
return nil, e
@@ -107,8 +113,7 @@ func Lookup(username string) (*User, error) {
return newUserFromSid(sid)
}
-// LookupId looks up a user by userid.
-func LookupId(uid string) (*User, error) {
+func lookupId(uid string) (*User, error) {
sid, e := syscall.StringToSid(uid)
if e != nil {
return nil, e
diff --git a/src/pkg/os/user/user_test.go b/src/pkg/os/user/user_test.go
index b812ebce7..444a9aacd 100644
--- a/src/pkg/os/user/user_test.go
+++ b/src/pkg/os/user/user_test.go
@@ -5,41 +5,34 @@
package user
import (
- "os"
"runtime"
"testing"
)
-func skip(t *testing.T) bool {
+func check(t *testing.T) {
if !implemented {
- t.Logf("user: not implemented; skipping tests")
- return true
+ t.Skip("user: not implemented; skipping tests")
}
-
switch runtime.GOOS {
case "linux", "freebsd", "darwin", "windows":
- return false
+ // test supported
+ default:
+ t.Skipf("user: Lookup not implemented on %q; skipping test", runtime.GOOS)
}
-
- t.Logf("user: Lookup not implemented on %s; skipping test", runtime.GOOS)
- return true
}
func TestCurrent(t *testing.T) {
- if skip(t) {
- return
- }
+ check(t)
u, err := Current()
if err != nil {
t.Fatalf("Current: %v", err)
}
- fi, err := os.Stat(u.HomeDir)
- if err != nil || !fi.IsDir() {
- t.Errorf("expected a valid HomeDir; stat(%q): err=%v", u.HomeDir, err)
+ if u.HomeDir == "" {
+ t.Errorf("didn't get a HomeDir")
}
if u.Username == "" {
- t.Fatalf("didn't get a username")
+ t.Errorf("didn't get a username")
}
}
@@ -55,8 +48,7 @@ func compare(t *testing.T, want, got *User) {
}
// TODO(brainman): fix it once we know how.
if runtime.GOOS == "windows" {
- t.Log("skipping Gid and HomeDir comparisons")
- return
+ t.Skip("skipping Gid and HomeDir comparisons")
}
if want.Gid != got.Gid {
t.Errorf("got Gid=%q; want %q", got.Gid, want.Gid)
@@ -67,9 +59,7 @@ func compare(t *testing.T, want, got *User) {
}
func TestLookup(t *testing.T) {
- if skip(t) {
- return
- }
+ check(t)
want, err := Current()
if err != nil {
@@ -83,9 +73,7 @@ func TestLookup(t *testing.T) {
}
func TestLookupId(t *testing.T) {
- if skip(t) {
- return
- }
+ check(t)
want, err := Current()
if err != nil {