diff options
| author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-05-02 14:43:35 +0000 |
|---|---|---|
| committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-05-02 14:43:35 +0000 |
| commit | 34efdaf078b01a7387007c4e6bde6db86384c4b7 (patch) | |
| tree | d503eaf41d085669d1481bb46ec038bc866fece6 /libgo/go/os | |
| parent | f733cf303bcdc952c92b81dd62199a40a1f555ec (diff) | |
| download | gcc-tarball-master.tar.gz | |
gcc-7.1.0gcc-7.1.0
Diffstat (limited to 'libgo/go/os')
83 files changed, 2591 insertions, 779 deletions
diff --git a/libgo/go/os/dir.go b/libgo/go/os/dir.go index d811c9fdd8..6c54456a21 100644 --- a/libgo/go/os/dir.go +++ b/libgo/go/os/dir.go @@ -1,102 +1,46 @@ -// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package os -import ( - "io" - "sync/atomic" - "syscall" - "unsafe" -) - -//extern opendir -func libc_opendir(*byte) *syscall.DIR - -//extern closedir -func libc_closedir(*syscall.DIR) int - -// FIXME: pathconf returns long, not int. -//extern pathconf -func libc_pathconf(*byte, int) int - -func clen(n []byte) int { - for i := 0; i < len(n); i++ { - if n[i] == 0 { - return i - } +// Readdir reads the contents of the directory associated with file and +// returns a slice of up to n FileInfo values, as would be returned +// by Lstat, in directory order. Subsequent calls on the same file will yield +// further FileInfos. +// +// If n > 0, Readdir returns at most n FileInfo structures. In this case, if +// Readdir returns an empty slice, it will return a non-nil error +// explaining why. At the end of a directory, the error is io.EOF. +// +// If n <= 0, Readdir returns all the FileInfo from the directory in +// a single slice. In this case, if Readdir succeeds (reads all +// the way to the end of the directory), it returns the slice and a +// nil error. If it encounters an error before the end of the +// directory, Readdir returns the FileInfo read until that point +// and a non-nil error. +func (f *File) Readdir(n int) ([]FileInfo, error) { + if f == nil { + return nil, ErrInvalid } - return len(n) + return f.readdir(n) } -var nameMax int32 - -func (file *File) readdirnames(n int) (names []string, err error) { - if file.dirinfo == nil { - p, err := syscall.BytePtrFromString(file.name) - if err != nil { - return nil, err - } - - elen := int(atomic.LoadInt32(&nameMax)) - if elen == 0 { - syscall.Entersyscall() - plen := libc_pathconf(p, syscall.PC_NAME_MAX) - syscall.Exitsyscall() - if plen < 1024 { - plen = 1024 - } - var dummy syscall.Dirent - elen = int(unsafe.Offsetof(dummy.Name)) + plen + 1 - atomic.StoreInt32(&nameMax, int32(elen)) - } - - syscall.Entersyscall() - r := libc_opendir(p) - errno := syscall.GetErrno() - syscall.Exitsyscall() - if r == nil { - return nil, &PathError{"opendir", file.name, errno} - } - - file.dirinfo = new(dirInfo) - file.dirinfo.buf = make([]byte, elen) - file.dirinfo.dir = r - } - - entryDirent := (*syscall.Dirent)(unsafe.Pointer(&file.dirinfo.buf[0])) - - size := n - if size <= 0 { - size = 100 - n = -1 - } - - names = make([]string, 0, size) // Empty with room to grow. - - for n != 0 { - var dirent *syscall.Dirent - pr := &dirent - syscall.Entersyscall() - i := libc_readdir_r(file.dirinfo.dir, entryDirent, pr) - syscall.Exitsyscall() - if i != 0 { - return names, NewSyscallError("readdir_r", i) - } - if dirent == nil { - break // EOF - } - bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0])) - var name = string(bytes[0:clen(bytes[:])]) - if name == "." || name == ".." { // Useless names - continue - } - names = append(names, name) - n-- - } - if n >= 0 && len(names) == 0 { - return names, io.EOF +// Readdirnames reads and returns a slice of names from the directory f. +// +// If n > 0, Readdirnames returns at most n names. In this case, if +// Readdirnames returns an empty slice, it will return a non-nil error +// explaining why. At the end of a directory, the error is io.EOF. +// +// If n <= 0, Readdirnames returns all the names from the directory in +// a single slice. In this case, if Readdirnames succeeds (reads all +// the way to the end of the directory), it returns the slice and a +// nil error. If it encounters an error before the end of the +// directory, Readdirnames returns the names read until that point and +// a non-nil error. +func (f *File) Readdirnames(n int) (names []string, err error) { + if f == nil { + return nil, ErrInvalid } - return names, nil + return f.readdirnames(n) } diff --git a/libgo/go/os/dir_gccgo.go b/libgo/go/os/dir_gccgo.go new file mode 100644 index 0000000000..d811c9fdd8 --- /dev/null +++ b/libgo/go/os/dir_gccgo.go @@ -0,0 +1,102 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os + +import ( + "io" + "sync/atomic" + "syscall" + "unsafe" +) + +//extern opendir +func libc_opendir(*byte) *syscall.DIR + +//extern closedir +func libc_closedir(*syscall.DIR) int + +// FIXME: pathconf returns long, not int. +//extern pathconf +func libc_pathconf(*byte, int) int + +func clen(n []byte) int { + for i := 0; i < len(n); i++ { + if n[i] == 0 { + return i + } + } + return len(n) +} + +var nameMax int32 + +func (file *File) readdirnames(n int) (names []string, err error) { + if file.dirinfo == nil { + p, err := syscall.BytePtrFromString(file.name) + if err != nil { + return nil, err + } + + elen := int(atomic.LoadInt32(&nameMax)) + if elen == 0 { + syscall.Entersyscall() + plen := libc_pathconf(p, syscall.PC_NAME_MAX) + syscall.Exitsyscall() + if plen < 1024 { + plen = 1024 + } + var dummy syscall.Dirent + elen = int(unsafe.Offsetof(dummy.Name)) + plen + 1 + atomic.StoreInt32(&nameMax, int32(elen)) + } + + syscall.Entersyscall() + r := libc_opendir(p) + errno := syscall.GetErrno() + syscall.Exitsyscall() + if r == nil { + return nil, &PathError{"opendir", file.name, errno} + } + + file.dirinfo = new(dirInfo) + file.dirinfo.buf = make([]byte, elen) + file.dirinfo.dir = r + } + + entryDirent := (*syscall.Dirent)(unsafe.Pointer(&file.dirinfo.buf[0])) + + size := n + if size <= 0 { + size = 100 + n = -1 + } + + names = make([]string, 0, size) // Empty with room to grow. + + for n != 0 { + var dirent *syscall.Dirent + pr := &dirent + syscall.Entersyscall() + i := libc_readdir_r(file.dirinfo.dir, entryDirent, pr) + syscall.Exitsyscall() + if i != 0 { + return names, NewSyscallError("readdir_r", i) + } + if dirent == nil { + break // EOF + } + bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0])) + var name = string(bytes[0:clen(bytes[:])]) + if name == "." || name == ".." { // Useless names + continue + } + names = append(names, name) + n-- + } + if n >= 0 && len(names) == 0 { + return names, io.EOF + } + return names, nil +} diff --git a/libgo/go/os/dir_largefile.go b/libgo/go/os/dir_largefile.go index 2555c7ba33..28733428fd 100644 --- a/libgo/go/os/dir_largefile.go +++ b/libgo/go/os/dir_largefile.go @@ -5,6 +5,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build linux solaris,386 solaris,sparc + package os import "syscall" diff --git a/libgo/go/os/dir_regfile.go b/libgo/go/os/dir_regfile.go index e5f7c5745d..8b17f387f1 100644 --- a/libgo/go/os/dir_regfile.go +++ b/libgo/go/os/dir_regfile.go @@ -5,6 +5,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build !linux +// +build !solaris !386 +// +build !solaris !sparc + package os import "syscall" diff --git a/libgo/go/os/dir_unix.go b/libgo/go/os/dir_unix.go index 589db85274..cd42f59e01 100644 --- a/libgo/go/os/dir_unix.go +++ b/libgo/go/os/dir_unix.go @@ -8,51 +8,31 @@ package os import ( "io" - "syscall" ) -const ( - blockSize = 4096 -) - -func (f *File) readdirnames(n int) (names []string, err error) { - // If this file has no dirinfo, create one. - if f.dirinfo == nil { - f.dirinfo = new(dirInfo) - // The buffer must be at least a block long. - f.dirinfo.buf = make([]byte, blockSize) - } - d := f.dirinfo - - size := n - if size <= 0 { - size = 100 - n = -1 +func (f *File) readdir(n int) (fi []FileInfo, err error) { + dirname := f.name + if dirname == "" { + dirname = "." } - - names = make([]string, 0, size) // Empty with room to grow. - for n != 0 { - // Refill the buffer if necessary - if d.bufp >= d.nbuf { - d.bufp = 0 - var errno error - d.nbuf, errno = fixCount(syscall.ReadDirent(f.fd, d.buf)) - if errno != nil { - return names, NewSyscallError("readdirent", errno) - } - if d.nbuf <= 0 { - break // EOF - } + names, err := f.Readdirnames(n) + fi = make([]FileInfo, 0, len(names)) + for _, filename := range names { + fip, lerr := lstat(dirname + "/" + filename) + if IsNotExist(lerr) { + // File disappeared between readdir + stat. + // Just treat it as if it didn't exist. + continue } - - // 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 lerr != nil { + return fi, lerr + } + fi = append(fi, fip) } - if n >= 0 && len(names) == 0 { - return names, io.EOF + if len(fi) == 0 && err == nil && n > 0 { + // Per File.Readdir, the slice must be non-empty or err + // must be non-nil if n > 0. + err = io.EOF } - return names, nil + return fi, err } diff --git a/libgo/go/os/doc.go b/libgo/go/os/doc.go deleted file mode 100644 index 869a28a8a4..0000000000 --- a/libgo/go/os/doc.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package os - -import "time" - -// FindProcess looks for a running process by its pid. -// -// The Process it returns can be used to obtain information -// about the underlying operating system process. -// -// On Unix systems, FindProcess always succeeds and returns a Process -// for the given pid, regardless of whether the process exists. -func FindProcess(pid int) (*Process, error) { - return findProcess(pid) -} - -// StartProcess starts a new process with the program, arguments and attributes -// specified by name, argv and attr. -// -// StartProcess is a low-level interface. The os/exec package provides -// higher-level interfaces. -// -// If there is an error, it will be of type *PathError. -func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) { - return startProcess(name, argv, attr) -} - -// Release releases any resources associated with the Process p, -// rendering it unusable in the future. -// Release only needs to be called if Wait is not. -func (p *Process) Release() error { - return p.release() -} - -// Kill causes the Process to exit immediately. -func (p *Process) Kill() error { - return p.kill() -} - -// Wait waits for the Process to exit, and then returns a -// ProcessState describing its status and an error, if any. -// Wait releases any resources associated with the Process. -// On most operating systems, the Process must be a child -// of the current process or an error will be returned. -func (p *Process) Wait() (*ProcessState, error) { - return p.wait() -} - -// Signal sends a signal to the Process. -// Sending Interrupt on Windows is not implemented. -func (p *Process) Signal(sig Signal) error { - return p.signal(sig) -} - -// UserTime returns the user CPU time of the exited process and its children. -func (p *ProcessState) UserTime() time.Duration { - return p.userTime() -} - -// SystemTime returns the system CPU time of the exited process and its children. -func (p *ProcessState) SystemTime() time.Duration { - return p.systemTime() -} - -// Exited reports whether the program has exited. -func (p *ProcessState) Exited() bool { - return p.exited() -} - -// Success reports whether the program exited successfully, -// such as with exit status 0 on Unix. -func (p *ProcessState) Success() bool { - return p.success() -} - -// Sys returns system-dependent exit information about -// the process. Convert it to the appropriate underlying -// type, such as syscall.WaitStatus on Unix, to access its contents. -func (p *ProcessState) Sys() interface{} { - return p.sys() -} - -// SysUsage returns system-dependent resource usage information about -// the exited process. Convert it to the appropriate underlying -// type, such as *syscall.Rusage on Unix, to access its contents. -// (On Unix, *syscall.Rusage matches struct rusage as defined in the -// getrusage(2) manual page.) -func (p *ProcessState) SysUsage() interface{} { - return p.sysUsage() -} - -// Hostname returns the host name reported by the kernel. -func Hostname() (name string, err error) { - return hostname() -} - -// Readdir reads the contents of the directory associated with file and -// returns a slice of up to n FileInfo values, as would be returned -// by Lstat, in directory order. Subsequent calls on the same file will yield -// further FileInfos. -// -// If n > 0, Readdir returns at most n FileInfo structures. In this case, if -// Readdir returns an empty slice, it will return a non-nil error -// explaining why. At the end of a directory, the error is io.EOF. -// -// If n <= 0, Readdir returns all the FileInfo from the directory in -// a single slice. In this case, if Readdir succeeds (reads all -// the way to the end of the directory), it returns the slice and a -// nil error. If it encounters an error before the end of the -// directory, Readdir returns the FileInfo read until that point -// and a non-nil error. -func (f *File) Readdir(n int) (fi []FileInfo, err error) { - if f == nil { - return nil, ErrInvalid - } - return f.readdir(n) -} - -// Readdirnames reads and returns a slice of names from the directory f. -// -// If n > 0, Readdirnames returns at most n names. In this case, if -// Readdirnames returns an empty slice, it will return a non-nil error -// explaining why. At the end of a directory, the error is io.EOF. -// -// If n <= 0, Readdirnames returns all the names from the directory in -// a single slice. In this case, if Readdirnames succeeds (reads all -// the way to the end of the directory), it returns the slice and a -// nil error. If it encounters an error before the end of the -// directory, Readdirnames returns the names read until that point and -// a non-nil error. -func (f *File) Readdirnames(n int) (names []string, err error) { - if f == nil { - return nil, ErrInvalid - } - return f.readdirnames(n) -} diff --git a/libgo/go/os/env.go b/libgo/go/os/env.go index a4ede15e61..a03b8f68f5 100644 --- a/libgo/go/os/env.go +++ b/libgo/go/os/env.go @@ -1,4 +1,4 @@ -// Copyright 2010 The Go Authors. All rights reserved. +// 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. @@ -27,7 +27,7 @@ func Expand(s string, mapping func(string) string) string { } // ExpandEnv replaces ${var} or $var in the string according to the values -// of the current environment variables. References to undefined +// of the current environment variables. References to undefined // variables are replaced by the empty string. func ExpandEnv(s string) string { return Expand(s, Getenv) @@ -37,7 +37,7 @@ func ExpandEnv(s string) string { // shell variable such as $*. func isShellSpecialVar(c uint8) bool { switch c { - case '*', '#', '$', '@', '!', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': return true } return false @@ -49,7 +49,7 @@ func isAlphaNum(c uint8) bool { } // getShellName 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 ${} +// 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 { @@ -76,6 +76,7 @@ func getShellName(s string) (string, int) { // 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. +// To distinguish between an empty value and an unset value, use LookupEnv. func Getenv(key string) string { v, _ := syscall.Getenv(key) return v diff --git a/libgo/go/os/env_test.go b/libgo/go/os/env_test.go index d1074cdc60..e5749f0e89 100644 --- a/libgo/go/os/env_test.go +++ b/libgo/go/os/env_test.go @@ -95,6 +95,34 @@ func TestUnsetenv(t *testing.T) { } } +func TestClearenv(t *testing.T) { + const testKey = "GO_TEST_CLEARENV" + const testValue = "1" + + // reset env + defer func(origEnv []string) { + for _, pair := range origEnv { + // Environment variables on Windows can begin with = + // http://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx + i := strings.Index(pair[1:], "=") + 1 + if err := Setenv(pair[:i], pair[i+1:]); err != nil { + t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i], pair[i+1:], err) + } + } + }(Environ()) + + if err := Setenv(testKey, testValue); err != nil { + t.Fatalf("Setenv(%q, %q) failed: %v", testKey, testValue, err) + } + if _, ok := LookupEnv(testKey); !ok { + t.Errorf("Setenv(%q, %q) didn't set $%s", testKey, testValue, testKey) + } + Clearenv() + if val, ok := LookupEnv(testKey); ok { + t.Errorf("Clearenv() didn't clear $%s, remained with value %q", testKey, val) + } +} + func TestLookupEnv(t *testing.T) { const smallpox = "SMALLPOX" // No one has smallpox. value, ok := LookupEnv(smallpox) // Should not exist. diff --git a/libgo/go/os/env_unix_test.go b/libgo/go/os/env_unix_test.go index 5ec07ee1b1..f7b67ebbb8 100644 --- a/libgo/go/os/env_unix_test.go +++ b/libgo/go/os/env_unix_test.go @@ -7,6 +7,7 @@ package os_test import ( + "fmt" . "os" "testing" ) @@ -28,3 +29,28 @@ func TestSetenvUnixEinval(t *testing.T) { } } } + +var shellSpecialVarTests = []struct { + k, v string +}{ + {"*", "asterisk"}, + {"#", "pound"}, + {"$", "dollar"}, + {"@", "at"}, + {"!", "exclamation mark"}, + {"?", "question mark"}, + {"-", "dash"}, +} + +func TestExpandEnvShellSpecialVar(t *testing.T) { + for _, tt := range shellSpecialVarTests { + Setenv(tt.k, tt.v) + defer Unsetenv(tt.k) + + argRaw := fmt.Sprintf("$%s", tt.k) + argWithBrace := fmt.Sprintf("${%s}", tt.k) + if gotRaw, gotBrace := ExpandEnv(argRaw), ExpandEnv(argWithBrace); gotRaw != gotBrace { + t.Errorf("ExpandEnv(%q) = %q, ExpandEnv(%q) = %q; expect them to be equal", argRaw, gotRaw, argWithBrace, gotBrace) + } + } +} diff --git a/libgo/go/os/error.go b/libgo/go/os/error.go index e26ce27970..7235bfb6d6 100644 --- a/libgo/go/os/error.go +++ b/libgo/go/os/error.go @@ -14,6 +14,7 @@ var ( ErrPermission = errors.New("permission denied") ErrExist = errors.New("file already exists") ErrNotExist = errors.New("file does not exist") + ErrClosed = errors.New("file already closed") ) // PathError records an error and the operation and file path that caused it. @@ -63,3 +64,16 @@ func IsNotExist(err error) bool { func IsPermission(err error) bool { return isPermission(err) } + +// underlyingError returns the underlying error for known os error types. +func underlyingError(err error) error { + switch err := err.(type) { + case *PathError: + return err.Err + case *LinkError: + return err.Err + case *SyscallError: + return err.Err + } + return err +} diff --git a/libgo/go/os/error_plan9.go b/libgo/go/os/error_plan9.go index 2dc6b39c39..a67343981e 100644 --- a/libgo/go/os/error_plan9.go +++ b/libgo/go/os/error_plan9.go @@ -5,46 +5,30 @@ package os func isExist(err error) bool { - switch pe := err.(type) { - case nil: - return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err - } - return contains(err.Error(), " exists") + return checkErrMessageContent(err, " exists") } func isNotExist(err error) bool { - switch pe := err.(type) { - case nil: - return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err - } - return contains(err.Error(), "does not exist") || contains(err.Error(), "not found") || - contains(err.Error(), "has been removed") || contains(err.Error(), "no parent") + return checkErrMessageContent(err, "does not exist", "not found", + "has been removed", "no parent") } func isPermission(err error) bool { - switch pe := err.(type) { - case nil: + return checkErrMessageContent(err, "permission denied") +} + +// checkErrMessageContent checks if err message contains one of msgs. +func checkErrMessageContent(err error, msgs ...string) bool { + if err == nil { return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err } - return contains(err.Error(), "permission denied") + err = underlyingError(err) + for _, msg := range msgs { + if contains(err.Error(), msg) { + return true + } + } + return false } // contains is a local version of strings.Contains. It knows len(sep) > 1. diff --git a/libgo/go/os/error_test.go b/libgo/go/os/error_test.go index 5477e7ecbd..3499ceec95 100644 --- a/libgo/go/os/error_test.go +++ b/libgo/go/os/error_test.go @@ -80,19 +80,23 @@ func checkErrorPredicate(predName string, pred func(error) bool, err error) stri return "" } -var isExistTests = []struct { +type isExistTest struct { err error is bool isnot bool -}{ +} + +var isExistTests = []isExistTest{ {&os.PathError{Err: os.ErrInvalid}, false, false}, {&os.PathError{Err: os.ErrPermission}, false, false}, {&os.PathError{Err: os.ErrExist}, true, false}, {&os.PathError{Err: os.ErrNotExist}, false, true}, + {&os.PathError{Err: os.ErrClosed}, false, false}, {&os.LinkError{Err: os.ErrInvalid}, false, false}, {&os.LinkError{Err: os.ErrPermission}, false, false}, {&os.LinkError{Err: os.ErrExist}, true, false}, {&os.LinkError{Err: os.ErrNotExist}, false, true}, + {&os.LinkError{Err: os.ErrClosed}, false, false}, {&os.SyscallError{Err: os.ErrNotExist}, false, true}, {&os.SyscallError{Err: os.ErrExist}, true, false}, {nil, false, false}, @@ -109,10 +113,12 @@ func TestIsExist(t *testing.T) { } } -var isPermissionTests = []struct { +type isPermissionTest struct { err error want bool -}{ +} + +var isPermissionTests = []isPermissionTest{ {nil, false}, {&os.PathError{Err: os.ErrPermission}, true}, {&os.SyscallError{Err: os.ErrPermission}, true}, diff --git a/libgo/go/os/error_unix.go b/libgo/go/os/error_unix.go index c6002279da..be1440cacb 100644 --- a/libgo/go/os/error_unix.go +++ b/libgo/go/os/error_unix.go @@ -9,43 +9,16 @@ package os import "syscall" func isExist(err error) bool { - switch pe := err.(type) { - case nil: - return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err - } - return err == syscall.EEXIST || err == ErrExist + err = underlyingError(err) + return err == syscall.EEXIST || err == syscall.ENOTEMPTY || err == ErrExist } func isNotExist(err error) bool { - switch pe := err.(type) { - case nil: - return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err - } + err = underlyingError(err) return err == syscall.ENOENT || err == ErrNotExist } func isPermission(err error) bool { - switch pe := err.(type) { - case nil: - return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err - } + err = underlyingError(err) return err == syscall.EACCES || err == syscall.EPERM || err == ErrPermission } diff --git a/libgo/go/os/error_unix_test.go b/libgo/go/os/error_unix_test.go new file mode 100644 index 0000000000..76fe015b22 --- /dev/null +++ b/libgo/go/os/error_unix_test.go @@ -0,0 +1,39 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris + +package os_test + +import ( + "os" + "syscall" +) + +func init() { + isExistTests = append(isExistTests, + isExistTest{err: &os.PathError{Err: syscall.EEXIST}, is: true, isnot: false}, + isExistTest{err: &os.PathError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, + + isExistTest{err: &os.LinkError{Err: syscall.EEXIST}, is: true, isnot: false}, + isExistTest{err: &os.LinkError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, + + isExistTest{err: &os.SyscallError{Err: syscall.EEXIST}, is: true, isnot: false}, + isExistTest{err: &os.SyscallError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, + ) + isPermissionTests = append(isPermissionTests, + isPermissionTest{err: &os.PathError{Err: syscall.EACCES}, want: true}, + isPermissionTest{err: &os.PathError{Err: syscall.EPERM}, want: true}, + isPermissionTest{err: &os.PathError{Err: syscall.EEXIST}, want: false}, + + isPermissionTest{err: &os.LinkError{Err: syscall.EACCES}, want: true}, + isPermissionTest{err: &os.LinkError{Err: syscall.EPERM}, want: true}, + isPermissionTest{err: &os.LinkError{Err: syscall.EEXIST}, want: false}, + + isPermissionTest{err: &os.SyscallError{Err: syscall.EACCES}, want: true}, + isPermissionTest{err: &os.SyscallError{Err: syscall.EPERM}, want: true}, + isPermissionTest{err: &os.SyscallError{Err: syscall.EEXIST}, want: false}, + ) + +} diff --git a/libgo/go/os/error_windows.go b/libgo/go/os/error_windows.go index 2c1c39c414..02593b53fe 100644 --- a/libgo/go/os/error_windows.go +++ b/libgo/go/os/error_windows.go @@ -7,48 +7,22 @@ package os import "syscall" func isExist(err error) bool { - switch pe := err.(type) { - case nil: - return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err - } + err = underlyingError(err) return err == syscall.ERROR_ALREADY_EXISTS || + err == syscall.ERROR_DIR_NOT_EMPTY || err == syscall.ERROR_FILE_EXISTS || err == ErrExist } const _ERROR_BAD_NETPATH = syscall.Errno(53) func isNotExist(err error) bool { - switch pe := err.(type) { - case nil: - return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err - } + err = underlyingError(err) return err == syscall.ERROR_FILE_NOT_FOUND || err == _ERROR_BAD_NETPATH || err == syscall.ERROR_PATH_NOT_FOUND || err == ErrNotExist } func isPermission(err error) bool { - switch pe := err.(type) { - case nil: - return false - case *PathError: - err = pe.Err - case *LinkError: - err = pe.Err - case *SyscallError: - err = pe.Err - } + err = underlyingError(err) return err == syscall.ERROR_ACCESS_DENIED || err == ErrPermission } diff --git a/libgo/go/os/error_windows_test.go b/libgo/go/os/error_windows_test.go new file mode 100644 index 0000000000..1635c1088e --- /dev/null +++ b/libgo/go/os/error_windows_test.go @@ -0,0 +1,39 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package os_test + +import ( + "os" + "syscall" +) + +func init() { + const _ERROR_BAD_NETPATH = syscall.Errno(53) + + isExistTests = append(isExistTests, + isExistTest{err: &os.PathError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true}, + isExistTest{err: &os.LinkError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true}, + isExistTest{err: &os.SyscallError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true}, + + isExistTest{err: &os.PathError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true}, + isExistTest{err: &os.LinkError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true}, + isExistTest{err: &os.SyscallError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true}, + + isExistTest{err: &os.PathError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true}, + isExistTest{err: &os.LinkError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true}, + isExistTest{err: &os.SyscallError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true}, + + isExistTest{err: &os.PathError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false}, + isExistTest{err: &os.LinkError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false}, + isExistTest{err: &os.SyscallError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false}, + ) + isPermissionTests = append(isPermissionTests, + isPermissionTest{err: &os.PathError{Err: syscall.ERROR_ACCESS_DENIED}, want: true}, + isPermissionTest{err: &os.LinkError{Err: syscall.ERROR_ACCESS_DENIED}, want: true}, + isPermissionTest{err: &os.SyscallError{Err: syscall.ERROR_ACCESS_DENIED}, want: true}, + ) +} diff --git a/libgo/go/os/example_test.go b/libgo/go/os/example_test.go new file mode 100644 index 0000000000..07f9c76959 --- /dev/null +++ b/libgo/go/os/example_test.go @@ -0,0 +1,106 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + "fmt" + "log" + "os" + "time" +) + +func ExampleOpenFile() { + f, err := os.OpenFile("notes.txt", os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + log.Fatal(err) + } + if err := f.Close(); err != nil { + log.Fatal(err) + } +} + +func ExampleChmod() { + if err := os.Chmod("some-filename", 0644); err != nil { + log.Fatal(err) + } +} + +func ExampleChtimes() { + mtime := time.Date(2006, time.February, 1, 3, 4, 5, 0, time.UTC) + atime := time.Date(2007, time.March, 2, 4, 5, 6, 0, time.UTC) + if err := os.Chtimes("some-filename", atime, mtime); err != nil { + log.Fatal(err) + } +} + +func ExampleFileMode() { + fi, err := os.Stat("some-filename") + if err != nil { + log.Fatal(err) + } + + switch mode := fi.Mode(); { + case mode.IsRegular(): + fmt.Println("regular file") + case mode.IsDir(): + fmt.Println("directory") + case mode&os.ModeSymlink != 0: + fmt.Println("symbolic link") + case mode&os.ModeNamedPipe != 0: + fmt.Println("named pipe") + } +} + +func ExampleIsNotExist() { + filename := "a-nonexistent-file" + if _, err := os.Stat(filename); os.IsNotExist(err) { + fmt.Printf("file does not exist") + } + // Output: + // file does not exist +} + +func init() { + os.Setenv("USER", "gopher") + os.Setenv("HOME", "/usr/gopher") + os.Unsetenv("GOPATH") +} + +func ExampleExpandEnv() { + fmt.Println(os.ExpandEnv("$USER lives in ${HOME}.")) + + // Output: + // gopher lives in /usr/gopher. +} + +func ExampleLookupEnv() { + show := func(key string) { + val, ok := os.LookupEnv(key) + if !ok { + fmt.Printf("%s not set\n", key) + } else { + fmt.Printf("%s=%s\n", key, val) + } + } + + show("USER") + show("GOPATH") + + // Output: + // USER=gopher + // GOPATH not set +} + +func ExampleGetenv() { + fmt.Printf("%s lives in %s.\n", os.Getenv("USER"), os.Getenv("HOME")) + + // Output: + // gopher lives in /usr/gopher. +} + +func ExampleUnsetenv() { + os.Setenv("TMPDIR", "/my/tmp") + defer os.Unsetenv("TMPDIR") +} diff --git a/libgo/go/os/exec.go b/libgo/go/os/exec.go index 15e95b9172..8a53e5dd1e 100644 --- a/libgo/go/os/exec.go +++ b/libgo/go/os/exec.go @@ -6,15 +6,18 @@ package os import ( "runtime" + "sync" "sync/atomic" "syscall" + "time" ) // Process stores the information about a process created by StartProcess. type Process struct { Pid int - handle uintptr // handle is accessed atomically on Windows - isdone uint32 // process has been successfully waited on, non zero if true + handle uintptr // handle is accessed atomically on Windows + isdone uint32 // process has been successfully waited on, non zero if true + sigMu sync.RWMutex // avoid race between wait and signal } func newProcess(pid int, handle uintptr) *Process { @@ -41,10 +44,10 @@ type ProcAttr struct { // 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 + // 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 + // 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 @@ -68,3 +71,89 @@ func Getpid() int { return syscall.Getpid() } // Getppid returns the process id of the caller's parent. func Getppid() int { return syscall.Getppid() } + +// 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. +// +// On Unix systems, FindProcess always succeeds and returns a Process +// for the given pid, regardless of whether the process exists. +func FindProcess(pid int) (*Process, error) { + return findProcess(pid) +} + +// StartProcess starts a new process with the program, arguments and attributes +// specified by name, argv and attr. +// +// StartProcess is a low-level interface. The os/exec package provides +// higher-level interfaces. +// +// If there is an error, it will be of type *PathError. +func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) { + return startProcess(name, argv, attr) +} + +// Release releases any resources associated with the Process p, +// rendering it unusable in the future. +// Release only needs to be called if Wait is not. +func (p *Process) Release() error { + return p.release() +} + +// Kill causes the Process to exit immediately. +func (p *Process) Kill() error { + return p.kill() +} + +// Wait waits for the Process to exit, and then returns a +// ProcessState describing its status and an error, if any. +// Wait releases any resources associated with the Process. +// On most operating systems, the Process must be a child +// of the current process or an error will be returned. +func (p *Process) Wait() (*ProcessState, error) { + return p.wait() +} + +// Signal sends a signal to the Process. +// Sending Interrupt on Windows is not implemented. +func (p *Process) Signal(sig Signal) error { + return p.signal(sig) +} + +// UserTime returns the user CPU time of the exited process and its children. +func (p *ProcessState) UserTime() time.Duration { + return p.userTime() +} + +// SystemTime returns the system CPU time of the exited process and its children. +func (p *ProcessState) SystemTime() time.Duration { + return p.systemTime() +} + +// Exited reports whether the program has exited. +func (p *ProcessState) Exited() bool { + return p.exited() +} + +// Success reports whether the program exited successfully, +// such as with exit status 0 on Unix. +func (p *ProcessState) Success() bool { + return p.success() +} + +// Sys returns system-dependent exit information about +// the process. Convert it to the appropriate underlying +// type, such as syscall.WaitStatus on Unix, to access its contents. +func (p *ProcessState) Sys() interface{} { + return p.sys() +} + +// SysUsage returns system-dependent resource usage information about +// the exited process. Convert it to the appropriate underlying +// type, such as *syscall.Rusage on Unix, to access its contents. +// (On Unix, *syscall.Rusage matches struct rusage as defined in the +// getrusage(2) manual page.) +func (p *ProcessState) SysUsage() interface{} { + return p.sysUsage() +} diff --git a/libgo/go/os/exec/exec.go b/libgo/go/os/exec/exec.go index 340ebd498b..c4c5168b98 100644 --- a/libgo/go/os/exec/exec.go +++ b/libgo/go/os/exec/exec.go @@ -13,6 +13,7 @@ package exec import ( "bytes" + "context" "errors" "io" "os" @@ -102,13 +103,15 @@ type Cmd struct { // available after a call to Wait or Run. ProcessState *os.ProcessState - lookPathErr error // LookPath error, if any. - finished bool // when Wait was called + ctx context.Context // nil means none + lookPathErr error // LookPath error, if any. + finished bool // when Wait was called childFiles []*os.File closeAfterStart []io.Closer closeAfterWait []io.Closer goroutine []func() error errch chan error // one send per goroutine + waitDone chan struct{} } // Command returns the Cmd struct to execute the named program with @@ -117,12 +120,13 @@ type Cmd struct { // It sets only the Path and Args in the returned structure. // // If name contains no path separators, Command uses LookPath to -// resolve the path to a complete name if possible. Otherwise it uses -// name directly. +// resolve name to a complete path if possible. Otherwise it uses name +// directly as Path. // // The returned Cmd's Args field is constructed from the command name // followed by the elements of arg, so arg should not include the -// command name itself. For example, Command("echo", "hello") +// command name itself. For example, Command("echo", "hello"). +// Args[0] is always name, not the possibly resolved Path. func Command(name string, arg ...string) *Cmd { cmd := &Cmd{ Path: name, @@ -138,6 +142,20 @@ func Command(name string, arg ...string) *Cmd { return cmd } +// CommandContext is like Command but includes a context. +// +// The provided context is used to kill the process (by calling +// os.Process.Kill) if the context becomes done before the command +// completes on its own. +func CommandContext(ctx context.Context, name string, arg ...string) *Cmd { + if ctx == nil { + panic("nil Context") + } + cmd := Command(name, arg...) + cmd.ctx = ctx + return cmd +} + // interfaceEqual protects against panics from doing equality tests on // two interfaces with non-comparable underlying types. func interfaceEqual(a, b interface{}) bool { @@ -310,6 +328,15 @@ func (c *Cmd) Start() error { if c.Process != nil { return errors.New("exec: already started") } + if c.ctx != nil { + select { + case <-c.ctx.Done(): + c.closeDescriptors(c.closeAfterStart) + c.closeDescriptors(c.closeAfterWait) + return c.ctx.Err() + default: + } + } type F func(*Cmd) (*os.File, error) for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} { @@ -345,6 +372,17 @@ func (c *Cmd) Start() error { }(fn) } + if c.ctx != nil { + c.waitDone = make(chan struct{}) + go func() { + select { + case <-c.ctx.Done(): + c.Process.Kill() + case <-c.waitDone: + } + }() + } + return nil } @@ -393,7 +431,11 @@ func (c *Cmd) Wait() error { return errors.New("exec: Wait was already called") } c.finished = true + state, err := c.Process.Wait() + if c.waitDone != nil { + close(c.waitDone) + } c.ProcessState = state var copyError error @@ -474,15 +516,16 @@ func (c *Cmd) StdinPipe() (io.WriteCloser, error) { c.Stdin = pr c.closeAfterStart = append(c.closeAfterStart, pr) wc := &closeOnce{File: pw} - c.closeAfterWait = append(c.closeAfterWait, wc) + c.closeAfterWait = append(c.closeAfterWait, closerFunc(wc.safeClose)) return wc, nil } type closeOnce struct { *os.File - once sync.Once - err error + writers sync.RWMutex // coordinate safeClose and Write + once sync.Once + err error } func (c *closeOnce) Close() error { @@ -494,6 +537,55 @@ func (c *closeOnce) close() { c.err = c.File.Close() } +type closerFunc func() error + +func (f closerFunc) Close() error { return f() } + +// safeClose closes c being careful not to race with any calls to c.Write. +// See golang.org/issue/9307 and TestEchoFileRace in exec_test.go. +// In theory other calls could also be excluded (by writing appropriate +// wrappers like c.Write's implementation below), but since c is most +// commonly used as a WriteCloser, Write is the main one to worry about. +// See also #7970, for which this is a partial fix for this specific instance. +// The idea is that we return a WriteCloser, and so the caller can be +// relied upon not to call Write and Close simultaneously, but it's less +// obvious that cmd.Wait calls Close and that the caller must not call +// Write and cmd.Wait simultaneously. In fact that seems too onerous. +// So we change the use of Close in cmd.Wait to use safeClose, which will +// synchronize with any Write. +// +// It's important that we know this won't block forever waiting for the +// operations being excluded. At the point where this is called, +// the invoked command has exited and the parent copy of the read side +// of the pipe has also been closed, so there should really be no read side +// of the pipe left. Any active writes should return very shortly with an EPIPE, +// making it reasonable to wait for them. +// Technically it is possible that the child forked a sub-process or otherwise +// handed off the read side of the pipe before exiting and the current holder +// is not reading from the pipe, and the pipe is full, in which case the close here +// might block waiting for the write to complete. That's probably OK. +// It's a small enough problem to be outweighed by eliminating the race here. +func (c *closeOnce) safeClose() error { + c.writers.Lock() + err := c.Close() + c.writers.Unlock() + return err +} + +func (c *closeOnce) Write(b []byte) (int, error) { + c.writers.RLock() + n, err := c.File.Write(b) + c.writers.RUnlock() + return n, err +} + +func (c *closeOnce) WriteString(s string) (int, error) { + c.writers.RLock() + n, err := c.File.WriteString(s) + c.writers.RUnlock() + return n, err +} + // StdoutPipe returns a pipe that will be connected to the command's // standard output when the command starts. // diff --git a/libgo/go/os/exec/exec_test.go b/libgo/go/os/exec/exec_test.go index 1576e8f156..f13635138a 100644 --- a/libgo/go/os/exec/exec_test.go +++ b/libgo/go/os/exec/exec_test.go @@ -10,6 +10,7 @@ package exec_test import ( "bufio" "bytes" + "context" "fmt" "internal/testenv" "io" @@ -28,12 +29,16 @@ import ( "time" ) -func helperCommand(t *testing.T, s ...string) *exec.Cmd { +func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) { testenv.MustHaveExec(t) cs := []string{"-test.run=TestHelperProcess", "--"} cs = append(cs, s...) - cmd := exec.Command(os.Args[0], cs...) + if ctx != nil { + cmd = exec.CommandContext(ctx, os.Args[0], cs...) + } else { + cmd = exec.Command(os.Args[0], cs...) + } cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} path := os.Getenv("LD_LIBRARY_PATH") if path != "" { @@ -42,6 +47,10 @@ func helperCommand(t *testing.T, s ...string) *exec.Cmd { return cmd } +func helperCommand(t *testing.T, s ...string) *exec.Cmd { + return helperCommandContext(t, nil, s...) +} + func TestEcho(t *testing.T) { bs, err := helperCommand(t, "echo", "foo bar", "baz").Output() if err != nil { @@ -96,6 +105,26 @@ func TestCatStdin(t *testing.T) { } } +func TestEchoFileRace(t *testing.T) { + cmd := helperCommand(t, "echo") + stdin, err := cmd.StdinPipe() + if err != nil { + t.Fatalf("StdinPipe: %v", err) + } + if err := cmd.Start(); err != nil { + t.Fatalf("Start: %v", err) + } + wrote := make(chan bool) + go func() { + defer close(wrote) + fmt.Fprint(stdin, "echo\n") + }() + if err := cmd.Wait(); err != nil { + t.Fatalf("Wait: %v", err) + } + <-wrote +} + func TestCatGoodAndBadFile(t *testing.T) { // Testing combined output and error values. bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() @@ -221,6 +250,46 @@ func TestStdinClose(t *testing.T) { check("Wait", cmd.Wait()) } +// Issue 17647. +// It used to be the case that TestStdinClose, above, would fail when +// run under the race detector. This test is a variant of TestStdinClose +// that also used to fail when run under the race detector. +// This test is run by cmd/dist under the race detector to verify that +// the race detector no longer reports any problems. +func TestStdinCloseRace(t *testing.T) { + cmd := helperCommand(t, "stdinClose") + stdin, err := cmd.StdinPipe() + if err != nil { + t.Fatalf("StdinPipe: %v", err) + } + if err := cmd.Start(); err != nil { + t.Fatalf("Start: %v", err) + } + go func() { + // We don't check the error return of Kill. It is + // possible that the process has already exited, in + // which case Kill will return an error "process + // already finished". The purpose of this test is to + // see whether the race detector reports an error; it + // doesn't matter whether this Kill succeeds or not. + cmd.Process.Kill() + }() + go func() { + // Send the wrong string, so that the child fails even + // if the other goroutine doesn't manage to kill it first. + // This test is to check that the race detector does not + // falsely report an error, so it doesn't matter how the + // child process fails. + io.Copy(stdin, strings.NewReader("unexpected string")) + if err := stdin.Close(); err != nil { + t.Errorf("stdin.Close: %v", err) + } + }() + if err := cmd.Wait(); err == nil { + t.Fatalf("Wait: succeeded unexpectedly") + } +} + // Issue 5071 func TestPipeLookPathLeak(t *testing.T) { fd0, lsof0 := numOpenFDS(t) @@ -345,7 +414,7 @@ func TestExtraFilesFDShuffle(t *testing.T) { // // We want to test that FDs in the child do not get overwritten // by one another as this shuffle occurs. The original implementation - // was buggy in that in some data dependent cases it would ovewrite + // was buggy in that in some data dependent cases it would overwrite // stderr in the child with one of the ExtraFile members. // Testing for this case is difficult because it relies on using // the same FD values as that case. In particular, an FD of 3 @@ -407,7 +476,7 @@ func TestExtraFilesFDShuffle(t *testing.T) { buf := make([]byte, 512) n, err := stderr.Read(buf) if err != nil { - t.Fatalf("Read: %s", err) + t.Errorf("Read: %s", err) ch <- err.Error() } else { ch <- string(buf[:n]) @@ -483,7 +552,7 @@ func TestExtraFiles(t *testing.T) { if err != nil { t.Fatalf("Write: %v", err) } - _, err = tf.Seek(0, os.SEEK_SET) + _, err = tf.Seek(0, io.SeekStart) if err != nil { t.Fatalf("Seek: %v", err) } @@ -663,10 +732,6 @@ func TestHelperProcess(*testing.T) { // the cloned file descriptors that result from opening // /dev/urandom. // https://golang.org/issue/3955 - case "plan9": - // TODO(0intro): Determine why Plan 9 is leaking - // file descriptors. - // https://golang.org/issue/7118 case "solaris": // TODO(aram): This fails on Solaris because libc opens // its own files, as it sees fit. Darwin does the same, @@ -701,7 +766,7 @@ func TestHelperProcess(*testing.T) { } // Referring to fd3 here ensures that it is not // garbage collected, and therefore closed, while - // executing the wantfd loop above. It doesn't matter + // executing the wantfd loop above. It doesn't matter // what we do with fd3 as long as we refer to it; // closing it is the easy choice. fd3.Close() @@ -839,3 +904,111 @@ func TestOutputStderrCapture(t *testing.T) { t.Errorf("ExitError.Stderr = %q; want %q", got, want) } } + +func TestContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + c := helperCommandContext(t, ctx, "pipetest") + stdin, err := c.StdinPipe() + if err != nil { + t.Fatal(err) + } + stdout, err := c.StdoutPipe() + if err != nil { + t.Fatal(err) + } + if err := c.Start(); err != nil { + t.Fatal(err) + } + + if _, err := stdin.Write([]byte("O:hi\n")); err != nil { + t.Fatal(err) + } + buf := make([]byte, 5) + n, err := io.ReadFull(stdout, buf) + if n != len(buf) || err != nil || string(buf) != "O:hi\n" { + t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n]) + } + waitErr := make(chan error, 1) + go func() { + waitErr <- c.Wait() + }() + cancel() + select { + case err := <-waitErr: + if err == nil { + t.Fatal("expected Wait failure") + } + case <-time.After(3 * time.Second): + t.Fatal("timeout waiting for child process death") + } +} + +func TestContextCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + c := helperCommandContext(t, ctx, "cat") + + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + c.Stdin = r + + stdout, err := c.StdoutPipe() + if err != nil { + t.Fatal(err) + } + readDone := make(chan struct{}) + go func() { + defer close(readDone) + var a [1024]byte + for { + n, err := stdout.Read(a[:]) + if err != nil { + if err != io.EOF { + t.Errorf("unexpected read error: %v", err) + } + return + } + t.Logf("%s", a[:n]) + } + }() + + if err := c.Start(); err != nil { + t.Fatal(err) + } + + if err := r.Close(); err != nil { + t.Fatal(err) + } + + if _, err := io.WriteString(w, "echo"); err != nil { + t.Fatal(err) + } + + cancel() + + // Calling cancel should have killed the process, so writes + // should now fail. Give the process a little while to die. + start := time.Now() + for { + if _, err := io.WriteString(w, "echo"); err != nil { + break + } + if time.Since(start) > time.Second { + t.Fatal("canceling context did not stop program") + } + time.Sleep(time.Millisecond) + } + + if err := w.Close(); err != nil { + t.Errorf("error closing write end of pipe: %v", err) + } + <-readDone + + if err := c.Wait(); err == nil { + t.Error("program unexpectedly exited successfully") + } else { + t.Logf("exit status: %v", err) + } +} diff --git a/libgo/go/os/exec/lp_plan9.go b/libgo/go/os/exec/lp_plan9.go index 5aa8a54ed8..142f87ed32 100644 --- a/libgo/go/os/exec/lp_plan9.go +++ b/libgo/go/os/exec/lp_plan9.go @@ -1,4 +1,4 @@ -// Copyright 2011 The Go Authors. All rights reserved. +// 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. @@ -7,6 +7,7 @@ package exec import ( "errors" "os" + "path/filepath" "strings" ) @@ -44,9 +45,10 @@ func LookPath(file string) (string, error) { } path := os.Getenv("path") - for _, dir := range strings.Split(path, "\000") { - if err := findExecutable(dir + "/" + file); err == nil { - return dir + "/" + file, nil + for _, dir := range filepath.SplitList(path) { + path := filepath.Join(dir, file) + if err := findExecutable(path); err == nil { + return path, nil } } return "", &Error{file, ErrNotFound} diff --git a/libgo/go/os/exec/lp_unix.go b/libgo/go/os/exec/lp_unix.go index 3f895d5b3b..7a302752a8 100644 --- a/libgo/go/os/exec/lp_unix.go +++ b/libgo/go/os/exec/lp_unix.go @@ -1,4 +1,4 @@ -// Copyright 2010 The Go Authors. All rights reserved. +// 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. @@ -9,6 +9,7 @@ package exec import ( "errors" "os" + "path/filepath" "strings" ) @@ -42,16 +43,13 @@ func LookPath(file string) (string, error) { } return "", &Error{file, err} } - pathenv := os.Getenv("PATH") - if pathenv == "" { - return "", &Error{file, ErrNotFound} - } - for _, dir := range strings.Split(pathenv, ":") { + path := os.Getenv("PATH") + for _, dir := range filepath.SplitList(path) { if dir == "" { // Unix shell semantics: path element "" means "." dir = "." } - path := dir + "/" + file + path := filepath.Join(dir, file) if err := findExecutable(path); err == nil { return path, nil } diff --git a/libgo/go/os/exec/lp_unix_test.go b/libgo/go/os/exec/lp_unix_test.go index 051db664a8..d467acf5db 100644 --- a/libgo/go/os/exec/lp_unix_test.go +++ b/libgo/go/os/exec/lp_unix_test.go @@ -1,4 +1,4 @@ -// Copyright 2013 The Go Authors. All rights reserved. +// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/libgo/go/os/exec/lp_windows.go b/libgo/go/os/exec/lp_windows.go index c3efd67e9e..793d4d98b3 100644 --- a/libgo/go/os/exec/lp_windows.go +++ b/libgo/go/os/exec/lp_windows.go @@ -1,4 +1,4 @@ -// Copyright 2010 The Go Authors. All rights reserved. +// 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. @@ -7,6 +7,7 @@ package exec import ( "errors" "os" + "path/filepath" "strings" ) @@ -46,7 +47,7 @@ func findExecutable(file string, exts []string) (string, error) { return f, nil } } - return ``, os.ErrNotExist + return "", os.ErrNotExist } // LookPath searches for an executable binary named file @@ -55,69 +56,38 @@ func findExecutable(file string, exts []string) (string, error) { // LookPath also uses PATHEXT environment variable to match // a suitable candidate. // The result may be an absolute path or a path relative to the current directory. -func LookPath(file string) (f string, err error) { +func LookPath(file string) (string, error) { + var exts []string x := os.Getenv(`PATHEXT`) - if x == `` { - x = `.COM;.EXE;.BAT;.CMD` - } - exts := []string{} - for _, e := range strings.Split(strings.ToLower(x), `;`) { - if e == "" { - continue - } - if e[0] != '.' { - e = "." + e - } - exts = append(exts, e) - } - if strings.IndexAny(file, `:\/`) != -1 { - if f, err = findExecutable(file, exts); err == nil { - return - } - return ``, &Error{file, err} - } - if f, err = findExecutable(`.\`+file, exts); err == nil { - return - } - if pathenv := os.Getenv(`PATH`); pathenv != `` { - for _, dir := range splitList(pathenv) { - if f, err = findExecutable(dir+`\`+file, exts); err == nil { - return + if x != "" { + for _, e := range strings.Split(strings.ToLower(x), `;`) { + if e == "" { + continue } + if e[0] != '.' { + e = "." + e + } + exts = append(exts, e) } + } else { + exts = []string{".com", ".exe", ".bat", ".cmd"} } - return ``, &Error{file, ErrNotFound} -} -func splitList(path string) []string { - // The same implementation is used in SplitList in path/filepath; - // consider changing path/filepath when changing this. - - if path == "" { - return []string{} - } - - // Split path, respecting but preserving quotes. - list := []string{} - start := 0 - quo := false - for i := 0; i < len(path); i++ { - switch c := path[i]; { - case c == '"': - quo = !quo - case c == os.PathListSeparator && !quo: - list = append(list, path[start:i]) - start = i + 1 + if strings.ContainsAny(file, `:\/`) { + if f, err := findExecutable(file, exts); err == nil { + return f, nil + } else { + return "", &Error{file, err} } } - list = append(list, path[start:]) - - // Remove quotes. - for i, s := range list { - if strings.Contains(s, `"`) { - list[i] = strings.Replace(s, `"`, ``, -1) + if f, err := findExecutable(filepath.Join(".", file), exts); err == nil { + return f, nil + } + path := os.Getenv("path") + for _, dir := range filepath.SplitList(path) { + if f, err := findExecutable(filepath.Join(dir, file), exts); err == nil { + return f, nil } } - - return list + return "", &Error{file, ErrNotFound} } diff --git a/libgo/go/os/exec_posix.go b/libgo/go/os/exec_posix.go index 94dd04beb2..3cf38b68ad 100644 --- a/libgo/go/os/exec_posix.go +++ b/libgo/go/os/exec_posix.go @@ -21,7 +21,7 @@ var ( func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) { // If there is no SysProcAttr (ie. no Chroot or changed // UID/GID), double-check existence of the directory we want - // to chdir into. We can make the error clearer this way. + // to chdir into. We can make the error clearer this way. if attr != nil && attr.Sys == nil && attr.Dir != "" { if _, err := Stat(attr.Dir); err != nil { pe := err.(*PathError) diff --git a/libgo/go/os/exec_unix.go b/libgo/go/os/exec_unix.go index ed97f85e22..c4999db57f 100644 --- a/libgo/go/os/exec_unix.go +++ b/libgo/go/os/exec_unix.go @@ -17,6 +17,22 @@ func (p *Process) wait() (ps *ProcessState, err error) { if p.Pid == -1 { return nil, syscall.EINVAL } + + // If we can block until Wait4 will succeed immediately, do so. + ready, err := p.blockUntilWaitable() + if err != nil { + return nil, err + } + if ready { + // Mark the process done now, before the call to Wait4, + // so that Process.signal will not send a signal. + p.setDone() + // Acquire a write lock on sigMu to wait for any + // active call to the signal method to complete. + p.sigMu.Lock() + p.sigMu.Unlock() + } + var status syscall.WaitStatus var rusage syscall.Rusage pid1, e := syscall.Wait4(p.Pid, &status, 0, &rusage) @@ -43,6 +59,8 @@ func (p *Process) signal(sig Signal) error { if p.Pid == 0 { return errors.New("os: process not initialized") } + p.sigMu.RLock() + defer p.sigMu.RUnlock() if p.done() { return errFinished } diff --git a/libgo/go/os/exec_windows.go b/libgo/go/os/exec_windows.go index 3264271b2e..d89db2022c 100644 --- a/libgo/go/os/exec_windows.go +++ b/libgo/go/os/exec_windows.go @@ -63,7 +63,9 @@ func (p *Process) signal(sig Signal) error { return errors.New("os: process already finished") } if sig == Kill { - return terminateProcess(p.Pid, 1) + err := terminateProcess(p.Pid, 1) + runtime.KeepAlive(p) + return err } // TODO(rsc): Handle Interrupt too? return syscall.Errno(syscall.EWINDOWS) @@ -104,7 +106,7 @@ func init() { 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)[:])) + Args[i] = syscall.UTF16ToString((*v)[:]) } } diff --git a/libgo/go/os/executable.go b/libgo/go/os/executable.go new file mode 100644 index 0000000000..8c21246f5a --- /dev/null +++ b/libgo/go/os/executable.go @@ -0,0 +1,23 @@ +// Copyright 2016 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 + +// Executable returns the path name for the executable that started +// the current process. There is no guarantee that the path is still +// pointing to the correct executable. If a symlink was used to start +// the process, depending on the operating system, the result might +// be the symlink or the path it pointed to. If a stable result is +// needed, path/filepath.EvalSymlinks might help. +// +// Executable returns an absolute path unless an error occurred. +// +// The main use case is finding resources located relative to an +// executable. +// +// Executable is not supported on nacl or OpenBSD (unless procfs is +// mounted.) +func Executable() (string, error) { + return executable() +} diff --git a/libgo/go/os/executable_darwin.go b/libgo/go/os/executable_darwin.go new file mode 100644 index 0000000000..ce5b8140a4 --- /dev/null +++ b/libgo/go/os/executable_darwin.go @@ -0,0 +1,24 @@ +// Copyright 2016 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 + +var executablePath string // set by ../runtime/os_darwin.go + +var initCwd, initCwdErr = Getwd() + +func executable() (string, error) { + ep := executablePath + if ep[0] != '/' { + if initCwdErr != nil { + return ep, initCwdErr + } + if len(ep) > 2 && ep[0:2] == "./" { + // skip "./" + ep = ep[2:] + } + ep = initCwd + "/" + ep + } + return ep, nil +} diff --git a/libgo/go/os/executable_freebsd.go b/libgo/go/os/executable_freebsd.go new file mode 100644 index 0000000000..ccaf8e6dd4 --- /dev/null +++ b/libgo/go/os/executable_freebsd.go @@ -0,0 +1,33 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os + +import ( + "syscall" + "unsafe" +) + +func executable() (string, error) { + mib := [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1} + + n := uintptr(0) + // get length + _, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0) + if err != 0 { + return "", err + } + if n == 0 { // shouldn't happen + return "", nil + } + buf := make([]byte, n) + _, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0) + if err != 0 { + return "", err + } + if n == 0 { // shouldn't happen + return "", nil + } + return string(buf[:n-1]), nil +} diff --git a/libgo/go/os/executable_plan9.go b/libgo/go/os/executable_plan9.go new file mode 100644 index 0000000000..a5947eaae1 --- /dev/null +++ b/libgo/go/os/executable_plan9.go @@ -0,0 +1,19 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build plan9 + +package os + +import "syscall" + +func executable() (string, error) { + fn := "/proc/" + itoa(Getpid()) + "/text" + f, err := Open(fn) + if err != nil { + return "", err + } + defer f.Close() + return syscall.Fd2path(int(f.Fd())) +} diff --git a/libgo/go/os/executable_procfs.go b/libgo/go/os/executable_procfs.go new file mode 100644 index 0000000000..69a70e18df --- /dev/null +++ b/libgo/go/os/executable_procfs.go @@ -0,0 +1,36 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux netbsd openbsd dragonfly nacl + +package os + +import ( + "errors" + "runtime" +) + +// We query the executable path at init time to avoid the problem of +// readlink returns a path appended with " (deleted)" when the original +// binary gets deleted. +var executablePath, executablePathErr = func() (string, error) { + var procfn string + switch runtime.GOOS { + default: + return "", errors.New("Executable not implemented for " + runtime.GOOS) + case "linux", "android": + procfn = "/proc/self/exe" + case "netbsd": + procfn = "/proc/curproc/exe" + case "openbsd": + procfn = "/proc/curproc/file" + case "dragonfly": + procfn = "/proc/curproc/file" + } + return Readlink(procfn) +}() + +func executable() (string, error) { + return executablePath, executablePathErr +} diff --git a/libgo/go/os/executable_solaris.go b/libgo/go/os/executable_solaris.go new file mode 100644 index 0000000000..80f937201a --- /dev/null +++ b/libgo/go/os/executable_solaris.go @@ -0,0 +1,27 @@ +// Copyright 2016 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" + +var initCwd, initCwdErr = Getwd() + +func executable() (string, error) { + path, err := syscall.Getexecname() + if err != nil { + return path, err + } + if len(path) > 0 && path[0] != '/' { + if initCwdErr != nil { + return path, initCwdErr + } + if len(path) > 2 && path[0:2] == "./" { + // skip "./" + path = path[2:] + } + return initCwd + "/" + path, nil + } + return path, nil +} diff --git a/libgo/go/os/executable_test.go b/libgo/go/os/executable_test.go new file mode 100644 index 0000000000..a4d89092ac --- /dev/null +++ b/libgo/go/os/executable_test.go @@ -0,0 +1,87 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + "fmt" + "internal/testenv" + "os" + osexec "os/exec" + "path/filepath" + "runtime" + "testing" +) + +const executable_EnvVar = "OSTEST_OUTPUT_EXECPATH" + +func TestExecutable(t *testing.T) { + testenv.MustHaveExec(t) // will also execlude nacl, which doesn't support Executable anyway + ep, err := os.Executable() + if err != nil { + switch goos := runtime.GOOS; goos { + case "openbsd": // procfs is not mounted by default + t.Skipf("Executable failed on %s: %v, expected", goos, err) + } + t.Fatalf("Executable failed: %v", err) + } + // we want fn to be of the form "dir/prog" + dir := filepath.Dir(filepath.Dir(ep)) + fn, err := filepath.Rel(dir, ep) + if err != nil { + t.Fatalf("filepath.Rel: %v", err) + } + cmd := &osexec.Cmd{} + // make child start with a relative program path + cmd.Dir = dir + cmd.Path = fn + // forge argv[0] for child, so that we can verify we could correctly + // get real path of the executable without influenced by argv[0]. + cmd.Args = []string{"-", "-test.run=XXXX"} + cmd.Env = append(os.Environ(), fmt.Sprintf("%s=1", executable_EnvVar)) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("exec(self) failed: %v", err) + } + outs := string(out) + if !filepath.IsAbs(outs) { + t.Fatalf("Child returned %q, want an absolute path", out) + } + if !sameFile(outs, ep) { + t.Fatalf("Child returned %q, not the same file as %q", out, ep) + } +} + +func sameFile(fn1, fn2 string) bool { + fi1, err := os.Stat(fn1) + if err != nil { + return false + } + fi2, err := os.Stat(fn2) + if err != nil { + return false + } + return os.SameFile(fi1, fi2) +} + +func init() { + if e := os.Getenv(executable_EnvVar); e != "" { + // first chdir to another path + dir := "/" + if runtime.GOOS == "windows" { + cwd, err := os.Getwd() + if err != nil { + panic(err) + } + dir = filepath.VolumeName(cwd) + } + os.Chdir(dir) + if ep, err := os.Executable(); err != nil { + fmt.Fprint(os.Stderr, "ERROR: ", err) + } else { + fmt.Fprint(os.Stderr, ep) + } + os.Exit(0) + } +} diff --git a/libgo/go/os/executable_windows.go b/libgo/go/os/executable_windows.go new file mode 100644 index 0000000000..fc5cf86005 --- /dev/null +++ b/libgo/go/os/executable_windows.go @@ -0,0 +1,32 @@ +// Copyright 2016 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 ( + "internal/syscall/windows" + "syscall" +) + +func getModuleFileName(handle syscall.Handle) (string, error) { + n := uint32(1024) + var buf []uint16 + for { + buf = make([]uint16, n) + r, err := windows.GetModuleFileName(handle, &buf[0], n) + if err != nil { + return "", err + } + if r < n { + break + } + // r == n means n not big enough + n += 1024 + } + return syscall.UTF16ToString(buf), nil +} + +func executable() (string, error) { + return getModuleFileName(0) +} diff --git a/libgo/go/os/export_test.go b/libgo/go/os/export_test.go index 9fa7936ae6..d735aeea61 100644 --- a/libgo/go/os/export_test.go +++ b/libgo/go/os/export_test.go @@ -1,4 +1,4 @@ -// Copyright 2011 The Go Authors. All rights reserved. +// 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. diff --git a/libgo/go/os/export_windows_test.go b/libgo/go/os/export_windows_test.go new file mode 100644 index 0000000000..3bb2d2015f --- /dev/null +++ b/libgo/go/os/export_windows_test.go @@ -0,0 +1,13 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os + +// Export for testing. + +var ( + FixLongPath = fixLongPath + NewConsoleFile = newConsoleFile + ReadConsoleFunc = &readConsole +) diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go index 4f8e3f3450..d45a00b123 100644 --- a/libgo/go/os/file.go +++ b/libgo/go/os/file.go @@ -46,6 +46,10 @@ func (f *File) Name() string { return f.name } // Stdin, Stdout, and Stderr are open Files pointing to the standard input, // standard output, and standard error file descriptors. +// +// Note that the Go runtime writes to standard error for panics and crashes; +// closing Stderr may cause those messages to go elsewhere, perhaps +// to a file opened later. var ( Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin") Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout") @@ -66,6 +70,8 @@ const ( ) // Seek whence values. +// +// Deprecated: Use io.SeekStart, io.SeekCurrent, and io.SeekEnd. const ( SEEK_SET int = 0 // seek relative to the origin of the file SEEK_CUR int = 1 // seek relative to the current offset @@ -86,11 +92,11 @@ func (e *LinkError) Error() string { } // Read reads up to len(b) bytes from the File. -// It returns the number of bytes read and an error, if any. -// EOF is signaled by a zero count with err set to io.EOF. +// It returns the number of bytes read and any error encountered. +// At end of file, Read returns 0, io.EOF. func (f *File) Read(b []byte) (n int, err error) { - if f == nil { - return 0, ErrInvalid + if err := f.checkValid("read"); err != nil { + return 0, err } n, e := f.read(b) if n == 0 && len(b) > 0 && e == nil { @@ -107,8 +113,8 @@ func (f *File) Read(b []byte) (n int, err error) { // ReadAt always returns a non-nil error when n < len(b). // At end of file, that error is io.EOF. func (f *File) ReadAt(b []byte, off int64) (n int, err error) { - if f == nil { - return 0, ErrInvalid + if err := f.checkValid("read"); err != nil { + return 0, err } for len(b) > 0 { m, e := f.pread(b, off) @@ -130,8 +136,8 @@ func (f *File) ReadAt(b []byte, off int64) (n int, err error) { // It returns the number of bytes written and an error, if any. // Write returns a non-nil error when n != len(b). func (f *File) Write(b []byte) (n int, err error) { - if f == nil { - return 0, ErrInvalid + if err := f.checkValid("write"); err != nil { + return 0, err } n, e := f.write(b) if n < 0 { @@ -153,8 +159,8 @@ func (f *File) Write(b []byte) (n int, err error) { // It returns the number of bytes written and an error, if any. // WriteAt returns a non-nil error when n != len(b). func (f *File) WriteAt(b []byte, off int64) (n int, err error) { - if f == nil { - return 0, ErrInvalid + if err := f.checkValid("write"); err != nil { + return 0, err } for len(b) > 0 { m, e := f.pwrite(b, off) @@ -175,8 +181,8 @@ func (f *File) WriteAt(b []byte, off int64) (n int, err error) { // It returns the new offset and an error, if any. // The behavior of Seek on a file opened with O_APPEND is not specified. func (f *File) Seek(offset int64, whence int) (ret int64, err error) { - if f == nil { - return 0, ErrInvalid + if err := f.checkValid("seek"); err != nil { + return 0, err } r, e := f.seek(offset, whence) if e == nil && f.dirinfo != nil && r != 0 { @@ -191,16 +197,13 @@ func (f *File) Seek(offset int64, whence int) (ret int64, err error) { // WriteString is like Write, but writes the contents of string s rather than // a slice of bytes. func (f *File) WriteString(s string) (n int, err error) { - if f == nil { - return 0, ErrInvalid - } return f.Write([]byte(s)) } // Mkdir creates a new directory with the specified name and permission bits. // If there is an error, it will be of type *PathError. func Mkdir(name string, perm FileMode) error { - e := syscall.Mkdir(name, syscallMode(perm)) + e := syscall.Mkdir(fixLongPath(name), syscallMode(perm)) if e != nil { return &PathError{"mkdir", name, e} @@ -227,8 +230,8 @@ func Chdir(dir string) error { // which must be a directory. // If there is an error, it will be of type *PathError. func (f *File) Chdir() error { - if f == nil { - return ErrInvalid + if err := f.checkValid("chdir"); err != nil { + return err } if e := syscall.Fchdir(f.fd); e != nil { return &PathError{"chdir", f.name, e} @@ -236,7 +239,7 @@ func (f *File) Chdir() error { return nil } -// Open opens the named file for reading. If successful, methods on +// Open opens the named file for reading. If successful, methods on // the returned file can be used for reading; the associated file // descriptor has mode O_RDONLY. // If there is an error, it will be of type *PathError. @@ -257,7 +260,7 @@ func Create(name string) (*File, error) { var lstat = Lstat // Rename renames (moves) oldpath to newpath. -// If newpath already exists, Rename replaces it. +// If newpath already exists and is not a directory, Rename replaces it. // OS-specific restrictions may apply when oldpath and newpath are in different directories. // If there is an error, it will be of type *LinkError. func Rename(oldpath, newpath string) error { @@ -272,3 +275,15 @@ func fixCount(n int, err error) (int, error) { } return n, err } + +// checkValid checks whether f is valid for use. +// If not, it returns an appropriate error, perhaps incorporating the operation name op. +func (f *File) checkValid(op string) error { + if f == nil { + return ErrInvalid + } + if f.fd == badFd { + return &PathError{op, f.name, ErrClosed} + } + return nil +} diff --git a/libgo/go/os/file_plan9.go b/libgo/go/os/file_plan9.go index c83fa028b9..5276a7ec54 100644 --- a/libgo/go/os/file_plan9.go +++ b/libgo/go/os/file_plan9.go @@ -5,14 +5,15 @@ package os import ( + "io" "runtime" "syscall" "time" ) -// File represents an open file descriptor. -type File struct { - *file +// fixLongPath is a noop on non-Windows platforms. +func fixLongPath(path string) string { + return path } // file is the real representation of *File. @@ -75,8 +76,8 @@ func syscallMode(i FileMode) (o uint32) { } // 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, +// or Create instead. It opens the named file with specified flag +// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful, // methods on the returned File can be used for I/O. // If there is an error, it will be of type *PathError. func OpenFile(name string, flag int, perm FileMode) (*File, error) { @@ -123,7 +124,7 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) { } if append { - if _, e = syscall.Seek(fd, 0, SEEK_END); e != nil { + if _, e = syscall.Seek(fd, 0, io.SeekEnd); e != nil { return nil, &PathError{"seek", name, e} } } @@ -134,23 +135,21 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) { // Close closes the File, rendering it unusable for I/O. // It returns an error, if any. func (f *File) Close() error { - if f == nil { - return ErrInvalid + if err := f.checkValid("close"); err != nil { + return err } return f.file.close() } func (file *file) close() error { - if file == nil || file.fd < 0 { + if file == nil || file.fd == badFd { return ErrInvalid } var err error - syscall.ForkLock.RLock() if e := syscall.Close(file.fd); e != nil { err = &PathError{"close", file.name, e} } - syscall.ForkLock.RUnlock() - file.fd = -1 // so it can't be closed again + file.fd = badFd // so it can't be closed again // no need for a finalizer anymore runtime.SetFinalizer(file, nil) @@ -419,12 +418,9 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { func Pipe() (r *File, w *File, err error) { var p [2]int - syscall.ForkLock.RLock() if e := syscall.Pipe(p[0:]); e != nil { - syscall.ForkLock.RUnlock() return nil, nil, NewSyscallError("pipe", e) } - syscall.ForkLock.RUnlock() return NewFile(uintptr(p[0]), "|0"), NewFile(uintptr(p[1]), "|1"), nil } diff --git a/libgo/go/os/file_posix.go b/libgo/go/os/file_posix.go index 6d8076fdf5..d817f34b1d 100644 --- a/libgo/go/os/file_posix.go +++ b/libgo/go/os/file_posix.go @@ -18,7 +18,7 @@ func sigpipe() // implemented in package runtime func Readlink(name string) (string, error) { for len := 128; ; len *= 2 { b := make([]byte, len) - n, e := fixCount(syscall.Readlink(name, b)) + n, e := fixCount(syscall.Readlink(fixLongPath(name), b)) if e != nil { return "", &PathError{"readlink", name, e} } @@ -57,8 +57,8 @@ func Chmod(name string, mode FileMode) error { // Chmod changes the mode of the file to mode. // If there is an error, it will be of type *PathError. func (f *File) Chmod(mode FileMode) error { - if f == nil { - return ErrInvalid + if err := f.checkValid("chmod"); err != nil { + return err } if e := syscall.Fchmod(f.fd, syscallMode(mode)); e != nil { return &PathError{"chmod", f.name, e} @@ -89,8 +89,8 @@ func Lchown(name string, uid, gid int) error { // Chown changes the numeric uid and gid of the named file. // If there is an error, it will be of type *PathError. func (f *File) Chown(uid, gid int) error { - if f == nil { - return ErrInvalid + if err := f.checkValid("chown"); err != nil { + return err } if e := syscall.Fchown(f.fd, uid, gid); e != nil { return &PathError{"chown", f.name, e} @@ -102,8 +102,8 @@ func (f *File) Chown(uid, gid int) error { // It does not change the I/O offset. // If there is an error, it will be of type *PathError. func (f *File) Truncate(size int64) error { - if f == nil { - return ErrInvalid + if err := f.checkValid("truncate"); err != nil { + return err } if e := syscall.Ftruncate(f.fd, size); e != nil { return &PathError{"truncate", f.name, e} @@ -115,11 +115,11 @@ func (f *File) Truncate(size int64) error { // Typically, this means flushing the file system's in-memory copy // of recently written data to disk. func (f *File) Sync() error { - if f == nil { - return ErrInvalid + if err := f.checkValid("sync"); err != nil { + return err } if e := syscall.Fsync(f.fd); e != nil { - return NewSyscallError("fsync", e) + return &PathError{"sync", f.name, e} } return nil } @@ -134,7 +134,7 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { var utimes [2]syscall.Timespec utimes[0] = syscall.NsecToTimespec(atime.UnixNano()) utimes[1] = syscall.NsecToTimespec(mtime.UnixNano()) - if e := syscall.UtimesNano(name, utimes[0:]); e != nil { + if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil { return &PathError{"chtimes", name, e} } return nil diff --git a/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go index c3119cd459..54b5dfd128 100644 --- a/libgo/go/os/file_unix.go +++ b/libgo/go/os/file_unix.go @@ -11,11 +11,16 @@ import ( "syscall" ) -func sameFile(fs1, fs2 *fileStat) bool { - return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino +// fixLongPath is a noop on non-Windows platforms. +func fixLongPath(path string) string { + return path } func rename(oldname, newname string) error { + fi, err := Lstat(newname) + if err == nil && fi.IsDir() { + return &LinkError{"rename", oldname, newname, syscall.EEXIST} + } e := syscall.Rename(oldname, newname) if e != nil { return &LinkError{"rename", oldname, newname, e} @@ -23,11 +28,6 @@ func rename(oldname, newname string) error { return nil } -// File represents an open file descriptor. -type File struct { - *file -} - // file is the real representation of *File. // The extra level of indirection ensures that no clients of os // can overwrite this data, which could cause the finalizer @@ -78,8 +78,8 @@ func epipecheck(file *File, e error) { 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, +// or Create instead. It opens the named file with specified flag +// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful, // methods on the returned File can be used for I/O. // If there is an error, it will be of type *PathError. func OpenFile(name string, flag int, perm FileMode) (*File, error) { @@ -114,7 +114,7 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) { } // There's a race here with fork/exec, which we are - // content to live with. See ../syscall/exec_unix.go. + // content to live with. See ../syscall/exec_unix.go. if !supportsCloseOnExec { syscall.CloseOnExec(r) } @@ -132,7 +132,7 @@ func (f *File) Close() error { } func (file *file) close() error { - if file == nil || file.fd < 0 { + if file == nil || file.fd == badFd { return syscall.EINVAL } var err error @@ -158,69 +158,6 @@ func (file *file) close() error { return err } -// Stat returns the FileInfo structure describing file. -// If there is an error, it will be of type *PathError. -func (f *File) Stat() (FileInfo, error) { - if f == nil { - return nil, ErrInvalid - } - var fs fileStat - err := syscall.Fstat(f.fd, &fs.sys) - if err != nil { - return nil, &PathError{"stat", f.name, err} - } - fillFileStatFromSys(&fs, f.name) - return &fs, nil -} - -// Stat returns a FileInfo describing the named file. -// If there is an error, it will be of type *PathError. -func Stat(name string) (FileInfo, error) { - var fs fileStat - err := syscall.Stat(name, &fs.sys) - if err != nil { - return nil, &PathError{"stat", name, err} - } - fillFileStatFromSys(&fs, name) - return &fs, nil -} - -// Lstat returns a FileInfo describing the named file. -// If the file is a symbolic link, the returned FileInfo -// describes the symbolic link. Lstat makes no attempt to follow the link. -// If there is an error, it will be of type *PathError. -func Lstat(name string) (FileInfo, error) { - var fs fileStat - err := syscall.Lstat(name, &fs.sys) - if err != nil { - return nil, &PathError{"lstat", name, err} - } - fillFileStatFromSys(&fs, name) - return &fs, nil -} - -func (f *File) readdir(n int) (fi []FileInfo, err error) { - dirname := f.name - if dirname == "" { - dirname = "." - } - names, err := f.Readdirnames(n) - fi = make([]FileInfo, 0, len(names)) - for _, filename := range names { - fip, lerr := lstat(dirname + "/" + filename) - if IsNotExist(lerr) { - // File disappeared between readdir + stat. - // Just treat it as if it didn't exist. - continue - } - if lerr != nil { - return fi, lerr - } - fi = append(fi, fip) - } - return fi, err -} - // Darwin and FreeBSD can't read or write 2GB+ at a time, // even on 64-bit systems. See golang.org/issue/7812. // Use 1GB instead of, say, 2GB-1, to keep subsequent @@ -322,7 +259,7 @@ func Remove(name string) error { // 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, + // 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 @@ -335,24 +272,6 @@ func Remove(name string) error { return &PathError{"remove", name, e} } -// basename removes trailing slashes and the leading directory name from path name -func basename(name string) string { - i := len(name) - 1 - // Remove trailing slashes - for ; i > 0 && name[i] == '/'; i-- { - name = name[:i] - } - // Remove leading directory name - for i--; i >= 0; i-- { - if name[i] == '/' { - name = name[i+1:] - break - } - } - - return name -} - // TempDir returns the default directory to use for temporary files. func TempDir() string { dir := Getenv("TMPDIR") diff --git a/libgo/go/os/getwd.go b/libgo/go/os/getwd.go index d5da53b34b..4c3c0d94cb 100644 --- a/libgo/go/os/getwd.go +++ b/libgo/go/os/getwd.go @@ -20,7 +20,7 @@ var getwdCache struct { var useSyscallwd = func(error) bool { return true } // Getwd returns a rooted path name corresponding to the -// current directory. If the current directory can be +// current directory. If the current directory can be // reached via multiple paths (due to symbolic links), // Getwd may return any one of them. func Getwd() (dir string, err error) { @@ -74,7 +74,7 @@ func Getwd() (dir string, err error) { } // General algorithm: find name in parent - // and then find name of parent. Each iteration + // and then find name of parent. Each iteration // adds /name to the beginning of dir. dir = "" for parent := ".."; ; parent = "../" + parent { diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go index 5497704e16..dcc8d762bf 100644 --- a/libgo/go/os/os_test.go +++ b/libgo/go/os/os_test.go @@ -25,10 +25,8 @@ import ( "time" ) -var supportsSymlinks = true - var dot = []string{ - "dir_unix.go", + "dir.go", "env.go", "error.go", "file.go", @@ -45,21 +43,24 @@ var sysdir = func() *sysDir { switch runtime.GOOS { case "android": return &sysDir{ - "/system/framework", + "/system/lib", []string{ - "ext.jar", - "framework.jar", + "libmedia.so", + "libpowermanager.so", }, } case "darwin": switch runtime.GOARCH { case "arm", "arm64": + /// At this point the test harness has not had a chance + // to move us into the ./src/os directory, so the + // current working directory is the root of the app. wd, err := syscall.Getwd() if err != nil { wd = err.Error() } return &sysDir{ - filepath.Join(wd, "..", ".."), + wd, []string{ "ResourceRules.plist", "Info.plist", @@ -229,6 +230,28 @@ func TestRead0(t *testing.T) { } } +// Reading a closed file should should return ErrClosed error +func TestReadClosed(t *testing.T) { + path := sfdir + "/" + sfname + file, err := Open(path) + if err != nil { + t.Fatal("open failed:", err) + } + file.Close() // close immediately + + b := make([]byte, 100) + _, err = file.Read(b) + + e, ok := err.(*PathError) + if !ok { + t.Fatalf("Read: %T(%v), want PathError", e, e) + } + + if e.Err != ErrClosed { + t.Errorf("Read: %v, want PathError(ErrClosed)", e) + } +} + func testReaddirnames(dir string, contents []string, t *testing.T) { file, err := Open(dir) if err != nil { @@ -536,7 +559,7 @@ func TestReaddirStatFailures(t *testing.T) { return s } - if got, want := names(mustReadDir("inital readdir")), + if got, want := names(mustReadDir("initial readdir")), []string{"good1", "good2", "x"}; !reflect.DeepEqual(got, want) { t.Errorf("initial readdir got %q; want %q", got, want) } @@ -578,15 +601,8 @@ func TestReaddirOfFile(t *testing.T) { } func TestHardLink(t *testing.T) { - if runtime.GOOS == "plan9" { - t.Skip("skipping on plan9, hardlinks not supported") - } - // From Android release M (Marshmallow), hard linking files is blocked - // and an attempt to call link() on a file will return EACCES. - // - https://code.google.com/p/android-developer-preview/issues/detail?id=3150 - if runtime.GOOS == "android" { - t.Skip("skipping on android, hardlinks not supported") - } + testenv.MustHaveLink(t) + defer chtmpdir(t)() from, to := "hardlinktestfrom", "hardlinktestto" Remove(from) // Just in case. @@ -650,14 +666,8 @@ func chtmpdir(t *testing.T) func() { } func TestSymlink(t *testing.T) { - switch runtime.GOOS { - case "android", "nacl", "plan9": - t.Skipf("skipping on %s", runtime.GOOS) - case "windows": - if !supportsSymlinks { - t.Skipf("skipping on %s", runtime.GOOS) - } - } + testenv.MustHaveSymlink(t) + defer chtmpdir(t)() from, to := "symlinktestfrom", "symlinktestto" Remove(from) // Just in case. @@ -717,14 +727,8 @@ func TestSymlink(t *testing.T) { } func TestLongSymlink(t *testing.T) { - switch runtime.GOOS { - case "android", "plan9", "nacl": - t.Skipf("skipping on %s", runtime.GOOS) - case "windows": - if !supportsSymlinks { - t.Skipf("skipping on %s", runtime.GOOS) - } - } + testenv.MustHaveSymlink(t) + defer chtmpdir(t)() s := "0123456789abcdef" // Long, but not too long: a common limit is 255. @@ -840,6 +844,39 @@ func TestRenameFailed(t *testing.T) { } } +func TestRenameToDirFailed(t *testing.T) { + defer chtmpdir(t)() + from, to := "renamefrom", "renameto" + + Remove(from) + Remove(to) + Mkdir(from, 0777) + Mkdir(to, 0777) + defer Remove(from) + defer Remove(to) + + err := Rename(from, to) + switch err := err.(type) { + case *LinkError: + if err.Op != "rename" { + t.Errorf("rename %q, %q: err.Op: want %q, got %q", from, to, "rename", err.Op) + } + if err.Old != from { + t.Errorf("rename %q, %q: err.Old: want %q, got %q", from, to, from, err.Old) + } + if err.New != to { + t.Errorf("rename %q, %q: err.New: want %q, got %q", from, to, to, err.New) + } + case nil: + t.Errorf("rename %q, %q: expected error, got nil", from, to) + + // cleanup whatever was placed in "renameto" + Remove(to) + default: + t.Errorf("rename %q, %q: expected %T, got %T %v", from, to, new(LinkError), err, err) + } +} + func exec(t *testing.T, dir, cmd string, args []string, expect string) { r, w, err := Pipe() if err != nil { @@ -1028,7 +1065,7 @@ func testChtimes(t *testing.T, name string) { } if !pmt.Before(mt) { - t.Errorf("ModTime didn't go backwards; was=%d, after=%d", mt, pmt) + t.Errorf("ModTime didn't go backwards; was=%v, after=%v", mt, pmt) } } @@ -1180,14 +1217,14 @@ func TestSeek(t *testing.T) { 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))}, + {0, io.SeekCurrent, int64(len(data))}, + {0, io.SeekStart, 0}, + {5, io.SeekStart, 5}, + {0, io.SeekEnd, int64(len(data))}, + {0, io.SeekStart, 0}, + {-1, io.SeekEnd, int64(len(data)) - 1}, + {1 << 33, io.SeekStart, 1 << 33}, + {1 << 33, io.SeekEnd, 1<<33 + int64(len(data))}, } for i, tt := range tests { off, err := f.Seek(tt.in, tt.whence) @@ -1273,15 +1310,19 @@ func TestOpenNoName(t *testing.T) { } } -func run(t *testing.T, cmd []string) string { +func runBinHostname(t *testing.T) string { // Run /bin/hostname and collect output. r, w, err := Pipe() if err != nil { t.Fatal(err) } defer r.Close() - p, err := StartProcess("/bin/hostname", []string{"hostname"}, &ProcAttr{Files: []*File{nil, w, Stderr}}) + const path = "/bin/hostname" + p, err := StartProcess(path, []string{"hostname"}, &ProcAttr{Files: []*File{nil, w, Stderr}}) if err != nil { + if _, err := Stat(path); IsNotExist(err) { + t.Skipf("skipping test; test requires %s but it does not exist", path) + } t.Fatal(err) } w.Close() @@ -1301,7 +1342,7 @@ func run(t *testing.T, cmd []string) string { output = output[0 : n-1] } if output == "" { - t.Fatalf("%v produced no output", cmd) + t.Fatalf("/bin/hostname produced no output") } return output @@ -1343,7 +1384,7 @@ func TestHostname(t *testing.T) { if err != nil { t.Fatalf("%v", err) } - want := run(t, []string{"/bin/hostname"}) + want := runBinHostname(t) if hostname != want { i := strings.Index(hostname, ".") if i < 0 || hostname[0:i] != want { @@ -1370,6 +1411,38 @@ func TestReadAt(t *testing.T) { } } +// Verify that ReadAt doesn't affect seek offset. +// In the Plan 9 kernel, there used to be a bug in the implementation of +// the pread syscall, where the channel offset was erroneously updated after +// calling pread on a file. +func TestReadAtOffset(t *testing.T) { + f := newFile("TestReadAtOffset", t) + defer Remove(f.Name()) + defer f.Close() + + const data = "hello, world\n" + io.WriteString(f, data) + + f.Seek(0, 0) + b := make([]byte, 5) + + n, err := f.ReadAt(b, 7) + if err != nil || n != len(b) { + t.Fatalf("ReadAt 7: %d, %v", n, err) + } + if string(b) != "world" { + t.Fatalf("ReadAt 7: have %q want %q", string(b), "world") + } + + n, err = f.Read(b) + if err != nil || n != len(b) { + t.Fatalf("Read: %d, %v", n, err) + } + if string(b) != "hello" { + t.Fatalf("Read: have %q want %q", string(b), "hello") + } +} + func TestWriteAt(t *testing.T) { f := newFile("TestWriteAt", t) defer Remove(f.Name()) @@ -1577,6 +1650,42 @@ func TestStatDirModeExec(t *testing.T) { } } +func TestStatStdin(t *testing.T) { + switch runtime.GOOS { + case "android", "plan9": + t.Skipf("%s doesn't have /bin/sh", runtime.GOOS) + } + + testenv.MustHaveExec(t) + + if Getenv("GO_WANT_HELPER_PROCESS") == "1" { + st, err := Stdin.Stat() + if err != nil { + t.Fatalf("Stat failed: %v", err) + } + fmt.Println(st.Mode() & ModeNamedPipe) + Exit(0) + } + + var cmd *osexec.Cmd + if runtime.GOOS == "windows" { + cmd = osexec.Command("cmd", "/c", "echo output | "+Args[0]+" -test.run=TestStatStdin") + } else { + cmd = osexec.Command("/bin/sh", "-c", "echo output | "+Args[0]+" -test.run=TestStatStdin") + } + cmd.Env = append(Environ(), "GO_WANT_HELPER_PROCESS=1") + + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Failed to spawn child process: %v %q", err, string(output)) + } + + // result will be like "prw-rw-rw" + if len(output) < 1 || output[0] != 'p' { + t.Fatalf("Child process reports stdin is not pipe '%v'", string(output)) + } +} + func TestReadAtEOF(t *testing.T) { f := newFile("TestReadAtEOF", t) defer Remove(f.Name()) @@ -1593,6 +1702,72 @@ func TestReadAtEOF(t *testing.T) { } } +func TestLongPath(t *testing.T) { + tmpdir := newDir("TestLongPath", t) + defer func(d string) { + if err := RemoveAll(d); err != nil { + t.Fatalf("RemoveAll failed: %v", err) + } + }(tmpdir) + + // Test the boundary of 247 and fewer bytes (normal) and 248 and more bytes (adjusted). + sizes := []int{247, 248, 249, 400} + for len(tmpdir) < 400 { + tmpdir += "/dir3456789" + } + for _, sz := range sizes { + t.Run(fmt.Sprintf("length=%d", sz), func(t *testing.T) { + sizedTempDir := tmpdir[:sz-1] + "x" // Ensure it does not end with a slash. + + // The various sized runs are for this call to trigger the boundary + // condition. + if err := MkdirAll(sizedTempDir, 0755); err != nil { + t.Fatalf("MkdirAll failed: %v", err) + } + data := []byte("hello world\n") + if err := ioutil.WriteFile(sizedTempDir+"/foo.txt", data, 0644); err != nil { + t.Fatalf("ioutil.WriteFile() failed: %v", err) + } + if err := Rename(sizedTempDir+"/foo.txt", sizedTempDir+"/bar.txt"); err != nil { + t.Fatalf("Rename failed: %v", err) + } + mtime := time.Now().Truncate(time.Minute) + if err := Chtimes(sizedTempDir+"/bar.txt", mtime, mtime); err != nil { + t.Fatalf("Chtimes failed: %v", err) + } + names := []string{"bar.txt"} + if testenv.HasSymlink() { + if err := Symlink(sizedTempDir+"/bar.txt", sizedTempDir+"/symlink.txt"); err != nil { + t.Fatalf("Symlink failed: %v", err) + } + names = append(names, "symlink.txt") + } + if testenv.HasLink() { + if err := Link(sizedTempDir+"/bar.txt", sizedTempDir+"/link.txt"); err != nil { + t.Fatalf("Link failed: %v", err) + } + names = append(names, "link.txt") + } + for _, wantSize := range []int64{int64(len(data)), 0} { + for _, name := range names { + path := sizedTempDir + "/" + name + dir, err := Stat(path) + if err != nil { + t.Fatalf("Stat(%q) failed: %v", path, err) + } + filesize := size(path, t) + if dir.Size() != filesize || filesize != wantSize { + t.Errorf("Size(%q) is %d, len(ReadFile()) is %d, want %d", path, dir.Size(), filesize, wantSize) + } + } + if err := Truncate(sizedTempDir+"/bar.txt", 0); err != nil { + t.Fatalf("Truncate failed: %v", err) + } + } + }) + } +} + func testKillProcess(t *testing.T, processKiller func(p *Process)) { testenv.MustHaveExec(t) @@ -1684,7 +1859,7 @@ var nilFileMethodTests = []struct { {"ReadAt", func(f *File) error { _, err := f.ReadAt(make([]byte, 0), 0); return err }}, {"Readdir", func(f *File) error { _, err := f.Readdir(1); return err }}, {"Readdirnames", func(f *File) error { _, err := f.Readdirnames(1); return err }}, - {"Seek", func(f *File) error { _, err := f.Seek(0, 0); return err }}, + {"Seek", func(f *File) error { _, err := f.Seek(0, io.SeekStart); return err }}, {"Stat", func(f *File) error { _, err := f.Stat(); return err }}, {"Sync", func(f *File) error { return f.Sync() }}, {"Truncate", func(f *File) error { return f.Truncate(0) }}, diff --git a/libgo/go/os/os_unix_test.go b/libgo/go/os/os_unix_test.go index d02e07b478..e239835c6a 100644 --- a/libgo/go/os/os_unix_test.go +++ b/libgo/go/os/os_unix_test.go @@ -7,8 +7,12 @@ package os_test import ( + "io" + "io/ioutil" . "os" + "path/filepath" "runtime" + "strings" "syscall" "testing" ) @@ -39,7 +43,7 @@ func TestChown(t *testing.T) { } // 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 + // on the file. On NFS, the Getgroups groups are // basically useless. f := newFile("TestChown", t) defer Remove(f.Name()) @@ -50,7 +54,7 @@ func TestChown(t *testing.T) { } // Can't change uid unless root, but can try - // changing the group id. First try our current group. + // changing the group id. First try our current group. gid := Getgid() t.Log("gid:", gid) if err = Chown(f.Name(), -1, gid); err != nil { @@ -86,7 +90,7 @@ func TestFileChown(t *testing.T) { } // 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 + // on the file. On NFS, the Getgroups groups are // basically useless. f := newFile("TestFileChown", t) defer Remove(f.Name()) @@ -97,7 +101,7 @@ func TestFileChown(t *testing.T) { } // Can't change uid unless root, but can try - // changing the group id. First try our current group. + // changing the group id. First try our current group. gid := Getgid() t.Log("gid:", gid) if err = f.Chown(-1, gid); err != nil { @@ -133,7 +137,7 @@ func TestLchown(t *testing.T) { } // 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 + // on the file. On NFS, the Getgroups groups are // basically useless. f := newFile("TestLchown", t) defer Remove(f.Name()) @@ -145,12 +149,15 @@ func TestLchown(t *testing.T) { linkname := f.Name() + "2" if err := Symlink(f.Name(), linkname); err != nil { + if runtime.GOOS == "android" && IsPermission(err) { + t.Skip("skipping test on Android; permission error creating symlink") + } t.Fatalf("link %s -> %s: %v", f.Name(), linkname, err) } defer Remove(linkname) // Can't change uid unless root, but can try - // changing the group id. First try our current group. + // changing the group id. First try our current group. gid := Getgid() t.Log("gid:", gid) if err = Lchown(linkname, -1, gid); err != nil { @@ -175,3 +182,38 @@ func TestLchown(t *testing.T) { checkUidGid(t, f.Name(), int(sys.Uid), int(sys.Gid)) } } + +// Issue 16919: Readdir must return a non-empty slice or an error. +func TestReaddirRemoveRace(t *testing.T) { + oldStat := *LstatP + defer func() { *LstatP = oldStat }() + *LstatP = func(name string) (FileInfo, error) { + if strings.HasSuffix(name, "some-file") { + // Act like it's been deleted. + return nil, ErrNotExist + } + return oldStat(name) + } + dir := newDir("TestReaddirRemoveRace", t) + defer RemoveAll(dir) + if err := ioutil.WriteFile(filepath.Join(dir, "some-file"), []byte("hello"), 0644); err != nil { + t.Fatal(err) + } + d, err := Open(dir) + if err != nil { + t.Fatal(err) + } + defer d.Close() + fis, err := d.Readdir(2) // notably, greater than zero + if len(fis) == 0 && err == nil { + // This is what used to happen (Issue 16919) + t.Fatal("Readdir = empty slice & err == nil") + } + if len(fis) != 0 || err != io.EOF { + t.Errorf("Readdir = %d entries: %v; want 0, io.EOF", len(fis), err) + for i, fi := range fis { + t.Errorf(" entry[%d]: %q, %v", i, fi.Name(), fi.Mode()) + } + t.FailNow() + } +} diff --git a/libgo/go/os/path.go b/libgo/go/os/path.go index 84a3be3348..146d7b6954 100644 --- a/libgo/go/os/path.go +++ b/libgo/go/os/path.go @@ -61,7 +61,7 @@ func MkdirAll(path string, perm FileMode) error { // 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 +// 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. diff --git a/libgo/go/os/path_test.go b/libgo/go/os/path_test.go index b4531314d0..6f5bfa54f8 100644 --- a/libgo/go/os/path_test.go +++ b/libgo/go/os/path_test.go @@ -5,6 +5,7 @@ package os_test import ( + "internal/testenv" "io/ioutil" . "os" "path/filepath" @@ -147,7 +148,7 @@ func TestRemoveAll(t *testing.T) { // 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 + // 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) @@ -169,14 +170,7 @@ func TestRemoveAll(t *testing.T) { } func TestMkdirAllWithSymlink(t *testing.T) { - switch runtime.GOOS { - case "android", "nacl", "plan9": - t.Skipf("skipping on %s", runtime.GOOS) - case "windows": - if !supportsSymlinks { - t.Skipf("skipping on %s", runtime.GOOS) - } - } + testenv.MustHaveSymlink(t) tmpDir, err := ioutil.TempDir("", "TestMkdirAllWithSymlink-") if err != nil { diff --git a/libgo/go/os/path_unix.go b/libgo/go/os/path_unix.go index 36f8e61bf9..ecf098c461 100644 --- a/libgo/go/os/path_unix.go +++ b/libgo/go/os/path_unix.go @@ -15,3 +15,21 @@ const ( func IsPathSeparator(c uint8) bool { return PathSeparator == c } + +// 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 +} diff --git a/libgo/go/os/path_windows.go b/libgo/go/os/path_windows.go index c96f137686..101b026dc9 100644 --- a/libgo/go/os/path_windows.go +++ b/libgo/go/os/path_windows.go @@ -14,3 +14,196 @@ func IsPathSeparator(c uint8) bool { // NOTE: Windows accept / as path separator. return c == '\\' || c == '/' } + +// basename removes trailing slashes and the leading +// directory name and drive letter from path name. +func basename(name string) string { + // Remove drive letter + if len(name) == 2 && name[1] == ':' { + name = "." + } else if len(name) > 2 && name[1] == ':' { + name = name[2:] + } + i := len(name) - 1 + // Remove trailing slashes + for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i-- { + name = name[:i] + } + // Remove leading directory name + for i--; i >= 0; i-- { + if name[i] == '/' || name[i] == '\\' { + name = name[i+1:] + break + } + } + return name +} + +func isAbs(path string) (b bool) { + v := volumeName(path) + if v == "" { + return false + } + path = path[len(v):] + if path == "" { + return false + } + return IsPathSeparator(path[0]) +} + +func volumeName(path string) (v string) { + if len(path) < 2 { + return "" + } + // with drive letter + c := path[0] + if path[1] == ':' && + ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || + 'A' <= c && c <= 'Z') { + return path[:2] + } + // is it UNC + if l := len(path); l >= 5 && IsPathSeparator(path[0]) && IsPathSeparator(path[1]) && + !IsPathSeparator(path[2]) && path[2] != '.' { + // first, leading `\\` and next shouldn't be `\`. its server name. + for n := 3; n < l-1; n++ { + // second, next '\' shouldn't be repeated. + if IsPathSeparator(path[n]) { + n++ + // third, following something characters. its share name. + if !IsPathSeparator(path[n]) { + if path[n] == '.' { + break + } + for ; n < l; n++ { + if IsPathSeparator(path[n]) { + break + } + } + return path[:n] + } + break + } + } + } + return "" +} + +func fromSlash(path string) string { + // Replace each '/' with '\\' if present + var pathbuf []byte + var lastSlash int + for i, b := range path { + if b == '/' { + if pathbuf == nil { + pathbuf = make([]byte, len(path)) + } + copy(pathbuf[lastSlash:], path[lastSlash:i]) + pathbuf[i] = '\\' + lastSlash = i + 1 + } + } + if pathbuf == nil { + return path + } + + copy(pathbuf[lastSlash:], path[lastSlash:]) + return string(pathbuf) +} + +func dirname(path string) string { + vol := volumeName(path) + i := len(path) - 1 + for i >= len(vol) && !IsPathSeparator(path[i]) { + i-- + } + dir := path[len(vol) : i+1] + last := len(dir) - 1 + if last > 0 && IsPathSeparator(dir[last]) { + dir = dir[:last] + } + if dir == "" { + dir = "." + } + return vol + dir +} + +// fixLongPath returns the extended-length (\\?\-prefixed) form of +// path when needed, in order to avoid the default 260 character file +// path limit imposed by Windows. If path is not easily converted to +// the extended-length form (for example, if path is a relative path +// or contains .. elements), or is short enough, fixLongPath returns +// path unmodified. +// +// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath +func fixLongPath(path string) string { + // Do nothing (and don't allocate) if the path is "short". + // Empirically (at least on the Windows Server 2013 builder), + // the kernel is arbitrarily okay with < 248 bytes. That + // matches what the docs above say: + // "When using an API to create a directory, the specified + // path cannot be so long that you cannot append an 8.3 file + // name (that is, the directory name cannot exceed MAX_PATH + // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. + // + // The MSDN docs appear to say that a normal path that is 248 bytes long + // will work; empirically the path must be less then 248 bytes long. + if len(path) < 248 { + // Don't fix. (This is how Go 1.7 and earlier worked, + // not automatically generating the \\?\ form) + return path + } + + // The extended form begins with \\?\, as in + // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. + // The extended form disables evaluation of . and .. path + // elements and disables the interpretation of / as equivalent + // to \. The conversion here rewrites / to \ and elides + // . elements as well as trailing or duplicate separators. For + // simplicity it avoids the conversion entirely for relative + // paths or paths containing .. elements. For now, + // \\server\share paths are not converted to + // \\?\UNC\server\share paths because the rules for doing so + // are less well-specified. + if len(path) >= 2 && path[:2] == `\\` { + // Don't canonicalize UNC paths. + return path + } + if !isAbs(path) { + // Relative path + return path + } + + const prefix = `\\?` + + pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) + copy(pathbuf, prefix) + n := len(path) + r, w := 0, len(prefix) + for r < n { + switch { + case IsPathSeparator(path[r]): + // empty block + r++ + case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])): + // /./ + r++ + case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])): + // /../ is currently unhandled + return path + default: + pathbuf[w] = '\\' + w++ + for ; r < n && !IsPathSeparator(path[r]); r++ { + pathbuf[w] = path[r] + w++ + } + } + } + // A drive's root directory needs a trailing \ + if w == len(`\\?\c:`) { + pathbuf[w] = '\\' + w++ + } + return string(pathbuf[:w]) +} diff --git a/libgo/go/os/path_windows_test.go b/libgo/go/os/path_windows_test.go new file mode 100644 index 0000000000..cce0bdd522 --- /dev/null +++ b/libgo/go/os/path_windows_test.go @@ -0,0 +1,46 @@ +// Copyright 2016 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" + "strings" + "testing" +) + +func TestFixLongPath(t *testing.T) { + // 248 is long enough to trigger the longer-than-248 checks in + // fixLongPath, but short enough not to make a path component + // longer than 255, which is illegal on Windows. (which + // doesn't really matter anyway, since this is purely a string + // function we're testing, and it's not actually being used to + // do a system call) + veryLong := "l" + strings.Repeat("o", 248) + "ng" + for _, test := range []struct{ in, want string }{ + // Short; unchanged: + {`C:\short.txt`, `C:\short.txt`}, + {`C:\`, `C:\`}, + {`C:`, `C:`}, + // The "long" substring is replaced by a looooooong + // string which triggers the rewriting. Except in the + // cases below where it doesn't. + {`C:\long\foo.txt`, `\\?\C:\long\foo.txt`}, + {`C:/long/foo.txt`, `\\?\C:\long\foo.txt`}, + {`C:\long\foo\\bar\.\baz\\`, `\\?\C:\long\foo\bar\baz`}, + {`\\unc\path`, `\\unc\path`}, + {`long.txt`, `long.txt`}, + {`C:long.txt`, `C:long.txt`}, + {`c:\long\..\bar\baz`, `c:\long\..\bar\baz`}, + {`\\?\c:\long\foo.txt`, `\\?\c:\long\foo.txt`}, + {`\\?\c:\long/foo.txt`, `\\?\c:\long/foo.txt`}, + } { + in := strings.Replace(test.in, "long", veryLong, -1) + want := strings.Replace(test.want, "long", veryLong, -1) + if got := os.FixLongPath(in); got != want { + got = strings.Replace(got, veryLong, "long", -1) + t.Errorf("fixLongPath(%q) = %q; want %q", test.in, got, test.want) + } + } +} diff --git a/libgo/go/os/pipe_test.go b/libgo/go/os/pipe_test.go index 82b792eac4..74cce80ee4 100644 --- a/libgo/go/os/pipe_test.go +++ b/libgo/go/os/pipe_test.go @@ -88,7 +88,7 @@ func TestStdPipe(t *testing.T) { } } -// This is a helper for TestStdPipe. It's not a test in itself. +// This is a helper for TestStdPipe. It's not a test in itself. func TestStdPipeHelper(t *testing.T) { if os.Getenv("GO_TEST_STD_PIPE_HELPER_SIGNAL") != "" { signal.Notify(make(chan os.Signal, 1), syscall.SIGPIPE) diff --git a/libgo/go/os/signal/doc.go b/libgo/go/os/signal/doc.go index 80e66cffe5..73b01a2764 100644 --- a/libgo/go/os/signal/doc.go +++ b/libgo/go/os/signal/doc.go @@ -11,7 +11,7 @@ package on Windows and Plan 9, see below. Types of signals The signals SIGKILL and SIGSTOP may not be caught by a program, and -therefore can not be affected by this package. +therefore cannot be affected by this package. Synchronous signals are signals triggered by errors in program execution: SIGBUS, SIGFPE, and SIGSEGV. These are only considered @@ -205,8 +205,8 @@ before raising the signal. Windows On Windows a ^C (Control-C) or ^BREAK (Control-Break) normally cause -the program to exit. If Notify is called for os.SIGINT, ^C or ^BREAK -will cause os.SIGINT to be sent on the channel, and the program will +the program to exit. If Notify is called for os.Interrupt, ^C or ^BREAK +will cause os.Interrupt to be sent on the channel, and the program will not exit. If Reset is called, or Stop is called on all channels passed to Notify, then the default behavior will be restored. diff --git a/libgo/go/os/signal/signal.go b/libgo/go/os/signal/signal.go index 2e6f186658..c1376daaea 100644 --- a/libgo/go/os/signal/signal.go +++ b/libgo/go/os/signal/signal.go @@ -79,7 +79,7 @@ func Ignore(sig ...os.Signal) { // // Package signal will not block sending to c: the caller must ensure // that c has sufficient buffer space to keep up with the expected -// signal rate. For a channel used for notification of just one signal value, +// signal rate. For a channel used for notification of just one signal value, // a buffer of size 1 is sufficient. // // It is allowed to call Notify multiple times with the same channel: diff --git a/libgo/go/os/signal/signal_test.go b/libgo/go/os/signal/signal_test.go index 56d786e501..406102c663 100644 --- a/libgo/go/os/signal/signal_test.go +++ b/libgo/go/os/signal/signal_test.go @@ -139,6 +139,19 @@ func testCancel(t *testing.T, ignore bool) { Reset(syscall.SIGWINCH, syscall.SIGHUP) } + // At this point we do not expect any further signals on c1. + // However, it is just barely possible that the initial SIGWINCH + // at the start of this function was delivered after we called + // Notify on c1. In that case the waitSig for SIGWINCH may have + // picked up that initial SIGWINCH, and the second SIGWINCH may + // then have been delivered on the channel. This sequence of events + // may have caused issue 15661. + // So, read any possible signal from the channel now. + select { + case <-c1: + default: + } + // Send this process a SIGWINCH. It should be ignored. syscall.Kill(syscall.Getpid(), syscall.SIGWINCH) diff --git a/libgo/go/os/stat.go b/libgo/go/os/stat.go index 9758d33496..59cac9c7ce 100644 --- a/libgo/go/os/stat.go +++ b/libgo/go/os/stat.go @@ -2,6 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build !darwin +// +build !freebsd +// +build !linux +// +build !netbsd +// +build !openbsd +// +build !solaris + package os import ( diff --git a/libgo/go/os/stat_atim.go b/libgo/go/os/stat_atim.go index 69e63230eb..ef8a574e7b 100644 --- a/libgo/go/os/stat_atim.go +++ b/libgo/go/os/stat_atim.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build linux openbsd solaristag + package os import ( diff --git a/libgo/go/os/stat_atimespec.go b/libgo/go/os/stat_atimespec.go index 9dc7a99fb7..c6ce2bc8f4 100644 --- a/libgo/go/os/stat_atimespec.go +++ b/libgo/go/os/stat_atimespec.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build darwin freebsd netbsd + package os import ( diff --git a/libgo/go/os/stat_dragonfly.go b/libgo/go/os/stat_dragonfly.go index 69e63230eb..217bc6726d 100644 --- a/libgo/go/os/stat_dragonfly.go +++ b/libgo/go/os/stat_dragonfly.go @@ -11,7 +11,7 @@ import ( func fillFileStatFromSys(fs *fileStat, name string) { fs.name = basename(name) - fs.size = int64(fs.sys.Size) + fs.size = fs.sys.Size fs.modTime = timespecToTime(fs.sys.Mtim) fs.mode = FileMode(fs.sys.Mode & 0777) switch fs.sys.Mode & syscall.S_IFMT { @@ -42,7 +42,7 @@ func fillFileStatFromSys(fs *fileStat, name string) { } func timespecToTime(ts syscall.Timespec) time.Time { - return time.Unix(int64(ts.Sec), int64(ts.Nsec)) + return time.Unix(ts.Sec, ts.Nsec) } // For testing. diff --git a/libgo/go/os/stat_nacl.go b/libgo/go/os/stat_nacl.go index d3bed14e43..0c53f2faa4 100644 --- a/libgo/go/os/stat_nacl.go +++ b/libgo/go/os/stat_nacl.go @@ -11,7 +11,7 @@ import ( func fillFileStatFromSys(fs *fileStat, name string) { fs.name = basename(name) - fs.size = int64(fs.sys.Size) + fs.size = fs.sys.Size fs.modTime = timespecToTime(fs.sys.Mtime, fs.sys.MtimeNsec) fs.mode = FileMode(fs.sys.Mode & 0777) switch fs.sys.Mode & syscall.S_IFMT { diff --git a/libgo/go/os/stat_plan9.go b/libgo/go/os/stat_plan9.go index fa4bd83aef..274d0d86f3 100644 --- a/libgo/go/os/stat_plan9.go +++ b/libgo/go/os/stat_plan9.go @@ -11,16 +11,10 @@ import ( const _BIT16SZ = 2 -func sameFile(fs1, fs2 *fileStat) bool { - a := fs1.sys.(*syscall.Dir) - b := fs2.sys.(*syscall.Dir) - return a.Qid.Path == b.Qid.Path && a.Type == b.Type && a.Dev == b.Dev -} - func fileInfoFromStat(d *syscall.Dir) FileInfo { fs := &fileStat{ name: d.Name, - size: int64(d.Length), + size: d.Length, modTime: time.Unix(int64(d.Mtime), 0), sys: d, } @@ -37,6 +31,10 @@ func fileInfoFromStat(d *syscall.Dir) FileInfo { if d.Mode&syscall.DMTMP != 0 { fs.mode |= ModeTemporary } + // Consider all files not served by #M as device files. + if d.Type != 'M' { + fs.mode |= ModeDevice + } return fs } @@ -100,7 +98,7 @@ func Stat(name string) (FileInfo, error) { // Lstat returns a FileInfo describing the named file. // If the file is a symbolic link, the returned FileInfo -// describes the symbolic link. Lstat makes no attempt to follow the link. +// describes the symbolic link. Lstat makes no attempt to follow the link. // If there is an error, it will be of type *PathError. func Lstat(name string) (FileInfo, error) { return Stat(name) diff --git a/libgo/go/os/stat_solaris.go b/libgo/go/os/stat_solaris.go index aad429dcb9..1f1b4ab825 100644 --- a/libgo/go/os/stat_solaris.go +++ b/libgo/go/os/stat_solaris.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build !solaristag + package os import ( diff --git a/libgo/go/os/stat_unix.go b/libgo/go/os/stat_unix.go new file mode 100644 index 0000000000..1733d3f132 --- /dev/null +++ b/libgo/go/os/stat_unix.go @@ -0,0 +1,52 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris + +package os + +import ( + "syscall" +) + +// Stat returns the FileInfo structure describing file. +// If there is an error, it will be of type *PathError. +func (f *File) Stat() (FileInfo, error) { + if f == nil { + return nil, ErrInvalid + } + var fs fileStat + err := syscall.Fstat(f.fd, &fs.sys) + if err != nil { + return nil, &PathError{"stat", f.name, err} + } + fillFileStatFromSys(&fs, f.name) + return &fs, nil +} + +// Stat returns a FileInfo describing the named file. +// If there is an error, it will be of type *PathError. +func Stat(name string) (FileInfo, error) { + var fs fileStat + err := syscall.Stat(name, &fs.sys) + if err != nil { + return nil, &PathError{"stat", name, err} + } + fillFileStatFromSys(&fs, name) + return &fs, nil +} + +// Lstat returns a FileInfo describing the named file. +// If the file is a symbolic link, the returned FileInfo +// describes the symbolic link. Lstat makes no attempt to follow the link. +// If there is an error, it will be of type *PathError. +func Lstat(name string) (FileInfo, error) { + var fs fileStat + err := syscall.Lstat(name, &fs.sys) + if err != nil { + return nil, &PathError{"lstat", name, err} + } + fillFileStatFromSys(&fs, name) + return &fs, nil +} diff --git a/libgo/go/os/str.go b/libgo/go/os/str.go index d3e03e9849..cba9fa3e8d 100644 --- a/libgo/go/os/str.go +++ b/libgo/go/os/str.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Simple converions to avoid depending on strconv. +// Simple conversions to avoid depending on strconv. package os diff --git a/libgo/go/os/sys.go b/libgo/go/os/sys.go new file mode 100644 index 0000000000..28b0f6bab0 --- /dev/null +++ b/libgo/go/os/sys.go @@ -0,0 +1,10 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os + +// Hostname returns the host name reported by the kernel. +func Hostname() (name string, err error) { + return hostname() +} diff --git a/libgo/go/os/sys_uname.go b/libgo/go/os/sys_uname.go index 16568b69f4..71fa867595 100644 --- a/libgo/go/os/sys_uname.go +++ b/libgo/go/os/sys_uname.go @@ -4,6 +4,8 @@ // For systems which only store the hostname in uname (Solaris). +// +build solaris irix rtems + package os import "syscall" diff --git a/libgo/go/os/types.go b/libgo/go/os/types.go index 9d6f8e13d6..c56548353f 100644 --- a/libgo/go/os/types.go +++ b/libgo/go/os/types.go @@ -12,6 +12,11 @@ import ( // Getpagesize returns the underlying system's memory page size. func Getpagesize() int { return syscall.Getpagesize() } +// File represents an open file descriptor. +type File struct { + *file // os specific +} + // A FileInfo describes a file and is returned by Stat and Lstat. type FileInfo interface { Name() string // base name of the file @@ -25,7 +30,7 @@ type FileInfo interface { // A FileMode represents a file's mode and permission bits. // The bits have the same definition on all systems, so that // information about files can be moved from one system -// to another portably. Not all bits apply to all systems. +// to another portably. Not all bits apply to all systems. // The only required bit is ModeDir for directories. type FileMode uint32 diff --git a/libgo/go/os/types_plan9.go b/libgo/go/os/types_plan9.go index 6d46ca9dd3..125da661b7 100644 --- a/libgo/go/os/types_plan9.go +++ b/libgo/go/os/types_plan9.go @@ -4,7 +4,10 @@ package os -import "time" +import ( + "syscall" + "time" +) // A fileStat is the implementation of FileInfo returned by Stat and Lstat. type fileStat struct { @@ -19,3 +22,11 @@ func (fs *fileStat) Size() int64 { return fs.size } func (fs *fileStat) Mode() FileMode { return fs.mode } func (fs *fileStat) ModTime() time.Time { return fs.modTime } func (fs *fileStat) Sys() interface{} { return fs.sys } + +func sameFile(fs1, fs2 *fileStat) bool { + a := fs1.sys.(*syscall.Dir) + b := fs2.sys.(*syscall.Dir) + return a.Qid.Path == b.Qid.Path && a.Type == b.Type && a.Dev == b.Dev +} + +const badFd = -1 diff --git a/libgo/go/os/types_unix.go b/libgo/go/os/types_unix.go index 056220c09b..1f614812fd 100644 --- a/libgo/go/os/types_unix.go +++ b/libgo/go/os/types_unix.go @@ -25,3 +25,9 @@ func (fs *fileStat) Size() int64 { return fs.size } func (fs *fileStat) Mode() FileMode { return fs.mode } func (fs *fileStat) ModTime() time.Time { return fs.modTime } func (fs *fileStat) Sys() interface{} { return &fs.sys } + +func sameFile(fs1, fs2 *fileStat) bool { + return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino +} + +const badFd = -1 diff --git a/libgo/go/os/types_windows.go b/libgo/go/os/types_windows.go index 7b2e54698c..ad4e863fcb 100644 --- a/libgo/go/os/types_windows.go +++ b/libgo/go/os/types_windows.go @@ -14,6 +14,7 @@ import ( type fileStat struct { name string sys syscall.Win32FileAttributeData + pipe bool // used to implement SameFile sync.Mutex @@ -42,6 +43,9 @@ func (fs *fileStat) Mode() (m FileMode) { if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { m |= ModeSymlink } + if fs.pipe { + m |= ModeNamedPipe + } return m } @@ -69,7 +73,7 @@ func (fs *fileStat) loadFileId() error { } defer syscall.CloseHandle(h) var i syscall.ByHandleFileInformation - err = syscall.GetFileInformationByHandle(syscall.Handle(h), &i) + err = syscall.GetFileInformationByHandle(h, &i) if err != nil { return err } diff --git a/libgo/go/os/user/decls_solaris.go b/libgo/go/os/user/decls_solaris.go index 788a00f066..ccdf36b13d 100644 --- a/libgo/go/os/user/decls_solaris.go +++ b/libgo/go/os/user/decls_solaris.go @@ -16,3 +16,9 @@ func libc_getpwnam_r(name *byte, pwd *syscall.Passwd, buf *byte, buflen syscall. //extern __posix_getpwuid_r func libc_getpwuid_r(uid syscall.Uid_t, pwd *syscall.Passwd, buf *byte, buflen syscall.Size_t, result **syscall.Passwd) int + +//extern __posix_getgrnam_r +func libc_getgrnam_r(name *byte, grp *syscall.Group, buf *byte, buflen syscall.Size_t, result **syscall.Group) int + +//extern __posix_getgrgid_r +func libc_getgrgid_r(gid syscall.Gid_t, grp *syscall.Group, buf *byte, buflen syscall.Size_t, result **syscall.Group) int diff --git a/libgo/go/os/user/decls_unix.go b/libgo/go/os/user/decls_unix.go index f76e4c9bdf..c2eb51e5bb 100644 --- a/libgo/go/os/user/decls_unix.go +++ b/libgo/go/os/user/decls_unix.go @@ -16,3 +16,12 @@ func libc_getpwnam_r(name *byte, pwd *syscall.Passwd, buf *byte, buflen syscall. //extern getpwuid_r func libc_getpwuid_r(uid syscall.Uid_t, pwd *syscall.Passwd, buf *byte, buflen syscall.Size_t, result **syscall.Passwd) int + +//extern getgrnam_r +func libc_getgrnam_r(name *byte, grp *syscall.Group, buf *byte, buflen syscall.Size_t, result **syscall.Group) int + +//extern getgrgid_r +func libc_getgrgid_r(gid syscall.Gid_t, grp *syscall.Group, buf *byte, buflen syscall.Size_t, result **syscall.Group) int + +//extern getgrouplist +func libc_getgrouplist(user *byte, group syscall.Gid_t, groups *syscall.Gid_t, ngroups *int32) int diff --git a/libgo/go/os/user/listgroups_solaris.go b/libgo/go/os/user/listgroups_solaris.go new file mode 100644 index 0000000000..28a8a78dbb --- /dev/null +++ b/libgo/go/os/user/listgroups_solaris.go @@ -0,0 +1,17 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build cgo + +// Even though this file requires no C, it is used to provide a +// listGroup stub because all the other Solaris calls work. Otherwise, +// this stub will conflict with the lookup_stubs.go fallback. + +package user + +import "fmt" + +func listGroups(u *User) ([]string, error) { + return nil, fmt.Errorf("user: list groups for %s: not supported on Solaris", u.Username) +} diff --git a/libgo/go/os/user/listgroups_unix.go b/libgo/go/os/user/listgroups_unix.go new file mode 100644 index 0000000000..8b2b13d563 --- /dev/null +++ b/libgo/go/os/user/listgroups_unix.go @@ -0,0 +1,57 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build dragonfly darwin freebsd !android,linux netbsd openbsd + +package user + +import ( + "fmt" + "strconv" + "syscall" +) + +/* +#include <unistd.h> +#include <sys/types.h> +#include <stdlib.h> +*/ + +func listGroups(u *User) ([]string, error) { + ug, err := strconv.Atoi(u.Gid) + if err != nil { + return nil, fmt.Errorf("user: list groups for %s: invalid gid %q", u.Username, u.Gid) + } + userGID := syscall.Gid_t(ug) + nameC, err := syscall.BytePtrFromString(u.Username) + if err != nil { + return nil, fmt.Errorf("user: invalid user name %q: %v", strconv.Quote(u.Username), err) + } + + n := int32(256) + gidsC := make([]syscall.Gid_t, n) + syscall.Entersyscall() + rv := libc_getgrouplist(nameC, userGID, &gidsC[0], &n) + syscall.Exitsyscall() + if rv == -1 { + // More than initial buffer, but now n contains the correct size. + const maxGroups = 2048 + if n > maxGroups { + return nil, fmt.Errorf("user: list groups for %s: member of more than %d groups", u.Username, maxGroups) + } + gidsC = make([]syscall.Gid_t, n) + syscall.Entersyscall() + rv := libc_getgrouplist(nameC, userGID, &gidsC[0], &n) + syscall.Exitsyscall() + if rv == -1 { + return nil, fmt.Errorf("user: list groups for %s failed (changed groups?)", u.Username) + } + } + gidsC = gidsC[:n] + gids := make([]string, 0, n) + for _, g := range gidsC[:n] { + gids = append(gids, strconv.Itoa(int(g))) + } + return gids, nil +} diff --git a/libgo/go/os/user/lookup.go b/libgo/go/os/user/lookup.go index 09f00c7bdb..3b4421badd 100644 --- a/libgo/go/os/user/lookup.go +++ b/libgo/go/os/user/lookup.go @@ -12,11 +12,28 @@ func Current() (*User, error) { // Lookup looks up a user by username. If the user cannot be found, the // returned error is of type UnknownUserError. func Lookup(username string) (*User, error) { - return lookup(username) + return lookupUser(username) } // LookupId looks up a user by userid. If the user cannot be found, the // returned error is of type UnknownUserIdError. func LookupId(uid string) (*User, error) { - return lookupId(uid) + return lookupUserId(uid) +} + +// LookupGroup looks up a group by name. If the group cannot be found, the +// returned error is of type UnknownGroupError. +func LookupGroup(name string) (*Group, error) { + return lookupGroup(name) +} + +// LookupGroupId looks up a group by groupid. If the group cannot be found, the +// returned error is of type UnknownGroupIdError. +func LookupGroupId(gid string) (*Group, error) { + return lookupGroupId(gid) +} + +// GroupIds returns the list of group IDs that the user is a member of. +func (u *User) GroupIds() ([]string, error) { + return listGroups(u) } diff --git a/libgo/go/os/user/lookup_android.go b/libgo/go/os/user/lookup_android.go new file mode 100644 index 0000000000..b1be3dc193 --- /dev/null +++ b/libgo/go/os/user/lookup_android.go @@ -0,0 +1,38 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build android + +package user + +import "errors" + +func init() { + userImplemented = false + groupImplemented = false +} + +func current() (*User, error) { + return nil, errors.New("user: Current not implemented on android") +} + +func lookupUser(string) (*User, error) { + return nil, errors.New("user: Lookup not implemented on android") +} + +func lookupUserId(string) (*User, error) { + return nil, errors.New("user: LookupId not implemented on android") +} + +func lookupGroup(string) (*Group, error) { + return nil, errors.New("user: LookupGroup not implemented on android") +} + +func lookupGroupId(string) (*Group, error) { + return nil, errors.New("user: LookupGroupId not implemented on android") +} + +func listGroups(*User) ([]string, error) { + return nil, errors.New("user: GroupIds not implemented on android") +} diff --git a/libgo/go/os/user/lookup_plan9.go b/libgo/go/os/user/lookup_plan9.go index f7ef3482b7..ea3ce0bc7c 100644 --- a/libgo/go/os/user/lookup_plan9.go +++ b/libgo/go/os/user/lookup_plan9.go @@ -18,6 +18,10 @@ const ( userFile = "/dev/user" ) +func init() { + groupImplemented = false +} + func current() (*User, error) { ubytes, err := ioutil.ReadFile(userFile) if err != nil { @@ -37,10 +41,22 @@ func current() (*User, error) { return u, nil } -func lookup(username string) (*User, error) { +func lookupUser(username string) (*User, error) { + return nil, syscall.EPLAN9 +} + +func lookupUserId(uid string) (*User, error) { + return nil, syscall.EPLAN9 +} + +func lookupGroup(groupname string) (*Group, error) { + return nil, syscall.EPLAN9 +} + +func lookupGroupId(string) (*Group, error) { return nil, syscall.EPLAN9 } -func lookupId(uid string) (*User, error) { +func listGroups(*User) ([]string, error) { return nil, syscall.EPLAN9 } diff --git a/libgo/go/os/user/lookup_stubs.go b/libgo/go/os/user/lookup_stubs.go index 4fb0e3c6ed..ebf24f79de 100644 --- a/libgo/go/os/user/lookup_stubs.go +++ b/libgo/go/os/user/lookup_stubs.go @@ -2,27 +2,83 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !cgo,!windows,!plan9 android +// +build !cgo,!windows,!plan9,!android package user import ( + "errors" "fmt" + "os" "runtime" + "strconv" ) func init() { - implemented = false + userImplemented = false + groupImplemented = false } func current() (*User, error) { - return nil, fmt.Errorf("user: Current not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) + u := &User{ + Uid: currentUID(), + Gid: currentGID(), + Username: os.Getenv("USER"), + Name: "", // ignored + HomeDir: os.Getenv("HOME"), + } + if runtime.GOOS == "nacl" { + if u.Uid == "" { + u.Uid = "1" + } + if u.Username == "" { + u.Username = "nacl" + } + if u.HomeDir == "" { + u.HomeDir = "/home/nacl" + } + } + // cgo isn't available, but if we found the minimum information + // without it, use it: + if u.Uid != "" && u.Username != "" && u.HomeDir != "" { + return u, nil + } + return u, fmt.Errorf("user: Current not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) } -func lookup(username string) (*User, error) { - return nil, fmt.Errorf("user: Lookup not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +func lookupUser(username string) (*User, error) { + return nil, errors.New("user: Lookup requires cgo") } -func lookupId(uid string) (*User, error) { - return nil, fmt.Errorf("user: LookupId not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) +func lookupUserId(uid string) (*User, error) { + return nil, errors.New("user: LookupId requires cgo") +} + +func lookupGroup(groupname string) (*Group, error) { + return nil, errors.New("user: LookupGroup requires cgo") +} + +func lookupGroupId(string) (*Group, error) { + return nil, errors.New("user: LookupGroupId requires cgo") +} + +func listGroups(*User) ([]string, error) { + return nil, errors.New("user: GroupIds requires cgo") +} + +func currentUID() string { + if id := os.Getuid(); id >= 0 { + return strconv.Itoa(id) + } + // Note: Windows returns -1, but this file isn't used on + // Windows anyway, so this empty return path shouldn't be + // used. + return "" +} + +func currentGID() string { + if id := os.Getgid(); id >= 0 { + return strconv.Itoa(id) + } + return "" } diff --git a/libgo/go/os/user/lookup_unix.go b/libgo/go/os/user/lookup_unix.go index bebb9e8f2b..8881366cf9 100644 --- a/libgo/go/os/user/lookup_unix.go +++ b/libgo/go/os/user/lookup_unix.go @@ -15,24 +15,6 @@ import ( "unsafe" ) -/* -#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS -#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); -} - -static int mygetpwnam_r(const char *name, struct passwd *pwd, - char *buf, size_t buflen, struct passwd **result) { - return getpwnam_r(name, pwd, buf, buflen, result); -} -*/ - // bytePtrToString takes a NUL-terminated array of bytes and convert // it to a Go string. func bytePtrToString(p *byte) string { @@ -45,58 +27,77 @@ func bytePtrToString(p *byte) string { } func current() (*User, error) { - return lookupUnix(syscall.Getuid(), "", false) + return lookupUnixUid(syscall.Getuid()) } -func lookup(username string) (*User, error) { - return lookupUnix(-1, username, true) +func lookupUser(username string) (*User, error) { + var pwd syscall.Passwd + var result *syscall.Passwd + p := syscall.StringBytePtr(username) + + buf := alloc(userBuffer) + defer buf.free() + + err := retryWithBuffer(buf, func() syscall.Errno { + syscall.Entersyscall() + rv := libc_getpwnam_r(p, + &pwd, + buf.ptr, + buf.size, + &result) + syscall.Exitsyscall() + if rv != 0 { + return syscall.GetErrno() + } + return 0 + }) + if err != nil { + return nil, fmt.Errorf("user: lookup username %s: %v", username, err) + } + if result == nil { + return nil, UnknownUserError(username) + } + return buildUser(&pwd), err } -func lookupId(uid string) (*User, error) { +func lookupUserId(uid string) (*User, error) { i, e := strconv.Atoi(uid) if e != nil { return nil, e } - return lookupUnix(i, "", false) + return lookupUnixUid(i) } -func lookupUnix(uid int, username string, lookupByName bool) (*User, error) { +func lookupUnixUid(uid int) (*User, error) { var pwd syscall.Passwd var result *syscall.Passwd - // FIXME: Should let buf grow if necessary. - const bufSize = 1024 - buf := make([]byte, bufSize) - if lookupByName { - nameC := syscall.StringBytePtr(username) - syscall.Entersyscall() - rv := libc_getpwnam_r(nameC, - &pwd, - &buf[0], - bufSize, - &result) - syscall.Exitsyscall() - if rv != 0 { - return nil, fmt.Errorf("user: lookup username %s: %s", username, syscall.GetErrno()) - } - if result == nil { - return nil, UnknownUserError(username) - } - } else { + buf := alloc(userBuffer) + defer buf.free() + + err := retryWithBuffer(buf, func() syscall.Errno { syscall.Entersyscall() rv := libc_getpwuid_r(syscall.Uid_t(uid), &pwd, - &buf[0], - bufSize, + buf.ptr, + buf.size, &result) syscall.Exitsyscall() if rv != 0 { - return nil, fmt.Errorf("user: lookup userid %d: %s", uid, syscall.GetErrno()) - } - if result == nil { - return nil, UnknownUserIdError(uid) + return syscall.GetErrno() } + return 0 + }) + if err != nil { + return nil, fmt.Errorf("user: lookup userid %d: %v", uid, err) + } + if result == nil { + return nil, UnknownUserIdError(uid) } + return buildUser(&pwd), nil +} + +func buildUser(pwd *syscall.Passwd) *User { u := &User{ Uid: strconv.Itoa(int(pwd.Pw_uid)), Gid: strconv.Itoa(int(pwd.Pw_gid)), @@ -104,12 +105,162 @@ func lookupUnix(uid int, username string, lookupByName bool) (*User, error) { Name: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_gecos))), HomeDir: bytePtrToString((*byte)(unsafe.Pointer(pwd.Pw_dir))), } - // The pw_gecos field isn't quite standardized. Some docs + // 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 + return u +} + +func currentGroup() (*Group, error) { + return lookupUnixGid(syscall.Getgid()) +} + +func lookupGroup(groupname string) (*Group, error) { + var grp syscall.Group + var result *syscall.Group + + buf := alloc(groupBuffer) + defer buf.free() + p := syscall.StringBytePtr(groupname) + + err := retryWithBuffer(buf, func() syscall.Errno { + syscall.Entersyscall() + rv := libc_getgrnam_r(p, + &grp, + buf.ptr, + buf.size, + &result) + syscall.Exitsyscall() + if rv != 0 { + return syscall.GetErrno() + } + return 0 + }) + if err != nil { + return nil, fmt.Errorf("user: lookup groupname %s: %v", groupname, err) + } + if result == nil { + return nil, UnknownGroupError(groupname) + } + return buildGroup(&grp), nil +} + +func lookupGroupId(gid string) (*Group, error) { + i, e := strconv.Atoi(gid) + if e != nil { + return nil, e + } + return lookupUnixGid(i) +} + +func lookupUnixGid(gid int) (*Group, error) { + var grp syscall.Group + var result *syscall.Group + + buf := alloc(groupBuffer) + defer buf.free() + + err := retryWithBuffer(buf, func() syscall.Errno { + syscall.Entersyscall() + rv := libc_getgrgid_r(syscall.Gid_t(gid), + &grp, + buf.ptr, + buf.size, + &result) + syscall.Exitsyscall() + if rv != 0 { + return syscall.GetErrno() + } + return 0 + }) + if err != nil { + return nil, fmt.Errorf("user: lookup groupid %d: %v", gid, err) + } + if result == nil { + return nil, UnknownGroupIdError(strconv.Itoa(gid)) + } + return buildGroup(&grp), nil +} + +func buildGroup(grp *syscall.Group) *Group { + g := &Group{ + Gid: strconv.Itoa(int(grp.Gr_gid)), + Name: bytePtrToString((*byte)(unsafe.Pointer(grp.Gr_name))), + } + return g +} + +type bufferKind int + +const ( + userBuffer = bufferKind(syscall.SC_GETPW_R_SIZE_MAX) + groupBuffer = bufferKind(syscall.SC_GETGR_R_SIZE_MAX) +) + +func (k bufferKind) initialSize() syscall.Size_t { + sz, _ := syscall.Sysconf(int(k)) + if sz == -1 { + // DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX. + // Additionally, not all Linux systems have it, either. For + // example, the musl libc returns -1. + return 1024 + } + if !isSizeReasonable(int64(sz)) { + // Truncate. If this truly isn't enough, retryWithBuffer will error on the first run. + return maxBufferSize + } + return syscall.Size_t(sz) +} + +type memBuffer struct { + ptr *byte + size syscall.Size_t +} + +func alloc(kind bufferKind) *memBuffer { + sz := kind.initialSize() + b := make([]byte, sz) + return &memBuffer{ + ptr: &b[0], + size: sz, + } +} + +func (mb *memBuffer) resize(newSize syscall.Size_t) { + b := make([]byte, newSize) + mb.ptr = &b[0] + mb.size = newSize +} + +func (mb *memBuffer) free() { + mb.ptr = nil +} + +// retryWithBuffer repeatedly calls f(), increasing the size of the +// buffer each time, until f succeeds, fails with a non-ERANGE error, +// or the buffer exceeds a reasonable limit. +func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error { + for { + errno := f() + if errno == 0 { + return nil + } else if errno != syscall.ERANGE { + return errno + } + newSize := buf.size * 2 + if !isSizeReasonable(int64(newSize)) { + return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize) + } + buf.resize(newSize) + } +} + +const maxBufferSize = 1 << 20 + +func isSizeReasonable(sz int64) bool { + return sz > 0 && sz <= maxBufferSize } diff --git a/libgo/go/os/user/lookup_windows.go b/libgo/go/os/user/lookup_windows.go index 99c325ff01..4e36a5c2bf 100644 --- a/libgo/go/os/user/lookup_windows.go +++ b/libgo/go/os/user/lookup_windows.go @@ -5,11 +5,16 @@ package user import ( + "errors" "fmt" "syscall" "unsafe" ) +func init() { + groupImplemented = false +} + func isDomainJoined() (bool, error) { var domain *uint16 var status uint32 @@ -61,7 +66,7 @@ func lookupFullName(domain, username, domainAndUser string) (string, error) { if err == nil { return name, nil } - // domain worked neigher as a domain nor as a server + // domain worked neither as a domain nor as a server // could be domain server unavailable // pretend username is fullname return username, nil @@ -129,7 +134,7 @@ func newUserFromSid(usid *syscall.SID) (*User, error) { return newUser(usid, gid, dir) } -func lookup(username string) (*User, error) { +func lookupUser(username string) (*User, error) { sid, _, t, e := syscall.LookupSID("", username) if e != nil { return nil, e @@ -140,10 +145,22 @@ func lookup(username string) (*User, error) { return newUserFromSid(sid) } -func lookupId(uid string) (*User, error) { +func lookupUserId(uid string) (*User, error) { sid, e := syscall.StringToSid(uid) if e != nil { return nil, e } return newUserFromSid(sid) } + +func lookupGroup(groupname string) (*Group, error) { + return nil, errors.New("user: LookupGroup not implemented on windows") +} + +func lookupGroupId(string) (*Group, error) { + return nil, errors.New("user: LookupGroupId not implemented on windows") +} + +func listGroups(*User) ([]string, error) { + return nil, errors.New("user: GroupIds not implemented on windows") +} diff --git a/libgo/go/os/user/user.go b/libgo/go/os/user/user.go index e8680fe546..ad61992ad3 100644 --- a/libgo/go/os/user/user.go +++ b/libgo/go/os/user/user.go @@ -9,25 +9,45 @@ import ( "strconv" ) -var implemented = true // set to false by lookup_stubs.go's init +var ( + userImplemented = true // set to false by lookup_stubs.go's init + groupImplemented = true // set to false by lookup_stubs.go's init +) // User represents a user account. -// -// On posix systems Uid and Gid contain a decimal number -// representing uid and gid. On windows Uid and Gid -// contain security identifier (SID) in a string format. -// On Plan 9, Uid, Gid, Username, and Name will be the -// contents of /dev/user. type User struct { - Uid string // user id - Gid string // primary group id + // Uid is the user ID. + // On POSIX systems, this is a decimal number representing the uid. + // On Windows, this is a security identifier (SID) in a string format. + // On Plan 9, this is the contents of /dev/user. + Uid string + // Gid is the primary group ID. + // On POSIX systems, this is a decimal number representing the gid. + // On Windows, this is a SID in a string format. + // On Plan 9, this is the contents of /dev/user. + Gid string + // Username is the login name. Username string - Name string - HomeDir string + // Name is the user's real or display name. + // It might be blank. + // On POSIX systems, this is the first (or only) entry in the GECOS field + // list. + // On Windows, this is the user's display name. + // On Plan 9, this is the contents of /dev/user. + Name string + // HomeDir is the path to the user's home directory (if they have one). + HomeDir string } -// UnknownUserIdError is returned by LookupId when -// a user cannot be found. +// Group represents a grouping of users. +// +// On POSIX systems Gid contains a decimal number representing the group ID. +type Group struct { + Gid string // group ID + Name string // group name +} + +// UnknownUserIdError is returned by LookupId when a user cannot be found. type UnknownUserIdError int func (e UnknownUserIdError) Error() string { @@ -41,3 +61,19 @@ type UnknownUserError string func (e UnknownUserError) Error() string { return "user: unknown user " + string(e) } + +// UnknownGroupIdError is returned by LookupGroupId when +// a group cannot be found. +type UnknownGroupIdError string + +func (e UnknownGroupIdError) Error() string { + return "group: unknown groupid " + string(e) +} + +// UnknownGroupError is returned by LookupGroup when +// a group cannot be found. +type UnknownGroupError string + +func (e UnknownGroupError) Error() string { + return "group: unknown group " + string(e) +} diff --git a/libgo/go/os/user/user_test.go b/libgo/go/os/user/user_test.go index 9d9420e809..9d8d94d8da 100644 --- a/libgo/go/os/user/user_test.go +++ b/libgo/go/os/user/user_test.go @@ -9,18 +9,19 @@ import ( "testing" ) -func check(t *testing.T) { - if !implemented { +func checkUser(t *testing.T) { + if !userImplemented { t.Skip("user: not implemented; skipping tests") } } func TestCurrent(t *testing.T) { - check(t) - + if runtime.GOOS == "android" { + t.Skipf("skipping on %s", runtime.GOOS) + } u, err := Current() if err != nil { - t.Fatalf("Current: %v", err) + t.Fatalf("Current: %v (got %#v)", err, u) } if u.HomeDir == "" { t.Errorf("didn't get a HomeDir") @@ -53,7 +54,7 @@ func compare(t *testing.T, want, got *User) { } func TestLookup(t *testing.T) { - check(t) + checkUser(t) if runtime.GOOS == "plan9" { t.Skipf("Lookup not implemented on %q", runtime.GOOS) @@ -71,7 +72,7 @@ func TestLookup(t *testing.T) { } func TestLookupId(t *testing.T) { - check(t) + checkUser(t) if runtime.GOOS == "plan9" { t.Skipf("LookupId not implemented on %q", runtime.GOOS) @@ -87,3 +88,64 @@ func TestLookupId(t *testing.T) { } compare(t, want, got) } + +func checkGroup(t *testing.T) { + if !groupImplemented { + t.Skip("user: group not implemented; skipping test") + } +} + +func TestLookupGroup(t *testing.T) { + checkGroup(t) + user, err := Current() + if err != nil { + t.Fatalf("Current(): %v", err) + } + + g1, err := LookupGroupId(user.Gid) + if err != nil { + // NOTE(rsc): Maybe the group isn't defined. That's fine. + // On my OS X laptop, rsc logs in with group 5000 even + // though there's no name for group 5000. Such is Unix. + t.Logf("LookupGroupId(%q): %v", user.Gid, err) + return + } + if g1.Gid != user.Gid { + t.Errorf("LookupGroupId(%q).Gid = %s; want %s", user.Gid, g1.Gid, user.Gid) + } + + g2, err := LookupGroup(g1.Name) + if err != nil { + t.Fatalf("LookupGroup(%q): %v", g1.Name, err) + } + if g1.Gid != g2.Gid || g1.Name != g2.Name { + t.Errorf("LookupGroup(%q) = %+v; want %+v", g1.Name, g2, g1) + } +} + +func TestGroupIds(t *testing.T) { + checkGroup(t) + if runtime.GOOS == "solaris" { + t.Skip("skipping GroupIds, see golang.org/issue/14709") + } + user, err := Current() + if err != nil { + t.Fatalf("Current(): %v", err) + } + gids, err := user.GroupIds() + if err != nil { + t.Fatalf("%+v.GroupIds(): %v", user, err) + } + if !containsID(gids, user.Gid) { + t.Errorf("%+v.GroupIds() = %v; does not contain user GID %s", user, gids, user.Gid) + } +} + +func containsID(ids []string, id string) bool { + for _, x := range ids { + if x == id { + return true + } + } + return false +} diff --git a/libgo/go/os/wait_unimp.go b/libgo/go/os/wait_unimp.go new file mode 100644 index 0000000000..7059e59ab2 --- /dev/null +++ b/libgo/go/os/wait_unimp.go @@ -0,0 +1,16 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build dragonfly nacl netbsd openbsd solaris + +package os + +// blockUntilWaitable attempts to block until a call to p.Wait will +// succeed immediately, and returns whether it has done so. +// It does not actually call p.Wait. +// This version is used on systems that do not implement waitid, +// or where we have not implemented it yet. +func (p *Process) blockUntilWaitable() (bool, error) { + return false, nil +} diff --git a/libgo/go/os/wait_wait6.go b/libgo/go/os/wait_wait6.go new file mode 100644 index 0000000000..b30981199e --- /dev/null +++ b/libgo/go/os/wait_wait6.go @@ -0,0 +1,41 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build freebsd + +package os + +import ( + "runtime" + "syscall" +) + +const _P_PID = 0 + +// blockUntilWaitable attempts to block until a call to p.Wait will +// succeed immediately, and returns whether it has done so. +// It does not actually call p.Wait. +func (p *Process) blockUntilWaitable() (bool, error) { + var errno syscall.Errno + // The arguments on 32-bit FreeBSD look like the following: + // - freebsd32_wait6_args{ idtype, id1, id2, status, options, wrusage, info } or + // - freebsd32_wait6_args{ idtype, pad, id1, id2, status, options, wrusage, info } when PAD64_REQUIRED=1 on ARM, MIPS or PowerPC + if runtime.GOARCH == "386" { + _, _, errno = syscall.Syscall9(syscall.SYS_WAIT6, _P_PID, uintptr(p.Pid), 0, 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0, 0, 0) + } else if runtime.GOARCH == "arm" { + _, _, errno = syscall.Syscall9(syscall.SYS_WAIT6, _P_PID, 0, uintptr(p.Pid), 0, 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0, 0) + } else { + _, _, errno = syscall.Syscall6(syscall.SYS_WAIT6, _P_PID, uintptr(p.Pid), 0, syscall.WEXITED|syscall.WNOWAIT, 0, 0) + } + runtime.KeepAlive(p) + if errno != 0 { + // The wait6 system call is supported only on FreeBSD + // 9.3 and above, so it may return an ENOSYS error. + if errno == syscall.ENOSYS { + return false, nil + } + return false, NewSyscallError("wait6", errno) + } + return true, nil +} diff --git a/libgo/go/os/wait_waitid.go b/libgo/go/os/wait_waitid.go new file mode 100644 index 0000000000..653fce9253 --- /dev/null +++ b/libgo/go/os/wait_waitid.go @@ -0,0 +1,40 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin linux + +package os + +import ( + "runtime" + "syscall" + "unsafe" +) + +const _P_PID = 1 + +// blockUntilWaitable attempts to block until a call to p.Wait will +// succeed immediately, and returns whether it has done so. +// It does not actually call p.Wait. +func (p *Process) blockUntilWaitable() (bool, error) { + // The waitid system call expects a pointer to a siginfo_t, + // which is 128 bytes on all GNU/Linux systems. + // On Darwin, it requires greater than or equal to 64 bytes + // for darwin/{386,arm} and 104 bytes for darwin/amd64. + // We don't care about the values it returns. + var siginfo [128]byte + psig := &siginfo[0] + _, _, e := syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(p.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0) + runtime.KeepAlive(p) + if e != 0 { + // waitid has been available since Linux 2.6.9, but + // reportedly is not available in Ubuntu on Windows. + // See issue 16610. + if e == syscall.ENOSYS { + return false, nil + } + return false, NewSyscallError("waitid", e) + } + return true, nil +} |
