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
;
29 Encapsulated management of a memory-mapped file.
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
;
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
51 /// Name of underlying file, zero-terminated
52 private const(char)* name
;
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.
67 filename = the name of the file to be mapped in memory
69 this(const char* filename
)
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.
88 fprintf(stderr
, "open(\"%s\") failed: %s\n", filename
, strerror(errno
));
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);
100 fprintf(stderr
, "mmap(null, %zu) for \"%s\" failed: %s\n", cast(size_t
) size
, filename
, strerror(errno
));
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
;
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))
129 fprintf(stderr
, "CreateFileW() failed for \"%s\": %d\n", filename
, GetLastError());
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`
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
)
167 static if (is(Datum
== const))
169 enum fileMappingFlags
= PAGE_READONLY
;
170 enum mapViewFlags
= FILE_MAP_READ
;
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
186 auto p
= MapViewOfFile(fileMappingObject
, mapViewFlags
, 0, 0, 0);
189 fprintf(stderr
, "MapViewOfFile() failed for \"%s\": %d\n", filename
, GetLastError());
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.
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)
219 if (handle
!= invalidHandle
&& close(handle
) != 0)
223 handle
= invalidHandle
;
225 else version(Windows
)
227 if (data
.ptr
!is null && UnmapViewOfFile(cast(void*) data
.ptr
) == 0)
232 if (fileMappingObject
!= invalidHandle
&& CloseHandle(fileMappingObject
) == 0)
236 fileMappingObject
= invalidHandle
;
237 if (handle
!= invalidHandle
&& CloseHandle(handle
) == 0)
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.
260 handle
= invalidHandle
;
261 version(Windows
) fileMappingObject
= invalidHandle
;
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))
281 // Truncate file to zero so unflushed buffers are not flushed unnecessarily.
283 auto deleteme
= name
;
285 // In-memory resource freed, now get rid of the underlying temp file.
288 import core
.sys
.posix
.unistd
: unlink
;
289 if (unlink(deleteme
) != 0)
291 fprintf(stderr
, "unlink(\"%s\") failed: %s\n", filename
, strerror(errno
));
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());
304 else static assert(0);
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
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`.
337 size = new length requested
339 static if (!is(Datum
== const))
340 void resize(size_t size
) pure
342 assert(handle
!= invalidHandle
);
346 import core
.sys
.posix
.unistd
: ftruncate
;
347 import core
.sys
.posix
.sys
.mman
;
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);
356 if (ftruncate(handle
, size
) != 0)
358 fprintf(stderr
, "ftruncate() failed for \"%s\": %s\n", filename
, strerror(errno
));
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
));
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());
382 if (fileMappingObject
!= invalidHandle
&& CloseHandle(fileMappingObject
) == 0)
384 fprintf(stderr
, "CloseHandle() failed for memory mapping of \"%s\": %d\n", filename
, GetLastError());
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());
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
416 import core
.stdc
.stdlib
;
417 scope(exit
) free(cast(void*) oldname
);
421 // Rename the underlying file to the target, no copy necessary.
424 if (.rename(oldname
, filename
) != 0)
426 fprintf(stderr
, "rename(\"%s\", \"%s\") failed: %s\n", oldname
, filename
, strerror(errno
));
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
))
438 fprintf(stderr
, "MoveFileExW(\"%s\", \"%s\") failed: %d\n", oldname
, filename
, GetLastError());
442 else static assert(0);
447 /// Write a file, returning `true` on success.
448 extern(D
) static bool writeFile(const(char)* name
, const void[] data
) nothrow
452 int fd
= open(name
, O_CREAT | O_WRONLY | O_TRUNC
, (6 << 6) |
(4 << 3) |
4);
455 if (.write(fd
, data
.ptr
, data
.length
) != data
.length
)
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
,
478 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN
,
480 if (h
== INVALID_HANDLE_VALUE
)
483 if (WriteFile(h
, data
.ptr
, cast(DWORD
)data
.length
, &numwritten
, null) != TRUE
)
485 if (numwritten
!= data
.length
)
492 nameStr
.extendedPathThen
!(p
=> DeleteFileW(p
.ptr
));
502 /// Touch a file to current date
503 bool touchFile(const char* namez
)
508 SYSTEMTIME st
= void;
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
,
518 FILE_ATTRIBUTE_NORMAL
, null));
519 if (h
== INVALID_HANDLE_VALUE
)
522 const f
= SetFileTime(h
, null, null, &ft
); // set last write time
531 import core
.sys
.posix
.utime
;
532 return utime(namez
, null) == 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.
545 private ulong fileSize(int fd
)
547 import core
.sys
.posix
.sys
.stat
;
549 if (fstat(fd
, &buf
) == 0)
556 private ulong fileSize(HANDLE fd
)
559 if (GetFileSizeEx(fd
, cast(LARGE_INTEGER
*) &result
) == 0)
565 Runs a non-pure function or delegate as pure code. Use with caution.
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
)();