Skip to content

Instantly share code, notes, and snippets.

@kostix
Last active February 11, 2016 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kostix/63949f9ff6d8fc79c6d8 to your computer and use it in GitHub Desktop.
Save kostix/63949f9ff6d8fc79c6d8 to your computer and use it in GitHub Desktop.
`tempfile` Go package mimicking mkstemp(3) and mkstemps(3) libc calls
package tmpfile
import (
"bytes"
"errors"
"os"
"sync/atomic"
"time"
)
func Mkstemp(tmpl string) (fd *os.File, err error) {
return create(tmpl, 0)
}
func Mkstemps(tmpl string, sfxlen int) (fd *os.File, err error) {
return create(tmpl, sfxlen)
}
var ErrExhaust = errors.New("Too many collisions when creating temporary file")
// These are the characters used in temporary filenames:
var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
var x6 = []byte{'X', 'X', 'X', 'X', 'X', 'X'}
// A shared volatile accumulator value.
// TODO ideally we should somehow preseed it with garbage.
var value uint64
// __gen_tempname (char *tmpl, int sfxlen, int flags, int kind)
func create(tmpl string, sfxlen int) (fd *os.File, err error) {
const (
// A lower bound on the number of temporary files to attempt to
// generate. The maximum total number of temporary file names that
// can exist for a given template is 62**6. It should never be
// necessary to try all these combinations. Instead if a reasonable
// number of names is tried (we define reasonable as 62**3) fail to
// give the system administrator the chance to remove the problems.
// Opengroups states that TMP_MAX must be at least 25,
// and that on OSI-compatible systems it should be ≥ 10000.
// See <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html>
ATTEMPTS_MIN = 62 * 62 * 62
)
b := []byte(tmpl)
tlen := len(tmpl)
if tlen < 6+sfxlen {
panic("Template string too short")
}
xs := b[tlen-6-sfxlen : tlen-sfxlen]
if bytes.Compare(xs, x6) != 0 {
panic("Invalid suffix length value")
}
// Get some more or less random data.
tm := time.Now()
sec := uint64(tm.Unix())
usec := uint64(tm.Nanosecond() / 1000)
random_time_bits := (usec << 16) ^ sec
v := atomic.AddUint64(&value, random_time_bits^uint64(os.Getpid()))
// The number of times to attempt to generate a temporary file. To
// conform to POSIX, this must be no smaller than TMP_MAX.
attempts := ATTEMPTS_MIN
for {
// Fill in the random bits:
xs[0] = letters[v%62]
v /= 62
xs[1] = letters[v%62]
v /= 62
xs[2] = letters[v%62]
v /= 62
xs[3] = letters[v%62]
v /= 62
xs[4] = letters[v%62]
v /= 62
xs[5] = letters[v%62]
s := string(b)
fd, err = os.OpenFile(s, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if err == nil {
break
}
if pe := err.(*os.PathError); pe.Err != os.ErrExist {
return
}
attempts--
if attempts == 0 {
err = &os.PathError{
Op: "Temporary file name creation",
Path: s,
Err: ErrExhaust,
}
}
v = atomic.AddUint64(&value, 7777)
}
return
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment