yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
3aff764c2
master
1#define _CRT_SECURE_NO_WARNINGS 1 2 3#include "slang-io.h" 4 5#include "slang-char-util.h" 6#include "slang-com-helper.h" 7#include "slang-exception.h" 8#include "slang-string-util.h" 9 10#ifndef __STDC__ 11#define __STDC__ 1 12#endif 13 14#include <sys/stat.h> 15 16#ifdef _WIN32 17// clang-format off 18// include ordering sensitive 19# include <windows.h> 20# include <direct.h> 21# include <shellapi.h> 22// clang-format on 23#endif 24 25#if defined(__linux__ )|| defined(__CYGWIN__ )|| SLANG_APPLE_FAMILY || SLANG_WASM 26#include <fcntl.h> 27#include <unistd.h> 28// For Path::find 29#include <dirent.h> 30#include <fnmatch.h> 31#include <ftw.h> // for nftw 32#include <sys/file.h> 33#include <sys/stat.h> 34#endif 35 36#if SLANG_APPLE_FAMILY 37#include <mach-o/dyld.h> 38#endif 39 40#include <filesystem> 41#include <limits.h> /* PATH_MAX */ 42#include <stdio.h> 43#include <stdlib.h> 44 45namespace Slang 46{ 47 48/* static */ SlangResult File ::remove (const String & fileName ) 49{ 50#ifdef _WIN32 51 52// https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-deletefilew 53if (DeleteFileW (fileName .toWString ())) 54 { 55return SLANG_OK ; 56 } 57return SLANG_FAIL ; 58#else 59// https://linux.die.net/man/3/remove 60if (::remove (fileName .getBuffer ())== 0 ) 61 { 62return SLANG_OK ; 63 } 64return SLANG_FAIL ; 65#endif 66} 67 68 69#ifdef _WIN32 70/* static */ SlangResult File ::generateTemporary ( 71const UnownedStringSlice & inPrefix , 72Slang ::String & outFileName ) 73{ 74// https://docs.microsoft.com/en-us/windows/win32/fileio/creating-and-using-a-temporary-file 75 76String tempPath ; 77 { 78int count = MAX_PATH + 1 ; 79while (true) 80 { 81wchar_t * wideChars = (wchar_t * )_alloca (count * sizeof (wchar_t )); 82// Gets the temp path env string (no guarantee it's a valid path). 83// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw 84DWORD ret = ::GetTempPathW (count - 1 ,wideChars ); 85if (ret == 0 ) 86 { 87return SLANG_FAIL ; 88 } 89if (ret > DWORD (count - 1 )) 90 { 91count = ret + 1 ; 92continue ; 93 } 94tempPath = String ::fromWString (wideChars ); 95break ; 96 } 97 } 98 99if (!File ::exists (tempPath )) 100 { 101return SLANG_FAIL ; 102 } 103 104const String prefix (inPrefix ); 105String tempFileName ; 106 107 { 108wchar_t wideChars [MAX_PATH + 1 ]; 109 110// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettempfilenamew 111// Generates a temporary file name. 112// Will create a file with this name. 113DWORD ret = ::GetTempFileNameW (tempPath .toWString (),prefix .toWString (),0 ,wideChars ); 114 115if (ret == 0 ) 116 { 117return SLANG_FAIL ; 118 } 119tempFileName = String ::fromWString (wideChars ); 120 } 121 122SLANG_ASSERT (File ::exists (tempFileName )); 123 124outFileName = tempFileName ; 125return SLANG_OK ; 126} 127#else 128/* static */ SlangResult File ::generateTemporary ( 129const UnownedStringSlice & inPrefix , 130Slang ::String & outFileName ) 131{ 132StringBuilder builder ; 133builder <<"/tmp/" <<inPrefix <<"-XXXXXX" ; 134 135List < char > buffer ; 136auto copySize = builder .getLength (); 137buffer .setCount (copySize + 1 ); 138// Satisfy GCC 139SLANG_ASSUME (copySize < PTRDIFF_MAX && copySize > 0 ); 140 ::memcpy (buffer .getBuffer (),builder .getBuffer (),copySize ); 141buffer [copySize ]= 0 ; 142 143int handle = mkstemp (buffer .getBuffer ()); 144if (handle == -1 ) 145 { 146return SLANG_FAIL ; 147 } 148 149// Close the handle.. 150close (handle ); 151 152outFileName = buffer .getBuffer (); 153SLANG_ASSERT (File ::exists (outFileName )); 154 155return SLANG_OK ; 156} 157#endif 158 159/* static */ SlangResult File ::makeExecutable (const String & fileName ) 160{ 161#ifdef _WIN32 162SLANG_UNUSED (fileName ); 163// As long as file extension is executable, it can be executed 164return SLANG_OK ; 165#else 166struct stat st ; 167if (::stat (fileName .getBuffer (),& st )!= 0 ) 168 { 169return SLANG_FAIL ; 170 } 171if (st .st_mode & S_IXUSR ) 172 { 173return SLANG_OK ; 174 } 175// It would probably be slightly neater to set all executable bits 176// aside from those in umask.. 177if (::chmod (fileName .getBuffer (),st .st_mode & 07777 |S_IXUSR )!= 0 ) 178 { 179return SLANG_FAIL ; 180 } 181return SLANG_OK ; 182#endif 183} 184 185 186bool File ::exists (const String & fileName ) 187{ 188#ifdef _WIN32 189struct _stat32 statVar ; 190return ::_wstat32 (((String )fileName ).toWString (),& statVar )!= -1 ; 191#else 192struct stat statVar ; 193return ::stat (fileName .getBuffer (),& statVar )== 0 ; 194#endif 195} 196 197String Path ::replaceExt (const String & path ,const char * newExt ) 198{ 199StringBuilder sb (path .getLength ()+ 10 ); 200Index dotPos = findExtIndex (path ); 201 202if (dotPos < 0 ) 203dotPos = path .getLength (); 204sb .append (path .getBuffer (),dotPos ); 205sb .append ('.' ); 206sb .append (newExt ); 207return sb .produceString (); 208} 209 210/* static */ Index Path ::findLastSeparatorIndex (UnownedStringSlice const & path ) 211{ 212const char * chars = path .begin (); 213for (Index i = path .getLength ()- 1 ;i >=0 ;-- i ) 214 { 215const char c = chars [i ]; 216if (c == '/' || c == '\\' ) 217 { 218return i ; 219 } 220 } 221return -1 ; 222} 223 224/* static */ Index Path ::findExtIndex (UnownedStringSlice const & path ) 225{ 226const Index sepIndex = findLastSeparatorIndex (path ); 227 228const Index dotIndex = path .lastIndexOf ('.' ); 229if (sepIndex >=0 ) 230 { 231// Index has to be in the last part of the path 232return (dotIndex > sepIndex ) ?dotIndex :-1 ; 233 } 234else 235 { 236return dotIndex ; 237 } 238} 239 240String Path ::getFileName (const String & path ) 241{ 242Index pos = findLastSeparatorIndex (path ); 243if (pos >=0 ) 244 { 245pos = pos + 1 ; 246return path .subString (pos ,path .getLength ()- pos ); 247 } 248else 249 { 250return path ; 251 } 252} 253 254/* static */ String Path ::getFileNameWithoutExt (const String & path ) 255{ 256Index sepIndex = findLastSeparatorIndex (path ); 257sepIndex = (sepIndex < 0 ) ?0 : (sepIndex + 1 ); 258Index dotIndex = findExtIndex (path ); 259dotIndex = (dotIndex < 0 ) ?path .getLength () :dotIndex ; 260 261return path .subString (sepIndex ,dotIndex - sepIndex ); 262} 263 264/* static*/ String Path ::getPathWithoutExt (const String & path ) 265{ 266Index dotPos = findExtIndex (path ); 267if (dotPos >=0 ) 268return path .subString (0 ,dotPos ); 269else 270return path ; 271} 272 273UnownedStringSlice Path ::getPathExt (const UnownedStringSlice & path ) 274{ 275const Index dotPos = findExtIndex (path ); 276if (dotPos >=0 ) 277 { 278return path .subString (dotPos + 1 ,path .getLength ()- dotPos - 1 ); 279 } 280else 281 { 282// Note that the caller can identify if path has no extension or just a . 283// as if it's a dot a zero length slice is returned in path 284// If it's not then a default slice is returned (which doesn't point into path). 285// 286// Granted this is a little obscure and perhaps should be improved. 287return UnownedStringSlice (); 288 } 289} 290 291String Path ::getParentDirectory (const String & path ) 292{ 293Index pos = findLastSeparatorIndex (path ); 294if (pos >=0 ) 295return path .subString (0 ,pos ); 296else 297return "" ; 298} 299 300/* static */ void Path ::append (StringBuilder & ioBuilder ,const UnownedStringSlice & path ) 301{ 302if (ioBuilder .getLength ()== 0 ) 303 { 304ioBuilder .append (path ); 305return ; 306 } 307if (path .getLength ()> 0 ) 308 { 309// If ioBuilder doesn't end in a delimiter, add one 310if (!isDelimiter (ioBuilder [ioBuilder .getLength ()- 1 ])) 311 { 312// Determine the preferred delimiter to use based on existing path. 313char preferedDelimiter = kOSCanonicalPathDelimiter ; 314if (kOSAlternativePathDelimiter != preferedDelimiter ) 315 { 316// If we found the existing path uses the alternative delimiter, we will 317// use that instead of the canonical one. 318constexpr Index kMaxDelimiterSearchRange = 32 ; 319for (Index i = 0 ;i < Math ::Min (kMaxDelimiterSearchRange ,ioBuilder .getLength ()); 320i ++ ) 321 { 322if (ioBuilder [i ]== kOSAlternativePathDelimiter ) 323 { 324preferedDelimiter = kOSAlternativePathDelimiter ; 325break ; 326 } 327 } 328 } 329ioBuilder .append (preferedDelimiter ); 330 } 331// Check that path doesn't start with a path delimiter 332SLANG_ASSERT (!isDelimiter (path [0 ])); 333// Append the path 334ioBuilder .append (path ); 335 } 336} 337 338/* static */ void Path ::combineIntoBuilder ( 339const UnownedStringSlice & path1 , 340const UnownedStringSlice & path2 , 341StringBuilder & outBuilder ) 342{ 343outBuilder .clear (); 344outBuilder .append (path1 ); 345append (outBuilder ,path2 ); 346} 347 348String Path ::combine (const String & path1 ,const String & path2 ) 349{ 350if (path1 .getLength ()== 0 ) 351 { 352return path2 ; 353 } 354 355StringBuilder sb ; 356combineIntoBuilder (path1 .getUnownedSlice (),path2 .getUnownedSlice (),sb ); 357return sb .produceString (); 358} 359String Path ::combine (const String & path1 ,const String & path2 ,const String & path3 ) 360{ 361StringBuilder sb ; 362sb .append (path1 ); 363append (sb ,path2 .getUnownedSlice ()); 364append (sb ,path3 .getUnownedSlice ()); 365return sb .produceString (); 366} 367 368/* static */ bool Path ::isDriveSpecification (const UnownedStringSlice & element ) 369{ 370switch (element .getLength ()) 371 { 372case 0 : 373 { 374// We'll just assume it is 375return true; 376 } 377case 2 : 378 { 379// Look for a windows like drive spec 380const char firstChar = element [0 ]; 381return element [1 ]== ':' && ((firstChar >='a' && firstChar <='z' )|| 382 (firstChar >='A' && firstChar <='Z' )); 383 } 384default : 385return false; 386 } 387} 388 389UnownedStringSlice Path ::getFirstElement (const UnownedStringSlice & in ) 390{ 391const char * end = in .end (); 392const char * cur = in .begin (); 393// Find delimiter or the end 394while (cur < end && !Path ::isDelimiter (* cur )) 395++ cur ; 396return UnownedStringSlice (in .begin (),cur ); 397} 398 399/* static */ bool Path ::isAbsolute (const UnownedStringSlice & path ) 400{ 401if (path .getLength ()> 0 && isDelimiter (path [0 ])) 402 { 403return true; 404 } 405 406#if SLANG_WINDOWS_FAMILY 407// Check for the \\ network drive style 408if (path .getLength () >=2 && path [0 ]== '\\' && path [1 ]== '\\' ) 409 { 410return true; 411 } 412 413// Check for drive 414if (isDriveSpecification (getFirstElement (path ))) 415 { 416return true; 417 } 418#endif 419 420return false; 421} 422 423/* static */ void Path ::split (const UnownedStringSlice & path ,List < UnownedStringSlice >& splitOut ) 424{ 425splitOut .clear (); 426 427const char * start = path .begin (); 428const char * end = path .end (); 429 430while (start < end ) 431 { 432const char * cur = start ; 433// Find the split 434while (cur < end && !isDelimiter (* cur )) 435cur ++ ; 436 437splitOut .add (UnownedStringSlice (start ,cur )); 438 439// Next 440start = cur + 1 ; 441 } 442 443// Okay if the end is empty. And we aren't with a spec like // or c:/ , then drop the final 444// slash 445if (splitOut .getCount ()> 1 && splitOut .getLast ().getLength ()== 0 ) 446 { 447if (splitOut .getCount ()== 2 && isDriveSpecification (splitOut [0 ])) 448 { 449return ; 450 } 451// Remove the last 452splitOut .removeLast (); 453 } 454} 455 456/* static */ bool Path ::hasRelativeElement (const UnownedStringSlice & path ) 457{ 458List < UnownedStringSlice > splitPath ; 459split (path ,splitPath ); 460 461for (const auto & cur :splitPath ) 462 { 463if (cur == "." || cur == ".." ) 464 { 465return true; 466 } 467 } 468return false; 469} 470 471/* static */ SlangResult Path ::simplify ( 472const UnownedStringSlice & path , 473SimplifyStyle style , 474StringBuilder & outPath ) 475{ 476if (path .getLength ()== 0 ) 477 { 478return SLANG_FAIL ; 479 } 480 481List < UnownedStringSlice > splitPath ; 482split (UnownedStringSlice (path ),splitPath ); 483 484simplify (splitPath ); 485 486const auto simplifyIntegral = SimplifyIntegral (style ); 487 488// If it has a relative part then it's not absolute 489if ((simplifyIntegral & SimplifyFlag ::AbsoluteOnly )&& 490splitPath .indexOf (UnownedStringSlice ::fromLiteral (".." )) >=0 ) 491 { 492return SLANG_E_NOT_FOUND ; 493 } 494 495// We allow splitPath.getCount() == 0, because 496// the original path could have been '.' or './.' 497// 498// Special handling this case is in Path::join 499 500// If we want the path produced such that is *not* output with a root (ie SimplifyFlag::NoRoot) 501// we detect if we a rooted path (ie in effect starting with "/") and so splitPath[0] == "" 502// and remove that part from when doing the join. 503if ((simplifyIntegral & SimplifyFlag ::NoRoot )&& 504 (splitPath .getCount ()&& splitPath [0 ].getLength ()== 0 )) 505 { 506// If we allow without a root, we remove from the join 507Path ::join (splitPath .getBuffer ()+ 1 ,splitPath .getCount ()- 1 ,outPath ); 508 } 509else 510 { 511Path ::join (splitPath .getBuffer (),splitPath .getCount (),outPath ); 512 } 513 514return SLANG_OK ; 515} 516 517/* static */ void Path ::simplify (List < UnownedStringSlice >& ioSplit ) 518{ 519// Strictly speaking we could do something about case on platforms like window, but here we 520// won't worry about that 521for (Index i = 0 ;i < ioSplit .getCount ();i ++ ) 522 { 523const UnownedStringSlice & cur = ioSplit [i ]; 524if (cur == "." && ioSplit .getCount ()> 1 ) 525 { 526// Just remove it 527ioSplit .removeAt (i ); 528i -- ; 529 } 530else if (cur == ".." && i > 0 ) 531 { 532// Can we remove this and the one before ? 533UnownedStringSlice & before = ioSplit [i - 1 ]; 534if (before == ".." || (i == 1 && isDriveSpecification (before ))) 535 { 536// Can't do it, but we allow relative, so just leave for now 537continue ; 538 } 539ioSplit .removeRange (i - 1 ,2 ); 540i -= 2 ; 541 } 542 } 543} 544 545/* static */ void Path ::join (const UnownedStringSlice * slices ,Index count ,StringBuilder & out ) 546{ 547out .clear (); 548 549if (count == 0 ) 550 { 551out <<"." ; 552 } 553else if (count == 1 && slices [0 ].getLength ()== 0 ) 554 { 555// It's the root 556out <<kPathDelimiter ; 557 } 558else 559 { 560StringUtil ::join (slices ,count ,kPathDelimiter ,out ); 561 } 562} 563 564 565/* static */ String Path ::simplify (const UnownedStringSlice & path ) 566{ 567List < UnownedStringSlice > splitPath ; 568split (path ,splitPath ); 569simplify (splitPath ); 570 571// Reconstruct the string 572StringBuilder builder ; 573join (splitPath .getBuffer (),splitPath .getCount (),builder ); 574return builder .toString (); 575} 576 577bool Path ::createDirectory (const String & path ) 578{ 579#if defined(_WIN32 ) 580return _wmkdir (path .toWString ())== 0 ; 581#else 582return mkdir (path .getBuffer (),0777 )== 0 ; 583#endif 584} 585 586bool Path ::createDirectoryRecursive (const String & path ) 587{ 588String finalPath = Path ::simplify (path ); 589if (finalPath .getLength ()== 0 ) 590 { 591return false; 592 } 593 594List < String > pathList ; 595 596// Check whether the parent directories exist, and add to the pathList if they are 597// not, we will create all the directories from back of the list. 598String parentDir = finalPath ; 599for (;;) 600 { 601if (parentDir .getLength ()== 0 || File ::exists (parentDir )) 602 { 603break ; 604 } 605else 606 { 607pathList .add (parentDir ); 608parentDir = Path ::getParentDirectory (parentDir ); 609 } 610 } 611 612// If there are no directories to create, then we are done 613if (pathList .getCount ()== 0 ) 614 { 615return true; 616 } 617 618// Traverse from back of the list, because that is most outer directory. 619Int i = 0 ; 620for (i = pathList .getCount ()- 1 ;i >=0 ;i -- ) 621 { 622if (!createDirectory (pathList [i ])) 623 { 624break ; 625 } 626 } 627 628// Something wrong when creating parent directories 629if (i > 0 ) 630 { 631// Remove the directories if we've created 632if (i != pathList .getCount ()- 1 ) 633remove (pathList [i ]); 634 635return false; 636 } 637 638return true; 639} 640 641/* static */ SlangResult Path ::getPathType (const String & path ,SlangPathType * pathTypeOut ) 642{ 643#ifdef _WIN32 644// https://msdn.microsoft.com/en-us/library/14h5k7ff.aspx 645struct _stat32 statVar ; 646if (::_wstat32 (String (path ).toWString (),& statVar )== 0 ) 647 { 648if (statVar .st_mode & _S_IFDIR ) 649 { 650* pathTypeOut = SLANG_PATH_TYPE_DIRECTORY ; 651return SLANG_OK ; 652 } 653else if (statVar .st_mode & _S_IFREG ) 654 { 655* pathTypeOut = SLANG_PATH_TYPE_FILE ; 656return SLANG_OK ; 657 } 658return SLANG_FAIL ; 659 } 660 661return SLANG_E_NOT_FOUND ; 662#else 663struct stat statVar ; 664if (::stat (path .getBuffer (),& statVar )== 0 ) 665 { 666if (S_ISDIR (statVar .st_mode )) 667 { 668* pathTypeOut = SLANG_PATH_TYPE_DIRECTORY ; 669return SLANG_OK ; 670 } 671if (S_ISREG (statVar .st_mode )) 672 { 673* pathTypeOut = SLANG_PATH_TYPE_FILE ; 674return SLANG_OK ; 675 } 676return SLANG_FAIL ; 677 } 678 679return SLANG_E_NOT_FOUND ; 680#endif 681} 682 683 684/* static */ SlangResult Path ::getCanonical (const String & path ,String & canonicalPathOut ) 685{ 686#if defined(_WIN32 ) 687// https://msdn.microsoft.com/en-us/library/506720ff.aspx 688wchar_t * absPath = ::_wfullpath (nullptr ,path .toWString (),0 ); 689if (!absPath ) 690 { 691return SLANG_FAIL ; 692 } 693 694canonicalPathOut = String ::fromWString (absPath ); 695 ::free (absPath ); 696return SLANG_OK ; 697#else 698#if 1 699 700// http://man7.org/linux/man-pages/man3/realpath.3.html 701char * canonicalPath = ::realpath (path .begin (),nullptr ); 702if (canonicalPath ) 703 { 704canonicalPathOut = canonicalPath ; 705 ::free (canonicalPath ); 706return SLANG_OK ; 707 } 708return SLANG_FAIL ; 709#else 710// This is a mechanism to get an approximation of canonical path if we don't have 'realpath' 711// We only can get if the file exists. This checks that the ../. etc are really valid 712SlangPathType pathType ; 713SLANG_RETURN_ON_FAIL (getPathType (path ,& pathType )); 714if (isAbsolute (path )) 715 { 716// If it's absolute, we can just simplify as is 717canonicalPathOut = Path ::simplify (path ); 718return SLANG_OK ; 719 } 720else 721 { 722char buffer [PATH_MAX ]; 723// https://linux.die.net/man/3/getcwd 724const char * getCwdPath = getcwd (buffer ,SLANG_COUNT_OF (buffer )); 725if (!getCwdPath ) 726 { 727return SLANG_FAIL ; 728 } 729 730// Okay combine the paths 731String combinedPaths = Path ::combine (String (getCwdPath ),path ); 732// Simplify 733canonicalPathOut = Path ::simplify (combinedPaths ); 734return SLANG_OK ; 735 } 736#endif 737#endif 738} 739 740String Path ::getCurrentPath () 741{ 742Slang ::String path ; 743getCanonical ("." ,path ); 744return path ; 745} 746 747String Path ::getRelativePath (String base ,String path ) 748{ 749 std::filesystem::path p1 (base .getBuffer ()); 750 std::filesystem::path p2 (path .getBuffer ()); 751 std::error_code ec ; 752auto result = std::filesystem::relative (p2 ,p1 ,ec ); 753if (ec ) 754return path ; 755return String (reinterpret_cast < const char *> (result .generic_u8string ().c_str ())); 756} 757 758SlangResult Path ::remove (const String & path ) 759{ 760#ifdef _WIN32 761// Need to determine if its a file or directory 762 763SlangPathType pathType ; 764SLANG_RETURN_ON_FAIL (getPathType (path ,& pathType )); 765 766 767switch (pathType ) 768 { 769case SLANG_PATH_TYPE_FILE : 770 { 771// https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-deletefilew 772if (DeleteFileW (path .toWString ())) 773 { 774return SLANG_OK ; 775 } 776break ; 777 } 778case SLANG_PATH_TYPE_DIRECTORY : 779 { 780// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectoryw 781if (RemoveDirectoryW (path .toWString ())) 782 { 783return SLANG_OK ; 784 } 785break ; 786 } 787default : 788break ; 789 } 790 791return SLANG_FAIL ; 792#else 793// https://linux.die.net/man/3/remove 794if (::remove (path .getBuffer ())== 0 ) 795 { 796return SLANG_OK ; 797 } 798return SLANG_FAIL ; 799#endif 800} 801 802/* static */ SlangResult Path ::removeNonEmpty (const String & path ) 803{ 804if (File ::exists (path )== false) 805 { 806return SLANG_OK ; 807 } 808 809StringBuilder msgBuilder ; 810// Path::remove() doesn't support remove a non-empty directory, so we need to implement 811// a simple function to remove the directory recursively. 812#ifdef _WIN32 813// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shfileoperationw 814// Note: the fromPath requires a double-null-terminated string. 815// Convert to wide string first, then manually create double-null-terminated buffer 816auto widePath = path .toWString (); 817Index widePathLen = wcslen (widePath ); 818wchar_t * doubleNullPath = (wchar_t * )_alloca ((widePathLen + 2 )* sizeof (wchar_t )); 819wcscpy (doubleNullPath ,widePath ); 820doubleNullPath [widePathLen ]= L'\0' ;// First null terminator 821doubleNullPath [widePathLen + 1 ]= L'\0' ;// Second null terminator for SHFileOperationW 822 823SHFILEOPSTRUCTW file_op = { 824NULL , 825FO_DELETE , 826doubleNullPath , 827nullptr , 828FOF_NOCONFIRMATION |FOF_NOERRORUI |FOF_SILENT , 829 false, 8300 , 831nullptr }; 832int ret = SHFileOperationW (& file_op ); 833if (ret ) 834 { 835return SLANG_FAIL ; 836 } 837#else 838auto unlink_cb = 839 [](const char * fpath ,const struct stat * sb ,int typeflag ,struct FTW * ftwbuf )-> int 840 { 841SLANG_UNUSED (sb ) 842SLANG_UNUSED (typeflag ) 843SLANG_UNUSED (ftwbuf ) 844int rv = ::remove (fpath ); 845if (rv ) 846 { 847perror (fpath ); 848 } 849return rv ; 850 }; 851// https://linux.die.net/man/3/nftw 852int ret = ::nftw (path .begin (),unlink_cb ,64 ,FTW_DEPTH |FTW_PHYS ); 853if (ret ) 854 { 855return SLANG_FAIL ; 856 } 857#endif 858 859return SLANG_OK ; 860} 861 862#if defined(_WIN32 ) 863/* static */ SlangResult Path ::find ( 864const String & directoryPath , 865const char * pattern , 866Visitor * visitor ) 867{ 868pattern = pattern ?pattern :"*" ; 869String searchPath = Path ::combine (directoryPath ,pattern ); 870 871WIN32_FIND_DATAW fileData ; 872 873HANDLE findHandle = FindFirstFileW (searchPath .toWString (),& fileData ); 874if (findHandle == INVALID_HANDLE_VALUE ) 875 { 876return SLANG_E_NOT_FOUND ; 877 } 878 879do 880 { 881if (!((wcscmp (fileData .cFileName ,L"." )== 0 )|| (wcscmp (fileData .cFileName ,L".." )== 0 ))) 882 { 883const Type type = (fileData .dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) 884 ?Type ::Directory 885 :Type ::File ; 886 887String filename = String ::fromWString (fileData .cFileName ); 888visitor -> accept (type ,filename .getUnownedSlice ()); 889 } 890 }while (FindNextFileW (findHandle ,& fileData )!= 0 ); 891 892 ::FindClose (findHandle ); 893return SLANG_OK ; 894} 895#else 896/* static */ SlangResult Path ::find ( 897const String & directoryPath , 898const char * pattern , 899Visitor * visitor ) 900{ 901DIR * directory = opendir (directoryPath .getBuffer ()); 902 903if (!directory ) 904 { 905return SLANG_E_NOT_FOUND ; 906 } 907 908StringBuilder builder ; 909for (;;) 910 { 911dirent * entry = readdir (directory ); 912if (entry == nullptr ) 913 { 914break ; 915 } 916 917if (strcmp (entry -> d_name ,"." )== 0 || strcmp (entry -> d_name ,".." )== 0 ) 918 { 919continue ; 920 } 921 922// If there is a pattern, check if it matches, and if it doesn't ignore it 923if (pattern && fnmatch (pattern ,entry -> d_name ,0 )!= 0 ) 924 { 925continue ; 926 } 927 928const UnownedStringSlice filename (entry -> d_name ); 929 930// Produce the full path, to do stat 931Path ::combineIntoBuilder (directoryPath .getUnownedSlice (),filename ,builder ); 932 933// fprintf(stderr, "stat(%s)\n", path.getBuffer()); 934struct stat fileInfo ; 935if (stat (builder .getBuffer (),& fileInfo )!= 0 ) 936 { 937continue ; 938 } 939 940Type type = Type ::Unknown ; 941if (S_ISDIR (fileInfo .st_mode )) 942 { 943type = Type ::Directory ; 944 } 945else if (S_ISREG (fileInfo .st_mode )) 946 { 947type = Type ::File ; 948 } 949 950visitor -> accept (type ,filename ); 951 } 952 953closedir (directory ); 954return SLANG_OK ; 955} 956#endif 957 958bool Path ::equals (String path1 ,String path2 ) 959{ 960Path ::getCanonical (path1 ,path1 ); 961Path ::getCanonical (path2 ,path2 ); 962#if SLANG_WINDOWS_FAMILY 963return path1 .getUnownedSlice ().caseInsensitiveEquals (path2 .getUnownedSlice ()); 964#else 965return path1 == path2 ; 966#endif 967} 968 969/// Gets the path to the executable that was invoked that led to the current threads execution 970/// If run from a shared library/dll will be the path of the executable that loaded said library 971/// @param outPath Pointer to buffer to hold the path. 972/// @param ioPathSize Size of the buffer to hold the path (including zero terminator). 973/// @return SLANG_OK on success, SLANG_E_BUFFER_TOO_SMALL if buffer is too small. If ioPathSize is 974/// changed it will be the required size 975static SlangResult _calcExectuablePath (char * outPath ,size_t * ioSize ) 976{ 977SLANG_ASSERT (ioSize ); 978const size_t bufferSize = * ioSize ; 979SLANG_ASSERT (bufferSize > 0 ); 980 981#if SLANG_WINDOWS_FAMILY 982// https://docs.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-getmodulefilenamew 983 984// Use wide character version and convert back to UTF-8 985wchar_t * widePath = (wchar_t * )_alloca (bufferSize * sizeof (wchar_t )); 986DWORD res = ::GetModuleFileNameW (::GetModuleHandle (nullptr ),widePath ,DWORD (bufferSize )); 987// If it fits it's the size not including terminator. So must be less than bufferSize 988if (res < bufferSize ) 989 { 990// Convert back to UTF-8 991int utf8Len = WideCharToMultiByte ( 992CP_UTF8 , 9930 , 994widePath , 995-1 , 996outPath , 997 (int )bufferSize , 998nullptr , 999nullptr ); 1000if (utf8Len > 0 ) 1001 { 1002return SLANG_OK ; 1003 } 1004 } 1005return SLANG_E_BUFFER_TOO_SMALL ; 1006#elif SLANG_LINUX_FAMILY 1007 1008#if defined(__linux__ )|| defined(__CYGWIN__ ) 1009// https://linux.die.net/man/2/readlink 1010// Mark last byte with 0, so can check overrun 1011ssize_t resSize = ::readlink ("/proc/self/exe" ,outPath ,bufferSize ); 1012if (resSize < 0 ) 1013 { 1014return SLANG_FAIL ; 1015 } 1016if (size_t (resSize + 1 ) >=bufferSize ) 1017 { 1018return SLANG_E_BUFFER_TOO_SMALL ; 1019 } 1020// Zero terminate 1021outPath [resSize ]= 0 ; 1022return SLANG_OK ; 1023#else 1024String text = Slang ::File ::readAllText ("/proc/self/maps" ); 1025Index startIndex = text .indexOf ('/' ); 1026if (startIndex == Index (-1 )) 1027 { 1028return SLANG_FAIL ; 1029 } 1030Index endIndex = text .indexOf ("\n" ,startIndex ); 1031endIndex = (endIndex == Index (-1 )) ?text .getLength () :endIndex ; 1032 1033auto path = text .subString (startIndex ,endIndex - startIndex ); 1034 1035if (path .getLength ()< bufferSize ) 1036 { 1037 ::memcpy (outPath ,path .begin (),path .getLength ()); 1038outPath [path .getLength ()]= 0 ; 1039return SLANG_OK ; 1040 } 1041 1042* ioSize = path .getLength ()+ 1 ; 1043return SLANG_E_BUFFER_TOO_SMALL ; 1044#endif 1045 1046#elif SLANG_APPLE_FAMILY 1047// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html 1048uint32_t size = uint32_t (* ioSize ); 1049switch (_NSGetExecutablePath (outPath ,& size )) 1050 { 1051case 0 : 1052return SLANG_OK ; 1053case -1 : 1054 { 1055* ioSize = size ; 1056return SLANG_E_BUFFER_TOO_SMALL ; 1057 } 1058default : 1059break ; 1060 } 1061return SLANG_FAIL ; 1062#else 1063SLANG_UNUSED (outPath ); 1064return SLANG_E_NOT_IMPLEMENTED ; 1065#endif 1066} 1067 1068static String _getExecutablePath () 1069{ 1070List < char > buffer ; 1071// Guess an initial buffer size 1072buffer .setCount (1024 ); 1073 1074while (true) 1075 { 1076const size_t size = size_t (buffer .getCount ()); 1077size_t bufferSize = size ; 1078SlangResult res = _calcExectuablePath (buffer .getBuffer (),& bufferSize ); 1079 1080if (SLANG_SUCCEEDED (res )) 1081 { 1082return String (buffer .getBuffer ()); 1083 } 1084 1085if (res != SLANG_E_BUFFER_TOO_SMALL ) 1086 { 1087// Couldn't determine the executable string 1088return String (); 1089 } 1090 1091// If bufferSize changed it should be the exact fit size, else we just make the buffer 1092// bigger by a guess (50% bigger) 1093bufferSize = (bufferSize > size ) ?bufferSize : (bufferSize + bufferSize /2 ); 1094buffer .setCount (Index (bufferSize )); 1095 } 1096} 1097 1098/* static */ String Path ::getExecutablePath () 1099{ 1100// TODO(JS): It would be better if we lazily evaluated this, and then returned the same string 1101// on subsequent calls, because it has to do a fair amount of work depending on target. This was 1102// how previous code worked, with a static variable. Unfortunately this led to a memory leak 1103// being reported - because reporting is done before a global variable is released. It would be 1104// good to have a mechanism that allows 'core' library source free memory in some controlled 1105// manner. 1106return _getExecutablePath (); 1107} 1108 1109SlangResult File ::readAllText (const Slang ::String & fileName ,String & outText ) 1110{ 1111RefPtr < FileStream > stream (new FileStream ); 1112SLANG_RETURN_ON_FAIL ( 1113stream -> init (fileName ,FileMode ::Open ,FileAccess ::Read ,FileShare ::ReadWrite )); 1114 1115StreamReader reader ; 1116SLANG_RETURN_ON_FAIL (reader .init (stream )); 1117SLANG_RETURN_ON_FAIL (reader .readToEnd (outText )); 1118 1119return SLANG_OK ; 1120} 1121 1122SlangResult File ::readAllBytes (const Slang ::String & path ,Slang ::List < unsigned char >& out ) 1123{ 1124FileStream stream ; 1125SLANG_RETURN_ON_FAIL (stream .init (path ,FileMode ::Open ,FileAccess ::Read ,FileShare ::ReadWrite )); 1126 1127const Int64 start = stream .getPosition (); 1128stream .seek (SeekOrigin ::End ,0 ); 1129const Int64 end = stream .getPosition (); 1130stream .seek (SeekOrigin ::Start ,start ); 1131 1132const Int64 positionSizeInBytes = end - start ; 1133 1134if (UInt64 (positionSizeInBytes )> UInt64 (kMaxIndex )) 1135 { 1136// It's too large to fit in memory. 1137return SLANG_FAIL ; 1138 } 1139 1140const Index sizeInBytes = Index (positionSizeInBytes ); 1141 1142out .setCount (sizeInBytes ); 1143 1144size_t readSizeInBytes ; 1145SLANG_RETURN_ON_FAIL (stream .read (out .getBuffer (),sizeInBytes ,readSizeInBytes )); 1146 1147// If not all read just return an error 1148return (size_t (sizeInBytes )== readSizeInBytes ) ?SLANG_OK :SLANG_FAIL ; 1149} 1150 1151SlangResult File ::readAllBytes (const String & path ,ScopedAllocation & out ) 1152{ 1153FileStream stream ; 1154SLANG_RETURN_ON_FAIL (stream .init (path ,FileMode ::Open ,FileAccess ::Read ,FileShare ::ReadWrite )); 1155 1156const Int64 start = stream .getPosition (); 1157stream .seek (SeekOrigin ::End ,0 ); 1158const Int64 end = stream .getPosition (); 1159stream .seek (SeekOrigin ::Start ,start ); 1160 1161const Int64 positionSizeInBytes = end - start ; 1162 1163if (UInt64 (positionSizeInBytes )> UInt64 (~size_t (0 ))) 1164 { 1165// It's too large to fit in memory. 1166return SLANG_FAIL ; 1167 } 1168 1169const size_t sizeInBytes = size_t (positionSizeInBytes ); 1170 1171void * data = out .allocateTerminated (sizeInBytes ); 1172if (!data ) 1173 { 1174return SLANG_E_OUT_OF_MEMORY ; 1175 } 1176 1177size_t readSizeInBytes ; 1178SLANG_RETURN_ON_FAIL (stream .read (data ,sizeInBytes ,readSizeInBytes )); 1179 1180// If not all read just return an error 1181return (sizeInBytes == readSizeInBytes ) ?SLANG_OK :SLANG_FAIL ; 1182} 1183 1184SlangResult File ::writeAllBytes (const String & path ,const void * data ,size_t size ) 1185{ 1186FileStream stream ; 1187SLANG_RETURN_ON_FAIL ( 1188stream .init (path ,FileMode ::Create ,FileAccess ::Write ,FileShare ::ReadWrite )); 1189SLANG_RETURN_ON_FAIL (stream .write (data ,size )); 1190return SLANG_OK ; 1191} 1192 1193SlangResult File ::writeAllText (const Slang ::String & fileName ,const Slang ::String & text ) 1194{ 1195RefPtr < FileStream > stream = new FileStream ; 1196SLANG_RETURN_ON_FAIL (stream -> init (fileName ,FileMode ::Create )); 1197 1198StreamWriter writer ; 1199SLANG_RETURN_ON_FAIL (writer .init (stream )); 1200SLANG_RETURN_ON_FAIL (writer .write (text )); 1201 1202return SLANG_OK ; 1203} 1204 1205SlangResult File ::writeAllTextIfChanged (const String & fileName ,UnownedStringSlice text ) 1206{ 1207String existingContent ; 1208auto result = File ::readAllText (fileName ,existingContent ); 1209if (SLANG_FAILED (result )|| existingContent != text ) 1210 { 1211return File ::writeNativeText (fileName ,text .begin (),text .getLength ()); 1212 } 1213return SLANG_OK ; 1214} 1215 1216/* static */ SlangResult File ::writeNativeText (const String & path ,const void * data ,size_t size ) 1217{ 1218FILE * file = fopen (path .getBuffer (),"w" ); 1219if (!file ) 1220 { 1221return SLANG_FAIL ; 1222 } 1223 1224const auto count = fwrite (data ,size ,1 ,file ); 1225fclose (file ); 1226 1227return (count == 1 ) ?SLANG_OK :SLANG_FAIL ; 1228} 1229 1230String URI ::getPath ()const 1231{ 1232Index startIndex = uri .indexOf ("://" ); 1233if (startIndex == -1 ) 1234return String (); 1235startIndex += 3 ; 1236Index endIndex = uri .indexOf ('?' ); 1237if (endIndex == -1 ) 1238endIndex = uri .getLength (); 1239StringBuilder sb ; 1240#if SLANG_WINDOWS_FAMILY 1241if (uri [startIndex ]== '/' ) 1242startIndex ++ ; 1243#endif 1244for (Index i = startIndex ;i < endIndex ;) 1245 { 1246auto ch = uri [i ]; 1247if (ch == '%' ) 1248 { 1249Int charVal = CharUtil ::getHexDigitValue (uri [i + 1 ])* 16 + 1250CharUtil ::getHexDigitValue (uri [i + 2 ]); 1251sb .appendChar ((char )charVal ); 1252i += 3 ; 1253 } 1254else 1255 { 1256sb .appendChar (uri [i ]); 1257i ++ ; 1258 } 1259 } 1260return sb .produceString (); 1261} 1262 1263StringSlice URI ::getProtocol ()const 1264{ 1265Index separatorIndex = uri .indexOf ("://" ); 1266if (separatorIndex != -1 ) 1267return uri .subString (0 ,separatorIndex ); 1268return StringSlice (); 1269} 1270 1271bool URI ::isSafeURIChar (char ch ) 1272{ 1273return (ch >='0' && ch <='9' )|| (ch >='A' && ch <='Z' )|| (ch >='a' && ch <='z' )|| 1274ch == '-' || ch == '_' || ch == '/' || ch == '.' ; 1275} 1276 1277URI URI ::fromLocalFilePath (UnownedStringSlice path ) 1278{ 1279URI uri ; 1280StringBuilder sb ; 1281sb <<"file://" ; 1282 1283#if SLANG_WINDOWS_FAMILY 1284sb <<"/" ; 1285#endif 1286 1287for (auto ch :path ) 1288 { 1289if (isSafeURIChar (ch )) 1290 { 1291sb .appendChar (ch ); 1292 } 1293else if (ch == '\\' ) 1294 { 1295sb .appendChar ('/' ); 1296 } 1297else 1298 { 1299char buffer [32 ]; 1300int length = intToAscii (buffer , (int )ch ,16 ); 1301sb <<"%" <<UnownedStringSlice (buffer ,length ); 1302 } 1303 } 1304return URI ::fromString (sb .getUnownedSlice ()); 1305} 1306 1307URI URI ::fromString (UnownedStringSlice uriString ) 1308{ 1309URI uri ; 1310uri .uri = uriString ; 1311return uri ; 1312} 1313 1314 1315SlangResult LockFile ::open (const String & fileName ) 1316{ 1317#if SLANG_WINDOWS_FAMILY 1318m_fileHandle = ::CreateFileW ( 1319fileName .toWString (), 1320GENERIC_READ |GENERIC_WRITE , 1321FILE_SHARE_READ |FILE_SHARE_WRITE , 1322NULL , 1323CREATE_ALWAYS , 1324FILE_ATTRIBUTE_NORMAL |FILE_FLAG_OVERLAPPED , 1325NULL ); 1326m_isOpen = m_fileHandle != INVALID_HANDLE_VALUE ; 1327#else 1328m_fileHandle = ::open (fileName .getBuffer (),O_RDWR |O_CREAT ,0600 ); 1329m_isOpen = m_fileHandle != -1 ; 1330#endif 1331return m_isOpen ?SLANG_OK :SLANG_E_CANNOT_OPEN ; 1332} 1333 1334void LockFile ::close () 1335{ 1336if (!m_isOpen ) 1337return ; 1338 1339#if SLANG_WINDOWS_FAMILY 1340if (m_fileHandle != INVALID_HANDLE_VALUE ) 1341 { 1342 ::CloseHandle (m_fileHandle ); 1343m_fileHandle = INVALID_HANDLE_VALUE ; 1344 } 1345#else 1346if (m_fileHandle != -1 ) 1347 { 1348 ::close (m_fileHandle ); 1349m_fileHandle = -1 ; 1350 } 1351#endif 1352 1353m_isOpen = false; 1354} 1355 1356SlangResult LockFile ::tryLock (LockType lockType ) 1357{ 1358if (!m_isOpen ) 1359return SLANG_E_CANNOT_OPEN ; 1360 1361SlangResult result = SLANG_OK ; 1362#if SLANG_WINDOWS_FAMILY 1363OVERLAPPED overlapped = {}; 1364DWORD flags = lockType == LockType ::Shared 1365 ?LOCKFILE_FAIL_IMMEDIATELY 1366 : (LOCKFILE_EXCLUSIVE_LOCK |LOCKFILE_FAIL_IMMEDIATELY ); 1367if (::LockFileEx (m_fileHandle ,flags ,DWORD (0 ), ~DWORD (0 ), ~DWORD (0 ),& overlapped )== 0 ) 1368 { 1369result = SLANG_E_TIME_OUT ; 1370 } 1371#else 1372int operation = lockType == LockType ::Shared ? (LOCK_SH |LOCK_NB ) : (LOCK_EX |LOCK_NB ); 1373if (::flock (m_fileHandle ,operation )!= 0 ) 1374 { 1375result = SLANG_E_TIME_OUT ; 1376 } 1377#endif 1378return result ; 1379} 1380 1381SlangResult LockFile ::lock (LockType lockType ) 1382{ 1383if (!m_isOpen ) 1384return SLANG_E_CANNOT_OPEN ; 1385 1386SlangResult result = SLANG_OK ; 1387#if SLANG_WINDOWS_FAMILY 1388OVERLAPPED overlapped = {}; 1389overlapped .hEvent = ::CreateEvent (NULL , TRUE, FALSE,NULL ); 1390DWORD flags = lockType == LockType ::Shared ?0 :LOCKFILE_EXCLUSIVE_LOCK ; 1391if (::LockFileEx (m_fileHandle ,flags ,DWORD (0 ), ~DWORD (0 ), ~DWORD (0 ),& overlapped )== 0 ) 1392 { 1393auto err = ::GetLastError (); 1394if (err == ERROR_IO_PENDING ) 1395 { 1396DWORD bytes ; 1397if (::GetOverlappedResult (m_fileHandle ,& overlapped ,& bytes , TRUE)== 0 ) 1398 { 1399result = SLANG_E_INTERNAL_FAIL ; 1400 } 1401 } 1402else 1403 { 1404result = SLANG_E_INTERNAL_FAIL ; 1405 } 1406 } 1407 ::CloseHandle (overlapped .hEvent ); 1408#else 1409int operation = lockType == LockType ::Shared ?LOCK_SH :LOCK_EX ; 1410if (::flock (m_fileHandle ,operation )!= 0 ) 1411 { 1412result = SLANG_E_INTERNAL_FAIL ; 1413 } 1414#endif 1415return result ; 1416} 1417 1418SlangResult LockFile ::unlock () 1419{ 1420if (!m_isOpen ) 1421return SLANG_E_CANNOT_OPEN ; 1422 1423#if SLANG_WINDOWS_FAMILY 1424OVERLAPPED overlapped = {}; 1425if (::UnlockFileEx (m_fileHandle ,DWORD (0 ), ~DWORD (0 ), ~DWORD (0 ),& overlapped )== 0 ) 1426 { 1427return SLANG_E_INTERNAL_FAIL ; 1428 } 1429#else 1430if (::flock (m_fileHandle ,LOCK_UN )!= 0 ) 1431 { 1432return SLANG_E_INTERNAL_FAIL ; 1433 } 1434#endif 1435return SLANG_OK ; 1436} 1437 1438LockFile ::LockFile () 1439 :m_isOpen (false) 1440{ 1441#if SLANG_WINDOWS_FAMILY 1442m_fileHandle = INVALID_HANDLE_VALUE ; 1443#else 1444m_fileHandle = -1 ; 1445#endif 1446} 1447 1448LockFile ::~LockFile () 1449{ 1450close (); 1451} 1452}// namespace Slang