aboutsummaryrefslogtreecommitdiff
path: root/src/io
diff options
context:
space:
mode:
authorTom Limoncelli <tal@whatexit.org>2018-04-08 16:05:04 -0400
committerBrad Fitzpatrick <bradfitz@golang.org>2018-04-12 20:00:25 +0000
commit191efbc419d7e5dec842c20841f6f716da4b561d (patch)
treefc840d07b3c973c5aaf92b1afc779abd83084b9b /src/io
parent9be1921042b6b81b7f16fa8640ddc5868af6d31e (diff)
downloadgo-191efbc419d7e5dec842c20841f6f716da4b561d.tar.gz
go-191efbc419d7e5dec842c20841f6f716da4b561d.zip
io/ioutil: change TempFile prefix to a pattern
Users of TempFile need to be able to supply the suffix, especially when using operating systems that give semantic meaning to the filename extension such as Windows. Renaming the file to include an extension after the fact is insufficient as it could lead to race conditions. If the string given to TempFile includes a "*", the random string replaces the "*". For example "myname.*.bat" will result in a random filename such as "myname.123456.bat". If no "*' is included the old behavior is retained, and the random digits are appended to the end. If multiple "*" are included, the final one is replaced, thus permitting a pathological programmer to create filenames such as "foo*.123456.bat" but not "foo.123456.*.bat" Fixes #4896 Change-Id: Iae7f0980b4de6d7d31b87c8c3c3d40767b283c1f Reviewed-on: https://go-review.googlesource.com/105675 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
Diffstat (limited to 'src/io')
-rw-r--r--src/io/ioutil/example_test.go18
-rw-r--r--src/io/ioutil/tempfile.go24
-rw-r--r--src/io/ioutil/tempfile_test.go27
3 files changed, 53 insertions, 16 deletions
diff --git a/src/io/ioutil/example_test.go b/src/io/ioutil/example_test.go
index 53f71070d3..0b24f672ee 100644
--- a/src/io/ioutil/example_test.go
+++ b/src/io/ioutil/example_test.go
@@ -70,6 +70,24 @@ func ExampleTempFile() {
}
}
+func ExampleTempFile_suffix() {
+ content := []byte("temporary file's content")
+ tmpfile, err := ioutil.TempFile("", "example.*.txt")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ defer os.Remove(tmpfile.Name()) // clean up
+
+ if _, err := tmpfile.Write(content); err != nil {
+ tmpfile.Close()
+ log.Fatal(err)
+ }
+ if err := tmpfile.Close(); err != nil {
+ log.Fatal(err)
+ }
+}
+
func ExampleReadFile() {
content, err := ioutil.ReadFile("testdata/hello")
if err != nil {
diff --git a/src/io/ioutil/tempfile.go b/src/io/ioutil/tempfile.go
index e5e315cfb7..ba8783b9a0 100644
--- a/src/io/ioutil/tempfile.go
+++ b/src/io/ioutil/tempfile.go
@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"strconv"
+ "strings"
"sync"
"time"
)
@@ -23,7 +24,7 @@ func reseed() uint32 {
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
}
-func nextSuffix() string {
+func nextRandom() string {
randmu.Lock()
r := rand
if r == 0 {
@@ -35,23 +36,32 @@ func nextSuffix() string {
return strconv.Itoa(int(1e9 + r%1e9))[1:]
}
-// TempFile creates a new temporary file in the directory dir
-// with a name beginning with prefix, opens the file for reading
-// and writing, and returns the resulting *os.File.
+// TempFile creates a new temporary file in the directory dir,
+// opens the file for reading and writing, and returns the resulting *os.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 (see os.TempDir).
// Multiple programs calling TempFile simultaneously
// will not choose the same file. The caller can use f.Name()
// to find the pathname of the file. It is the caller's responsibility
// to remove the file when no longer needed.
-func TempFile(dir, prefix string) (f *os.File, err error) {
+func TempFile(dir, pattern string) (f *os.File, err error) {
if dir == "" {
dir = os.TempDir()
}
+ var prefix, suffix string
+ if pos := strings.LastIndex(pattern, "*"); pos != -1 {
+ prefix, suffix = pattern[:pos], pattern[pos+1:]
+ } else {
+ prefix = pattern
+ }
+
nconflict := 0
for i := 0; i < 10000; i++ {
- name := filepath.Join(dir, prefix+nextSuffix())
+ name := filepath.Join(dir, prefix+nextRandom()+suffix)
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if os.IsExist(err) {
if nconflict++; nconflict > 10 {
@@ -80,7 +90,7 @@ func TempDir(dir, prefix string) (name string, err error) {
nconflict := 0
for i := 0; i < 10000; i++ {
- try := filepath.Join(dir, prefix+nextSuffix())
+ try := filepath.Join(dir, prefix+nextRandom())
err = os.Mkdir(try, 0700)
if os.IsExist(err) {
if nconflict++; nconflict > 10 {
diff --git a/src/io/ioutil/tempfile_test.go b/src/io/ioutil/tempfile_test.go
index 9d54bad2ff..0758890b69 100644
--- a/src/io/ioutil/tempfile_test.go
+++ b/src/io/ioutil/tempfile_test.go
@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"regexp"
+ "strings"
"testing"
)
@@ -23,18 +24,26 @@ func TestTempFile(t *testing.T) {
if f != nil || err == nil {
t.Errorf("TempFile(%q, `foo`) = %v, %v", nonexistentDir, f, err)
}
+}
- dir = os.TempDir()
- f, err = TempFile(dir, "ioutil_test")
- if f == nil || err != nil {
- t.Errorf("TempFile(dir, `ioutil_test`) = %v, %v", f, err)
+func TestTempFile_pattern(t *testing.T) {
+ tests := []struct{ pattern, prefix, suffix string }{
+ {"ioutil_test", "ioutil_test", ""},
+ {"ioutil_test*", "ioutil_test", ""},
+ {"ioutil_test*xyz", "ioutil_test", "xyz"},
}
- if f != nil {
+ for _, test := range tests {
+ f, err := TempFile("", test.pattern)
+ if err != nil {
+ t.Errorf("TempFile(..., %q) error: %v", test.pattern, err)
+ continue
+ }
+ defer os.Remove(f.Name())
+ base := filepath.Base(f.Name())
f.Close()
- os.Remove(f.Name())
- re := regexp.MustCompile("^" + regexp.QuoteMeta(filepath.Join(dir, "ioutil_test")) + "[0-9]+$")
- if !re.MatchString(f.Name()) {
- t.Errorf("TempFile(`"+dir+"`, `ioutil_test`) created bad name %s", f.Name())
+ if !(strings.HasPrefix(base, test.prefix) && strings.HasSuffix(base, test.suffix)) {
+ t.Errorf("TempFile pattern %q created bad name %q; want prefix %q & suffix %q",
+ test.pattern, base, test.prefix, test.suffix)
}
}
}