Skip to content

Commit 3d913a9

Browse files
committed
os: add ReadFile, WriteFile, CreateTemp (was TempFile), MkdirTemp (was TempDir) from io/ioutil
io/ioutil was a poorly defined collection of helpers. Proposal #40025 moved out the generic I/O helpers to io. This CL for proposal #42026 moves the OS-specific helpers to os, making the entire io/ioutil package deprecated. For #42026. Change-Id: I018bcb2115ef2ff1bc7ca36a9247eda429af21ad Reviewed-on: https://go-review.googlesource.com/c/go/+/266364 Trust: Russ Cox <[email protected]> Trust: Emmanuel Odeke <[email protected]> Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]>
1 parent 5984ea7 commit 3d913a9

File tree

12 files changed

+666
-58
lines changed

12 files changed

+666
-58
lines changed

src/io/ioutil/ioutil.go

+19-51
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
// license that can be found in the LICENSE file.
44

55
// Package ioutil implements some I/O utility functions.
6+
//
7+
// As of Go 1.16, the same functionality is now provided
8+
// by package io or package os, and those implementations
9+
// should be preferred in new code.
10+
// See the specific function documentation for details.
611
package ioutil
712

813
import (
@@ -26,67 +31,30 @@ func ReadAll(r io.Reader) ([]byte, error) {
2631
// A successful call returns err == nil, not err == EOF. Because ReadFile
2732
// reads the whole file, it does not treat an EOF from Read as an error
2833
// to be reported.
34+
//
35+
// As of Go 1.16, this function simply calls os.ReadFile.
2936
func ReadFile(filename string) ([]byte, error) {
30-
f, err := os.Open(filename)
31-
if err != nil {
32-
return nil, err
33-
}
34-
defer f.Close()
35-
// It's a good but not certain bet that FileInfo will tell us exactly how much to
36-
// read, so let's try it but be prepared for the answer to be wrong.
37-
const minRead = 512
38-
var n int64 = minRead
39-
40-
if fi, err := f.Stat(); err == nil {
41-
// As initial capacity for readAll, use Size + a little extra in case Size
42-
// is zero, and to avoid another allocation after Read has filled the
43-
// buffer. The readAll call will read into its allocated internal buffer
44-
// cheaply. If the size was wrong, we'll either waste some space off the end
45-
// or reallocate as needed, but in the overwhelmingly common case we'll get
46-
// it just right.
47-
if size := fi.Size() + minRead; size > n {
48-
n = size
49-
}
50-
}
51-
52-
if int64(int(n)) != n {
53-
n = minRead
54-
}
55-
56-
b := make([]byte, 0, n)
57-
for {
58-
if len(b) == cap(b) {
59-
// Add more capacity (let append pick how much).
60-
b = append(b, 0)[:len(b)]
61-
}
62-
n, err := f.Read(b[len(b):cap(b)])
63-
b = b[:len(b)+n]
64-
if err != nil {
65-
if err == io.EOF {
66-
err = nil
67-
}
68-
return b, err
69-
}
70-
}
37+
return os.ReadFile(filename)
7138
}
7239

7340
// WriteFile writes data to a file named by filename.
7441
// If the file does not exist, WriteFile creates it with permissions perm
7542
// (before umask); otherwise WriteFile truncates it before writing, without changing permissions.
43+
//
44+
// As of Go 1.16, this function simply calls os.WriteFile.
7645
func WriteFile(filename string, data []byte, perm fs.FileMode) error {
77-
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
78-
if err != nil {
79-
return err
80-
}
81-
_, err = f.Write(data)
82-
if err1 := f.Close(); err == nil {
83-
err = err1
84-
}
85-
return err
46+
return os.WriteFile(filename, data, perm)
8647
}
8748

8849
// ReadDir reads the directory named by dirname and returns
89-
// a list of directory entries sorted by filename.
50+
// a list of fs.FileInfo for the directory's contents,
51+
// sorted by filename. If an error occurs reading the directory,
52+
// ReadDir returns no directory entries along with the error.
53+
//
54+
// As of Go 1.16, os.ReadDir is a more efficient and correct choice:
55+
// it returns a list of fs.DirEntry instead of fs.FileInfo,
56+
// and it returns partial results in the case of an error
57+
// midway through reading a directory.
9058
func ReadDir(dirname string) ([]fs.FileInfo, error) {
9159
f, err := os.Open(dirname)
9260
if err != nil {

src/os/dir.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44

55
package os
66

7-
import "io/fs"
7+
import (
8+
"io/fs"
9+
"sort"
10+
)
811

912
type readdirMode int
1013

@@ -103,3 +106,20 @@ func (f *File) ReadDir(n int) ([]DirEntry, error) {
103106
// testingForceReadDirLstat forces ReadDir to call Lstat, for testing that code path.
104107
// This can be difficult to provoke on some Unix systems otherwise.
105108
var testingForceReadDirLstat bool
109+
110+
// ReadDir reads the named directory,
111+
// returning all its directory entries sorted by filename.
112+
// If an error occurs reading the directory,
113+
// ReadDir returns the entries it was able to read before the error,
114+
// along with the error.
115+
func ReadDir(name string) ([]DirEntry, error) {
116+
f, err := Open(name)
117+
if err != nil {
118+
return nil, err
119+
}
120+
defer f.Close()
121+
122+
dirs, err := f.ReadDir(-1)
123+
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
124+
return dirs, err
125+
}

src/os/example_test.go

+96
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io/fs"
1010
"log"
1111
"os"
12+
"path/filepath"
1213
"time"
1314
)
1415

@@ -144,3 +145,98 @@ func ExampleUnsetenv() {
144145
os.Setenv("TMPDIR", "/my/tmp")
145146
defer os.Unsetenv("TMPDIR")
146147
}
148+
149+
func ExampleReadDir() {
150+
files, err := os.ReadDir(".")
151+
if err != nil {
152+
log.Fatal(err)
153+
}
154+
155+
for _, file := range files {
156+
fmt.Println(file.Name())
157+
}
158+
}
159+
160+
func ExampleMkdirTemp() {
161+
dir, err := os.MkdirTemp("", "example")
162+
if err != nil {
163+
log.Fatal(err)
164+
}
165+
defer os.RemoveAll(dir) // clean up
166+
167+
file := filepath.Join(dir, "tmpfile")
168+
if err := os.WriteFile(file, []byte("content"), 0666); err != nil {
169+
log.Fatal(err)
170+
}
171+
}
172+
173+
func ExampleMkdirTemp_suffix() {
174+
logsDir, err := os.MkdirTemp("", "*-logs")
175+
if err != nil {
176+
log.Fatal(err)
177+
}
178+
defer os.RemoveAll(logsDir) // clean up
179+
180+
// Logs can be cleaned out earlier if needed by searching
181+
// for all directories whose suffix ends in *-logs.
182+
globPattern := filepath.Join(os.TempDir(), "*-logs")
183+
matches, err := filepath.Glob(globPattern)
184+
if err != nil {
185+
log.Fatalf("Failed to match %q: %v", globPattern, err)
186+
}
187+
188+
for _, match := range matches {
189+
if err := os.RemoveAll(match); err != nil {
190+
log.Printf("Failed to remove %q: %v", match, err)
191+
}
192+
}
193+
}
194+
195+
func ExampleCreateTemp() {
196+
f, err := os.CreateTemp("", "example")
197+
if err != nil {
198+
log.Fatal(err)
199+
}
200+
defer os.Remove(f.Name()) // clean up
201+
202+
if _, err := f.Write([]byte("content")); err != nil {
203+
log.Fatal(err)
204+
}
205+
if err := f.Close(); err != nil {
206+
log.Fatal(err)
207+
}
208+
}
209+
210+
func ExampleCreateTemp_suffix() {
211+
f, err := os.CreateTemp("", "example.*.txt")
212+
if err != nil {
213+
log.Fatal(err)
214+
}
215+
defer os.Remove(f.Name()) // clean up
216+
217+
if _, err := f.Write([]byte("content")); err != nil {
218+
f.Close()
219+
log.Fatal(err)
220+
}
221+
if err := f.Close(); err != nil {
222+
log.Fatal(err)
223+
}
224+
}
225+
226+
func ExampleReadFile() {
227+
data, err := os.ReadFile("testdata/hello")
228+
if err != nil {
229+
log.Fatal(err)
230+
}
231+
os.Stdout.Write(data)
232+
233+
// Output:
234+
// Hello, Gophers!
235+
}
236+
237+
func ExampleWriteFile() {
238+
err := os.WriteFile("testdata/hello", []byte("Hello, Gophers!"), 0666)
239+
if err != nil {
240+
log.Fatal(err)
241+
}
242+
}

src/os/export_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ var Atime = atime
1010
var LstatP = &lstat
1111
var ErrWriteAtInAppendMode = errWriteAtInAppendMode
1212
var TestingForceReadDirLstat = &testingForceReadDirLstat
13+
var ErrPatternHasSeparator = errPatternHasSeparator

src/os/file.go

+60
Original file line numberDiff line numberDiff line change
@@ -625,3 +625,63 @@ func (dir dirFS) Open(name string) (fs.File, error) {
625625
}
626626
return f, nil
627627
}
628+
629+
// ReadFile reads the named file and returns the contents.
630+
// A successful call returns err == nil, not err == EOF.
631+
// Because ReadFile reads the whole file, it does not treat an EOF from Read
632+
// as an error to be reported.
633+
func ReadFile(name string) ([]byte, error) {
634+
f, err := Open(name)
635+
if err != nil {
636+
return nil, err
637+
}
638+
defer f.Close()
639+
640+
var size int
641+
if info, err := f.Stat(); err == nil {
642+
size64 := info.Size()
643+
if int64(int(size64)) == size64 {
644+
size = int(size64)
645+
}
646+
}
647+
size++ // one byte for final read at EOF
648+
649+
// If a file claims a small size, read at least 512 bytes.
650+
// In particular, files in Linux's /proc claim size 0 but
651+
// then do not work right if read in small pieces,
652+
// so an initial read of 1 byte would not work correctly.
653+
if size < 512 {
654+
size = 512
655+
}
656+
657+
data := make([]byte, 0, size)
658+
for {
659+
if len(data) >= cap(data) {
660+
d := append(data[:cap(data)], 0)
661+
data = d[:len(data)]
662+
}
663+
n, err := f.Read(data[len(data):cap(data)])
664+
data = data[:len(data)+n]
665+
if err != nil {
666+
if err == io.EOF {
667+
err = nil
668+
}
669+
return data, err
670+
}
671+
}
672+
}
673+
674+
// WriteFile writes data to the named file, creating it if necessary.
675+
// If the file does not exist, WriteFile creates it with permissions perm (before umask);
676+
// otherwise WriteFile truncates it before writing, without changing permissions.
677+
func WriteFile(name string, data []byte, perm FileMode) error {
678+
f, err := OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm)
679+
if err != nil {
680+
return err
681+
}
682+
_, err = f.Write(data)
683+
if err1 := f.Close(); err1 != nil && err == nil {
684+
err = err1
685+
}
686+
return err
687+
}

src/os/os_test.go

+23-3
Original file line numberDiff line numberDiff line change
@@ -419,19 +419,19 @@ func testReadDir(dir string, contents []string, t *testing.T) {
419419
}
420420
}
421421

422-
func TestReaddirnames(t *testing.T) {
422+
func TestFileReaddirnames(t *testing.T) {
423423
testReaddirnames(".", dot, t)
424424
testReaddirnames(sysdir.name, sysdir.files, t)
425425
testReaddirnames(t.TempDir(), nil, t)
426426
}
427427

428-
func TestReaddir(t *testing.T) {
428+
func TestFileReaddir(t *testing.T) {
429429
testReaddir(".", dot, t)
430430
testReaddir(sysdir.name, sysdir.files, t)
431431
testReaddir(t.TempDir(), nil, t)
432432
}
433433

434-
func TestReadDir(t *testing.T) {
434+
func TestFileReadDir(t *testing.T) {
435435
testReadDir(".", dot, t)
436436
testReadDir(sysdir.name, sysdir.files, t)
437437
testReadDir(t.TempDir(), nil, t)
@@ -1235,6 +1235,7 @@ func TestChmod(t *testing.T) {
12351235
}
12361236

12371237
func checkSize(t *testing.T, f *File, size int64) {
1238+
t.Helper()
12381239
dir, err := f.Stat()
12391240
if err != nil {
12401241
t.Fatalf("Stat %q (looking for size %d): %s", f.Name(), size, err)
@@ -2690,3 +2691,22 @@ func TestDirFS(t *testing.T) {
26902691
t.Fatal(err)
26912692
}
26922693
}
2694+
2695+
func TestReadFileProc(t *testing.T) {
2696+
// Linux files in /proc report 0 size,
2697+
// but then if ReadFile reads just a single byte at offset 0,
2698+
// the read at offset 1 returns EOF instead of more data.
2699+
// ReadFile has a minimum read size of 512 to work around this,
2700+
// but test explicitly that it's working.
2701+
name := "/proc/sys/fs/pipe-max-size"
2702+
if _, err := Stat(name); err != nil {
2703+
t.Skip(err)
2704+
}
2705+
data, err := ReadFile(name)
2706+
if err != nil {
2707+
t.Fatal(err)
2708+
}
2709+
if len(data) == 0 || data[len(data)-1] != '\n' {
2710+
t.Fatalf("read %s: not newline-terminated: %q", name, data)
2711+
}
2712+
}

0 commit comments

Comments
 (0)