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