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