// zlib open source license // // Copyright (c) 2020 to 2022 David Forsgren Piuva // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // Include fileAPI without falling back on local syntax implicitly. // This prevents any local syntax from being implied in functions that are supposed to use variable pathSyntax. #define NO_IMPLICIT_PATH_SYNTAX #include "fileAPI.h" #ifdef USE_MICROSOFT_WINDOWS // Headers for MS-Windows #include #include #else // Headers for Posix compliant systems. #include #include #include #include #include // The environment flags contain information such as username, language, color settings, which system shell and window manager is used... extern char **environ; #endif #include #include #include "bufferAPI.h" #include "../base/virtualStack.h" namespace dsr { constexpr const char32_t* getPathSeparator(PathSyntax pathSyntax) { if (pathSyntax == PathSyntax::Windows) { return U"\\"; } else if (pathSyntax == PathSyntax::Posix) { return U"/"; } else { return U"?"; } } #ifdef USE_MICROSOFT_WINDOWS using NativeChar = wchar_t; // UTF-16 static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF16LE; #define FILE_ACCESS_FUNCTION _wfopen #define FILE_ACCESS_SELECTION (write ? L"wb" : L"rb") List file_impl_getInputArguments() { // Get a pointer to static memory with the command LPWSTR cmd = GetCommandLineW(); // Split the arguments into an array of arguments int argc = 0; LPWSTR *argv = CommandLineToArgvW(cmd, &argc); // Convert the arguments into dsr::List List args = file_impl_convertInputArguments(argc, (void**)argv); // Free the native list of arguments LocalFree(argv); return args; } #else using NativeChar = char; // UTF-8 static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF8; #define FILE_ACCESS_FUNCTION fopen #define FILE_ACCESS_SELECTION (write ? "wb" : "rb") List file_impl_getInputArguments() { return List(); } #endif // Length of fixed size buffers. const int maxLength = 512; static const NativeChar* toNativeString(const ReadableString &filename, Buffer &buffer) { buffer = string_saveToMemory(filename, nativeEncoding, LineEncoding::CrLf, false, true); return (const NativeChar*)buffer_dangerous_getUnsafeData(buffer); } static String fromNativeString(const NativeChar *text) { return string_dangerous_decodeFromData(text, nativeEncoding); } List file_impl_convertInputArguments(int argn, void **argv) { List result; result.reserve(argn); for (int a = 0; a < argn; a++) { result.push(fromNativeString((NativeChar*)(argv[a]))); } return result; } static FILE* accessFile(const ReadableString &filename, bool write) { Buffer buffer; return FILE_ACCESS_FUNCTION(toNativeString(filename, buffer), FILE_ACCESS_SELECTION); } Buffer file_loadBuffer(const ReadableString& filename, bool mustExist) { String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX); FILE *file = accessFile(modifiedFilename, false); if (file != nullptr) { // Get the file's size by going to the end, measuring, and going back fseek(file, 0L, SEEK_END); uintptr_t fileSize = ftell(file); rewind(file); // Allocate a buffer of the file's size Buffer buffer = buffer_create(fileSize); size_t resultSize = fread((void*)buffer_dangerous_getUnsafeData(buffer), fileSize, 1, file); // Supress warnings. (void)resultSize; fclose(file); return buffer; } else { if (mustExist) { throwError(U"Failed to load ", modifiedFilename, U".\n"); } // If the file cound not be found and opened, an empty buffer is returned return Buffer(); } } bool file_saveBuffer(const ReadableString& filename, Buffer buffer, bool mustWork) { String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX); if (!buffer_exists(buffer)) { if (mustWork) { throwError(U"buffer_save: Can't save a buffer that don't exist to a file.\n"); } return false; } else { FILE *file = accessFile(modifiedFilename, true); if (file != nullptr) { fwrite((void*)buffer_dangerous_getUnsafeData(buffer), buffer_getSize(buffer), 1, file); fclose(file); } else { if (mustWork) { throwError(U"Failed to save ", modifiedFilename, U".\n"); } return false; } } // Success if there are no errors. return true; } const char32_t* file_separator() { return getPathSeparator(LOCAL_PATH_SYNTAX); } bool file_isSeparator(DsrChar c) { return c == U'\\' || c == U'/'; } // Returns the index of the first / or \ in path, or defaultIndex if none existed. int64_t file_findFirstSeparator(const ReadableString &path, int64_t defaultIndex, int64_t startIndex) { for (int64_t i = startIndex; i < string_length(path); i++) { DsrChar c = path[i]; if (file_isSeparator(c)) { return i; } } return defaultIndex; } // Returns the index of the last / or \ in path, or defaultIndex if none existed. int64_t file_findLastSeparator(const ReadableString &path, int64_t defaultIndex) { for (int64_t i = string_length(path) - 1; i >= 0; i--) { DsrChar c = path[i]; if (file_isSeparator(c)) { return i; } } return defaultIndex; } String file_optimizePath(const ReadableString &path, PathSyntax pathSyntax) { String result; // The final output being appended. String currentEntry; // The current entry. bool hadSeparator = false; bool hadContent = false; int64_t inputLength = string_length(path); string_reserve(result, inputLength); // Read null terminator from one element outside of the path to allow concluding an entry not followed by any separator. // The null terminator is not actually stored, but reading out of bound gives a null terminator. for (int64_t i = 0; i <= inputLength; i++) { DsrChar c = path[i]; bool separator = file_isSeparator(c); if (separator || i == inputLength) { bool appendEntry = true; bool appendSeparator = separator; if (hadSeparator) { if (hadContent && string_length(currentEntry) == 0) { // Reduce non-leading // into / by skipping "" entries. // Any leading multiples of slashes have their count preserved, because some systems use them to indicate special use cases. appendEntry = false; appendSeparator = false; } else if (string_match(currentEntry, U".")) { // Reduce /./ into / by skipping "." entries. appendEntry = false; appendSeparator = false; } else if (string_match(currentEntry, U"..")) { // Reduce the parent directory against the reverse ".." entry. result = file_getRelativeParentFolder(result, pathSyntax); if (string_match(result, U"?")) { return U"?"; } appendEntry = false; } } if (appendEntry) { string_append(result, string_removeOuterWhiteSpace(currentEntry)); } if (appendSeparator) { string_append(result, getPathSeparator(pathSyntax)); } currentEntry = U""; if (separator) { hadSeparator = true; } } else { string_appendChar(currentEntry, c); hadContent = true; } } // Remove trailing separators if we had content. if (hadSeparator && hadContent) { int64_t lastNonSeparator = -1; for (int64_t i = string_length(result) - 1; i >= 0; i--) { if (!file_isSeparator(result[i])) { lastNonSeparator = i; break; } } result = string_until(result, lastNonSeparator); } return result; } ReadableString file_getPathlessName(const ReadableString &path) { return string_after(path, file_findLastSeparator(path)); } bool file_hasExtension(const String& path) { int64_t lastDotIndex = string_findLast(path, U'.'); int64_t lastSeparatorIndex = file_findLastSeparator(path); if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) { return true; } else { return false; } } ReadableString file_getExtension(const String& filename) { int64_t lastDotIndex = string_findLast(filename, U'.'); int64_t lastSeparatorIndex = file_findLastSeparator(filename); // Only use the last dot if there is no folder separator after it. if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) { return string_removeOuterWhiteSpace(string_after(filename, lastDotIndex)); } else { return U""; } } ReadableString file_getExtensionless(const String& filename) { int64_t lastDotIndex = string_findLast(filename, U'.'); int64_t lastSeparatorIndex = file_findLastSeparator(filename); // Only use the last dot if there is no folder separator after it. if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) { return string_removeOuterWhiteSpace(string_before(filename, lastDotIndex)); } else { return string_removeOuterWhiteSpace(filename); } } String file_getRelativeParentFolder(const ReadableString &path, PathSyntax pathSyntax) { String optimizedPath = file_optimizePath(path, pathSyntax); if (string_length(optimizedPath) == 0) { // Use .. to go outside of the current directory. return U".."; } else if (string_match(file_getPathlessName(optimizedPath), U"?")) { // From unknown to unknown. return U"?"; } else if (file_isRoot(optimizedPath, false, pathSyntax)) { // If it's the known true root, then we know that it does not have a parent and must fail. return U"?"; } else if (file_isRoot(optimizedPath, true, pathSyntax)) { // If it's an alias for an arbitrary folder, use .. to leave it. return file_combinePaths(optimizedPath, U"..", pathSyntax); } else if (string_match(file_getPathlessName(optimizedPath), U"..")) { // Add more dots to the path. return file_combinePaths(optimizedPath, U"..", pathSyntax); } else { // Inside of something. int64_t lastSeparator = file_findLastSeparator(optimizedPath, 0); if (pathSyntax == PathSyntax::Windows) { // Return everything before the last separator. return string_before(optimizedPath, lastSeparator); } else { // PathSyntax::Posix if (file_hasRoot(path, false, pathSyntax) && lastSeparator == 0) { // Keep the absolute root. return U"/"; } else { // Keep everything before the last separator. return string_before(optimizedPath, lastSeparator); } } } } String file_getTheoreticalAbsoluteParentFolder(const ReadableString &path, const ReadableString ¤tPath, PathSyntax pathSyntax) { if (file_hasRoot(path, true, LOCAL_PATH_SYNTAX)) { // Absolute paths should be treated the same as a theoretical path. return file_getRelativeParentFolder(path, pathSyntax); } else { // If the input is not absolute, convert it before taking the parent directory. return file_getRelativeParentFolder(file_getTheoreticalAbsolutePath(path, currentPath, pathSyntax), pathSyntax); } } String file_getAbsoluteParentFolder(const ReadableString &path) { return file_getTheoreticalAbsoluteParentFolder(path, file_getCurrentPath(), LOCAL_PATH_SYNTAX); } bool file_isRoot(const ReadableString &path, bool treatHomeFolderAsRoot, PathSyntax pathSyntax) { ReadableString cleanPath = string_removeOuterWhiteSpace(path); int64_t length = string_length(cleanPath); if (length == 0) { // Relative path is not a root. return false; } else if (length == 1) { DsrChar c = cleanPath[0]; if (pathSyntax == PathSyntax::Windows) { return c == U'\\'; // Implicit drive root. } else { // PathSyntax::Posix return c == U'/' || (c == U'~' && treatHomeFolderAsRoot); // Root over all drives or home folder. } } else { if (pathSyntax == PathSyntax::Windows && cleanPath[length - 1] == U':') { // C:, D:, ... return true; } else { return false; } } } bool file_hasRoot(const ReadableString &path, bool treatHomeFolderAsRoot, PathSyntax pathSyntax) { int64_t firstSeparator = file_findFirstSeparator(path); if (firstSeparator == -1) { // If there is no separator, path has a root if it is a root. return file_isRoot(path, treatHomeFolderAsRoot, pathSyntax); } else if (firstSeparator == 0) { // Starting with a separator. Either an implicit drive on Windows or the whole system's root on Posix. return true; } else { // Has a root if the first entry before the first slash is a root. return file_isRoot(string_before(path, firstSeparator), treatHomeFolderAsRoot, pathSyntax); } } bool file_setCurrentPath(const ReadableString &path) { Buffer buffer; const NativeChar *nativePath = toNativeString(file_optimizePath(path, LOCAL_PATH_SYNTAX), buffer); #ifdef USE_MICROSOFT_WINDOWS return SetCurrentDirectoryW(nativePath) != 0; #else return chdir(nativePath) == 0; #endif } String file_getCurrentPath() { #ifdef USE_MICROSOFT_WINDOWS NativeChar resultBuffer[maxLength + 1] = {0}; GetCurrentDirectoryW(maxLength, resultBuffer); return fromNativeString(resultBuffer); #else NativeChar resultBuffer[maxLength + 1] = {0}; char* result = getcwd(resultBuffer, maxLength); // Supress warnings about not using the result, because we already have it in the buffer. (void)result; return fromNativeString(resultBuffer); #endif } String file_followSymbolicLink(const ReadableString &path, bool mustExist) { #ifdef USE_MICROSOFT_WINDOWS // TODO: Is there anything that can be used as a symbolic link on Windows? #else String modifiedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX); Buffer buffer; const NativeChar *nativePath = toNativeString(modifiedPath, buffer); NativeChar resultBuffer[maxLength + 1] = {0}; if (readlink(nativePath, resultBuffer, maxLength) != -1) { return fromNativeString(resultBuffer); } #endif if (mustExist) { throwError(U"The symbolic link ", path, U" could not be found!\n"); } return U"?"; } String file_getApplicationFolder(bool allowFallback) { #ifdef USE_MICROSOFT_WINDOWS NativeChar resultBuffer[maxLength + 1] = {0}; GetModuleFileNameW(nullptr, resultBuffer, maxLength); return file_getRelativeParentFolder(fromNativeString(resultBuffer), LOCAL_PATH_SYNTAX); #else NativeChar resultBuffer[maxLength + 1] = {0}; if (readlink("/proc/self/exe", resultBuffer, maxLength) != -1) { // Linux detected return file_getAbsoluteParentFolder(fromNativeString(resultBuffer)); } else if (readlink("/proc/curproc/file", resultBuffer, maxLength) != -1) { // BSD detected return file_getAbsoluteParentFolder(fromNativeString(resultBuffer)); } else if (readlink("/proc/self/path/a.out", resultBuffer, maxLength) != -1) { // Solaris detected return file_getAbsoluteParentFolder(fromNativeString(resultBuffer)); } else if (allowFallback) { return file_getCurrentPath(); } else { throwError(U"file_getApplicationFolder is not implemented for the current system!\n"); return U""; } #endif } String file_combinePaths(const ReadableString &a, const ReadableString &b, PathSyntax pathSyntax) { ReadableString cleanA = string_removeOuterWhiteSpace(a); ReadableString cleanB = string_removeOuterWhiteSpace(b); int64_t lengthA = string_length(cleanA); int64_t lengthB = string_length(cleanB); if (file_hasRoot(b, true, pathSyntax)) { // Restarting from root or home folder. return cleanB; } else if (lengthA == 0) { // Ignoring initial relative path, so that relative paths are not suddenly moved to the root by a new separator. return cleanB; } else if (lengthB == 0) { // Ignoring initial relative path, so that relative paths are not suddenly moved to the root by a new separator. return cleanA; } else { if (file_isSeparator(a[lengthA - 1])) { // Already ending with a separator. return string_combine(cleanA, cleanB); } else { // Combine using a separator. return string_combine(cleanA, getPathSeparator(pathSyntax), cleanB); } } } // Returns path with the drive letter applied from currentPath if missing in path. // Used for converting drive relative paths into true absolute paths on MS-Windows. static String applyDriveLetter(const ReadableString &path, const ReadableString ¤tPath) { // Convert implicit drive into a named drive. if (path[0] == U'\\') { int64_t colonIndex = string_findFirst(currentPath, U':', -1); if (colonIndex == -1) { return U"?"; } else { // Get the drive letter from the current path. String drive = string_until(currentPath, colonIndex); return string_combine(drive, path); } } else { // Already absolute. return path; } } String file_getTheoreticalAbsolutePath(const ReadableString &path, const ReadableString ¤tPath, PathSyntax pathSyntax) { // Home folders are absolute enough, because we don't want to lose the account ambiguity by mangling it into hardcoded usernames. if (file_hasRoot(path, true, pathSyntax)) { if (pathSyntax == PathSyntax::Windows) { // Make sure that no drive letter is missing. return applyDriveLetter(file_optimizePath(path, pathSyntax), currentPath); } else { // Already absolute. return file_optimizePath(path, pathSyntax); } } else { // Convert from relative path. return file_optimizePath(file_combinePaths(currentPath, path, pathSyntax), pathSyntax); } } String file_getAbsolutePath(const ReadableString &path) { return file_getTheoreticalAbsolutePath(path, file_getCurrentPath(), LOCAL_PATH_SYNTAX); } int64_t file_getFileSize(const ReadableString& filename) { int64_t result = -1; String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX); Buffer buffer; const NativeChar *nativePath = toNativeString(modifiedFilename, buffer); #ifdef USE_MICROSOFT_WINDOWS LARGE_INTEGER fileSize; HANDLE fileHandle = CreateFileW(nativePath, 0, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (fileHandle != INVALID_HANDLE_VALUE) { if (GetFileSizeEx(fileHandle, &fileSize)) { result = fileSize.QuadPart; } CloseHandle(fileHandle); } #else struct stat info; if (stat(nativePath, &info) == 0) { result = info.st_size; } #endif return result; } String& string_toStreamIndented(String& target, const EntryType& source, const ReadableString& indentation) { string_append(target, indentation); if (source == EntryType::NotFound) { string_append(target, U"not found"); } else if (source == EntryType::File) { string_append(target, U"a file"); } else if (source == EntryType::Folder) { string_append(target, U"a folder"); } else if (source == EntryType::SymbolicLink) { string_append(target, U"a symbolic link"); } else { string_append(target, U"unhandled"); } return target; } EntryType file_getEntryType(const ReadableString &path) { EntryType result = EntryType::NotFound; String optimizedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX); Buffer buffer; const NativeChar *nativePath = toNativeString(optimizedPath, buffer); #ifdef USE_MICROSOFT_WINDOWS DWORD dwAttrib = GetFileAttributesW(nativePath); if (dwAttrib != INVALID_FILE_ATTRIBUTES) { if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) { result = EntryType::Folder; } else { result = EntryType::File; } } #else struct stat info; int errorCode = stat(nativePath, &info); if (errorCode == 0) { if (S_ISDIR(info.st_mode)) { result = EntryType::Folder; } else if (S_ISREG(info.st_mode)) { result = EntryType::File; } else if (S_ISLNK(info.st_mode)) { result = EntryType::SymbolicLink; } else { result = EntryType::UnhandledType; } } #endif return result; } bool file_getFolderContent(const ReadableString& folderPath, std::function action) { String optimizedPath = file_optimizePath(folderPath, LOCAL_PATH_SYNTAX); #ifdef USE_MICROSOFT_WINDOWS String pattern = file_combinePaths(optimizedPath, U"*.*", LOCAL_PATH_SYNTAX); Buffer buffer; const NativeChar *nativePattern = toNativeString(pattern, buffer); WIN32_FIND_DATAW findData; HANDLE findHandle = FindFirstFileW(nativePattern, &findData); if (findHandle == INVALID_HANDLE_VALUE) { return false; } else { while (true) { String entryName = fromNativeString(findData.cFileName); if (!string_match(entryName, U".") && !string_match(entryName, U"..")) { String entryPath = file_combinePaths(optimizedPath, entryName, LOCAL_PATH_SYNTAX); EntryType entryType = EntryType::UnhandledType; if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { entryType = EntryType::Folder; } else { entryType = EntryType::File; } action(entryPath, entryName, entryType); } if (!FindNextFileW(findHandle, &findData)) { break; } } FindClose(findHandle); } #else Buffer buffer; const NativeChar *nativePath = toNativeString(optimizedPath, buffer); DIR *directory = opendir(nativePath); if (directory == nullptr) { return false; } else { while (true) { dirent *entry = readdir(directory); if (entry != nullptr) { String entryName = fromNativeString(entry->d_name); if (!string_match(entryName, U".") && !string_match(entryName, U"..")) { String entryPath = file_combinePaths(optimizedPath, entryName, LOCAL_PATH_SYNTAX); EntryType entryType = file_getEntryType(entryPath); action(entryPath, entryName, entryType); } } else { break; } } } closedir(directory); #endif return true; } void file_getPathEntries(const ReadableString& path, std::function action) { int64_t sectionStart = 0; int64_t length = string_length(path); for (int64_t i = 0; i < string_length(path); i++) { DsrChar c = path[i]; if (file_isSeparator(c)) { int64_t sectionEnd = i - 1; // Inclusive end ReadableString content = string_inclusiveRange(path, sectionStart, sectionEnd); if (string_length(content)) { action(content, sectionStart, sectionEnd); } sectionStart = i + 1; } } if (length > sectionStart) { int64_t sectionEnd = length - 1; // Inclusive end ReadableString content = string_inclusiveRange(path, sectionStart, sectionEnd); if (string_length(content)) { action(content, sectionStart, sectionEnd); } } } bool file_createFolder(const ReadableString &path) { bool result = false; String optimizedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX); Buffer buffer; const NativeChar *nativePath = toNativeString(optimizedPath, buffer); #ifdef USE_MICROSOFT_WINDOWS // Create folder with permissions inherited. result = (CreateDirectoryW(nativePath, nullptr) != 0); #else // Create folder with default permissions. Read, write and search for owner and group. Read and search for others. result = (mkdir(nativePath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0); #endif return result; } bool file_removeEmptyFolder(const ReadableString& path) { bool result = false; String optimizedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX); Buffer buffer; const NativeChar *nativePath = toNativeString(optimizedPath, buffer); // Remove the empty folder. #ifdef USE_MICROSOFT_WINDOWS result = (RemoveDirectoryW(nativePath) != 0); #else result = (rmdir(nativePath) == 0); #endif return result; } bool file_removeFile(const ReadableString& filename) { bool result = false; String optimizedPath = file_optimizePath(filename, LOCAL_PATH_SYNTAX); Buffer buffer; const NativeChar *nativePath = toNativeString(optimizedPath, buffer); // Remove the empty folder. #ifdef USE_MICROSOFT_WINDOWS result = (DeleteFileW(nativePath) != 0); #else result = (unlink(nativePath) == 0); #endif return result; } // DsrProcess is a reference counted pointer to DsrProcessImpl where the last retrieved status still remains for all to read. // Because aliasing with multiple users of the same pid would deplete the messages in advance. struct DsrProcessImpl { // Once the process has already terminated, process_getStatus will only return lastStatus. bool terminated = false; // We can assume that a newly created process is running until we are told that it terminated or crashed, // because DsrProcessImpl would not be created unless launching the application was successful. DsrProcessStatus lastStatus = DsrProcessStatus::Running; #ifdef USE_MICROSOFT_WINDOWS PROCESS_INFORMATION processInfo; DsrProcessImpl(const PROCESS_INFORMATION &processInfo) : processInfo(processInfo) {} ~DsrProcessImpl() { CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); } #else pid_t pid; DsrProcessImpl(pid_t pid) : pid(pid) {} #endif }; DsrProcessStatus process_getStatus(const DsrProcess &process) { if (process.isNull()) { return DsrProcessStatus::NotStarted; } else { if (!process->terminated) { #ifdef USE_MICROSOFT_WINDOWS DWORD status = WaitForSingleObject(process->processInfo.hProcess, 0); if (status == WAIT_OBJECT_0) { DWORD processResult; GetExitCodeProcess(process->processInfo.hProcess, &processResult); process->lastStatus = (processResult == 0) ? DsrProcessStatus::Completed : DsrProcessStatus::Crashed; process->terminated = true; } #else // When using WNOHANG, waitpid returns zero when the program is still running, and the child pid if it terminated. int status = 0; if (waitpid(process->pid, &status, WNOHANG) != 0) { if (WIFEXITED(status)) { if (WEXITSTATUS(status) == 0) { // The program finished and returned 0 for success. process->lastStatus = DsrProcessStatus::Completed; } else { // The program finished, but returned a non-zero result indicating that something still went wrong. process->lastStatus = DsrProcessStatus::Crashed; } process->terminated = true; } else if (WIFSIGNALED(status)) { // The program was stopped due to a hard crash. process->lastStatus = DsrProcessStatus::Crashed; process->terminated = true; } } #endif } return process->lastStatus; } } DsrProcess process_execute(const ReadableString& programPath, List arguments, bool mustWork) { // Convert the program path into the native format. String optimizedPath = file_optimizePath(programPath, LOCAL_PATH_SYNTAX); // Convert #ifdef USE_MICROSOFT_WINDOWS DsrChar separator = U' '; #else DsrChar separator = U'\0'; #endif String flattenedArguments; string_append(flattenedArguments, optimizedPath); string_appendChar(flattenedArguments, separator); for (int64_t a = 0; a < arguments.length(); a++) { string_append(flattenedArguments, arguments[a]); string_appendChar(flattenedArguments, separator); } Buffer argBuffer; const NativeChar *nativeArgs = toNativeString(flattenedArguments, argBuffer); #ifdef USE_MICROSOFT_WINDOWS STARTUPINFOW startInfo; PROCESS_INFORMATION processInfo; memset(&startInfo, 0, sizeof(STARTUPINFO)); memset(&processInfo, 0, sizeof(PROCESS_INFORMATION)); startInfo.cb = sizeof(STARTUPINFO); if (CreateProcessW(nullptr, (LPWSTR)nativeArgs, nullptr, nullptr, true, 0, nullptr, nullptr, &startInfo, &processInfo)) { return handle_create(processInfo).setName("DSR Process"); // Success } else { if (mustWork) { throwError(U"Failed to call ", programPath, U"! False returned from CreateProcessW.\n"); } return DsrProcess(); // Failure } #else Buffer pathBuffer; const NativeChar *nativePath = toNativeString(optimizedPath, pathBuffer); int64_t codePoints = buffer_getSize(argBuffer) / sizeof(NativeChar); // Count arguments. int argc = arguments.length() + 1; // Allocate an array of pointers for each argument and a null terminator. VirtualStackAllocation argv(argc + 1); // Fill the array with pointers to the native strings. int64_t startOffset = 0; int currentArg = 0; for (int64_t c = 0; c < codePoints && currentArg < argc; c++) { if (nativeArgs[c] == 0) { argv[currentArg] = &(nativeArgs[startOffset]); startOffset = c + 1; currentArg++; } } argv[currentArg] = nullptr; pid_t pid = 0; int error = posix_spawn(&pid, nativePath, nullptr, nullptr, (char**)argv.getUnsafe(), environ); if (error == 0) { return handle_create(pid).setName("DSR Process"); // Success } else { if (mustWork) { throwError(U"Failed to call ", programPath, U"! Got error code ", error, " from posix_spawn.\n"); } return DsrProcess(); // Failure } #endif } }