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 * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/file_manager.d, _file_manager.d) 7 * Documentation: https://dlang.org/phobos/dmd_file_manager.html 8 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/file_manager.d 9 */ 10 11 module dmd.file_manager; 12 13 import dmd.root.stringtable : StringTable; 14 import dmd.root.file : File, Buffer; 15 import dmd.root.filename : FileName, isDirSeparator; 16 import dmd.root.string : toDString; 17 import dmd.globals; 18 import dmd.identifier; 19 import dmd.location; 20 21 enum package_d = "package." ~ mars_ext; 22 enum package_di = "package." ~ hdr_ext; 23 24 /// Returns: whether a file with `name` is a special "package.d" module 25 bool isPackageFileName(scope FileName fileName) nothrow 26 { 27 return FileName.equals(fileName.name, package_d) || FileName.equals(fileName.name, package_di); 28 } 29 30 // A path stack that allows one to go up and down the path using directory 31 // separators. `cur` is the current path, `up` goes up one path, `down` goes 32 // down one path. if `up` or `down` return false, there are no further paths. 33 private struct PathStack 34 { 35 private const(char)[] path; 36 private size_t pos; 37 38 @safe @nogc nothrow pure: 39 40 this(const(char)[] p) 41 { 42 path = p; 43 pos = p.length; 44 } 45 46 const(char)[] cur() 47 { 48 return path[0 .. pos]; 49 } 50 51 bool up() 52 { 53 if (pos == 0) 54 return false; 55 while (--pos != 0) 56 if (isDirSeparator(path[pos])) 57 return true; 58 return false; 59 } 60 61 bool down() 62 { 63 if (pos == path.length) 64 return false; 65 while (++pos != path.length) 66 if (isDirSeparator(path[pos])) 67 return true; 68 return false; 69 } 70 } 71 72 final class FileManager 73 { 74 private StringTable!(const(ubyte)[]) files; 75 private StringTable!(bool) packageStatus; 76 77 // check if the package path of the given path exists. The input path is 78 // expected to contain the full path to the module, so the parent 79 // directories of that path are checked. 80 private bool packageExists(const(char)[] p) nothrow 81 { 82 // step 1, look for the closest parent path that is cached 83 bool exists = true; 84 auto st = PathStack(p); 85 while (st.up) { 86 if (auto cached = packageStatus.lookup(st.cur)) { 87 exists = cached.value; 88 break; 89 } 90 } 91 // found a parent that is cached (or reached the end of the stack). 92 // step 2, traverse back up the stack storing either false if the 93 // parent doesn't exist, or the result of the `exists` call if it does. 94 while (st.down) { 95 if (!exists) 96 packageStatus.insert(st.cur, false); 97 else 98 exists = packageStatus.insert(st.cur, FileName.exists(st.cur) == 2).value; 99 } 100 101 // at this point, exists should be the answer. 102 return exists; 103 } 104 105 /// 106 public this () nothrow 107 { 108 this.files._init(); 109 this.packageStatus._init(); 110 } 111 112 nothrow: 113 /******************************************** 114 * Look for the source file if it's different from filename. 115 * Look for .di, .d, directory, and along global.path. 116 * Does not open the file. 117 * Params: 118 * filename = as supplied by the user 119 * path = path to look for filename 120 * Returns: 121 * the found file name or 122 * `null` if it is not different from filename. 123 */ 124 const(char)[] lookForSourceFile(const char[] filename, const char*[] path) 125 { 126 //printf("lookForSourceFile(`%.*s`)\n", cast(int)filename.length, filename.ptr); 127 /* Search along path[] for .di file, then .d file. 128 */ 129 // see if we should check for the module locally. 130 bool checkLocal = packageExists(filename); 131 const sdi = FileName.forceExt(filename, hdr_ext); 132 if (checkLocal && FileName.exists(sdi) == 1) 133 return sdi; 134 scope(exit) FileName.free(sdi.ptr); 135 136 const sd = FileName.forceExt(filename, mars_ext); 137 // Special file name representing `stdin`, always assume its presence 138 if (sd == "__stdin.d") 139 return sd; 140 if (checkLocal && FileName.exists(sd) == 1) 141 return sd; 142 scope(exit) FileName.free(sd.ptr); 143 144 if (checkLocal) 145 { 146 auto cached = packageStatus.lookup(filename); 147 if (!cached) 148 cached = packageStatus.insert(filename, FileName.exists(filename) == 2); 149 if (cached.value) 150 { 151 /* The filename exists and it's a directory. 152 * Therefore, the result should be: filename/package.d 153 * iff filename/package.d is a file 154 */ 155 const ni = FileName.combine(filename, package_di); 156 if (FileName.exists(ni) == 1) 157 return ni; 158 FileName.free(ni.ptr); 159 160 const n = FileName.combine(filename, package_d); 161 if (FileName.exists(n) == 1) 162 return n; 163 FileName.free(n.ptr); 164 } 165 } 166 167 if (FileName.absolute(filename)) 168 return null; 169 if (!path.length) 170 return null; 171 foreach (entry; path) 172 { 173 const p = entry.toDString(); 174 175 const(char)[] n = FileName.combine(p, sdi); 176 177 if (!packageExists(n)) { 178 FileName.free(n.ptr); 179 continue; // no need to check for anything else. 180 } 181 if (FileName.exists(n) == 1) { 182 return n; 183 } 184 FileName.free(n.ptr); 185 186 n = FileName.combine(p, sd); 187 if (FileName.exists(n) == 1) { 188 return n; 189 } 190 FileName.free(n.ptr); 191 192 const b = FileName.removeExt(filename); 193 n = FileName.combine(p, b); 194 FileName.free(b.ptr); 195 196 scope(exit) FileName.free(n.ptr); 197 198 // also cache this if we are looking for package.d[i] 199 auto cached = packageStatus.lookup(n); 200 if (!cached) { 201 cached = packageStatus.insert(n, FileName.exists(n) == 2); 202 } 203 204 if (cached.value) 205 { 206 const n2i = FileName.combine(n, package_di); 207 if (FileName.exists(n2i) == 1) 208 return n2i; 209 FileName.free(n2i.ptr); 210 const n2 = FileName.combine(n, package_d); 211 if (FileName.exists(n2) == 1) { 212 return n2; 213 } 214 FileName.free(n2.ptr); 215 } 216 } 217 218 /* ImportC: No D modules found, now search along path[] for .i file, then .c file. 219 */ 220 const si = FileName.forceExt(filename, i_ext); 221 if (FileName.exists(si) == 1) 222 return si; 223 scope(exit) FileName.free(si.ptr); 224 225 const sc = FileName.forceExt(filename, c_ext); 226 if (FileName.exists(sc) == 1) 227 return sc; 228 scope(exit) FileName.free(sc.ptr); 229 foreach (entry; path) 230 { 231 const p = entry.toDString(); 232 233 const(char)[] n = FileName.combine(p, si); 234 if (FileName.exists(n) == 1) { 235 return n; 236 } 237 FileName.free(n.ptr); 238 239 n = FileName.combine(p, sc); 240 if (FileName.exists(n) == 1) { 241 return n; 242 } 243 FileName.free(n.ptr); 244 } 245 return null; 246 } 247 248 /** 249 * Looks up the given filename from the internal file buffer table. 250 * If the file does not already exist within the table, it will be read from the filesystem. 251 * If it has been read before, 252 * 253 * Returns: the loaded source file if it was found in memory, 254 * otherwise `null` 255 */ 256 const(ubyte)[] lookup(FileName filename) 257 { 258 const name = filename.toString; 259 if (auto val = files.lookup(name)) 260 return val.value; 261 262 if (name == "__stdin.d") 263 { 264 auto buffer = readFromStdin().extractSlice(); 265 if (this.files.insert(name, buffer) is null) 266 assert(0, "stdin: Insert after lookup failure should never return `null`"); 267 return buffer; 268 } 269 270 if (FileName.exists(name) != 1) 271 return null; 272 273 auto readResult = File.read(name); 274 if (!readResult.success) 275 return null; 276 277 auto fb = readResult.extractSlice(); 278 if (files.insert(name, fb) is null) 279 assert(0, "Insert after lookup failure should never return `null`"); 280 281 return fb; 282 } 283 284 /** 285 * Looks up the given filename from the internal file buffer table, and returns the lines within the file. 286 * If the file does not already exist within the table, it will be read from the filesystem. 287 * If it has been read before, 288 * 289 * Returns: the loaded source file if it was found in memory, 290 * otherwise `null` 291 */ 292 const(char)[][] getLines(FileName file) 293 { 294 const(char)[][] lines; 295 if (const buffer = lookup(file)) 296 { 297 const slice = buffer; 298 size_t start, end; 299 for (auto i = 0; i < slice.length; i++) 300 { 301 const c = slice[i]; 302 if (c == '\n' || c == '\r') 303 { 304 if (i != 0) 305 { 306 end = i; 307 // Appending lines one at a time will certainly be slow 308 lines ~= cast(const(char)[])slice[start .. end]; 309 } 310 // Check for Windows-style CRLF newlines 311 if (c == '\r') 312 { 313 if (slice.length > i + 1 && slice[i + 1] == '\n') 314 { 315 // This is a CRLF sequence, skip over two characters 316 start = i + 2; 317 i++; 318 } 319 else 320 { 321 // Just a CR sequence 322 start = i + 1; 323 } 324 } 325 else 326 { 327 // The next line should start after the LF sequence 328 start = i + 1; 329 } 330 } 331 } 332 333 if (slice[$ - 1] != '\r' && slice[$ - 1] != '\n') 334 { 335 end = slice.length; 336 lines ~= cast(const(char)[])slice[start .. end]; 337 } 338 } 339 340 return lines; 341 } 342 343 /** 344 * Adds the contents of a file to the table. 345 * Params: 346 * filename = name of the file 347 * buffer = contents of the file 348 * Returns: 349 * the buffer added, or null 350 */ 351 const(ubyte)[] add(FileName filename, const(ubyte)[] buffer) 352 { 353 auto val = files.insert(filename.toString, buffer); 354 return val == null ? null : val.value; 355 } 356 } 357 358 private Buffer readFromStdin() nothrow 359 { 360 import core.stdc.stdio; 361 import dmd.errors; 362 import dmd.root.rmem; 363 364 enum bufIncrement = 128 * 1024; 365 size_t pos = 0; 366 size_t sz = bufIncrement; 367 368 ubyte* buffer = null; 369 for (;;) 370 { 371 buffer = cast(ubyte*)mem.xrealloc(buffer, sz + 4); // +2 for sentinel and +2 for lexer 372 373 // Fill up buffer 374 do 375 { 376 assert(sz > pos); 377 size_t rlen = fread(buffer + pos, 1, sz - pos, stdin); 378 pos += rlen; 379 if (ferror(stdin)) 380 { 381 import core.stdc.errno; 382 error(Loc.initial, "cannot read from stdin, errno = %d", errno); 383 fatal(); 384 } 385 if (feof(stdin)) 386 { 387 // We're done 388 assert(pos < sz + 2); 389 buffer[pos .. pos + 4] = '\0'; 390 return Buffer(buffer[0 .. pos]); 391 } 392 } while (pos < sz); 393 394 // Buffer full, expand 395 sz += bufIncrement; 396 } 397 398 assert(0); 399 }