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