1 /**
2  * Read a file from disk and store it in memory.
3  *
4  * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved
5  * Authors:   Walter Bright, http://www.digitalmars.com
6  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:    $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/root/file.d, root/_file.d)
8  * Documentation:  https://dlang.org/phobos/dmd_root_file.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/file.d
10  */
11 
12 module dmd.root.file;
13 
14 import core.stdc.errno;
15 import core.stdc.stdio;
16 import core.stdc.stdlib;
17 import core.sys.posix.fcntl;
18 import core.sys.posix.unistd;
19 import core.sys.windows.winbase;
20 import core.sys.windows.winnt;
21 import dmd.root.filename;
22 import dmd.root.rmem;
23 import dmd.root.string;
24 
25 /// Owns a (rmem-managed) file buffer.
26 struct FileBuffer
27 {
28     ubyte[] data;
29 
30     this(this) @disable;
31 
32     ~this() pure nothrow
33     {
34         mem.xfree(data.ptr);
35     }
36 
37     /// Transfers ownership of the buffer to the caller.
38     ubyte[] extractSlice() pure nothrow @nogc @safe
39     {
40         auto result = data;
41         data = null;
42         return result;
43     }
44 
45     extern (C++) static FileBuffer* create()
46     {
47         return new FileBuffer();
48     }
49 }
50 
51 ///
52 struct File
53 {
54     ///
55     static struct ReadResult
56     {
57         bool success;
58         FileBuffer buffer;
59 
60         /// Transfers ownership of the buffer to the caller.
61         ubyte[] extractSlice() pure nothrow @nogc @safe
62         {
63             return buffer.extractSlice();
64         }
65 
66         /// ditto
67         /// Include the null-terminator at the end of the buffer in the returned array.
68         ubyte[] extractDataZ() @nogc nothrow pure
69         {
70             auto result = buffer.extractSlice();
71             return result.ptr[0 .. result.length + 1];
72         }
73     }
74 
75 nothrow:
76     /// Read the full content of a file.
77     extern (C++) static ReadResult read(const(char)* name)
78     {
79         return read(name.toDString());
80     }
81 
82     /// Ditto
83     static ReadResult read(const(char)[] name)
84     {
85         ReadResult result;
86 
87         version (Posix)
88         {
89             size_t size;
90             stat_t buf;
91             ssize_t numread;
92             //printf("File::read('%s')\n",name);
93             int fd = name.toCStringThen!(slice => open(slice.ptr, O_RDONLY));
94             if (fd == -1)
95             {
96                 //printf("\topen error, errno = %d\n",errno);
97                 return result;
98             }
99             //printf("\tfile opened\n");
100             if (fstat(fd, &buf))
101             {
102                 printf("\tfstat error, errno = %d\n", errno);
103                 close(fd);
104                 return result;
105             }
106             size = cast(size_t)buf.st_size;
107             ubyte* buffer = cast(ubyte*)mem.xmalloc_noscan(size + 4);
108             numread = .read(fd, buffer, size);
109             if (numread != size)
110             {
111                 printf("\tread error, errno = %d\n", errno);
112                 goto err2;
113             }
114             if (close(fd) == -1)
115             {
116                 printf("\tclose error, errno = %d\n", errno);
117                 goto err;
118             }
119             // Always store a wchar ^Z past end of buffer so scanner has a sentinel
120             buffer[size] = 0; // ^Z is obsolete, use 0
121             buffer[size + 1] = 0;
122             buffer[size + 2] = 0; //add two more so lexer doesnt read pass the buffer
123             buffer[size + 3] = 0;
124 
125             result.success = true;
126             result.buffer.data = buffer[0 .. size];
127             return result;
128         err2:
129             close(fd);
130         err:
131             mem.xfree(buffer);
132             return result;
133         }
134         else version (Windows)
135         {
136             DWORD size;
137             DWORD numread;
138 
139             // work around Windows file path length limitation
140             // (see documentation for extendedPathThen).
141             HANDLE h = name.extendedPathThen!
142                 (p => CreateFileW(p.ptr,
143                                   GENERIC_READ,
144                                   FILE_SHARE_READ,
145                                   null,
146                                   OPEN_EXISTING,
147                                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
148                                   null));
149             if (h == INVALID_HANDLE_VALUE)
150                 return result;
151             size = GetFileSize(h, null);
152             ubyte* buffer = cast(ubyte*)mem.xmalloc_noscan(size + 4);
153             if (ReadFile(h, buffer, size, &numread, null) != TRUE)
154                 goto err2;
155             if (numread != size)
156                 goto err2;
157             if (!CloseHandle(h))
158                 goto err;
159             // Always store a wchar ^Z past end of buffer so scanner has a sentinel
160             buffer[size] = 0; // ^Z is obsolete, use 0
161             buffer[size + 1] = 0;
162             buffer[size + 2] = 0; //add two more so lexer doesnt read pass the buffer
163             buffer[size + 3] = 0;
164             result.success = true;
165             result.buffer.data = buffer[0 .. size];
166             return result;
167         err2:
168             CloseHandle(h);
169         err:
170             mem.xfree(buffer);
171             return result;
172         }
173         else
174         {
175             assert(0);
176         }
177     }
178 
179     /// Write a file, returning `true` on success.
180     extern (D) static bool write(const(char)* name, const void[] data)
181     {
182         version (Posix)
183         {
184             ssize_t numwritten;
185             int fd = open(name, O_CREAT | O_WRONLY | O_TRUNC, (6 << 6) | (4 << 3) | 4);
186             if (fd == -1)
187                 goto err;
188             numwritten = .write(fd, data.ptr, data.length);
189             if (numwritten != data.length)
190                 goto err2;
191             if (close(fd) == -1)
192                 goto err;
193             return true;
194         err2:
195             close(fd);
196             .remove(name);
197         err:
198             return false;
199         }
200         else version (Windows)
201         {
202             DWORD numwritten; // here because of the gotos
203             const nameStr = name.toDString;
204             // work around Windows file path length limitation
205             // (see documentation for extendedPathThen).
206             HANDLE h = nameStr.extendedPathThen!
207                 (p => CreateFileW(p.ptr,
208                                   GENERIC_WRITE,
209                                   0,
210                                   null,
211                                   CREATE_ALWAYS,
212                                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
213                                   null));
214             if (h == INVALID_HANDLE_VALUE)
215                 goto err;
216 
217             if (WriteFile(h, data.ptr, cast(DWORD)data.length, &numwritten, null) != TRUE)
218                 goto err2;
219             if (numwritten != data.length)
220                 goto err2;
221             if (!CloseHandle(h))
222                 goto err;
223             return true;
224         err2:
225             CloseHandle(h);
226             nameStr.extendedPathThen!(p => DeleteFileW(p.ptr));
227         err:
228             return false;
229         }
230         else
231         {
232             assert(0);
233         }
234     }
235 
236     ///ditto
237     extern(D) static bool write(const(char)[] name, const void[] data)
238     {
239         return name.toCStringThen!((fname) => write(fname.ptr, data));
240     }
241 
242     /// ditto
243     extern (C++) static bool write(const(char)* name, const(void)* data, size_t size)
244     {
245         return write(name, data[0 .. size]);
246     }
247 
248     /// Delete a file.
249     extern (C++) static void remove(const(char)* name)
250     {
251         version (Posix)
252         {
253             .remove(name);
254         }
255         else version (Windows)
256         {
257             name.toDString.extendedPathThen!(p => DeleteFileW(p.ptr));
258         }
259         else
260         {
261             assert(0);
262         }
263     }
264 
265     /***************************************************
266      * Update file
267      *
268      * If the file exists and is identical to what is to be written,
269      * merely update the timestamp on the file.
270      * Otherwise, write the file.
271      *
272      * The idea is writes are much slower than reads, and build systems
273      * often wind up generating identical files.
274      * Params:
275      *  name = name of file to update
276      *  data = updated contents of file
277      * Returns:
278      *  `true` on success
279      */
280     extern (D) static bool update(const(char)* namez, const void[] data)
281     {
282         enum log = false;
283         if (log) printf("update %s\n", namez);
284         version (Windows)
285         {
286             const nameStr = namez.toDString();
287 
288             import core.sys.windows.windows;
289 
290             WIN32_FILE_ATTRIBUTE_DATA fad = void;
291             // Doesn't exist, not a regular file, different size
292             if (nameStr.extendedPathThen!(p => GetFileAttributesExW(p.ptr, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad)) == 0 ||
293                 fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ||
294                 ((cast(ulong) fad.nFileSizeHigh << 32) | fad.nFileSizeLow) != data.length)
295             {
296                 return write(namez, data);               // write new file
297             }
298         }
299         else version (Posix)
300         {
301             import core.sys.posix.sys.stat;
302 
303             stat_t statbuf = void;
304             if (stat(namez, &statbuf) != 0 ||            // doesn't exist
305                 (statbuf.st_mode & S_IFMT) != S_IFREG || // not a regular file
306                 statbuf.st_size != data.length)          // different size
307             {
308                 if (log) printf("not exist or diff size %d %d %d\n",
309                     stat(namez, &statbuf) != 0,
310                     (statbuf.st_mode & S_IFMT) != S_IFREG,
311                      statbuf.st_size != data.length);
312                 return write(namez, data);               // write new file
313             }
314         }
315         else
316             static assert(0);
317         if (log) printf("same size\n");
318 
319         /* The file already exists, and is the same size.
320          * Read it in, and compare for equality.
321          * For larger files, this could be faster by comparing the file
322          * block by block and quitting on first difference.
323          */
324         ReadResult r = read(namez);
325         if (!r.success ||
326             r.buffer.data[] != data[])
327             return write(namez, data); // contents not same, so write new file
328         if (log) printf("same contents\n");
329 
330         /* Contents are identical, so set timestamp of existing file to current time
331          */
332         version (Windows)
333         {
334             FILETIME ft = void;
335             SYSTEMTIME st = void;
336             GetSystemTime(&st);
337             SystemTimeToFileTime(&st, &ft);
338 
339             // get handle to file
340             HANDLE h = nameStr.extendedPathThen!(p => CreateFile(p.ptr,
341                 FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE,
342                 null, OPEN_EXISTING,
343                 FILE_ATTRIBUTE_NORMAL, null));
344             if (h == INVALID_HANDLE_VALUE)
345                 return false;
346 
347             const f = SetFileTime(h, null, null, &ft); // set last write time
348 
349             if (!CloseHandle(h))
350                 return false;
351 
352             return f != 0;
353         }
354         else version (Posix)
355         {
356             import core.sys.posix.utime;
357 
358             return utime(namez, null) == 0;
359         }
360         else
361             static assert(0);
362     }
363 
364     ///ditto
365     extern(D) static bool update(const(char)[] name, const void[] data)
366     {
367         return name.toCStringThen!((fname) => update(fname.ptr, data));
368     }
369 
370     /// ditto
371     extern (C++) static bool update(const(char)* name, const(void)* data, size_t size)
372     {
373         return update(name, data[0 .. size]);
374     }
375 
376 }