yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Gangzheng TongUse wide char version of Windows API (#8390)3aff764c2

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