diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
commit | 5ff4c17907d5b19510a62e08fd8d3b11e62b431d (patch) | |
tree | c0650497e988f47be9c6f2324fa692a52dea82e1 /src/pkg/os | |
parent | 80f18fc933cf3f3e829c5455a1023d69f7b86e52 (diff) | |
download | golang-upstream/60.tar.gz |
Imported Upstream version 60upstream/60
Diffstat (limited to 'src/pkg/os')
55 files changed, 5662 insertions, 0 deletions
diff --git a/src/pkg/os/Makefile b/src/pkg/os/Makefile new file mode 100644 index 000000000..8923a8b48 --- /dev/null +++ b/src/pkg/os/Makefile @@ -0,0 +1,100 @@ +# 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. + +include ../../Make.inc + +TARG=os +GOFILES=\ + error.go\ + env.go\ + exec.go\ + file.go\ + getwd.go\ + path.go\ + proc.go\ + stat_$(GOOS).go\ + time.go\ + types.go\ + +GOFILES_freebsd=\ + dir_unix.go\ + error_posix.go\ + env_unix.go\ + file_posix.go\ + file_unix.go\ + path_unix.go\ + sys_bsd.go\ + exec_posix.go\ + exec_unix.go\ + signal_unix.go\ + +GOFILES_darwin=\ + dir_unix.go\ + error_posix.go\ + env_unix.go\ + file_posix.go\ + file_unix.go\ + path_unix.go\ + sys_bsd.go\ + exec_posix.go\ + exec_unix.go\ + signal_unix.go\ + +GOFILES_linux=\ + dir_unix.go\ + error_posix.go\ + env_unix.go\ + file_posix.go\ + file_unix.go\ + path_unix.go\ + sys_linux.go\ + exec_posix.go\ + exec_unix.go\ + signal_unix.go\ + +GOFILES_openbsd=\ + dir_unix.go\ + error_posix.go\ + env_unix.go\ + file_posix.go\ + file_unix.go\ + path_unix.go\ + sys_bsd.go\ + exec_posix.go\ + exec_unix.go\ + signal_unix.go\ + +GOFILES_windows=\ + dir_windows.go\ + error_posix.go\ + env_windows.go\ + file_posix.go\ + file_windows.go\ + path_windows.go\ + sys_windows.go\ + exec_posix.go\ + exec_windows.go\ + signal_windows.go\ + +GOFILES_plan9=\ + dir_plan9.go\ + error_plan9.go\ + env_plan9.go\ + file_plan9.go\ + path_plan9.go\ + sys_plan9.go\ + exec_plan9.go\ + str.go\ + +GOFILES+=$(GOFILES_$(GOOS)) + +CLEANFILES+=signal_unix.go signal_windows.go + +include ../../Make.pkg + +signal_unix.go: ../syscall/zerrors_$(GOOS)_$(GOARCH).go + ./mkunixsignals.sh $< > $@ || rm -f $@ + +signal_windows.go: ../syscall/ztypes_$(GOOS).go + ./mkunixsignals.sh $< > $@ || rm -f $@ diff --git a/src/pkg/os/dir_plan9.go b/src/pkg/os/dir_plan9.go new file mode 100644 index 000000000..bbc2cb647 --- /dev/null +++ b/src/pkg/os/dir_plan9.go @@ -0,0 +1,300 @@ +// 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" +) + +// Readdir reads the contents of the directory associated with file and +// returns an array of up to n FileInfo structures, 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 +// Readdirnames returns an empty slice, it will return a non-nil error +// explaining why. At the end of a directory, the error is os.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 os.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 (file *File) Readdir(n int) (fi []FileInfo, err 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 + } + result := make([]FileInfo, 0, size) // Empty with room to grow. + for n != 0 { + // Refill the buffer if necessary + if d.bufp >= d.nbuf { + d.bufp = 0 + var e Error + d.nbuf, e = file.Read(d.buf[:]) + if e != nil && e != EOF { + return result, &PathError{"readdir", file.name, e} + } + if e == EOF { + break + } + if d.nbuf < syscall.STATFIXLEN { + return result, &PathError{"readdir", file.name, Eshortstat} + } + } + + // Get a record from buffer + m, _ := gbit16(d.buf[d.bufp:]) + m += 2 + if m < syscall.STATFIXLEN { + return result, &PathError{"readdir", file.name, Eshortstat} + } + dir, e := UnmarshalDir(d.buf[d.bufp : d.bufp+int(m)]) + if e != nil { + return result, &PathError{"readdir", file.name, e} + } + var f FileInfo + fileInfoFromStat(&f, dir) + result = append(result, f) + + d.bufp += int(m) + n-- + } + + if n >= 0 && len(result) == 0 { + return result, EOF + } + return result, nil +} + +// 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 os.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 os.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 (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 +} + +type Dir struct { + // system-modified data + Type uint16 // server type + Dev uint32 // server subtype + // file data + Qid Qid // unique id from server + Mode uint32 // permissions + Atime uint32 // last read time + Mtime uint32 // last write time + Length uint64 // file length + Name string // last element of path + Uid string // owner name + Gid string // group name + Muid string // last modifier name +} + +type Qid struct { + Path uint64 // the file server's unique identification for the file + Vers uint32 // version number for given Path + Type uint8 // the type of the file (syscall.QTDIR for example) +} + +var nullDir = Dir{ + ^uint16(0), + ^uint32(0), + Qid{^uint64(0), ^uint32(0), ^uint8(0)}, + ^uint32(0), + ^uint32(0), + ^uint32(0), + ^uint64(0), + "", + "", + "", + "", +} + +// Null assigns members of d with special "don't care" values indicating +// they should not be written by syscall.Wstat. +func (d *Dir) Null() { + *d = nullDir +} + +// pdir appends a 9P Stat message based on the contents of Dir d to a byte slice b. +func pdir(b []byte, d *Dir) []byte { + n := len(b) + b = pbit16(b, 0) // length, filled in later + b = pbit16(b, d.Type) + b = pbit32(b, d.Dev) + b = pqid(b, d.Qid) + b = pbit32(b, d.Mode) + b = pbit32(b, d.Atime) + b = pbit32(b, d.Mtime) + b = pbit64(b, d.Length) + b = pstring(b, d.Name) + b = pstring(b, d.Uid) + b = pstring(b, d.Gid) + b = pstring(b, d.Muid) + pbit16(b[0:n], uint16(len(b)-(n+2))) + return b +} + +// UnmarshalDir reads a 9P Stat message from a 9P protocol message stored in b, +// returning the corresponding Dir struct. +func UnmarshalDir(b []byte) (d *Dir, err Error) { + n := uint16(0) + n, b = gbit16(b) + + if int(n) != len(b) { + return nil, Ebadstat + } + + d = new(Dir) + d.Type, b = gbit16(b) + d.Dev, b = gbit32(b) + d.Qid, b = gqid(b) + d.Mode, b = gbit32(b) + d.Atime, b = gbit32(b) + d.Mtime, b = gbit32(b) + d.Length, b = gbit64(b) + d.Name, b = gstring(b) + d.Uid, b = gstring(b) + d.Gid, b = gstring(b) + d.Muid, b = gstring(b) + + if len(b) != 0 { + return nil, Ebadstat + } + + return d, nil +} + +// gqid reads the qid part of a 9P Stat message from a 9P protocol message stored in b, +// returning the corresponding Qid struct and the remaining slice of b. +func gqid(b []byte) (Qid, []byte) { + var q Qid + q.Path, b = gbit64(b) + q.Vers, b = gbit32(b) + q.Type, b = gbit8(b) + return q, b +} + +// pqid appends a Qid struct q to a 9P message b. +func pqid(b []byte, q Qid) []byte { + b = pbit64(b, q.Path) + b = pbit32(b, q.Vers) + b = pbit8(b, q.Type) + return b +} + +// gbit8 reads a byte-sized numeric value from a 9P protocol message stored in b, +// returning the value and the remaining slice of b. +func gbit8(b []byte) (uint8, []byte) { + return uint8(b[0]), b[1:] +} + +// gbit16 reads a 16-bit numeric value from a 9P protocol message stored in b, +// returning the value and the remaining slice of b. +func gbit16(b []byte) (uint16, []byte) { + return uint16(b[0]) | uint16(b[1])<<8, b[2:] +} + +// gbit32 reads a 32-bit numeric value from a 9P protocol message stored in b, +// returning the value and the remaining slice of b. +func gbit32(b []byte) (uint32, []byte) { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24, b[4:] +} + +// gbit64 reads a 64-bit numeric value from a 9P protocol message stored in b, +// returning the value and the remaining slice of b. +func gbit64(b []byte) (uint64, []byte) { + lo, b := gbit32(b) + hi, b := gbit32(b) + return uint64(hi)<<32 | uint64(lo), b +} + +// gstring reads a string from a 9P protocol message stored in b, +// returning the value as a Go string and the remaining slice of b. +func gstring(b []byte) (string, []byte) { + n, b := gbit16(b) + return string(b[0:n]), b[n:] +} + +// pbit8 appends a byte-sized numeric value x to a 9P message b. +func pbit8(b []byte, x uint8) []byte { + n := len(b) + if n+1 > cap(b) { + nb := make([]byte, n, 100+2*cap(b)) + copy(nb, b) + b = nb + } + b = b[0 : n+1] + b[n] = x + return b +} + +// pbit16 appends a 16-bit numeric value x to a 9P message b. +func pbit16(b []byte, x uint16) []byte { + n := len(b) + if n+2 > cap(b) { + nb := make([]byte, n, 100+2*cap(b)) + copy(nb, b) + b = nb + } + b = b[0 : n+2] + b[n] = byte(x) + b[n+1] = byte(x >> 8) + return b +} + +// pbit32 appends a 32-bit numeric value x to a 9P message b. +func pbit32(b []byte, x uint32) []byte { + n := len(b) + if n+4 > cap(b) { + nb := make([]byte, n, 100+2*cap(b)) + copy(nb, b) + b = nb + } + b = b[0 : n+4] + b[n] = byte(x) + b[n+1] = byte(x >> 8) + b[n+2] = byte(x >> 16) + b[n+3] = byte(x >> 24) + return b +} + +// pbit64 appends a 64-bit numeric value x to a 9P message b. +func pbit64(b []byte, x uint64) []byte { + b = pbit32(b, uint32(x)) + b = pbit32(b, uint32(x>>32)) + return b +} + +// pstring appends a Go string s to a 9P message b. +func pstring(b []byte, s string) []byte { + if len(s) >= 1<<16 { + panic(NewError("string too long")) + } + b = pbit16(b, uint16(len(s))) + b = append(b, []byte(s)...) + return b +} diff --git a/src/pkg/os/dir_unix.go b/src/pkg/os/dir_unix.go new file mode 100644 index 000000000..7835ed52b --- /dev/null +++ b/src/pkg/os/dir_unix.go @@ -0,0 +1,67 @@ +// 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" +) + +const ( + blockSize = 4096 +) + +// 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 os.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 os.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 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 int + d.nbuf, errno = syscall.ReadDirent(f.fd, d.buf) + if errno != 0 { + 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, EOF + } + return names, nil +} diff --git a/src/pkg/os/dir_windows.go b/src/pkg/os/dir_windows.go new file mode 100644 index 000000000..d76e88fdb --- /dev/null +++ b/src/pkg/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/pkg/os/env.go b/src/pkg/os/env.go new file mode 100644 index 000000000..3772c090b --- /dev/null +++ b/src/pkg/os/env.go @@ -0,0 +1,75 @@ +// 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 + +func setenv_c(k, v string) + +// Expand replaces ${var} or $var in the string based on the mapping function. +// Invocations of undefined variables are replaced with the empty string. +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, []byte(s[i:j])...) + name, w := getShellName(s[j+1:]) + buf = append(buf, []byte(mapping(name))...) + j += w + i = j + 1 + } + } + return string(buf) + s[i:] +} + +// ShellExpand replaces ${var} or $var in the string according to the values +// of the operating system's environment variables. References to undefined +// variables are replaced by the empty string. +func ShellExpand(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 +} diff --git a/src/pkg/os/env_plan9.go b/src/pkg/os/env_plan9.go new file mode 100644 index 000000000..1fed89f92 --- /dev/null +++ b/src/pkg/os/env_plan9.go @@ -0,0 +1,96 @@ +// 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 environment variables. + +package os + +import "syscall" + +// ENOENV is the Error indicating that an environment variable does not exist. +var ENOENV = NewError("no such environment variable") + +// Getenverror retrieves the value of the environment variable named by the key. +// It returns the value and an error, if any. +func Getenverror(key string) (value string, err Error) { + if len(key) == 0 { + return "", EINVAL + } + f, e := Open("/env/" + key) + if iserror(e) { + return "", ENOENV + } + defer f.Close() + + l, _ := f.Seek(0, 2) + f.Seek(0, 0) + buf := make([]byte, l) + n, e := f.Read(buf) + if iserror(e) { + return "", ENOENV + } + + if n > 0 && buf[n-1] == 0 { + buf = buf[:n-1] + } + return string(buf), nil +} + +// 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, _ := Getenverror(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 { + if len(key) == 0 { + return EINVAL + } + + f, e := Create("/env/" + key) + if iserror(e) { + return e + } + defer f.Close() + + _, e = f.Write([]byte(value)) + return nil +} + +// Clearenv deletes all environment variables. +func Clearenv() { + syscall.RawSyscall(syscall.SYS_RFORK, syscall.RFCENVG, 0, 0) +} + +// Environ returns an array of strings representing the environment, +// in the form "key=value". +func Environ() []string { + env := make([]string, 0, 100) + + f, e := Open("/env") + if iserror(e) { + panic(e) + } + defer f.Close() + + names, e := f.Readdirnames(-1) + if iserror(e) { + panic(e) + } + + for _, k := range names { + if v, e := Getenverror(k); !iserror(e) { + env = append(env, k+"="+v) + } + } + return env[0:len(env)] +} + +// TempDir returns the default directory to use for temporary files. +func TempDir() string { + return "/tmp" +} diff --git a/src/pkg/os/env_test.go b/src/pkg/os/env_test.go new file mode 100644 index 000000000..04ff39072 --- /dev/null +++ b/src/pkg/os/env_test.go @@ -0,0 +1,59 @@ +// 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" + "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) + } + } +} diff --git a/src/pkg/os/env_unix.go b/src/pkg/os/env_unix.go new file mode 100644 index 000000000..9cc0b03d8 --- /dev/null +++ b/src/pkg/os/env_unix.go @@ -0,0 +1,109 @@ +// 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. + +// Unix environment variables. + +package os + +import ( + "sync" +) + +// ENOENV is the Error indicating that an environment variable does not exist. +var ENOENV = NewError("no such environment variable") + +var env map[string]string +var once sync.Once + +func copyenv() { + env = make(map[string]string) + for _, s := range Envs { + for j := 0; j < len(s); j++ { + if s[j] == '=' { + env[s[0:j]] = s[j+1:] + break + } + } + } +} + +var envLock sync.RWMutex + +// Getenverror retrieves the value of the environment variable named by the key. +// It returns the value and an error, if any. +func Getenverror(key string) (value string, err Error) { + once.Do(copyenv) + + if len(key) == 0 { + return "", EINVAL + } + + envLock.RLock() + defer envLock.RUnlock() + + v, ok := env[key] + if !ok { + return "", ENOENV + } + return v, nil +} + +// 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, _ := Getenverror(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 { + once.Do(copyenv) + if len(key) == 0 { + return EINVAL + } + + envLock.Lock() + defer envLock.Unlock() + + env[key] = value + setenv_c(key, value) // is a no-op if cgo isn't loaded + return nil +} + +// Clearenv deletes all environment variables. +func Clearenv() { + once.Do(copyenv) // prevent copyenv in Getenv/Setenv + + envLock.Lock() + defer envLock.Unlock() + + env = make(map[string]string) + + // TODO(bradfitz): pass through to C +} + +// Environ returns an array of strings representing the environment, +// in the form "key=value". +func Environ() []string { + once.Do(copyenv) + envLock.RLock() + defer envLock.RUnlock() + a := make([]string, len(env)) + i := 0 + for k, v := range env { + a[i] = k + "=" + v + i++ + } + return a +} + +// TempDir returns the default directory to use for temporary files. +func TempDir() string { + dir := Getenv("TMPDIR") + if dir == "" { + dir = "/tmp" + } + return dir +} diff --git a/src/pkg/os/env_windows.go b/src/pkg/os/env_windows.go new file mode 100644 index 000000000..e6ddc4065 --- /dev/null +++ b/src/pkg/os/env_windows.go @@ -0,0 +1,127 @@ +// 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. + +// Windows environment variables. + +package os + +import ( + "syscall" + "utf16" + "unsafe" +) + +// ENOENV is the Error indicating that an environment variable does not exist. +var ENOENV = NewError("no such environment variable") + +// Getenverror retrieves the value of the environment variable named by the key. +// It returns the value and an error, if any. +func Getenverror(key string) (value string, err Error) { + b := make([]uint16, 100) + n, e := syscall.GetEnvironmentVariable(syscall.StringToUTF16Ptr(key), &b[0], uint32(len(b))) + if n == 0 && e == syscall.ERROR_ENVVAR_NOT_FOUND { + return "", ENOENV + } + if n > uint32(len(b)) { + b = make([]uint16, n) + n, e = syscall.GetEnvironmentVariable(syscall.StringToUTF16Ptr(key), &b[0], uint32(len(b))) + if n > uint32(len(b)) { + n = 0 + } + } + if n == 0 { + return "", NewSyscallError("GetEnvironmentVariable", e) + } + return string(utf16.Decode(b[0:n])), nil +} + +// 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, _ := Getenverror(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 { + var v *uint16 + if len(value) > 0 { + v = syscall.StringToUTF16Ptr(value) + } + e := syscall.SetEnvironmentVariable(syscall.StringToUTF16Ptr(key), v) + if e != 0 { + return NewSyscallError("SetEnvironmentVariable", e) + } + return nil +} + +// Clearenv deletes all environment variables. +func Clearenv() { + for _, s := range Environ() { + // Environment variables can begin with = + // so start looking for the separator = at j=1. + // http://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx + for j := 1; j < len(s); j++ { + if s[j] == '=' { + Setenv(s[0:j], "") + break + } + } + } +} + +// Environ returns an array of strings representing the environment, +// in the form "key=value". +func Environ() []string { + s, e := syscall.GetEnvironmentStrings() + if e != 0 { + return nil + } + defer syscall.FreeEnvironmentStrings(s) + r := make([]string, 0, 50) // Empty with room to grow. + for from, i, p := 0, 0, (*[1 << 24]uint16)(unsafe.Pointer(s)); true; i++ { + if p[i] == 0 { + // empty string marks the end + if i <= from { + break + } + r = append(r, string(utf16.Decode(p[from:i]))) + from = i + 1 + } + } + return r +} + +// 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])) +} + +func init() { + var argc int32 + cmd := syscall.GetCommandLine() + argv, e := syscall.CommandLineToArgv(cmd, &argc) + if e != 0 { + 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)[:])) + } +} diff --git a/src/pkg/os/error.go b/src/pkg/os/error.go new file mode 100644 index 000000000..b4511dd2f --- /dev/null +++ b/src/pkg/os/error.go @@ -0,0 +1,31 @@ +// 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 + +// An Error can represent any printable error condition. +type Error interface { + String() string +} + +// // errorString is a helper type used by NewError. +type errorString string + +func (e errorString) String() string { return string(e) } + +// Note: If the name of the function NewError changes, +// pkg/go/doc/doc.go should be adjusted since it hardwires +// this name in a heuristic. + +// // NewError returns a new error with error.String() == s. +func NewError(s string) Error { return errorString(s) } + +// PathError records an error and the operation and file path that caused it. +type PathError struct { + Op string + Path string + Error Error +} + +func (e *PathError) String() string { return e.Op + " " + e.Path + ": " + e.Error.String() } diff --git a/src/pkg/os/error_plan9.go b/src/pkg/os/error_plan9.go new file mode 100644 index 000000000..cacfc150c --- /dev/null +++ b/src/pkg/os/error_plan9.go @@ -0,0 +1,61 @@ +// 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 "syscall" + +// SyscallError records an error from a specific system call. +type SyscallError struct { + Syscall string + Err string +} + +func (e *SyscallError) String() string { return e.Syscall + ": " + e.Err } + +// Note: If the name of the function NewSyscallError changes, +// pkg/go/doc/doc.go should be adjusted since it hardwires +// this name in a heuristic. + +// 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 syscall.Error) Error { + if err == nil { + return nil + } + return &SyscallError{syscall, err.String()} +} + +var ( + Eshortstat = NewError("stat buffer too small") + Ebadstat = NewError("malformed stat buffer") + Ebadfd = NewError("fd out of range or not open") + Ebadarg = NewError("bad arg in system call") + Enotdir = NewError("not a directory") + Enonexist = NewError("file does not exist") + Eexist = NewError("file already exists") + Eio = NewError("i/o error") + Eperm = NewError("permission denied") + + EINVAL = Ebadarg + ENOTDIR = Enotdir + ENOENT = Enonexist + EEXIST = Eexist + EIO = Eio + EACCES = Eperm + EPERM = Eperm + EISDIR = syscall.EISDIR + + ENAMETOOLONG = NewError("file name too long") + ERANGE = NewError("math result not representable") + EPIPE = NewError("Broken Pipe") + EPLAN9 = NewError("not supported by plan 9") +) + +func iserror(err syscall.Error) bool { + return err != nil +} + +func Errno(e syscall.Error) syscall.Error { return e } diff --git a/src/pkg/os/error_posix.go b/src/pkg/os/error_posix.go new file mode 100644 index 000000000..d43f1786d --- /dev/null +++ b/src/pkg/os/error_posix.go @@ -0,0 +1,90 @@ +// 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 "syscall" + +// Errno is the Unix error number. Names such as EINVAL are simple +// wrappers to convert the error number into an Error. +type Errno int64 + +func (e Errno) String() string { return syscall.Errstr(int(e)) } + +func (e Errno) Temporary() bool { + return e == Errno(syscall.EINTR) || e == Errno(syscall.EMFILE) || e.Timeout() +} + +func (e Errno) Timeout() bool { + return e == Errno(syscall.EAGAIN) || e == Errno(syscall.EWOULDBLOCK) || e == Errno(syscall.ETIMEDOUT) +} + +// Commonly known Unix errors. +var ( + EPERM Error = Errno(syscall.EPERM) + ENOENT Error = Errno(syscall.ENOENT) + ESRCH Error = Errno(syscall.ESRCH) + EINTR Error = Errno(syscall.EINTR) + EIO Error = Errno(syscall.EIO) + ENXIO Error = Errno(syscall.ENXIO) + E2BIG Error = Errno(syscall.E2BIG) + ENOEXEC Error = Errno(syscall.ENOEXEC) + EBADF Error = Errno(syscall.EBADF) + ECHILD Error = Errno(syscall.ECHILD) + EDEADLK Error = Errno(syscall.EDEADLK) + ENOMEM Error = Errno(syscall.ENOMEM) + EACCES Error = Errno(syscall.EACCES) + EFAULT Error = Errno(syscall.EFAULT) + EBUSY Error = Errno(syscall.EBUSY) + EEXIST Error = Errno(syscall.EEXIST) + EXDEV Error = Errno(syscall.EXDEV) + ENODEV Error = Errno(syscall.ENODEV) + ENOTDIR Error = Errno(syscall.ENOTDIR) + EISDIR Error = Errno(syscall.EISDIR) + EINVAL Error = Errno(syscall.EINVAL) + ENFILE Error = Errno(syscall.ENFILE) + EMFILE Error = Errno(syscall.EMFILE) + ENOTTY Error = Errno(syscall.ENOTTY) + EFBIG Error = Errno(syscall.EFBIG) + ENOSPC Error = Errno(syscall.ENOSPC) + ESPIPE Error = Errno(syscall.ESPIPE) + EROFS Error = Errno(syscall.EROFS) + EMLINK Error = Errno(syscall.EMLINK) + EPIPE Error = Errno(syscall.EPIPE) + EAGAIN Error = Errno(syscall.EAGAIN) + EDOM Error = Errno(syscall.EDOM) + ERANGE Error = Errno(syscall.ERANGE) + EADDRINUSE Error = Errno(syscall.EADDRINUSE) + ECONNREFUSED Error = Errno(syscall.ECONNREFUSED) + ENAMETOOLONG Error = Errno(syscall.ENAMETOOLONG) + EAFNOSUPPORT Error = Errno(syscall.EAFNOSUPPORT) + ETIMEDOUT Error = Errno(syscall.ETIMEDOUT) + ENOTCONN Error = Errno(syscall.ENOTCONN) +) + +// SyscallError records an error from a specific system call. +type SyscallError struct { + Syscall string + Errno Errno +} + +func (e *SyscallError) String() string { return e.Syscall + ": " + e.Errno.String() } + +// Note: If the name of the function NewSyscallError changes, +// pkg/go/doc/doc.go should be adjusted since it hardwires +// this name in a heuristic. + +// NewSyscallError returns, as an Error, a new SyscallError +// with the given system call name and error details. +// As a convenience, if errno is 0, NewSyscallError returns nil. +func NewSyscallError(syscall string, errno int) Error { + if errno == 0 { + return nil + } + return &SyscallError{syscall, Errno(errno)} +} + +func iserror(errno int) bool { + return errno != 0 +} diff --git a/src/pkg/os/exec.go b/src/pkg/os/exec.go new file mode 100644 index 000000000..33e223fd2 --- /dev/null +++ b/src/pkg/os/exec.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. + +package os + +import ( + "runtime" + "syscall" +) + +// Process stores the information about a process created by StartProcess. +type Process struct { + Pid int + handle int + done bool // process has been successfuly waited on +} + +func newProcess(pid, handle int) *Process { + p := &Process{Pid: pid, handle: handle} + runtime.SetFinalizer(p, (*Process).Release) + return p +} + +// 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 can represent any operating system signal. +type Signal interface { + String() string +} + +// 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/pkg/os/exec_plan9.go b/src/pkg/os/exec_plan9.go new file mode 100644 index 000000000..6f0722a22 --- /dev/null +++ b/src/pkg/os/exec_plan9.go @@ -0,0 +1,153 @@ +// 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" + "syscall" +) + +// StartProcess starts a new process with the program, arguments and attributes +// specified by name, argv and attr. +func StartProcess(name string, argv []string, attr *ProcAttr) (p *Process, err Error) { + sysattr := &syscall.ProcAttr{ + Dir: attr.Dir, + Env: attr.Env, + Sys: attr.Sys, + } + + // Create array of integer (system) fds. + intfd := make([]int, len(attr.Files)) + for i, f := range attr.Files { + if f == nil { + intfd[i] = -1 + } else { + intfd[i] = f.Fd() + } + } + + sysattr.Files = intfd + + pid, h, e := syscall.StartProcess(name, argv, sysattr) + if iserror(e) { + return nil, &PathError{"fork/exec", name, e} + } + + return newProcess(pid, h), nil +} + +// Plan9Note implements the Signal interface on Plan 9. +type Plan9Note string + +func (note Plan9Note) String() string { + return string(note) +} + +func (p *Process) Signal(sig Signal) Error { + if p.done { + return NewError("os: process already finished") + } + + f, e := OpenFile("/proc/"+itoa(p.Pid)+"/note", O_WRONLY, 0) + if iserror(e) { + return NewSyscallError("signal", e) + } + defer f.Close() + _, e = f.Write([]byte(sig.String())) + return e +} + +// Kill causes the Process to exit immediately. +func (p *Process) Kill() Error { + f, e := OpenFile("/proc/"+itoa(p.Pid)+"/ctl", O_WRONLY, 0) + if iserror(e) { + return NewSyscallError("kill", e) + } + defer f.Close() + _, e = f.Write([]byte("kill")) + return e +} + +// Exec replaces the current process with an execution of the +// named binary, with arguments argv and environment envv. +// If successful, Exec never returns. If it fails, it returns an Error. +// ForkExec is almost always a better way to execute a program. +func Exec(name string, argv []string, envv []string) Error { + e := syscall.Exec(name, argv, envv) + if iserror(e) { + return &PathError{"exec", name, e} + } + + return nil +} + +// Waitmsg stores the information about an exited process as reported by Wait. +type Waitmsg struct { + syscall.Waitmsg +} + +// Wait waits for the Process to exit or stop, and then returns a +// Waitmsg describing its status and an Error, if any. The options +// (WNOHANG etc.) affect the behavior of the Wait call. +func (p *Process) Wait(options int) (w *Waitmsg, err Error) { + var waitmsg syscall.Waitmsg + + if p.Pid == -1 { + return nil, EINVAL + } + + for true { + err = syscall.Await(&waitmsg) + + if iserror(err) { + return nil, NewSyscallError("wait", err) + } + + if waitmsg.Pid == p.Pid { + p.done = true + break + } + } + + return &Waitmsg{waitmsg}, nil +} + +// Wait waits for process pid to exit or stop, and then returns a +// Waitmsg describing its status and an Error, if any. The options +// (WNOHANG etc.) affect the behavior of the Wait call. +// Wait is equivalent to calling FindProcess and then Wait +// and Release on the result. +func Wait(pid int, options int) (w *Waitmsg, err Error) { + p, e := FindProcess(pid) + if e != nil { + return nil, e + } + defer p.Release() + return p.Wait(options) +} + +// Release releases any resources associated with the Process. +func (p *Process) Release() Error { + // NOOP for Plan 9. + p.Pid = -1 + // no need for a finalizer anymore + runtime.SetFinalizer(p, nil) + return nil +} + +// 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) { + // NOOP for Plan 9. + return newProcess(pid, 0), nil +} + +func (w *Waitmsg) String() string { + if w == nil { + return "<nil>" + } + return "exit status: " + w.Msg +} diff --git a/src/pkg/os/exec_posix.go b/src/pkg/os/exec_posix.go new file mode 100644 index 000000000..f37bfab58 --- /dev/null +++ b/src/pkg/os/exec_posix.go @@ -0,0 +1,147 @@ +// 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" + "syscall" +) + +type UnixSignal int32 + +func (sig UnixSignal) String() string { + s := runtime.Signame(int32(sig)) + if len(s) > 0 { + return s + } + return "UnixSignal" +} + +// StartProcess starts a new process with the program, arguments and attributes +// specified by name, argv and attr. +// +// StartProcess is a low-level interface. The exec package provides +// higher-level interfaces. +func StartProcess(name string, argv []string, attr *ProcAttr) (p *Process, err Error) { + 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 iserror(e) { + return nil, &PathError{"fork/exec", name, Errno(e)} + } + return newProcess(pid, h), nil +} + +// Kill causes the Process to exit immediately. +func (p *Process) Kill() Error { + return p.Signal(SIGKILL) +} + +// Exec replaces the current process with an execution of the +// named binary, with arguments argv and environment envv. +// If successful, Exec never returns. If it fails, it returns an Error. +// +// To run a child process, see StartProcess (for a low-level interface) +// or the exec package (for higher-level interfaces). +func Exec(name string, argv []string, envv []string) Error { + if envv == nil { + envv = Environ() + } + e := syscall.Exec(name, argv, envv) + if iserror(e) { + return &PathError{"exec", name, Errno(e)} + } + return nil +} + +// TODO(rsc): Should os implement its own syscall.WaitStatus +// wrapper with the methods, or is exposing the underlying one enough? +// +// TODO(rsc): Certainly need to have Rusage struct, +// since syscall one might have different field types across +// different OS. + +// Waitmsg stores the information about an exited process as reported by Wait. +type Waitmsg struct { + Pid int // The process's id. + syscall.WaitStatus // System-dependent status info. + Rusage *syscall.Rusage // System-dependent resource usage info. +} + +// Wait waits for process pid to exit or stop, and then returns a +// Waitmsg describing its status and an Error, if any. The options +// (WNOHANG etc.) affect the behavior of the Wait call. +// Wait is equivalent to calling FindProcess and then Wait +// and Release on the result. +func Wait(pid int, options int) (w *Waitmsg, err Error) { + p, e := FindProcess(pid) + if e != nil { + return nil, e + } + defer p.Release() + return p.Wait(options) +} + +// 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 (w *Waitmsg) String() string { + if w == nil { + return "<nil>" + } + // TODO(austin) Use signal names when possible? + res := "" + switch { + case w.Exited(): + res = "exit status " + itod(w.ExitStatus()) + case w.Signaled(): + res = "signal " + itod(w.Signal()) + case w.Stopped(): + res = "stop signal " + itod(w.StopSignal()) + if w.StopSignal() == syscall.SIGTRAP && w.TrapCause() != 0 { + res += " (trap " + itod(w.TrapCause()) + ")" + } + case w.Continued(): + res = "continued" + } + if w.CoreDump() { + res += " (core dumped)" + } + return res +} diff --git a/src/pkg/os/exec_unix.go b/src/pkg/os/exec_unix.go new file mode 100644 index 000000000..8a4b2e1b8 --- /dev/null +++ b/src/pkg/os/exec_unix.go @@ -0,0 +1,77 @@ +// 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" + "syscall" +) + +// Options for Wait. +const ( + WNOHANG = syscall.WNOHANG // Don't wait if no process has exited. + WSTOPPED = syscall.WSTOPPED // If set, status of stopped subprocesses is also reported. + WUNTRACED = syscall.WUNTRACED // Usually an alias for WSTOPPED. + WRUSAGE = 1 << 20 // Record resource usage. +) + +// WRUSAGE must not be too high a bit, to avoid clashing with Linux's +// WCLONE, WALL, and WNOTHREAD flags, which sit in the top few bits of +// the options + +// Wait waits for the Process to exit or stop, and then returns a +// Waitmsg describing its status and an Error, if any. The options +// (WNOHANG etc.) affect the behavior of the Wait call. +func (p *Process) Wait(options int) (w *Waitmsg, err Error) { + if p.Pid == -1 { + return nil, EINVAL + } + var status syscall.WaitStatus + var rusage *syscall.Rusage + if options&WRUSAGE != 0 { + rusage = new(syscall.Rusage) + options ^= WRUSAGE + } + pid1, e := syscall.Wait4(p.Pid, &status, options, rusage) + if e != 0 { + return nil, NewSyscallError("wait", e) + } + if options&WSTOPPED == 0 { + p.done = true + } + w = new(Waitmsg) + w.Pid = pid1 + w.WaitStatus = status + w.Rusage = rusage + return w, nil +} + +// Signal sends a signal to the Process. +func (p *Process) Signal(sig Signal) Error { + if p.done { + return NewError("os: process already finished") + } + if e := syscall.Kill(p.Pid, int(sig.(UnixSignal))); e != 0 { + return Errno(e) + } + return nil +} + +// Release releases any resources associated with the Process. +func (p *Process) Release() Error { + // NOOP for unix. + p.Pid = -1 + // no need for a finalizer anymore + runtime.SetFinalizer(p, nil) + return nil +} + +// 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) { + // NOOP for unix. + return newProcess(pid, 0), nil +} diff --git a/src/pkg/os/exec_windows.go b/src/pkg/os/exec_windows.go new file mode 100644 index 000000000..65e94ac4a --- /dev/null +++ b/src/pkg/os/exec_windows.go @@ -0,0 +1,66 @@ +// 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" + "syscall" +) + +func (p *Process) Wait(options int) (w *Waitmsg, 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, NewError("os: unexpected result from WaitForSingleObject") + } + var ec uint32 + e = syscall.GetExitCodeProcess(syscall.Handle(p.handle), &ec) + if e != 0 { + return nil, NewSyscallError("GetExitCodeProcess", e) + } + p.done = true + return &Waitmsg{p.Pid, syscall.WaitStatus{s, ec}, new(syscall.Rusage)}, nil +} + +// Signal sends a signal to the Process. +func (p *Process) Signal(sig Signal) Error { + if p.done { + return NewError("os: process already finished") + } + switch sig.(UnixSignal) { + case SIGKILL: + e := syscall.TerminateProcess(syscall.Handle(p.handle), 1) + return NewSyscallError("TerminateProcess", e) + } + return Errno(syscall.EWINDOWS) +} + +func (p *Process) Release() Error { + if p.handle == -1 { + return EINVAL + } + e := syscall.CloseHandle(syscall.Handle(p.handle)) + if e != 0 { + return NewSyscallError("CloseHandle", e) + } + p.handle = -1 + // 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 != 0 { + return nil, NewSyscallError("OpenProcess", e) + } + return newProcess(pid, int(h)), nil +} diff --git a/src/pkg/os/file.go b/src/pkg/os/file.go new file mode 100644 index 000000000..4335d45e5 --- /dev/null +++ b/src/pkg/os/file.go @@ -0,0 +1,211 @@ +// 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. +// The os interface is intended to be uniform across all operating systems. +// Features not generally available appear in the system-specific package syscall. +package os + +import ( + "syscall" +) + +// Name returns the name of the file as presented to Open. +func (file *File) Name() string { return file.name } + +// Stdin, Stdout, and Stderr are open Files pointing to the standard input, +// standard output, and standard error file descriptors. +var ( + Stdin = NewFile(syscall.Stdin, "/dev/stdin") + Stdout = NewFile(syscall.Stdout, "/dev/stdout") + Stderr = NewFile(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_ASYNC int = syscall.O_ASYNC // generate a signal when I/O is available. + 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_NOCTTY int = syscall.O_NOCTTY // do not make file the controlling tty. + O_NONBLOCK int = syscall.O_NONBLOCK // open in non-blocking mode. + O_NDELAY int = O_NONBLOCK // synonym for O_NONBLOCK + 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 +) + +type eofError int + +func (eofError) String() string { return "EOF" } + +// EOF is the Error returned by Read when no more input is available. +// Functions should return EOF only to signal a graceful end of input. +// If the EOF occurs unexpectedly in a structured data stream, +// the appropriate error is either io.ErrUnexpectedEOF or some other error +// giving more detail. +var EOF Error = eofError(0) + +// 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 EOF. +func (file *File) Read(b []byte) (n int, err Error) { + if file == nil { + return 0, EINVAL + } + n, e := file.read(b) + if n < 0 { + n = 0 + } + if n == 0 && !iserror(e) { + return 0, EOF + } + if iserror(e) { + err = &PathError{"read", file.name, Errno(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. +// EOF is signaled by a zero count with err set to EOF. +// ReadAt always returns a non-nil Error when n != len(b). +func (file *File) ReadAt(b []byte, off int64) (n int, err Error) { + if file == nil { + return 0, EINVAL + } + for len(b) > 0 { + m, e := file.pread(b, off) + if m == 0 && !iserror(e) { + return n, EOF + } + if iserror(e) { + err = &PathError{"read", file.name, Errno(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 (file *File) Write(b []byte) (n int, err Error) { + if file == nil { + return 0, EINVAL + } + n, e := file.write(b) + if n < 0 { + n = 0 + } + + epipecheck(file, e) + + if iserror(e) { + err = &PathError{"write", file.name, Errno(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 (file *File) WriteAt(b []byte, off int64) (n int, err Error) { + if file == nil { + return 0, EINVAL + } + for len(b) > 0 { + m, e := file.pwrite(b, off) + if iserror(e) { + err = &PathError{"write", file.name, Errno(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 (file *File) Seek(offset int64, whence int) (ret int64, err Error) { + r, e := file.seek(offset, whence) + if !iserror(e) && file.dirinfo != nil && r != 0 { + e = syscall.EISDIR + } + if iserror(e) { + return 0, &PathError{"seek", file.name, Errno(e)} + } + return r, nil +} + +// WriteString is like Write, but writes the contents of string s rather than +// an array of bytes. +func (file *File) WriteString(s string) (ret int, err Error) { + if file == nil { + return 0, EINVAL + } + return file.Write([]byte(s)) +} + +// Mkdir creates a new directory with the specified name and permission bits. +// It returns an error, if any. +func Mkdir(name string, perm uint32) Error { + e := syscall.Mkdir(name, perm) + if iserror(e) { + return &PathError{"mkdir", name, Errno(e)} + } + return nil +} + +// Chdir changes the current working directory to the named directory. +func Chdir(dir string) Error { + if e := syscall.Chdir(dir); iserror(e) { + return &PathError{"chdir", dir, Errno(e)} + } + return nil +} + +// Chdir changes the current working directory to the file, +// which must be a directory. +func (f *File) Chdir() Error { + if e := syscall.Fchdir(f.fd); iserror(e) { + return &PathError{"chdir", f.name, Errno(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. +// It returns the File and an Error, if any. +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. +// It returns the File and an Error, if any. +func Create(name string) (file *File, err Error) { + return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) +} diff --git a/src/pkg/os/file_plan9.go b/src/pkg/os/file_plan9.go new file mode 100644 index 000000000..1e94fb715 --- /dev/null +++ b/src/pkg/os/file_plan9.go @@ -0,0 +1,331 @@ +// 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" +) + +// File represents an open file descriptor. +type File struct { + fd int + name string + dirinfo *dirInfo // nil unless directory being read +} + +// Fd returns the integer Unix file descriptor referencing the open file. +func (file *File) Fd() int { + if file == nil { + return -1 + } + return file.fd +} + +// NewFile returns a new File with the given file descriptor and name. +func NewFile(fd int, name string) *File { + if fd < 0 { + return nil + } + f := &File{fd: fd, name: name} + runtime.SetFinalizer(f, (*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 syscall.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" + +// 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. +// It returns the File and an Error, if any. +func OpenFile(name string, flag int, perm uint32) (file *File, err Error) { + var ( + fd int + e syscall.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 + } + + syscall.ForkLock.RLock() + if (create && trunc) || excl { + fd, e = syscall.Create(name, flag, perm) + } else { + fd, e = syscall.Open(name, flag) + if e != nil && create { + var e1 syscall.Error + fd, e1 = syscall.Create(name, flag, perm) + if e1 == nil { + e = nil + } + } + } + syscall.ForkLock.RUnlock() + + 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(fd, name), nil +} + +// Close closes the File, rendering it unusable for I/O. +// It returns an Error, if any. +func (file *File) Close() Error { + if file == nil || file.fd < 0 { + return Ebadfd + } + 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. +// It returns the FileInfo and an error, if any. +func (f *File) Stat() (fi *FileInfo, err Error) { + d, err := dirstat(f) + if iserror(err) { + return nil, err + } + return fileInfoFromStat(new(FileInfo), d), err +} + +// Truncate changes the size of the file. +// It does not change the I/O offset. +func (f *File) Truncate(size int64) Error { + var d Dir + d.Null() + + d.Length = uint64(size) + + if e := syscall.Fwstat(f.fd, pdir(nil, &d)); iserror(e) { + return &PathError{"truncate", f.name, e} + } + return nil +} + +// Chmod changes the mode of the file to mode. +func (f *File) Chmod(mode uint32) Error { + var d Dir + var mask = ^uint32(0777) + + d.Null() + odir, e := dirstat(f) + if iserror(e) { + return &PathError{"chmod", f.name, e} + } + + d.Mode = (odir.Mode & mask) | (mode &^ mask) + if e := syscall.Fwstat(f.fd, pdir(nil, &d)); iserror(e) { + return &PathError{"chmod", 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 EINVAL + } + + var d Dir + d.Null() + + if e := syscall.Fwstat(f.fd, pdir(nil, &d)); iserror(e) { + return NewSyscallError("fsync", e) + } + 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 syscall.Error) { + return syscall.Read(f.fd, b) +} + +// pread reads len(b) bytes from the File starting at byte offset off. +// It returns the number of bytes read and the error, if any. +// EOF is signaled by a zero count with err set to nil. +func (f *File) pread(b []byte, off int64) (n int, err syscall.Error) { + return syscall.Pread(f.fd, b, off) +} + +// write writes len(b) bytes to the File. +// It returns the number of bytes written and an error, if any. +func (f *File) write(b []byte) (n int, err syscall.Error) { + return syscall.Write(f.fd, b) +} + +// pwrite writes len(b) bytes to the File starting at byte offset off. +// It returns the number of bytes written and an error, if any. +func (f *File) pwrite(b []byte, off int64) (n int, err syscall.Error) { + return syscall.Pwrite(f.fd, b, off) +} + +// seek sets the offset for the next Read or Write on file to offset, interpreted +// according to whence: 0 means relative to the origin of the file, 1 means +// relative to the current offset, and 2 means relative to the end. +// It returns the new offset and an error, if any. +func (f *File) seek(offset int64, whence int) (ret int64, err syscall.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. +func Truncate(name string, size int64) Error { + var d Dir + d.Null() + + d.Length = uint64(size) + + if e := syscall.Wstat(name, pdir(nil, &d)); iserror(e) { + return &PathError{"truncate", name, e} + } + return nil +} + +// Remove removes the named file or directory. +func Remove(name string) Error { + if e := syscall.Remove(name); iserror(e) { + return &PathError{"remove", name, e} + } + return nil +} + +// Rename renames a file. +func Rename(oldname, newname string) Error { + var d Dir + d.Null() + + d.Name = newname + + if e := syscall.Wstat(oldname, pdir(nil, &d)); iserror(e) { + return &PathError{"rename", oldname, e} + } + return nil +} + +// Chmod changes the mode of the named file to mode. +func Chmod(name string, mode uint32) Error { + var d Dir + var mask = ^uint32(0777) + + d.Null() + odir, e := dirstat(name) + if iserror(e) { + return &PathError{"chmod", name, e} + } + + d.Mode = (odir.Mode & mask) | (mode &^ mask) + if e := syscall.Wstat(name, pdir(nil, &d)); iserror(e) { + return &PathError{"chmod", name, e} + } + return nil +} + +// Chtimes changes the access and modification times of the named +// file, similar to the Unix utime() or utimes() functions. +// +// The argument times are in nanoseconds, although the underlying +// filesystem may truncate or round the values to a more +// coarse time unit. +func Chtimes(name string, atimeNs int64, mtimeNs int64) Error { + var d Dir + d.Null() + + d.Atime = uint32(atimeNs / 1e9) + d.Mtime = uint32(mtimeNs / 1e9) + + if e := syscall.Wstat(name, pdir(nil, &d)); iserror(e) { + return &PathError{"chtimes", name, e} + } + return nil +} + +func Pipe() (r *File, w *File, err Error) { + var p [2]int + + syscall.ForkLock.RLock() + if e := syscall.Pipe(p[0:]); iserror(e) { + syscall.ForkLock.RUnlock() + return nil, nil, NewSyscallError("pipe", e) + } + syscall.ForkLock.RUnlock() + + return NewFile(p[0], "|0"), NewFile(p[1], "|1"), nil +} + +// not supported on Plan 9 + +// Link creates a hard link. +func Link(oldname, newname string) Error { + return EPLAN9 +} + +func Symlink(oldname, newname string) Error { + return EPLAN9 +} + +func Readlink(name string) (string, Error) { + return "", EPLAN9 +} + +func Chown(name string, uid, gid int) Error { + return EPLAN9 +} + +func Lchown(name string, uid, gid int) Error { + return EPLAN9 +} + +func (f *File) Chown(uid, gid int) Error { + return EPLAN9 +} diff --git a/src/pkg/os/file_posix.go b/src/pkg/os/file_posix.go new file mode 100644 index 000000000..0791a0dc0 --- /dev/null +++ b/src/pkg/os/file_posix.go @@ -0,0 +1,226 @@ +// 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 sigpipe() // implemented in package runtime + +func epipecheck(file *File, e int) { + if e == syscall.EPIPE { + file.nepipe++ + if file.nepipe >= 10 { + sigpipe() + } + } else { + file.nepipe = 0 + } +} + +// Stat returns a FileInfo structure describing the named file and an error, if any. +// If name names a valid symbolic link, the returned FileInfo describes +// the file pointed at by the link and has fi.FollowedSymlink set to true. +// If name names an invalid symbolic link, the returned FileInfo describes +// the link itself and has fi.FollowedSymlink set to false. +func Stat(name string) (fi *FileInfo, err Error) { + var lstat, stat syscall.Stat_t + e := syscall.Lstat(name, &lstat) + if iserror(e) { + return nil, &PathError{"stat", name, Errno(e)} + } + statp := &lstat + if lstat.Mode&syscall.S_IFMT == syscall.S_IFLNK { + e := syscall.Stat(name, &stat) + if !iserror(e) { + statp = &stat + } + } + return fileInfoFromStat(name, new(FileInfo), &lstat, statp), nil +} + +// Lstat returns the FileInfo structure describing the named file and an +// error, if any. If the file is a symbolic link, the returned FileInfo +// describes the symbolic link. Lstat makes no attempt to follow the link. +func Lstat(name string) (fi *FileInfo, err Error) { + var stat syscall.Stat_t + e := syscall.Lstat(name, &stat) + if iserror(e) { + return nil, &PathError{"lstat", name, Errno(e)} + } + return fileInfoFromStat(name, new(FileInfo), &stat, &stat), nil +} + +// Remove removes the named file or directory. +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 !iserror(e) { + return nil + } + e1 := syscall.Rmdir(name) + if !iserror(e1) { + 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. + // For windows syscall.ENOTDIR is set + // to syscall.ERROR_DIRECTORY, hopefully it should + // do the trick. + if e1 != syscall.ENOTDIR { + e = e1 + } + return &PathError{"remove", name, Errno(e)} +} + +// 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 + Error Error +} + +func (e *LinkError) String() string { + return e.Op + " " + e.Old + " " + e.New + ": " + e.Error.String() +} + +// Link creates a hard link. +func Link(oldname, newname string) Error { + e := syscall.Link(oldname, newname) + if iserror(e) { + return &LinkError{"link", oldname, newname, Errno(e)} + } + return nil +} + +// Symlink creates a symbolic link. +func Symlink(oldname, newname string) Error { + e := syscall.Symlink(oldname, newname) + if iserror(e) { + return &LinkError{"symlink", oldname, newname, Errno(e)} + } + return nil +} + +// Readlink reads the contents of a symbolic link: the destination of +// the link. It returns the contents and an Error, if any. +func Readlink(name string) (string, Error) { + for len := 128; ; len *= 2 { + b := make([]byte, len) + n, e := syscall.Readlink(name, b) + if iserror(e) { + return "", &PathError{"readlink", name, Errno(e)} + } + if n < len { + return string(b[0:n]), nil + } + } + // Silence 6g. + return "", nil +} + +// Rename renames a file. +func Rename(oldname, newname string) Error { + e := syscall.Rename(oldname, newname) + if iserror(e) { + return &LinkError{"rename", oldname, newname, Errno(e)} + } + 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. +func Chmod(name string, mode uint32) Error { + if e := syscall.Chmod(name, mode); iserror(e) { + return &PathError{"chmod", name, Errno(e)} + } + return nil +} + +// Chmod changes the mode of the file to mode. +func (f *File) Chmod(mode uint32) Error { + if e := syscall.Fchmod(f.fd, mode); iserror(e) { + return &PathError{"chmod", f.name, Errno(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. +func Chown(name string, uid, gid int) Error { + if e := syscall.Chown(name, uid, gid); iserror(e) { + return &PathError{"chown", name, Errno(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. +func Lchown(name string, uid, gid int) Error { + if e := syscall.Lchown(name, uid, gid); iserror(e) { + return &PathError{"lchown", name, Errno(e)} + } + return nil +} + +// Chown changes the numeric uid and gid of the named file. +func (f *File) Chown(uid, gid int) Error { + if e := syscall.Fchown(f.fd, uid, gid); iserror(e) { + return &PathError{"chown", f.name, Errno(e)} + } + return nil +} + +// Truncate changes the size of the file. +// It does not change the I/O offset. +func (f *File) Truncate(size int64) Error { + if e := syscall.Ftruncate(f.fd, size); iserror(e) { + return &PathError{"truncate", f.name, Errno(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 (file *File) Sync() (err Error) { + if file == nil { + return EINVAL + } + if e := syscall.Fsync(file.fd); iserror(e) { + 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 argument times are in nanoseconds, although the underlying +// filesystem may truncate or round the values to a more +// coarse time unit. +func Chtimes(name string, atime_ns int64, mtime_ns int64) Error { + var utimes [2]syscall.Timeval + utimes[0] = syscall.NsecToTimeval(atime_ns) + utimes[1] = syscall.NsecToTimeval(mtime_ns) + if e := syscall.Utimes(name, utimes[0:]); iserror(e) { + return &PathError{"chtimes", name, Errno(e)} + } + return nil +} diff --git a/src/pkg/os/file_unix.go b/src/pkg/os/file_unix.go new file mode 100644 index 000000000..301c2f473 --- /dev/null +++ b/src/pkg/os/file_unix.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. + +package os + +import ( + "runtime" + "syscall" +) + +// File represents an open file descriptor. +type File struct { + fd int + name string + dirinfo *dirInfo // nil unless directory being read + nepipe int // number of consecutive EPIPE in Write +} + +// Fd returns the integer Unix file descriptor referencing the open file. +func (file *File) Fd() int { + if file == nil { + return -1 + } + return file.fd +} + +// NewFile returns a new File with the given file descriptor and name. +func NewFile(fd int, name string) *File { + if fd < 0 { + return nil + } + f := &File{fd: fd, name: name} + runtime.SetFinalizer(f, (*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. +} + +// 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. +// It returns the File and an Error, if any. +func OpenFile(name string, flag int, perm uint32) (file *File, err Error) { + r, e := syscall.Open(name, flag|syscall.O_CLOEXEC, perm) + if e != 0 { + return nil, &PathError{"open", name, Errno(e)} + } + + // There's a race here with fork/exec, which we are + // content to live with. See ../syscall/exec.go + if syscall.O_CLOEXEC == 0 { // O_CLOEXEC not supported + syscall.CloseOnExec(r) + } + + return NewFile(r, name), nil +} + +// Close closes the File, rendering it unusable for I/O. +// It returns an Error, if any. +func (file *File) Close() Error { + if file == nil || file.fd < 0 { + return EINVAL + } + var err Error + if e := syscall.Close(file.fd); e != 0 { + err = &PathError{"close", file.name, Errno(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. +// It returns the FileInfo and an error, if any. +func (file *File) Stat() (fi *FileInfo, err Error) { + var stat syscall.Stat_t + e := syscall.Fstat(file.fd, &stat) + if e != 0 { + return nil, &PathError{"stat", file.name, Errno(e)} + } + return fileInfoFromStat(file.name, new(FileInfo), &stat, &stat), nil +} + +// Readdir reads the contents of the directory associated with file and +// returns an array of up to n FileInfo structures, 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 os.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 os.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 (file *File) Readdir(n int) (fi []FileInfo, err Error) { + dirname := file.name + if dirname == "" { + dirname = "." + } + dirname += "/" + names, err := file.Readdirnames(n) + fi = make([]FileInfo, len(names)) + for i, filename := range names { + fip, err := Lstat(dirname + filename) + if fip == nil || err != nil { + fi[i].Name = filename // rest is already zeroed out + } else { + fi[i] = *fip + } + } + return +} + +// 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 int) { + return syscall.Read(f.fd, b) +} + +// pread reads len(b) bytes from the File starting at byte offset off. +// It returns the number of bytes read and the error, if any. +// EOF is signaled by a zero count with err set to 0. +func (f *File) pread(b []byte, off int64) (n int, err int) { + return syscall.Pread(f.fd, b, off) +} + +// write writes len(b) bytes to the File. +// It returns the number of bytes written and an error, if any. +func (f *File) write(b []byte) (n int, err int) { + return syscall.Write(f.fd, b) +} + +// pwrite writes len(b) bytes to the File starting at byte offset off. +// It returns the number of bytes written and an error, if any. +func (f *File) pwrite(b []byte, off int64) (n int, err int) { + return syscall.Pwrite(f.fd, b, off) +} + +// seek sets the offset for the next Read or Write on file to offset, interpreted +// according to whence: 0 means relative to the origin of the file, 1 means +// relative to the current offset, and 2 means relative to the end. +// It returns the new offset and an error, if any. +func (f *File) seek(offset int64, whence int) (ret int64, err int) { + 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 { + if e := syscall.Truncate(name, size); e != 0 { + return &PathError{"truncate", name, Errno(e)} + } + return nil +} + +// 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 +} + +// 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 iserror(e) { + syscall.ForkLock.RUnlock() + return nil, nil, NewSyscallError("pipe", e) + } + syscall.CloseOnExec(p[0]) + syscall.CloseOnExec(p[1]) + syscall.ForkLock.RUnlock() + + return NewFile(p[0], "|0"), NewFile(p[1], "|1"), nil +} diff --git a/src/pkg/os/file_windows.go b/src/pkg/os/file_windows.go new file mode 100644 index 000000000..70dd6e241 --- /dev/null +++ b/src/pkg/os/file_windows.go @@ -0,0 +1,317 @@ +// 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" +) + +// File represents an open file descriptor. +type File struct { + fd syscall.Handle + name string + dirinfo *dirInfo // nil unless directory being read + nepipe int // number of consecutive EPIPE in Write + l sync.Mutex // used to implement windows pread/pwrite +} + +// Fd returns the Windows handle referencing the open file. +func (file *File) Fd() syscall.Handle { + if file == nil { + return syscall.InvalidHandle + } + return file.fd +} + +// NewFile returns a new File with the given file descriptor and name. +func NewFile(fd syscall.Handle, name string) *File { + if fd < 0 { + return nil + } + f := &File{fd: fd, name: name} + runtime.SetFinalizer(f, (*File).Close) + return f +} + +// Auxiliary information if the File describes a directory +type dirInfo struct { + stat syscall.Stat_t + usefirststat bool +} + +const DevNull = "NUL" + +func (file *File) isdir() bool { return file != nil && file.dirinfo != nil } + +func openFile(name string, flag int, perm uint32) (file *File, err Error) { + r, e := syscall.Open(name, flag|syscall.O_CLOEXEC, perm) + if e != 0 { + return nil, &PathError{"open", name, Errno(e)} + } + + // There's a race here with fork/exec, which we are + // content to live with. See ../syscall/exec.go + if syscall.O_CLOEXEC == 0 { // O_CLOEXEC not supported + syscall.CloseOnExec(r) + } + + return NewFile(r, name), nil +} + +func openDir(name string) (file *File, err Error) { + d := new(dirInfo) + r, e := syscall.FindFirstFile(syscall.StringToUTF16Ptr(name+"\\*"), &d.stat.Windata) + if e != 0 { + return nil, &PathError{"open", name, Errno(e)} + } + f := NewFile(r, name) + d.usefirststat = true + 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. +// It returns the File and an Error, if any. +func OpenFile(name string, flag int, perm uint32) (file *File, err Error) { + // TODO(brainman): not sure about my logic of assuming it is dir first, then fall back to file + r, e := openDir(name) + if e == nil { + if flag&O_WRONLY != 0 || flag&O_RDWR != 0 { + r.Close() + return nil, &PathError{"open", name, EISDIR} + } + return r, nil + } + r, e = openFile(name, flag, perm) + if e == nil { + return r, nil + } + // Imitating Unix behavior by replacing syscall.ERROR_PATH_NOT_FOUND with + // os.ENOTDIR. Not sure if we should go into that. + if e2, ok := e.(*PathError); ok { + if e3, ok := e2.Error.(Errno); ok { + if e3 == Errno(syscall.ERROR_PATH_NOT_FOUND) { + return nil, &PathError{"open", name, ENOTDIR} + } + } + } + return nil, e +} + +// Close closes the File, rendering it unusable for I/O. +// It returns an Error, if any. +func (file *File) Close() Error { + if file == nil || file.fd < 0 { + return EINVAL + } + var e int + if file.isdir() { + e = syscall.FindClose(syscall.Handle(file.fd)) + } else { + e = syscall.CloseHandle(syscall.Handle(file.fd)) + } + var err Error + if e != 0 { + err = &PathError{"close", file.name, Errno(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) statFile(name string) (fi *FileInfo, err Error) { + var stat syscall.ByHandleFileInformation + e := syscall.GetFileInformationByHandle(syscall.Handle(file.fd), &stat) + if e != 0 { + return nil, &PathError{"stat", file.name, Errno(e)} + } + return fileInfoFromByHandleInfo(new(FileInfo), file.name, &stat), nil +} + +// Stat returns the FileInfo structure describing file. +// It returns the FileInfo and an error, if any. +func (file *File) Stat() (fi *FileInfo, err Error) { + if file == nil || file.fd < 0 { + return nil, EINVAL + } + if file.isdir() { + // I don't know any better way to do that for directory + return Stat(file.name) + } + return file.statFile(file.name) +} + +// Readdir reads the contents of the directory associated with file and +// returns an array of up to n FileInfo structures, 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 os.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 os.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 (file *File) Readdir(n int) (fi []FileInfo, err Error) { + if file == nil || file.fd < 0 { + return nil, EINVAL + } + if !file.isdir() { + return nil, &PathError{"Readdir", file.name, ENOTDIR} + } + di := file.dirinfo + wantAll := n <= 0 + size := n + if wantAll { + n = -1 + size = 100 + } + fi = make([]FileInfo, 0, size) // Empty with room to grow. + for n != 0 { + if di.usefirststat { + di.usefirststat = false + } else { + e := syscall.FindNextFile(syscall.Handle(file.fd), &di.stat.Windata) + if e != 0 { + if e == syscall.ERROR_NO_MORE_FILES { + break + } else { + err = &PathError{"FindNextFile", file.name, Errno(e)} + if !wantAll { + fi = nil + } + return + } + } + } + var f FileInfo + fileInfoFromWin32finddata(&f, &di.stat.Windata) + if f.Name == "." || f.Name == ".." { // Useless names + continue + } + n-- + fi = append(fi, f) + } + if !wantAll && len(fi) == 0 { + return fi, EOF + } + return fi, 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 int) { + f.l.Lock() + defer f.l.Unlock() + return syscall.Read(f.fd, b) +} + +// pread reads len(b) bytes from the File starting at byte offset off. +// It returns the number of bytes read and the error, if any. +// EOF is signaled by a zero count with err set to 0. +func (f *File) pread(b []byte, off int64) (n int, err int) { + f.l.Lock() + defer f.l.Unlock() + curoffset, e := syscall.Seek(f.fd, 0, 1) + if e != 0 { + 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 != 0 { + return 0, e + } + return int(done), 0 +} + +// 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 int) { + f.l.Lock() + defer f.l.Unlock() + return syscall.Write(f.fd, b) +} + +// pwrite writes len(b) bytes to the File starting at byte offset off. +// It returns the number of bytes written and an error, if any. +func (f *File) pwrite(b []byte, off int64) (n int, err int) { + f.l.Lock() + defer f.l.Unlock() + curoffset, e := syscall.Seek(f.fd, 0, 1) + if e != 0 { + 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 != 0 { + return 0, e + } + return int(done), 0 +} + +// 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 int) { + 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 +} + +// 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 iserror(e) { + syscall.ForkLock.RUnlock() + return nil, nil, NewSyscallError("pipe", e) + } + syscall.CloseOnExec(p[0]) + syscall.CloseOnExec(p[1]) + syscall.ForkLock.RUnlock() + + return NewFile(p[0], "|0"), NewFile(p[1], "|1"), nil +} diff --git a/src/pkg/os/getwd.go b/src/pkg/os/getwd.go new file mode 100644 index 000000000..4c142ad3a --- /dev/null +++ b/src/pkg/os/getwd.go @@ -0,0 +1,92 @@ +// 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" +) + +// 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() (string, Error) { + // If the operating system provides a Getwd call, use it. + if syscall.ImplementsGetwd { + s, e := syscall.Getwd() + return s, NewSyscallError("getwd", e) + } + + // Otherwise, we're trying to find our way back to ".". + dot, err := Stat(".") + if err != nil { + return "", err + } + + // Clumsy but widespread kludge: + // if $PWD is set and matches ".", use it. + pwd := Getenv("PWD") + if len(pwd) > 0 && pwd[0] == '/' { + d, err := Stat(pwd) + if err == nil && d.Dev == dot.Dev && d.Ino == dot.Ino { + return pwd, 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 root.Dev == dot.Dev && root.Ino == dot.Ino { + return "/", nil + } + + // General algorithm: find name in parent + // and then find name of parent. Each iteration + // adds /name to the beginning of pwd. + pwd = "" + for parent := ".."; ; parent = "../" + parent { + if len(parent) >= 1024 { // Sanity check + return "", 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 d.Dev == dot.Dev && d.Ino == dot.Ino { + pwd = "/" + name + pwd + goto Found + } + } + } + fd.Close() + return "", ENOENT + + Found: + pd, err := fd.Stat() + if err != nil { + return "", err + } + fd.Close() + if pd.Dev == root.Dev && pd.Ino == root.Ino { + break + } + // Set up for next round. + dot = pd + } + return pwd, nil +} diff --git a/src/pkg/os/inotify/Makefile b/src/pkg/os/inotify/Makefile new file mode 100644 index 000000000..90e18da57 --- /dev/null +++ b/src/pkg/os/inotify/Makefile @@ -0,0 +1,14 @@ +# 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. + +include ../../../Make.inc + +TARG=os/inotify + +GOFILES_linux=\ + inotify_linux.go\ + +GOFILES+=$(GOFILES_$(GOOS)) + +include ../../../Make.pkg diff --git a/src/pkg/os/inotify/inotify_linux.go b/src/pkg/os/inotify/inotify_linux.go new file mode 100644 index 000000000..99fa51622 --- /dev/null +++ b/src/pkg/os/inotify/inotify_linux.go @@ -0,0 +1,288 @@ +// 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 inotify implements a wrapper for the Linux inotify system. + +Example: + watcher, err := inotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + err = watcher.Watch("/tmp") + if err != nil { + log.Fatal(err) + } + for { + select { + case ev := <-watcher.Event: + log.Println("event:", ev) + case err := <-watcher.Error: + log.Println("error:", err) + } + } + +*/ +package inotify + +import ( + "fmt" + "os" + "strings" + "syscall" + "unsafe" +) + +type Event struct { + Mask uint32 // Mask of events + Cookie uint32 // Unique cookie associating related events (for rename(2)) + Name string // File name (optional) +} + +type watch struct { + wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) + flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) +} + +type Watcher struct { + fd int // File descriptor (as returned by the inotify_init() syscall) + watches map[string]*watch // Map of inotify watches (key: path) + paths map[int]string // Map of watched paths (key: watch descriptor) + Error chan os.Error // Errors are sent on this channel + Event chan *Event // Events are returned on this channel + done chan bool // Channel for sending a "quit message" to the reader goroutine + isClosed bool // Set to true when Close() is first called +} + +// NewWatcher creates and returns a new inotify instance using inotify_init(2) +func NewWatcher() (*Watcher, os.Error) { + fd, errno := syscall.InotifyInit() + if fd == -1 { + return nil, os.NewSyscallError("inotify_init", errno) + } + w := &Watcher{ + fd: fd, + watches: make(map[string]*watch), + paths: make(map[int]string), + Event: make(chan *Event), + Error: make(chan os.Error), + done: make(chan bool, 1), + } + + go w.readEvents() + return w, nil +} + +// Close closes an inotify watcher instance +// It sends a message to the reader goroutine to quit and removes all watches +// associated with the inotify instance +func (w *Watcher) Close() os.Error { + if w.isClosed { + return nil + } + w.isClosed = true + + // Send "quit" message to the reader goroutine + w.done <- true + for path := range w.watches { + w.RemoveWatch(path) + } + + return nil +} + +// AddWatch adds path to the watched file set. +// The flags are interpreted as described in inotify_add_watch(2). +func (w *Watcher) AddWatch(path string, flags uint32) os.Error { + if w.isClosed { + return os.NewError("inotify instance already closed") + } + + watchEntry, found := w.watches[path] + if found { + watchEntry.flags |= flags + flags |= syscall.IN_MASK_ADD + } + wd, errno := syscall.InotifyAddWatch(w.fd, path, flags) + if wd == -1 { + return &os.PathError{"inotify_add_watch", path, os.Errno(errno)} + } + + if !found { + w.watches[path] = &watch{wd: uint32(wd), flags: flags} + w.paths[wd] = path + } + return nil +} + +// Watch adds path to the watched file set, watching all events. +func (w *Watcher) Watch(path string) os.Error { + return w.AddWatch(path, IN_ALL_EVENTS) +} + +// RemoveWatch removes path from the watched file set. +func (w *Watcher) RemoveWatch(path string) os.Error { + watch, ok := w.watches[path] + if !ok { + return os.NewError(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path)) + } + success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) + if success == -1 { + return os.NewSyscallError("inotify_rm_watch", errno) + } + w.watches[path] = nil, false + return nil +} + +// readEvents reads from the inotify file descriptor, converts the +// received events into Event objects and sends them via the Event channel +func (w *Watcher) readEvents() { + var ( + buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events + n int // Number of bytes read with read() + errno int // Syscall errno + ) + + for { + n, errno = syscall.Read(w.fd, buf[0:]) + // See if there is a message on the "done" channel + var done bool + select { + case done = <-w.done: + default: + } + + // If EOF or a "done" message is received + if n == 0 || done { + errno := syscall.Close(w.fd) + if errno == -1 { + w.Error <- os.NewSyscallError("close", errno) + } + close(w.Event) + close(w.Error) + return + } + if n < 0 { + w.Error <- os.NewSyscallError("read", errno) + continue + } + if n < syscall.SizeofInotifyEvent { + w.Error <- os.NewError("inotify: short read in readEvents()") + continue + } + + var offset uint32 = 0 + // We don't know how many events we just read into the buffer + // While the offset points to at least one whole event... + for offset <= uint32(n-syscall.SizeofInotifyEvent) { + // Point "raw" to the event in the buffer + raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) + event := new(Event) + event.Mask = uint32(raw.Mask) + event.Cookie = uint32(raw.Cookie) + nameLen := uint32(raw.Len) + // If the event happened to the watched directory or the watched file, the kernel + // doesn't append the filename to the event, but we would like to always fill the + // the "Name" field with a valid filename. We retrieve the path of the watch from + // the "paths" map. + event.Name = w.paths[int(raw.Wd)] + if nameLen > 0 { + // Point "bytes" at the first byte of the filename + bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent])) + // The filename is padded with NUL bytes. TrimRight() gets rid of those. + event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") + } + // Send the event on the events channel + w.Event <- event + + // Move to the next event in the buffer + offset += syscall.SizeofInotifyEvent + nameLen + } + } +} + +// String formats the event e in the form +// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..." +func (e *Event) String() string { + var events string = "" + + m := e.Mask + for _, b := range eventBits { + if m&b.Value != 0 { + m &^= b.Value + events += "|" + b.Name + } + } + + if m != 0 { + events += fmt.Sprintf("|%#x", m) + } + if len(events) > 0 { + events = " == " + events[1:] + } + + return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events) +} + +const ( + // Options for inotify_init() are not exported + // IN_CLOEXEC uint32 = syscall.IN_CLOEXEC + // IN_NONBLOCK uint32 = syscall.IN_NONBLOCK + + // Options for AddWatch + IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW + IN_ONESHOT uint32 = syscall.IN_ONESHOT + IN_ONLYDIR uint32 = syscall.IN_ONLYDIR + + // The "IN_MASK_ADD" option is not exported, as AddWatch + // adds it automatically, if there is already a watch for the given path + // IN_MASK_ADD uint32 = syscall.IN_MASK_ADD + + // Events + IN_ACCESS uint32 = syscall.IN_ACCESS + IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS + IN_ATTRIB uint32 = syscall.IN_ATTRIB + IN_CLOSE uint32 = syscall.IN_CLOSE + IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE + IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE + IN_CREATE uint32 = syscall.IN_CREATE + IN_DELETE uint32 = syscall.IN_DELETE + IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF + IN_MODIFY uint32 = syscall.IN_MODIFY + IN_MOVE uint32 = syscall.IN_MOVE + IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM + IN_MOVED_TO uint32 = syscall.IN_MOVED_TO + IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF + IN_OPEN uint32 = syscall.IN_OPEN + + // Special events + IN_ISDIR uint32 = syscall.IN_ISDIR + IN_IGNORED uint32 = syscall.IN_IGNORED + IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW + IN_UNMOUNT uint32 = syscall.IN_UNMOUNT +) + +var eventBits = []struct { + Value uint32 + Name string +}{ + {IN_ACCESS, "IN_ACCESS"}, + {IN_ATTRIB, "IN_ATTRIB"}, + {IN_CLOSE, "IN_CLOSE"}, + {IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"}, + {IN_CLOSE_WRITE, "IN_CLOSE_WRITE"}, + {IN_CREATE, "IN_CREATE"}, + {IN_DELETE, "IN_DELETE"}, + {IN_DELETE_SELF, "IN_DELETE_SELF"}, + {IN_MODIFY, "IN_MODIFY"}, + {IN_MOVE, "IN_MOVE"}, + {IN_MOVED_FROM, "IN_MOVED_FROM"}, + {IN_MOVED_TO, "IN_MOVED_TO"}, + {IN_MOVE_SELF, "IN_MOVE_SELF"}, + {IN_OPEN, "IN_OPEN"}, + {IN_ISDIR, "IN_ISDIR"}, + {IN_IGNORED, "IN_IGNORED"}, + {IN_Q_OVERFLOW, "IN_Q_OVERFLOW"}, + {IN_UNMOUNT, "IN_UNMOUNT"}, +} diff --git a/src/pkg/os/inotify/inotify_linux_test.go b/src/pkg/os/inotify/inotify_linux_test.go new file mode 100644 index 000000000..aa72604eb --- /dev/null +++ b/src/pkg/os/inotify/inotify_linux_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 inotify + +import ( + "os" + "time" + "testing" +) + +func TestInotifyEvents(t *testing.T) { + // Create an inotify watcher instance and initialize it + watcher, err := NewWatcher() + if err != nil { + t.Fatalf("NewWatcher() failed: %s", err) + } + + // Add a watch for "_test" + err = watcher.Watch("_test") + if err != nil { + t.Fatalf("Watcher.Watch() failed: %s", err) + } + + // Receive errors on the error channel on a separate goroutine + go func() { + for err := range watcher.Error { + t.Fatalf("error received: %s", err) + } + }() + + const testFile string = "_test/TestInotifyEvents.testfile" + + // Receive events on the event channel on a separate goroutine + eventstream := watcher.Event + var eventsReceived = 0 + done := make(chan bool) + go func() { + for event := range eventstream { + // Only count relevant events + if event.Name == testFile { + eventsReceived++ + t.Logf("event received: %s", event) + } else { + t.Logf("unexpected event received: %s", event) + } + } + done <- true + }() + + // Create a file + // This should add at least one event to the inotify event queue + _, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + t.Fatalf("creating test file failed: %s", err) + } + + // We expect this event to be received almost immediately, but let's wait 1 s to be sure + time.Sleep(1000e6) // 1000 ms + if eventsReceived == 0 { + t.Fatal("inotify event hasn't been received after 1 second") + } + + // Try closing the inotify instance + t.Log("calling Close()") + watcher.Close() + t.Log("waiting for the event channel to become closed...") + select { + case <-done: + t.Log("event channel closed") + case <-time.After(1e9): + t.Fatal("event stream was not closed after 1 second") + } +} + +func TestInotifyClose(t *testing.T) { + watcher, _ := NewWatcher() + watcher.Close() + + done := false + go func() { + watcher.Close() + done = true + }() + + time.Sleep(50e6) // 50 ms + if !done { + t.Fatal("double Close() test failed: second Close() call didn't return") + } + + err := watcher.Watch("_test") + if err == nil { + t.Fatal("expected error on Watch() after Close(), got nil") + } +} diff --git a/src/pkg/os/mkunixsignals.sh b/src/pkg/os/mkunixsignals.sh new file mode 100755 index 000000000..4bbc43f3d --- /dev/null +++ b/src/pkg/os/mkunixsignals.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# 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. + +echo '// ./mkunix.sh' "$1" +echo '// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT' +echo + +cat <<EOH +package os + +import ( + "syscall" +) + +var _ = syscall.Open // in case there are zero signals + +const ( +EOH + +sed -n 's/^[ ]*\(SIG[A-Z0-9][A-Z0-9]*\)[ ].*/ \1 = UnixSignal(syscall.\1)/p' "$1" + +echo ")" diff --git a/src/pkg/os/os_test.go b/src/pkg/os/os_test.go new file mode 100644 index 000000000..4d60333df --- /dev/null +++ b/src/pkg/os/os_test.go @@ -0,0 +1,1058 @@ +// 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" + "fmt" + "io" + "io/ioutil" + . "os" + "path/filepath" + "strings" + "syscall" + "testing" +) + +var dot = []string{ + "dir_unix.go", + "env_unix.go", + "error.go", + "file.go", + "os_test.go", + "time.go", + "types.go", + "stat_darwin.go", + "stat_linux.go", +} + +type sysDir struct { + name string + files []string +} + +var sysdir = func() (sd *sysDir) { + switch syscall.OS { + case "windows": + sd = &sysDir{ + Getenv("SystemRoot") + "\\system32\\drivers\\etc", + []string{ + "hosts", + "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) + defer file.Close() + if err != nil { + t.Fatal("open failed:", err) + } + var buf [100]byte + len := 0 + for { + n, e := file.Read(buf[0:]) + len += n + if e == EOF { + break + } + if e != nil { + t.Fatal("read failed:", err) + } + } + return int64(len) +} + +func equal(name1, name2 string) (r bool) { + switch syscall.OS { + 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 syscall.OS != "windows" { + dir = "/tmp" + } + f, err := ioutil.TempFile(dir, "_Go_"+testName) + if err != nil { + t.Fatalf("open %s: %s", testName, err) + } + return +} + +var sfdir = sysdir.name +var sfname = sysdir.files[0] + +func TestStat(t *testing.T) { + path := sfdir + "/" + sfname + dir, err := Stat(path) + if err != nil { + t.Fatal("stat failed:", err) + } + if !equal(sfname, dir.Name) { + t.Error("name should be ", sfname, "; is", dir.Name) + } + filesize := size(path, t) + if dir.Size != filesize { + t.Error("size should be", filesize, "; is", dir.Size) + } +} + +func TestFstat(t *testing.T) { + path := sfdir + "/" + sfname + file, err1 := Open(path) + defer file.Close() + if err1 != nil { + t.Fatal("open failed:", err1) + } + 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) + } +} + +func testReaddirnames(dir string, contents []string, t *testing.T) { + file, err := Open(dir) + defer file.Close() + if err != nil { + t.Fatalf("open %q failed: %v", dir, err) + } + 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) + defer file.Close() + if err != nil { + t.Fatalf("open %q failed: %v", dir, err) + } + 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 == 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 syscall.OS { + case "windows": + dir = Getenv("SystemRoot") + "\\system32" + case "plan9": + dir = "/bin" + } + file, err := Open(dir) + defer file.Close() + if err != nil { + t.Fatalf("open %q failed: %v", dir, err) + } + 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) + } + 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.Logf("test.short; skipping") + return + } + 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, EOF) + d.Close() + } +} + +func TestHardLink(t *testing.T) { + // Hardlinks are not supported under windows or Plan 9. + if syscall.OS == "windows" || syscall.OS == "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 tostat.Dev != fromstat.Dev || tostat.Ino != fromstat.Ino { + t.Errorf("link %q, %q did not create hard link", to, from) + } +} + +func TestSymLink(t *testing.T) { + // Symlinks are not supported under windows or Plan 9. + if syscall.OS == "windows" || syscall.OS == "plan9" { + return + } + 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 := Stat(to) + if err != nil { + t.Fatalf("stat %q failed: %v", to, err) + } + if tostat.FollowedSymlink { + t.Fatalf("stat %q claims to have followed a symlink", to) + } + fromstat, err := Stat(from) + if err != nil { + t.Fatalf("stat %q failed: %v", from, err) + } + if tostat.Dev != fromstat.Dev || tostat.Ino != fromstat.Ino { + 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.IsSymlink() { + 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.FollowedSymlink { + 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) { + // Symlinks are not supported under windows or Plan 9. + if syscall.OS == "windows" || syscall.OS == "plan9" { + return + } + 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) + } + attr := &ProcAttr{Dir: dir, Files: []*File{nil, w, Stderr}} + p, err := StartProcess(cmd, args, attr) + if err != nil { + t.Fatalf("StartProcess: %v", err) + } + defer p.Release() + w.Close() + + var b bytes.Buffer + io.Copy(&b, r) + output := b.String() + // Accept /usr prefix because Solaris /bin is symlinked to /usr/bin. + if output != expect && output != "/usr"+expect { + t.Errorf("exec %q returned %q wanted %q", + strings.Join(append([]string{cmd}, args...), " "), output, expect) + } + p.Wait(0) +} + +func TestStartProcess(t *testing.T) { + var dir, cmd, le string + var args []string + if syscall.OS == "windows" { + le = "\r\n" + cmd = Getenv("COMSPEC") + dir = Getenv("SystemRoot") + args = []string{"/c", "cd"} + } else { + le = "\n" + 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+le) + // Test relative executable path. + exec(t, cmddir, cmdbase, args, filepath.Clean(cmddir)+le) +} + +func checkMode(t *testing.T, path string, mode uint32) { + 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 syscall.OS == "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 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) + } + if dir.Uid != uid { + t.Errorf("Stat %q: uid %d want %d", path, dir.Uid, uid) + } + if dir.Gid != gid { + t.Errorf("Stat %q: gid %d want %d", path, dir.Gid, gid) + } +} + +func TestChown(t *testing.T) { + // Chown is not supported under windows or Plan 9. + // Plan9 provides a native ChownPlan9 version instead. + if syscall.OS == "windows" || syscall.OS == "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) + } + checkUidGid(t, f.Name(), dir.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(), dir.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(), dir.Uid, gid) + } +} + +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) + f.Write([]byte("surprise!")) + 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) + f.Write([]byte("surprise!")) + checkSize(t, f, 13+9) // wrote at offset past where hello, world was. +} + +// Use TempDir() to make sure we're on a local file system, +// so that timings are not distorted by latency and caching. +// On NFS, timings can be off due to caching of meta-data on +// NFS servers (Issue 848). +func TestChtimes(t *testing.T) { + f := newFile("TestChtimes", t) + defer Remove(f.Name()) + defer f.Close() + + f.Write([]byte("hello, world\n")) + f.Close() + + preStat, err := Stat(f.Name()) + if err != nil { + t.Fatalf("Stat %s: %s", f.Name(), err) + } + + // Move access and modification time back a second + const OneSecond = 1e9 // in nanoseconds + err = Chtimes(f.Name(), preStat.Atime_ns-OneSecond, preStat.Mtime_ns-OneSecond) + if err != nil { + t.Fatalf("Chtimes %s: %s", f.Name(), err) + } + + postStat, err := Stat(f.Name()) + if err != nil { + t.Fatalf("second Stat %s: %s", f.Name(), err) + } + + /* Plan 9: + 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. + */ + if postStat.Atime_ns >= preStat.Atime_ns && syscall.OS != "plan9" { + t.Errorf("Atime_ns didn't go backwards; was=%d, after=%d", + preStat.Atime_ns, + postStat.Atime_ns) + } + + if postStat.Mtime_ns >= preStat.Mtime_ns { + t.Errorf("Mtime_ns didn't go backwards; was=%d, after=%d", + preStat.Mtime_ns, + postStat.Mtime_ns) + } +} + +func TestChdirAndGetwd(t *testing.T) { + // TODO(brainman): file.Chdir() is not implemented on windows. + if syscall.OS == "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, and /tmp). + dirs := []string{"/", "/usr/bin"} + // /usr/bin does not usually exist on Plan 9. + if syscall.OS == "plan9" { + dirs = []string{"/", "/usr"} + } + 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() + } + pwd, err1 := Getwd() + 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 TestTime(t *testing.T) { + // Just want to check that Time() is getting something. + // A common failure mode on Darwin is to get 0, 0, + // because it returns the time in registers instead of + // filling in the structure passed to the system call. + // Too bad the compiler doesn't know that + // 365.24*86400 is an integer. + sec, nsec, err := Time() + if sec < (2009-1970)*36524*864 { + t.Errorf("Time() = %d, %d, %s; not plausible", sec, nsec, err) + } +} + +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.Error == 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, + ENOENT, + }, + { + sfdir, + O_WRONLY, + EISDIR, + }, + { + sfdir + "/" + sfname + "/no-such-file", + O_WRONLY, + 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 *os.PathError", tt.path, tt.mode, err) + } + if perr.Error != tt.error { + if syscall.OS == "plan9" { + syscallErrStr := perr.Error.String() + expectedErrStr := strings.Replace(tt.error.String(), "file ", "", 1) + if !strings.HasSuffix(syscallErrStr, expectedErrStr) { + t.Errorf("Open(%q, %d) = _, %q; want suffix %q", tt.path, tt.mode, syscallErrStr, expectedErrStr) + } + } else { + t.Errorf("Open(%q, %d) = _, %q; want %q", tt.path, tt.mode, perr.Error.String(), tt.error.String()) + } + } + } +} + +func run(t *testing.T, cmd []string) string { + // Run /bin/hostname and collect output. + r, w, err := Pipe() + if err != nil { + t.Fatal(err) + } + p, err := StartProcess("/bin/hostname", []string{"hostname"}, &ProcAttr{Files: []*File{nil, w, Stderr}}) + if err != nil { + t.Fatal(err) + } + defer p.Release() + w.Close() + + var b bytes.Buffer + io.Copy(&b, r) + _, err = p.Wait(0) + 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 is can be taken from #c/sysname as Hostname() does. + if syscall.OS == "windows" || syscall.OS == "plan9" { + return + } + // 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, %r", 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 dir, in _test so it will get + // cleaned up by make if not by us. + path := "_test/_TestStatDirWithSlash_" + err := MkdirAll(path, 0777) + if err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + defer RemoveAll(path) + + // Stat of path should succeed. + _, err = Stat(path) + if err != nil { + t.Fatal("stat failed:", err) + } + + // Stat of path+"/" should succeed too. + _, err = Stat(path + "/") + if err != nil { + t.Fatal("stat failed:", err) + } +} + +func TestNilWaitmsgString(t *testing.T) { + var w *Waitmsg + s := w.String() + if s != "<nil>" { + t.Errorf("(*Waitmsg)(nil).String() = %q, want %q", s, "<nil>") + } +} diff --git a/src/pkg/os/path.go b/src/pkg/os/path.go new file mode 100644 index 000000000..a8dfce307 --- /dev/null +++ b/src/pkg/os/path.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 + +// 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 uint32) Error { + // If path exists, stop with success or error. + dir, err := Stat(path) + if err == nil { + if dir.IsDirectory() { + return nil + } + return &PathError{"mkdir", path, ENOTDIR} + } + + // Doesn't already exist; make sure parent does. + i := len(path) + for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator. + i-- + } + + j := i + for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element. + j-- + } + + if j > 1 { + // Create parent + err = MkdirAll(path[0:j-1], perm) + if err != nil { + return err + } + } + + // Now parent exists, try to create. + err = Mkdir(path, perm) + if err != nil { + // Handle arguments like "foo/." by + // double-checking that directory doesn't exist. + dir, err1 := Lstat(path) + if err1 == nil && dir.IsDirectory() { + return nil + } + return err + } + return nil +} + +// RemoveAll removes path and any children it contains. +// It removes everything it can but returns the first error +// it encounters. If the path does not exist, RemoveAll +// returns nil (no error). +func RemoveAll(path string) Error { + // Simple case: if Remove works, we're done. + err := Remove(path) + if err == nil { + return nil + } + + // Otherwise, is this a directory we need to recurse into? + dir, serr := Lstat(path) + if serr != nil { + if serr, ok := serr.(*PathError); ok && serr.Error == ENOENT { + return nil + } + return serr + } + if !dir.IsDirectory() { + // Not a directory; return the error from Remove. + return err + } + + // Directory. + fd, err := Open(path) + if err != nil { + return err + } + + // Remove contents & return first error. + err = nil + for { + names, err1 := fd.Readdirnames(100) + for _, name := range names { + err1 := RemoveAll(path + string(PathSeparator) + name) + if err == nil { + err = err1 + } + } + if err1 == EOF { + break + } + // If Readdirnames returned an error, use it. + if err == nil { + err = err1 + } + if len(names) == 0 { + break + } + } + + // Close directory, because windows won't remove opened directory. + fd.Close() + + // Remove directory. + err1 := Remove(path) + if err == nil { + err = err1 + } + return err +} diff --git a/src/pkg/os/path_plan9.go b/src/pkg/os/path_plan9.go new file mode 100644 index 000000000..3121b7bc7 --- /dev/null +++ b/src/pkg/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 = 0 // 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/pkg/os/path_test.go b/src/pkg/os/path_test.go new file mode 100644 index 000000000..31acbaa43 --- /dev/null +++ b/src/pkg/os/path_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. + +package os_test + +import ( + . "os" + "path/filepath" + "testing" + "runtime" + "syscall" +) + +func TestMkdirAll(t *testing.T) { + // Create new dir, in _test so it will get + // cleaned up by make if not by us. + path := "_test/_TestMkdirAll_/dir/./dir2" + err := MkdirAll(path, 0777) + if err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + defer RemoveAll("_test/_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 syscall.OS == "windows" { + path := `_test\_TestMkdirAll_\dir\.\dir2\` + err := MkdirAll(path, 0777) + if err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + } +} + +func TestRemoveAll(t *testing.T) { + // Work directory. + path := "_test/_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 syscall.OS == "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) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Log("Skipping test: symlinks don't exist under Windows/Plan 9") + return + } + + err := Mkdir("_test/dir", 0755) + if err != nil { + t.Fatal(`Mkdir "_test/dir":`, err) + } + defer RemoveAll("_test/dir") + + err = Symlink("dir", "_test/link") + if err != nil { + t.Fatal(`Symlink "dir", "_test/link":`, err) + } + defer RemoveAll("_test/link") + + path := "_test/link/foo" + err = MkdirAll(path, 0755) + if err != nil { + t.Errorf("MkdirAll %q: %s", path, err) + } +} + +func TestMkdirAllAtSlash(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + return + } + 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.Error == EACCES { + return + } + t.Fatalf(`MkdirAll "/_go_os_test/dir": %v`, err) + } + RemoveAll("/_go_os_test") +} diff --git a/src/pkg/os/path_unix.go b/src/pkg/os/path_unix.go new file mode 100644 index 000000000..0d327cddd --- /dev/null +++ b/src/pkg/os/path_unix.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 = ':' // 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/pkg/os/path_windows.go b/src/pkg/os/path_windows.go new file mode 100644 index 000000000..61f2ca59f --- /dev/null +++ b/src/pkg/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/pkg/os/proc.go b/src/pkg/os/proc.go new file mode 100644 index 000000000..dfe388f25 --- /dev/null +++ b/src/pkg/os/proc.go @@ -0,0 +1,34 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Process etc. + +package os + +import "syscall" + +var Args []string // provided by runtime +var Envs []string // provided by 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. +func Exit(code int) { syscall.Exit(code) } diff --git a/src/pkg/os/signal/Makefile b/src/pkg/os/signal/Makefile new file mode 100644 index 000000000..26f58760e --- /dev/null +++ b/src/pkg/os/signal/Makefile @@ -0,0 +1,11 @@ +# 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. + +include ../../../Make.inc + +TARG=os/signal +GOFILES=\ + signal.go\ + +include ../../../Make.pkg diff --git a/src/pkg/os/signal/signal.go b/src/pkg/os/signal/signal.go new file mode 100644 index 000000000..520f3f8a9 --- /dev/null +++ b/src/pkg/os/signal/signal.go @@ -0,0 +1,33 @@ +// 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 signal implements operating system-independent signal handling. +package signal + +import ( + "os" + "runtime" +) + +// Incoming is the global signal channel. +// All signals received by the program will be delivered to this channel. +var Incoming <-chan os.Signal + +func process(ch chan<- os.Signal) { + for { + var mask uint32 = runtime.Sigrecv() + for sig := uint(0); sig < 32; sig++ { + if mask&(1<<sig) != 0 { + ch <- os.UnixSignal(sig) + } + } + } +} + +func init() { + runtime.Siginit() + ch := make(chan os.Signal) // Done here so Incoming can have type <-chan Signal + Incoming = ch + go process(ch) +} diff --git a/src/pkg/os/signal/signal_test.go b/src/pkg/os/signal/signal_test.go new file mode 100644 index 000000000..00eb29578 --- /dev/null +++ b/src/pkg/os/signal/signal_test.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. + +package signal + +import ( + "os" + "syscall" + "testing" +) + +func TestSignal(t *testing.T) { + // Send this process a SIGHUP. + syscall.Syscall(syscall.SYS_KILL, uintptr(syscall.Getpid()), syscall.SIGHUP, 0) + + if sig := (<-Incoming).(os.UnixSignal); sig != os.SIGHUP { + t.Errorf("signal was %v, want %v", sig, os.SIGHUP) + } +} diff --git a/src/pkg/os/stat_darwin.go b/src/pkg/os/stat_darwin.go new file mode 100644 index 000000000..0661a6d59 --- /dev/null +++ b/src/pkg/os/stat_darwin.go @@ -0,0 +1,32 @@ +// 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 isSymlink(stat *syscall.Stat_t) bool { + return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +} + +func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { + fi.Dev = uint64(stat.Dev) + fi.Ino = stat.Ino + fi.Nlink = uint64(stat.Nlink) + fi.Mode = uint32(stat.Mode) + fi.Uid = int(stat.Uid) + fi.Gid = int(stat.Gid) + fi.Rdev = uint64(stat.Rdev) + fi.Size = stat.Size + fi.Blksize = int64(stat.Blksize) + fi.Blocks = stat.Blocks + fi.Atime_ns = syscall.TimespecToNsec(stat.Atimespec) + fi.Mtime_ns = syscall.TimespecToNsec(stat.Mtimespec) + fi.Ctime_ns = syscall.TimespecToNsec(stat.Ctimespec) + fi.Name = basename(name) + if isSymlink(lstat) && !isSymlink(stat) { + fi.FollowedSymlink = true + } + return fi +} diff --git a/src/pkg/os/stat_freebsd.go b/src/pkg/os/stat_freebsd.go new file mode 100644 index 000000000..454165d4e --- /dev/null +++ b/src/pkg/os/stat_freebsd.go @@ -0,0 +1,32 @@ +// 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 isSymlink(stat *syscall.Stat_t) bool { + return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +} + +func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { + fi.Dev = uint64(stat.Dev) + fi.Ino = uint64(stat.Ino) + fi.Nlink = uint64(stat.Nlink) + fi.Mode = uint32(stat.Mode) + fi.Uid = int(stat.Uid) + fi.Gid = int(stat.Gid) + fi.Rdev = uint64(stat.Rdev) + fi.Size = int64(stat.Size) + fi.Blksize = int64(stat.Blksize) + fi.Blocks = stat.Blocks + fi.Atime_ns = syscall.TimespecToNsec(stat.Atimespec) + fi.Mtime_ns = syscall.TimespecToNsec(stat.Mtimespec) + fi.Ctime_ns = syscall.TimespecToNsec(stat.Ctimespec) + fi.Name = basename(name) + if isSymlink(lstat) && !isSymlink(stat) { + fi.FollowedSymlink = true + } + return fi +} diff --git a/src/pkg/os/stat_linux.go b/src/pkg/os/stat_linux.go new file mode 100644 index 000000000..7a3cf794d --- /dev/null +++ b/src/pkg/os/stat_linux.go @@ -0,0 +1,32 @@ +// 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 isSymlink(stat *syscall.Stat_t) bool { + return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +} + +func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { + fi.Dev = stat.Dev + fi.Ino = stat.Ino + fi.Nlink = uint64(stat.Nlink) + fi.Mode = stat.Mode + fi.Uid = int(stat.Uid) + fi.Gid = int(stat.Gid) + fi.Rdev = stat.Rdev + fi.Size = stat.Size + fi.Blksize = int64(stat.Blksize) + fi.Blocks = stat.Blocks + fi.Atime_ns = syscall.TimespecToNsec(stat.Atim) + fi.Mtime_ns = syscall.TimespecToNsec(stat.Mtim) + fi.Ctime_ns = syscall.TimespecToNsec(stat.Ctim) + fi.Name = basename(name) + if isSymlink(lstat) && !isSymlink(stat) { + fi.FollowedSymlink = true + } + return fi +} diff --git a/src/pkg/os/stat_openbsd.go b/src/pkg/os/stat_openbsd.go new file mode 100644 index 000000000..6d3a3813b --- /dev/null +++ b/src/pkg/os/stat_openbsd.go @@ -0,0 +1,32 @@ +// 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 isSymlink(stat *syscall.Stat_t) bool { + return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +} + +func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { + fi.Dev = uint64(stat.Dev) + fi.Ino = uint64(stat.Ino) + fi.Nlink = uint64(stat.Nlink) + fi.Mode = uint32(stat.Mode) + fi.Uid = int(stat.Uid) + fi.Gid = int(stat.Gid) + fi.Rdev = uint64(stat.Rdev) + fi.Size = int64(stat.Size) + fi.Blksize = int64(stat.Blksize) + fi.Blocks = stat.Blocks + fi.Atime_ns = syscall.TimespecToNsec(stat.Atim) + fi.Mtime_ns = syscall.TimespecToNsec(stat.Mtim) + fi.Ctime_ns = syscall.TimespecToNsec(stat.Ctim) + fi.Name = basename(name) + if isSymlink(lstat) && !isSymlink(stat) { + fi.FollowedSymlink = true + } + return fi +} diff --git a/src/pkg/os/stat_plan9.go b/src/pkg/os/stat_plan9.go new file mode 100644 index 000000000..173a23f8b --- /dev/null +++ b/src/pkg/os/stat_plan9.go @@ -0,0 +1,90 @@ +// 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" + +func fileInfoFromStat(fi *FileInfo, d *Dir) *FileInfo { + fi.Dev = uint64(d.Qid.Vers) | uint64(d.Qid.Type<<32) + fi.Ino = d.Qid.Path + + fi.Mode = uint32(d.Mode) & 0777 + if (d.Mode & syscall.DMDIR) == syscall.DMDIR { + fi.Mode |= syscall.S_IFDIR + } else { + fi.Mode |= syscall.S_IFREG + } + + fi.Size = int64(d.Length) + fi.Atime_ns = 1e9 * int64(d.Atime) + fi.Mtime_ns = 1e9 * int64(d.Mtime) + fi.Name = d.Name + fi.FollowedSymlink = false + return fi +} + +// arg is an open *File or a path string. +func dirstat(arg interface{}) (d *Dir, err Error) { + var name string + nd := syscall.STATFIXLEN + 16*4 + + for i := 0; i < 2; i++ { /* should work by the second try */ + buf := make([]byte, nd) + + var n int + var e syscall.Error + + switch syscallArg := arg.(type) { + case *File: + name = syscallArg.name + n, e = syscall.Fstat(syscallArg.fd, buf) + case string: + name = syscallArg + n, e = syscall.Stat(name, buf) + } + + if e != nil { + return nil, &PathError{"stat", name, e} + } + + if n < syscall.STATFIXLEN { + return nil, &PathError{"stat", name, Eshortstat} + } + + ntmp, _ := gbit16(buf) + nd = int(ntmp) + + if nd <= n { + d, e := UnmarshalDir(buf[:n]) + + if e != nil { + return nil, &PathError{"stat", name, e} + } + return d, e + } + } + + return nil, &PathError{"stat", name, Ebadstat} +} + +// Stat returns a FileInfo structure describing the named file and an error, if any. +func Stat(name string) (fi *FileInfo, err Error) { + d, err := dirstat(name) + if iserror(err) { + return nil, err + } + return fileInfoFromStat(new(FileInfo), d), err +} + +// Lstat returns the FileInfo structure describing the named file and an +// error, if any. If the file is a symbolic link (though Plan 9 does not have symbolic links), +// the returned FileInfo describes the symbolic link. Lstat makes no attempt to follow the link. +func Lstat(name string) (fi *FileInfo, err Error) { + d, err := dirstat(name) + if iserror(err) { + return nil, err + } + return fileInfoFromStat(new(FileInfo), d), err +} diff --git a/src/pkg/os/stat_windows.go b/src/pkg/os/stat_windows.go new file mode 100644 index 000000000..11088436a --- /dev/null +++ b/src/pkg/os/stat_windows.go @@ -0,0 +1,46 @@ +// 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 fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { + return fileInfoFromWin32finddata(fi, &stat.Windata) +} + +func fileInfoFromWin32finddata(fi *FileInfo, d *syscall.Win32finddata) *FileInfo { + return setFileInfo(fi, string(syscall.UTF16ToString(d.FileName[0:])), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime) +} + +func fileInfoFromByHandleInfo(fi *FileInfo, name string, d *syscall.ByHandleFileInformation) *FileInfo { + for i := len(name) - 1; i >= 0; i-- { + if name[i] == '/' || name[i] == '\\' { + name = name[i+1:] + break + } + } + return setFileInfo(fi, name, d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime) +} + +func setFileInfo(fi *FileInfo, name string, fa, sizehi, sizelo uint32, ctime, atime, wtime syscall.Filetime) *FileInfo { + fi.Mode = 0 + if fa&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { + fi.Mode = fi.Mode | syscall.S_IFDIR + } else { + fi.Mode = fi.Mode | syscall.S_IFREG + } + if fa&syscall.FILE_ATTRIBUTE_READONLY != 0 { + fi.Mode = fi.Mode | 0444 + } else { + fi.Mode = fi.Mode | 0666 + } + fi.Size = int64(sizehi)<<32 + int64(sizelo) + fi.Name = name + fi.FollowedSymlink = false + fi.Atime_ns = atime.Nanoseconds() + fi.Mtime_ns = wtime.Nanoseconds() + fi.Ctime_ns = ctime.Nanoseconds() + return fi +} diff --git a/src/pkg/os/str.go b/src/pkg/os/str.go new file mode 100644 index 000000000..8dc9e4747 --- /dev/null +++ b/src/pkg/os/str.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. + +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/pkg/os/sys_bsd.go b/src/pkg/os/sys_bsd.go new file mode 100644 index 000000000..188993b69 --- /dev/null +++ b/src/pkg/os/sys_bsd.go @@ -0,0 +1,19 @@ +// 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. + +// os code shared between *BSD systems including OS X (Darwin) +// and FreeBSD. + +package os + +import "syscall" + +func Hostname() (name string, err Error) { + var errno int + name, errno = syscall.Sysctl("kern.hostname") + if errno != 0 { + return "", NewSyscallError("sysctl kern.hostname", errno) + } + return name, nil +} diff --git a/src/pkg/os/sys_linux.go b/src/pkg/os/sys_linux.go new file mode 100644 index 000000000..2accd6c1f --- /dev/null +++ b/src/pkg/os/sys_linux.go @@ -0,0 +1,27 @@ +// 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 + +// Hostname returns the host name reported by the kernel. +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/pkg/os/sys_plan9.go b/src/pkg/os/sys_plan9.go new file mode 100644 index 000000000..c24cde05e --- /dev/null +++ b/src/pkg/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/pkg/os/sys_windows.go b/src/pkg/os/sys_windows.go new file mode 100644 index 000000000..a78798458 --- /dev/null +++ b/src/pkg/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 != 0 { + return "", NewSyscallError("ComputerName", e) + } + return s, nil +} diff --git a/src/pkg/os/time.go b/src/pkg/os/time.go new file mode 100644 index 000000000..949574d19 --- /dev/null +++ b/src/pkg/os/time.go @@ -0,0 +1,19 @@ +// 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 returns the current time, in whole seconds and +// fractional nanoseconds, plus an Error if any. The current +// time is thus 1e9*sec+nsec, in nanoseconds. The zero of +// time is the Unix epoch. +func Time() (sec int64, nsec int64, err Error) { + var tv syscall.Timeval + if e := syscall.Gettimeofday(&tv); iserror(e) { + return 0, 0, NewSyscallError("gettimeofday", e) + } + return int64(tv.Sec), int64(tv.Usec) * 1000, err +} diff --git a/src/pkg/os/types.go b/src/pkg/os/types.go new file mode 100644 index 000000000..df57b59a3 --- /dev/null +++ b/src/pkg/os/types.go @@ -0,0 +1,56 @@ +// 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" + +// An operating-system independent representation of Unix data structures. +// OS-specific routines in this directory convert the OS-local versions to these. + +// 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, Fstat, and Lstat +type FileInfo struct { + Dev uint64 // device number of file system holding file. + Ino uint64 // inode number. + Nlink uint64 // number of hard links. + Mode uint32 // permission and mode bits. + Uid int // user id of owner. + Gid int // group id of owner. + Rdev uint64 // device type for special file. + Size int64 // length in bytes. + Blksize int64 // size of blocks, in bytes. + Blocks int64 // number of blocks allocated for file. + Atime_ns int64 // access time; nanoseconds since epoch. + Mtime_ns int64 // modified time; nanoseconds since epoch. + Ctime_ns int64 // status change time; nanoseconds since epoch. + Name string // base name of the file name provided in Open, Stat, etc. + FollowedSymlink bool // followed a symlink to get this information +} + +// IsFifo reports whether the FileInfo describes a FIFO file. +func (f *FileInfo) IsFifo() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFIFO } + +// IsChar reports whether the FileInfo describes a character special file. +func (f *FileInfo) IsChar() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFCHR } + +// IsDirectory reports whether the FileInfo describes a directory. +func (f *FileInfo) IsDirectory() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFDIR } + +// IsBlock reports whether the FileInfo describes a block special file. +func (f *FileInfo) IsBlock() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFBLK } + +// IsRegular reports whether the FileInfo describes a regular file. +func (f *FileInfo) IsRegular() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFREG } + +// IsSymlink reports whether the FileInfo describes a symbolic link. +func (f *FileInfo) IsSymlink() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFLNK } + +// IsSocket reports whether the FileInfo describes a socket. +func (f *FileInfo) IsSocket() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFSOCK } + +// Permission returns the file permission bits. +func (f *FileInfo) Permission() uint32 { return f.Mode & 0777 } diff --git a/src/pkg/os/user/Makefile b/src/pkg/os/user/Makefile new file mode 100644 index 000000000..aabb54995 --- /dev/null +++ b/src/pkg/os/user/Makefile @@ -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. + +include ../../../Make.inc + +TARG=os/user +GOFILES=\ + user.go\ + +ifeq ($(CGO_ENABLED),1) +CGOFILES_linux=\ + lookup_unix.go +CGOFILES_freebsd=\ + lookup_unix.go +CGOFILES_darwin=\ + lookup_unix.go +endif + +ifneq ($(CGOFILES_$(GOOS)),) +CGOFILES+=$(CGOFILES_$(GOOS)) +else +GOFILES+=lookup_stubs.go +endif + +include ../../../Make.pkg diff --git a/src/pkg/os/user/lookup_stubs.go b/src/pkg/os/user/lookup_stubs.go new file mode 100644 index 000000000..2f08f70fd --- /dev/null +++ b/src/pkg/os/user/lookup_stubs.go @@ -0,0 +1,19 @@ +// 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 ( + "fmt" + "os" + "runtime" +) + +func Lookup(username string) (*User, os.Error) { + return nil, fmt.Errorf("user: Lookup not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} + +func LookupId(int) (*User, os.Error) { + return nil, fmt.Errorf("user: LookupId not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +} diff --git a/src/pkg/os/user/lookup_unix.go b/src/pkg/os/user/lookup_unix.go new file mode 100644 index 000000000..1b2c9e8c9 --- /dev/null +++ b/src/pkg/os/user/lookup_unix.go @@ -0,0 +1,108 @@ +// 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 ( + "fmt" + "os" + "runtime" + "strings" + "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 init() { + implemented = true +} + +// 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, os.Error) { + return lookup(-1, username, true) +} + +// LookupId looks up a user by userid. If the user cannot be found, +// the returned error is of type UnknownUserIdError. +func LookupId(uid int) (*User, os.Error) { + return lookup(uid, "", false) +} + +func lookup(uid int, username string, lookupByName bool) (*User, os.Error) { + var pwd C.struct_passwd + var result *C.struct_passwd + + var bufSize C.long + if runtime.GOOS == "freebsd" { + // FreeBSD doesn't have _SC_GETPW_R_SIZE_MAX + // and just returns -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, os.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, os.Errno(rv)) + } + if result == nil { + return nil, UnknownUserIdError(uid) + } + } + u := &User{ + Uid: int(pwd.pw_uid), + Gid: 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/pkg/os/user/user.go b/src/pkg/os/user/user.go new file mode 100644 index 000000000..f71e11d8b --- /dev/null +++ b/src/pkg/os/user/user.go @@ -0,0 +1,37 @@ +// 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 = false // set to true by lookup_unix.go's init + +// User represents a user account. +type User struct { + Uid int // user id + Gid int // 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) String() 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) String() string { + return "user: unknown user " + string(e) +} diff --git a/src/pkg/os/user/user_test.go b/src/pkg/os/user/user_test.go new file mode 100644 index 000000000..59f15e4c6 --- /dev/null +++ b/src/pkg/os/user/user_test.go @@ -0,0 +1,61 @@ +// 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 ( + "os" + "reflect" + "runtime" + "syscall" + "testing" +) + +func skip(t *testing.T) bool { + if !implemented { + t.Logf("user: not implemented; skipping tests") + return true + } + + if runtime.GOOS == "linux" || runtime.GOOS == "freebsd" || runtime.GOOS == "darwin" { + return false + } + + t.Logf("user: Lookup not implemented on %s; skipping test", runtime.GOOS) + return true +} + +func TestLookup(t *testing.T) { + if skip(t) { + return + } + + // Test LookupId on the current user + uid := syscall.Getuid() + u, err := LookupId(uid) + if err != nil { + t.Fatalf("LookupId: %v", err) + } + if e, g := uid, u.Uid; e != g { + t.Errorf("expected Uid of %d; got %d", e, g) + } + fi, err := os.Stat(u.HomeDir) + if err != nil || !fi.IsDirectory() { + t.Errorf("expected a valid HomeDir; stat(%q): err=%v, IsDirectory=%v", u.HomeDir, err, fi.IsDirectory()) + } + if u.Username == "" { + t.Fatalf("didn't get a username") + } + + // Test Lookup by username, using the username from LookupId + un, err := Lookup(u.Username) + if err != nil { + t.Fatalf("Lookup: %v", err) + } + if !reflect.DeepEqual(u, un) { + t.Errorf("Lookup by userid vs. name didn't match\n"+ + "LookupId(%d): %#v\n"+ + "Lookup(%q): %#v\n", uid, u, u.Username, un) + } +} |