mirror of
https://github.com/golang/go
synced 2024-11-11 19:51:37 -07:00
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 <rsc@golang.org> Trust: Emmanuel Odeke <emmanuel@orijtech.com> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
This commit is contained in:
parent
5984ea7197
commit
3d913a9266
@ -3,6 +3,11 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ioutil implements some I/O utility functions.
|
||||
//
|
||||
// As of Go 1.16, the same functionality is now provided
|
||||
// by package io or package os, and those implementations
|
||||
// should be preferred in new code.
|
||||
// See the specific function documentation for details.
|
||||
package ioutil
|
||||
|
||||
import (
|
||||
@ -26,67 +31,30 @@ func ReadAll(r io.Reader) ([]byte, error) {
|
||||
// A successful call returns err == nil, not err == EOF. Because ReadFile
|
||||
// reads the whole file, it does not treat an EOF from Read as an error
|
||||
// to be reported.
|
||||
//
|
||||
// As of Go 1.16, this function simply calls os.ReadFile.
|
||||
func ReadFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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.
|
||||
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
|
||||
// is zero, and to avoid another allocation after Read has filled the
|
||||
// buffer. The readAll call will read into its allocated internal buffer
|
||||
// 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() + minRead; size > n {
|
||||
n = size
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// As of Go 1.16, this function simply calls os.WriteFile.
|
||||
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
|
||||
}
|
||||
_, err = f.Write(data)
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
return os.WriteFile(filename, data, perm)
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns
|
||||
// a list of directory entries sorted by filename.
|
||||
// a list of fs.FileInfo for the directory's contents,
|
||||
// sorted by filename. If an error occurs reading the directory,
|
||||
// ReadDir returns no directory entries along with the error.
|
||||
//
|
||||
// As of Go 1.16, os.ReadDir is a more efficient and correct choice:
|
||||
// it returns a list of fs.DirEntry instead of fs.FileInfo,
|
||||
// and it returns partial results in the case of an error
|
||||
// midway through reading a directory.
|
||||
func ReadDir(dirname string) ([]fs.FileInfo, error) {
|
||||
f, err := os.Open(dirname)
|
||||
if err != nil {
|
||||
|
@ -4,7 +4,10 @@
|
||||
|
||||
package os
|
||||
|
||||
import "io/fs"
|
||||
import (
|
||||
"io/fs"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type readdirMode int
|
||||
|
||||
@ -103,3 +106,20 @@ func (f *File) ReadDir(n int) ([]DirEntry, error) {
|
||||
// testingForceReadDirLstat forces ReadDir to call Lstat, for testing that code path.
|
||||
// This can be difficult to provoke on some Unix systems otherwise.
|
||||
var testingForceReadDirLstat bool
|
||||
|
||||
// ReadDir reads the named directory,
|
||||
// returning all its directory entries sorted by filename.
|
||||
// If an error occurs reading the directory,
|
||||
// ReadDir returns the entries it was able to read before the error,
|
||||
// along with the error.
|
||||
func ReadDir(name string) ([]DirEntry, error) {
|
||||
f, err := Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dirs, err := f.ReadDir(-1)
|
||||
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
|
||||
return dirs, err
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -144,3 +145,98 @@ func ExampleUnsetenv() {
|
||||
os.Setenv("TMPDIR", "/my/tmp")
|
||||
defer os.Unsetenv("TMPDIR")
|
||||
}
|
||||
|
||||
func ExampleReadDir() {
|
||||
files, err := os.ReadDir(".")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
fmt.Println(file.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMkdirTemp() {
|
||||
dir, err := os.MkdirTemp("", "example")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir) // clean up
|
||||
|
||||
file := filepath.Join(dir, "tmpfile")
|
||||
if err := os.WriteFile(file, []byte("content"), 0666); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMkdirTemp_suffix() {
|
||||
logsDir, err := os.MkdirTemp("", "*-logs")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(logsDir) // clean up
|
||||
|
||||
// Logs can be cleaned out earlier if needed by searching
|
||||
// for all directories whose suffix ends in *-logs.
|
||||
globPattern := filepath.Join(os.TempDir(), "*-logs")
|
||||
matches, err := filepath.Glob(globPattern)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to match %q: %v", globPattern, err)
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
if err := os.RemoveAll(match); err != nil {
|
||||
log.Printf("Failed to remove %q: %v", match, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleCreateTemp() {
|
||||
f, err := os.CreateTemp("", "example")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f.Name()) // clean up
|
||||
|
||||
if _, err := f.Write([]byte("content")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleCreateTemp_suffix() {
|
||||
f, err := os.CreateTemp("", "example.*.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f.Name()) // clean up
|
||||
|
||||
if _, err := f.Write([]byte("content")); err != nil {
|
||||
f.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleReadFile() {
|
||||
data, err := os.ReadFile("testdata/hello")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Stdout.Write(data)
|
||||
|
||||
// Output:
|
||||
// Hello, Gophers!
|
||||
}
|
||||
|
||||
func ExampleWriteFile() {
|
||||
err := os.WriteFile("testdata/hello", []byte("Hello, Gophers!"), 0666)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
@ -10,3 +10,4 @@ var Atime = atime
|
||||
var LstatP = &lstat
|
||||
var ErrWriteAtInAppendMode = errWriteAtInAppendMode
|
||||
var TestingForceReadDirLstat = &testingForceReadDirLstat
|
||||
var ErrPatternHasSeparator = errPatternHasSeparator
|
||||
|
@ -625,3 +625,63 @@ func (dir dirFS) Open(name string) (fs.File, error) {
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ReadFile reads the named file and returns the contents.
|
||||
// A successful call returns err == nil, not err == EOF.
|
||||
// Because ReadFile reads the whole file, it does not treat an EOF from Read
|
||||
// as an error to be reported.
|
||||
func ReadFile(name string) ([]byte, error) {
|
||||
f, err := Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var size int
|
||||
if info, err := f.Stat(); err == nil {
|
||||
size64 := info.Size()
|
||||
if int64(int(size64)) == size64 {
|
||||
size = int(size64)
|
||||
}
|
||||
}
|
||||
size++ // one byte for final read at EOF
|
||||
|
||||
// If a file claims a small size, read at least 512 bytes.
|
||||
// In particular, files in Linux's /proc claim size 0 but
|
||||
// then do not work right if read in small pieces,
|
||||
// so an initial read of 1 byte would not work correctly.
|
||||
if size < 512 {
|
||||
size = 512
|
||||
}
|
||||
|
||||
data := make([]byte, 0, size)
|
||||
for {
|
||||
if len(data) >= cap(data) {
|
||||
d := append(data[:cap(data)], 0)
|
||||
data = d[:len(data)]
|
||||
}
|
||||
n, err := f.Read(data[len(data):cap(data)])
|
||||
data = data[:len(data)+n]
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteFile writes data to the named file, creating it if necessary.
|
||||
// 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(name string, data []byte, perm FileMode) error {
|
||||
f, err := OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write(data)
|
||||
if err1 := f.Close(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -419,19 +419,19 @@ func testReadDir(dir string, contents []string, t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaddirnames(t *testing.T) {
|
||||
func TestFileReaddirnames(t *testing.T) {
|
||||
testReaddirnames(".", dot, t)
|
||||
testReaddirnames(sysdir.name, sysdir.files, t)
|
||||
testReaddirnames(t.TempDir(), nil, t)
|
||||
}
|
||||
|
||||
func TestReaddir(t *testing.T) {
|
||||
func TestFileReaddir(t *testing.T) {
|
||||
testReaddir(".", dot, t)
|
||||
testReaddir(sysdir.name, sysdir.files, t)
|
||||
testReaddir(t.TempDir(), nil, t)
|
||||
}
|
||||
|
||||
func TestReadDir(t *testing.T) {
|
||||
func TestFileReadDir(t *testing.T) {
|
||||
testReadDir(".", dot, t)
|
||||
testReadDir(sysdir.name, sysdir.files, t)
|
||||
testReadDir(t.TempDir(), nil, t)
|
||||
@ -1235,6 +1235,7 @@ func TestChmod(t *testing.T) {
|
||||
}
|
||||
|
||||
func checkSize(t *testing.T, f *File, size int64) {
|
||||
t.Helper()
|
||||
dir, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("Stat %q (looking for size %d): %s", f.Name(), size, err)
|
||||
@ -2690,3 +2691,22 @@ func TestDirFS(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFileProc(t *testing.T) {
|
||||
// Linux files in /proc report 0 size,
|
||||
// but then if ReadFile reads just a single byte at offset 0,
|
||||
// the read at offset 1 returns EOF instead of more data.
|
||||
// ReadFile has a minimum read size of 512 to work around this,
|
||||
// but test explicitly that it's working.
|
||||
name := "/proc/sys/fs/pipe-max-size"
|
||||
if _, err := Stat(name); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
data, err := ReadFile(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data) == 0 || data[len(data)-1] != '\n' {
|
||||
t.Fatalf("read %s: not newline-terminated: %q", name, data)
|
||||
}
|
||||
}
|
||||
|
127
src/os/read_test.go
Normal file
127
src/os/read_test.go
Normal file
@ -0,0 +1,127 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package os_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
. "os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func checkNamedSize(t *testing.T, path string, size int64) {
|
||||
dir, err := Stat(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Stat %q (looking for size %d): %s", path, size, err)
|
||||
}
|
||||
if dir.Size() != size {
|
||||
t.Errorf("Stat %q: size %d want %d", path, dir.Size(), size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
filename := "rumpelstilzchen"
|
||||
contents, err := ReadFile(filename)
|
||||
if err == nil {
|
||||
t.Fatalf("ReadFile %s: error expected, none found", filename)
|
||||
}
|
||||
|
||||
filename = "read_test.go"
|
||||
contents, err = ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile %s: %v", filename, err)
|
||||
}
|
||||
|
||||
checkNamedSize(t, filename, int64(len(contents)))
|
||||
}
|
||||
|
||||
func TestWriteFile(t *testing.T) {
|
||||
f, err := CreateTemp("", "ioutil-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer Remove(f.Name())
|
||||
|
||||
msg := "Programming today is a race between software engineers striving to " +
|
||||
"build bigger and better idiot-proof programs, and the Universe trying " +
|
||||
"to produce bigger and better idiots. So far, the Universe is winning."
|
||||
|
||||
if err := WriteFile(f.Name(), []byte(msg), 0644); err != nil {
|
||||
t.Fatalf("WriteFile %s: %v", f.Name(), err)
|
||||
}
|
||||
|
||||
data, err := ReadFile(f.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile %s: %v", f.Name(), err)
|
||||
}
|
||||
|
||||
if string(data) != msg {
|
||||
t.Fatalf("ReadFile: wrong data:\nhave %q\nwant %q", string(data), msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadOnlyWriteFile(t *testing.T) {
|
||||
if Getuid() == 0 {
|
||||
t.Skipf("Root can write to read-only files anyway, so skip the read-only test.")
|
||||
}
|
||||
|
||||
// We don't want to use CreateTemp directly, since that opens a file for us as 0600.
|
||||
tempDir, err := MkdirTemp("", t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer RemoveAll(tempDir)
|
||||
filename := filepath.Join(tempDir, "blurp.txt")
|
||||
|
||||
shmorp := []byte("shmorp")
|
||||
florp := []byte("florp")
|
||||
err = WriteFile(filename, shmorp, 0444)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteFile %s: %v", filename, err)
|
||||
}
|
||||
err = WriteFile(filename, florp, 0444)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected an error when writing to read-only file %s", filename)
|
||||
}
|
||||
got, err := ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile %s: %v", filename, err)
|
||||
}
|
||||
if !bytes.Equal(got, shmorp) {
|
||||
t.Fatalf("want %s, got %s", shmorp, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadDir(t *testing.T) {
|
||||
dirname := "rumpelstilzchen"
|
||||
_, err := ReadDir(dirname)
|
||||
if err == nil {
|
||||
t.Fatalf("ReadDir %s: error expected, none found", dirname)
|
||||
}
|
||||
|
||||
dirname = "."
|
||||
list, err := ReadDir(dirname)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadDir %s: %v", dirname, err)
|
||||
}
|
||||
|
||||
foundFile := false
|
||||
foundSubDir := false
|
||||
for _, dir := range list {
|
||||
switch {
|
||||
case !dir.IsDir() && dir.Name() == "read_test.go":
|
||||
foundFile = true
|
||||
case dir.IsDir() && dir.Name() == "exec":
|
||||
foundSubDir = true
|
||||
}
|
||||
}
|
||||
if !foundFile {
|
||||
t.Fatalf("ReadDir %s: read_test.go file not found", dirname)
|
||||
}
|
||||
if !foundSubDir {
|
||||
t.Fatalf("ReadDir %s: exec directory not found", dirname)
|
||||
}
|
||||
}
|
@ -355,11 +355,12 @@ func TestRemoveAllButReadOnlyAndPathError(t *testing.T) {
|
||||
// The error should be of type *PathError.
|
||||
// see issue 30491 for details.
|
||||
if pathErr, ok := err.(*PathError); ok {
|
||||
if g, w := pathErr.Path, filepath.Join(tempDir, "b", "y"); g != w {
|
||||
t.Errorf("got %q, expected pathErr.path %q", g, w)
|
||||
want := filepath.Join(tempDir, "b", "y")
|
||||
if pathErr.Path != want {
|
||||
t.Errorf("RemoveAll(%q): err.Path=%q, want %q", tempDir, pathErr.Path, want)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("got %T, expected *fs.PathError", err)
|
||||
t.Errorf("RemoveAll(%q): error has type %T, want *fs.PathError", tempDir, err)
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
|
118
src/os/tempfile.go
Normal file
118
src/os/tempfile.go
Normal file
@ -0,0 +1,118 @@
|
||||
// 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 os
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// fastrand provided by runtime.
|
||||
// We generate random temporary file names so that there's a good
|
||||
// chance the file doesn't exist yet - keeps the number of tries in
|
||||
// TempFile to a minimum.
|
||||
func fastrand() uint32
|
||||
|
||||
func nextRandom() string {
|
||||
return uitoa(uint(fastrand()))
|
||||
}
|
||||
|
||||
// CreateTemp creates a new temporary file in the directory dir,
|
||||
// opens the file for reading and writing, and returns the resulting file.
|
||||
// The filename is generated by taking pattern and adding a random string to the end.
|
||||
// If pattern includes a "*", the random string replaces the last "*".
|
||||
// If dir is the empty string, TempFile uses the default directory for temporary files, as returned by TempDir.
|
||||
// Multiple programs or goroutines calling CreateTemp simultaneously will not choose the same file.
|
||||
// The caller can use the file's Name method to find the pathname of the file.
|
||||
// It is the caller's responsibility to remove the file when it is no longer needed.
|
||||
func CreateTemp(dir, pattern string) (*File, error) {
|
||||
if dir == "" {
|
||||
dir = TempDir()
|
||||
}
|
||||
|
||||
prefix, suffix, err := prefixAndSuffix(pattern)
|
||||
if err != nil {
|
||||
return nil, &PathError{Op: "createtemp", Path: pattern, Err: err}
|
||||
}
|
||||
prefix = joinPath(dir, prefix)
|
||||
|
||||
try := 0
|
||||
for {
|
||||
name := prefix + nextRandom() + suffix
|
||||
f, err := OpenFile(name, O_RDWR|O_CREATE|O_EXCL, 0600)
|
||||
if IsExist(err) {
|
||||
if try++; try < 10000 {
|
||||
continue
|
||||
}
|
||||
return nil, &PathError{Op: "createtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist}
|
||||
}
|
||||
return f, err
|
||||
}
|
||||
}
|
||||
|
||||
var errPatternHasSeparator = errors.New("pattern contains path separator")
|
||||
|
||||
// prefixAndSuffix splits pattern by the last wildcard "*", if applicable,
|
||||
// returning prefix as the part before "*" and suffix as the part after "*".
|
||||
func prefixAndSuffix(pattern string) (prefix, suffix string, err error) {
|
||||
for i := 0; i < len(pattern); i++ {
|
||||
if IsPathSeparator(pattern[i]) {
|
||||
return "", "", errPatternHasSeparator
|
||||
}
|
||||
}
|
||||
if pos := strings.LastIndex(pattern, "*"); pos != -1 {
|
||||
prefix, suffix = pattern[:pos], pattern[pos+1:]
|
||||
} else {
|
||||
prefix = pattern
|
||||
}
|
||||
return prefix, suffix, nil
|
||||
}
|
||||
|
||||
// MkdirTemp creates a new temporary directory in the directory dir
|
||||
// and returns the pathname of the new directory.
|
||||
// The new directory's name is generated by adding a random string to the end of pattern.
|
||||
// If pattern includes a "*", the random string replaces the last "*" instead.
|
||||
// If dir is the empty string, TempFile uses the default directory for temporary files, as returned by TempDir.
|
||||
// Multiple programs or goroutines calling MkdirTemp simultaneously will not choose the same directory.
|
||||
// It is the caller's responsibility to remove the directory when it is no longer needed.
|
||||
func MkdirTemp(dir, pattern string) (string, error) {
|
||||
if dir == "" {
|
||||
dir = TempDir()
|
||||
}
|
||||
|
||||
prefix, suffix, err := prefixAndSuffix(pattern)
|
||||
if err != nil {
|
||||
return "", &PathError{Op: "mkdirtemp", Path: pattern, Err: err}
|
||||
}
|
||||
prefix = joinPath(dir, prefix)
|
||||
|
||||
try := 0
|
||||
for {
|
||||
name := prefix + nextRandom() + suffix
|
||||
err := Mkdir(name, 0700)
|
||||
if err == nil {
|
||||
return name, nil
|
||||
}
|
||||
if IsExist(err) {
|
||||
if try++; try < 10000 {
|
||||
continue
|
||||
}
|
||||
return "", &PathError{Op: "mkdirtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist}
|
||||
}
|
||||
if IsNotExist(err) {
|
||||
if _, err := Stat(dir); IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
func joinPath(dir, name string) string {
|
||||
if len(dir) > 0 && IsPathSeparator(dir[len(dir)-1]) {
|
||||
return dir + name
|
||||
}
|
||||
return dir + string(PathSeparator) + name
|
||||
}
|
193
src/os/tempfile_test.go
Normal file
193
src/os/tempfile_test.go
Normal file
@ -0,0 +1,193 @@
|
||||
// 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 os_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
. "os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreateTemp(t *testing.T) {
|
||||
dir, err := MkdirTemp("", "TestCreateTempBadDir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer RemoveAll(dir)
|
||||
|
||||
nonexistentDir := filepath.Join(dir, "_not_exists_")
|
||||
f, err := CreateTemp(nonexistentDir, "foo")
|
||||
if f != nil || err == nil {
|
||||
t.Errorf("CreateTemp(%q, `foo`) = %v, %v", nonexistentDir, f, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTempPattern(t *testing.T) {
|
||||
tests := []struct{ pattern, prefix, suffix string }{
|
||||
{"tempfile_test", "tempfile_test", ""},
|
||||
{"tempfile_test*", "tempfile_test", ""},
|
||||
{"tempfile_test*xyz", "tempfile_test", "xyz"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
f, err := CreateTemp("", test.pattern)
|
||||
if err != nil {
|
||||
t.Errorf("CreateTemp(..., %q) error: %v", test.pattern, err)
|
||||
continue
|
||||
}
|
||||
defer Remove(f.Name())
|
||||
base := filepath.Base(f.Name())
|
||||
f.Close()
|
||||
if !(strings.HasPrefix(base, test.prefix) && strings.HasSuffix(base, test.suffix)) {
|
||||
t.Errorf("CreateTemp pattern %q created bad name %q; want prefix %q & suffix %q",
|
||||
test.pattern, base, test.prefix, test.suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateTempBadPattern(t *testing.T) {
|
||||
tmpDir, err := MkdirTemp("", t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer RemoveAll(tmpDir)
|
||||
|
||||
const sep = string(PathSeparator)
|
||||
tests := []struct {
|
||||
pattern string
|
||||
wantErr bool
|
||||
}{
|
||||
{"ioutil*test", false},
|
||||
{"tempfile_test*foo", false},
|
||||
{"tempfile_test" + sep + "foo", true},
|
||||
{"tempfile_test*" + sep + "foo", true},
|
||||
{"tempfile_test" + sep + "*foo", true},
|
||||
{sep + "tempfile_test" + sep + "*foo", true},
|
||||
{"tempfile_test*foo" + sep, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.pattern, func(t *testing.T) {
|
||||
tmpfile, err := CreateTemp(tmpDir, tt.pattern)
|
||||
if tmpfile != nil {
|
||||
defer tmpfile.Close()
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("CreateTemp(..., %#q) succeeded, expected error", tt.pattern)
|
||||
}
|
||||
if !errors.Is(err, ErrPatternHasSeparator) {
|
||||
t.Errorf("CreateTemp(..., %#q): %v, expected ErrPatternHasSeparator", tt.pattern, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("CreateTemp(..., %#q): %v", tt.pattern, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMkdirTemp(t *testing.T) {
|
||||
name, err := MkdirTemp("/_not_exists_", "foo")
|
||||
if name != "" || err == nil {
|
||||
t.Errorf("MkdirTemp(`/_not_exists_`, `foo`) = %v, %v", name, err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
pattern string
|
||||
wantPrefix, wantSuffix string
|
||||
}{
|
||||
{"tempfile_test", "tempfile_test", ""},
|
||||
{"tempfile_test*", "tempfile_test", ""},
|
||||
{"tempfile_test*xyz", "tempfile_test", "xyz"},
|
||||
}
|
||||
|
||||
dir := filepath.Clean(TempDir())
|
||||
|
||||
runTestMkdirTemp := func(t *testing.T, pattern, wantRePat string) {
|
||||
name, err := MkdirTemp(dir, pattern)
|
||||
if name == "" || err != nil {
|
||||
t.Fatalf("MkdirTemp(dir, `tempfile_test`) = %v, %v", name, err)
|
||||
}
|
||||
defer Remove(name)
|
||||
|
||||
re := regexp.MustCompile(wantRePat)
|
||||
if !re.MatchString(name) {
|
||||
t.Errorf("MkdirTemp(%q, %q) created bad name\n\t%q\ndid not match pattern\n\t%q", dir, pattern, name, wantRePat)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.pattern, func(t *testing.T) {
|
||||
wantRePat := "^" + regexp.QuoteMeta(filepath.Join(dir, tt.wantPrefix)) + "[0-9]+" + regexp.QuoteMeta(tt.wantSuffix) + "$"
|
||||
runTestMkdirTemp(t, tt.pattern, wantRePat)
|
||||
})
|
||||
}
|
||||
|
||||
// Separately testing "*xyz" (which has no prefix). That is when constructing the
|
||||
// pattern to assert on, as in the previous loop, using filepath.Join for an empty
|
||||
// prefix filepath.Join(dir, ""), produces the pattern:
|
||||
// ^<DIR>[0-9]+xyz$
|
||||
// yet we just want to match
|
||||
// "^<DIR>/[0-9]+xyz"
|
||||
t.Run("*xyz", func(t *testing.T) {
|
||||
wantRePat := "^" + regexp.QuoteMeta(filepath.Join(dir)) + regexp.QuoteMeta(string(filepath.Separator)) + "[0-9]+xyz$"
|
||||
runTestMkdirTemp(t, "*xyz", wantRePat)
|
||||
})
|
||||
}
|
||||
|
||||
// test that we return a nice error message if the dir argument to TempDir doesn't
|
||||
// exist (or that it's empty and TempDir doesn't exist)
|
||||
func TestMkdirTempBadDir(t *testing.T) {
|
||||
dir, err := MkdirTemp("", "MkdirTempBadDir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer RemoveAll(dir)
|
||||
|
||||
badDir := filepath.Join(dir, "not-exist")
|
||||
_, err = MkdirTemp(badDir, "foo")
|
||||
if pe, ok := err.(*fs.PathError); !ok || !IsNotExist(err) || pe.Path != badDir {
|
||||
t.Errorf("TempDir error = %#v; want PathError for path %q satisifying IsNotExist", err, badDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMkdirTempBadPattern(t *testing.T) {
|
||||
tmpDir, err := MkdirTemp("", t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer RemoveAll(tmpDir)
|
||||
|
||||
const sep = string(PathSeparator)
|
||||
tests := []struct {
|
||||
pattern string
|
||||
wantErr bool
|
||||
}{
|
||||
{"ioutil*test", false},
|
||||
{"tempfile_test*foo", false},
|
||||
{"tempfile_test" + sep + "foo", true},
|
||||
{"tempfile_test*" + sep + "foo", true},
|
||||
{"tempfile_test" + sep + "*foo", true},
|
||||
{sep + "tempfile_test" + sep + "*foo", true},
|
||||
{"tempfile_test*foo" + sep, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.pattern, func(t *testing.T) {
|
||||
_, err := MkdirTemp(tmpDir, tt.pattern)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("MkdirTemp(..., %#q) succeeded, expected error", tt.pattern)
|
||||
}
|
||||
if !errors.Is(err, ErrPatternHasSeparator) {
|
||||
t.Errorf("MkdirTemp(..., %#q): %v, expected ErrPatternHasSeparator", tt.pattern, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("MkdirTemp(..., %#q): %v", tt.pattern, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
1
src/os/testdata/hello
vendored
Normal file
1
src/os/testdata/hello
vendored
Normal file
@ -0,0 +1 @@
|
||||
Hello, Gophers!
|
@ -133,6 +133,9 @@ func sync_fastrand() uint32 { return fastrand() }
|
||||
//go:linkname net_fastrand net.fastrand
|
||||
func net_fastrand() uint32 { return fastrand() }
|
||||
|
||||
//go:linkname os_fastrand os.fastrand
|
||||
func os_fastrand() uint32 { return fastrand() }
|
||||
|
||||
// in internal/bytealg/equal_*.s
|
||||
//go:noescape
|
||||
func memequal(a, b unsafe.Pointer, size uintptr) bool
|
||||
|
Loading…
Reference in New Issue
Block a user