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 }