1 /**
2  * Copyright: Copyright Jason White, 2014-2016
3  * License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4  * Authors:   Jason White
5  */
6 module io.file.temp;
7 
8 import io.stream;
9 import io.file.stream;
10 
11 private version (Windows)
12 {
13     import core.sys.windows.windows;
14 
15     extern (Windows) nothrow export
16     {
17         UINT GetTempFileNameW(
18             LPCWSTR lpPathName,
19             LPCWSTR lpPrefixString,
20             UINT uUnique,
21             LPWSTR lpTempFileName
22         );
23 
24         DWORD GetTempPathW(
25             DWORD nBufferLength,
26             LPWSTR lpBuffer
27         );
28     }
29 }
30 
31 version (Posix)
32 private const(char*) tempDirImpl()
33 {
34     import core.sys.posix.stdlib;
35     import core.sys.posix.fcntl;
36 
37     static const(char*) isDir(const char *path)
38     {
39         stat_t statbuf = void;
40 
41         if (stat(path, &statbuf) == 0 && (statbuf.st_mode & S_IFMT) == S_IFDIR)
42             return path;
43 
44         return null;
45     }
46 
47     // TODO: Use secure_getenv, if available, instead?
48     if (auto path = getenv("TMPDIR")) return path;
49     if (auto path = getenv("TEMP")) return path;
50     if (auto path = getenv("TMP")) return path;
51 
52     if (auto path = isDir("/tmp")) return path;
53     if (auto path = isDir("/var/tmp")) return path;
54     if (auto path = isDir("/usr/tmp")) return path;
55 
56     return null;
57 }
58 
59 version (Windows)
60 private wstring tempDirImpl()
61 {
62     static wchar[MAX_PATH] buf;
63     immutable len = GetTempPathW(buf.length, buf.ptr);
64     return cast(wstring)(buf[0 .. len]);
65 }
66 
67 /**
68  * Returns the path to a directory for temporary files.
69  *
70  * On Windows, this function returns the result of calling the Windows API
71  * function
72  * $(LINK2 http://msdn.microsoft.com/en-us/library/windows/desktop/aa364992.aspx, $(D GetTempPath)).
73  *
74  * On POSIX platforms, it searches through the following list of directories and
75  * returns the first one which is found to exist:
76  * $(OL
77  *     $(LI The directory given by the $(D TMPDIR) environment variable.)
78  *     $(LI The directory given by the $(D TEMP) environment variable.)
79  *     $(LI The directory given by the $(D TMP) environment variable.)
80  *     $(LI $(D /tmp))
81  *     $(LI $(D /var/tmp))
82  *     $(LI $(D /usr/tmp))
83  * )
84  *
85  * On all platforms, $(D tempDir) returns $(D ".") on failure, representing the
86  * current working directory.
87  *
88  * The return value of the function is cached, so the procedures described above
89  * will only be performed the first time the function is called. All subsequent
90  * runs will return the same string, regardless of whether environment variables
91  * and directory structures have changed in the meantime.
92  *
93  * The POSIX $(D tempDir) algorithm is inspired by Python's
94  * $(LINK2 http://docs.python.org/library/tempfile.html#tempfile.tempdir, $(D tempfile.tempdir)).
95  */
96 T tempDir(T = string)() @trusted
97     if (is(T : string) || is(T : wstring))
98 {
99     import std.conv : to;
100 
101     static T cache;
102 
103     if (cache is null)
104     {
105         cache = tempDirImpl().to!T();
106         if (cache is null) cache = ".";
107     }
108 
109     return cache;
110 }
111 
112 /**
113  * Struct representing a temporary file. Returned by $(LREF tempFile).
114  */
115 struct TempFile(File, Path)
116 {
117     /**
118      * The opened file stream. If $(D AutoDelete.yes) is specified, when this is
119      * closed, the file is deleted.
120      */
121     File file;
122 
123     /**
124      * Path to the file. This is not guaranteed to exist if $(D AutoDelete.yes)
125      * is specified. For example, on POSIX, the file is deleted as soon as it is
126      * created such that, when the last file descriptor to it is closed, the
127      * file is deleted. If $(D AutoDelete.no) is specified, this path $(I is)
128      * guaranteed to exist.
129      */
130     Path path;
131 }
132 
133 /**
134  * Used with $(LREF tempFile) to choose if a temporary file should be deleted
135  * automatically when it is closed.
136  */
137 enum AutoDelete
138 {
139     no,
140     yes
141 }
142 
143 /**
144  * Creates a temporary file. The file is automatically deleted when it is no
145  * longer referenced. The temporary file is always opened with both read and
146  * write access.
147  *
148  * Params:
149  *  autoDelete = If set to $(D AutoDelete.yes) (the default), the file is
150  *               deleted from the file system after the file handle is closed.
151  *               Otherwise, the file must be deleted manually.
152  *  dir = Directory to create the temporary file in. By default, this is $(LREF
153  *        tempDir).
154  *
155  * Example:
156  * Creates a temporary file and writes to it.
157  * ---
158  * auto f = tempFile.file;
159  * assert(f.position == 0);
160  * f.write("Hello");
161  * assert(f.position == 5);
162  * ---
163  *
164  * Example:
165  * Creates a temporary file, but doesn't delete it. This is useful to ensure a
166  * uniquely named file exists so that it can be written to by another process.
167  * ---
168  * auto path = tempFile(AutoDelete.no).path;
169  * ---
170  */
171 version (Posix)
172 TempFile!(F, string) tempFile(F = File)(AutoDelete autoDelete = AutoDelete.yes,
173         string dir = tempDir)
174 {
175     /* Implementation note: Since Linux 3.11, there is the flag O_TMPFILE which
176      * can be used to open a temporary file. This creates an unnamed inode in
177      * the specified directory. Because the inode is unnamed, it will be
178      * automatically deleted once the file descriptor is closed. In the future,
179      * once Linux 3.11 is not so new, this flag could be used instead.
180      */
181 
182     import core.sys.posix.stdlib : mkstemp;
183     import core.sys.posix.unistd : unlink;
184     import std.exception : assumeUnique;
185 
186     char[] path = dir ~ "/XXXXXX\0".dup;
187 
188     int fd = mkstemp(path.ptr);
189     sysEnforce(fd != File.InvalidHandle,
190         "Failed to create temporary file '"~ path[0 .. $-1].idup ~"'"
191         );
192 
193     // Unlink the file to ensure it is deleted automatically when all
194     // file descriptors referring to it are closed.
195     if (autoDelete == AutoDelete.yes)
196         sysEnforce(unlink(path.ptr) == 0, "Failed to unlink temporary file");
197 
198     static if (is(F == class))
199         return typeof(return)(new F(fd), assumeUnique(path[0 .. $-1]));
200     else
201         return typeof(return)(F(fd), assumeUnique(path[0 .. $-1]));
202 }
203 
204 version (Windows)
205 TempFile!(F, T) tempFile(F = File, T = string)(
206         AutoDelete autoDelete = AutoDelete.yes, T dir = tempDir!T)
207     if ((is(T : string) || is(T : wstring)))
208 {
209     import std.conv : to;
210     import std.utf : toUTF16z;
211     import std.exception : assumeUnique;
212     import core.stdc.wchar_ : wcslen;
213 
214     wchar[MAX_PATH] buf;
215     sysEnforce(
216         GetTempFileNameW(toUTF16z(dir), "tmp", 0, buf.ptr),
217         "Failed to generate temporary file path"
218         );
219 
220     wchar[] path = buf[0 .. wcslen(buf.ptr)];
221 
222     auto h = CreateFileW(
223         // Temporary file name
224         path.ptr,
225 
226         // Desired access
227         GENERIC_READ | GENERIC_WRITE,
228 
229         // Share mode
230         FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
231 
232         // Security attributes
233         null,
234 
235         // Creation disposition. Note that GetTempFileName creates this file.
236         CREATE_ALWAYS,
237 
238         // Flags and attributes
239         FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_TEMPORARY |
240         ((autoDelete == AutoDelete.yes) ? FILE_FLAG_DELETE_ON_CLOSE : 0),
241 
242         // Template file
243         null,
244     );
245 
246     sysEnforce(
247         h != File.InvalidHandle,
248         "Failed to create temporary file '"~ path.to!string ~"'"
249     );
250 
251     static if (is(F == class))
252         return typeof(return)(new F(h), assumeUnique(path).to!T);
253     else
254         return typeof(return)(F(h), assumeUnique(path).to!T);
255 }
256 
257 unittest
258 {
259     auto f = tempFile.file;
260     assert(f.position == 0);
261     f.write("Hello");
262     assert(f.position == 5);
263 }