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) +	} +} | 
