fileAPI.cpp 30 KB

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