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