1 /** 2 * Encapsulate path and file names. 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/filename.d, root/_filename.d) 8 * Documentation: https://dlang.org/phobos/dmd_root_filename.html 9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/root/filename.d 10 */ 11 12 module dmd.root.filename; 13 14 import core.stdc.ctype; 15 import core.stdc.errno; 16 import core.stdc.string; 17 import dmd.root.array; 18 import dmd.root.file; 19 import dmd.common.outbuffer; 20 import dmd.common.file; 21 import dmd.root.port; 22 import dmd.root.rmem; 23 import dmd.root.rootobject; 24 import dmd.root.string; 25 26 version (Posix) 27 { 28 import core.sys.posix.stdlib; 29 import core.sys.posix.sys.stat; 30 import core.sys.posix.unistd : getcwd; 31 } 32 33 version (Windows) 34 { 35 import core.sys.windows.winbase; 36 import core.sys.windows.windef; 37 import core.sys.windows.winnls; 38 39 import dmd.common.string : extendedPathThen; 40 41 extern (Windows) DWORD GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR*) nothrow @nogc; 42 extern (Windows) void SetLastError(DWORD) nothrow @nogc; 43 extern (C) char* getcwd(char* buffer, size_t maxlen) nothrow; 44 45 // assume filenames encoded in system default Windows ANSI code page 46 private enum CodePage = CP_ACP; 47 } 48 49 version (CRuntime_Glibc) 50 { 51 extern (C) char* canonicalize_file_name(const char*) nothrow; 52 } 53 54 alias Strings = Array!(const(char)*); 55 56 57 // Check whether character is a directory separator 58 bool isDirSeparator(char c) pure nothrow @nogc @safe 59 { 60 version (Windows) 61 { 62 return c == '\\' || c == '/'; 63 } 64 else version (Posix) 65 { 66 return c == '/'; 67 } 68 else 69 { 70 assert(0); 71 } 72 } 73 74 /*********************************************************** 75 * Encapsulate path and file names. 76 */ 77 struct FileName 78 { 79 nothrow: 80 private const(char)[] str; 81 82 /// 83 extern (D) this(const(char)[] str) pure 84 { 85 this.str = str.xarraydup; 86 } 87 88 /// 89 extern (C++) static FileName create(const(char)* name) pure 90 { 91 return FileName(name.toDString); 92 } 93 94 /// Compare two name according to the platform's rules (case sensitive or not) 95 extern (C++) static bool equals(const(char)* name1, const(char)* name2) pure @nogc 96 { 97 return equals(name1.toDString, name2.toDString); 98 } 99 100 /// Ditto 101 extern (D) static bool equals(const(char)[] name1, const(char)[] name2) pure @nogc 102 { 103 if (name1.length != name2.length) 104 return false; 105 106 version (Windows) 107 { 108 return name1.ptr == name2.ptr || 109 Port.memicmp(name1.ptr, name2.ptr, name1.length) == 0; 110 } 111 else 112 { 113 return name1 == name2; 114 } 115 } 116 117 /************************************ 118 * Determine if path is absolute. 119 * Params: 120 * name = path 121 * Returns: 122 * true if absolute path name. 123 */ 124 extern (C++) static bool absolute(const(char)* name) pure @nogc 125 { 126 return absolute(name.toDString); 127 } 128 129 /// Ditto 130 extern (D) static bool absolute(const(char)[] name) pure @nogc 131 { 132 if (!name.length) 133 return false; 134 135 version (Windows) 136 { 137 return isDirSeparator(name[0]) 138 || (name.length >= 2 && name[1] == ':'); 139 } 140 else version (Posix) 141 { 142 return isDirSeparator(name[0]); 143 } 144 else 145 { 146 assert(0); 147 } 148 } 149 150 unittest 151 { 152 assert(absolute("/"[]) == true); 153 assert(absolute(""[]) == false); 154 155 version (Windows) 156 { 157 assert(absolute(r"\"[]) == true); 158 assert(absolute(r"\\"[]) == true); 159 assert(absolute(r"c:"[]) == true); 160 } 161 } 162 163 /** 164 Return the given name as an absolute path 165 166 Params: 167 name = path 168 base = the absolute base to prefix name with if it is relative 169 170 Returns: name as an absolute path relative to base 171 */ 172 extern (C++) static const(char)* toAbsolute(const(char)* name, const(char)* base = null) 173 { 174 const name_ = name.toDString(); 175 const base_ = base ? base.toDString() : getcwd(null, 0).toDString(); 176 return absolute(name_) ? name : combine(base_, name_).ptr; 177 } 178 179 /******************************** 180 * Determine file name extension as slice of input. 181 * Params: 182 * str = file name 183 * Returns: 184 * filename extension (read-only). 185 * Points past '.' of extension. 186 * If there isn't one, return null. 187 */ 188 extern (C++) static const(char)* ext(const(char)* str) pure @nogc 189 { 190 return ext(str.toDString).ptr; 191 } 192 193 /// Ditto 194 extern (D) static const(char)[] ext(const(char)[] str) nothrow pure @safe @nogc 195 { 196 foreach_reverse (idx, char e; str) 197 { 198 switch (e) 199 { 200 case '.': 201 return str[idx + 1 .. $]; 202 version (Posix) 203 { 204 case '/': 205 return null; 206 } 207 version (Windows) 208 { 209 case '\\': 210 case ':': 211 case '/': 212 return null; 213 } 214 default: 215 continue; 216 } 217 } 218 return null; 219 } 220 221 unittest 222 { 223 assert(ext("/foo/bar/dmd.conf"[]) == "conf"); 224 assert(ext("object.o"[]) == "o"); 225 assert(ext("/foo/bar/dmd"[]) == null); 226 assert(ext(".objdir.o/object"[]) == null); 227 assert(ext([]) == null); 228 } 229 230 extern (C++) const(char)* ext() const pure @nogc 231 { 232 return ext(str).ptr; 233 } 234 235 /******************************** 236 * Return file name without extension. 237 * 238 * TODO: 239 * Once slice are used everywhere and `\0` is not assumed, 240 * this can be turned into a simple slicing. 241 * 242 * Params: 243 * str = file name 244 * 245 * Returns: 246 * mem.xmalloc'd filename with extension removed. 247 */ 248 extern (C++) static const(char)* removeExt(const(char)* str) 249 { 250 return removeExt(str.toDString).ptr; 251 } 252 253 /// Ditto 254 extern (D) static const(char)[] removeExt(const(char)[] str) 255 { 256 auto e = ext(str); 257 if (e.length) 258 { 259 const len = (str.length - e.length) - 1; // -1 for the dot 260 char* n = cast(char*)mem.xmalloc(len + 1); 261 memcpy(n, str.ptr, len); 262 n[len] = 0; 263 return n[0 .. len]; 264 } 265 return mem.xstrdup(str.ptr)[0 .. str.length]; 266 } 267 268 unittest 269 { 270 assert(removeExt("/foo/bar/object.d"[]) == "/foo/bar/object"); 271 assert(removeExt("/foo/bar/frontend.di"[]) == "/foo/bar/frontend"); 272 } 273 274 /******************************** 275 * Return filename name excluding path (read-only). 276 */ 277 extern (C++) static const(char)* name(const(char)* str) pure @nogc 278 { 279 return name(str.toDString).ptr; 280 } 281 282 /// Ditto 283 extern (D) static const(char)[] name(const(char)[] str) pure @nogc 284 { 285 foreach_reverse (idx, char e; str) 286 { 287 switch (e) 288 { 289 version (Posix) 290 { 291 case '/': 292 return str[idx + 1 .. $]; 293 } 294 version (Windows) 295 { 296 case '/': 297 case '\\': 298 return str[idx + 1 .. $]; 299 case ':': 300 /* The ':' is a drive letter only if it is the second 301 * character or the last character, 302 * otherwise it is an ADS (Alternate Data Stream) separator. 303 * Consider ADS separators as part of the file name. 304 */ 305 if (idx == 1 || idx == str.length - 1) 306 return str[idx + 1 .. $]; 307 break; 308 } 309 default: 310 break; 311 } 312 } 313 return str; 314 } 315 316 extern (C++) const(char)* name() const pure @nogc 317 { 318 return name(str).ptr; 319 } 320 321 unittest 322 { 323 assert(name("/foo/bar/object.d"[]) == "object.d"); 324 assert(name("/foo/bar/frontend.di"[]) == "frontend.di"); 325 } 326 327 /************************************** 328 * Return path portion of str. 329 * returned string is newly allocated 330 * Path does not include trailing path separator. 331 */ 332 extern (C++) static const(char)* path(const(char)* str) 333 { 334 return path(str.toDString).ptr; 335 } 336 337 /// Ditto 338 extern (D) static const(char)[] path(const(char)[] str) 339 { 340 const n = name(str); 341 bool hasTrailingSlash; 342 if (n.length < str.length) 343 { 344 if (isDirSeparator(str[$ - n.length - 1])) 345 hasTrailingSlash = true; 346 } 347 const pathlen = str.length - n.length - (hasTrailingSlash ? 1 : 0); 348 char* path = cast(char*)mem.xmalloc(pathlen + 1); 349 memcpy(path, str.ptr, pathlen); 350 path[pathlen] = 0; 351 return path[0 .. pathlen]; 352 } 353 354 unittest 355 { 356 assert(path("/foo/bar"[]) == "/foo"); 357 assert(path("foo"[]) == ""); 358 } 359 360 /************************************** 361 * Replace filename portion of path. 362 */ 363 extern (D) static const(char)[] replaceName(const(char)[] path, const(char)[] name) 364 { 365 if (absolute(name)) 366 return name; 367 auto n = FileName.name(path); 368 if (n == path) 369 return name; 370 return combine(path[0 .. $ - n.length], name); 371 } 372 373 /** 374 Combine a `path` and a file `name` 375 376 Params: 377 path = Path to append to 378 name = Name to append to path 379 380 Returns: 381 The `\0` terminated string which is the combination of `path` and `name` 382 and a valid path. 383 */ 384 extern (C++) static const(char)* combine(const(char)* path, const(char)* name) 385 { 386 if (!path) 387 return name; 388 return combine(path.toDString, name.toDString).ptr; 389 } 390 391 /// Ditto 392 extern(D) static const(char)[] combine(const(char)[] path, const(char)[] name) 393 { 394 return !path.length ? name : buildPath(path, name); 395 } 396 397 unittest 398 { 399 version (Windows) 400 assert(combine("foo"[], "bar"[]) == "foo\\bar"); 401 else 402 assert(combine("foo"[], "bar"[]) == "foo/bar"); 403 assert(combine("foo/"[], "bar"[]) == "foo/bar"); 404 } 405 406 static const(char)[] buildPath(const(char)[][] fragments...) 407 { 408 size_t size; 409 foreach (f; fragments) 410 size += f.length ? f.length + 1 : 0; 411 if (size == 0) 412 size = 1; 413 414 char* p = cast(char*) mem.xmalloc_noscan(size); 415 size_t length; 416 foreach (f; fragments) 417 { 418 if (!f.length) 419 continue; 420 421 p[length .. length + f.length] = f; 422 length += f.length; 423 424 const last = p[length - 1]; 425 version (Posix) 426 { 427 if (!isDirSeparator(last)) 428 p[length++] = '/'; 429 } 430 else version (Windows) 431 { 432 if (!isDirSeparator(last) && last != ':') 433 p[length++] = '\\'; 434 } 435 else 436 assert(0); 437 } 438 439 // overwrite last slash with null terminator 440 p[length ? --length : 0] = 0; 441 442 return p[0 .. length]; 443 } 444 445 unittest 446 { 447 assert(buildPath() == ""); 448 assert(buildPath("foo") == "foo"); 449 assert(buildPath("foo", null) == "foo"); 450 assert(buildPath(null, "foo") == "foo"); 451 version (Windows) 452 assert(buildPath("C:", r"a\", "bb/", "ccc", "d") == r"C:a\bb/ccc\d"); 453 else 454 assert(buildPath("a/", "bb", "ccc") == "a/bb/ccc"); 455 } 456 457 // Split a path into an Array of paths 458 extern (C++) static Strings* splitPath(const(char)* path) 459 { 460 auto array = new Strings(); 461 int sink(const(char)* p) nothrow 462 { 463 array.push(p); 464 return 0; 465 } 466 splitPath(&sink, path); 467 return array; 468 } 469 470 /**** 471 * Split path (such as that returned by `getenv("PATH")`) into pieces, each piece is mem.xmalloc'd 472 * Handle double quotes and ~. 473 * Pass the pieces to sink() 474 * Params: 475 * sink = send the path pieces here, end when sink() returns !=0 476 * path = the path to split up. 477 */ 478 static void splitPath(int delegate(const(char)*) nothrow sink, const(char)* path) 479 { 480 if (!path) 481 return; 482 483 auto p = path; 484 OutBuffer buf; 485 char c; 486 do 487 { 488 const(char)* home; 489 bool instring = false; 490 while (isspace(*p)) // skip leading whitespace 491 ++p; 492 buf.reserve(8); // guess size of piece 493 for (;; ++p) 494 { 495 c = *p; 496 switch (c) 497 { 498 case '"': 499 instring ^= false; // toggle inside/outside of string 500 continue; 501 502 version (OSX) 503 { 504 case ',': 505 } 506 version (Windows) 507 { 508 case ';': 509 } 510 version (Posix) 511 { 512 case ':': 513 } 514 p++; // ; cannot appear as part of a 515 break; // path, quotes won't protect it 516 517 case 0x1A: // ^Z means end of file 518 case 0: 519 break; 520 521 case '\r': 522 continue; // ignore carriage returns 523 524 version (Posix) 525 { 526 case '~': 527 if (!home) 528 home = getenv("HOME"); 529 // Expand ~ only if it is prefixing the rest of the path. 530 if (!buf.length && p[1] == '/' && home) 531 buf.writestring(home); 532 else 533 buf.writeByte('~'); 534 continue; 535 } 536 537 version (none) 538 { 539 case ' ': 540 case '\t': // tabs in filenames? 541 if (!instring) // if not in string 542 break; // treat as end of path 543 } 544 default: 545 buf.writeByte(c); 546 continue; 547 } 548 break; 549 } 550 if (buf.length) // if path is not empty 551 { 552 if (sink(buf.extractChars())) 553 break; 554 } 555 } while (c); 556 } 557 558 /** 559 * Add the extension `ext` to `name`, regardless of the content of `name` 560 * 561 * Params: 562 * name = Path to append the extension to 563 * ext = Extension to add (should not include '.') 564 * 565 * Returns: 566 * A newly allocated string (free with `FileName.free`) 567 */ 568 extern(D) static char[] addExt(const(char)[] name, const(char)[] ext) pure 569 { 570 const len = name.length + ext.length + 2; 571 auto s = cast(char*)mem.xmalloc(len); 572 s[0 .. name.length] = name[]; 573 s[name.length] = '.'; 574 s[name.length + 1 .. len - 1] = ext[]; 575 s[len - 1] = '\0'; 576 return s[0 .. len - 1]; 577 } 578 579 580 /*************************** 581 * Free returned value with FileName::free() 582 */ 583 extern (C++) static const(char)* defaultExt(const(char)* name, const(char)* ext) 584 { 585 return defaultExt(name.toDString, ext.toDString).ptr; 586 } 587 588 /// Ditto 589 extern (D) static const(char)[] defaultExt(const(char)[] name, const(char)[] ext) 590 { 591 auto e = FileName.ext(name); 592 if (e.length) // it already has an extension 593 return name.xarraydup; 594 return addExt(name, ext); 595 } 596 597 unittest 598 { 599 assert(defaultExt("/foo/object.d"[], "d") == "/foo/object.d"); 600 assert(defaultExt("/foo/object"[], "d") == "/foo/object.d"); 601 assert(defaultExt("/foo/bar.d"[], "o") == "/foo/bar.d"); 602 } 603 604 /*************************** 605 * Free returned value with FileName::free() 606 */ 607 extern (C++) static const(char)* forceExt(const(char)* name, const(char)* ext) 608 { 609 return forceExt(name.toDString, ext.toDString).ptr; 610 } 611 612 /// Ditto 613 extern (D) static const(char)[] forceExt(const(char)[] name, const(char)[] ext) 614 { 615 if (auto e = FileName.ext(name)) 616 return addExt(name[0 .. $ - e.length - 1], ext); 617 return defaultExt(name, ext); // doesn't have one 618 } 619 620 unittest 621 { 622 assert(forceExt("/foo/object.d"[], "d") == "/foo/object.d"); 623 assert(forceExt("/foo/object"[], "d") == "/foo/object.d"); 624 assert(forceExt("/foo/bar.d"[], "o") == "/foo/bar.o"); 625 } 626 627 /// Returns: 628 /// `true` if `name`'s extension is `ext` 629 extern (C++) static bool equalsExt(const(char)* name, const(char)* ext) pure @nogc 630 { 631 return equalsExt(name.toDString, ext.toDString); 632 } 633 634 /// Ditto 635 extern (D) static bool equalsExt(const(char)[] name, const(char)[] ext) pure @nogc 636 { 637 auto e = FileName.ext(name); 638 if (!e.length && !ext.length) 639 return true; 640 if (!e.length || !ext.length) 641 return false; 642 return FileName.equals(e, ext); 643 } 644 645 unittest 646 { 647 assert(!equalsExt("foo.bar"[], "d")); 648 assert(equalsExt("foo.bar"[], "bar")); 649 assert(equalsExt("object.d"[], "d")); 650 assert(!equalsExt("object"[], "d")); 651 } 652 653 /****************************** 654 * Return !=0 if extensions match. 655 */ 656 extern (C++) bool equalsExt(const(char)* ext) const pure @nogc 657 { 658 return equalsExt(str, ext.toDString()); 659 } 660 661 /************************************* 662 * Search paths for file. 663 * Params: 664 * path = array of path strings 665 * name = file to look for 666 * cwd = true means search current directory before searching path 667 * Returns: 668 * if found, filename combined with path, otherwise null 669 */ 670 extern (C++) static const(char)* searchPath(Strings* path, const(char)* name, bool cwd) 671 { 672 return searchPath(path, name.toDString, cwd).ptr; 673 } 674 675 extern (D) static const(char)[] searchPath(Strings* path, const(char)[] name, bool cwd) 676 { 677 if (absolute(name)) 678 { 679 return exists(name) ? name : null; 680 } 681 if (cwd) 682 { 683 if (exists(name)) 684 return name; 685 } 686 if (path) 687 { 688 foreach (p; *path) 689 { 690 auto n = combine(p.toDString, name); 691 if (exists(n)) 692 return n; 693 //combine might return name 694 if (n.ptr != name.ptr) 695 { 696 mem.xfree(cast(void*)n.ptr); 697 } 698 } 699 } 700 return null; 701 } 702 703 extern (D) static const(char)[] searchPath(const(char)* path, const(char)[] name, bool cwd) 704 { 705 if (absolute(name)) 706 { 707 return exists(name) ? name : null; 708 } 709 if (cwd) 710 { 711 if (exists(name)) 712 return name; 713 } 714 if (path && *path) 715 { 716 const(char)[] result; 717 718 int sink(const(char)* p) nothrow 719 { 720 auto n = combine(p.toDString, name); 721 mem.xfree(cast(void*)p); 722 if (exists(n)) 723 { 724 result = n; 725 return 1; // done with splitPath() call 726 } 727 return 0; 728 } 729 730 splitPath(&sink, path); 731 return result; 732 } 733 return null; 734 } 735 736 /************************************ 737 * Determine if path contains reserved character. 738 * Params: 739 * name = path 740 * Returns: 741 * index of the first reserved character in path if found, size_t.max otherwise 742 */ 743 extern (D) static size_t findReservedChar(const(char)[] name) pure @nogc @safe 744 { 745 version (Windows) 746 { 747 // According to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions 748 // the following characters are not allowed in path: < > : " | ? * 749 foreach (idx; 0 .. name.length) 750 { 751 char c = name[idx]; 752 if (c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*') 753 { 754 return idx; 755 } 756 } 757 return size_t.max; 758 } 759 else 760 { 761 return size_t.max; 762 } 763 } 764 unittest 765 { 766 assert(findReservedChar(r"") == size_t.max); 767 assert(findReservedChar(r" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_=+()") == size_t.max); 768 769 version (Windows) 770 { 771 assert(findReservedChar(` < `) == 1); 772 assert(findReservedChar(` >`) == 1); 773 assert(findReservedChar(`: `) == 0); 774 assert(findReservedChar(`"`) == 0); 775 assert(findReservedChar(`|`) == 0); 776 assert(findReservedChar(`?`) == 0); 777 assert(findReservedChar(`*`) == 0); 778 } 779 else 780 { 781 assert(findReservedChar(`<>:"|?*`) == size_t.max); 782 } 783 } 784 785 /************************************ 786 * Determine if path has a reference to parent directory. 787 * Params: 788 * name = path 789 * Returns: 790 * true if path contains '..' reference to parent directory 791 */ 792 extern (D) static bool refersToParentDir(const(char)[] name) pure @nogc @safe 793 { 794 size_t s = 0; 795 foreach (i; 0 .. name.length) 796 { 797 if (isDirSeparator(name[i])) 798 { 799 if (name[s..i] == "..") 800 return true; 801 s = i + 1; 802 } 803 } 804 if (name[s..$] == "..") 805 return true; 806 807 return false; 808 } 809 unittest 810 { 811 assert(!refersToParentDir(r"")); 812 assert(!refersToParentDir(r"foo")); 813 assert(!refersToParentDir(r"foo..")); 814 assert(!refersToParentDir(r"foo..boo")); 815 assert(!refersToParentDir(r"foo/..boo")); 816 assert(!refersToParentDir(r"foo../boo")); 817 assert(refersToParentDir(r"..")); 818 assert(refersToParentDir(r"../")); 819 assert(refersToParentDir(r"foo/..")); 820 assert(refersToParentDir(r"foo/../")); 821 assert(refersToParentDir(r"foo/../../boo")); 822 823 version (Windows) 824 { 825 // Backslash as directory separator 826 assert(!refersToParentDir(r"foo\..boo")); 827 assert(!refersToParentDir(r"foo..\boo")); 828 assert(refersToParentDir(r"..\")); 829 assert(refersToParentDir(r"foo\..")); 830 assert(refersToParentDir(r"foo\..\")); 831 assert(refersToParentDir(r"foo\..\..\boo")); 832 } 833 } 834 835 836 /** 837 Check if the file the `path` points to exists 838 839 Returns: 840 0 if it does not exists 841 1 if it exists and is not a directory 842 2 if it exists and is a directory 843 */ 844 extern (C++) static int exists(const(char)* name) 845 { 846 return exists(name.toDString); 847 } 848 849 /// Ditto 850 extern (D) static int exists(const(char)[] name) 851 { 852 if (!name.length) 853 return 0; 854 version (Posix) 855 { 856 stat_t st; 857 if (name.toCStringThen!((v) => stat(v.ptr, &st)) < 0) 858 return 0; 859 if (S_ISDIR(st.st_mode)) 860 return 2; 861 return 1; 862 } 863 else version (Windows) 864 { 865 return name.extendedPathThen!((wname) 866 { 867 const dw = GetFileAttributesW(&wname[0]); 868 if (dw == -1) 869 return 0; 870 else if (dw & FILE_ATTRIBUTE_DIRECTORY) 871 return 2; 872 else 873 return 1; 874 }); 875 } 876 else 877 { 878 assert(0); 879 } 880 } 881 882 /** 883 Ensure that the provided path exists 884 885 Accepts a path to either a file or a directory. 886 In the former case, the basepath (path to the containing directory) 887 will be checked for existence, and created if it does not exists. 888 In the later case, the directory pointed to will be checked for existence 889 and created if needed. 890 891 Params: 892 path = a path to a file or a directory 893 894 Returns: 895 `true` if the directory exists or was successfully created 896 */ 897 extern (D) static bool ensurePathExists(const(char)[] path) 898 { 899 //printf("FileName::ensurePathExists(%s)\n", path ? path : ""); 900 if (!path.length) 901 return true; 902 if (exists(path)) 903 return true; 904 905 // We were provided with a file name 906 // We need to call ourselves recursively to ensure parent dir exist 907 const char[] p = FileName.path(path); 908 if (p.length) 909 { 910 version (Windows) 911 { 912 // Note: Windows filename comparison should be case-insensitive, 913 // however p is a subslice of path so we don't need it 914 if (path.length == p.length || 915 (path.length > 2 && path[1] == ':' && path[2 .. $] == p)) 916 { 917 mem.xfree(cast(void*)p.ptr); 918 return true; 919 } 920 } 921 const r = ensurePathExists(p); 922 mem.xfree(cast(void*)p); 923 924 if (!r) 925 return r; 926 } 927 928 version (Windows) 929 const r = _mkdir(path); 930 version (Posix) 931 { 932 errno = 0; 933 const r = path.toCStringThen!((pathCS) => mkdir(pathCS.ptr, (7 << 6) | (7 << 3) | 7)); 934 } 935 936 if (r == 0) 937 return true; 938 939 // Don't error out if another instance of dmd just created 940 // this directory 941 version (Windows) 942 { 943 import core.sys.windows.winerror : ERROR_ALREADY_EXISTS; 944 if (GetLastError() == ERROR_ALREADY_EXISTS) 945 return true; 946 } 947 version (Posix) 948 { 949 if (errno == EEXIST) 950 return true; 951 } 952 953 return false; 954 } 955 956 ///ditto 957 extern (C++) static bool ensurePathExists(const(char)* path) 958 { 959 return ensurePathExists(path.toDString); 960 } 961 962 /****************************************** 963 * Return canonical version of name. 964 * This code is high risk. 965 */ 966 extern (C++) static const(char)* canonicalName(const(char)* name) 967 { 968 return canonicalName(name.toDString).ptr; 969 } 970 971 /// Ditto 972 extern (D) static const(char)[] canonicalName(const(char)[] name) 973 { 974 version (Posix) 975 { 976 import core.stdc.limits; // PATH_MAX 977 import core.sys.posix.unistd; // _PC_PATH_MAX 978 979 // Older versions of druntime don't have PATH_MAX defined. 980 // i.e: dmd __VERSION__ < 2085, gdc __VERSION__ < 2076. 981 static if (!__traits(compiles, PATH_MAX)) 982 { 983 version (DragonFlyBSD) 984 enum PATH_MAX = 1024; 985 else version (FreeBSD) 986 enum PATH_MAX = 1024; 987 else version (linux) 988 enum PATH_MAX = 4096; 989 else version (NetBSD) 990 enum PATH_MAX = 1024; 991 else version (OpenBSD) 992 enum PATH_MAX = 1024; 993 else version (OSX) 994 enum PATH_MAX = 1024; 995 else version (Solaris) 996 enum PATH_MAX = 1024; 997 } 998 999 // Have realpath(), passing a NULL destination pointer may return an 1000 // internally malloc'd buffer, however it is implementation defined 1001 // as to what happens, so cannot rely on it. 1002 static if (__traits(compiles, PATH_MAX)) 1003 { 1004 // Have compile time limit on filesystem path, use it with realpath. 1005 char[PATH_MAX] buf = void; 1006 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf.ptr)); 1007 if (path !is null) 1008 return xarraydup(path.toDString); 1009 } 1010 else static if (__traits(compiles, canonicalize_file_name)) 1011 { 1012 // Have canonicalize_file_name, which malloc's memory. 1013 // We need a dmd.root.rmem allocation though. 1014 auto path = name.toCStringThen!((n) => canonicalize_file_name(n.ptr)); 1015 scope(exit) .free(path); 1016 if (path !is null) 1017 return xarraydup(path.toDString); 1018 } 1019 else static if (__traits(compiles, _PC_PATH_MAX)) 1020 { 1021 // Panic! Query the OS for the buffer limit. 1022 auto path_max = pathconf("/", _PC_PATH_MAX); 1023 if (path_max > 0) 1024 { 1025 char *buf = cast(char*)mem.xmalloc(path_max); 1026 scope(exit) mem.xfree(buf); 1027 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf)); 1028 if (path !is null) 1029 return xarraydup(path.toDString); 1030 } 1031 } 1032 // Give up trying to support this platform, just duplicate the filename 1033 // unless there is nothing to copy from. 1034 if (!name.length) 1035 return null; 1036 return xarraydup(name); 1037 } 1038 else version (Windows) 1039 { 1040 // Convert to wstring first since otherwise the Win32 APIs have a character limit 1041 return name.toWStringzThen!((wname) 1042 { 1043 /* Apparently, there is no good way to do this on Windows. 1044 * GetFullPathName isn't it, but use it anyway. 1045 */ 1046 // First find out how long the buffer has to be, incl. terminating null. 1047 const capacity = GetFullPathNameW(&wname[0], 0, null, null); 1048 if (!capacity) return null; 1049 auto buffer = cast(wchar*) mem.xmalloc_noscan(capacity * wchar.sizeof); 1050 scope(exit) mem.xfree(buffer); 1051 1052 // Actually get the full path name. If the buffer is large enough, 1053 // the returned length does NOT include the terminating null... 1054 const length = GetFullPathNameW(&wname[0], capacity, buffer, null /*filePart*/); 1055 assert(length == capacity - 1); 1056 1057 return toNarrowStringz(buffer[0 .. length]); 1058 }); 1059 } 1060 else 1061 { 1062 assert(0); 1063 } 1064 } 1065 1066 unittest 1067 { 1068 string filename = "foo.bar"; 1069 const path = canonicalName(filename); 1070 scope(exit) free(path.ptr); 1071 assert(path.length >= filename.length); 1072 assert(path[$ - filename.length .. $] == filename); 1073 } 1074 1075 /******************************** 1076 * Free memory allocated by FileName routines 1077 */ 1078 extern (C++) static void free(const(char)* str) pure 1079 { 1080 if (str) 1081 { 1082 assert(str[0] != cast(char)0xAB); 1083 memset(cast(void*)str, 0xAB, strlen(str) + 1); // stomp 1084 } 1085 mem.xfree(cast(void*)str); 1086 } 1087 1088 extern (C++) const(char)* toChars() const pure nothrow @nogc @trusted 1089 { 1090 // Since we can return an empty slice (but '\0' terminated), 1091 // we don't do bounds check (as `&str[0]` does) 1092 return str.ptr; 1093 } 1094 1095 const(char)[] toString() const pure nothrow @nogc @trusted 1096 { 1097 return str; 1098 } 1099 1100 bool opCast(T)() const pure nothrow @nogc @safe 1101 if (is(T == bool)) 1102 { 1103 return str.ptr !is null; 1104 } 1105 } 1106 1107 version(Windows) 1108 { 1109 /**************************************************************** 1110 * The code before used the POSIX function `mkdir` on Windows. That 1111 * function is now deprecated and fails with long paths, so instead 1112 * we use the newer `CreateDirectoryW`. 1113 * 1114 * `CreateDirectoryW` is the unicode version of the generic macro 1115 * `CreateDirectory`. `CreateDirectoryA` has a file path 1116 * limitation of 248 characters, `mkdir` fails with less and might 1117 * fail due to the number of consecutive `..`s in the 1118 * path. `CreateDirectoryW` also normally has a 248 character 1119 * limit, unless the path is absolute and starts with `\\?\`. Note 1120 * that this is different from starting with the almost identical 1121 * `\\?`. 1122 * 1123 * Params: 1124 * path = The path to create. 1125 * 1126 * Returns: 1127 * 0 on success, 1 on failure. 1128 * 1129 * References: 1130 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx 1131 */ 1132 private int _mkdir(const(char)[] path) nothrow 1133 { 1134 const createRet = path.extendedPathThen!( 1135 p => CreateDirectoryW(&p[0], null /*securityAttributes*/)); 1136 // different conventions for CreateDirectory and mkdir 1137 return createRet == 0 ? 1 : 0; 1138 } 1139 1140 /********************************** 1141 * Converts a UTF-16 string to a (null-terminated) narrow string. 1142 * Returns: 1143 * If `buffer` is specified and the result fits, a slice of that buffer, 1144 * otherwise a new buffer which can be released via `mem.xfree()`. 1145 * Nulls are propagated, i.e., if `wide` is null, the returned slice is 1146 * null too. 1147 */ 1148 char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow 1149 { 1150 if (wide is null) 1151 return null; 1152 1153 const requiredLength = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, buffer.ptr, cast(int) buffer.length, null, null); 1154 if (requiredLength < buffer.length) 1155 { 1156 buffer[requiredLength] = 0; 1157 return buffer[0 .. requiredLength]; 1158 } 1159 1160 char* newBuffer = cast(char*) mem.xmalloc_noscan(requiredLength + 1); 1161 const length = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, newBuffer, requiredLength, null, null); 1162 assert(length == requiredLength); 1163 newBuffer[length] = 0; 1164 return newBuffer[0 .. length]; 1165 } 1166 1167 /********************************** 1168 * Converts a slice of UTF-8 characters to an array of wchar that's null 1169 * terminated so it can be passed to Win32 APIs then calls the supplied 1170 * function on it. 1171 * 1172 * Params: 1173 * str = The string to convert. 1174 * 1175 * Returns: 1176 * The result of calling F on the UTF16 version of str. 1177 */ 1178 private auto toWStringzThen(alias F)(const(char)[] str) nothrow 1179 { 1180 import dmd.common.string : SmallBuffer, toWStringz; 1181 1182 if (!str.length) return F(""w.ptr); 1183 1184 wchar[1024] support = void; 1185 auto buf = SmallBuffer!wchar(support.length, support); 1186 wchar[] wide = toWStringz(str, buf); 1187 scope(exit) wide.ptr != buf.ptr && mem.xfree(wide.ptr); 1188 1189 return F(wide); 1190 } 1191 }