1// Copyright 2010 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package os
6
7import (
8	"errors"
9	"internal/bytealg"
10	"internal/itoa"
11	_ "unsafe" // for go:linkname
12)
13
14// random number source provided by runtime.
15// We generate random temporary file names so that there's a good
16// chance the file doesn't exist yet - keeps the number of tries in
17// TempFile to a minimum.
18//
19//go:linkname runtime_rand runtime.rand
20func runtime_rand() uint64
21
22func nextRandom() string {
23	return itoa.Uitoa(uint(uint32(runtime_rand())))
24}
25
26// CreateTemp creates a new temporary file in the directory dir,
27// opens the file for reading and writing, and returns the resulting file.
28// The filename is generated by taking pattern and adding a random string to the end.
29// If pattern includes a "*", the random string replaces the last "*".
30// The file is created with mode 0o600 (before umask).
31// If dir is the empty string, CreateTemp uses the default directory for temporary files, as returned by [TempDir].
32// Multiple programs or goroutines calling CreateTemp simultaneously will not choose the same file.
33// The caller can use the file's Name method to find the pathname of the file.
34// It is the caller's responsibility to remove the file when it is no longer needed.
35func CreateTemp(dir, pattern string) (*File, error) {
36	if dir == "" {
37		dir = TempDir()
38	}
39
40	prefix, suffix, err := prefixAndSuffix(pattern)
41	if err != nil {
42		return nil, &PathError{Op: "createtemp", Path: pattern, Err: err}
43	}
44	prefix = joinPath(dir, prefix)
45
46	try := 0
47	for {
48		name := prefix + nextRandom() + suffix
49		f, err := OpenFile(name, O_RDWR|O_CREATE|O_EXCL, 0600)
50		if IsExist(err) {
51			if try++; try < 10000 {
52				continue
53			}
54			return nil, &PathError{Op: "createtemp", Path: prefix + "*" + suffix, Err: ErrExist}
55		}
56		return f, err
57	}
58}
59
60var errPatternHasSeparator = errors.New("pattern contains path separator")
61
62// prefixAndSuffix splits pattern by the last wildcard "*", if applicable,
63// returning prefix as the part before "*" and suffix as the part after "*".
64func prefixAndSuffix(pattern string) (prefix, suffix string, err error) {
65	for i := 0; i < len(pattern); i++ {
66		if IsPathSeparator(pattern[i]) {
67			return "", "", errPatternHasSeparator
68		}
69	}
70	if pos := bytealg.LastIndexByteString(pattern, '*'); pos != -1 {
71		prefix, suffix = pattern[:pos], pattern[pos+1:]
72	} else {
73		prefix = pattern
74	}
75	return prefix, suffix, nil
76}
77
78// MkdirTemp creates a new temporary directory in the directory dir
79// and returns the pathname of the new directory.
80// The new directory's name is generated by adding a random string to the end of pattern.
81// If pattern includes a "*", the random string replaces the last "*" instead.
82// The directory is created with mode 0o700 (before umask).
83// If dir is the empty string, MkdirTemp uses the default directory for temporary files, as returned by TempDir.
84// Multiple programs or goroutines calling MkdirTemp simultaneously will not choose the same directory.
85// It is the caller's responsibility to remove the directory when it is no longer needed.
86func MkdirTemp(dir, pattern string) (string, error) {
87	if dir == "" {
88		dir = TempDir()
89	}
90
91	prefix, suffix, err := prefixAndSuffix(pattern)
92	if err != nil {
93		return "", &PathError{Op: "mkdirtemp", Path: pattern, Err: err}
94	}
95	prefix = joinPath(dir, prefix)
96
97	try := 0
98	for {
99		name := prefix + nextRandom() + suffix
100		err := Mkdir(name, 0700)
101		if err == nil {
102			return name, nil
103		}
104		if IsExist(err) {
105			if try++; try < 10000 {
106				continue
107			}
108			return "", &PathError{Op: "mkdirtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist}
109		}
110		if IsNotExist(err) {
111			if _, err := Stat(dir); IsNotExist(err) {
112				return "", err
113			}
114		}
115		return "", err
116	}
117}
118
119func joinPath(dir, name string) string {
120	if len(dir) > 0 && IsPathSeparator(dir[len(dir)-1]) {
121		return dir + name
122	}
123	return dir + string(PathSeparator) + name
124}
125