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 }