From dd931d9b48647e898dc80927c532ae93cc09e192 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 24 Sep 2018 21:46:21 +0000 Subject: 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 --- libgo/go/os/dir_unix.go | 2 +- libgo/go/os/env.go | 24 ++- libgo/go/os/env_test.go | 29 +++- libgo/go/os/error_posix.go | 2 +- libgo/go/os/error_unix.go | 2 +- libgo/go/os/error_unix_test.go | 2 +- libgo/go/os/example_test.go | 18 +++ libgo/go/os/exec.go | 4 +- libgo/go/os/exec/exec.go | 10 +- libgo/go/os/exec/exec_test.go | 19 ++- libgo/go/os/exec/lp_js.go | 23 +++ libgo/go/os/exec/lp_plan9.go | 4 +- libgo/go/os/exec/lp_unix.go | 4 +- libgo/go/os/exec/lp_windows.go | 4 +- libgo/go/os/exec_posix.go | 2 +- libgo/go/os/exec_unix.go | 2 +- libgo/go/os/executable_darwin.go | 5 + libgo/go/os/executable_procfs.go | 2 +- libgo/go/os/executable_solaris.go | 11 +- libgo/go/os/fifo_test.go | 112 ++++++++++++++ libgo/go/os/file.go | 68 ++++++++- libgo/go/os/file_plan9.go | 14 +- libgo/go/os/file_posix.go | 7 +- libgo/go/os/file_unix.go | 47 ++++-- libgo/go/os/os_test.go | 86 ++++++++--- libgo/go/os/os_unix_test.go | 74 +++++++++ libgo/go/os/path.go | 65 +++++--- libgo/go/os/path_plan9.go | 4 + libgo/go/os/path_unix.go | 6 +- libgo/go/os/path_windows.go | 11 ++ libgo/go/os/path_windows_test.go | 30 ++++ libgo/go/os/pipe2_bsd.go | 22 +++ libgo/go/os/pipe_bsd.go | 2 +- libgo/go/os/pipe_freebsd.go | 20 --- libgo/go/os/pipe_test.go | 134 +++++++++++++++- libgo/go/os/signal/signal.go | 6 + libgo/go/os/signal/signal_plan9.go | 5 + libgo/go/os/signal/signal_test.go | 59 +++++++ libgo/go/os/signal/signal_unix.go | 7 +- libgo/go/os/stat_nacl.go | 52 ------- libgo/go/os/stat_nacljs.go | 54 +++++++ libgo/go/os/stat_plan9.go | 10 +- libgo/go/os/stat_unix.go | 2 +- libgo/go/os/sys_bsd.go | 5 +- libgo/go/os/sys_darwin.go | 11 -- libgo/go/os/sys_freebsd.go | 10 -- libgo/go/os/sys_js.go | 11 ++ libgo/go/os/sys_linux.go | 37 ++++- libgo/go/os/sys_plan9.go | 2 - libgo/go/os/sys_unix.go | 5 +- libgo/go/os/timeout_test.go | 40 +++++ libgo/go/os/types.go | 5 +- libgo/go/os/types_windows.go | 154 +++++++++++++++++-- libgo/go/os/user/cgo_lookup_unix.go | 2 +- libgo/go/os/user/cgo_unix_test.go | 2 +- libgo/go/os/user/listgroups_solaris.go | 2 +- libgo/go/os/user/listgroups_unix.go | 1 + libgo/go/os/user/lookup_stubs.go | 2 +- libgo/go/os/user/lookup_unix.go | 4 +- libgo/go/os/user/lookup_windows.go | 270 ++++++++++++++++++++++++++++++--- libgo/go/os/user/user.go | 13 +- libgo/go/os/user/user_test.go | 24 ++- libgo/go/os/wait_unimp.go | 2 +- 63 files changed, 1409 insertions(+), 259 deletions(-) create mode 100644 libgo/go/os/exec/lp_js.go create mode 100644 libgo/go/os/fifo_test.go create mode 100644 libgo/go/os/pipe2_bsd.go delete mode 100644 libgo/go/os/pipe_freebsd.go delete mode 100644 libgo/go/os/stat_nacl.go create mode 100644 libgo/go/os/stat_nacljs.go delete mode 100644 libgo/go/os/sys_darwin.go delete mode 100644 libgo/go/os/sys_freebsd.go create mode 100644 libgo/go/os/sys_js.go (limited to 'libgo/go/os') 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/pipe2_bsd.go b/libgo/go/os/pipe2_bsd.go new file mode 100644 index 0000000..0ef894b --- /dev/null +++ b/libgo/go/os/pipe2_bsd.go @@ -0,0 +1,22 @@ +// Copyright 2017 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 netbsd openbsd + +package os + +import "syscall" + +// Pipe returns a connected pair of Files; reads from r return bytes written to w. +// It returns the files and an error, if any. +func Pipe() (r *File, w *File, err error) { + var p [2]int + + e := syscall.Pipe2(p[0:], syscall.O_CLOEXEC) + if e != nil { + return nil, nil, NewSyscallError("pipe", e) + } + + return newFile(uintptr(p[0]), "|0", kindPipe), newFile(uintptr(p[1]), "|1", kindPipe), nil +} 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_freebsd.go b/libgo/go/os/pipe_freebsd.go deleted file mode 100644 index 93bd869..0000000 --- a/libgo/go/os/pipe_freebsd.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package os - -import "syscall" - -// Pipe returns a connected pair of Files; reads from r return bytes written to w. -// It returns the files and an error, if any. -func Pipe() (r *File, w *File, err error) { - var p [2]int - - e := syscall.Pipe2(p[0:], syscall.O_CLOEXEC) - if e != nil { - return nil, nil, NewSyscallError("pipe", e) - } - - return newFile(uintptr(p[0]), "|0", kindPipe), newFile(uintptr(p[1]), "|1", kindPipe), nil -} 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_nacl.go deleted file mode 100644 index 0c53f2f..0000000 --- a/libgo/go/os/stat_nacl.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package os - -import ( - "syscall" - "time" -) - -func fillFileStatFromSys(fs *fileStat, name string) { - fs.name = basename(name) - 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 { - case syscall.S_IFBLK: - fs.mode |= ModeDevice - case syscall.S_IFCHR: - fs.mode |= ModeDevice | ModeCharDevice - case syscall.S_IFDIR: - fs.mode |= ModeDir - case syscall.S_IFIFO: - fs.mode |= ModeNamedPipe - case syscall.S_IFLNK: - fs.mode |= ModeSymlink - case syscall.S_IFREG: - // nothing to do - case syscall.S_IFSOCK: - fs.mode |= ModeSocket - } - if fs.sys.Mode&syscall.S_ISGID != 0 { - fs.mode |= ModeSetgid - } - if fs.sys.Mode&syscall.S_ISUID != 0 { - fs.mode |= ModeSetuid - } - if fs.sys.Mode&syscall.S_ISVTX != 0 { - fs.mode |= ModeSticky - } -} - -func timespecToTime(sec, nsec int64) time.Time { - return time.Unix(sec, nsec) -} - -// For testing. -func atime(fi FileInfo) time.Time { - st := fi.Sys().(*syscall.Stat_t) - return timespecToTime(st.Atime, st.AtimeNsec) -} diff --git a/libgo/go/os/stat_nacljs.go b/libgo/go/os/stat_nacljs.go new file mode 100644 index 0000000..f14add8 --- /dev/null +++ b/libgo/go/os/stat_nacljs.go @@ -0,0 +1,54 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js,wasm nacl + +package os + +import ( + "syscall" + "time" +) + +func fillFileStatFromSys(fs *fileStat, name string) { + fs.name = basename(name) + 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 { + case syscall.S_IFBLK: + fs.mode |= ModeDevice + case syscall.S_IFCHR: + fs.mode |= ModeDevice | ModeCharDevice + case syscall.S_IFDIR: + fs.mode |= ModeDir + case syscall.S_IFIFO: + fs.mode |= ModeNamedPipe + case syscall.S_IFLNK: + fs.mode |= ModeSymlink + case syscall.S_IFREG: + // nothing to do + case syscall.S_IFSOCK: + fs.mode |= ModeSocket + } + if fs.sys.Mode&syscall.S_ISGID != 0 { + fs.mode |= ModeSetgid + } + if fs.sys.Mode&syscall.S_ISUID != 0 { + fs.mode |= ModeSetuid + } + if fs.sys.Mode&syscall.S_ISVTX != 0 { + fs.mode |= ModeSticky + } +} + +func timespecToTime(sec, nsec int64) time.Time { + return time.Unix(sec, nsec) +} + +// For testing. +func atime(fi FileInfo) time.Time { + st := fi.Sys().(*syscall.Stat_t) + return timespecToTime(st.Atime, st.AtimeNsec) +} diff --git a/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_freebsd.go deleted file mode 100644 index 3ec49fa..0000000 --- a/libgo/go/os/sys_freebsd.go +++ /dev/null @@ -1,10 +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 FreeBSD 8.3. -const supportsCloseOnExec bool = true diff --git a/libgo/go/os/sys_js.go b/libgo/go/os/sys_js.go new file mode 100644 index 0000000..e860654 --- /dev/null +++ b/libgo/go/os/sys_js.go @@ -0,0 +1,11 @@ +// 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. +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 -- cgit v1.1