d: Merge upstream dmd 568496d5b, druntime 178c44ff, phobos 574bf883b.
[official-gcc.git] / gcc / d / dmd / common / file.d
blobb8cde3757360a5f68d395f7fe004f398037d58ed
1 /**
2 * File utilities.
4 * Functions and objects dedicated to file I/O and management. TODO: Move here artifacts
5 * from places such as root/ so both the frontend and the backend have access to them.
7 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
8 * Authors: Walter Bright, http://www.digitalmars.com
9 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
10 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/common/file.d, common/_file.d)
11 * Documentation: https://dlang.org/phobos/dmd_common_file.html
12 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/common/file.d
15 module dmd.common.file;
17 import core.stdc.errno : errno;
18 import core.stdc.stdio : fprintf, remove, rename, stderr;
19 import core.stdc.stdlib : exit;
20 import core.stdc.string : strerror;
21 import core.sys.windows.winbase;
22 import core.sys.windows.winnt;
23 import core.sys.posix.fcntl;
24 import core.sys.posix.unistd;
26 import dmd.common.string;
28 /**
29 Encapsulated management of a memory-mapped file.
31 Params:
32 Datum = the mapped data type: Use a POD of size 1 for read/write mapping
33 and a `const` version thereof for read-only mapping. Other primitive types
34 should work, but have not been yet tested.
36 struct FileMapping(Datum)
38 static assert(__traits(isPOD, Datum) && Datum.sizeof == 1,
39 "Not tested with other data types yet. Add new types with care.");
41 version(Posix) enum invalidHandle = -1;
42 else version(Windows) enum invalidHandle = INVALID_HANDLE_VALUE;
44 // state {
45 /// Handle of underlying file
46 private auto handle = invalidHandle;
47 /// File mapping object needed on Windows
48 version(Windows) private HANDLE fileMappingObject = invalidHandle;
49 /// Memory-mapped array
50 private Datum[] data;
51 /// Name of underlying file, zero-terminated
52 private const(char)* name;
53 // state }
55 /**
56 Open `filename` and map it in memory. If `Datum` is `const`, opens for
57 read-only and maps the content in memory; no error is issued if the file
58 does not exist. This makes it easy to treat a non-existing file as empty.
60 If `Datum` is mutable, opens for read/write (creates file if it does not
61 exist) and fails fatally on any error.
63 Due to quirks in `mmap`, if the file is empty, `handle` is valid but `data`
64 is `null`. This state is valid and accounted for.
66 Params:
67 filename = the name of the file to be mapped in memory
69 this(const char* filename)
71 version (Posix)
73 import core.sys.posix.sys.mman;
74 import core.sys.posix.fcntl : open, O_CREAT, O_RDONLY, O_RDWR, S_IRGRP, S_IROTH, S_IRUSR, S_IWUSR;
76 handle = open(filename, is(Datum == const) ? O_RDONLY : (O_CREAT | O_RDWR),
77 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
79 if (handle == invalidHandle)
81 static if (is(Datum == const))
83 // No error, nonexisting file in read mode behaves like an empty file.
84 return;
86 else
88 fprintf(stderr, "open(\"%s\") failed: %s\n", filename, strerror(errno));
89 exit(1);
93 const size = fileSize(handle);
95 if (size > 0 && size != ulong.max && size <= size_t.max)
97 auto p = mmap(null, cast(size_t) size, is(Datum == const) ? PROT_READ : PROT_WRITE, MAP_SHARED, handle, 0);
98 if (p == MAP_FAILED)
100 fprintf(stderr, "mmap(null, %zu) for \"%s\" failed: %s\n", cast(size_t) size, filename, strerror(errno));
101 exit(1);
103 // The cast below will always work because it's gated by the `size <= size_t.max` condition.
104 data = cast(Datum[]) p[0 .. cast(size_t) size];
107 else version(Windows)
109 static if (is(Datum == const))
111 enum createFileMode = GENERIC_READ;
112 enum openFlags = OPEN_EXISTING;
114 else
116 enum createFileMode = GENERIC_READ | GENERIC_WRITE;
117 enum openFlags = CREATE_ALWAYS;
120 handle = filename.asDString.extendedPathThen!(p => CreateFileW(p.ptr, createFileMode, 0, null, openFlags, FILE_ATTRIBUTE_NORMAL, null));
121 if (handle == invalidHandle)
123 static if (is(Datum == const))
125 return;
127 else
129 fprintf(stderr, "CreateFileW() failed for \"%s\": %d\n", filename, GetLastError());
130 exit(1);
133 createMapping(filename, fileSize(handle));
135 else static assert(0);
137 // Save the name for later. Technically there's no need: on Linux one can use readlink on /proc/self/fd/NNN.
138 // On BSD and OSX one can use fcntl with F_GETPATH. On Windows one can use GetFileInformationByHandleEx.
139 // But just saving the name is simplest, fastest, and most portable...
140 import core.stdc.string : strlen;
141 import core.stdc.stdlib : malloc;
142 import core.stdc.string : memcpy;
143 auto totalNameLength = filename.strlen() + 1;
144 name = cast(char*) memcpy(malloc(totalNameLength), filename, totalNameLength);
145 name || assert(0, "FileMapping: Out of memory.");
149 Common code factored opportunistically. Windows only. Assumes `handle` is
150 already pointing to an opened file. Initializes the `fileMappingObject`
151 and `data` members.
153 Params:
154 filename = the file to be mapped
155 size = the size of the file in bytes
157 version(Windows) private void createMapping(const char* filename, ulong size)
159 assert(size <= size_t.max || size == ulong.max);
160 assert(handle != invalidHandle);
161 assert(data is null);
162 assert(fileMappingObject == invalidHandle);
164 if (size == 0 || size == ulong.max)
165 return;
167 static if (is(Datum == const))
169 enum fileMappingFlags = PAGE_READONLY;
170 enum mapViewFlags = FILE_MAP_READ;
172 else
174 enum fileMappingFlags = PAGE_READWRITE;
175 enum mapViewFlags = FILE_MAP_WRITE;
178 fileMappingObject = CreateFileMappingW(handle, null, fileMappingFlags, 0, 0, null);
179 if (!fileMappingObject)
181 fprintf(stderr, "CreateFileMappingW(%p) failed for %llu bytes of \"%s\": %d\n",
182 handle, size, filename, GetLastError());
183 fileMappingObject = invalidHandle; // by convention always use invalidHandle, not null
184 exit(1);
186 auto p = MapViewOfFile(fileMappingObject, mapViewFlags, 0, 0, 0);
187 if (!p)
189 fprintf(stderr, "MapViewOfFile() failed for \"%s\": %d\n", filename, GetLastError());
190 exit(1);
192 data = cast(Datum[]) p[0 .. cast(size_t) size];
195 // Not copyable or assignable (for now).
196 @disable this(const FileMapping!Datum rhs);
197 @disable void opAssign(const ref FileMapping!Datum rhs);
200 Frees resources associated with this mapping. However, it does not deallocate the name.
202 ~this() pure nothrow
204 if (!active)
205 return;
206 fakePure({
207 version (Posix)
209 import core.sys.posix.sys.mman : munmap;
210 import core.sys.posix.unistd : close;
212 // Cannot call fprintf from inside a destructor, so exiting silently.
214 if (data.ptr && munmap(cast(void*) data.ptr, data.length) != 0)
216 exit(1);
218 data = null;
219 if (handle != invalidHandle && close(handle) != 0)
221 exit(1);
223 handle = invalidHandle;
225 else version(Windows)
227 if (data.ptr !is null && UnmapViewOfFile(cast(void*) data.ptr) == 0)
229 exit(1);
231 data = null;
232 if (fileMappingObject != invalidHandle && CloseHandle(fileMappingObject) == 0)
234 exit(1);
236 fileMappingObject = invalidHandle;
237 if (handle != invalidHandle && CloseHandle(handle) == 0)
239 exit(1);
241 handle = invalidHandle;
243 else static assert(0);
248 Returns the zero-terminated file name associated with the mapping. Can NOT
249 be saved beyond the lifetime of `this`.
251 private const(char)* filename() const pure @nogc @safe nothrow { return name; }
254 Frees resources associated with this mapping. However, it does not deallocate the name.
255 Reinitializes `this` as a fresh object that can be reused.
257 void close()
259 __dtor();
260 handle = invalidHandle;
261 version(Windows) fileMappingObject = invalidHandle;
262 data = null;
263 name = null;
267 Deletes the underlying file and frees all resources associated.
268 Reinitializes `this` as a fresh object that can be reused.
270 This function does not abort if the file cannot be deleted, but does print
271 a message on `stderr` and returns `false` to the caller. The underlying
272 rationale is to give the caller the option to continue execution if
273 deleting the file is not important.
275 Returns: `true` iff the file was successfully deleted. If the file was not
276 deleted, prints a message to `stderr` and returns `false`.
278 static if (!is(Datum == const))
279 bool discard()
281 // Truncate file to zero so unflushed buffers are not flushed unnecessarily.
282 resize(0);
283 auto deleteme = name;
284 close();
285 // In-memory resource freed, now get rid of the underlying temp file.
286 version(Posix)
288 import core.sys.posix.unistd : unlink;
289 if (unlink(deleteme) != 0)
291 fprintf(stderr, "unlink(\"%s\") failed: %s\n", filename, strerror(errno));
292 return false;
295 else version(Windows)
297 import core.sys.windows.winbase;
298 if (deleteme.asDString.extendedPathThen!(p => DeleteFileW(p.ptr)) == 0)
300 fprintf(stderr, "DeleteFileW error %d\n", GetLastError());
301 return false;
304 else static assert(0);
305 return true;
309 Queries whether `this` is currently associated with a file.
311 Returns: `true` iff there is an active mapping.
313 bool active() const pure @nogc nothrow
315 return handle !is invalidHandle;
319 Queries the length of the file associated with this mapping. If not
320 active, returns 0.
322 Returns: the length of the file, or 0 if no file associated.
324 size_t length() const pure @nogc @safe nothrow { return data.length; }
327 Get a slice to the contents of the entire file.
329 Returns: the contents of the file. If not active, returns the `null` slice.
331 auto opSlice() pure @nogc @safe nothrow { return data; }
334 Resizes the file and mapping to the specified `size`.
336 Params:
337 size = new length requested
339 static if (!is(Datum == const))
340 void resize(size_t size) pure
342 assert(handle != invalidHandle);
343 fakePure({
344 version(Posix)
346 import core.sys.posix.unistd : ftruncate;
347 import core.sys.posix.sys.mman;
349 if (data.length)
351 assert(data.ptr, "Corrupt memory mapping");
352 // assert(0) here because it would indicate an internal error
353 munmap(cast(void*) data.ptr, data.length) == 0 || assert(0);
354 data = null;
356 if (ftruncate(handle, size) != 0)
358 fprintf(stderr, "ftruncate() failed for \"%s\": %s\n", filename, strerror(errno));
359 exit(1);
361 if (size > 0)
363 auto p = mmap(null, size, PROT_WRITE, MAP_SHARED, handle, 0);
364 if (cast(ssize_t) p == -1)
366 fprintf(stderr, "mmap() failed for \"%s\": %s\n", filename, strerror(errno));
367 exit(1);
369 data = cast(Datum[]) p[0 .. size];
372 else version(Windows)
374 // Per documentation, must unmap first.
375 if (data.length > 0 && UnmapViewOfFile(cast(void*) data.ptr) == 0)
377 fprintf(stderr, "UnmapViewOfFile(%p) failed for memory mapping of \"%s\": %d\n",
378 data.ptr, filename, GetLastError());
379 exit(1);
381 data = null;
382 if (fileMappingObject != invalidHandle && CloseHandle(fileMappingObject) == 0)
384 fprintf(stderr, "CloseHandle() failed for memory mapping of \"%s\": %d\n", filename, GetLastError());
385 exit(1);
387 fileMappingObject = invalidHandle;
388 LARGE_INTEGER biggie;
389 biggie.QuadPart = size;
390 if (SetFilePointerEx(handle, biggie, null, FILE_BEGIN) == 0 || SetEndOfFile(handle) == 0)
392 fprintf(stderr, "SetFilePointer() failed for \"%s\": %d\n", filename, GetLastError());
393 exit(1);
395 createMapping(name, size);
397 else static assert(0);
402 Unconditionally and destructively moves the underlying file to `filename`.
403 If the operation succeeds, returns true. Upon failure, prints a message to
404 `stderr` and returns `false`. In all cases it closes the underlying file.
406 Params: filename = zero-terminated name of the file to move to.
408 Returns: `true` iff the operation was successful.
410 bool moveToFile(const char* filename)
412 assert(name !is null);
414 // Fetch the name and then set it to `null` so it doesn't get deallocated
415 auto oldname = name;
416 import core.stdc.stdlib;
417 scope(exit) free(cast(void*) oldname);
418 name = null;
419 close();
421 // Rename the underlying file to the target, no copy necessary.
422 version(Posix)
424 if (.rename(oldname, filename) != 0)
426 fprintf(stderr, "rename(\"%s\", \"%s\") failed: %s\n", oldname, filename, strerror(errno));
427 return false;
430 else version(Windows)
432 import core.sys.windows.winbase;
433 auto r = oldname.asDString.extendedPathThen!(
434 p1 => filename.asDString.extendedPathThen!(p2 => MoveFileExW(p1.ptr, p2.ptr, MOVEFILE_REPLACE_EXISTING))
436 if (r == 0)
438 fprintf(stderr, "MoveFileExW(\"%s\", \"%s\") failed: %d\n", oldname, filename, GetLastError());
439 return false;
442 else static assert(0);
443 return true;
447 /// Write a file, returning `true` on success.
448 extern(D) static bool writeFile(const(char)* name, const void[] data) nothrow
450 version (Posix)
452 int fd = open(name, O_CREAT | O_WRONLY | O_TRUNC, (6 << 6) | (4 << 3) | 4);
453 if (fd == -1)
454 goto err;
455 if (.write(fd, data.ptr, data.length) != data.length)
456 goto err2;
457 if (close(fd) == -1)
458 goto err;
459 return true;
460 err2:
461 close(fd);
462 .remove(name);
463 err:
464 return false;
466 else version (Windows)
468 DWORD numwritten; // here because of the gotos
469 const nameStr = name.asDString;
470 // work around Windows file path length limitation
471 // (see documentation for extendedPathThen).
472 HANDLE h = nameStr.extendedPathThen!
473 (p => CreateFileW(p.ptr,
474 GENERIC_WRITE,
476 null,
477 CREATE_ALWAYS,
478 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
479 null));
480 if (h == INVALID_HANDLE_VALUE)
481 goto err;
483 if (WriteFile(h, data.ptr, cast(DWORD)data.length, &numwritten, null) != TRUE)
484 goto err2;
485 if (numwritten != data.length)
486 goto err2;
487 if (!CloseHandle(h))
488 goto err;
489 return true;
490 err2:
491 CloseHandle(h);
492 nameStr.extendedPathThen!(p => DeleteFileW(p.ptr));
493 err:
494 return false;
496 else
498 static assert(0);
502 /// Touch a file to current date
503 bool touchFile(const char* namez)
505 version (Windows)
507 FILETIME ft = void;
508 SYSTEMTIME st = void;
509 GetSystemTime(&st);
510 SystemTimeToFileTime(&st, &ft);
512 import core.stdc.string : strlen;
514 // get handle to file
515 HANDLE h = namez[0 .. namez.strlen()].extendedPathThen!(p => CreateFile(p.ptr,
516 FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE,
517 null, OPEN_EXISTING,
518 FILE_ATTRIBUTE_NORMAL, null));
519 if (h == INVALID_HANDLE_VALUE)
520 return false;
522 const f = SetFileTime(h, null, null, &ft); // set last write time
524 if (!CloseHandle(h))
525 return false;
527 return f != 0;
529 else version (Posix)
531 import core.sys.posix.utime;
532 return utime(namez, null) == 0;
534 else
535 static assert(0);
538 // Feel free to make these public if used elsewhere.
540 Size of a file in bytes.
541 Params: fd = file handle
542 Returns: file size in bytes, or `ulong.max` on any error.
544 version (Posix)
545 private ulong fileSize(int fd)
547 import core.sys.posix.sys.stat;
548 stat_t buf;
549 if (fstat(fd, &buf) == 0)
550 return buf.st_size;
551 return ulong.max;
554 /// Ditto
555 version (Windows)
556 private ulong fileSize(HANDLE fd)
558 ulong result;
559 if (GetFileSizeEx(fd, cast(LARGE_INTEGER*) &result) == 0)
560 return result;
561 return ulong.max;
565 Runs a non-pure function or delegate as pure code. Use with caution.
567 Params:
568 fun = the delegate to run, usually inlined: `fakePure({ ... });`
570 Returns: whatever `fun` returns.
572 private auto ref fakePure(F)(scope F fun) pure
574 mixin("alias PureFun = " ~ F.stringof ~ " pure;");
575 return (cast(PureFun) fun)();