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