fileAPI.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2020 to 2022 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. // Include fileAPI without falling back on local syntax implicitly.
  24. // This prevents any local syntax from being implied in functions that are supposed to use variable pathSyntax.
  25. #define NO_IMPLICIT_PATH_SYNTAX
  26. #include "fileAPI.h"
  27. #ifdef USE_MICROSOFT_WINDOWS
  28. // Headers for MS-Windows
  29. #include <windows.h>
  30. #include <synchapi.h>
  31. #else
  32. // Headers for Posix compliant systems.
  33. #include <unistd.h>
  34. #include <spawn.h>
  35. #include <sys/wait.h>
  36. #include <sys/stat.h>
  37. #include <dirent.h>
  38. // The environment flags contain information such as username, language, color settings, which system shell and window manager is used...
  39. extern char **environ;
  40. #endif
  41. #include <fstream>
  42. #include <cstdlib>
  43. #include "bufferAPI.h"
  44. namespace dsr {
  45. constexpr const char32_t* getPathSeparator(PathSyntax pathSyntax) {
  46. if (pathSyntax == PathSyntax::Windows) {
  47. return U"\\";
  48. } else if (pathSyntax == PathSyntax::Posix) {
  49. return U"/";
  50. } else {
  51. return U"?";
  52. }
  53. }
  54. #ifdef USE_MICROSOFT_WINDOWS
  55. using NativeChar = wchar_t; // UTF-16
  56. static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF16LE;
  57. #define FILE_ACCESS_FUNCTION _wfopen
  58. #define FILE_ACCESS_SELECTION (write ? L"wb" : L"rb")
  59. List<String> file_impl_getInputArguments() {
  60. // Get a pointer to static memory with the command
  61. LPWSTR cmd = GetCommandLineW();
  62. // Split the arguments into an array of arguments
  63. int argc = 0;
  64. LPWSTR *argv = CommandLineToArgvW(cmd, &argc);
  65. // Convert the arguments into dsr::List<dsr::String>
  66. List<String> args = file_impl_convertInputArguments(argc, (void**)argv);
  67. // Free the native list of arguments
  68. LocalFree(argv);
  69. return args;
  70. }
  71. #else
  72. using NativeChar = char; // UTF-8
  73. static const CharacterEncoding nativeEncoding = CharacterEncoding::BOM_UTF8;
  74. #define FILE_ACCESS_FUNCTION fopen
  75. #define FILE_ACCESS_SELECTION (write ? "wb" : "rb")
  76. List<String> file_impl_getInputArguments() { return List<String>(); }
  77. #endif
  78. // Length of fixed size buffers.
  79. const int maxLength = 512;
  80. static const NativeChar* toNativeString(const ReadableString &filename, Buffer &buffer) {
  81. buffer = string_saveToMemory(filename, nativeEncoding, LineEncoding::CrLf, false, true);
  82. return (const NativeChar*)buffer_dangerous_getUnsafeData(buffer);
  83. }
  84. static String fromNativeString(const NativeChar *text) {
  85. return string_dangerous_decodeFromData(text, nativeEncoding);
  86. }
  87. List<String> file_impl_convertInputArguments(int argn, void **argv) {
  88. List<String> result;
  89. result.reserve(argn);
  90. for (int a = 0; a < argn; a++) {
  91. result.push(fromNativeString((NativeChar*)(argv[a])));
  92. }
  93. return result;
  94. }
  95. static FILE* accessFile(const ReadableString &filename, bool write) {
  96. Buffer buffer;
  97. return FILE_ACCESS_FUNCTION(toNativeString(filename, buffer), FILE_ACCESS_SELECTION);
  98. }
  99. Buffer file_loadBuffer(const ReadableString& filename, bool mustExist) {
  100. String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX);
  101. FILE *file = accessFile(modifiedFilename, false);
  102. if (file != nullptr) {
  103. // Get the file's size by going to the end, measuring, and going back
  104. fseek(file, 0L, SEEK_END);
  105. int64_t fileSize = ftell(file);
  106. rewind(file);
  107. // Allocate a buffer of the file's size
  108. Buffer buffer = buffer_create(fileSize);
  109. fread((void*)buffer_dangerous_getUnsafeData(buffer), fileSize, 1, file);
  110. fclose(file);
  111. return buffer;
  112. } else {
  113. if (mustExist) {
  114. throwError(U"Failed to load ", modifiedFilename, ".\n");
  115. }
  116. // If the file cound not be found and opened, an empty buffer is returned
  117. return Buffer();
  118. }
  119. }
  120. bool file_saveBuffer(const ReadableString& filename, Buffer buffer, bool mustWork) {
  121. String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX);
  122. if (!buffer_exists(buffer)) {
  123. if (mustWork) {
  124. throwError(U"buffer_save: Can't save a buffer that don't exist to a file.\n");
  125. }
  126. return false;
  127. } else {
  128. FILE *file = accessFile(modifiedFilename, true);
  129. if (file != nullptr) {
  130. fwrite((void*)buffer_dangerous_getUnsafeData(buffer), buffer_getSize(buffer), 1, file);
  131. fclose(file);
  132. } else {
  133. if (mustWork) {
  134. throwError("Failed to save ", modifiedFilename, ".\n");
  135. }
  136. return false;
  137. }
  138. }
  139. // Success if there are no errors.
  140. return true;
  141. }
  142. const char32_t* file_separator() {
  143. return getPathSeparator(LOCAL_PATH_SYNTAX);
  144. }
  145. bool file_isSeparator(DsrChar c) {
  146. return c == U'\\' || c == U'/';
  147. }
  148. // Returns the index of the first / or \ in path, or defaultIndex if none existed.
  149. int64_t file_findFirstSeparator(const ReadableString &path, int64_t defaultIndex, int64_t startIndex) {
  150. for (int64_t i = startIndex; i < string_length(path); i++) {
  151. DsrChar c = path[i];
  152. if (file_isSeparator(c)) {
  153. return i;
  154. }
  155. }
  156. return defaultIndex;
  157. }
  158. // Returns the index of the last / or \ in path, or defaultIndex if none existed.
  159. int64_t file_findLastSeparator(const ReadableString &path, int64_t defaultIndex) {
  160. for (int64_t i = string_length(path) - 1; i >= 0; i--) {
  161. DsrChar c = path[i];
  162. if (file_isSeparator(c)) {
  163. return i;
  164. }
  165. }
  166. return defaultIndex;
  167. }
  168. String file_optimizePath(const ReadableString &path, PathSyntax pathSyntax) {
  169. String result; // The final output being appended.
  170. String currentEntry; // The current entry.
  171. bool hadSeparator = false;
  172. bool hadContent = false;
  173. int64_t inputLength = string_length(path);
  174. string_reserve(result, inputLength);
  175. // Read null terminator from one element outside of the path to allow concluding an entry not followed by any separator.
  176. // The null terminator is not actually stored, but reading out of bound gives a null terminator.
  177. for (int64_t i = 0; i <= inputLength; i++) {
  178. DsrChar c = path[i];
  179. bool separator = file_isSeparator(c);
  180. if (separator || i == inputLength) {
  181. bool appendEntry = true;
  182. bool appendSeparator = separator;
  183. if (hadSeparator) {
  184. if (hadContent && string_length(currentEntry) == 0) {
  185. // Reduce non-leading // into / by skipping "" entries.
  186. // Any leading multiples of slashes have their count preserved, because some systems use them to indicate special use cases.
  187. appendEntry = false;
  188. appendSeparator = false;
  189. } else if (string_match(currentEntry, U".")) {
  190. // Reduce /./ into / by skipping "." entries.
  191. appendEntry = false;
  192. appendSeparator = false;
  193. } else if (string_match(currentEntry, U"..")) {
  194. // Reduce the parent directory against the reverse ".." entry.
  195. result = file_getRelativeParentFolder(result, pathSyntax);
  196. if (string_match(result, U"?")) {
  197. return U"?";
  198. }
  199. appendEntry = false;
  200. }
  201. }
  202. if (appendEntry) {
  203. string_append(result, string_removeOuterWhiteSpace(currentEntry));
  204. }
  205. if (appendSeparator) {
  206. string_append(result, getPathSeparator(pathSyntax));
  207. }
  208. currentEntry = U"";
  209. if (separator) {
  210. hadSeparator = true;
  211. }
  212. } else {
  213. string_appendChar(currentEntry, c);
  214. hadContent = true;
  215. }
  216. }
  217. // Remove trailing separators if we had content.
  218. if (hadSeparator && hadContent) {
  219. int64_t lastNonSeparator = -1;
  220. for (int64_t i = string_length(result) - 1; i >= 0; i--) {
  221. if (!file_isSeparator(result[i])) {
  222. lastNonSeparator = i;
  223. break;
  224. }
  225. }
  226. result = string_until(result, lastNonSeparator);
  227. }
  228. return result;
  229. }
  230. ReadableString file_getPathlessName(const ReadableString &path) {
  231. return string_after(path, file_findLastSeparator(path));
  232. }
  233. bool file_hasExtension(const String& path) {
  234. int64_t lastDotIndex = string_findLast(path, U'.');
  235. int64_t lastSeparatorIndex = file_findLastSeparator(path);
  236. if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) {
  237. return true;
  238. } else {
  239. return false;
  240. }
  241. }
  242. ReadableString file_getExtension(const String& filename) {
  243. int64_t lastDotIndex = string_findLast(filename, U'.');
  244. int64_t lastSeparatorIndex = file_findLastSeparator(filename);
  245. // Only use the last dot if there is no folder separator after it.
  246. if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) {
  247. return string_removeOuterWhiteSpace(string_after(filename, lastDotIndex));
  248. } else {
  249. return U"";
  250. }
  251. }
  252. ReadableString file_getExtensionless(const String& filename) {
  253. int64_t lastDotIndex = string_findLast(filename, U'.');
  254. int64_t lastSeparatorIndex = file_findLastSeparator(filename);
  255. // Only use the last dot if there is no folder separator after it.
  256. if (lastDotIndex != -1 && lastSeparatorIndex < lastDotIndex) {
  257. return string_removeOuterWhiteSpace(string_before(filename, lastDotIndex));
  258. } else {
  259. return string_removeOuterWhiteSpace(filename);
  260. }
  261. }
  262. String file_getRelativeParentFolder(const ReadableString &path, PathSyntax pathSyntax) {
  263. String optimizedPath = file_optimizePath(path, pathSyntax);
  264. if (string_length(optimizedPath) == 0) {
  265. // Use .. to go outside of the current directory.
  266. return U"..";
  267. } else if (string_match(file_getPathlessName(optimizedPath), U"?")) {
  268. // From unknown to unknown.
  269. return U"?";
  270. } else if (file_isRoot(optimizedPath, false, pathSyntax)) {
  271. // If it's the known true root, then we know that it does not have a parent and must fail.
  272. return U"?";
  273. } else if (file_isRoot(optimizedPath, true, pathSyntax)) {
  274. // If it's an alias for an arbitrary folder, use .. to leave it.
  275. return file_combinePaths(optimizedPath, U"..", pathSyntax);
  276. } else if (string_match(file_getPathlessName(optimizedPath), U"..")) {
  277. // Add more dots to the path.
  278. return file_combinePaths(optimizedPath, U"..", pathSyntax);
  279. } else {
  280. // Inside of something.
  281. int64_t lastSeparator = file_findLastSeparator(optimizedPath, 0);
  282. if (pathSyntax == PathSyntax::Windows) {
  283. // Return everything before the last separator.
  284. return string_before(optimizedPath, lastSeparator);
  285. } else { // PathSyntax::Posix
  286. if (file_hasRoot(path, false, pathSyntax) && lastSeparator == 0) {
  287. // Keep the absolute root.
  288. return U"/";
  289. } else {
  290. // Keep everything before the last separator.
  291. return string_before(optimizedPath, lastSeparator);
  292. }
  293. }
  294. }
  295. }
  296. String file_getTheoreticalAbsoluteParentFolder(const ReadableString &path, const ReadableString &currentPath, PathSyntax pathSyntax) {
  297. if (file_hasRoot(path, true, LOCAL_PATH_SYNTAX)) {
  298. // Absolute paths should be treated the same as a theoretical path.
  299. return file_getRelativeParentFolder(path, pathSyntax);
  300. } else {
  301. // If the input is not absolute, convert it before taking the parent directory.
  302. return file_getRelativeParentFolder(file_getTheoreticalAbsolutePath(path, currentPath, pathSyntax), pathSyntax);
  303. }
  304. }
  305. String file_getAbsoluteParentFolder(const ReadableString &path) {
  306. return file_getTheoreticalAbsoluteParentFolder(path, file_getCurrentPath(), LOCAL_PATH_SYNTAX);
  307. }
  308. bool file_isRoot(const ReadableString &path, bool treatHomeFolderAsRoot, PathSyntax pathSyntax) {
  309. ReadableString cleanPath = string_removeOuterWhiteSpace(path);
  310. int64_t length = string_length(cleanPath);
  311. if (length == 0) {
  312. // Relative path is not a root.
  313. return false;
  314. } else if (length == 1) {
  315. DsrChar c = cleanPath[0];
  316. if (pathSyntax == PathSyntax::Windows) {
  317. return c == U'\\'; // Implicit drive root.
  318. } else { // PathSyntax::Posix
  319. return c == U'/' || (c == U'~' && treatHomeFolderAsRoot); // Root over all drives or home folder.
  320. }
  321. } else {
  322. if (pathSyntax == PathSyntax::Windows && cleanPath[length - 1] == U':') {
  323. // C:, D:, ...
  324. return true;
  325. } else {
  326. return false;
  327. }
  328. }
  329. }
  330. bool file_hasRoot(const ReadableString &path, bool treatHomeFolderAsRoot, PathSyntax pathSyntax) {
  331. int64_t firstSeparator = file_findFirstSeparator(path);
  332. if (firstSeparator == -1) {
  333. // If there is no separator, path has a root if it is a root.
  334. return file_isRoot(path, treatHomeFolderAsRoot, pathSyntax);
  335. } else if (firstSeparator == 0) {
  336. // Starting with a separator. Either an implicit drive on Windows or the whole system's root on Posix.
  337. return true;
  338. } else {
  339. // Has a root if the first entry before the first slash is a root.
  340. return file_isRoot(string_before(path, firstSeparator), treatHomeFolderAsRoot, pathSyntax);
  341. }
  342. }
  343. bool file_setCurrentPath(const ReadableString &path) {
  344. Buffer buffer;
  345. const NativeChar *nativePath = toNativeString(file_optimizePath(path, LOCAL_PATH_SYNTAX), buffer);
  346. #ifdef USE_MICROSOFT_WINDOWS
  347. return SetCurrentDirectoryW(nativePath) != 0;
  348. #else
  349. return chdir(nativePath) == 0;
  350. #endif
  351. }
  352. String file_getCurrentPath() {
  353. #ifdef USE_MICROSOFT_WINDOWS
  354. NativeChar resultBuffer[maxLength + 1] = {0};
  355. GetCurrentDirectoryW(maxLength, resultBuffer);
  356. return fromNativeString(resultBuffer);
  357. #else
  358. NativeChar resultBuffer[maxLength + 1] = {0};
  359. getcwd(resultBuffer, maxLength);
  360. return fromNativeString(resultBuffer);
  361. #endif
  362. }
  363. String file_followSymbolicLink(const ReadableString &path, bool mustExist) {
  364. String modifiedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX);
  365. Buffer buffer;
  366. const NativeChar *nativePath = toNativeString(modifiedPath, buffer);
  367. #ifdef USE_MICROSOFT_WINDOWS
  368. // TODO: Is there anything that can be used as a symbolic link on Windows?
  369. #else
  370. NativeChar resultBuffer[maxLength + 1] = {0};
  371. if (readlink(nativePath, resultBuffer, maxLength) != -1) {
  372. return fromNativeString(resultBuffer);
  373. }
  374. #endif
  375. if (mustExist) { throwError(U"The symbolic link ", path, " could not be found!\n"); }
  376. return U"?";
  377. }
  378. String file_getApplicationFolder(bool allowFallback) {
  379. #ifdef USE_MICROSOFT_WINDOWS
  380. NativeChar resultBuffer[maxLength + 1] = {0};
  381. GetModuleFileNameW(nullptr, resultBuffer, maxLength);
  382. return file_getRelativeParentFolder(fromNativeString(resultBuffer), LOCAL_PATH_SYNTAX);
  383. #else
  384. NativeChar resultBuffer[maxLength + 1] = {0};
  385. if (readlink("/proc/self/exe", resultBuffer, maxLength) != -1) {
  386. // Linux detected
  387. return file_getAbsoluteParentFolder(fromNativeString(resultBuffer));
  388. } else if (readlink("/proc/curproc/file", resultBuffer, maxLength) != -1) {
  389. // BSD detected
  390. return file_getAbsoluteParentFolder(fromNativeString(resultBuffer));
  391. } else if (readlink("/proc/self/path/a.out", resultBuffer, maxLength) != -1) {
  392. // Solaris detected
  393. return file_getAbsoluteParentFolder(fromNativeString(resultBuffer));
  394. } else if (allowFallback) {
  395. return file_getCurrentPath();
  396. } else {
  397. throwError("file_getApplicationFolder is not implemented for the current system!\n");
  398. return U"";
  399. }
  400. #endif
  401. }
  402. String file_combinePaths(const ReadableString &a, const ReadableString &b, PathSyntax pathSyntax) {
  403. ReadableString cleanA = string_removeOuterWhiteSpace(a);
  404. ReadableString cleanB = string_removeOuterWhiteSpace(b);
  405. int64_t lengthA = string_length(cleanA);
  406. int64_t lengthB = string_length(cleanB);
  407. if (file_hasRoot(b, true, pathSyntax)) {
  408. // Restarting from root or home folder.
  409. return cleanB;
  410. } else if (lengthA == 0) {
  411. // Ignoring initial relative path, so that relative paths are not suddenly moved to the root by a new separator.
  412. return cleanB;
  413. } else if (lengthB == 0) {
  414. // Ignoring initial relative path, so that relative paths are not suddenly moved to the root by a new separator.
  415. return cleanA;
  416. } else {
  417. if (file_isSeparator(a[lengthA - 1])) {
  418. // Already ending with a separator.
  419. return string_combine(cleanA, cleanB);
  420. } else {
  421. // Combine using a separator.
  422. return string_combine(cleanA, getPathSeparator(pathSyntax), cleanB);
  423. }
  424. }
  425. }
  426. // Returns path with the drive letter applied from currentPath if missing in path.
  427. // Used for converting drive relative paths into true absolute paths on MS-Windows.
  428. static String applyDriveLetter(const ReadableString &path, const ReadableString &currentPath) {
  429. // Convert implicit drive into a named drive.
  430. if (path[0] == U'\\') {
  431. int64_t colonIndex = string_findFirst(currentPath, U':', -1);
  432. if (colonIndex == -1) {
  433. return U"?";
  434. } else {
  435. // Get the drive letter from the current path.
  436. String drive = string_until(currentPath, colonIndex);
  437. return string_combine(drive, path);
  438. }
  439. } else {
  440. // Already absolute.
  441. return path;
  442. }
  443. }
  444. String file_getTheoreticalAbsolutePath(const ReadableString &path, const ReadableString &currentPath, PathSyntax pathSyntax) {
  445. // Home folders are absolute enough, because we don't want to lose the account ambiguity by mangling it into hardcoded usernames.
  446. if (file_hasRoot(path, true, pathSyntax)) {
  447. if (pathSyntax == PathSyntax::Windows) {
  448. // Make sure that no drive letter is missing.
  449. return applyDriveLetter(file_optimizePath(path, pathSyntax), currentPath);
  450. } else {
  451. // Already absolute.
  452. return file_optimizePath(path, pathSyntax);
  453. }
  454. } else {
  455. // Convert from relative path.
  456. return file_optimizePath(file_combinePaths(currentPath, path, pathSyntax), pathSyntax);
  457. }
  458. }
  459. String file_getAbsolutePath(const ReadableString &path) {
  460. return file_getTheoreticalAbsolutePath(path, file_getCurrentPath(), LOCAL_PATH_SYNTAX);
  461. }
  462. int64_t file_getFileSize(const ReadableString& filename) {
  463. int64_t result = -1;
  464. String modifiedFilename = file_optimizePath(filename, LOCAL_PATH_SYNTAX);
  465. Buffer buffer;
  466. const NativeChar *nativePath = toNativeString(modifiedFilename, buffer);
  467. #ifdef USE_MICROSOFT_WINDOWS
  468. LARGE_INTEGER fileSize;
  469. HANDLE fileHandle = CreateFileW(nativePath, 0, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
  470. if (fileHandle != INVALID_HANDLE_VALUE) {
  471. if (GetFileSizeEx(fileHandle, &fileSize)) {
  472. result = fileSize.QuadPart;
  473. }
  474. CloseHandle(fileHandle);
  475. }
  476. #else
  477. struct stat info;
  478. if (stat(nativePath, &info) == 0) {
  479. result = info.st_size;
  480. }
  481. #endif
  482. return result;
  483. }
  484. String& string_toStreamIndented(String& target, const EntryType& source, const ReadableString& indentation) {
  485. string_append(target, indentation);
  486. if (source == EntryType::NotFound) {
  487. string_append(target, U"not found");
  488. } else if (source == EntryType::File) {
  489. string_append(target, U"a file");
  490. } else if (source == EntryType::Folder) {
  491. string_append(target, U"a folder");
  492. } else if (source == EntryType::SymbolicLink) {
  493. string_append(target, U"a symbolic link");
  494. } else {
  495. string_append(target, U"unhandled");
  496. }
  497. return target;
  498. }
  499. EntryType file_getEntryType(const ReadableString &path) {
  500. EntryType result = EntryType::NotFound;
  501. String optimizedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX);
  502. Buffer buffer;
  503. const NativeChar *nativePath = toNativeString(optimizedPath, buffer);
  504. #ifdef USE_MICROSOFT_WINDOWS
  505. DWORD dwAttrib = GetFileAttributesW(nativePath);
  506. if (dwAttrib != INVALID_FILE_ATTRIBUTES) {
  507. if (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) {
  508. result = EntryType::Folder;
  509. } else {
  510. result = EntryType::File;
  511. }
  512. }
  513. #else
  514. struct stat info;
  515. int errorCode = stat(nativePath, &info);
  516. if (errorCode == 0) {
  517. if (S_ISDIR(info.st_mode)) {
  518. result = EntryType::Folder;
  519. } else if (S_ISREG(info.st_mode)) {
  520. result = EntryType::File;
  521. } else if (S_ISLNK(info.st_mode)) {
  522. result = EntryType::SymbolicLink;
  523. } else {
  524. result = EntryType::UnhandledType;
  525. }
  526. }
  527. #endif
  528. return result;
  529. }
  530. bool file_getFolderContent(const ReadableString& folderPath, std::function<void(const ReadableString& entryPath, const ReadableString& entryName, EntryType entryType)> action) {
  531. String optimizedPath = file_optimizePath(folderPath, LOCAL_PATH_SYNTAX);
  532. #ifdef USE_MICROSOFT_WINDOWS
  533. String pattern = file_combinePaths(optimizedPath, U"*.*", LOCAL_PATH_SYNTAX);
  534. Buffer buffer;
  535. const NativeChar *nativePattern = toNativeString(pattern, buffer);
  536. WIN32_FIND_DATAW findData;
  537. HANDLE findHandle = FindFirstFileW(nativePattern, &findData);
  538. if (findHandle == INVALID_HANDLE_VALUE) {
  539. return false;
  540. } else {
  541. while (true) {
  542. String entryName = fromNativeString(findData.cFileName);
  543. if (!string_match(entryName, U".") && !string_match(entryName, U"..")) {
  544. String entryPath = file_combinePaths(optimizedPath, entryName, LOCAL_PATH_SYNTAX);
  545. EntryType entryType = EntryType::UnhandledType;
  546. if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  547. entryType = EntryType::Folder;
  548. } else {
  549. entryType = EntryType::File;
  550. }
  551. action(entryPath, entryName, entryType);
  552. }
  553. if (!FindNextFileW(findHandle, &findData)) { break; }
  554. }
  555. FindClose(findHandle);
  556. }
  557. #else
  558. Buffer buffer;
  559. const NativeChar *nativePath = toNativeString(optimizedPath, buffer);
  560. DIR *directory = opendir(nativePath);
  561. if (directory == nullptr) {
  562. return false;
  563. } else {
  564. while (true) {
  565. dirent *entry = readdir(directory);
  566. if (entry != nullptr) {
  567. String entryName = fromNativeString(entry->d_name);
  568. if (!string_match(entryName, U".") && !string_match(entryName, U"..")) {
  569. String entryPath = file_combinePaths(optimizedPath, entryName, LOCAL_PATH_SYNTAX);
  570. EntryType entryType = file_getEntryType(entryPath);
  571. action(entryPath, entryName, entryType);
  572. }
  573. } else {
  574. break;
  575. }
  576. }
  577. }
  578. closedir(directory);
  579. #endif
  580. return true;
  581. }
  582. void file_getPathEntries(const ReadableString& path, std::function<void(ReadableString, int64_t, int64_t)> action) {
  583. int64_t sectionStart = 0;
  584. int64_t length = string_length(path);
  585. for (int64_t i = 0; i < string_length(path); i++) {
  586. DsrChar c = path[i];
  587. if (file_isSeparator(c)) {
  588. int64_t sectionEnd = i - 1; // Inclusive end
  589. ReadableString content = string_inclusiveRange(path, sectionStart, sectionEnd);
  590. if (string_length(content)) { action(content, sectionStart, sectionEnd); }
  591. sectionStart = i + 1;
  592. }
  593. }
  594. if (length > sectionStart) {
  595. int64_t sectionEnd = length - 1; // Inclusive end
  596. ReadableString content = string_inclusiveRange(path, sectionStart, sectionEnd);
  597. if (string_length(content)) { action(content, sectionStart, sectionEnd); }
  598. }
  599. }
  600. bool file_createFolder(const ReadableString &path) {
  601. bool result = false;
  602. String optimizedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX);
  603. Buffer buffer;
  604. const NativeChar *nativePath = toNativeString(optimizedPath, buffer);
  605. #ifdef USE_MICROSOFT_WINDOWS
  606. // Create folder with permissions inherited.
  607. result = (CreateDirectoryW(nativePath, nullptr) != 0);
  608. #else
  609. // Create folder with default permissions. Read, write and search for owner and group. Read and search for others.
  610. result = (mkdir(nativePath, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0);
  611. #endif
  612. return result;
  613. }
  614. bool file_removeEmptyFolder(const ReadableString& path) {
  615. bool result = false;
  616. String optimizedPath = file_optimizePath(path, LOCAL_PATH_SYNTAX);
  617. Buffer buffer;
  618. const NativeChar *nativePath = toNativeString(optimizedPath, buffer);
  619. // Remove the empty folder.
  620. #ifdef USE_MICROSOFT_WINDOWS
  621. result = (RemoveDirectoryW(nativePath) != 0);
  622. #else
  623. result = (rmdir(nativePath) == 0);
  624. #endif
  625. return result;
  626. }
  627. bool file_removeFile(const ReadableString& filename) {
  628. bool result = false;
  629. String optimizedPath = file_optimizePath(filename, LOCAL_PATH_SYNTAX);
  630. Buffer buffer;
  631. const NativeChar *nativePath = toNativeString(filename, buffer);
  632. // Remove the empty folder.
  633. #ifdef USE_MICROSOFT_WINDOWS
  634. result = (DeleteFileW(nativePath) != 0);
  635. #else
  636. result = (unlink(nativePath) == 0);
  637. #endif
  638. return result;
  639. }
  640. // DsrProcess is a reference counted pointer to DsrProcessImpl where the last retrieved status still remains for all to read.
  641. // Because aliasing with multiple users of the same pid would deplete the messages in advance.
  642. struct DsrProcessImpl {
  643. // Once the process has already terminated, process_getStatus will only return lastStatus.
  644. bool terminated = false;
  645. // We can assume that a newly created process is running until we are told that it terminated or crashed,
  646. // because DsrProcessImpl would not be created unless launching the application was successful.
  647. DsrProcessStatus lastStatus = DsrProcessStatus::Running;
  648. #ifdef USE_MICROSOFT_WINDOWS
  649. PROCESS_INFORMATION processInfo;
  650. DsrProcessImpl(const PROCESS_INFORMATION &processInfo)
  651. : processInfo(processInfo) {}
  652. ~DsrProcessImpl() {
  653. CloseHandle(processInfo.hProcess);
  654. CloseHandle(processInfo.hThread);
  655. }
  656. #else
  657. pid_t pid;
  658. DsrProcessImpl(pid_t pid) : pid(pid) {}
  659. #endif
  660. };
  661. DsrProcessStatus process_getStatus(const DsrProcess &process) {
  662. if (process.get() == nullptr) {
  663. return DsrProcessStatus::NotStarted;
  664. } else {
  665. if (!process->terminated) {
  666. #ifdef USE_MICROSOFT_WINDOWS
  667. DWORD status = WaitForSingleObject(process->processInfo.hProcess, 0);
  668. if (status == WAIT_OBJECT_0) {
  669. DWORD processResult;
  670. GetExitCodeProcess(process->processInfo.hProcess, &processResult);
  671. process->lastStatus = (processResult == 0) ? DsrProcessStatus::Completed : DsrProcessStatus::Crashed;
  672. process->terminated = true;
  673. }
  674. #else
  675. // When using WNOHANG, waitpid returns zero when the program is still running, and the child pid if it terminated.
  676. int status = 0;
  677. if (waitpid(process->pid, &status, WNOHANG) != 0) {
  678. if (WIFEXITED(status)) {
  679. process->lastStatus = DsrProcessStatus::Completed;
  680. process->terminated = true;
  681. } else if (WIFSIGNALED(status)) {
  682. process->lastStatus = DsrProcessStatus::Crashed;
  683. process->terminated = true;
  684. }
  685. }
  686. #endif
  687. }
  688. return process->lastStatus;
  689. }
  690. }
  691. DsrProcess process_execute(const ReadableString& programPath, List<String> arguments) {
  692. // Convert the program path into the native format.
  693. String optimizedPath = file_optimizePath(programPath, LOCAL_PATH_SYNTAX);
  694. Buffer pathBuffer;
  695. const NativeChar *nativePath = toNativeString(optimizedPath, pathBuffer);
  696. // Convert
  697. #ifdef USE_MICROSOFT_WINDOWS
  698. DsrChar separator = U' ';
  699. #else
  700. DsrChar separator = U'\0';
  701. #endif
  702. String flattenedArguments;
  703. string_append(flattenedArguments, programPath);
  704. string_appendChar(flattenedArguments, U'\0');
  705. for (int64_t a = 0; a < arguments.length(); a++) {
  706. string_append(flattenedArguments, arguments[a]);
  707. string_appendChar(flattenedArguments, U'\0');
  708. }
  709. Buffer argBuffer;
  710. const NativeChar *nativeArgs = toNativeString(flattenedArguments, argBuffer);
  711. #ifdef USE_MICROSOFT_WINDOWS
  712. STARTUPINFOW startInfo;
  713. PROCESS_INFORMATION processInfo;
  714. memset(&startInfo, 0, sizeof(STARTUPINFO));
  715. memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
  716. startInfo.cb = sizeof(STARTUPINFO);
  717. if (CreateProcessW(nullptr, (LPWSTR)nativeArgs, nullptr, nullptr, true, 0, nullptr, nullptr, &startInfo, &processInfo)) {
  718. return std::make_shared<DsrProcessImpl>(processInfo); // Success
  719. } else {
  720. return DsrProcess(); // Failure
  721. }
  722. #else
  723. int64_t codePoints = buffer_getSize(argBuffer) / sizeof(NativeChar);
  724. // Count arguments.
  725. int argc = arguments.length() + 1;
  726. // Allocate an array of pointers for each argument and a null terminator.
  727. const NativeChar *argv[argc + 1]; // TODO: Implement without VLA.
  728. // Fill the array with pointers to the native strings.
  729. int64_t startOffset = 0;
  730. int currentArg = 0;
  731. for (int64_t c = 0; c < codePoints && currentArg < argc; c++) {
  732. if (nativeArgs[c] == 0) {
  733. argv[currentArg] = &(nativeArgs[startOffset]);
  734. startOffset = c + 1;
  735. currentArg++;
  736. }
  737. }
  738. argv[currentArg] = nullptr;
  739. pid_t pid = 0;
  740. if (posix_spawn(&pid, nativePath, nullptr, nullptr, (char* const*)argv, environ) == 0) {
  741. return std::make_shared<DsrProcessImpl>(pid); // Success
  742. } else {
  743. return DsrProcess(); // Failure
  744. }
  745. #endif
  746. }
  747. }