aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/os
diff options
context:
space:
mode:
authorIan Lance Taylor <iant@golang.org>2018-09-24 21:46:21 +0000
committerIan Lance Taylor <ian@gcc.gnu.org>2018-09-24 21:46:21 +0000
commitdd931d9b48647e898dc80927c532ae93cc09e192 (patch)
tree71be2295cd79b8a182f6130611658db8628772d5 /libgo/go/os
parent779d8a5ad09b01428726ea5a0e6c87bd9ac3c0e4 (diff)
downloadgcc-dd931d9b48647e898dc80927c532ae93cc09e192.zip
gcc-dd931d9b48647e898dc80927c532ae93cc09e192.tar.gz
gcc-dd931d9b48647e898dc80927c532ae93cc09e192.tar.bz2
libgo: update to Go 1.11
Reviewed-on: https://go-review.googlesource.com/136435 gotools/: * Makefile.am (mostlyclean-local): Run chmod on check-go-dir to make sure it is writable. (check-go-tools): Likewise. (check-vet): Copy internal/objabi to check-vet-dir. * Makefile.in: Rebuild. From-SVN: r264546
Diffstat (limited to 'libgo/go/os')
-rw-r--r--libgo/go/os/dir_unix.go2
-rw-r--r--libgo/go/os/env.go24
-rw-r--r--libgo/go/os/env_test.go29
-rw-r--r--libgo/go/os/error_posix.go2
-rw-r--r--libgo/go/os/error_unix.go2
-rw-r--r--libgo/go/os/error_unix_test.go2
-rw-r--r--libgo/go/os/example_test.go18
-rw-r--r--libgo/go/os/exec.go4
-rw-r--r--libgo/go/os/exec/exec.go10
-rw-r--r--libgo/go/os/exec/exec_test.go19
-rw-r--r--libgo/go/os/exec/lp_js.go23
-rw-r--r--libgo/go/os/exec/lp_plan9.go4
-rw-r--r--libgo/go/os/exec/lp_unix.go4
-rw-r--r--libgo/go/os/exec/lp_windows.go4
-rw-r--r--libgo/go/os/exec_posix.go2
-rw-r--r--libgo/go/os/exec_unix.go2
-rw-r--r--libgo/go/os/executable_darwin.go5
-rw-r--r--libgo/go/os/executable_procfs.go2
-rw-r--r--libgo/go/os/executable_solaris.go11
-rw-r--r--libgo/go/os/fifo_test.go112
-rw-r--r--libgo/go/os/file.go68
-rw-r--r--libgo/go/os/file_plan9.go14
-rw-r--r--libgo/go/os/file_posix.go7
-rw-r--r--libgo/go/os/file_unix.go47
-rw-r--r--libgo/go/os/os_test.go86
-rw-r--r--libgo/go/os/os_unix_test.go74
-rw-r--r--libgo/go/os/path.go65
-rw-r--r--libgo/go/os/path_plan9.go4
-rw-r--r--libgo/go/os/path_unix.go6
-rw-r--r--libgo/go/os/path_windows.go11
-rw-r--r--libgo/go/os/path_windows_test.go30
-rw-r--r--libgo/go/os/pipe2_bsd.go (renamed from libgo/go/os/pipe_freebsd.go)2
-rw-r--r--libgo/go/os/pipe_bsd.go2
-rw-r--r--libgo/go/os/pipe_test.go134
-rw-r--r--libgo/go/os/signal/signal.go6
-rw-r--r--libgo/go/os/signal/signal_plan9.go5
-rw-r--r--libgo/go/os/signal/signal_test.go59
-rw-r--r--libgo/go/os/signal/signal_unix.go7
-rw-r--r--libgo/go/os/stat_nacljs.go (renamed from libgo/go/os/stat_nacl.go)2
-rw-r--r--libgo/go/os/stat_plan9.go10
-rw-r--r--libgo/go/os/stat_unix.go2
-rw-r--r--libgo/go/os/sys_bsd.go5
-rw-r--r--libgo/go/os/sys_darwin.go11
-rw-r--r--libgo/go/os/sys_js.go (renamed from libgo/go/os/sys_freebsd.go)7
-rw-r--r--libgo/go/os/sys_linux.go37
-rw-r--r--libgo/go/os/sys_plan9.go2
-rw-r--r--libgo/go/os/sys_unix.go5
-rw-r--r--libgo/go/os/timeout_test.go40
-rw-r--r--libgo/go/os/types.go5
-rw-r--r--libgo/go/os/types_windows.go154
-rw-r--r--libgo/go/os/user/cgo_lookup_unix.go2
-rw-r--r--libgo/go/os/user/cgo_unix_test.go2
-rw-r--r--libgo/go/os/user/listgroups_solaris.go2
-rw-r--r--libgo/go/os/user/listgroups_unix.go1
-rw-r--r--libgo/go/os/user/lookup_stubs.go2
-rw-r--r--libgo/go/os/user/lookup_unix.go4
-rw-r--r--libgo/go/os/user/lookup_windows.go270
-rw-r--r--libgo/go/os/user/user.go13
-rw-r--r--libgo/go/os/user/user_test.go24
-rw-r--r--libgo/go/os/wait_unimp.go2
60 files changed, 1330 insertions, 180 deletions
diff --git a/libgo/go/os/dir_unix.go b/libgo/go/os/dir_unix.go
index 2dc6a89..edfc9ea 100644
--- a/libgo/go/os/dir_unix.go
+++ b/libgo/go/os/dir_unix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package os
diff --git a/libgo/go/os/env.go b/libgo/go/os/env.go
index 4e0171f..330297b 100644
--- a/libgo/go/os/env.go
+++ b/libgo/go/os/env.go
@@ -14,18 +14,33 @@ import (
// Expand replaces ${var} or $var in the string based on the mapping function.
// For example, os.ExpandEnv(s) is equivalent to os.Expand(s, os.Getenv).
func Expand(s string, mapping func(string) string) string {
- buf := make([]byte, 0, 2*len(s))
+ var buf []byte
// ${} is all ASCII, so bytes are fine for this operation.
i := 0
for j := 0; j < len(s); j++ {
if s[j] == '$' && j+1 < len(s) {
+ if buf == nil {
+ buf = make([]byte, 0, 2*len(s))
+ }
buf = append(buf, s[i:j]...)
name, w := getShellName(s[j+1:])
- buf = append(buf, mapping(name)...)
+ if name == "" && w > 0 {
+ // Encountered invalid syntax; eat the
+ // characters.
+ } else if name == "" {
+ // Valid syntax, but $ was not followed by a
+ // name. Leave the dollar character untouched.
+ buf = append(buf, s[j])
+ } else {
+ buf = append(buf, mapping(name)...)
+ }
j += w
i = j + 1
}
}
+ if buf == nil {
+ return s
+ }
return string(buf) + s[i:]
}
@@ -63,10 +78,13 @@ func getShellName(s string) (string, int) {
// Scan to closing brace
for i := 1; i < len(s); i++ {
if s[i] == '}' {
+ if i == 1 {
+ return "", 2 // Bad syntax; eat "${}"
+ }
return s[1:i], i + 1
}
}
- return "", 1 // Bad syntax; just eat the brace.
+ return "", 1 // Bad syntax; eat "${"
case isShellSpecialVar(s[0]):
return s[0:1], 1
}
diff --git a/libgo/go/os/env_test.go b/libgo/go/os/env_test.go
index 16f1945..4b86015 100644
--- a/libgo/go/os/env_test.go
+++ b/libgo/go/os/env_test.go
@@ -49,6 +49,12 @@ var expandTests = []struct {
{"${HOME}", "/usr/gopher"},
{"${H}OME", "(Value of H)OME"},
{"A$$$#$1$H$home_1*B", "APIDNARGSARGUMENT1(Value of H)/usr/foo*B"},
+ {"start$+middle$^end$", "start$+middle$^end$"},
+ {"mixed$|bag$$$", "mixed$|bagPID$"},
+ {"$", "$"},
+ {"$}", "$}"},
+ {"${", ""}, // invalid syntax; eat up the characters
+ {"${}", ""}, // invalid syntax; eat up the characters
}
func TestExpand(t *testing.T) {
@@ -60,6 +66,27 @@ func TestExpand(t *testing.T) {
}
}
+var global interface{}
+
+func BenchmarkExpand(b *testing.B) {
+ b.Run("noop", func(b *testing.B) {
+ var s string
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ s = Expand("tick tick tick tick", func(string) string { return "" })
+ }
+ global = s
+ })
+ b.Run("multiple", func(b *testing.B) {
+ var s string
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ s = Expand("$a $a $a $a", func(string) string { return "boom" })
+ }
+ global = s
+ })
+}
+
func TestConsistentEnviron(t *testing.T) {
e0 := Environ()
for i := 0; i < 10; i++ {
@@ -103,7 +130,7 @@ func TestClearenv(t *testing.T) {
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
+ // https://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)
diff --git a/libgo/go/os/error_posix.go b/libgo/go/os/error_posix.go
index 2049e44..3c81b41 100644
--- a/libgo/go/os/error_posix.go
+++ b/libgo/go/os/error_posix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+// +build darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris windows
package os
diff --git a/libgo/go/os/error_unix.go b/libgo/go/os/error_unix.go
index 2349851..bb6bbcc 100644
--- a/libgo/go/os/error_unix.go
+++ b/libgo/go/os/error_unix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package os
diff --git a/libgo/go/os/error_unix_test.go b/libgo/go/os/error_unix_test.go
index 76fe015..8db9867 100644
--- a/libgo/go/os/error_unix_test.go
+++ b/libgo/go/os/error_unix_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+// +build darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package os_test
diff --git a/libgo/go/os/example_test.go b/libgo/go/os/example_test.go
index 5749194..e21415a 100644
--- a/libgo/go/os/example_test.go
+++ b/libgo/go/os/example_test.go
@@ -82,6 +82,24 @@ func init() {
os.Unsetenv("GOPATH")
}
+func ExampleExpand() {
+ mapper := func(placeholderName string) string {
+ switch placeholderName {
+ case "DAY_PART":
+ return "morning"
+ case "USER":
+ return "Gopher"
+ }
+
+ return ""
+ }
+
+ fmt.Println(os.Expand("Good ${DAY_PART}, $USER!", mapper))
+
+ // Output:
+ // Good morning, Gopher!
+}
+
func ExampleExpandEnv() {
fmt.Println(os.ExpandEnv("$USER lives in ${HOME}."))
diff --git a/libgo/go/os/exec.go b/libgo/go/os/exec.go
index a7f8710..cab6a73 100644
--- a/libgo/go/os/exec.go
+++ b/libgo/go/os/exec.go
@@ -109,7 +109,9 @@ func (p *Process) Release() error {
return p.release()
}
-// Kill causes the Process to exit immediately.
+// Kill causes the Process to exit immediately. Kill does not wait until
+// the Process has actually exited. This only kills the Process itself,
+// not any other processes it may have started.
func (p *Process) Kill() error {
return p.kill()
}
diff --git a/libgo/go/os/exec/exec.go b/libgo/go/os/exec/exec.go
index 5ef9540..88b0a91 100644
--- a/libgo/go/os/exec/exec.go
+++ b/libgo/go/os/exec/exec.go
@@ -34,11 +34,13 @@ import (
"syscall"
)
-// Error records the name of a binary that failed to be executed
-// and the reason it failed.
+// Error is returned by LookPath when it fails to classify a file as an
+// executable.
type Error struct {
+ // Name is the file name for which the error occurred.
Name string
- Err error
+ // Err is the underlying error.
+ Err error
}
func (e *Error) Error() string {
@@ -111,6 +113,8 @@ type Cmd struct {
// ExtraFiles specifies additional open files to be inherited by the
// new process. It does not include standard input, standard output, or
// standard error. If non-nil, entry i becomes file descriptor 3+i.
+ //
+ // ExtraFiles is not supported on Windows.
ExtraFiles []*os.File
// SysProcAttr holds optional, operating system-specific attributes.
diff --git a/libgo/go/os/exec/exec_test.go b/libgo/go/os/exec/exec_test.go
index aa33570f..1e38285 100644
--- a/libgo/go/os/exec/exec_test.go
+++ b/libgo/go/os/exec/exec_test.go
@@ -146,11 +146,11 @@ func TestCatGoodAndBadFile(t *testing.T) {
}
}
-func TestNoExistBinary(t *testing.T) {
- // Can't run a non-existent binary
- err := exec.Command("/no-exist-binary").Run()
+func TestNoExistExecutable(t *testing.T) {
+ // Can't run a non-existent executable
+ err := exec.Command("/no-exist-executable").Run()
if err == nil {
- t.Error("expected error from /no-exist-binary")
+ t.Error("expected error from /no-exist-executable")
}
}
@@ -338,7 +338,7 @@ func TestPipeLookPathLeak(t *testing.T) {
}
for i := 0; i < 6; i++ {
- cmd := exec.Command("something-that-does-not-exist-binary")
+ cmd := exec.Command("something-that-does-not-exist-executable")
cmd.StdoutPipe()
cmd.StderrPipe()
cmd.StdinPipe()
@@ -408,6 +408,12 @@ var testedAlreadyLeaked = false
// stdin, stdout, stderr, epoll/kqueue, maybe testlog
func basefds() uintptr {
n := os.Stderr.Fd() + 1
+ // The poll (epoll/kqueue) descriptor can be numerically
+ // either between stderr and the testlog-fd, or after
+ // testlog-fd.
+ if poll.PollDescriptor() == n {
+ n++
+ }
for _, arg := range os.Args {
if strings.HasPrefix(arg, "-test.testlogfile=") {
n++
@@ -1009,9 +1015,6 @@ func TestContext(t *testing.T) {
}
func TestContextCancel(t *testing.T) {
- if testenv.Builder() == "windows-386-xp" {
- t.Skipf("known to fail on Windows XP. Issue 17245")
- }
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c := helperCommandContext(t, ctx, "cat")
diff --git a/libgo/go/os/exec/lp_js.go b/libgo/go/os/exec/lp_js.go
new file mode 100644
index 0000000..6750fb9
--- /dev/null
+++ b/libgo/go/os/exec/lp_js.go
@@ -0,0 +1,23 @@
+// Copyright 2018 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 js,wasm
+
+package exec
+
+import (
+ "errors"
+)
+
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = errors.New("executable file not found in $PATH")
+
+// LookPath searches for an executable named file in the
+// directories named by the PATH environment variable.
+// If file contains a slash, it is tried directly and the PATH is not consulted.
+// The result may be an absolute path or a path relative to the current directory.
+func LookPath(file string) (string, error) {
+ // Wasm can not execute processes, so act as if there are no executables at all.
+ return "", &Error{file, ErrNotFound}
+}
diff --git a/libgo/go/os/exec/lp_plan9.go b/libgo/go/os/exec/lp_plan9.go
index 142f87e..5860cbc 100644
--- a/libgo/go/os/exec/lp_plan9.go
+++ b/libgo/go/os/exec/lp_plan9.go
@@ -25,8 +25,8 @@ func findExecutable(file string) error {
return os.ErrPermission
}
-// LookPath searches for an executable binary named file
-// in the directories named by the path environment variable.
+// LookPath searches for an executable named file in the
+// directories named by the path environment variable.
// If file begins with "/", "#", "./", or "../", it is tried
// directly and the path is not consulted.
// The result may be an absolute path or a path relative to the current directory.
diff --git a/libgo/go/os/exec/lp_unix.go b/libgo/go/os/exec/lp_unix.go
index 20ce7a4..799e0b4 100644
--- a/libgo/go/os/exec/lp_unix.go
+++ b/libgo/go/os/exec/lp_unix.go
@@ -27,8 +27,8 @@ func findExecutable(file string) error {
return os.ErrPermission
}
-// LookPath searches for an executable binary named file
-// in the directories named by the PATH environment variable.
+// LookPath searches for an executable named file in the
+// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
// The result may be an absolute path or a path relative to the current directory.
func LookPath(file string) (string, error) {
diff --git a/libgo/go/os/exec/lp_windows.go b/libgo/go/os/exec/lp_windows.go
index 793d4d9..9ea3d76 100644
--- a/libgo/go/os/exec/lp_windows.go
+++ b/libgo/go/os/exec/lp_windows.go
@@ -50,8 +50,8 @@ func findExecutable(file string, exts []string) (string, error) {
return "", os.ErrNotExist
}
-// LookPath searches for an executable binary named file
-// in the directories named by the PATH environment variable.
+// LookPath searches for an executable named file in the
+// directories named by the PATH environment variable.
// If file contains a slash, it is tried directly and the PATH is not consulted.
// LookPath also uses PATHEXT environment variable to match
// a suitable candidate.
diff --git a/libgo/go/os/exec_posix.go b/libgo/go/os/exec_posix.go
index 056f139..dbbb5df 100644
--- a/libgo/go/os/exec_posix.go
+++ b/libgo/go/os/exec_posix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris windows
package os
diff --git a/libgo/go/os/exec_unix.go b/libgo/go/os/exec_unix.go
index d6433bf..abae5a2 100644
--- a/libgo/go/os/exec_unix.go
+++ b/libgo/go/os/exec_unix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package os
diff --git a/libgo/go/os/executable_darwin.go b/libgo/go/os/executable_darwin.go
index ce5b814..dae9f4e 100644
--- a/libgo/go/os/executable_darwin.go
+++ b/libgo/go/os/executable_darwin.go
@@ -4,12 +4,17 @@
package os
+import "errors"
+
var executablePath string // set by ../runtime/os_darwin.go
var initCwd, initCwdErr = Getwd()
func executable() (string, error) {
ep := executablePath
+ if len(ep) == 0 {
+ return ep, errors.New("cannot find executable path")
+ }
if ep[0] != '/' {
if initCwdErr != nil {
return ep, initCwdErr
diff --git a/libgo/go/os/executable_procfs.go b/libgo/go/os/executable_procfs.go
index b5fae59..5bb63b9 100644
--- a/libgo/go/os/executable_procfs.go
+++ b/libgo/go/os/executable_procfs.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build linux netbsd dragonfly nacl
+// +build linux netbsd dragonfly nacl js,wasm
package os
diff --git a/libgo/go/os/executable_solaris.go b/libgo/go/os/executable_solaris.go
index 80f9372..b145980 100644
--- a/libgo/go/os/executable_solaris.go
+++ b/libgo/go/os/executable_solaris.go
@@ -6,12 +6,17 @@ package os
import "syscall"
+var executablePath string // set by sysauxv in ../runtime/os3_solaris.go
+
var initCwd, initCwdErr = Getwd()
func executable() (string, error) {
- path, err := syscall.Getexecname()
- if err != nil {
- return path, err
+ path := executablePath
+ if len(path) == 0 {
+ path, err := syscall.Getexecname()
+ if err != nil {
+ return path, err
+ }
}
if len(path) > 0 && path[0] != '/' {
if initCwdErr != nil {
diff --git a/libgo/go/os/fifo_test.go b/libgo/go/os/fifo_test.go
new file mode 100644
index 0000000..3041dcf
--- /dev/null
+++ b/libgo/go/os/fifo_test.go
@@ -0,0 +1,112 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin dragonfly freebsd linux netbsd openbsd
+
+package os_test
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "runtime"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+)
+
+// Issue 24164.
+func TestFifoEOF(t *testing.T) {
+ switch runtime.GOOS {
+ case "android":
+ t.Skip("skipping on Android; mkfifo syscall not available")
+ case "openbsd":
+ // On OpenBSD 6.2 this test just hangs for some reason.
+ t.Skip("skipping on OpenBSD; issue 25877")
+ }
+
+ dir, err := ioutil.TempDir("", "TestFifoEOF")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ fifoName := filepath.Join(dir, "fifo")
+ if err := syscall.Mkfifo(fifoName, 0600); err != nil {
+ t.Fatal(err)
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ w, err := os.OpenFile(fifoName, os.O_WRONLY, 0)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ defer func() {
+ if err := w.Close(); err != nil {
+ t.Errorf("error closing writer: %v", err)
+ }
+ }()
+
+ for i := 0; i < 3; i++ {
+ time.Sleep(10 * time.Millisecond)
+ _, err := fmt.Fprintf(w, "line %d\n", i)
+ if err != nil {
+ t.Errorf("error writing to fifo: %v", err)
+ return
+ }
+ }
+ time.Sleep(10 * time.Millisecond)
+ }()
+
+ defer wg.Wait()
+
+ r, err := os.Open(fifoName)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ done := make(chan bool)
+ go func() {
+ defer close(done)
+
+ defer func() {
+ if err := r.Close(); err != nil {
+ t.Errorf("error closing reader: %v", err)
+ }
+ }()
+
+ rbuf := bufio.NewReader(r)
+ for {
+ b, err := rbuf.ReadBytes('\n')
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ t.Logf("%s\n", bytes.TrimSpace(b))
+ }
+ }()
+
+ select {
+ case <-done:
+ // Test succeeded.
+ case <-time.After(time.Second):
+ t.Error("timed out waiting for read")
+ // Close the reader to force the read to complete.
+ r.Close()
+ }
+}
diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go
index c667421..cba70d7 100644
--- a/libgo/go/os/file.go
+++ b/libgo/go/os/file.go
@@ -41,6 +41,7 @@ import (
"internal/poll"
"internal/testlog"
"io"
+ "runtime"
"syscall"
"time"
)
@@ -220,12 +221,26 @@ func Mkdir(name string, perm FileMode) error {
// mkdir(2) itself won't handle the sticky bit on *BSD and Solaris
if !supportsCreateWithStickyBit && perm&ModeSticky != 0 {
- Chmod(name, perm)
+ e = setStickyBit(name)
+
+ if e != nil {
+ Remove(name)
+ return e
+ }
}
return nil
}
+// setStickyBit adds ModeSticky to the permision bits of path, non atomic.
+func setStickyBit(name string) error {
+ fi, err := Stat(name)
+ if err != nil {
+ return err
+ }
+ return Chmod(name, fi.Mode()|ModeSticky)
+}
+
// Chdir changes the current working directory to the named directory.
// If there is an error, it will be of type *PathError.
func Chdir(dir string) error {
@@ -315,6 +330,57 @@ func TempDir() string {
return tempDir()
}
+// UserCacheDir returns the default root directory to use for user-specific
+// cached data. Users should create their own application-specific subdirectory
+// within this one and use that.
+//
+// On Unix systems, it returns $XDG_CACHE_HOME as specified by
+// https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html if
+// non-empty, else $HOME/.cache.
+// On Darwin, it returns $HOME/Library/Caches.
+// On Windows, it returns %LocalAppData%.
+// On Plan 9, it returns $home/lib/cache.
+//
+// If the location cannot be determined (for example, $HOME is not defined),
+// then it will return an error.
+func UserCacheDir() (string, error) {
+ var dir string
+
+ switch runtime.GOOS {
+ case "windows":
+ dir = Getenv("LocalAppData")
+ if dir == "" {
+ return "", errors.New("%LocalAppData% is not defined")
+ }
+
+ case "darwin":
+ dir = Getenv("HOME")
+ if dir == "" {
+ return "", errors.New("$HOME is not defined")
+ }
+ dir += "/Library/Caches"
+
+ case "plan9":
+ dir = Getenv("home")
+ if dir == "" {
+ return "", errors.New("$home is not defined")
+ }
+ dir += "/lib/cache"
+
+ default: // Unix
+ dir = Getenv("XDG_CACHE_HOME")
+ if dir == "" {
+ dir = Getenv("HOME")
+ if dir == "" {
+ return "", errors.New("neither $XDG_CACHE_HOME nor $HOME are defined")
+ }
+ dir += "/.cache"
+ }
+ }
+
+ return dir, nil
+}
+
// Chmod changes the mode of the named file to mode.
// If the file is a symbolic link, it changes the mode of the link's target.
// If there is an error, it will be of type *PathError.
diff --git a/libgo/go/os/file_plan9.go b/libgo/go/os/file_plan9.go
index 7e28178..2c74403 100644
--- a/libgo/go/os/file_plan9.go
+++ b/libgo/go/os/file_plan9.go
@@ -133,7 +133,8 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
}
// Close closes the File, rendering it unusable for I/O.
-// It returns an error, if any.
+// On files that support SetDeadline, any pending I/O operations will
+// be canceled and return immediately with an error.
func (f *File) Close() error {
if err := f.checkValid("close"); err != nil {
return err
@@ -451,7 +452,11 @@ func Readlink(name string) (string, error) {
// Chown changes the numeric uid and gid of the named file.
// If the file is a symbolic link, it changes the uid and gid of the link's target.
+// A uid or gid of -1 means to not change that value.
// If there is an error, it will be of type *PathError.
+//
+// On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or
+// EPLAN9 error, wrapped in *PathError.
func Chown(name string, uid, gid int) error {
return &PathError{"chown", name, syscall.EPLAN9}
}
@@ -473,7 +478,12 @@ func (f *File) Chown(uid, gid int) error {
}
func tempDir() string {
- return "/tmp"
+ dir := Getenv("TMPDIR")
+ if dir == "" {
+ dir = "/tmp"
+ }
+ return dir
+
}
// Chdir changes the current working directory to the file,
diff --git a/libgo/go/os/file_posix.go b/libgo/go/os/file_posix.go
index 67da384..9bd1cc7 100644
--- a/libgo/go/os/file_posix.go
+++ b/libgo/go/os/file_posix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris windows
package os
@@ -69,10 +69,11 @@ func (f *File) chmod(mode FileMode) error {
// Chown changes the numeric uid and gid of the named file.
// If the file is a symbolic link, it changes the uid and gid of the link's target.
+// A uid or gid of -1 means to not change that value.
// If there is an error, it will be of type *PathError.
//
-// On Windows, it always returns the syscall.EWINDOWS error, wrapped
-// in *PathError.
+// On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or
+// EPLAN9 error, wrapped in *PathError.
func Chown(name string, uid, gid int) error {
if e := syscall.Chown(name, uid, gid); e != nil {
return &PathError{"chown", name, e}
diff --git a/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go
index 67d2ee1..b2aea33 100644
--- a/libgo/go/os/file_unix.go
+++ b/libgo/go/os/file_unix.go
@@ -2,12 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package os
import (
"internal/poll"
+ "internal/syscall/unix"
"runtime"
"syscall"
)
@@ -74,9 +75,15 @@ func (f *File) Fd() uintptr {
// NewFile returns a new File with the given file descriptor and
// name. The returned value will be nil if fd is not a valid file
-// descriptor.
+// descriptor. On Unix systems, if the file descriptor is in
+// non-blocking mode, NewFile will attempt to return a pollable File
+// (one for which the SetDeadline methods work).
func NewFile(fd uintptr, name string) *File {
- return newFile(fd, name, kindNewFile)
+ kind := kindNewFile
+ if nb, err := unix.IsNonblock(int(fd)); err == nil && nb {
+ kind = kindNonBlock
+ }
+ return newFile(fd, name, kind)
}
// newFileKind describes the kind of file to newFile.
@@ -86,6 +93,7 @@ const (
kindNewFile newFileKind = iota
kindOpenFile
kindPipe
+ kindNonBlock
)
// newFile is like NewFile, but if called from OpenFile or Pipe
@@ -106,14 +114,28 @@ func newFile(fd uintptr, name string, kind newFileKind) *File {
stdoutOrErr: fdi == 1 || fdi == 2,
}}
+ pollable := kind == kindOpenFile || kind == kindPipe || kind == kindNonBlock
+
// Don't try to use kqueue with regular files on FreeBSD.
// It crashes the system unpredictably while running all.bash.
// Issue 19093.
+ // If the caller passed a non-blocking filedes (kindNonBlock),
+ // we assume they know what they are doing so we allow it to be
+ // used with kqueue.
if runtime.GOOS == "freebsd" && kind == kindOpenFile {
- kind = kindNewFile
+ pollable = false
+ }
+
+ // On Darwin, kqueue does not work properly with fifos:
+ // closing the last writer does not cause a kqueue event
+ // for any readers. See issue #24164.
+ if runtime.GOOS == "darwin" && kind == kindOpenFile {
+ var st syscall.Stat_t
+ if err := syscall.Fstat(fdi, &st); err == nil && st.Mode&syscall.S_IFMT == syscall.S_IFIFO {
+ pollable = false
+ }
}
- pollable := kind == kindOpenFile || kind == kindPipe
if err := f.pfd.Init("file", pollable); err != nil {
// An error here indicates a failure to register
// with the netpoll system. That can happen for
@@ -154,10 +176,10 @@ const DevNull = "/dev/null"
// openFileNolog is the Unix implementation of OpenFile.
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
- chmod := false
+ setSticky := false
if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
if _, err := Stat(name); IsNotExist(err) {
- chmod = true
+ setSticky = true
}
}
@@ -171,7 +193,7 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
// On OS X, sigaction(2) doesn't guarantee that SA_RESTART will cause
// open(2) to be restarted for regular files. This is easy to reproduce on
- // fuse file systems (see http://golang.org/issue/11180).
+ // fuse file systems (see https://golang.org/issue/11180).
if runtime.GOOS == "darwin" && e == syscall.EINTR {
continue
}
@@ -180,8 +202,8 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
}
// open(2) itself won't handle the sticky bit on *BSD and Solaris
- if chmod {
- Chmod(name, perm)
+ if setSticky {
+ setStickyBit(name)
}
// There's a race here with fork/exec, which we are
@@ -194,7 +216,8 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
}
// Close closes the File, rendering it unusable for I/O.
-// It returns an error, if any.
+// On files that support SetDeadline, any pending I/O operations will
+// be canceled and return immediately with an error.
func (f *File) Close() error {
if f == nil {
return ErrInvalid
@@ -283,7 +306,7 @@ func Truncate(name string, size int64) error {
return nil
}
-// Remove removes the named file or directory.
+// Remove removes the named file or (empty) directory.
// If there is an error, it will be of type *PathError.
func Remove(name string) error {
// System call interface forces us to know
diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go
index 8ed9252..5a77d66 100644
--- a/libgo/go/os/os_test.go
+++ b/libgo/go/os/os_test.go
@@ -1388,7 +1388,7 @@ func TestSeek(t *testing.T) {
func TestSeekError(t *testing.T) {
switch runtime.GOOS {
- case "plan9", "nacl":
+ case "js", "nacl", "plan9":
t.Skipf("skipping test on %v", runtime.GOOS)
}
@@ -1521,11 +1521,7 @@ func runBinHostname(t *testing.T, argv []string) string {
return output
}
-func testWindowsHostname(t *testing.T) {
- hostname, err := Hostname()
- if err != nil {
- t.Fatal(err)
- }
+func testWindowsHostname(t *testing.T, hostname string) {
cmd := osexec.Command("hostname")
out, err := cmd.CombinedOutput()
if err != nil {
@@ -1533,18 +1529,30 @@ func testWindowsHostname(t *testing.T) {
}
want := strings.Trim(string(out), "\r\n")
if hostname != want {
- t.Fatalf("Hostname() = %q, want %q", hostname, want)
+ t.Fatalf("Hostname() = %q != system hostname of %q", hostname, want)
}
}
func TestHostname(t *testing.T) {
+ hostname, err := Hostname()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if hostname == "" {
+ t.Fatal("Hostname returned empty string and no error")
+ }
+ if strings.Contains(hostname, "\x00") {
+ t.Fatalf("unexpected zero byte in hostname: %q", hostname)
+ }
+
// There is no other way to fetch hostname on windows, but via winapi.
// On Plan 9 it can be taken from #c/sysname as Hostname() does.
switch runtime.GOOS {
case "android", "plan9":
- t.Skipf("%s doesn't have /bin/hostname", runtime.GOOS)
+ // No /bin/hostname to verify against.
+ return
case "windows":
- testWindowsHostname(t)
+ testWindowsHostname(t, hostname)
return
}
@@ -1553,11 +1561,6 @@ func TestHostname(t *testing.T) {
// Check internal Hostname() against the output of /bin/hostname.
// Allow that the internal Hostname returns a Fully Qualified Domain Name
// and the /bin/hostname only returns the first component
- hostname, err := Hostname()
- if err != nil {
- t.Fatalf("%v", err)
- }
-
var want string
if runtime.GOOS == "aix" {
want = runBinHostname(t, []string{"hostname", "-s"})
@@ -1795,23 +1798,54 @@ func TestSameFile(t *testing.T) {
}
}
-func TestDevNullFile(t *testing.T) {
- f, err := Open(DevNull)
+func testDevNullFileInfo(t *testing.T, statname, devNullName string, fi FileInfo, ignoreCase bool) {
+ pre := fmt.Sprintf("%s(%q): ", statname, devNullName)
+ name := filepath.Base(devNullName)
+ if ignoreCase {
+ if strings.ToUpper(fi.Name()) != strings.ToUpper(name) {
+ t.Errorf(pre+"wrong file name have %v want %v", fi.Name(), name)
+ }
+ } else {
+ if fi.Name() != name {
+ t.Errorf(pre+"wrong file name have %v want %v", fi.Name(), name)
+ }
+ }
+ if fi.Size() != 0 {
+ t.Errorf(pre+"wrong file size have %d want 0", fi.Size())
+ }
+ if fi.Mode()&ModeDevice == 0 {
+ t.Errorf(pre+"wrong file mode %q: ModeDevice is not set", fi.Mode())
+ }
+ if fi.Mode()&ModeCharDevice == 0 {
+ t.Errorf(pre+"wrong file mode %q: ModeCharDevice is not set", fi.Mode())
+ }
+ if fi.Mode().IsRegular() {
+ t.Errorf(pre+"wrong file mode %q: IsRegular returns true", fi.Mode())
+ }
+}
+
+func testDevNullFile(t *testing.T, devNullName string, ignoreCase bool) {
+ f, err := Open(devNullName)
if err != nil {
- t.Fatalf("Open(%s): %v", DevNull, err)
+ t.Fatalf("Open(%s): %v", devNullName, err)
}
defer f.Close()
+
fi, err := f.Stat()
if err != nil {
- t.Fatalf("Stat(%s): %v", DevNull, err)
- }
- name := filepath.Base(DevNull)
- if fi.Name() != name {
- t.Fatalf("wrong file name have %v want %v", fi.Name(), name)
+ t.Fatalf("Stat(%s): %v", devNullName, err)
}
- if fi.Size() != 0 {
- t.Fatalf("wrong file size have %d want 0", fi.Size())
+ testDevNullFileInfo(t, "f.Stat", devNullName, fi, ignoreCase)
+
+ fi, err = Stat(devNullName)
+ if err != nil {
+ t.Fatalf("Stat(%s): %v", devNullName, err)
}
+ testDevNullFileInfo(t, "Stat", devNullName, fi, ignoreCase)
+}
+
+func TestDevNullFile(t *testing.T) {
+ testDevNullFile(t, DevNull, false)
}
var testLargeWrite = flag.Bool("large_write", false, "run TestLargeWriteToConsole test that floods console with output")
@@ -1885,7 +1919,7 @@ func TestStatStdin(t *testing.T) {
t.Fatal(err)
}
switch mode := fi.Mode(); {
- case mode&ModeCharDevice != 0:
+ case mode&ModeCharDevice != 0 && mode&ModeDevice != 0:
case mode&ModeNamedPipe != 0:
default:
t.Fatalf("unexpected Stdin mode (%v), want ModeCharDevice or ModeNamedPipe", mode)
@@ -2221,6 +2255,8 @@ func TestPipeThreads(t *testing.T) {
t.Skip("skipping on Windows; issue 19098")
case "plan9":
t.Skip("skipping on Plan 9; does not support runtime poller")
+ case "js":
+ t.Skip("skipping on js; no support for os.Pipe")
}
threads := 100
diff --git a/libgo/go/os/os_unix_test.go b/libgo/go/os/os_unix_test.go
index 56c885c..54f121e 100644
--- a/libgo/go/os/os_unix_test.go
+++ b/libgo/go/os/os_unix_test.go
@@ -15,6 +15,7 @@ import (
"strings"
"syscall"
"testing"
+ "time"
)
func init() {
@@ -204,3 +205,76 @@ func TestReaddirRemoveRace(t *testing.T) {
t.FailNow()
}
}
+
+// Issue 23120: respect umask when doing Mkdir with the sticky bit
+func TestMkdirStickyUmask(t *testing.T) {
+ const umask = 0077
+ dir := newDir("TestMkdirStickyUmask", t)
+ defer RemoveAll(dir)
+ oldUmask := syscall.Umask(umask)
+ defer syscall.Umask(oldUmask)
+ p := filepath.Join(dir, "dir1")
+ if err := Mkdir(p, ModeSticky|0755); err != nil {
+ t.Fatal(err)
+ }
+ fi, err := Stat(p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if mode := fi.Mode(); (mode&umask) != 0 || (mode&^ModePerm) != (ModeDir|ModeSticky) {
+ t.Errorf("unexpected mode %s", mode)
+ }
+}
+
+// See also issues: 22939, 24331
+func newFileTest(t *testing.T, blocking bool) {
+ p := make([]int, 2)
+ if err := syscall.Pipe(p); err != nil {
+ t.Fatalf("pipe: %v", err)
+ }
+ defer syscall.Close(p[1])
+
+ // Set the the read-side to non-blocking.
+ if !blocking {
+ if err := syscall.SetNonblock(p[0], true); err != nil {
+ syscall.Close(p[0])
+ t.Fatalf("SetNonblock: %v", err)
+ }
+ }
+ // Convert it to a file.
+ file := NewFile(uintptr(p[0]), "notapipe")
+ if file == nil {
+ syscall.Close(p[0])
+ t.Fatalf("failed to convert fd to file!")
+ }
+ defer file.Close()
+
+ // Try to read with deadline (but don't block forever).
+ b := make([]byte, 1)
+ // Send something after 100ms.
+ timer := time.AfterFunc(100*time.Millisecond, func() { syscall.Write(p[1], []byte("a")) })
+ defer timer.Stop()
+ file.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
+ _, err := file.Read(b)
+ if !blocking {
+ // We want it to fail with a timeout.
+ if !IsTimeout(err) {
+ t.Fatalf("No timeout reading from file: %v", err)
+ }
+ } else {
+ // We want it to succeed after 100ms
+ if err != nil {
+ t.Fatalf("Error reading from file: %v", err)
+ }
+ }
+}
+
+func TestNewFileBlock(t *testing.T) {
+ t.Parallel()
+ newFileTest(t, true)
+}
+
+func TestNewFileNonBlock(t *testing.T) {
+ t.Parallel()
+ newFileTest(t, false)
+}
diff --git a/libgo/go/os/path.go b/libgo/go/os/path.go
index eb996e5..cdfbc18 100644
--- a/libgo/go/os/path.go
+++ b/libgo/go/os/path.go
@@ -6,7 +6,6 @@ package os
import (
"io"
- "runtime"
"syscall"
)
@@ -39,8 +38,8 @@ func MkdirAll(path string, perm FileMode) error {
}
if j > 1 {
- // Create parent
- err = MkdirAll(path[0:j-1], perm)
+ // Create parent.
+ err = MkdirAll(fixRootDirectory(path[:j-1]), perm)
if err != nil {
return err
}
@@ -84,32 +83,35 @@ func RemoveAll(path string) error {
return err
}
- // Directory.
- fd, err := Open(path)
- if err != nil {
- if IsNotExist(err) {
- // Race. It was deleted between the Lstat and Open.
- // Return nil per RemoveAll's docs.
- return nil
- }
- return err
- }
-
// Remove contents & return first error.
err = nil
for {
- if err == nil && (runtime.GOOS == "plan9" || runtime.GOOS == "nacl") {
- // Reset read offset after removing directory entries.
- // See golang.org/issue/22572.
- fd.Seek(0, 0)
+ fd, err := Open(path)
+ if err != nil {
+ if IsNotExist(err) {
+ // Already deleted by someone else.
+ return nil
+ }
+ return err
}
- names, err1 := fd.Readdirnames(100)
+
+ const request = 1024
+ names, err1 := fd.Readdirnames(request)
+
+ // Removing files from the directory may have caused
+ // the OS to reshuffle it. Simply calling Readdirnames
+ // again may skip some entries. The only reliable way
+ // to avoid this is to close and re-open the
+ // directory. See issue 20841.
+ fd.Close()
+
for _, name := range names {
err1 := RemoveAll(path + string(PathSeparator) + name)
if err == nil {
err = err1
}
}
+
if err1 == io.EOF {
break
}
@@ -120,10 +122,29 @@ func RemoveAll(path string) error {
if len(names) == 0 {
break
}
- }
- // Close directory, because windows won't remove opened directory.
- fd.Close()
+ // We don't want to re-open unnecessarily, so if we
+ // got fewer than request names from Readdirnames, try
+ // simply removing the directory now. If that
+ // succeeds, we are done.
+ if len(names) < request {
+ err1 := Remove(path)
+ if err1 == nil || IsNotExist(err1) {
+ return nil
+ }
+
+ if err != nil {
+ // We got some error removing the
+ // directory contents, and since we
+ // read fewer names than we requested
+ // there probably aren't more files to
+ // remove. Don't loop around to read
+ // the directory again. We'll probably
+ // just get the same error.
+ return err
+ }
+ }
+ }
// Remove directory.
err1 := Remove(path)
diff --git a/libgo/go/os/path_plan9.go b/libgo/go/os/path_plan9.go
index b09b53a..a54b4b9 100644
--- a/libgo/go/os/path_plan9.go
+++ b/libgo/go/os/path_plan9.go
@@ -13,3 +13,7 @@ const (
func IsPathSeparator(c uint8) bool {
return PathSeparator == c
}
+
+func fixRootDirectory(p string) string {
+ return p
+}
diff --git a/libgo/go/os/path_unix.go b/libgo/go/os/path_unix.go
index bc0f239..3cb0e3a 100644
--- a/libgo/go/os/path_unix.go
+++ b/libgo/go/os/path_unix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package os
@@ -33,3 +33,7 @@ func basename(name string) string {
return name
}
+
+func fixRootDirectory(p string) string {
+ return p
+}
diff --git a/libgo/go/os/path_windows.go b/libgo/go/os/path_windows.go
index 101b026..87b1cac 100644
--- a/libgo/go/os/path_windows.go
+++ b/libgo/go/os/path_windows.go
@@ -207,3 +207,14 @@ func fixLongPath(path string) string {
}
return string(pathbuf[:w])
}
+
+// fixRootDirectory fixes a reference to a drive's root directory to
+// have the required trailing slash.
+func fixRootDirectory(p string) string {
+ if len(p) == len(`\\?\c:`) {
+ if IsPathSeparator(p[0]) && IsPathSeparator(p[1]) && p[2] == '?' && IsPathSeparator(p[3]) && p[5] == ':' {
+ return p + `\`
+ }
+ }
+ return p
+}
diff --git a/libgo/go/os/path_windows_test.go b/libgo/go/os/path_windows_test.go
index cce0bdd..00a3e63 100644
--- a/libgo/go/os/path_windows_test.go
+++ b/libgo/go/os/path_windows_test.go
@@ -5,8 +5,10 @@
package os_test
import (
+ "io/ioutil"
"os"
"strings"
+ "syscall"
"testing"
)
@@ -44,3 +46,31 @@ func TestFixLongPath(t *testing.T) {
}
}
}
+
+func TestMkdirAllExtendedLength(t *testing.T) {
+ tmpDir, err := ioutil.TempDir("", "TestMkdirAllExtendedLength")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ const prefix = `\\?\`
+ if len(tmpDir) < 4 || tmpDir[:4] != prefix {
+ fullPath, err := syscall.FullPath(tmpDir)
+ if err != nil {
+ t.Fatalf("FullPath(%q) fails: %v", tmpDir, err)
+ }
+ tmpDir = prefix + fullPath
+ }
+ path := tmpDir + `\dir\`
+ err = os.MkdirAll(path, 0777)
+ if err != nil {
+ t.Fatalf("MkdirAll(%q) failed: %v", path, err)
+ }
+
+ path = path + `.\dir2`
+ err = os.MkdirAll(path, 0777)
+ if err == nil {
+ t.Fatalf("MkdirAll(%q) should have failed, but did not", path)
+ }
+}
diff --git a/libgo/go/os/pipe_freebsd.go b/libgo/go/os/pipe2_bsd.go
index 93bd869..0ef894b 100644
--- a/libgo/go/os/pipe_freebsd.go
+++ b/libgo/go/os/pipe2_bsd.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 freebsd netbsd openbsd
+
package os
import "syscall"
diff --git a/libgo/go/os/pipe_bsd.go b/libgo/go/os/pipe_bsd.go
index 8398a24..dc4c951 100644
--- a/libgo/go/os/pipe_bsd.go
+++ b/libgo/go/os/pipe_bsd.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly nacl netbsd openbsd solaris
+// +build aix darwin dragonfly js,wasm nacl solaris
package os
diff --git a/libgo/go/os/pipe_test.go b/libgo/go/os/pipe_test.go
index aad6c27..59d31e5 100644
--- a/libgo/go/os/pipe_test.go
+++ b/libgo/go/os/pipe_test.go
@@ -3,11 +3,13 @@
// license that can be found in the LICENSE file.
// Test broken pipes on Unix systems.
-// +build !windows,!plan9,!nacl
+// +build !windows,!plan9,!nacl,!js
package os_test
import (
+ "bufio"
+ "bytes"
"fmt"
"internal/testenv"
"io"
@@ -305,3 +307,133 @@ func testCloseWithBlockingRead(t *testing.T, r, w *os.File) {
wg.Wait()
}
+
+// Issue 24164, for pipes.
+func TestPipeEOF(t *testing.T) {
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ defer func() {
+ if err := w.Close(); err != nil {
+ t.Errorf("error closing writer: %v", err)
+ }
+ }()
+
+ for i := 0; i < 3; i++ {
+ time.Sleep(10 * time.Millisecond)
+ _, err := fmt.Fprintf(w, "line %d\n", i)
+ if err != nil {
+ t.Errorf("error writing to fifo: %v", err)
+ return
+ }
+ }
+ time.Sleep(10 * time.Millisecond)
+ }()
+
+ defer wg.Wait()
+
+ done := make(chan bool)
+ go func() {
+ defer close(done)
+
+ defer func() {
+ if err := r.Close(); err != nil {
+ t.Errorf("error closing reader: %v", err)
+ }
+ }()
+
+ rbuf := bufio.NewReader(r)
+ for {
+ b, err := rbuf.ReadBytes('\n')
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ t.Logf("%s\n", bytes.TrimSpace(b))
+ }
+ }()
+
+ select {
+ case <-done:
+ // Test succeeded.
+ case <-time.After(time.Second):
+ t.Error("timed out waiting for read")
+ // Close the reader to force the read to complete.
+ r.Close()
+ }
+}
+
+// Issue 24481.
+func TestFdRace(t *testing.T) {
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ var wg sync.WaitGroup
+ call := func() {
+ defer wg.Done()
+ w.Fd()
+ }
+
+ const tries = 100
+ for i := 0; i < tries; i++ {
+ wg.Add(1)
+ go call()
+ }
+ wg.Wait()
+}
+
+func TestFdReadRace(t *testing.T) {
+ t.Parallel()
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ c := make(chan bool)
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ var buf [10]byte
+ r.SetReadDeadline(time.Now().Add(time.Second))
+ c <- true
+ if _, err := r.Read(buf[:]); os.IsTimeout(err) {
+ t.Error("read timed out")
+ }
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ <-c
+ // Give the other goroutine a chance to enter the Read.
+ // It doesn't matter if this occasionally fails, the test
+ // will still pass, it just won't test anything.
+ time.Sleep(10 * time.Millisecond)
+ r.Fd()
+
+ // The bug was that Fd would hang until Read timed out.
+ // If the bug is fixed, then closing r here will cause
+ // the Read to exit before the timeout expires.
+ r.Close()
+ }()
+
+ wg.Wait()
+}
diff --git a/libgo/go/os/signal/signal.go b/libgo/go/os/signal/signal.go
index e5a21e8..a0eba0d 100644
--- a/libgo/go/os/signal/signal.go
+++ b/libgo/go/os/signal/signal.go
@@ -86,6 +86,12 @@ func Ignore(sig ...os.Signal) {
cancel(sig, ignoreSignal)
}
+// Ignored reports whether sig is currently ignored.
+func Ignored(sig os.Signal) bool {
+ sn := signum(sig)
+ return sn >= 0 && signalIgnored(sn)
+}
+
// Notify causes package signal to relay incoming signals to c.
// If no signals are provided, all incoming signals will be relayed to c.
// Otherwise, just the provided signals will.
diff --git a/libgo/go/os/signal/signal_plan9.go b/libgo/go/os/signal/signal_plan9.go
index b065ae5..a1eb688 100644
--- a/libgo/go/os/signal/signal_plan9.go
+++ b/libgo/go/os/signal/signal_plan9.go
@@ -15,6 +15,7 @@ var sigtab = make(map[os.Signal]int)
func signal_disable(uint32)
func signal_enable(uint32)
func signal_ignore(uint32)
+func signal_ignored(uint32) bool
func signal_recv() string
func init() {
@@ -58,3 +59,7 @@ func disableSignal(sig int) {
func ignoreSignal(sig int) {
signal_ignore(uint32(sig))
}
+
+func signalIgnored(sig int) bool {
+ return signal_ignored(uint32(sig))
+}
diff --git a/libgo/go/os/signal/signal_test.go b/libgo/go/os/signal/signal_test.go
index d27ff0b..ecb05fd 100644
--- a/libgo/go/os/signal/signal_test.go
+++ b/libgo/go/os/signal/signal_test.go
@@ -192,6 +192,65 @@ func TestIgnore(t *testing.T) {
testCancel(t, true)
}
+// Test that Ignored correctly detects changes to the ignored status of a signal.
+func TestIgnored(t *testing.T) {
+ // Ask to be notified on SIGWINCH.
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGWINCH)
+
+ // If we're being notified, then the signal should not be ignored.
+ if Ignored(syscall.SIGWINCH) {
+ t.Errorf("expected SIGWINCH to not be ignored.")
+ }
+ Stop(c)
+ Ignore(syscall.SIGWINCH)
+
+ // We're no longer paying attention to this signal.
+ if !Ignored(syscall.SIGWINCH) {
+ t.Errorf("expected SIGWINCH to be ignored when explicitly ignoring it.")
+ }
+
+ Reset()
+}
+
+var checkSighupIgnored = flag.Bool("check_sighup_ignored", false, "if true, TestDetectNohup will fail if SIGHUP is not ignored.")
+
+// Test that Ignored(SIGHUP) correctly detects whether it is being run under nohup.
+func TestDetectNohup(t *testing.T) {
+ if *checkSighupIgnored {
+ if !Ignored(syscall.SIGHUP) {
+ t.Fatal("SIGHUP is not ignored.")
+ } else {
+ t.Log("SIGHUP is ignored.")
+ }
+ } else {
+ defer Reset()
+ // Ugly: ask for SIGHUP so that child will not have no-hup set
+ // even if test is running under nohup environment.
+ // We have no intention of reading from c.
+ c := make(chan os.Signal, 1)
+ Notify(c, syscall.SIGHUP)
+ if out, err := exec.Command(os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput(); err == nil {
+ t.Errorf("ran test with -check_sighup_ignored and it succeeded: expected failure.\nOutput:\n%s", out)
+ }
+ Stop(c)
+ // Again, this time with nohup, assuming we can find it.
+ _, err := os.Stat("/usr/bin/nohup")
+ if err != nil {
+ t.Skip("cannot find nohup; skipping second half of test")
+ }
+ Ignore(syscall.SIGHUP)
+ os.Remove("nohup.out")
+ out, err := exec.Command("/usr/bin/nohup", os.Args[0], "-test.run=TestDetectNohup", "-check_sighup_ignored").CombinedOutput()
+
+ data, _ := ioutil.ReadFile("nohup.out")
+ os.Remove("nohup.out")
+ if err != nil {
+ t.Errorf("ran test with -check_sighup_ignored under nohup and it failed: expected success.\nError: %v\nOutput:\n%s%s", err, out, data)
+ }
+ }
+}
+
var sendUncaughtSighup = flag.Int("send_uncaught_sighup", 0, "send uncaught SIGHUP during TestStop")
// Test that Stop cancels the channel's registrations.
diff --git a/libgo/go/os/signal/signal_unix.go b/libgo/go/os/signal/signal_unix.go
index 5ec7e97..7fa634f 100644
--- a/libgo/go/os/signal/signal_unix.go
+++ b/libgo/go/os/signal/signal_unix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris windows
package signal
@@ -15,6 +15,7 @@ import (
func signal_disable(uint32)
func signal_enable(uint32)
func signal_ignore(uint32)
+func signal_ignored(uint32) bool
func signal_recv() uint32
func loop() {
@@ -56,3 +57,7 @@ func disableSignal(sig int) {
func ignoreSignal(sig int) {
signal_ignore(uint32(sig))
}
+
+func signalIgnored(sig int) bool {
+ return signal_ignored(uint32(sig))
+}
diff --git a/libgo/go/os/stat_nacl.go b/libgo/go/os/stat_nacljs.go
index 0c53f2f..f14add8 100644
--- a/libgo/go/os/stat_nacl.go
+++ b/libgo/go/os/stat_nacljs.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 js,wasm nacl
+
package os
import (
diff --git a/libgo/go/os/stat_plan9.go b/libgo/go/os/stat_plan9.go
index 8057fd4..b43339a 100644
--- a/libgo/go/os/stat_plan9.go
+++ b/libgo/go/os/stat_plan9.go
@@ -9,7 +9,7 @@ import (
"time"
)
-const _BIT16SZ = 2
+const bitSize16 = 2
func fileInfoFromStat(d *syscall.Dir) FileInfo {
fs := &fileStat{
@@ -35,6 +35,10 @@ func fileInfoFromStat(d *syscall.Dir) FileInfo {
if d.Type != 'M' {
fs.mode |= ModeDevice
}
+ // Consider all files served by #c as character device files.
+ if d.Type == 'c' {
+ fs.mode |= ModeCharDevice
+ }
return fs
}
@@ -46,7 +50,7 @@ func dirstat(arg interface{}) (*syscall.Dir, error) {
size := syscall.STATFIXLEN + 16*4
for i := 0; i < 2; i++ {
- buf := make([]byte, _BIT16SZ+size)
+ buf := make([]byte, bitSize16+size)
var n int
switch a := arg.(type) {
@@ -60,7 +64,7 @@ func dirstat(arg interface{}) (*syscall.Dir, error) {
panic("phase error in dirstat")
}
- if n < _BIT16SZ {
+ if n < bitSize16 {
return nil, &PathError{"stat", name, err}
}
diff --git a/libgo/go/os/stat_unix.go b/libgo/go/os/stat_unix.go
index bc5d06c..4f85dce 100644
--- a/libgo/go/os/stat_unix.go
+++ b/libgo/go/os/stat_unix.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly freebsd linux nacl netbsd openbsd solaris
+// +build aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris
package os
diff --git a/libgo/go/os/sys_bsd.go b/libgo/go/os/sys_bsd.go
index 8ad5e21..d820be2 100644
--- a/libgo/go/os/sys_bsd.go
+++ b/libgo/go/os/sys_bsd.go
@@ -2,10 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build darwin dragonfly freebsd nacl netbsd openbsd
-
-// os code shared between *BSD systems including OS X (Darwin)
-// and FreeBSD.
+// +build darwin dragonfly freebsd js,wasm nacl netbsd openbsd
package os
diff --git a/libgo/go/os/sys_darwin.go b/libgo/go/os/sys_darwin.go
deleted file mode 100644
index 11d678e..0000000
--- a/libgo/go/os/sys_darwin.go
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2014 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package os
-
-// supportsCloseOnExec reports whether the platform supports the
-// O_CLOEXEC flag.
-// The O_CLOEXEC flag was introduced in OS X 10.7 (Darwin 11.0.0).
-// See http://support.apple.com/kb/HT1633.
-const supportsCloseOnExec = true
diff --git a/libgo/go/os/sys_freebsd.go b/libgo/go/os/sys_js.go
index 3ec49fa..e860654 100644
--- a/libgo/go/os/sys_freebsd.go
+++ b/libgo/go/os/sys_js.go
@@ -1,10 +1,11 @@
-// Copyright 2014 The Go Authors. All rights reserved.
+// Copyright 2018 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 js,wasm
+
package os
// supportsCloseOnExec reports whether the platform supports the
// O_CLOEXEC flag.
-// The O_CLOEXEC flag was introduced in FreeBSD 8.3.
-const supportsCloseOnExec bool = true
+const supportsCloseOnExec = false
diff --git a/libgo/go/os/sys_linux.go b/libgo/go/os/sys_linux.go
index 76cdf50..36a8a24 100644
--- a/libgo/go/os/sys_linux.go
+++ b/libgo/go/os/sys_linux.go
@@ -2,19 +2,46 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Linux-specific
-
package os
+import (
+ "runtime"
+ "syscall"
+)
+
func hostname() (name string, err error) {
+ // Try uname first, as it's only one system call and reading
+ // from /proc is not allowed on Android.
+ var un syscall.Utsname
+ err = syscall.Uname(&un)
+
+ var buf [512]byte // Enough for a DNS name.
+ for i, b := range un.Nodename[:] {
+ buf[i] = uint8(b)
+ if b == 0 {
+ name = string(buf[:i])
+ break
+ }
+ }
+ // If we got a name and it's not potentially truncated
+ // (Nodename is 65 bytes), return it.
+ if err == nil && len(name) > 0 && len(name) < 64 {
+ return name, nil
+ }
+ if runtime.GOOS == "android" {
+ if name != "" {
+ return name, nil
+ }
+ return "localhost", nil
+ }
+
f, err := Open("/proc/sys/kernel/hostname")
if err != nil {
return "", err
}
defer f.Close()
- var buf [512]byte // Enough for a DNS name.
- n, err := f.Read(buf[0:])
+ n, err := f.Read(buf[:])
if err != nil {
return "", err
}
@@ -22,5 +49,5 @@ func hostname() (name string, err error) {
if n > 0 && buf[n-1] == '\n' {
n--
}
- return string(buf[0:n]), nil
+ return string(buf[:n]), nil
}
diff --git a/libgo/go/os/sys_plan9.go b/libgo/go/os/sys_plan9.go
index 07a7905..40374eb 100644
--- a/libgo/go/os/sys_plan9.go
+++ b/libgo/go/os/sys_plan9.go
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Plan 9-specific
-
package os
func hostname() (name string, err error) {
diff --git a/libgo/go/os/sys_unix.go b/libgo/go/os/sys_unix.go
index 4caf8bd..8491bad 100644
--- a/libgo/go/os/sys_unix.go
+++ b/libgo/go/os/sys_unix.go
@@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix dragonfly linux netbsd openbsd solaris
+// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
package os
// supportsCloseOnExec reports whether the platform supports the
// O_CLOEXEC flag.
+// On Darwin, the O_CLOEXEC flag was introduced in OS X 10.7 (Darwin 11.0.0).
+// See https://support.apple.com/kb/HT1633.
+// On FreeBSD, the O_CLOEXEC flag was introduced in version 8.3.
const supportsCloseOnExec = true
diff --git a/libgo/go/os/timeout_test.go b/libgo/go/os/timeout_test.go
index 6f47ed0..4720738 100644
--- a/libgo/go/os/timeout_test.go
+++ b/libgo/go/os/timeout_test.go
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
// +build !nacl
+// +build !js
// +build !plan9
// +build !windows
@@ -15,8 +16,10 @@ import (
"io/ioutil"
"math/rand"
"os"
+ "os/signal"
"runtime"
"sync"
+ "syscall"
"testing"
"time"
)
@@ -587,3 +590,40 @@ func TestRacyWrite(t *testing.T) {
}()
}
}
+
+// Closing a TTY while reading from it should not hang. Issue 23943.
+func TestTTYClose(t *testing.T) {
+ // Ignore SIGTTIN in case we are running in the background.
+ signal.Ignore(syscall.SIGTTIN)
+ defer signal.Reset(syscall.SIGTTIN)
+
+ f, err := os.Open("/dev/tty")
+ if err != nil {
+ t.Skipf("skipping because opening /dev/tty failed: %v", err)
+ }
+
+ go func() {
+ var buf [1]byte
+ f.Read(buf[:])
+ }()
+
+ // Give the goroutine a chance to enter the read.
+ // It doesn't matter much if it occasionally fails to do so,
+ // we won't be testing what we want to test but the test will pass.
+ time.Sleep(time.Millisecond)
+
+ c := make(chan bool)
+ go func() {
+ defer close(c)
+ f.Close()
+ }()
+
+ select {
+ case <-c:
+ case <-time.After(time.Second):
+ t.Error("timed out waiting for close")
+ }
+
+ // On some systems the goroutines may now be hanging.
+ // There's not much we can do about that.
+}
diff --git a/libgo/go/os/types.go b/libgo/go/os/types.go
index db78487..b0b7d8d 100644
--- a/libgo/go/os/types.go
+++ b/libgo/go/os/types.go
@@ -54,15 +54,16 @@ const (
ModeSetgid // g: setgid
ModeCharDevice // c: Unix character device, when ModeDevice is set
ModeSticky // t: sticky
+ ModeIrregular // ?: non-regular file; nothing else is known about this file
// Mask for the type bits. For regular files, none will be set.
- ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
+ ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeIrregular
ModePerm FileMode = 0777 // Unix permission bits
)
func (m FileMode) String() string {
- const str = "dalTLDpSugct"
+ const str = "dalTLDpSugct?"
var buf [32]byte // Mode is uint32.
w := 0
for i, c := range str {
diff --git a/libgo/go/os/types_windows.go b/libgo/go/os/types_windows.go
index 01d6b62..f3297c0 100644
--- a/libgo/go/os/types_windows.go
+++ b/libgo/go/os/types_windows.go
@@ -5,16 +5,30 @@
package os
import (
+ "internal/syscall/windows"
"sync"
"syscall"
"time"
+ "unsafe"
)
// A fileStat is the implementation of FileInfo returned by Stat and Lstat.
type fileStat struct {
- name string
- sys syscall.Win32FileAttributeData
- filetype uint32 // what syscall.GetFileType returns
+ name string
+
+ // from ByHandleFileInformation, Win32FileAttributeData and Win32finddata
+ FileAttributes uint32
+ CreationTime syscall.Filetime
+ LastAccessTime syscall.Filetime
+ LastWriteTime syscall.Filetime
+ FileSizeHigh uint32
+ FileSizeLow uint32
+
+ // from Win32finddata
+ Reserved0 uint32
+
+ // what syscall.GetFileType returns
+ filetype uint32
// used to implement SameFile
sync.Mutex
@@ -25,40 +39,160 @@ type fileStat struct {
appendNameToPath bool
}
+// newFileStatFromGetFileInformationByHandle calls GetFileInformationByHandle
+// to gather all required information about the file handle h.
+func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (fs *fileStat, err error) {
+ var d syscall.ByHandleFileInformation
+ err = syscall.GetFileInformationByHandle(h, &d)
+ if err != nil {
+ return nil, &PathError{"GetFileInformationByHandle", path, err}
+ }
+ return &fileStat{
+ name: basename(path),
+ FileAttributes: d.FileAttributes,
+ CreationTime: d.CreationTime,
+ LastAccessTime: d.LastAccessTime,
+ LastWriteTime: d.LastWriteTime,
+ FileSizeHigh: d.FileSizeHigh,
+ FileSizeLow: d.FileSizeLow,
+ vol: d.VolumeSerialNumber,
+ idxhi: d.FileIndexHigh,
+ idxlo: d.FileIndexLow,
+ // fileStat.path is used by os.SameFile to decide if it needs
+ // to fetch vol, idxhi and idxlo. But these are already set,
+ // so set fileStat.path to "" to prevent os.SameFile doing it again.
+ }, nil
+}
+
+// newFileStatFromWin32finddata copies all required information
+// from syscall.Win32finddata d into the newly created fileStat.
+func newFileStatFromWin32finddata(d *syscall.Win32finddata) *fileStat {
+ return &fileStat{
+ FileAttributes: d.FileAttributes,
+ CreationTime: d.CreationTime,
+ LastAccessTime: d.LastAccessTime,
+ LastWriteTime: d.LastWriteTime,
+ FileSizeHigh: d.FileSizeHigh,
+ FileSizeLow: d.FileSizeLow,
+ Reserved0: d.Reserved0,
+ }
+}
+
+// newFileStatFromGetFileAttributesExOrFindFirstFile calls GetFileAttributesEx
+// and FindFirstFile to gather all required information about the provided file path pathp.
+func newFileStatFromGetFileAttributesExOrFindFirstFile(path string, pathp *uint16) (*fileStat, error) {
+ // As suggested by Microsoft, use GetFileAttributes() to acquire the file information,
+ // and if it's a reparse point use FindFirstFile() to get the tag:
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363940(v=vs.85).aspx
+ // Notice that always calling FindFirstFile can create performance problems
+ // (https://golang.org/issues/19922#issuecomment-300031421)
+ var fa syscall.Win32FileAttributeData
+ err := syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa)))
+ if err == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+ // Not a symlink.
+ return &fileStat{
+ FileAttributes: fa.FileAttributes,
+ CreationTime: fa.CreationTime,
+ LastAccessTime: fa.LastAccessTime,
+ LastWriteTime: fa.LastWriteTime,
+ FileSizeHigh: fa.FileSizeHigh,
+ FileSizeLow: fa.FileSizeLow,
+ }, nil
+ }
+ // GetFileAttributesEx returns ERROR_INVALID_NAME if called
+ // for invalid file name like "*.txt". Do not attempt to call
+ // FindFirstFile with "*.txt", because FindFirstFile will
+ // succeed. So just return ERROR_INVALID_NAME instead.
+ // see https://golang.org/issue/24999 for details.
+ if errno, _ := err.(syscall.Errno); errno == windows.ERROR_INVALID_NAME {
+ return nil, &PathError{"GetFileAttributesEx", path, err}
+ }
+ // We might have symlink here. But some directories also have
+ // FileAttributes FILE_ATTRIBUTE_REPARSE_POINT bit set.
+ // For example, OneDrive directory is like that
+ // (see golang.org/issue/22579 for details).
+ // So use FindFirstFile instead to distinguish directories like
+ // OneDrive from real symlinks (see instructions described at
+ // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
+ // and in particular bits about using both FileAttributes and
+ // Reserved0 fields).
+ var fd syscall.Win32finddata
+ sh, err := syscall.FindFirstFile(pathp, &fd)
+ if err != nil {
+ return nil, &PathError{"FindFirstFile", path, err}
+ }
+ syscall.FindClose(sh)
+
+ return newFileStatFromWin32finddata(&fd), nil
+}
+
+func (fs *fileStat) updatePathAndName(name string) error {
+ fs.path = name
+ if !isAbs(fs.path) {
+ var err error
+ fs.path, err = syscall.FullPath(fs.path)
+ if err != nil {
+ return &PathError{"FullPath", name, err}
+ }
+ }
+ fs.name = basename(name)
+ return nil
+}
+
+func (fs *fileStat) isSymlink() bool {
+ // Use instructions described at
+ // https://blogs.msdn.microsoft.com/oldnewthing/20100212-00/?p=14963/
+ // to recognize whether it's a symlink.
+ if fs.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT == 0 {
+ return false
+ }
+ return fs.Reserved0 == syscall.IO_REPARSE_TAG_SYMLINK ||
+ fs.Reserved0 == windows.IO_REPARSE_TAG_MOUNT_POINT
+}
+
func (fs *fileStat) Size() int64 {
- return int64(fs.sys.FileSizeHigh)<<32 + int64(fs.sys.FileSizeLow)
+ return int64(fs.FileSizeHigh)<<32 + int64(fs.FileSizeLow)
}
func (fs *fileStat) Mode() (m FileMode) {
if fs == &devNullStat {
return ModeDevice | ModeCharDevice | 0666
}
- if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
+ if fs.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY != 0 {
m |= 0444
} else {
m |= 0666
}
- if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
+ if fs.isSymlink() {
return m | ModeSymlink
}
- if fs.sys.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ if fs.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
m |= ModeDir | 0111
}
switch fs.filetype {
case syscall.FILE_TYPE_PIPE:
m |= ModeNamedPipe
case syscall.FILE_TYPE_CHAR:
- m |= ModeCharDevice
+ m |= ModeDevice | ModeCharDevice
}
return m
}
func (fs *fileStat) ModTime() time.Time {
- return time.Unix(0, fs.sys.LastWriteTime.Nanoseconds())
+ return time.Unix(0, fs.LastWriteTime.Nanoseconds())
}
// Sys returns syscall.Win32FileAttributeData for file fs.
-func (fs *fileStat) Sys() interface{} { return &fs.sys }
+func (fs *fileStat) Sys() interface{} {
+ return &syscall.Win32FileAttributeData{
+ FileAttributes: fs.FileAttributes,
+ CreationTime: fs.CreationTime,
+ LastAccessTime: fs.LastAccessTime,
+ LastWriteTime: fs.LastWriteTime,
+ FileSizeHigh: fs.FileSizeHigh,
+ FileSizeLow: fs.FileSizeLow,
+ }
+}
func (fs *fileStat) loadFileId() error {
fs.Lock()
diff --git a/libgo/go/os/user/cgo_lookup_unix.go b/libgo/go/os/user/cgo_lookup_unix.go
index 6c815b4..722e95b 100644
--- a/libgo/go/os/user/cgo_lookup_unix.go
+++ b/libgo/go/os/user/cgo_lookup_unix.go
@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// +build aix darwin dragonfly freebsd !android,linux netbsd openbsd solaris
-// +build cgo
+// +build cgo,!osusergo
package user
diff --git a/libgo/go/os/user/cgo_unix_test.go b/libgo/go/os/user/cgo_unix_test.go
index 6741118..1d341aa 100644
--- a/libgo/go/os/user/cgo_unix_test.go
+++ b/libgo/go/os/user/cgo_unix_test.go
@@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
-// +build cgo
+// +build cgo,!osusergo
package user
diff --git a/libgo/go/os/user/listgroups_solaris.go b/libgo/go/os/user/listgroups_solaris.go
index 28a8a78..f3cbf6c 100644
--- a/libgo/go/os/user/listgroups_solaris.go
+++ b/libgo/go/os/user/listgroups_solaris.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build cgo
+// +build cgo,!osusergo
// Even though this file requires no C, it is used to provide a
// listGroup stub because all the other Solaris calls work. Otherwise,
diff --git a/libgo/go/os/user/listgroups_unix.go b/libgo/go/os/user/listgroups_unix.go
index b142e2b..63f73e6 100644
--- a/libgo/go/os/user/listgroups_unix.go
+++ b/libgo/go/os/user/listgroups_unix.go
@@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
// +build dragonfly darwin freebsd !android,linux netbsd openbsd
+// +build cgo,!osusergo
package user
diff --git a/libgo/go/os/user/lookup_stubs.go b/libgo/go/os/user/lookup_stubs.go
index d23870f..f7d138f 100644
--- a/libgo/go/os/user/lookup_stubs.go
+++ b/libgo/go/os/user/lookup_stubs.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build !cgo,!windows,!plan9 android
+// +build !cgo,!windows,!plan9 android osusergo,!windows,!plan9
package user
diff --git a/libgo/go/os/user/lookup_unix.go b/libgo/go/os/user/lookup_unix.go
index 5f34ba8..c4e9ba1 100644
--- a/libgo/go/os/user/lookup_unix.go
+++ b/libgo/go/os/user/lookup_unix.go
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build darwin dragonfly freebsd !android,linux nacl netbsd openbsd solaris
-// +build !cgo
+// +build darwin dragonfly freebsd js,wasm !android,linux nacl netbsd openbsd solaris
+// +build !cgo osusergo
package user
diff --git a/libgo/go/os/user/lookup_windows.go b/libgo/go/os/user/lookup_windows.go
index 4e36a5c..7499f6a 100644
--- a/libgo/go/os/user/lookup_windows.go
+++ b/libgo/go/os/user/lookup_windows.go
@@ -5,16 +5,13 @@
package user
import (
- "errors"
"fmt"
+ "internal/syscall/windows"
+ "internal/syscall/windows/registry"
"syscall"
"unsafe"
)
-func init() {
- groupImplemented = false
-}
-
func isDomainJoined() (bool, error) {
var domain *uint16
var status uint32
@@ -72,19 +69,120 @@ func lookupFullName(domain, username, domainAndUser string) (string, error) {
return username, nil
}
-func newUser(usid *syscall.SID, gid, dir string) (*User, error) {
+// getProfilesDirectory retrieves the path to the root directory
+// where user profiles are stored.
+func getProfilesDirectory() (string, error) {
+ n := uint32(100)
+ for {
+ b := make([]uint16, n)
+ e := windows.GetProfilesDirectory(&b[0], &n)
+ if e == nil {
+ return syscall.UTF16ToString(b), nil
+ }
+ if e != syscall.ERROR_INSUFFICIENT_BUFFER {
+ return "", e
+ }
+ if n <= uint32(len(b)) {
+ return "", e
+ }
+ }
+}
+
+// lookupUsernameAndDomain obtains the username and domain for usid.
+func lookupUsernameAndDomain(usid *syscall.SID) (username, domain string, e error) {
username, domain, t, e := usid.LookupAccount("")
if e != nil {
- return nil, e
+ return "", "", e
}
if t != syscall.SidTypeUser {
- return nil, fmt.Errorf("user: should be user account type, not %d", t)
+ return "", "", fmt.Errorf("user: should be user account type, not %d", t)
}
- domainAndUser := domain + `\` + username
- uid, e := usid.String()
+ return username, domain, nil
+}
+
+// findHomeDirInRegistry finds the user home path based on the uid.
+func findHomeDirInRegistry(uid string) (dir string, e error) {
+ k, e := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\`+uid, registry.QUERY_VALUE)
if e != nil {
- return nil, e
+ return "", e
+ }
+ defer k.Close()
+ dir, _, e = k.GetStringValue("ProfileImagePath")
+ if e != nil {
+ return "", e
+ }
+ return dir, nil
+}
+
+// lookupGroupName accepts the name of a group and retrieves the group SID.
+func lookupGroupName(groupname string) (string, error) {
+ sid, _, t, e := syscall.LookupSID("", groupname)
+ if e != nil {
+ return "", e
+ }
+ // https://msdn.microsoft.com/en-us/library/cc245478.aspx#gt_0387e636-5654-4910-9519-1f8326cf5ec0
+ // SidTypeAlias should also be treated as a group type next to SidTypeGroup
+ // and SidTypeWellKnownGroup:
+ // "alias object -> resource group: A group object..."
+ //
+ // Tests show that "Administrators" can be considered of type SidTypeAlias.
+ if t != syscall.SidTypeGroup && t != syscall.SidTypeWellKnownGroup && t != syscall.SidTypeAlias {
+ return "", fmt.Errorf("lookupGroupName: should be group account type, not %d", t)
}
+ return sid.String()
+}
+
+// listGroupsForUsernameAndDomain accepts username and domain and retrieves
+// a SID list of the local groups where this user is a member.
+func listGroupsForUsernameAndDomain(username, domain string) ([]string, error) {
+ // Check if both the domain name and user should be used.
+ var query string
+ joined, err := isDomainJoined()
+ if err == nil && joined && len(domain) != 0 {
+ query = domain + `\` + username
+ } else {
+ query = username
+ }
+ q, err := syscall.UTF16PtrFromString(query)
+ if err != nil {
+ return nil, err
+ }
+ var p0 *byte
+ var entriesRead, totalEntries uint32
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370655(v=vs.85).aspx
+ // NetUserGetLocalGroups() would return a list of LocalGroupUserInfo0
+ // elements which hold the names of local groups where the user participates.
+ // The list does not follow any sorting order.
+ //
+ // If no groups can be found for this user, NetUserGetLocalGroups() should
+ // always return the SID of a single group called "None", which
+ // also happens to be the primary group for the local user.
+ err = windows.NetUserGetLocalGroups(nil, q, 0, windows.LG_INCLUDE_INDIRECT, &p0, windows.MAX_PREFERRED_LENGTH, &entriesRead, &totalEntries)
+ if err != nil {
+ return nil, err
+ }
+ defer syscall.NetApiBufferFree(p0)
+ if entriesRead == 0 {
+ return nil, fmt.Errorf("listGroupsForUsernameAndDomain: NetUserGetLocalGroups() returned an empty list for domain: %s, username: %s", domain, username)
+ }
+ entries := (*[1024]windows.LocalGroupUserInfo0)(unsafe.Pointer(p0))[:entriesRead]
+ var sids []string
+ for _, entry := range entries {
+ if entry.Name == nil {
+ continue
+ }
+ name := syscall.UTF16ToString((*[1024]uint16)(unsafe.Pointer(entry.Name))[:])
+ sid, err := lookupGroupName(name)
+ if err != nil {
+ return nil, err
+ }
+ sids = append(sids, sid)
+ }
+ return sids, nil
+}
+
+func newUser(uid, gid, dir, username, domain string) (*User, error) {
+ domainAndUser := domain + `\` + username
name, e := lookupFullName(domain, username, domainAndUser)
if e != nil {
return nil, e
@@ -113,6 +211,10 @@ func current() (*User, error) {
if e != nil {
return nil, e
}
+ uid, e := u.User.Sid.String()
+ if e != nil {
+ return nil, e
+ }
gid, e := pg.PrimaryGroup.String()
if e != nil {
return nil, e
@@ -121,17 +223,105 @@ func current() (*User, error) {
if e != nil {
return nil, e
}
- return newUser(u.User.Sid, gid, dir)
+ username, domain, e := lookupUsernameAndDomain(u.User.Sid)
+ if e != nil {
+ return nil, e
+ }
+ return newUser(uid, gid, dir, username, domain)
}
-// BUG(brainman): Lookup and LookupId functions do not set
-// Gid and HomeDir fields in the User struct returned on windows.
+// lookupUserPrimaryGroup obtains the primary group SID for a user using this method:
+// https://support.microsoft.com/en-us/help/297951/how-to-use-the-primarygroupid-attribute-to-find-the-primary-group-for
+// The method follows this formula: domainRID + "-" + primaryGroupRID
+func lookupUserPrimaryGroup(username, domain string) (string, error) {
+ // get the domain RID
+ sid, _, t, e := syscall.LookupSID("", domain)
+ if e != nil {
+ return "", e
+ }
+ if t != syscall.SidTypeDomain {
+ return "", fmt.Errorf("lookupUserPrimaryGroup: should be domain account type, not %d", t)
+ }
+ domainRID, e := sid.String()
+ if e != nil {
+ return "", e
+ }
+ // If the user has joined a domain use the RID of the default primary group
+ // called "Domain Users":
+ // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems
+ // SID: S-1-5-21domain-513
+ //
+ // The correct way to obtain the primary group of a domain user is
+ // probing the user primaryGroupID attribute in the server Active Directory:
+ // https://msdn.microsoft.com/en-us/library/ms679375(v=vs.85).aspx
+ //
+ // Note that the primary group of domain users should not be modified
+ // on Windows for performance reasons, even if it's possible to do that.
+ // The .NET Developer's Guide to Directory Services Programming - Page 409
+ // https://books.google.bg/books?id=kGApqjobEfsC&lpg=PA410&ots=p7oo-eOQL7&dq=primary%20group%20RID&hl=bg&pg=PA409#v=onepage&q&f=false
+ joined, err := isDomainJoined()
+ if err == nil && joined {
+ return domainRID + "-513", nil
+ }
+ // For non-domain users call NetUserGetInfo() with level 4, which
+ // in this case would not have any network overhead.
+ // The primary group should not change from RID 513 here either
+ // but the group will be called "None" instead:
+ // https://www.adampalmer.me/iodigitalsec/2013/08/10/windows-null-session-enumeration/
+ // "Group 'None' (RID: 513)"
+ u, e := syscall.UTF16PtrFromString(username)
+ if e != nil {
+ return "", e
+ }
+ d, e := syscall.UTF16PtrFromString(domain)
+ if e != nil {
+ return "", e
+ }
+ var p *byte
+ e = syscall.NetUserGetInfo(d, u, 4, &p)
+ if e != nil {
+ return "", e
+ }
+ defer syscall.NetApiBufferFree(p)
+ i := (*windows.UserInfo4)(unsafe.Pointer(p))
+ return fmt.Sprintf("%s-%d", domainRID, i.PrimaryGroupID), nil
+}
func newUserFromSid(usid *syscall.SID) (*User, error) {
- // TODO(brainman): do not know where to get gid and dir fields
- gid := "unknown"
- dir := "Unknown directory"
- return newUser(usid, gid, dir)
+ username, domain, e := lookupUsernameAndDomain(usid)
+ if e != nil {
+ return nil, e
+ }
+ gid, e := lookupUserPrimaryGroup(username, domain)
+ if e != nil {
+ return nil, e
+ }
+ uid, e := usid.String()
+ if e != nil {
+ return nil, e
+ }
+ // If this user has logged in at least once their home path should be stored
+ // in the registry under the specified SID. References:
+ // https://social.technet.microsoft.com/wiki/contents/articles/13895.how-to-remove-a-corrupted-user-profile-from-the-registry.aspx
+ // https://support.asperasoft.com/hc/en-us/articles/216127438-How-to-delete-Windows-user-profiles
+ //
+ // The registry is the most reliable way to find the home path as the user
+ // might have decided to move it outside of the default location,
+ // (e.g. C:\users). Reference:
+ // https://answers.microsoft.com/en-us/windows/forum/windows_7-security/how-do-i-set-a-home-directory-outside-cusers-for-a/aed68262-1bf4-4a4d-93dc-7495193a440f
+ dir, e := findHomeDirInRegistry(uid)
+ if e != nil {
+ // If the home path does not exist in the registry, the user might
+ // have not logged in yet; fall back to using getProfilesDirectory().
+ // Find the username based on a SID and append that to the result of
+ // getProfilesDirectory(). The domain is not relevant here.
+ dir, e = getProfilesDirectory()
+ if e != nil {
+ return nil, e
+ }
+ dir += `\` + username
+ }
+ return newUser(uid, gid, dir, username, domain)
}
func lookupUser(username string) (*User, error) {
@@ -154,13 +344,47 @@ func lookupUserId(uid string) (*User, error) {
}
func lookupGroup(groupname string) (*Group, error) {
- return nil, errors.New("user: LookupGroup not implemented on windows")
+ sid, err := lookupGroupName(groupname)
+ if err != nil {
+ return nil, err
+ }
+ return &Group{Name: groupname, Gid: sid}, nil
}
-func lookupGroupId(string) (*Group, error) {
- return nil, errors.New("user: LookupGroupId not implemented on windows")
+func lookupGroupId(gid string) (*Group, error) {
+ sid, err := syscall.StringToSid(gid)
+ if err != nil {
+ return nil, err
+ }
+ groupname, _, t, err := sid.LookupAccount("")
+ if err != nil {
+ return nil, err
+ }
+ if t != syscall.SidTypeGroup && t != syscall.SidTypeWellKnownGroup && t != syscall.SidTypeAlias {
+ return nil, fmt.Errorf("lookupGroupId: should be group account type, not %d", t)
+ }
+ return &Group{Name: groupname, Gid: gid}, nil
}
-func listGroups(*User) ([]string, error) {
- return nil, errors.New("user: GroupIds not implemented on windows")
+func listGroups(user *User) ([]string, error) {
+ sid, err := syscall.StringToSid(user.Uid)
+ if err != nil {
+ return nil, err
+ }
+ username, domain, err := lookupUsernameAndDomain(sid)
+ if err != nil {
+ return nil, err
+ }
+ sids, err := listGroupsForUsernameAndDomain(username, domain)
+ if err != nil {
+ return nil, err
+ }
+ // Add the primary group of the user to the list if it is not already there.
+ // This is done only to comply with the POSIX concept of a primary group.
+ for _, sid := range sids {
+ if sid == user.Gid {
+ return sids, nil
+ }
+ }
+ return append(sids, user.Gid), nil
}
diff --git a/libgo/go/os/user/user.go b/libgo/go/os/user/user.go
index ad61992..1f733b8 100644
--- a/libgo/go/os/user/user.go
+++ b/libgo/go/os/user/user.go
@@ -2,7 +2,18 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package user allows user account lookups by name or id.
+/*
+Package user allows user account lookups by name or id.
+
+For most Unix systems, this package has two internal implementations of
+resolving user and group ids to names. One is written in pure Go and
+parses /etc/passwd and /etc/group. The other is cgo-based and relies on
+the standard C library (libc) routines such as getpwuid_r and getgrnam_r.
+
+When cgo is available, cgo-based (libc-backed) code is used by default.
+This can be overriden by using osusergo build tag, which enforces
+the pure Go implementation.
+*/
package user
import (
diff --git a/libgo/go/os/user/user_test.go b/libgo/go/os/user/user_test.go
index b3aeed8..8fd760e 100644
--- a/libgo/go/os/user/user_test.go
+++ b/libgo/go/os/user/user_test.go
@@ -5,6 +5,8 @@
package user
import (
+ "internal/testenv"
+ "os"
"runtime"
"testing"
)
@@ -16,6 +18,20 @@ func checkUser(t *testing.T) {
}
func TestCurrent(t *testing.T) {
+ // The Go builders (in particular the ones using containers)
+ // often have minimal environments without $HOME or $USER set,
+ // which breaks Current which relies on those working as a
+ // fallback.
+ // TODO: we should fix that (Issue 24884) and remove these
+ // workarounds.
+ if testenv.Builder() != "" && runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
+ if os.Getenv("HOME") == "" {
+ os.Setenv("HOME", "/tmp")
+ }
+ if os.Getenv("USER") == "" {
+ os.Setenv("USER", "gobuilder")
+ }
+ }
u, err := Current()
if err != nil {
t.Fatalf("Current: %v (got %#v)", err, u)
@@ -44,16 +60,12 @@ func compare(t *testing.T, want, got *User) {
if want.Name != got.Name {
t.Errorf("got Name=%q; want %q", got.Name, want.Name)
}
- // TODO(brainman): fix it once we know how.
- if runtime.GOOS == "windows" {
- t.Skip("skipping Gid and HomeDir comparisons")
+ if want.HomeDir != got.HomeDir {
+ t.Errorf("got HomeDir=%q; want %q", got.HomeDir, want.HomeDir)
}
if want.Gid != got.Gid {
t.Errorf("got Gid=%q; want %q", got.Gid, want.Gid)
}
- if want.HomeDir != got.HomeDir {
- t.Errorf("got HomeDir=%q; want %q", got.HomeDir, want.HomeDir)
- }
}
func TestLookup(t *testing.T) {
diff --git a/libgo/go/os/wait_unimp.go b/libgo/go/os/wait_unimp.go
index 98243b5..d070604 100644
--- a/libgo/go/os/wait_unimp.go
+++ b/libgo/go/os/wait_unimp.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// +build aix darwin dragonfly nacl netbsd openbsd solaris
+// +build aix darwin dragonfly js,wasm nacl netbsd openbsd solaris
package os