]> Cypherpunks repositories - gostls13.git/commitdiff
io/ioutil: change TempFile prefix to a pattern
authorTom Limoncelli <tal@whatexit.org>
Sun, 8 Apr 2018 20:05:04 +0000 (16:05 -0400)
committerBrad Fitzpatrick <bradfitz@golang.org>
Thu, 12 Apr 2018 20:00:25 +0000 (20:00 +0000)
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>

src/io/ioutil/example_test.go
src/io/ioutil/tempfile.go
src/io/ioutil/tempfile_test.go

index 53f71070d33d4109e31550d59ddabd643d69894e..0b24f672eecdb5abdc4a234b85dc289fd44fd67f 100644 (file)
@@ -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 {
index e5e315cfb7d48b6fcf07730b20b916abd8f5ba84..ba8783b9a0dc172e9d5b657cca5d39e0dc8b499b 100644 (file)
@@ -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 {
index 9d54bad2ffa32a26bf4bd9ded0fa57751b775f7b..0758890b6996efb9f9155032f5d21b100eab3bd7 100644 (file)
@@ -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)
                }
        }
 }