1 /**
2  * Read a file from disk and store it in memory.
3  *
4  * Copyright: Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
5  * Authors:   Walter Bright, https://www.digitalmars.com
6  * License:   $(LINK2 https://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.stdc.string : strerror;
18 import core.sys.posix.fcntl;
19 import core.sys.posix.unistd;
20 import core.sys.windows.winbase;
21 import core.sys.windows.winnt;
22 import dmd.root.filename;
23 import dmd.root.rmem;
24 import dmd.root.string;
25 
26 import dmd.common.file;
27 import dmd.common.string;
28 
29 nothrow:
30 
31 /// Owns a (rmem-managed) buffer.
32 struct Buffer
33 {
34     ubyte[] data;
35 
36   nothrow:
37 
38     this(this) @disable;
39 
40     ~this() pure nothrow
41     {
42         mem.xfree(data.ptr);
43     }
44 
45     /// Transfers ownership of the buffer to the caller.
46     ubyte[] extractSlice() pure nothrow @nogc @safe
47     {
48         auto result = data;
49         data = null;
50         return result;
51     }
52 }
53 
54 ///
55 struct File
56 {
57     ///
58     static struct ReadResult
59     {
60         bool success;
61         Buffer buffer;
62 
63         /// Transfers ownership of the buffer to the caller.
64         ubyte[] extractSlice() pure nothrow @nogc @safe
65         {
66             return buffer.extractSlice();
67         }
68 
69         /// ditto
70         /// Include the null-terminator at the end of the buffer in the returned array.
71         ubyte[] extractDataZ() @nogc nothrow pure
72         {
73             auto result = buffer.extractSlice();
74             return result.ptr[0 .. result.length + 1];
75         }
76     }
77 
78 nothrow:
79     /// Read the full content of a file.
80     static ReadResult read(const(char)[] name)
81     {
82         ReadResult result;
83 
84         version (Posix)
85         {
86             size_t size;
87             stat_t buf;
88             ssize_t numread;
89             //printf("File::read('%s')\n",name);
90             int fd = name.toCStringThen!(slice => open(slice.ptr, O_RDONLY));
91             if (fd == -1)
92             {
93                 //perror("\topen error");
94                 return result;
95             }
96             //printf("\tfile opened\n");
97             if (fstat(fd, &buf))
98             {
99                 //perror("\tfstat error");
100                 close(fd);
101                 return result;
102             }
103             size = cast(size_t)buf.st_size;
104             ubyte* buffer = cast(ubyte*)mem.xmalloc_noscan(size + 4);
105             numread = .read(fd, buffer, size);
106             if (numread != size)
107             {
108                 //perror("\tread error");
109                 goto err2;
110             }
111             if (close(fd) == -1)
112             {
113                 //perror("\tclose error");
114                 goto err;
115             }
116             // Always store a wchar ^Z past end of buffer so scanner has a
117             // sentinel, although ^Z got obselete, so fill with two 0s and add
118             // two more so lexer doesn't read pass the buffer.
119             buffer[size .. size + 4] = 0;
120 
121             result.success = true;
122             result.buffer.data = buffer[0 .. size];
123             return result;
124         err2:
125             close(fd);
126         err:
127             mem.xfree(buffer);
128             return result;
129         }
130         else version (Windows)
131         {
132             DWORD size;
133             DWORD numread;
134 
135             // work around Windows file path length limitation
136             // (see documentation for extendedPathThen).
137             HANDLE h = name.extendedPathThen!
138                 (p => CreateFileW(p.ptr,
139                                   GENERIC_READ,
140                                   FILE_SHARE_READ,
141                                   null,
142                                   OPEN_EXISTING,
143                                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
144                                   null));
145             if (h == INVALID_HANDLE_VALUE)
146                 return result;
147             size = GetFileSize(h, null);
148             ubyte* buffer = cast(ubyte*)mem.xmalloc_noscan(size + 4);
149             if (ReadFile(h, buffer, size, &numread, null) != TRUE)
150                 goto err2;
151             if (numread != size)
152                 goto err2;
153             if (!CloseHandle(h))
154                 goto err;
155             // Always store a wchar ^Z past end of buffer so scanner has a
156             // sentinel, although ^Z got obselete, so fill with two 0s and add
157             // two more so lexer doesn't read pass the buffer.
158             buffer[size .. size + 4] = 0;
159             result.success = true;
160             result.buffer.data = buffer[0 .. size];
161             return result;
162         err2:
163             CloseHandle(h);
164         err:
165             mem.xfree(buffer);
166             return result;
167         }
168         else
169         {
170             assert(0);
171         }
172     }
173 
174     /// Write a file, returning `true` on success.
175     static bool write(const(char)* name, const void[] data)
176     {
177         import dmd.common.file : writeFile;
178         return writeFile(name, data);
179     }
180 
181     ///ditto
182     static bool write(const(char)[] name, const void[] data)
183     {
184         return name.toCStringThen!((fname) => write(fname.ptr, data));
185     }
186 
187     /// Delete a file.
188     extern (C++) static void remove(const(char)* name)
189     {
190         version (Posix)
191         {
192             .remove(name);
193         }
194         else version (Windows)
195         {
196             name.toDString.extendedPathThen!(p => DeleteFileW(p.ptr));
197         }
198         else
199         {
200             static assert(0);
201         }
202     }
203 
204     /***************************************************
205      * Update file
206      *
207      * If the file exists and is identical to what is to be written,
208      * merely update the timestamp on the file.
209      * Otherwise, write the file.
210      *
211      * The idea is writes are much slower than reads, and build systems
212      * often wind up generating identical files.
213      * Params:
214      *  name = name of file to update
215      *  data = updated contents of file
216      * Returns:
217      *  `true` on success
218      */
219     static bool update(const(char)* namez, const void[] data)
220     {
221         enum log = false;
222         if (log) printf("update %s\n", namez);
223 
224         if (data.length != File.size(namez))
225             return write(namez, data);               // write new file
226 
227         if (log) printf("same size\n");
228 
229         /* The file already exists, and is the same size.
230          * Read it in, and compare for equality.
231          */
232         //if (FileMapping!(const ubyte)(namez)[] != data[])
233             return write(namez, data); // contents not same, so write new file
234         //if (log) printf("same contents\n");
235 
236         /* Contents are identical, so set timestamp of existing file to current time
237          */
238         //return touch(namez);
239     }
240 
241     ///ditto
242     static bool update(const(char)[] name, const void[] data)
243     {
244         return name.toCStringThen!(fname => update(fname.ptr, data));
245     }
246 
247     /// Size of a file in bytes.
248     /// Params: namez = null-terminated filename
249     /// Returns: `ulong.max` on any error, the length otherwise.
250     static ulong size(const char* namez)
251     {
252         version (Posix)
253         {
254             stat_t buf;
255             if (stat(namez, &buf) == 0)
256                 return buf.st_size;
257         }
258         else version (Windows)
259         {
260             const nameStr = namez.toDString();
261             import core.sys.windows.windows;
262             WIN32_FILE_ATTRIBUTE_DATA fad = void;
263             // Doesn't exist, not a regular file, different size
264             if (nameStr.extendedPathThen!(p => GetFileAttributesExW(p.ptr, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &fad)) != 0)
265                 return (ulong(fad.nFileSizeHigh) << 32UL) | fad.nFileSizeLow;
266         }
267         else static assert(0);
268         // Error cases go here.
269         return ulong.max;
270     }
271 }
272 
273 private
274 {
275     version (linux) version (PPC)
276     {
277         // https://issues.dlang.org/show_bug.cgi?id=22823
278         // Define our own version of stat_t, as older versions of the compiler
279         // had the st_size field at the wrong offset on PPC.
280         alias stat_t_imported = core.sys.posix.sys.stat.stat_t;
281         static if (stat_t_imported.st_size.offsetof != 48)
282         {
283             extern (C) nothrow @nogc:
284             struct stat_t
285             {
286                 ulong[6] __pad1;
287                 ulong st_size;
288                 ulong[6] __pad2;
289             }
290             version (CRuntime_Glibc)
291             {
292                 int fstat64(int, stat_t*) @trusted;
293                 alias fstat = fstat64;
294                 int stat64(const scope char*, stat_t*) @system;
295                 alias stat = stat64;
296             }
297             else
298             {
299                 int fstat(int, stat_t*) @trusted;
300                 int stat(const scope char*, stat_t*) @system;
301             }
302         }
303     }
304 }