1 /**
2  * Read a file from disk and store it in memory.
3  *
4  * Copyright: Copyright (C) 1999-2020 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 }