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