aboutsummaryrefslogtreecommitdiff
path: root/src/io
diff options
context:
space:
mode:
Diffstat (limited to 'src/io')
-rw-r--r--src/io/example_test.go17
-rw-r--r--src/io/export_test.go8
-rw-r--r--src/io/fs/fs.go252
-rw-r--r--src/io/fs/fs_test.go48
-rw-r--r--src/io/fs/glob.go120
-rw-r--r--src/io/fs/glob_test.go86
-rw-r--r--src/io/fs/readdir.go47
-rw-r--r--src/io/fs/readdir_test.go35
-rw-r--r--src/io/fs/readfile.go63
-rw-r--r--src/io/fs/readfile_test.go43
-rw-r--r--src/io/fs/stat.go31
-rw-r--r--src/io/fs/stat_test.go36
-rw-r--r--src/io/io.go105
-rw-r--r--src/io/io_test.go28
-rw-r--r--src/io/ioutil/ioutil.go112
-rw-r--r--src/io/ioutil/tempfile_test.go3
-rw-r--r--src/io/multi_test.go11
17 files changed, 953 insertions, 92 deletions
diff --git a/src/io/example_test.go b/src/io/example_test.go
index 465eed4d5e..6d338acd14 100644
--- a/src/io/example_test.go
+++ b/src/io/example_test.go
@@ -8,7 +8,6 @@ import (
"bytes"
"fmt"
"io"
- "io/ioutil"
"log"
"os"
"strings"
@@ -141,7 +140,7 @@ func ExampleTeeReader() {
r = io.TeeReader(r, os.Stdout)
// Everything read from r will be copied to stdout.
- ioutil.ReadAll(r)
+ io.ReadAll(r)
// Output:
// some io.Reader stream to be read
@@ -241,3 +240,17 @@ func ExamplePipe() {
// Output:
// some io.Reader stream to be read
}
+
+func ExampleReadAll() {
+ r := strings.NewReader("Go is a general-purpose language designed with systems programming in mind.")
+
+ b, err := io.ReadAll(r)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("%s", b)
+
+ // Output:
+ // Go is a general-purpose language designed with systems programming in mind.
+}
diff --git a/src/io/export_test.go b/src/io/export_test.go
new file mode 100644
index 0000000000..fa3e8e76f6
--- /dev/null
+++ b/src/io/export_test.go
@@ -0,0 +1,8 @@
+// Copyright 2020 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 io
+
+// exported for test
+var ErrInvalidWrite = errInvalidWrite
diff --git a/src/io/fs/fs.go b/src/io/fs/fs.go
new file mode 100644
index 0000000000..d9f89fc6ee
--- /dev/null
+++ b/src/io/fs/fs.go
@@ -0,0 +1,252 @@
+// Copyright 2020 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 fs defines basic interfaces to a file system.
+// A file system can be provided by the host operating system
+// but also by other packages.
+package fs
+
+import (
+ "internal/oserror"
+ "time"
+)
+
+// An FS provides access to a hierarchical file system.
+//
+// The FS interface is the minimum implementation required of the file system.
+// A file system may implement additional interfaces,
+// such as fsutil.ReadFileFS, to provide additional or optimized functionality.
+// See io/fsutil for details.
+type FS interface {
+ // Open opens the named file.
+ //
+ // When Open returns an error, it should be of type *PathError
+ // with the Op field set to "open", the Path field set to name,
+ // and the Err field describing the problem.
+ //
+ // Open should reject attempts to open names that do not satisfy
+ // ValidPath(name), returning a *PathError with Err set to
+ // ErrInvalid or ErrNotExist.
+ Open(name string) (File, error)
+}
+
+// ValidPath reports whether the given path name
+// is valid for use in a call to Open.
+// Path names passed to open are unrooted, slash-separated
+// sequences of path elements, like “x/y/z”.
+// Path names must not contain a “.” or “..” or empty element,
+// except for the special case that the root directory is named “.”.
+//
+// Paths are slash-separated on all systems, even Windows.
+// Backslashes must not appear in path names.
+func ValidPath(name string) bool {
+ if name == "." {
+ // special case
+ return true
+ }
+
+ // Iterate over elements in name, checking each.
+ for {
+ i := 0
+ for i < len(name) && name[i] != '/' {
+ if name[i] == '\\' {
+ return false
+ }
+ i++
+ }
+ elem := name[:i]
+ if elem == "" || elem == "." || elem == ".." {
+ return false
+ }
+ if i == len(name) {
+ return true // reached clean ending
+ }
+ name = name[i+1:]
+ }
+}
+
+// A File provides access to a single file.
+// The File interface is the minimum implementation required of the file.
+// A file may implement additional interfaces, such as
+// ReadDirFile, ReaderAt, or Seeker, to provide additional or optimized functionality.
+type File interface {
+ Stat() (FileInfo, error)
+ Read([]byte) (int, error)
+ Close() error
+}
+
+// A DirEntry is an entry read from a directory
+// (using the ReadDir function or a ReadDirFile's ReadDir method).
+type DirEntry interface {
+ // Name returns the name of the file (or subdirectory) described by the entry.
+ // This name is only the final element of the path (the base name), not the entire path.
+ // For example, Name would return "hello.go" not "/home/gopher/hello.go".
+ Name() string
+
+ // IsDir reports whether the entry describes a directory.
+ IsDir() bool
+
+ // Type returns the type bits for the entry.
+ // The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method.
+ Type() FileMode
+
+ // Info returns the FileInfo for the file or subdirectory described by the entry.
+ // The returned FileInfo may be from the time of the original directory read
+ // or from the time of the call to Info. If the file has been removed or renamed
+ // since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist).
+ // If the entry denotes a symbolic link, Info reports the information about the link itself,
+ // not the link's target.
+ Info() (FileInfo, error)
+}
+
+// A ReadDirFile is a directory file whose entries can be read with the ReadDir method.
+// Every directory file should implement this interface.
+// (It is permissible for any file to implement this interface,
+// but if so ReadDir should return an error for non-directories.)
+type ReadDirFile interface {
+ File
+
+ // ReadDir reads the contents of the directory and returns
+ // a slice of up to n DirEntry values in directory order.
+ // Subsequent calls on the same file will yield further DirEntry values.
+ //
+ // If n > 0, ReadDir returns at most n DirEntry structures.
+ // In this case, if ReadDir returns an empty slice, it will return
+ // a non-nil error explaining why.
+ // At the end of a directory, the error is io.EOF.
+ //
+ // If n <= 0, ReadDir returns all the DirEntry values from the directory
+ // in a single slice. In this case, if ReadDir succeeds (reads all the way
+ // to the end of the directory), it returns the slice and a nil error.
+ // If it encounters an error before the end of the directory,
+ // ReadDir returns the DirEntry list read until that point and a non-nil error.
+ ReadDir(n int) ([]DirEntry, error)
+}
+
+// Generic file system errors.
+// Errors returned by file systems can be tested against these errors
+// using errors.Is.
+var (
+ ErrInvalid = errInvalid() // "invalid argument"
+ ErrPermission = errPermission() // "permission denied"
+ ErrExist = errExist() // "file already exists"
+ ErrNotExist = errNotExist() // "file does not exist"
+ ErrClosed = errClosed() // "file already closed"
+)
+
+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 }
+
+// A FileInfo describes a file and is returned by Stat.
+type FileInfo interface {
+ Name() string // base name of the file
+ Size() int64 // length in bytes for regular files; system-dependent for others
+ Mode() FileMode // file mode bits
+ ModTime() time.Time // modification time
+ IsDir() bool // abbreviation for Mode().IsDir()
+ Sys() interface{} // underlying data source (can return nil)
+}
+
+// A FileMode represents a file's mode and permission bits.
+// The bits have the same definition on all systems, so that
+// information about files can be moved from one system
+// to another portably. Not all bits apply to all systems.
+// The only required bit is ModeDir for directories.
+type FileMode uint32
+
+// The defined file mode bits are the most significant bits of the FileMode.
+// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
+// The values of these bits should be considered part of the public API and
+// may be used in wire protocols or disk representations: they must not be
+// changed, although new bits might be added.
+const (
+ // The single letters are the abbreviations
+ // used by the String method's formatting.
+ ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory
+ ModeAppend // a: append-only
+ ModeExclusive // l: exclusive use
+ ModeTemporary // T: temporary file; Plan 9 only
+ ModeSymlink // L: symbolic link
+ ModeDevice // D: device file
+ ModeNamedPipe // p: named pipe (FIFO)
+ ModeSocket // S: Unix domain socket
+ ModeSetuid // u: setuid
+ 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 | ModeCharDevice | ModeIrregular
+
+ ModePerm FileMode = 0777 // Unix permission bits
+)
+
+func (m FileMode) String() string {
+ const str = "dalTLDpSugct?"
+ var buf [32]byte // Mode is uint32.
+ w := 0
+ for i, c := range str {
+ if m&(1<<uint(32-1-i)) != 0 {
+ buf[w] = byte(c)
+ w++
+ }
+ }
+ if w == 0 {
+ buf[w] = '-'
+ w++
+ }
+ const rwx = "rwxrwxrwx"
+ for i, c := range rwx {
+ if m&(1<<uint(9-1-i)) != 0 {
+ buf[w] = byte(c)
+ } else {
+ buf[w] = '-'
+ }
+ w++
+ }
+ return string(buf[:w])
+}
+
+// IsDir reports whether m describes a directory.
+// That is, it tests for the ModeDir bit being set in m.
+func (m FileMode) IsDir() bool {
+ return m&ModeDir != 0
+}
+
+// IsRegular reports whether m describes a regular file.
+// That is, it tests that no mode type bits are set.
+func (m FileMode) IsRegular() bool {
+ return m&ModeType == 0
+}
+
+// Perm returns the Unix permission bits in m (m & ModePerm).
+func (m FileMode) Perm() FileMode {
+ return m & ModePerm
+}
+
+// Type returns type bits in m (m & ModeType).
+func (m FileMode) Type() FileMode {
+ return m & ModeType
+}
+
+// PathError records an error and the operation and file path that caused it.
+type PathError struct {
+ Op string
+ Path string
+ Err error
+}
+
+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.(interface{ Timeout() bool })
+ return ok && t.Timeout()
+}
diff --git a/src/io/fs/fs_test.go b/src/io/fs/fs_test.go
new file mode 100644
index 0000000000..8d395fc0db
--- /dev/null
+++ b/src/io/fs/fs_test.go
@@ -0,0 +1,48 @@
+// Copyright 2020 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 fs_test
+
+import (
+ . "io/fs"
+ "testing"
+)
+
+var isValidPathTests = []struct {
+ name string
+ ok bool
+}{
+ {".", true},
+ {"x", true},
+ {"x/y", true},
+
+ {"", false},
+ {"..", false},
+ {"/", false},
+ {"x/", false},
+ {"/x", false},
+ {"x/y/", false},
+ {"/x/y", false},
+ {"./", false},
+ {"./x", false},
+ {"x/.", false},
+ {"x/./y", false},
+ {"../", false},
+ {"../x", false},
+ {"x/..", false},
+ {"x/../y", false},
+ {"x//y", false},
+ {`x\`, false},
+ {`x\y`, false},
+ {`\x`, false},
+}
+
+func TestValidPath(t *testing.T) {
+ for _, tt := range isValidPathTests {
+ ok := ValidPath(tt.name)
+ if ok != tt.ok {
+ t.Errorf("ValidPath(%q) = %v, want %v", tt.name, ok, tt.ok)
+ }
+ }
+}
diff --git a/src/io/fs/glob.go b/src/io/fs/glob.go
new file mode 100644
index 0000000000..cde6c49f3d
--- /dev/null
+++ b/src/io/fs/glob.go
@@ -0,0 +1,120 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package fs
+
+import (
+ "path"
+ "runtime"
+)
+
+// A GlobFS is a file system with a Glob method.
+type GlobFS interface {
+ FS
+
+ // Glob returns the names of all files matching pattern,
+ // providing an implementation of the top-level
+ // Glob function.
+ Glob(pattern string) ([]string, error)
+}
+
+// Glob returns the names of all files matching pattern or nil
+// if there is no matching file. The syntax of patterns is the same
+// as in path.Match. The pattern may describe hierarchical names such as
+// /usr/*/bin/ed (assuming the Separator is '/').
+//
+// Glob ignores file system errors such as I/O errors reading directories.
+// The only possible returned error is path.ErrBadPattern, reporting that
+// the pattern is malformed.
+//
+// If fs implements GlobFS, Glob calls fs.Glob.
+// Otherwise, Glob uses ReadDir to traverse the directory tree
+// and look for matches for the pattern.
+func Glob(fsys FS, pattern string) (matches []string, err error) {
+ if fsys, ok := fsys.(GlobFS); ok {
+ return fsys.Glob(pattern)
+ }
+
+ // Check pattern is well-formed.
+ if _, err := path.Match(pattern, ""); err != nil {
+ return nil, err
+ }
+ if !hasMeta(pattern) {
+ if _, err = Stat(fsys, pattern); err != nil {
+ return nil, nil
+ }
+ return []string{pattern}, nil
+ }
+
+ dir, file := path.Split(pattern)
+ dir = cleanGlobPath(dir)
+
+ if !hasMeta(dir) {
+ return glob(fsys, dir, file, nil)
+ }
+
+ // Prevent infinite recursion. See issue 15879.
+ if dir == pattern {
+ return nil, path.ErrBadPattern
+ }
+
+ var m []string
+ m, err = Glob(fsys, dir)
+ if err != nil {
+ return
+ }
+ for _, d := range m {
+ matches, err = glob(fsys, d, file, matches)
+ if err != nil {
+ return
+ }
+ }
+ return
+}
+
+// cleanGlobPath prepares path for glob matching.
+func cleanGlobPath(path string) string {
+ switch path {
+ case "":
+ return "."
+ default:
+ return path[0 : len(path)-1] // chop off trailing separator
+ }
+}
+
+// glob searches for files matching pattern in the directory dir
+// and appends them to matches, returning the updated slice.
+// If the directory cannot be opened, glob returns the existing matches.
+// New matches are added in lexicographical order.
+func glob(fs FS, dir, pattern string, matches []string) (m []string, e error) {
+ m = matches
+ infos, err := ReadDir(fs, dir)
+ if err != nil {
+ return // ignore I/O error
+ }
+
+ for _, info := range infos {
+ n := info.Name()
+ matched, err := path.Match(pattern, n)
+ if err != nil {
+ return m, err
+ }
+ if matched {
+ m = append(m, path.Join(dir, n))
+ }
+ }
+ return
+}
+
+// hasMeta reports whether path contains any of the magic characters
+// recognized by path.Match.
+func hasMeta(path string) bool {
+ for i := 0; i < len(path); i++ {
+ c := path[i]
+ if c == '*' || c == '?' || c == '[' || runtime.GOOS == "windows" && c == '\\' {
+ return true
+ }
+ }
+ return false
+}
diff --git a/src/io/fs/glob_test.go b/src/io/fs/glob_test.go
new file mode 100644
index 0000000000..5c8ac3fbf3
--- /dev/null
+++ b/src/io/fs/glob_test.go
@@ -0,0 +1,86 @@
+// 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 fs_test
+
+import (
+ . "io/fs"
+ "os"
+ "path"
+ "testing"
+)
+
+var globTests = []struct {
+ fs FS
+ pattern, result string
+}{
+ {os.DirFS("."), "glob.go", "glob.go"},
+ {os.DirFS("."), "gl?b.go", "glob.go"},
+ {os.DirFS("."), "*", "glob.go"},
+ {os.DirFS(".."), "*/glob.go", "fs/glob.go"},
+}
+
+func TestGlob(t *testing.T) {
+ for _, tt := range globTests {
+ matches, err := Glob(tt.fs, tt.pattern)
+ if err != nil {
+ t.Errorf("Glob error for %q: %s", tt.pattern, err)
+ continue
+ }
+ if !contains(matches, tt.result) {
+ t.Errorf("Glob(%#q) = %#v want %v", tt.pattern, matches, tt.result)
+ }
+ }
+ for _, pattern := range []string{"no_match", "../*/no_match"} {
+ matches, err := Glob(os.DirFS("."), pattern)
+ if err != nil {
+ t.Errorf("Glob error for %q: %s", pattern, err)
+ continue
+ }
+ if len(matches) != 0 {
+ t.Errorf("Glob(%#q) = %#v want []", pattern, matches)
+ }
+ }
+}
+
+func TestGlobError(t *testing.T) {
+ bad := []string{`[]`, `nonexist/[]`}
+ for _, pattern := range bad {
+ _, err := Glob(os.DirFS("."), pattern)
+ if err != path.ErrBadPattern {
+ t.Errorf("Glob(fs, %#q) returned err=%v, want path.ErrBadPattern", pattern, err)
+ }
+ }
+}
+
+// contains reports whether vector contains the string s.
+func contains(vector []string, s string) bool {
+ for _, elem := range vector {
+ if elem == s {
+ return true
+ }
+ }
+ return false
+}
+
+type globOnly struct{ GlobFS }
+
+func (globOnly) Open(name string) (File, error) { return nil, ErrNotExist }
+
+func TestGlobMethod(t *testing.T) {
+ check := func(desc string, names []string, err error) {
+ t.Helper()
+ if err != nil || len(names) != 1 || names[0] != "hello.txt" {
+ t.Errorf("Glob(%s) = %v, %v, want %v, nil", desc, names, err, []string{"hello.txt"})
+ }
+ }
+
+ // Test that ReadDir uses the method when present.
+ names, err := Glob(globOnly{testFsys}, "*.txt")
+ check("readDirOnly", names, err)
+
+ // Test that ReadDir uses Open when the method is not present.
+ names, err = Glob(openOnly{testFsys}, "*.txt")
+ check("openOnly", names, err)
+}
diff --git a/src/io/fs/readdir.go b/src/io/fs/readdir.go
new file mode 100644
index 0000000000..3a5aa6d86a
--- /dev/null
+++ b/src/io/fs/readdir.go
@@ -0,0 +1,47 @@
+// Copyright 2020 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 fs
+
+import (
+ "errors"
+ "sort"
+)
+
+// ReadDirFS is the interface implemented by a file system
+// that provides an optimized implementation of ReadDir.
+type ReadDirFS interface {
+ FS
+
+ // ReadDir reads the named directory
+ // and returns a list of directory entries sorted by filename.
+ ReadDir(name string) ([]DirEntry, error)
+}
+
+// ReadDir reads the named directory
+// and returns a list of directory entries sorted by filename.
+//
+// If fs implements ReadDirFS, ReadDir calls fs.ReadDir.
+// Otherwise ReadDir calls fs.Open and uses ReadDir and Close
+// on the returned file.
+func ReadDir(fsys FS, name string) ([]DirEntry, error) {
+ if fsys, ok := fsys.(ReadDirFS); ok {
+ return fsys.ReadDir(name)
+ }
+
+ file, err := fsys.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ dir, ok := file.(ReadDirFile)
+ if !ok {
+ return nil, &PathError{Op: "readdir", Path: name, Err: errors.New("not implemented")}
+ }
+
+ list, err := dir.ReadDir(-1)
+ sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
+ return list, err
+}
diff --git a/src/io/fs/readdir_test.go b/src/io/fs/readdir_test.go
new file mode 100644
index 0000000000..46a4bc2788
--- /dev/null
+++ b/src/io/fs/readdir_test.go
@@ -0,0 +1,35 @@
+// Copyright 2020 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 fs_test
+
+import (
+ . "io/fs"
+ "testing"
+)
+
+type readDirOnly struct{ ReadDirFS }
+
+func (readDirOnly) Open(name string) (File, error) { return nil, ErrNotExist }
+
+func TestReadDir(t *testing.T) {
+ check := func(desc string, dirs []DirEntry, err error) {
+ t.Helper()
+ if err != nil || len(dirs) != 1 || dirs[0].Name() != "hello.txt" {
+ var names []string
+ for _, d := range dirs {
+ names = append(names, d.Name())
+ }
+ t.Errorf("ReadDir(%s) = %v, %v, want %v, nil", desc, names, err, []string{"hello.txt"})
+ }
+ }
+
+ // Test that ReadDir uses the method when present.
+ dirs, err := ReadDir(readDirOnly{testFsys}, ".")
+ check("readDirOnly", dirs, err)
+
+ // Test that ReadDir uses Open when the method is not present.
+ dirs, err = ReadDir(openOnly{testFsys}, ".")
+ check("openOnly", dirs, err)
+}
diff --git a/src/io/fs/readfile.go b/src/io/fs/readfile.go
new file mode 100644
index 0000000000..7ee9eadac4
--- /dev/null
+++ b/src/io/fs/readfile.go
@@ -0,0 +1,63 @@
+// Copyright 2020 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 fs
+
+import "io"
+
+// ReadFileFS is the interface implemented by a file system
+// that provides an optimized implementation of ReadFile.
+type ReadFileFS interface {
+ FS
+
+ // ReadFile reads the named file and returns its contents.
+ // A successful call returns a nil error, not io.EOF.
+ // (Because ReadFile reads the whole file, the expected EOF
+ // from the final Read is not treated as an error to be reported.)
+ ReadFile(name string) ([]byte, error)
+}
+
+// ReadFile reads the named file from the file system fs and returns its contents.
+// A successful call returns a nil error, not io.EOF.
+// (Because ReadFile reads the whole file, the expected EOF
+// from the final Read is not treated as an error to be reported.)
+//
+// If fs implements ReadFileFS, ReadFile calls fs.ReadFile.
+// Otherwise ReadFile calls fs.Open and uses Read and Close
+// on the returned file.
+func ReadFile(fsys FS, name string) ([]byte, error) {
+ if fsys, ok := fsys.(ReadFileFS); ok {
+ return fsys.ReadFile(name)
+ }
+
+ file, err := fsys.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ var size int
+ if info, err := file.Stat(); err == nil {
+ size64 := info.Size()
+ if int64(int(size64)) == size64 {
+ size = int(size64)
+ }
+ }
+
+ data := make([]byte, 0, size+1)
+ for {
+ if len(data) >= cap(data) {
+ d := append(data[:cap(data)], 0)
+ data = d[:len(data)]
+ }
+ n, err := file.Read(data[len(data):cap(data)])
+ data = data[:len(data)+n]
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ return data, err
+ }
+ }
+}
diff --git a/src/io/fs/readfile_test.go b/src/io/fs/readfile_test.go
new file mode 100644
index 0000000000..0afa334ace
--- /dev/null
+++ b/src/io/fs/readfile_test.go
@@ -0,0 +1,43 @@
+// Copyright 2020 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 fs_test
+
+import (
+ . "io/fs"
+ "testing"
+ "testing/fstest"
+ "time"
+)
+
+var testFsys = fstest.MapFS{
+ "hello.txt": {
+ Data: []byte("hello, world"),
+ Mode: 0456,
+ ModTime: time.Now(),
+ Sys: &sysValue,
+ },
+}
+
+var sysValue int
+
+type readFileOnly struct{ ReadFileFS }
+
+func (readFileOnly) Open(name string) (File, error) { return nil, ErrNotExist }
+
+type openOnly struct{ FS }
+
+func TestReadFile(t *testing.T) {
+ // Test that ReadFile uses the method when present.
+ data, err := ReadFile(readFileOnly{testFsys}, "hello.txt")
+ if string(data) != "hello, world" || err != nil {
+ t.Fatalf(`ReadFile(readFileOnly, "hello.txt") = %q, %v, want %q, nil`, data, err, "hello, world")
+ }
+
+ // Test that ReadFile uses Open when the method is not present.
+ data, err = ReadFile(openOnly{testFsys}, "hello.txt")
+ if string(data) != "hello, world" || err != nil {
+ t.Fatalf(`ReadFile(openOnly, "hello.txt") = %q, %v, want %q, nil`, data, err, "hello, world")
+ }
+}
diff --git a/src/io/fs/stat.go b/src/io/fs/stat.go
new file mode 100644
index 0000000000..735a6e3281
--- /dev/null
+++ b/src/io/fs/stat.go
@@ -0,0 +1,31 @@
+// Copyright 2020 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 fs
+
+// A StatFS is a file system with a Stat method.
+type StatFS interface {
+ FS
+
+ // Stat returns a FileInfo describing the file.
+ // If there is an error, it should be of type *PathError.
+ Stat(name string) (FileInfo, error)
+}
+
+// Stat returns a FileInfo describing the named file from the file system.
+//
+// If fs implements StatFS, Stat calls fs.Stat.
+// Otherwise, Stat opens the file to stat it.
+func Stat(fsys FS, name string) (FileInfo, error) {
+ if fsys, ok := fsys.(StatFS); ok {
+ return fsys.Stat(name)
+ }
+
+ file, err := fsys.Open(name)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ return file.Stat()
+}
diff --git a/src/io/fs/stat_test.go b/src/io/fs/stat_test.go
new file mode 100644
index 0000000000..e312b6fbd9
--- /dev/null
+++ b/src/io/fs/stat_test.go
@@ -0,0 +1,36 @@
+// Copyright 2020 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 fs_test
+
+import (
+ "fmt"
+ . "io/fs"
+ "testing"
+)
+
+type statOnly struct{ StatFS }
+
+func (statOnly) Open(name string) (File, error) { return nil, ErrNotExist }
+
+func TestStat(t *testing.T) {
+ check := func(desc string, info FileInfo, err error) {
+ t.Helper()
+ if err != nil || info == nil || info.Mode() != 0456 {
+ infoStr := "<nil>"
+ if info != nil {
+ infoStr = fmt.Sprintf("FileInfo(Mode: %#o)", info.Mode())
+ }
+ t.Fatalf("Stat(%s) = %v, %v, want Mode:0456, nil", desc, infoStr, err)
+ }
+ }
+
+ // Test that Stat uses the method when present.
+ info, err := Stat(statOnly{testFsys}, "hello.txt")
+ check("statOnly", info, err)
+
+ // Test that Stat uses Open when the method is not present.
+ info, err = Stat(openOnly{testFsys}, "hello.txt")
+ check("openOnly", info, err)
+}
diff --git a/src/io/io.go b/src/io/io.go
index 3dea70b947..ffd3cedc25 100644
--- a/src/io/io.go
+++ b/src/io/io.go
@@ -14,6 +14,7 @@ package io
import (
"errors"
+ "sync"
)
// Seek whence values.
@@ -27,10 +28,15 @@ const (
// but failed to return an explicit error.
var ErrShortWrite = errors.New("short write")
+// errInvalidWrite means that a write returned an impossible count.
+var errInvalidWrite = errors.New("invalid write result")
+
// ErrShortBuffer means that a read required a longer buffer than was provided.
var ErrShortBuffer = errors.New("short buffer")
// EOF is the error returned by Read when no more input is available.
+// (Read must return EOF itself, not an error wrapping EOF,
+// because callers will test for EOF using ==.)
// Functions should return EOF only to signal a graceful end of input.
// If the EOF occurs unexpectedly in a structured data stream,
// the appropriate error is either ErrUnexpectedEOF or some other error
@@ -41,9 +47,9 @@ var EOF = errors.New("EOF")
// middle of reading a fixed-size block or data structure.
var ErrUnexpectedEOF = errors.New("unexpected EOF")
-// ErrNoProgress is returned by some clients of an io.Reader when
+// ErrNoProgress is returned by some clients of an Reader when
// many calls to Read have failed to return any data or error,
-// usually the sign of a broken io.Reader implementation.
+// usually the sign of a broken Reader implementation.
var ErrNoProgress = errors.New("multiple Read calls return no data or error")
// Reader is the interface that wraps the basic Read method.
@@ -147,6 +153,14 @@ type ReadSeeker interface {
Seeker
}
+// ReadSeekCloser is the interface that groups the basic Read, Seek and Close
+// methods.
+type ReadSeekCloser interface {
+ Reader
+ Seeker
+ Closer
+}
+
// WriteSeeker is the interface that groups the basic Write and Seek methods.
type WriteSeeker interface {
Writer
@@ -164,7 +178,7 @@ type ReadWriteSeeker interface {
//
// ReadFrom reads data from r until EOF or error.
// The return value n is the number of bytes read.
-// Any error except io.EOF encountered during the read is also returned.
+// Any error except EOF encountered during the read is also returned.
//
// The Copy function uses ReaderFrom if available.
type ReaderFrom interface {
@@ -377,7 +391,7 @@ func Copy(dst Writer, src Reader) (written int64, err error) {
// buf will not be used to perform the copy.
func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
if buf != nil && len(buf) == 0 {
- panic("empty buffer in io.CopyBuffer")
+ panic("empty buffer in CopyBuffer")
}
return copyBuffer(dst, src, buf)
}
@@ -409,9 +423,13 @@ func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
- if nw > 0 {
- written += int64(nw)
+ if nw < 0 || nr < nw {
+ nw = 0
+ if ew == nil {
+ ew = errInvalidWrite
+ }
}
+ written += int64(nw)
if ew != nil {
err = ew
break
@@ -547,3 +565,78 @@ func (t *teeReader) Read(p []byte) (n int, err error) {
}
return
}
+
+// Discard is an Writer on which all Write calls succeed
+// without doing anything.
+var Discard Writer = discard{}
+
+type discard struct{}
+
+// discard implements ReaderFrom as an optimization so Copy to
+// io.Discard can avoid doing unnecessary work.
+var _ ReaderFrom = discard{}
+
+func (discard) Write(p []byte) (int, error) {
+ return len(p), nil
+}
+
+func (discard) WriteString(s string) (int, error) {
+ return len(s), nil
+}
+
+var blackHolePool = sync.Pool{
+ New: func() interface{} {
+ b := make([]byte, 8192)
+ return &b
+ },
+}
+
+func (discard) ReadFrom(r Reader) (n int64, err error) {
+ bufp := blackHolePool.Get().(*[]byte)
+ readSize := 0
+ for {
+ readSize, err = r.Read(*bufp)
+ n += int64(readSize)
+ if err != nil {
+ blackHolePool.Put(bufp)
+ if err == EOF {
+ return n, nil
+ }
+ return
+ }
+ }
+}
+
+// NopCloser returns a ReadCloser with a no-op Close method wrapping
+// the provided Reader r.
+func NopCloser(r Reader) ReadCloser {
+ return nopCloser{r}
+}
+
+type nopCloser struct {
+ Reader
+}
+
+func (nopCloser) Close() error { return nil }
+
+// ReadAll reads from r until an error or EOF and returns the data it read.
+// A successful call returns err == nil, not err == EOF. Because ReadAll is
+// defined to read from src until EOF, it does not treat an EOF from Read
+// as an error to be reported.
+func ReadAll(r Reader) ([]byte, error) {
+ b := make([]byte, 0, 512)
+ for {
+ if len(b) == cap(b) {
+ // Add more capacity (let append pick how much).
+ b = append(b, 0)[:len(b)]
+ }
+ n, err := r.Read(b[len(b):cap(b)])
+ b = b[:len(b)+n]
+ if err != nil {
+ if err == EOF {
+ err = nil
+ }
+ return b, err
+ }
+ }
+}
diff --git a/src/io/io_test.go b/src/io/io_test.go
index 170513dcc0..5b355e8c55 100644
--- a/src/io/io_test.go
+++ b/src/io/io_test.go
@@ -429,3 +429,31 @@ func TestSectionReader_Size(t *testing.T) {
}
}
}
+
+// largeWriter returns an invalid count that is larger than the number
+// of bytes provided (issue 39978).
+type largeWriter struct {
+ err error
+}
+
+func (w largeWriter) Write(p []byte) (int, error) {
+ return len(p) + 1, w.err
+}
+
+func TestCopyLargeWriter(t *testing.T) {
+ want := ErrInvalidWrite
+ rb := new(Buffer)
+ wb := largeWriter{}
+ rb.WriteString("hello, world.")
+ if _, err := Copy(wb, rb); err != want {
+ t.Errorf("Copy error: got %v, want %v", err, want)
+ }
+
+ want = errors.New("largeWriterError")
+ rb = new(Buffer)
+ wb = largeWriter{err: want}
+ rb.WriteString("hello, world.")
+ if _, err := Copy(wb, rb); err != want {
+ t.Errorf("Copy error: got %v, want %v", err, want)
+ }
+}
diff --git a/src/io/ioutil/ioutil.go b/src/io/ioutil/ioutil.go
index acc6ec3a40..a001c86b2f 100644
--- a/src/io/ioutil/ioutil.go
+++ b/src/io/ioutil/ioutil.go
@@ -6,43 +6,20 @@
package ioutil
import (
- "bytes"
"io"
+ "io/fs"
"os"
"sort"
- "sync"
)
-// readAll reads from r until an error or EOF and returns the data it read
-// from the internal buffer allocated with a specified capacity.
-func readAll(r io.Reader, capacity int64) (b []byte, err error) {
- var buf bytes.Buffer
- // If the buffer overflows, we will get bytes.ErrTooLarge.
- // Return that as an error. Any other panic remains.
- defer func() {
- e := recover()
- if e == nil {
- return
- }
- if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
- err = panicErr
- } else {
- panic(e)
- }
- }()
- if int64(int(capacity)) == capacity {
- buf.Grow(int(capacity))
- }
- _, err = buf.ReadFrom(r)
- return buf.Bytes(), err
-}
-
// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
+//
+// As of Go 1.16, this function simply calls io.ReadAll.
func ReadAll(r io.Reader) ([]byte, error) {
- return readAll(r, bytes.MinRead)
+ return io.ReadAll(r)
}
// ReadFile reads the file named by filename and returns the contents.
@@ -57,7 +34,8 @@ func ReadFile(filename string) ([]byte, error) {
defer f.Close()
// It's a good but not certain bet that FileInfo will tell us exactly how much to
// read, so let's try it but be prepared for the answer to be wrong.
- var n int64 = bytes.MinRead
+ const minRead = 512
+ var n int64 = minRead
if fi, err := f.Stat(); err == nil {
// As initial capacity for readAll, use Size + a little extra in case Size
@@ -66,17 +44,36 @@ func ReadFile(filename string) ([]byte, error) {
// cheaply. If the size was wrong, we'll either waste some space off the end
// or reallocate as needed, but in the overwhelmingly common case we'll get
// it just right.
- if size := fi.Size() + bytes.MinRead; size > n {
+ if size := fi.Size() + minRead; size > n {
n = size
}
}
- return readAll(f, n)
+
+ if int64(int(n)) != n {
+ n = minRead
+ }
+
+ b := make([]byte, 0, n)
+ for {
+ if len(b) == cap(b) {
+ // Add more capacity (let append pick how much).
+ b = append(b, 0)[:len(b)]
+ }
+ n, err := f.Read(b[len(b):cap(b)])
+ b = b[:len(b)+n]
+ if err != nil {
+ if err == io.EOF {
+ err = nil
+ }
+ return b, err
+ }
+ }
}
// WriteFile writes data to a file named by filename.
// If the file does not exist, WriteFile creates it with permissions perm
// (before umask); otherwise WriteFile truncates it before writing, without changing permissions.
-func WriteFile(filename string, data []byte, perm os.FileMode) error {
+func WriteFile(filename string, data []byte, perm fs.FileMode) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
@@ -90,7 +87,7 @@ func WriteFile(filename string, data []byte, perm os.FileMode) error {
// ReadDir reads the directory named by dirname and returns
// a list of directory entries sorted by filename.
-func ReadDir(dirname string) ([]os.FileInfo, error) {
+func ReadDir(dirname string) ([]fs.FileInfo, error) {
f, err := os.Open(dirname)
if err != nil {
return nil, err
@@ -104,55 +101,16 @@ func ReadDir(dirname string) ([]os.FileInfo, error) {
return list, nil
}
-type nopCloser struct {
- io.Reader
-}
-
-func (nopCloser) Close() error { return nil }
-
// NopCloser returns a ReadCloser with a no-op Close method wrapping
// the provided Reader r.
+//
+// As of Go 1.16, this function simply calls io.NopCloser.
func NopCloser(r io.Reader) io.ReadCloser {
- return nopCloser{r}
-}
-
-type devNull int
-
-// devNull implements ReaderFrom as an optimization so io.Copy to
-// ioutil.Discard can avoid doing unnecessary work.
-var _ io.ReaderFrom = devNull(0)
-
-func (devNull) Write(p []byte) (int, error) {
- return len(p), nil
-}
-
-func (devNull) WriteString(s string) (int, error) {
- return len(s), nil
-}
-
-var blackHolePool = sync.Pool{
- New: func() interface{} {
- b := make([]byte, 8192)
- return &b
- },
-}
-
-func (devNull) ReadFrom(r io.Reader) (n int64, err error) {
- bufp := blackHolePool.Get().(*[]byte)
- readSize := 0
- for {
- readSize, err = r.Read(*bufp)
- n += int64(readSize)
- if err != nil {
- blackHolePool.Put(bufp)
- if err == io.EOF {
- return n, nil
- }
- return
- }
- }
+ return io.NopCloser(r)
}
// Discard is an io.Writer on which all Write calls succeed
// without doing anything.
-var Discard io.Writer = devNull(0)
+//
+// As of Go 1.16, this value is simply io.Discard.
+var Discard io.Writer = io.Discard
diff --git a/src/io/ioutil/tempfile_test.go b/src/io/ioutil/tempfile_test.go
index fcc5101fcc..440c7cffc6 100644
--- a/src/io/ioutil/tempfile_test.go
+++ b/src/io/ioutil/tempfile_test.go
@@ -5,6 +5,7 @@
package ioutil_test
import (
+ "io/fs"
. "io/ioutil"
"os"
"path/filepath"
@@ -151,7 +152,7 @@ func TestTempDir_BadDir(t *testing.T) {
badDir := filepath.Join(dir, "not-exist")
_, err = TempDir(badDir, "foo")
- if pe, ok := err.(*os.PathError); !ok || !os.IsNotExist(err) || pe.Path != badDir {
+ if pe, ok := err.(*fs.PathError); !ok || !os.IsNotExist(err) || pe.Path != badDir {
t.Errorf("TempDir error = %#v; want PathError for path %q satisifying os.IsNotExist", err, badDir)
}
}
diff --git a/src/io/multi_test.go b/src/io/multi_test.go
index f05d5f74ef..909b6d8be2 100644
--- a/src/io/multi_test.go
+++ b/src/io/multi_test.go
@@ -10,7 +10,6 @@ import (
"errors"
"fmt"
. "io"
- "io/ioutil"
"runtime"
"strings"
"testing"
@@ -142,7 +141,7 @@ func testMultiWriter(t *testing.T, sink interface {
}
}
-// writerFunc is an io.Writer implemented by the underlying func.
+// writerFunc is an Writer implemented by the underlying func.
type writerFunc func(p []byte) (int, error)
func (f writerFunc) Write(p []byte) (int, error) {
@@ -196,7 +195,7 @@ func TestMultiReaderCopy(t *testing.T) {
slice := []Reader{strings.NewReader("hello world")}
r := MultiReader(slice...)
slice[0] = nil
- data, err := ioutil.ReadAll(r)
+ data, err := ReadAll(r)
if err != nil || string(data) != "hello world" {
t.Errorf("ReadAll() = %q, %v, want %q, nil", data, err, "hello world")
}
@@ -217,7 +216,7 @@ func TestMultiWriterCopy(t *testing.T) {
}
}
-// readerFunc is an io.Reader implemented by the underlying func.
+// readerFunc is an Reader implemented by the underlying func.
type readerFunc func(p []byte) (int, error)
func (f readerFunc) Read(p []byte) (int, error) {
@@ -261,7 +260,7 @@ func TestMultiReaderFlatten(t *testing.T) {
}
// byteAndEOFReader is a Reader which reads one byte (the underlying
-// byte) and io.EOF at once in its Read call.
+// byte) and EOF at once in its Read call.
type byteAndEOFReader byte
func (b byteAndEOFReader) Read(p []byte) (n int, err error) {
@@ -276,7 +275,7 @@ func (b byteAndEOFReader) Read(p []byte) (n int, err error) {
// This used to yield bytes forever; issue 16795.
func TestMultiReaderSingleByteWithEOF(t *testing.T) {
- got, err := ioutil.ReadAll(LimitReader(MultiReader(byteAndEOFReader('a'), byteAndEOFReader('b')), 10))
+ got, err := ReadAll(LimitReader(MultiReader(byteAndEOFReader('a'), byteAndEOFReader('b')), 10))
if err != nil {
t.Fatal(err)
}