Commit 191efbc4 authored by Tom Limoncelli's avatar Tom Limoncelli Committed by Brad Fitzpatrick

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/105675Reviewed-by: 's avatarBrad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 9be19210
...@@ -70,6 +70,24 @@ func ExampleTempFile() { ...@@ -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() { func ExampleReadFile() {
content, err := ioutil.ReadFile("testdata/hello") content, err := ioutil.ReadFile("testdata/hello")
if err != nil { if err != nil {
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
) )
...@@ -23,7 +24,7 @@ func reseed() uint32 { ...@@ -23,7 +24,7 @@ func reseed() uint32 {
return uint32(time.Now().UnixNano() + int64(os.Getpid())) return uint32(time.Now().UnixNano() + int64(os.Getpid()))
} }
func nextSuffix() string { func nextRandom() string {
randmu.Lock() randmu.Lock()
r := rand r := rand
if r == 0 { if r == 0 {
...@@ -35,23 +36,32 @@ func nextSuffix() string { ...@@ -35,23 +36,32 @@ func nextSuffix() string {
return strconv.Itoa(int(1e9 + r%1e9))[1:] return strconv.Itoa(int(1e9 + r%1e9))[1:]
} }
// TempFile creates a new temporary file in the directory dir // TempFile creates a new temporary file in the directory dir,
// with a name beginning with prefix, opens the file for reading // opens the file for reading and writing, and returns the resulting *os.File.
// 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 // If dir is the empty string, TempFile uses the default directory
// for temporary files (see os.TempDir). // for temporary files (see os.TempDir).
// Multiple programs calling TempFile simultaneously // Multiple programs calling TempFile simultaneously
// will not choose the same file. The caller can use f.Name() // 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 find the pathname of the file. It is the caller's responsibility
// to remove the file when no longer needed. // 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 == "" { if dir == "" {
dir = os.TempDir() 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 nconflict := 0
for i := 0; i < 10000; i++ { 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) f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if os.IsExist(err) { if os.IsExist(err) {
if nconflict++; nconflict > 10 { if nconflict++; nconflict > 10 {
...@@ -80,7 +90,7 @@ func TempDir(dir, prefix string) (name string, err error) { ...@@ -80,7 +90,7 @@ func TempDir(dir, prefix string) (name string, err error) {
nconflict := 0 nconflict := 0
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
try := filepath.Join(dir, prefix+nextSuffix()) try := filepath.Join(dir, prefix+nextRandom())
err = os.Mkdir(try, 0700) err = os.Mkdir(try, 0700)
if os.IsExist(err) { if os.IsExist(err) {
if nconflict++; nconflict > 10 { if nconflict++; nconflict > 10 {
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"testing" "testing"
) )
...@@ -23,18 +24,26 @@ func TestTempFile(t *testing.T) { ...@@ -23,18 +24,26 @@ func TestTempFile(t *testing.T) {
if f != nil || err == nil { if f != nil || err == nil {
t.Errorf("TempFile(%q, `foo`) = %v, %v", nonexistentDir, f, err) t.Errorf("TempFile(%q, `foo`) = %v, %v", nonexistentDir, f, err)
} }
}
dir = os.TempDir() func TestTempFile_pattern(t *testing.T) {
f, err = TempFile(dir, "ioutil_test") tests := []struct{ pattern, prefix, suffix string }{
if f == nil || err != nil { {"ioutil_test", "ioutil_test", ""},
t.Errorf("TempFile(dir, `ioutil_test`) = %v, %v", f, err) {"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() f.Close()
os.Remove(f.Name()) if !(strings.HasPrefix(base, test.prefix) && strings.HasSuffix(base, test.suffix)) {
re := regexp.MustCompile("^" + regexp.QuoteMeta(filepath.Join(dir, "ioutil_test")) + "[0-9]+$") t.Errorf("TempFile pattern %q created bad name %q; want prefix %q & suffix %q",
if !re.MatchString(f.Name()) { test.pattern, base, test.prefix, test.suffix)
t.Errorf("TempFile(`"+dir+"`, `ioutil_test`) created bad name %s", f.Name())
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment