1 /** 2 * Encapsulate path and file names. 3 * 4 * Copyright: Copyright (C) 1999-2020 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 is safe. 729 * Params: 730 * name = path 731 * Returns: 732 * true if path is safe. 733 */ 734 private extern (D) static bool safePath(const(char)* name) pure @nogc 735 { 736 // Don't allow absolute path 737 if (absolute(name)) 738 { 739 return false; 740 } 741 // Don't allow parent directory 742 if (name[0] == '.' && name[1] == '.' && (!name[2] || isDirSeparator(name[2]))) 743 { 744 return false; 745 } 746 747 version (Windows) 748 { 749 // According to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions 750 // the following characters are not allowed in path: < > : " | ? * 751 // Additionally we do not allow reference to parent directory ("..") in the path. 752 for (const(char)* p = name; *p; p++) 753 { 754 char c = *p; 755 if (c == '<' || c == '>' || c == ':' || c == '"' || c == '|' || c == '?' || c == '*' || 756 (isDirSeparator(c) && p[1] == '.' && p[2] == '.' && (!p[3] || isDirSeparator(p[3])))) 757 { 758 return false; 759 } 760 } 761 return true; 762 } 763 else version (Posix) 764 { 765 // Do not allow reference to parent directory ("..") in the path. 766 for (const(char)* p = name; *p; p++) 767 { 768 char c = *p; 769 if (isDirSeparator(c) && p[1] == '.' && p[2] == '.' && (!p[3] || isDirSeparator(p[3]))) 770 { 771 return false; 772 } 773 } 774 return true; 775 } 776 else 777 { 778 assert(0); 779 } 780 } 781 unittest 782 { 783 assert(safePath(r"")); 784 assert(safePath(r"foo.bar")); 785 assert(safePath(r"foo.bar")); 786 assert(safePath(r"foo.bar.boo")); 787 assert(safePath(r"foo/bar.boo")); 788 assert(safePath(r"foo/bar/boo")); 789 assert(safePath(r"foo/bar//boo")); // repeated directory separator 790 assert(safePath(r"foo..")); 791 assert(safePath(r"foo..boo")); 792 assert(safePath(r"foo/..boo")); 793 assert(safePath(r"foo../boo")); 794 assert(!safePath(r"/")); 795 assert(!safePath(r"..")); 796 assert(!safePath(r"../")); 797 assert(!safePath(r"foo/..")); 798 assert(!safePath(r"foo/../")); 799 assert(!safePath(r"foo/../../boo")); 800 801 version (Windows) 802 { 803 assert(!safePath(r"\foo")); // absolute path 804 assert(!safePath(r"\\foo")); // UNC path 805 assert(!safePath(r"c:foo")); // drive letter + relative path 806 assert(!safePath(r"c:\foo")); // drive letter + absolute path 807 808 // Backslash as directory separator 809 assert(safePath(r"foo\bar.boo")); 810 assert(safePath(r"foo\bar\boo")); 811 assert(safePath(r"foo\bar\\boo")); // repeated directory separator 812 assert(safePath(r"foo\..boo")); 813 assert(safePath(r"foo..\boo")); 814 assert(!safePath(r"\")); 815 assert(!safePath(r"..\")); 816 assert(!safePath(r"foo\..")); 817 assert(!safePath(r"foo\..\")); 818 assert(!safePath(r"foo\..\..\boo")); 819 } 820 } 821 822 /************************************* 823 * Search Path for file in a safe manner. 824 * 825 * Be wary of CWE-22: Improper Limitation of a Pathname to a Restricted Directory 826 * ('Path Traversal') attacks. 827 * http://cwe.mitre.org/data/definitions/22.html 828 * More info: 829 * https://www.securecoding.cert.org/confluence/display/c/FIO02-C.+Canonicalize+path+names+originating+from+tainted+sources 830 * Returns: 831 * NULL file not found 832 * !=NULL mem.xmalloc'd file name 833 */ 834 extern (C++) static const(char)* safeSearchPath(Strings* path, const(char)* name) 835 { 836 if (!safePath(name)) 837 { 838 return null; 839 } 840 841 version (Windows) 842 { 843 return FileName.searchPath(path, name, false); 844 } 845 else version (Posix) 846 { 847 if (path) 848 { 849 /* Each path is converted to a cannonical name and then a check is done to see 850 * that the searched name is really a child one of the the paths searched. 851 */ 852 for (size_t i = 0; i < path.dim; i++) 853 { 854 const(char)* cname = null; 855 const(char)* cpath = canonicalName((*path)[i]); 856 //printf("FileName::safeSearchPath(): name=%s; path=%s; cpath=%s\n", 857 // name, (char *)path.data[i], cpath); 858 if (cpath is null) 859 goto cont; 860 cname = canonicalName(combine(cpath, name)); 861 //printf("FileName::safeSearchPath(): cname=%s\n", cname); 862 if (cname is null) 863 goto cont; 864 //printf("FileName::safeSearchPath(): exists=%i " 865 // "strncmp(cpath, cname, %i)=%i\n", exists(cname), 866 // strlen(cpath), strncmp(cpath, cname, strlen(cpath))); 867 // exists and name is *really* a "child" of path 868 if (exists(cname) && strncmp(cpath, cname, strlen(cpath)) == 0) 869 { 870 mem.xfree(cast(void*)cpath); 871 const(char)* p = mem.xstrdup(cname); 872 mem.xfree(cast(void*)cname); 873 return p; 874 } 875 cont: 876 if (cpath) 877 mem.xfree(cast(void*)cpath); 878 if (cname) 879 mem.xfree(cast(void*)cname); 880 } 881 } 882 return null; 883 } 884 else 885 { 886 assert(0); 887 } 888 } 889 890 /** 891 Check if the file the `path` points to exists 892 893 Returns: 894 0 if it does not exists 895 1 if it exists and is not a directory 896 2 if it exists and is a directory 897 */ 898 extern (C++) static int exists(const(char)* name) 899 { 900 return exists(name.toDString); 901 } 902 903 /// Ditto 904 extern (D) static int exists(const(char)[] name) 905 { 906 if (!name.length) 907 return 0; 908 version (Posix) 909 { 910 stat_t st; 911 if (name.toCStringThen!((v) => stat(v.ptr, &st)) < 0) 912 return 0; 913 if (S_ISDIR(st.st_mode)) 914 return 2; 915 return 1; 916 } 917 else version (Windows) 918 { 919 return name.toWStringzThen!((wname) 920 { 921 const dw = GetFileAttributesW(&wname[0]); 922 if (dw == -1) 923 return 0; 924 else if (dw & FILE_ATTRIBUTE_DIRECTORY) 925 return 2; 926 else 927 return 1; 928 }); 929 } 930 else 931 { 932 assert(0); 933 } 934 } 935 936 /** 937 Ensure that the provided path exists 938 939 Accepts a path to either a file or a directory. 940 In the former case, the basepath (path to the containing directory) 941 will be checked for existence, and created if it does not exists. 942 In the later case, the directory pointed to will be checked for existence 943 and created if needed. 944 945 Params: 946 path = a path to a file or a directory 947 948 Returns: 949 `true` if the directory exists or was successfully created 950 */ 951 extern (D) static bool ensurePathExists(const(char)[] path) 952 { 953 //printf("FileName::ensurePathExists(%s)\n", path ? path : ""); 954 if (!path.length) 955 return true; 956 if (exists(path)) 957 return true; 958 959 // We were provided with a file name 960 // We need to call ourselves recursively to ensure parent dir exist 961 const char[] p = FileName.path(path); 962 if (p.length) 963 { 964 version (Windows) 965 { 966 // Note: Windows filename comparison should be case-insensitive, 967 // however p is a subslice of path so we don't need it 968 if (path.length == p.length || 969 (path.length > 2 && path[1] == ':' && path[2 .. $] == p)) 970 { 971 mem.xfree(cast(void*)p.ptr); 972 return true; 973 } 974 } 975 const r = ensurePathExists(p); 976 mem.xfree(cast(void*)p); 977 978 if (!r) 979 return r; 980 } 981 982 version (Windows) 983 const r = _mkdir(path); 984 version (Posix) 985 { 986 errno = 0; 987 const r = path.toCStringThen!((pathCS) => mkdir(pathCS.ptr, (7 << 6) | (7 << 3) | 7)); 988 } 989 990 if (r == 0) 991 return true; 992 993 // Don't error out if another instance of dmd just created 994 // this directory 995 version (Windows) 996 { 997 import core.sys.windows.winerror : ERROR_ALREADY_EXISTS; 998 if (GetLastError() == ERROR_ALREADY_EXISTS) 999 return true; 1000 } 1001 version (Posix) 1002 { 1003 if (errno == EEXIST) 1004 return true; 1005 } 1006 1007 return false; 1008 } 1009 1010 ///ditto 1011 extern (C++) static bool ensurePathExists(const(char)* path) 1012 { 1013 return ensurePathExists(path.toDString); 1014 } 1015 1016 /****************************************** 1017 * Return canonical version of name. 1018 * This code is high risk. 1019 */ 1020 extern (C++) static const(char)* canonicalName(const(char)* name) 1021 { 1022 return canonicalName(name.toDString).ptr; 1023 } 1024 1025 /// Ditto 1026 extern (D) static const(char)[] canonicalName(const(char)[] name) 1027 { 1028 version (Posix) 1029 { 1030 import core.stdc.limits; // PATH_MAX 1031 import core.sys.posix.unistd; // _PC_PATH_MAX 1032 1033 // Older versions of druntime don't have PATH_MAX defined. 1034 // i.e: dmd __VERSION__ < 2085, gdc __VERSION__ < 2076. 1035 static if (!__traits(compiles, PATH_MAX)) 1036 { 1037 version (DragonFlyBSD) 1038 enum PATH_MAX = 1024; 1039 else version (FreeBSD) 1040 enum PATH_MAX = 1024; 1041 else version (linux) 1042 enum PATH_MAX = 4096; 1043 else version (NetBSD) 1044 enum PATH_MAX = 1024; 1045 else version (OpenBSD) 1046 enum PATH_MAX = 1024; 1047 else version (OSX) 1048 enum PATH_MAX = 1024; 1049 else version (Solaris) 1050 enum PATH_MAX = 1024; 1051 } 1052 1053 // Have realpath(), passing a NULL destination pointer may return an 1054 // internally malloc'd buffer, however it is implementation defined 1055 // as to what happens, so cannot rely on it. 1056 static if (__traits(compiles, PATH_MAX)) 1057 { 1058 // Have compile time limit on filesystem path, use it with realpath. 1059 char[PATH_MAX] buf = void; 1060 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf.ptr)); 1061 if (path !is null) 1062 return xarraydup(path.toDString); 1063 } 1064 else static if (__traits(compiles, canonicalize_file_name)) 1065 { 1066 // Have canonicalize_file_name, which malloc's memory. 1067 // We need a dmd.root.rmem allocation though. 1068 auto path = name.toCStringThen!((n) => canonicalize_file_name(n.ptr)); 1069 scope(exit) .free(path.ptr); 1070 if (path !is null) 1071 return xarraydup(path.toDString); 1072 } 1073 else static if (__traits(compiles, _PC_PATH_MAX)) 1074 { 1075 // Panic! Query the OS for the buffer limit. 1076 auto path_max = pathconf("/", _PC_PATH_MAX); 1077 if (path_max > 0) 1078 { 1079 char *buf = cast(char*)mem.xmalloc(path_max); 1080 scope(exit) mem.xfree(buf); 1081 auto path = name.toCStringThen!((n) => realpath(n.ptr, buf)); 1082 if (path !is null) 1083 return xarraydup(path.toDString); 1084 } 1085 } 1086 // Give up trying to support this platform, just duplicate the filename 1087 // unless there is nothing to copy from. 1088 if (!name.length) 1089 return null; 1090 return xarraydup(name); 1091 } 1092 else version (Windows) 1093 { 1094 // Convert to wstring first since otherwise the Win32 APIs have a character limit 1095 return name.toWStringzThen!((wname) 1096 { 1097 /* Apparently, there is no good way to do this on Windows. 1098 * GetFullPathName isn't it, but use it anyway. 1099 */ 1100 // First find out how long the buffer has to be, incl. terminating null. 1101 const capacity = GetFullPathNameW(&wname[0], 0, null, null); 1102 if (!capacity) return null; 1103 auto buffer = cast(wchar*) mem.xmalloc_noscan(capacity * wchar.sizeof); 1104 scope(exit) mem.xfree(buffer); 1105 1106 // Actually get the full path name. If the buffer is large enough, 1107 // the returned length does NOT include the terminating null... 1108 const length = GetFullPathNameW(&wname[0], capacity, buffer, null /*filePart*/); 1109 assert(length == capacity - 1); 1110 1111 return toNarrowStringz(buffer[0 .. length]); 1112 }); 1113 } 1114 else 1115 { 1116 assert(0); 1117 } 1118 } 1119 1120 unittest 1121 { 1122 string filename = "foo.bar"; 1123 const path = canonicalName(filename); 1124 scope(exit) free(path.ptr); 1125 assert(path.length >= filename.length); 1126 assert(path[$ - filename.length .. $] == filename); 1127 } 1128 1129 /******************************** 1130 * Free memory allocated by FileName routines 1131 */ 1132 extern (C++) static void free(const(char)* str) pure 1133 { 1134 if (str) 1135 { 1136 assert(str[0] != cast(char)0xAB); 1137 memset(cast(void*)str, 0xAB, strlen(str) + 1); // stomp 1138 } 1139 mem.xfree(cast(void*)str); 1140 } 1141 1142 extern (C++) const(char)* toChars() const pure nothrow @nogc @trusted 1143 { 1144 // Since we can return an empty slice (but '\0' terminated), 1145 // we don't do bounds check (as `&str[0]` does) 1146 return str.ptr; 1147 } 1148 1149 const(char)[] toString() const pure nothrow @nogc @trusted 1150 { 1151 return str; 1152 } 1153 1154 bool opCast(T)() const pure nothrow @nogc @safe 1155 if (is(T == bool)) 1156 { 1157 return str.ptr !is null; 1158 } 1159 } 1160 1161 version(Windows) 1162 { 1163 /**************************************************************** 1164 * The code before used the POSIX function `mkdir` on Windows. That 1165 * function is now deprecated and fails with long paths, so instead 1166 * we use the newer `CreateDirectoryW`. 1167 * 1168 * `CreateDirectoryW` is the unicode version of the generic macro 1169 * `CreateDirectory`. `CreateDirectoryA` has a file path 1170 * limitation of 248 characters, `mkdir` fails with less and might 1171 * fail due to the number of consecutive `..`s in the 1172 * path. `CreateDirectoryW` also normally has a 248 character 1173 * limit, unless the path is absolute and starts with `\\?\`. Note 1174 * that this is different from starting with the almost identical 1175 * `\\?`. 1176 * 1177 * Params: 1178 * path = The path to create. 1179 * 1180 * Returns: 1181 * 0 on success, 1 on failure. 1182 * 1183 * References: 1184 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855(v=vs.85).aspx 1185 */ 1186 private int _mkdir(const(char)[] path) nothrow 1187 { 1188 const createRet = path.extendedPathThen!( 1189 p => CreateDirectoryW(&p[0], null /*securityAttributes*/)); 1190 // different conventions for CreateDirectory and mkdir 1191 return createRet == 0 ? 1 : 0; 1192 } 1193 1194 /************************************** 1195 * Converts a path to one suitable to be passed to Win32 API 1196 * functions that can deal with paths longer than 248 1197 * characters then calls the supplied function on it. 1198 * 1199 * Params: 1200 * path = The Path to call F on. 1201 * 1202 * Returns: 1203 * The result of calling F on path. 1204 * 1205 * References: 1206 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx 1207 */ 1208 package auto extendedPathThen(alias F)(const(char)[] path) 1209 { 1210 if (!path.length) 1211 return F((wchar[]).init); 1212 return path.toWStringzThen!((wpath) 1213 { 1214 // GetFullPathNameW expects a sized buffer to store the result in. Since we don't 1215 // know how large it has to be, we pass in null and get the needed buffer length 1216 // as the return code. 1217 const pathLength = GetFullPathNameW(&wpath[0], 1218 0 /*length8*/, 1219 null /*output buffer*/, 1220 null /*filePartBuffer*/); 1221 if (pathLength == 0) 1222 { 1223 return F((wchar[]).init); 1224 } 1225 1226 // wpath is the UTF16 version of path, but to be able to use 1227 // extended paths, we need to prefix with `\\?\` and the absolute 1228 // path. 1229 static immutable prefix = `\\?\`w; 1230 1231 // prefix only needed for long names and non-UNC names 1232 const needsPrefix = pathLength >= MAX_PATH && (wpath[0] != '\\' || wpath[1] != '\\'); 1233 const prefixLength = needsPrefix ? prefix.length : 0; 1234 1235 // +1 for the null terminator 1236 const bufferLength = pathLength + prefixLength + 1; 1237 1238 wchar[1024] absBuf = void; 1239 wchar[] absPath = bufferLength > absBuf.length 1240 ? new wchar[bufferLength] : absBuf[0 .. bufferLength]; 1241 1242 absPath[0 .. prefixLength] = prefix[0 .. prefixLength]; 1243 1244 const absPathRet = GetFullPathNameW(&wpath[0], 1245 cast(uint)(absPath.length - prefixLength - 1), 1246 &absPath[prefixLength], 1247 null /*filePartBuffer*/); 1248 1249 if (absPathRet == 0 || absPathRet > absPath.length - prefixLength) 1250 { 1251 return F((wchar[]).init); 1252 } 1253 1254 absPath[$ - 1] = '\0'; 1255 // Strip null terminator from the slice 1256 return F(absPath[0 .. $ - 1]); 1257 }); 1258 } 1259 1260 /********************************** 1261 * Converts a UTF-16 string to a (null-terminated) narrow string. 1262 * Returns: 1263 * If `buffer` is specified and the result fits, a slice of that buffer, 1264 * otherwise a new buffer which can be released via `mem.xfree()`. 1265 * Nulls are propagated, i.e., if `wide` is null, the returned slice is 1266 * null too. 1267 */ 1268 char[] toNarrowStringz(const(wchar)[] wide, char[] buffer = null) nothrow 1269 { 1270 if (wide is null) 1271 return null; 1272 1273 const requiredLength = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, buffer.ptr, cast(int) buffer.length, null, null); 1274 if (requiredLength < buffer.length) 1275 { 1276 buffer[requiredLength] = 0; 1277 return buffer[0 .. requiredLength]; 1278 } 1279 1280 char* newBuffer = cast(char*) mem.xmalloc_noscan(requiredLength + 1); 1281 const length = WideCharToMultiByte(CodePage, 0, wide.ptr, cast(int) wide.length, newBuffer, requiredLength, null, null); 1282 assert(length == requiredLength); 1283 newBuffer[length] = 0; 1284 return newBuffer[0 .. length]; 1285 } 1286 1287 /********************************** 1288 * Converts a narrow string to a (null-terminated) UTF-16 string. 1289 * Returns: 1290 * If `buffer` is specified and the result fits, a slice of that buffer, 1291 * otherwise a new buffer which can be released via `mem.xfree()`. 1292 * Nulls are propagated, i.e., if `narrow` is null, the returned slice is 1293 * null too. 1294 */ 1295 wchar[] toWStringz(const(char)[] narrow, wchar[] buffer = null) nothrow 1296 { 1297 if (narrow is null) 1298 return null; 1299 1300 const requiredLength = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, buffer.ptr, cast(int) buffer.length); 1301 if (requiredLength < buffer.length) 1302 { 1303 buffer[requiredLength] = 0; 1304 return buffer[0 .. requiredLength]; 1305 } 1306 1307 wchar* newBuffer = cast(wchar*) mem.xmalloc_noscan((requiredLength + 1) * wchar.sizeof); 1308 const length = MultiByteToWideChar(CodePage, 0, narrow.ptr, cast(int) narrow.length, newBuffer, requiredLength); 1309 assert(length == requiredLength); 1310 newBuffer[length] = 0; 1311 return newBuffer[0 .. length]; 1312 } 1313 1314 /********************************** 1315 * Converts a slice of UTF-8 characters to an array of wchar that's null 1316 * terminated so it can be passed to Win32 APIs then calls the supplied 1317 * function on it. 1318 * 1319 * Params: 1320 * str = The string to convert. 1321 * 1322 * Returns: 1323 * The result of calling F on the UTF16 version of str. 1324 */ 1325 private auto toWStringzThen(alias F)(const(char)[] str) nothrow 1326 { 1327 if (!str.length) return F(""w.ptr); 1328 1329 wchar[1024] buf = void; 1330 wchar[] wide = toWStringz(str, buf); 1331 scope(exit) wide.ptr != buf.ptr && mem.xfree(wide.ptr); 1332 1333 return F(wide); 1334 } 1335 }