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 }