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 + 2);
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             result.success = true;
123             result.buffer.data = buffer[0 .. size];
124             return result;
125         err2:
126             close(fd);
127         err:
128             mem.xfree(buffer);
129             return result;
130         }
131         else version (Windows)
132         {
133             DWORD size;
134             DWORD numread;
135 
136             // work around Windows file path length limitation
137             // (see documentation for extendedPathThen).
138             HANDLE h = name.extendedPathThen!
139                 (p => CreateFileW(p.ptr,
140                                   GENERIC_READ,
141                                   FILE_SHARE_READ,
142                                   null,
143                                   OPEN_EXISTING,
144                                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
145                                   null));
146             if (h == INVALID_HANDLE_VALUE)
147                 return result;
148             size = GetFileSize(h, null);
149             ubyte* buffer = cast(ubyte*)mem.xmalloc_noscan(size + 2);
150             if (ReadFile(h, buffer, size, &numread, null) != TRUE)
151                 goto err2;
152             if (numread != size)
153                 goto err2;
154             if (!CloseHandle(h))
155                 goto err;
156             // Always store a wchar ^Z past end of buffer so scanner has a sentinel
157             buffer[size] = 0; // ^Z is obsolete, use 0
158             buffer[size + 1] = 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     extern (D) static bool write(const(char)* name, const void[] data)
176     {
177         version (Posix)
178         {
179             ssize_t numwritten;
180             int fd = open(name, O_CREAT | O_WRONLY | O_TRUNC, (6 << 6) | (4 << 3) | 4);
181             if (fd == -1)
182                 goto err;
183             numwritten = .write(fd, data.ptr, data.length);
184             if (numwritten != data.length)
185                 goto err2;
186             if (close(fd) == -1)
187                 goto err;
188             return true;
189         err2:
190             close(fd);
191             .remove(name);
192         err:
193             return false;
194         }
195         else version (Windows)
196         {
197             DWORD numwritten; // here because of the gotos
198             const nameStr = name.toDString;
199             // work around Windows file path length limitation
200             // (see documentation for extendedPathThen).
201             HANDLE h = nameStr.extendedPathThen!
202                 (p => CreateFileW(p.ptr,
203                                   GENERIC_WRITE,
204                                   0,
205                                   null,
206                                   CREATE_ALWAYS,
207                                   FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
208                                   null));
209             if (h == INVALID_HANDLE_VALUE)
210                 goto err;
211 
212             if (WriteFile(h, data.ptr, cast(DWORD)data.length, &numwritten, null) != TRUE)
213                 goto err2;
214             if (numwritten != data.length)
215                 goto err2;
216             if (!CloseHandle(h))
217                 goto err;
218             return true;
219         err2:
220             CloseHandle(h);
221             nameStr.extendedPathThen!(p => DeleteFileW(p.ptr));
222         err:
223             return false;
224         }
225         else
226         {
227             assert(0);
228         }
229     }
230 
231     ///ditto
232     extern(D) static bool write(const(char)[] name, const void[] data)
233     {
234         return name.toCStringThen!((fname) => write(fname.ptr, data));
235     }
236 
237     /// ditto
238     extern (C++) static bool write(const(char)* name, const(void)* data, size_t size)
239     {
240         return write(name, data[0 .. size]);
241     }
242 
243     /// Delete a file.
244     extern (C++) static void remove(const(char)* name)
245     {
246         version (Posix)
247         {
248             .remove(name);
249         }
250         else version (Windows)
251         {
252             name.toDString.extendedPathThen!(p => DeleteFileW(p.ptr));
253         }
254         else
255         {
256             assert(0);
257         }
258     }
259 }