diff options
Diffstat (limited to 'libgo/go/os')
38 files changed, 692 insertions, 337 deletions
diff --git a/libgo/go/os/dir_ios.go b/libgo/go/os/dir_ios.go deleted file mode 100644 index 8c14d89..0000000 --- a/libgo/go/os/dir_ios.go +++ /dev/null @@ -1,87 +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. - -// +build darwin -// +build arm arm64 - -package os - -import ( - "io" - "runtime" - "syscall" - "unsafe" -) - -// Auxiliary information if the File describes a directory -type dirInfo struct { - dir uintptr // Pointer to DIR structure from dirent.h -} - -func (d *dirInfo) close() { - if d.dir == 0 { - return - } - closedir(d.dir) - d.dir = 0 -} - -func (f *File) readdirnames(n int) (names []string, err error) { - if f.dirinfo == nil { - dir, call, errno := f.pfd.OpenDir() - if errno != nil { - return nil, wrapSyscallError(call, errno) - } - f.dirinfo = &dirInfo{ - dir: dir, - } - } - d := f.dirinfo - - size := n - if size <= 0 { - size = 100 - n = -1 - } - - names = make([]string, 0, size) - var dirent syscall.Dirent - var entptr uintptr - for len(names) < size { - if res := readdir_r(d.dir, uintptr(unsafe.Pointer(&dirent)), uintptr(unsafe.Pointer(&entptr))); res != 0 { - return names, wrapSyscallError("readdir", syscall.Errno(res)) - } - if entptr == 0 { // EOF - break - } - if dirent.Ino == 0 { - continue - } - name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:] - for i, c := range name { - if c == 0 { - name = name[:i] - break - } - } - // Check for useless names before allocating a string. - if string(name) == "." || string(name) == ".." { - continue - } - names = append(names, string(name)) - runtime.KeepAlive(f) - } - if n >= 0 && len(names) == 0 { - return names, io.EOF - } - return names, nil -} - -// Implemented in syscall/syscall_darwin.go. - -//go:linkname closedir syscall.closedir -func closedir(dir uintptr) (err error) - -//go:linkname readdir_r syscall.readdir_r -func readdir_r(dir, entry, result uintptr) (res int) diff --git a/libgo/go/os/env_default.go b/libgo/go/os/env_default.go new file mode 100644 index 0000000..c11ccce --- /dev/null +++ b/libgo/go/os/env_default.go @@ -0,0 +1,13 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !windows + +package os + +import "syscall" + +func environForSysProcAttr(sys *syscall.SysProcAttr) ([]string, error) { + return Environ(), nil +} diff --git a/libgo/go/os/env_windows.go b/libgo/go/os/env_windows.go new file mode 100644 index 0000000..e8f647e --- /dev/null +++ b/libgo/go/os/env_windows.go @@ -0,0 +1,40 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os + +import ( + "internal/syscall/windows" + "syscall" + "unicode/utf16" + "unsafe" +) + +func environForSysProcAttr(sys *syscall.SysProcAttr) (env []string, err error) { + if sys == nil || sys.Token == 0 { + return Environ(), nil + } + var block *uint16 + err = windows.CreateEnvironmentBlock(&block, sys.Token, false) + if err != nil { + return nil, err + } + defer windows.DestroyEnvironmentBlock(block) + blockp := uintptr(unsafe.Pointer(block)) + for { + entry := (*[(1 << 30) - 1]uint16)(unsafe.Pointer(blockp))[:] + for i, v := range entry { + if v == 0 { + entry = entry[:i] + break + } + } + if len(entry) == 0 { + break + } + env = append(env, string(utf16.Decode(entry))) + blockp += 2 * (uintptr(len(entry)) + 1) + } + return +} diff --git a/libgo/go/os/error.go b/libgo/go/os/error.go index b4242a4..0c2e6a7 100644 --- a/libgo/go/os/error.go +++ b/libgo/go/os/error.go @@ -5,20 +5,37 @@ package os import ( - "errors" + "internal/oserror" "internal/poll" ) // Portable analogs of some common system call errors. +// +// Errors returned from this package may be tested against these errors +// with errors.Is. var ( - ErrInvalid = errors.New("invalid argument") // methods on File will return this error when the receiver is nil - ErrPermission = errors.New("permission denied") - ErrExist = errors.New("file already exists") - ErrNotExist = errors.New("file does not exist") - ErrClosed = errors.New("file already closed") - ErrNoDeadline = poll.ErrNoDeadline + // ErrInvalid indicates an invalid argument. + // Methods on File will return this error when the receiver is nil. + ErrInvalid = errInvalid() // "invalid argument" + + ErrPermission = errPermission() // "permission denied" + ErrExist = errExist() // "file already exists" + ErrNotExist = errNotExist() // "file does not exist" + ErrClosed = errClosed() // "file already closed" + ErrTimeout = errTimeout() // "deadline exceeded" + ErrTemporary = errTemporary() // "temporary error" + ErrNoDeadline = errNoDeadline() // "file type does not support deadline" ) +func errInvalid() error { return oserror.ErrInvalid } +func errPermission() error { return oserror.ErrPermission } +func errExist() error { return oserror.ErrExist } +func errNotExist() error { return oserror.ErrNotExist } +func errClosed() error { return oserror.ErrClosed } +func errTimeout() error { return oserror.ErrTimeout } +func errTemporary() error { return oserror.ErrTemporary } +func errNoDeadline() error { return poll.ErrNoDeadline } + type timeout interface { Timeout() bool } @@ -32,6 +49,8 @@ type PathError struct { func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } +func (e *PathError) Unwrap() error { return e.Err } + // Timeout reports whether this error represents a timeout. func (e *PathError) Timeout() bool { t, ok := e.Err.(timeout) @@ -46,6 +65,8 @@ type SyscallError struct { func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() } +func (e *SyscallError) Unwrap() error { return e.Err } + // Timeout reports whether this error represents a timeout. func (e *SyscallError) Timeout() bool { t, ok := e.Err.(timeout) @@ -66,21 +87,21 @@ func NewSyscallError(syscall string, err error) error { // that a file or directory already exists. It is satisfied by ErrExist as // well as some syscall errors. func IsExist(err error) bool { - return isExist(err) + return underlyingErrorIs(err, ErrExist) } // IsNotExist returns a boolean indicating whether the error is known to // report that a file or directory does not exist. It is satisfied by // ErrNotExist as well as some syscall errors. func IsNotExist(err error) bool { - return isNotExist(err) + return underlyingErrorIs(err, ErrNotExist) } // IsPermission returns a boolean indicating whether the error is known to // report that permission is denied. It is satisfied by ErrPermission as well // as some syscall errors. func IsPermission(err error) bool { - return isPermission(err) + return underlyingErrorIs(err, ErrPermission) } // IsTimeout returns a boolean indicating whether the error is known @@ -90,6 +111,18 @@ func IsTimeout(err error) bool { return ok && terr.Timeout() } +func underlyingErrorIs(err, target error) bool { + // Note that this function is not errors.Is: + // underlyingError only unwraps the specific error-wrapping types + // that it historically did, not all errors.Wrapper implementations. + err = underlyingError(err) + if err == target { + return true + } + e, ok := err.(interface{ Is(error) bool }) + return ok && e.Is(target) +} + // underlyingError returns the underlying error for known os error types. func underlyingError(err error) error { switch err := err.(type) { diff --git a/libgo/go/os/error_plan9.go b/libgo/go/os/error_plan9.go deleted file mode 100644 index b82bf0d..0000000 --- a/libgo/go/os/error_plan9.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package os - -func isExist(err error) bool { - return checkErrMessageContent(err, "exists", "is a directory") -} - -func isNotExist(err error) bool { - return checkErrMessageContent(err, "does not exist", "not found", - "has been removed", "no parent") -} - -func isPermission(err error) bool { - return checkErrMessageContent(err, "permission denied") -} - -// checkErrMessageContent checks if err message contains one of msgs. -func checkErrMessageContent(err error, msgs ...string) bool { - if err == nil { - return false - } - err = underlyingError(err) - for _, msg := range msgs { - if contains(err.Error(), msg) { - return true - } - } - return false -} - -// contains is a local version of strings.Contains. It knows len(sep) > 1. -func contains(s, sep string) bool { - n := len(sep) - c := sep[0] - for i := 0; i+n <= len(s); i++ { - if s[i] == c && s[i:i+n] == sep { - return true - } - } - return false -} diff --git a/libgo/go/os/error_test.go b/libgo/go/os/error_test.go index 3499cee..a03bd28 100644 --- a/libgo/go/os/error_test.go +++ b/libgo/go/os/error_test.go @@ -5,6 +5,7 @@ package os_test import ( + "errors" "fmt" "io/ioutil" "os" @@ -26,7 +27,7 @@ func TestErrIsExist(t *testing.T) { t.Fatal("Open should have failed") return } - if s := checkErrorPredicate("os.IsExist", os.IsExist, err); s != "" { + if s := checkErrorPredicate("os.IsExist", os.IsExist, err, os.ErrExist); s != "" { t.Fatal(s) return } @@ -38,7 +39,7 @@ func testErrNotExist(name string) string { f.Close() return "Open should have failed" } - if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err); s != "" { + if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, os.ErrNotExist); s != "" { return s } @@ -46,7 +47,7 @@ func testErrNotExist(name string) string { if err == nil { return "Chdir should have failed" } - if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err); s != "" { + if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, os.ErrNotExist); s != "" { return s } return "" @@ -73,10 +74,13 @@ func TestErrIsNotExist(t *testing.T) { } } -func checkErrorPredicate(predName string, pred func(error) bool, err error) string { +func checkErrorPredicate(predName string, pred func(error) bool, err, target error) string { if !pred(err) { return fmt.Sprintf("%s does not work as expected for %#v", predName, err) } + if !errors.Is(err, target) { + return fmt.Sprintf("errors.Is(%#v, %#v) = false, want true", err, target) + } return "" } @@ -107,9 +111,15 @@ func TestIsExist(t *testing.T) { if is := os.IsExist(tt.err); is != tt.is { t.Errorf("os.IsExist(%T %v) = %v, want %v", tt.err, tt.err, is, tt.is) } + if is := errors.Is(tt.err, os.ErrExist); is != tt.is { + t.Errorf("errors.Is(%T %v, os.ErrExist) = %v, want %v", tt.err, tt.err, is, tt.is) + } if isnot := os.IsNotExist(tt.err); isnot != tt.isnot { t.Errorf("os.IsNotExist(%T %v) = %v, want %v", tt.err, tt.err, isnot, tt.isnot) } + if isnot := errors.Is(tt.err, os.ErrNotExist); isnot != tt.isnot { + t.Errorf("errors.Is(%T %v, os.ErrNotExist) = %v, want %v", tt.err, tt.err, isnot, tt.isnot) + } } } @@ -129,6 +139,9 @@ func TestIsPermission(t *testing.T) { if got := os.IsPermission(tt.err); got != tt.want { t.Errorf("os.IsPermission(%#v) = %v; want %v", tt.err, got, tt.want) } + if got := errors.Is(tt.err, os.ErrPermission); got != tt.want { + t.Errorf("errors.Is(%#v, os.ErrPermission) = %v; want %v", tt.err, got, tt.want) + } } } @@ -155,3 +168,10 @@ func TestErrPathNUL(t *testing.T) { t.Fatal("Open should have failed") } } + +func TestPathErrorUnwrap(t *testing.T) { + pe := &os.PathError{Err: os.ErrInvalid} + if !errors.Is(pe, os.ErrInvalid) { + t.Error("errors.Is failed, wanted success") + } +} diff --git a/libgo/go/os/error_unix.go b/libgo/go/os/error_unix.go deleted file mode 100644 index 7801057..0000000 --- a/libgo/go/os/error_unix.go +++ /dev/null @@ -1,24 +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. - -// +build aix darwin dragonfly freebsd hurd js,wasm linux nacl netbsd openbsd solaris - -package os - -import "syscall" - -func isExist(err error) bool { - err = underlyingError(err) - return err == syscall.EEXIST || err == syscall.ENOTEMPTY || err == ErrExist -} - -func isNotExist(err error) bool { - err = underlyingError(err) - return err == syscall.ENOENT || err == ErrNotExist -} - -func isPermission(err error) bool { - err = underlyingError(err) - return err == syscall.EACCES || err == syscall.EPERM || err == ErrPermission -} diff --git a/libgo/go/os/error_windows.go b/libgo/go/os/error_windows.go deleted file mode 100644 index 02593b5..0000000 --- a/libgo/go/os/error_windows.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package os - -import "syscall" - -func isExist(err error) bool { - err = underlyingError(err) - return err == syscall.ERROR_ALREADY_EXISTS || - err == syscall.ERROR_DIR_NOT_EMPTY || - err == syscall.ERROR_FILE_EXISTS || err == ErrExist -} - -const _ERROR_BAD_NETPATH = syscall.Errno(53) - -func isNotExist(err error) bool { - err = underlyingError(err) - return err == syscall.ERROR_FILE_NOT_FOUND || - err == _ERROR_BAD_NETPATH || - err == syscall.ERROR_PATH_NOT_FOUND || err == ErrNotExist -} - -func isPermission(err error) bool { - err = underlyingError(err) - return err == syscall.ERROR_ACCESS_DENIED || err == ErrPermission -} diff --git a/libgo/go/os/example_test.go b/libgo/go/os/example_test.go index 8b6566e..822886f 100644 --- a/libgo/go/os/example_test.go +++ b/libgo/go/os/example_test.go @@ -28,6 +28,7 @@ func ExampleOpenFile_append() { log.Fatal(err) } if _, err := f.Write([]byte("appended some data\n")); err != nil { + f.Close() // ignore error; Write error takes precedence log.Fatal(err) } if err := f.Close(); err != nil { diff --git a/libgo/go/os/exec/bench_test.go b/libgo/go/os/exec/bench_test.go new file mode 100644 index 0000000..9a94001 --- /dev/null +++ b/libgo/go/os/exec/bench_test.go @@ -0,0 +1,23 @@ +// Copyright 2019 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 exec + +import ( + "testing" +) + +func BenchmarkExecHostname(b *testing.B) { + b.ReportAllocs() + path, err := LookPath("hostname") + if err != nil { + b.Fatalf("could not find hostname: %v", err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := Command(path).Run(); err != nil { + b.Fatalf("hostname: %v", err) + } + } +} diff --git a/libgo/go/os/exec/exec.go b/libgo/go/os/exec/exec.go index 1aa3ab9..17ef003 100644 --- a/libgo/go/os/exec/exec.go +++ b/libgo/go/os/exec/exec.go @@ -47,6 +47,8 @@ func (e *Error) Error() string { return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error() } +func (e *Error) Unwrap() error { return e.Err } + // Cmd represents an external command being prepared or run. // // A Cmd cannot be reused after calling its Run, Output or CombinedOutput @@ -71,6 +73,8 @@ type Cmd struct { // environment. // If Env contains duplicate environment keys, only the last // value in the slice for each duplicate key is used. + // As a special case on Windows, SYSTEMROOT is always added if + // missing and not explicitly set to the empty string. Env []string // Dir specifies the working directory of the command. @@ -190,6 +194,25 @@ func CommandContext(ctx context.Context, name string, arg ...string) *Cmd { return cmd } +// String returns a human-readable description of c. +// It is intended only for debugging. +// In particular, it is not suitable for use as input to a shell. +// The output of String may vary across Go releases. +func (c *Cmd) String() string { + if c.lookPathErr != nil { + // failed to resolve path; report the original requested path (plus args) + return strings.Join(c.Args, " ") + } + // report the exact executable path (plus args) + b := new(strings.Builder) + b.WriteString(c.Path) + for _, a := range c.Args[1:] { + b.WriteByte(' ') + b.WriteString(a) + } + return b.String() +} + // interfaceEqual protects against panics from doing equality tests on // two interfaces with non-comparable underlying types. func interfaceEqual(a, b interface{}) bool { @@ -376,6 +399,7 @@ func (c *Cmd) Start() error { } } + c.childFiles = make([]*os.File, 0, 3+len(c.ExtraFiles)) type F func(*Cmd) (*os.File, error) for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} { fd, err := setupFd(c) @@ -392,7 +416,7 @@ func (c *Cmd) Start() error { c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{ Dir: c.Dir, Files: c.childFiles, - Env: dedupEnv(c.envv()), + Env: addCriticalEnv(dedupEnv(c.envv())), Sys: c.SysProcAttr, }) if err != nil { @@ -403,11 +427,14 @@ func (c *Cmd) Start() error { c.closeDescriptors(c.closeAfterStart) - c.errch = make(chan error, len(c.goroutine)) - for _, fn := range c.goroutine { - go func(fn func() error) { - c.errch <- fn() - }(fn) + // Don't allocate the channel unless there are goroutines to fire. + if len(c.goroutine) > 0 { + c.errch = make(chan error, len(c.goroutine)) + for _, fn := range c.goroutine { + go func(fn func() error) { + c.errch <- fn() + }(fn) + } } if c.ctx != nil { @@ -713,7 +740,7 @@ func dedupEnv(env []string) []string { // If caseInsensitive is true, the case of keys is ignored. func dedupEnvCase(caseInsensitive bool, env []string) []string { out := make([]string, 0, len(env)) - saw := map[string]int{} // key => index into out + saw := make(map[string]int, len(env)) // key => index into out for _, kv := range env { eq := strings.Index(kv, "=") if eq < 0 { @@ -733,3 +760,24 @@ func dedupEnvCase(caseInsensitive bool, env []string) []string { } return out } + +// addCriticalEnv adds any critical environment variables that are required +// (or at least almost always required) on the operating system. +// Currently this is only used for Windows. +func addCriticalEnv(env []string) []string { + if runtime.GOOS != "windows" { + return env + } + for _, kv := range env { + eq := strings.Index(kv, "=") + if eq < 0 { + continue + } + k := kv[:eq] + if strings.EqualFold(k, "SYSTEMROOT") { + // We already have it. + return env + } + } + return append(env, "SYSTEMROOT="+os.Getenv("SYSTEMROOT")) +} diff --git a/libgo/go/os/exec/exec_posix_test.go b/libgo/go/os/exec/exec_posix_test.go index 46799cd..d4d67ac 100644 --- a/libgo/go/os/exec/exec_posix_test.go +++ b/libgo/go/os/exec/exec_posix_test.go @@ -8,6 +8,7 @@ package exec_test import ( "os/user" + "runtime" "strconv" "syscall" "testing" @@ -15,6 +16,10 @@ import ( ) func TestCredentialNoSetGroups(t *testing.T) { + if runtime.GOOS == "android" { + t.Skip("unsupported on Android") + } + u, err := user.Current() if err != nil { t.Fatalf("error getting current user: %v", err) diff --git a/libgo/go/os/exec/exec_test.go b/libgo/go/os/exec/exec_test.go index b7cc9da..cfbb87a 100644 --- a/libgo/go/os/exec/exec_test.go +++ b/libgo/go/os/exec/exec_test.go @@ -695,6 +695,9 @@ func TestExtraFilesRace(t *testing.T) { } for i := 0; i < 10; i++ { + if testing.Short() && i >= 3 { + break + } la := listen() ca := helperCommand(t, "describefiles") ca.ExtraFiles = []*os.File{listenerFile(la)} @@ -835,7 +838,7 @@ func TestHelperProcess(*testing.T) { // the cloned file descriptors that result from opening // /dev/urandom. // https://golang.org/issue/3955 - case "solaris": + case "illumos", "solaris": // TODO(aram): This fails on Solaris because libc opens // its own files, as it sees fit. Darwin does the same, // see: https://golang.org/issue/2603 @@ -1154,3 +1157,56 @@ func TestDedupEnvEcho(t *testing.T) { t.Errorf("output = %q; want %q", got, want) } } + +func TestString(t *testing.T) { + echoPath, err := exec.LookPath("echo") + if err != nil { + t.Skip(err) + } + tests := [...]struct { + path string + args []string + want string + }{ + {"echo", nil, echoPath}, + {"echo", []string{"a"}, echoPath + " a"}, + {"echo", []string{"a", "b"}, echoPath + " a b"}, + } + for _, test := range tests { + cmd := exec.Command(test.path, test.args...) + if got := cmd.String(); got != test.want { + t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want) + } + } +} + +func TestStringPathNotResolved(t *testing.T) { + _, err := exec.LookPath("makemeasandwich") + if err == nil { + t.Skip("wow, thanks") + } + cmd := exec.Command("makemeasandwich", "-lettuce") + want := "makemeasandwich -lettuce" + if got := cmd.String(); got != want { + t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want) + } +} + +// start a child process without the user code explicitly starting +// with a copy of the parent's. (The Windows SYSTEMROOT issue: Issue +// 25210) +func TestChildCriticalEnv(t *testing.T) { + testenv.MustHaveExec(t) + if runtime.GOOS != "windows" { + t.Skip("only testing on Windows") + } + cmd := helperCommand(t, "echoenv", "SYSTEMROOT") + cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(string(out)) == "" { + t.Error("no SYSTEMROOT found") + } +} diff --git a/libgo/go/os/exec_plan9.go b/libgo/go/os/exec_plan9.go index bab16cc..b0abf74 100644 --- a/libgo/go/os/exec_plan9.go +++ b/libgo/go/os/exec_plan9.go @@ -27,6 +27,7 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e Sys: attr.Sys, } + sysattr.Files = make([]uintptr, 0, len(attr.Files)) for _, f := range attr.Files { sysattr.Files = append(sysattr.Files, f.Fd()) } diff --git a/libgo/go/os/exec_posix.go b/libgo/go/os/exec_posix.go index 4b1902f..bb47e83 100644 --- a/libgo/go/os/exec_posix.go +++ b/libgo/go/os/exec_posix.go @@ -38,8 +38,12 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e Sys: attr.Sys, } if sysattr.Env == nil { - sysattr.Env = Environ() + sysattr.Env, err = environForSysProcAttr(sysattr.Sys) + if err != nil { + return nil, err + } } + sysattr.Files = make([]uintptr, 0, len(attr.Files)) for _, f := range attr.Files { sysattr.Files = append(sysattr.Files, f.Fd()) } diff --git a/libgo/go/os/export_test.go b/libgo/go/os/export_test.go index d735aee..d17d5e62 100644 --- a/libgo/go/os/export_test.go +++ b/libgo/go/os/export_test.go @@ -8,3 +8,5 @@ package os var Atime = atime var LstatP = &lstat +var ErrWriteAtInAppendMode = errWriteAtInAppendMode +var RemoveAllTestHook = &removeAllTestHook diff --git a/libgo/go/os/export_unix_test.go b/libgo/go/os/export_unix_test.go new file mode 100644 index 0000000..032b1a9 --- /dev/null +++ b/libgo/go/os/export_unix_test.go @@ -0,0 +1,9 @@ +// Copyright 2019 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 aix darwin dragonfly freebsd js,wasm linux nacl netbsd openbsd solaris + +package os + +var SplitPath = splitPath diff --git a/libgo/go/os/file.go b/libgo/go/os/file.go index fdead63..96df3fb 100644 --- a/libgo/go/os/file.go +++ b/libgo/go/os/file.go @@ -98,6 +98,10 @@ func (e *LinkError) Error() string { return e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error() } +func (e *LinkError) Unwrap() error { + return e.Err +} + // Read reads up to len(b) bytes from the File. // It returns the number of bytes read and any error encountered. // At end of file, Read returns 0, io.EOF. @@ -159,13 +163,20 @@ func (f *File) Write(b []byte) (n int, err error) { return n, err } +var errWriteAtInAppendMode = errors.New("os: invalid use of WriteAt on file opened with O_APPEND") + // WriteAt writes len(b) bytes to the File starting at byte offset off. // It returns the number of bytes written and an error, if any. // WriteAt returns a non-nil error when n != len(b). +// +// If file was opened with the O_APPEND flag, WriteAt returns an error. func (f *File) WriteAt(b []byte, off int64) (n int, err error) { if err := f.checkValid("write"); err != nil { return 0, err } + if f.appendMode { + return 0, errWriteAtInAppendMode + } if off < 0 { return 0, &PathError{"writeat", f.name, errors.New("negative offset")} @@ -265,10 +276,10 @@ func Open(name string) (*File, error) { return OpenFile(name, O_RDONLY, 0) } -// Create creates the named file with mode 0666 (before umask), truncating -// it if it already exists. If successful, methods on the returned -// File can be used for I/O; the associated file descriptor has mode -// O_RDWR. +// Create creates or truncates the named file. If the file already exists, +// it is truncated. If the file does not exist, it is created with mode 0666 +// (before umask). If successful, methods on the returned File can +// be used for I/O; the associated file descriptor has mode O_RDWR. // If there is an error, it will be of type *PathError. func Create(name string) (*File, error) { return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) @@ -276,12 +287,19 @@ func Create(name string) (*File, error) { // OpenFile is the generalized open call; most users will use Open // or Create instead. It opens the named file with specified flag -// (O_RDONLY etc.) and perm (before umask), if applicable. If successful, +// (O_RDONLY etc.). If the file does not exist, and the O_CREATE flag +// is passed, it is created with mode perm (before umask). If successful, // methods on the returned File can be used for I/O. // If there is an error, it will be of type *PathError. func OpenFile(name string, flag int, perm FileMode) (*File, error) { testlog.Open(name) - return openFileNolog(name, flag, perm) + f, err := openFileNolog(name, flag, perm) + if err != nil { + return nil, err + } + f.appendMode = flag&O_APPEND != 0 + + return f, nil } // lstat is overridden in tests. @@ -381,6 +399,57 @@ func UserCacheDir() (string, error) { return dir, nil } +// UserConfigDir returns the default root directory to use for user-specific +// configuration data. Users should create their own application-specific +// subdirectory within this one and use that. +// +// On Unix systems, it returns $XDG_CONFIG_HOME as specified by +// https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html if +// non-empty, else $HOME/.config. +// On Darwin, it returns $HOME/Library/Application Support. +// On Windows, it returns %AppData%. +// On Plan 9, it returns $home/lib. +// +// If the location cannot be determined (for example, $HOME is not defined), +// then it will return an error. +func UserConfigDir() (string, error) { + var dir string + + switch runtime.GOOS { + case "windows": + dir = Getenv("AppData") + if dir == "" { + return "", errors.New("%AppData% is not defined") + } + + case "darwin": + dir = Getenv("HOME") + if dir == "" { + return "", errors.New("$HOME is not defined") + } + dir += "/Library/Application Support" + + case "plan9": + dir = Getenv("home") + if dir == "" { + return "", errors.New("$home is not defined") + } + dir += "/lib" + + default: // Unix + dir = Getenv("XDG_CONFIG_HOME") + if dir == "" { + dir = Getenv("HOME") + if dir == "" { + return "", errors.New("neither $XDG_CONFIG_HOME nor $HOME are defined") + } + dir += "/.config" + } + } + + return dir, nil +} + // UserHomeDir returns the current user's home directory. // // On Unix, including macOS, it returns the $HOME environment variable. @@ -393,16 +462,21 @@ func UserHomeDir() (string, error) { env, enverr = "USERPROFILE", "%userprofile%" case "plan9": env, enverr = "home", "$home" - case "nacl", "android": + } + if v := Getenv(env); v != "" { + return v, nil + } + // On some geese the home directory is not always defined. + switch runtime.GOOS { + case "nacl": return "/", nil + case "android": + return "/sdcard", nil case "darwin": if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" { return "/", nil } } - if v := Getenv(env); v != "" { - return v, nil - } return "", errors.New(enverr + " is not defined") } @@ -416,11 +490,11 @@ func UserHomeDir() (string, error) { // On Unix, the mode's permission bits, ModeSetuid, ModeSetgid, and // ModeSticky are used. // -// On Windows, the mode must be non-zero but otherwise only the 0200 -// bit (owner writable) of mode is used; it controls whether the -// file's read-only attribute is set or cleared. attribute. The other -// bits are currently unused. Use mode 0400 for a read-only file and -// 0600 for a readable+writable file. +// On Windows, only the 0200 bit (owner writable) of mode is used; it +// controls whether the file's read-only attribute is set or cleared. +// The other bits are currently unused. For compatibility with Go 1.12 +// and earlier, use a non-zero mode. Use mode 0400 for a read-only +// file and 0600 for a readable+writable file. // // On Plan 9, the mode's permission bits, ModeAppend, ModeExclusive, // and ModeTemporary are used. diff --git a/libgo/go/os/file_plan9.go b/libgo/go/os/file_plan9.go index 3fa12e6..e0a3826 100644 --- a/libgo/go/os/file_plan9.go +++ b/libgo/go/os/file_plan9.go @@ -22,9 +22,10 @@ func fixLongPath(path string) string { // can overwrite this data, which could cause the finalizer // to close the wrong file descriptor. type file struct { - fd int - name string - dirinfo *dirInfo // nil unless directory being read + fd int + name string + dirinfo *dirInfo // nil unless directory being read + appendMode bool // whether file is opened for appending } // Fd returns the integer Plan 9 file descriptor referencing the open file. @@ -135,6 +136,7 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { // Close closes the File, rendering it unusable for I/O. // On files that support SetDeadline, any pending I/O operations will // be canceled and return immediately with an error. +// Close will return an error if it has already been called. func (f *File) Close() error { if err := f.checkValid("close"); err != nil { return err diff --git a/libgo/go/os/file_posix.go b/libgo/go/os/file_posix.go index 85395d6..2220a44 100644 --- a/libgo/go/os/file_posix.go +++ b/libgo/go/os/file_posix.go @@ -7,32 +7,12 @@ package os import ( - "runtime" "syscall" "time" ) func sigpipe() // implemented in package runtime -// Readlink returns the destination of the named symbolic link. -// If there is an error, it will be of type *PathError. -func Readlink(name string) (string, error) { - for len := 128; ; len *= 2 { - b := make([]byte, len) - n, e := fixCount(syscall.Readlink(fixLongPath(name), b)) - // buffer too small - if runtime.GOOS == "aix" && e == syscall.ERANGE { - continue - } - if e != nil { - return "", &PathError{"readlink", name, e} - } - if n < len { - return string(b[0:n]), nil - } - } -} - // syscallMode returns the syscall-specific mode bits from Go's portable mode bits. func syscallMode(i FileMode) (o uint32) { o |= uint32(i.Perm()) diff --git a/libgo/go/os/file_unix.go b/libgo/go/os/file_unix.go index 912ba5a..750771f 100644 --- a/libgo/go/os/file_unix.go +++ b/libgo/go/os/file_unix.go @@ -52,6 +52,7 @@ type file struct { dirinfo *dirInfo // nil unless directory being read nonblock bool // whether we set nonblocking mode stdoutOrErr bool // whether this is stdout or stderr + appendMode bool // whether file is opened for appending } // Fd returns the integer Unix file descriptor referencing the open file. @@ -121,33 +122,27 @@ func newFile(fd uintptr, name string, kind newFileKind) *File { // we assume they know what they are doing so we allow it to be // used with kqueue. if kind == kindOpenFile { - var st syscall.Stat_t switch runtime.GOOS { - case "freebsd": - // On FreeBSD before 10.4 it used to crash the - // system unpredictably while running all.bash. - // When we stop supporting FreeBSD 10 we can merge - // this into the dragonfly/netbsd/openbsd case. - // Issue 27619. - pollable = false - - case "dragonfly", "netbsd", "openbsd": + case "darwin", "dragonfly", "freebsd", "netbsd", "openbsd": + var st syscall.Stat_t + err := syscall.Fstat(fdi, &st) + typ := st.Mode & syscall.S_IFMT // Don't try to use kqueue with regular files on *BSDs. // On FreeBSD a regular file is always // reported as ready for writing. // On Dragonfly, NetBSD and OpenBSD the fd is signaled // only once as ready (both read and write). // Issue 19093. - if err := syscall.Fstat(fdi, &st); err == nil && st.Mode&syscall.S_IFMT == syscall.S_IFREG { + // Also don't add directories to the netpoller. + if err == nil && (typ == syscall.S_IFREG || typ == syscall.S_IFDIR) { pollable = false } - case "darwin": // In addition to the behavior described above for regular files, // 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 err := syscall.Fstat(fdi, &st); err == nil && (st.Mode&syscall.S_IFMT == syscall.S_IFIFO || st.Mode&syscall.S_IFMT == syscall.S_IFREG) { + if runtime.GOOS == "darwin" && typ == syscall.S_IFIFO { pollable = false } } @@ -236,6 +231,7 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { // Close closes the File, rendering it unusable for I/O. // On files that support SetDeadline, any pending I/O operations will // be canceled and return immediately with an error. +// Close will return an error if it has already been called. func (f *File) Close() error { if f == nil { return ErrInvalid @@ -413,3 +409,22 @@ func (f *File) readdir(n int) (fi []FileInfo, err error) { } return fi, err } + +// Readlink returns the destination of the named symbolic link. +// If there is an error, it will be of type *PathError. +func Readlink(name string) (string, error) { + for len := 128; ; len *= 2 { + b := make([]byte, len) + n, e := fixCount(syscall.Readlink(name, b)) + // buffer too small + if runtime.GOOS == "aix" && e == syscall.ERANGE { + continue + } + if e != nil { + return "", &PathError{"readlink", name, e} + } + if n < len { + return string(b[0:n]), nil + } + } +} diff --git a/libgo/go/os/os_test.go b/libgo/go/os/os_test.go index cf68cae..b6430d3 100644 --- a/libgo/go/os/os_test.go +++ b/libgo/go/os/os_test.go @@ -57,13 +57,26 @@ var sysdir = func() *sysDir { if err != nil { wd = err.Error() } - return &sysDir{ + sd := &sysDir{ filepath.Join(wd, "..", ".."), []string{ "ResourceRules.plist", "Info.plist", }, } + found := true + for _, f := range sd.files { + path := filepath.Join(sd.name, f) + if _, err := Stat(path); err != nil { + found = false + break + } + } + if found { + return sd + } + // In a self-hosted iOS build the above files might + // not exist. Look for system files instead below. } case "windows": return &sysDir{ @@ -1183,21 +1196,25 @@ func TestChdirAndGetwd(t *testing.T) { // /usr/bin does not usually exist on Plan 9 or Android. switch runtime.GOOS { case "android": - dirs = []string{"/", "/system/bin"} + dirs = []string{"/system/bin"} case "plan9": dirs = []string{"/", "/usr"} case "darwin": switch runtime.GOARCH { case "arm", "arm64": - d1, err := ioutil.TempDir("", "d1") - if err != nil { - t.Fatalf("TempDir: %v", err) - } - d2, err := ioutil.TempDir("", "d2") - if err != nil { - t.Fatalf("TempDir: %v", err) + dirs = nil + for _, d := range []string{"d1", "d2"} { + dir, err := ioutil.TempDir("", d) + if err != nil { + t.Fatalf("TempDir: %v", err) + } + // Expand symlinks so path equality tests work. + dir, err = filepath.EvalSymlinks(dir) + if err != nil { + t.Fatalf("EvalSymlinks: %v", err) + } + dirs = append(dirs, dir) } - dirs = []string{d1, d2} } } oldwd := Getenv("PWD") @@ -1648,6 +1665,21 @@ func TestWriteAtNegativeOffset(t *testing.T) { } } +// Verify that WriteAt doesn't work in append mode. +func TestWriteAtInAppendMode(t *testing.T) { + defer chtmpdir(t)() + f, err := OpenFile("write_at_in_append_mode.txt", O_APPEND|O_CREATE, 0666) + if err != nil { + t.Fatalf("OpenFile: %v", err) + } + defer f.Close() + + _, err = f.WriteAt([]byte(""), 1) + if err != ErrWriteAtInAppendMode { + t.Fatalf("f.WriteAt returned %v, expected %v", err, ErrWriteAtInAppendMode) + } +} + func writeFile(t *testing.T, fname string, flag int, text string) string { f, err := OpenFile(fname, flag, 0666) if err != nil { @@ -2213,8 +2245,8 @@ func TestPipeThreads(t *testing.T) { switch runtime.GOOS { case "freebsd": t.Skip("skipping on FreeBSD; issue 19093") - case "solaris": - t.Skip("skipping on Solaris; issue 19111") + case "illumos", "solaris": + t.Skip("skipping on Solaris and illumos; issue 19111") case "windows": t.Skip("skipping on Windows; issue 19098") case "plan9": @@ -2281,8 +2313,7 @@ func TestPipeThreads(t *testing.T) { } } -func TestDoubleCloseError(t *testing.T) { - path := sfdir + "/" + sfname +func testDoubleCloseError(t *testing.T, path string) { file, err := Open(path) if err != nil { t.Fatal(err) @@ -2301,6 +2332,11 @@ func TestDoubleCloseError(t *testing.T) { } } +func TestDoubleCloseError(t *testing.T) { + testDoubleCloseError(t, filepath.Join(sfdir, sfname)) + testDoubleCloseError(t, sfdir) +} + func TestUserHomeDir(t *testing.T) { dir, err := UserHomeDir() if dir == "" && err == nil { diff --git a/libgo/go/os/os_unix_test.go b/libgo/go/os/os_unix_test.go index f609660..7af20d7 100644 --- a/libgo/go/os/os_unix_test.go +++ b/libgo/go/os/os_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 aix darwin dragonfly freebsd hurd linux netbsd openbsd solaris +// +build aix darwin dragonfly freebsd hurd js,wasm linux netbsd openbsd solaris package os_test @@ -152,6 +152,9 @@ func TestLchown(t *testing.T) { gid := Getgid() t.Log("gid:", gid) if err = Lchown(linkname, -1, gid); err != nil { + if err, ok := err.(*PathError); ok && err.Err == syscall.ENOSYS { + t.Skip("lchown is unavailable") + } t.Fatalf("lchown %s -1 %d: %s", linkname, gid, err) } sys := dir.Sys().(*syscall.Stat_t) @@ -231,6 +234,10 @@ func TestMkdirStickyUmask(t *testing.T) { // See also issues: 22939, 24331 func newFileTest(t *testing.T, blocking bool) { + if runtime.GOOS == "js" { + t.Skipf("syscall.Pipe is not available on %s.", runtime.GOOS) + } + p := make([]int, 2) if err := syscall.Pipe(p); err != nil { t.Fatalf("pipe: %v", err) @@ -252,12 +259,19 @@ func newFileTest(t *testing.T, blocking bool) { } defer file.Close() + timeToWrite := 100 * time.Millisecond + timeToDeadline := 1 * time.Millisecond + if !blocking { + // Use a longer time to avoid flakes. + // We won't be waiting this long anyhow. + timeToWrite = 1 * time.Second + } + // 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")) }) + timer := time.AfterFunc(timeToWrite, func() { syscall.Write(p[1], []byte("a")) }) defer timer.Stop() - file.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + file.SetReadDeadline(time.Now().Add(timeToDeadline)) _, err := file.Read(b) if !blocking { // We want it to fail with a timeout. @@ -281,3 +295,28 @@ func TestNewFileNonBlock(t *testing.T) { t.Parallel() newFileTest(t, false) } + +func TestSplitPath(t *testing.T) { + t.Parallel() + for _, tt := range []struct{ path, wantDir, wantBase string }{ + {"a", ".", "a"}, + {"a/", ".", "a"}, + {"a//", ".", "a"}, + {"a/b", "a", "b"}, + {"a/b/", "a", "b"}, + {"a/b/c", "a/b", "c"}, + {"/a", "/", "a"}, + {"/a/", "/", "a"}, + {"/a/b", "/a", "b"}, + {"/a/b/", "/a", "b"}, + {"/a/b/c", "/a/b", "c"}, + {"//a", "/", "a"}, + {"//a/", "/", "a"}, + {"///a", "/", "a"}, + {"///a/", "/", "a"}, + } { + if dir, base := SplitPath(tt.path); dir != tt.wantDir || base != tt.wantBase { + t.Errorf("splitPath(%q) = %q, %q, want %q, %q", tt.path, dir, base, tt.wantDir, tt.wantBase) + } + } +} diff --git a/libgo/go/os/path.go b/libgo/go/os/path.go index ba43ea3..9d7ecad 100644 --- a/libgo/go/os/path.go +++ b/libgo/go/os/path.go @@ -58,6 +58,9 @@ func MkdirAll(path string, perm FileMode) error { return nil } +// removeAllTestHook is a hook for testing. +var removeAllTestHook = func(err error) error { return err } + // RemoveAll removes path and any children it contains. // It removes everything it can but returns the first error // it encounters. If the path does not exist, RemoveAll diff --git a/libgo/go/os/path_unix.go b/libgo/go/os/path_unix.go index 4864989..4c97f39 100644 --- a/libgo/go/os/path_unix.go +++ b/libgo/go/os/path_unix.go @@ -38,20 +38,30 @@ func basename(name string) string { func splitPath(path string) (string, string) { // if no better parent is found, the path is relative from "here" dirname := "." - // if no slashes in path, base is path - basename := path + + // Remove all but one leading slash. + for len(path) > 1 && path[0] == '/' && path[1] == '/' { + path = path[1:] + } i := len(path) - 1 - // Remove trailing slashes + // Remove trailing slashes. for ; i > 0 && path[i] == '/'; i-- { path = path[:i] } + // if no slashes in path, base is path + basename := path + // Remove leading directory path for i--; i >= 0; i-- { if path[i] == '/' { - dirname = path[:i] + if i == 0 { + dirname = path[:1] + } else { + dirname = path[:i] + } basename = path[i+1:] break } diff --git a/libgo/go/os/pipe_test.go b/libgo/go/os/pipe_test.go index 779b2bd..4c53bc9 100644 --- a/libgo/go/os/pipe_test.go +++ b/libgo/go/os/pipe_test.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Test broken pipes on Unix systems. -// +build !windows,!plan9,!nacl,!js +// +build !plan9,!nacl,!js package os_test @@ -35,6 +35,11 @@ func TestEPIPE(t *testing.T) { t.Fatal(err) } + expect := syscall.EPIPE + if runtime.GOOS == "windows" { + // 232 is Windows error code ERROR_NO_DATA, "The pipe is being closed". + expect = syscall.Errno(232) + } // Every time we write to the pipe we should get an EPIPE. for i := 0; i < 20; i++ { _, err = w.Write([]byte("hi")) @@ -47,13 +52,17 @@ func TestEPIPE(t *testing.T) { if se, ok := err.(*os.SyscallError); ok { err = se.Err } - if err != syscall.EPIPE { - t.Errorf("iteration %d: got %v, expected EPIPE", i, err) + if err != expect { + t.Errorf("iteration %d: got %v, expected %v", i, err, expect) } } } func TestStdPipe(t *testing.T) { + switch runtime.GOOS { + case "windows": + t.Skip("Windows doesn't support SIGPIPE") + } testenv.MustHaveExec(t) r, w, err := os.Pipe() if err != nil { @@ -195,8 +204,12 @@ func TestClosedPipeRaceWrite(t *testing.T) { // for unsupported file type." Currently it returns EAGAIN; it is // possible that in the future it will simply wait for data. func TestReadNonblockingFd(t *testing.T) { + switch runtime.GOOS { + case "windows": + t.Skip("Windows doesn't support SetNonblock") + } if os.Getenv("GO_WANT_READ_NONBLOCKING_FD") == "1" { - fd := int(os.Stdin.Fd()) + fd := syscallDescriptor(os.Stdin.Fd()) syscall.SetNonblock(fd, true) defer syscall.SetNonblock(fd, false) _, err := os.Stdin.Read(make([]byte, 1)) @@ -226,7 +239,7 @@ func TestReadNonblockingFd(t *testing.T) { } func TestCloseWithBlockingReadByNewFile(t *testing.T) { - var p [2]int + var p [2]syscallDescriptor err := syscall.Pipe(p[:]) if err != nil { t.Fatal(err) @@ -276,8 +289,11 @@ func testCloseWithBlockingRead(t *testing.T, r, w *os.File) { if err == nil { t.Error("I/O on closed pipe unexpectedly succeeded") } - if err != io.EOF { - t.Errorf("got %v, expected io.EOF", err) + if pe, ok := err.(*os.PathError); ok { + err = pe.Err + } + if err != io.EOF && err != os.ErrClosed { + t.Errorf("got %v, expected EOF or closed", err) } }(c2) diff --git a/libgo/go/os/proc.go b/libgo/go/os/proc.go index 804128a..7364d63 100644 --- a/libgo/go/os/proc.go +++ b/libgo/go/os/proc.go @@ -56,6 +56,8 @@ func Getgroups() ([]int, error) { // Exit causes the current program to exit with the given status code. // Conventionally, code zero indicates success, non-zero an error. // The program terminates immediately; deferred functions are not run. +// +// For portability, the status code should be in the range [0, 125]. func Exit(code int) { if code == 0 { // Give race detector a chance to fail the program. diff --git a/libgo/go/os/removeall_at.go b/libgo/go/os/removeall_at.go index 6fdd7e8..f3ecf57 100644 --- a/libgo/go/os/removeall_at.go +++ b/libgo/go/os/removeall_at.go @@ -91,7 +91,8 @@ func removeAllFrom(parent *File, base string) error { // Remove the directory's entries. var recurseErr error for { - const request = 1024 + const reqSize = 1024 + var respSize int // Open the directory to recurse into file, err := openFdAt(parentFd, base) @@ -103,23 +104,37 @@ func removeAllFrom(parent *File, base string) error { break } - names, readErr := file.Readdirnames(request) - // Errors other than EOF should stop us from continuing. - if readErr != nil && readErr != io.EOF { - file.Close() - if IsNotExist(readErr) { - return nil + for { + numErr := 0 + + names, readErr := file.Readdirnames(reqSize) + // Errors other than EOF should stop us from continuing. + if readErr != nil && readErr != io.EOF { + file.Close() + if IsNotExist(readErr) { + return nil + } + return &PathError{"readdirnames", base, readErr} } - return &PathError{"readdirnames", base, readErr} - } - for _, name := range names { - err := removeAllFrom(file, name) - if err != nil { - if pathErr, ok := err.(*PathError); ok { - pathErr.Path = base + string(PathSeparator) + pathErr.Path + respSize = len(names) + for _, name := range names { + err := removeAllFrom(file, name) + if err != nil { + if pathErr, ok := err.(*PathError); ok { + pathErr.Path = base + string(PathSeparator) + pathErr.Path + } + numErr++ + if recurseErr == nil { + recurseErr = err + } } - recurseErr = err + } + + // If we can delete any entry, break to start new iteration. + // Otherwise, we discard current names, get next entries and try deleting them. + if numErr != reqSize { + break } } @@ -131,13 +146,14 @@ func removeAllFrom(parent *File, base string) error { file.Close() // Finish when the end of the directory is reached - if len(names) < request { + if respSize < reqSize { break } } // Remove the directory itself. unlinkError := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR) + unlinkError = removeAllTestHook(unlinkError) if unlinkError == nil || IsNotExist(unlinkError) { return nil } diff --git a/libgo/go/os/removeall_noat.go b/libgo/go/os/removeall_noat.go index 7d9f73e..cf26bdb 100644 --- a/libgo/go/os/removeall_noat.go +++ b/libgo/go/os/removeall_noat.go @@ -56,8 +56,30 @@ func removeAll(path string) error { return err } - const request = 1024 - names, err1 := fd.Readdirnames(request) + const reqSize = 1024 + var names []string + var readErr error + + for { + numErr := 0 + names, readErr = fd.Readdirnames(reqSize) + + for _, name := range names { + err1 := RemoveAll(path + string(PathSeparator) + name) + if err == nil { + err = err1 + } + if err1 != nil { + numErr++ + } + } + + // If we can delete any entry, break to start new iteration. + // Otherwise, we discard current names, get next entries and try deleting them. + if numErr != reqSize { + break + } + } // Removing files from the directory may have caused // the OS to reshuffle it. Simply calling Readdirnames @@ -66,19 +88,12 @@ func removeAll(path string) error { // 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 { + if readErr == io.EOF { break } // If Readdirnames returned an error, use it. if err == nil { - err = err1 + err = readErr } if len(names) == 0 { break @@ -88,7 +103,7 @@ func removeAll(path string) error { // got fewer than request names from Readdirnames, try // simply removing the directory now. If that // succeeds, we are done. - if len(names) < request { + if len(names) < reqSize { err1 := Remove(path) if err1 == nil || IsNotExist(err1) { return nil @@ -109,6 +124,7 @@ func removeAll(path string) error { // Remove directory. err1 := Remove(path) + err1 = removeAllTestHook(err1) if err1 == nil || IsNotExist(err1) { return nil } diff --git a/libgo/go/os/removeall_test.go b/libgo/go/os/removeall_test.go index 945a38e..4d556f9 100644 --- a/libgo/go/os/removeall_test.go +++ b/libgo/go/os/removeall_test.go @@ -5,6 +5,7 @@ package os_test import ( + "errors" "fmt" "io/ioutil" . "os" @@ -80,16 +81,8 @@ func TestRemoveAll(t *testing.T) { t.Fatalf("Lstat %q succeeded after RemoveAll (third)", path) } - // Determine if we should run the following test. - testit := true - if runtime.GOOS == "windows" { - // Chmod is not supported under windows. - testit = false - } else { - // Test fails as root. - testit = Getuid() != 0 - } - if testit { + // Chmod is not supported under Windows and test fails as root. + if runtime.GOOS != "windows" && Getuid() != 0 { // Make directory with file and subdirectory and trigger error. if err = MkdirAll(dpath, 0777); err != nil { t.Fatalf("MkdirAll %q: %s", dpath, err) @@ -166,7 +159,7 @@ func TestRemoveAllLarge(t *testing.T) { func TestRemoveAllLongPath(t *testing.T) { switch runtime.GOOS { - case "aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": + case "aix", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "illumos", "solaris": break default: t.Skip("skipping for not implemented platforms") @@ -413,3 +406,49 @@ func TestRemoveUnreadableDir(t *testing.T) { t.Fatal(err) } } + +// Issue 29921 +func TestRemoveAllWithMoreErrorThanReqSize(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + defer func(oldHook func(error) error) { + *RemoveAllTestHook = oldHook + }(*RemoveAllTestHook) + + *RemoveAllTestHook = func(err error) error { + return errors.New("error from RemoveAllTestHook") + } + + tmpDir, err := ioutil.TempDir("", "TestRemoveAll-") + if err != nil { + t.Fatal(err) + } + defer RemoveAll(tmpDir) + + path := filepath.Join(tmpDir, "_TestRemoveAllWithMoreErrorThanReqSize_") + + // Make directory with 1025 files and remove. + if err := MkdirAll(path, 0777); err != nil { + t.Fatalf("MkdirAll %q: %s", path, err) + } + for i := 0; i < 1025; i++ { + fpath := filepath.Join(path, fmt.Sprintf("file%d", i)) + fd, err := Create(fpath) + if err != nil { + t.Fatalf("create %q: %s", fpath, err) + } + fd.Close() + } + + // This call should not hang + if err := RemoveAll(path); err == nil { + t.Fatal("Want error from RemoveAllTestHook, got nil") + } + + // We hook to inject error, but the actual files must be deleted + if _, err := Lstat(path); err == nil { + t.Fatal("directory must be deleted even with removeAllTetHook run") + } +} diff --git a/libgo/go/os/signal/internal/pty/pty.go b/libgo/go/os/signal/internal/pty/pty.go index 4f65ad8..f7d61f9 100644 --- a/libgo/go/os/signal/internal/pty/pty.go +++ b/libgo/go/os/signal/internal/pty/pty.go @@ -48,6 +48,8 @@ func (e *PtyError) Error() string { return fmt.Sprintf("%s: %s", e.FuncName, e.ErrorString) } +func (e *PtyError) Unwrap() error { return e.Errno } + // Open returns a master pty and the name of the linked slave tty. func Open() (master *os.File, slave string, err error) { m := posix_openpt(_O_RDWR) diff --git a/libgo/go/os/signal/signal_cgo_test.go b/libgo/go/os/signal/signal_cgo_test.go index 3c23090..075e8c1 100644 --- a/libgo/go/os/signal/signal_cgo_test.go +++ b/libgo/go/os/signal/signal_cgo_test.go @@ -101,6 +101,17 @@ func TestTerminalSignal(t *testing.T) { Ctty: int(slave.Fd()), } + // Test ctty management by sending enough child fd to overlap the + // parent's fd intended for child's ctty. + for 2+len(cmd.ExtraFiles) < cmd.SysProcAttr.Ctty { + dummy, err := os.Open(os.DevNull) + if err != nil { + t.Fatal(err) + } + defer dummy.Close() + cmd.ExtraFiles = append(cmd.ExtraFiles, dummy) + } + if err := cmd.Start(); err != nil { t.Fatal(err) } diff --git a/libgo/go/os/sticky_bsd.go b/libgo/go/os/sticky_bsd.go index ae2744f..c09b1ac 100644 --- a/libgo/go/os/sticky_bsd.go +++ b/libgo/go/os/sticky_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 freebsd netbsd openbsd solaris +// +build aix darwin dragonfly freebsd js,wasm netbsd openbsd solaris package os diff --git a/libgo/go/os/sticky_notbsd.go b/libgo/go/os/sticky_notbsd.go index edb5f69..c158506 100644 --- a/libgo/go/os/sticky_notbsd.go +++ b/libgo/go/os/sticky_notbsd.go @@ -6,6 +6,7 @@ // +build !darwin // +build !dragonfly // +build !freebsd +// +build !js !wasm // +build !netbsd // +build !openbsd // +build !solaris diff --git a/libgo/go/os/timeout_test.go b/libgo/go/os/timeout_test.go index 4720738..5d7ea7e 100644 --- a/libgo/go/os/timeout_test.go +++ b/libgo/go/os/timeout_test.go @@ -514,7 +514,7 @@ func TestReadWriteDeadlineRace(t *testing.T) { } // TestRacyRead tests that it is safe to mutate the input Read buffer -// immediately after cancelation has occurred. +// immediately after cancellation has occurred. func TestRacyRead(t *testing.T) { t.Parallel() @@ -553,7 +553,7 @@ func TestRacyRead(t *testing.T) { } // TestRacyWrite tests that it is safe to mutate the input Write buffer -// immediately after cancelation has occurred. +// immediately after cancellation has occurred. func TestRacyWrite(t *testing.T) { t.Parallel() diff --git a/libgo/go/os/types_windows.go b/libgo/go/os/types_windows.go index 5e33292..3d1a667 100644 --- a/libgo/go/os/types_windows.go +++ b/libgo/go/os/types_windows.go @@ -189,6 +189,21 @@ func (fs *fileStat) loadFileId() error { return nil } +// saveInfoFromPath saves full path of the file to be used by os.SameFile later, +// and set name from path. +func (fs *fileStat) saveInfoFromPath(path string) error { + fs.path = path + if !isAbs(fs.path) { + var err error + fs.path, err = syscall.FullPath(fs.path) + if err != nil { + return &PathError{"FullPath", path, err} + } + } + fs.name = basename(path) + return nil +} + // devNullStat is fileStat structure describing DevNull file ("NUL"). var devNullStat = fileStat{ name: DevNull, diff --git a/libgo/go/os/user/lookup_stubs.go b/libgo/go/os/user/lookup_stubs.go index 61bf1dc..d3acbdd 100644 --- a/libgo/go/os/user/lookup_stubs.go +++ b/libgo/go/os/user/lookup_stubs.go @@ -26,12 +26,14 @@ func current() (*User, error) { if err == nil { return u, nil } + + homeDir, _ := os.UserHomeDir() u = &User{ Uid: uid, Gid: currentGID(), Username: os.Getenv("USER"), Name: "", // ignored - HomeDir: os.Getenv("HOME"), + HomeDir: homeDir, } // On NaCL and Android, return a dummy user instead of failing. switch runtime.GOOS { @@ -42,9 +44,6 @@ func current() (*User, error) { if u.Username == "" { u.Username = "nacl" } - if u.HomeDir == "" { - u.HomeDir = "/" - } case "android": if u.Uid == "" { u.Uid = "1" @@ -52,16 +51,23 @@ func current() (*User, error) { if u.Username == "" { u.Username = "android" } - if u.HomeDir == "" { - u.HomeDir = "/sdcard" - } } // cgo isn't available, but if we found the minimum information // without it, use it: if u.Uid != "" && u.Username != "" && u.HomeDir != "" { return u, nil } - return u, fmt.Errorf("user: Current not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) + var missing string + if u.Username == "" { + missing = "$USER" + } + if u.HomeDir == "" { + if missing != "" { + missing += ", " + } + missing += "$HOME" + } + return u, fmt.Errorf("user: Current requires cgo or %s set in environment", missing) } func listGroups(*User) ([]string, error) { diff --git a/libgo/go/os/user/user_test.go b/libgo/go/os/user/user_test.go index eeb24dd..8c4c817 100644 --- a/libgo/go/os/user/user_test.go +++ b/libgo/go/os/user/user_test.go @@ -132,7 +132,7 @@ func TestGroupIds(t *testing.T) { if runtime.GOOS == "aix" { t.Skip("skipping GroupIds, see golang.org/issue/30563") } - if runtime.GOOS == "solaris" { + if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" { t.Skip("skipping GroupIds, see golang.org/issue/14709") } user, err := Current() |