No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

File.cpp 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. #include "il2cpp-config.h"
  2. #if IL2CPP_TARGET_WINDOWS
  3. #include "WindowsHelpers.h"
  4. #undef CopyFile
  5. #undef DeleteFile
  6. #undef MoveFile
  7. #undef ReplaceFile
  8. #undef GetFileAttributes
  9. #undef SetFileAttributes
  10. #undef CreatePipe
  11. #include "os/File.h"
  12. #include "utils/Expected.h"
  13. #include "utils/Il2CppError.h"
  14. #include "utils/StringUtils.h"
  15. #include "utils/PathUtils.h"
  16. #if IL2CPP_TARGET_WINRT
  17. #include "os/BrokeredFileSystem.h"
  18. #endif
  19. #include <stdint.h>
  20. static inline int FileWin32ErrorToErrorCode(DWORD win32ErrorCode)
  21. {
  22. return win32ErrorCode;
  23. }
  24. namespace il2cpp
  25. {
  26. namespace os
  27. {
  28. #if IL2CPP_TARGET_WINDOWS_DESKTOP
  29. utils::Expected<bool> File::Isatty(FileHandle* fileHandle)
  30. {
  31. DWORD mode;
  32. return GetConsoleMode((HANDLE)fileHandle, &mode) != 0;
  33. }
  34. #elif IL2CPP_TARGET_WINDOWS_GAMES
  35. utils::Expected<bool> File::Isatty(FileHandle* fileHandle)
  36. {
  37. return utils::Il2CppError(utils::NotSupported, "Console functions are not supported on Windows Games platforms.");
  38. }
  39. #endif
  40. #if IL2CPP_TARGET_WINDOWS_DESKTOP || IL2CPP_TARGET_WINDOWS_GAMES
  41. FileHandle* File::GetStdInput()
  42. {
  43. return (FileHandle*)GetStdHandle(STD_INPUT_HANDLE);
  44. }
  45. FileHandle* File::GetStdError()
  46. {
  47. return (FileHandle*)GetStdHandle(STD_ERROR_HANDLE);
  48. }
  49. FileHandle* File::GetStdOutput()
  50. {
  51. return (FileHandle*)GetStdHandle(STD_OUTPUT_HANDLE);
  52. }
  53. #endif // IL2CPP_TARGET_WINDOWS_DESKTOP || IL2CPP_TARGET_WINDOWS_GAMES
  54. utils::Expected<bool> File::CreatePipe(FileHandle** read_handle, FileHandle** write_handle)
  55. {
  56. int error;
  57. return CreatePipe(read_handle, write_handle, &error);
  58. }
  59. utils::Expected<bool> File::CreatePipe(FileHandle** read_handle, FileHandle** write_handle, int* error)
  60. {
  61. SECURITY_ATTRIBUTES attr;
  62. attr.nLength = sizeof(SECURITY_ATTRIBUTES);
  63. attr.bInheritHandle = TRUE;
  64. attr.lpSecurityDescriptor = NULL;
  65. bool ret = ::CreatePipe((PHANDLE)read_handle, (PHANDLE)write_handle, &attr, 0);
  66. if (ret == FALSE)
  67. {
  68. *error = GetLastError();
  69. /* FIXME: throw an exception? */
  70. return false;
  71. }
  72. return true;
  73. }
  74. #if !IL2CPP_TARGET_WINDOWS_GAMES
  75. UnityPalFileAttributes File::GetFileAttributes(const std::string& path, int *error)
  76. {
  77. const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str()));
  78. WIN32_FILE_ATTRIBUTE_DATA fileAttributes;
  79. BOOL result = ::GetFileAttributesExW((LPCWSTR)utf16Path.c_str(), GetFileExInfoStandard, &fileAttributes);
  80. if (result == FALSE)
  81. {
  82. auto lastError = ::GetLastError();
  83. #if IL2CPP_TARGET_WINRT
  84. if (lastError == ERROR_ACCESS_DENIED)
  85. return BrokeredFileSystem::GetFileAttributesW(utf16Path, error);
  86. #endif
  87. *error = FileWin32ErrorToErrorCode(lastError);
  88. return static_cast<UnityPalFileAttributes>(INVALID_FILE_ATTRIBUTES);
  89. }
  90. *error = kErrorCodeSuccess;
  91. return static_cast<UnityPalFileAttributes>(fileAttributes.dwFileAttributes);
  92. }
  93. #endif // !IL2CPP_TARGET_WINDOWS_GAMES
  94. bool File::SetFileAttributes(const std::string& path, UnityPalFileAttributes attributes, int* error)
  95. {
  96. const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str()));
  97. *error = kErrorCodeSuccess;
  98. if (::SetFileAttributesW((LPCWSTR)utf16Path.c_str(), attributes))
  99. return true;
  100. auto lastError = ::GetLastError();
  101. #if IL2CPP_TARGET_WINRT
  102. if (lastError == ERROR_ACCESS_DENIED)
  103. return BrokeredFileSystem::SetFileAttributesW(utf16Path, attributes, error);
  104. #endif
  105. *error = FileWin32ErrorToErrorCode(lastError);
  106. return false;
  107. }
  108. static inline int64_t HighAndLowToInt64(uint32_t high, uint32_t low)
  109. {
  110. return ((uint64_t)high << 32) + low;
  111. }
  112. static inline int64_t FileTimeToInt64(const FILETIME& fileTime)
  113. {
  114. return HighAndLowToInt64(fileTime.dwHighDateTime, fileTime.dwLowDateTime);
  115. }
  116. bool File::GetFileStat(const std::string& path, il2cpp::os::FileStat * stat, int* error)
  117. {
  118. *error = kErrorCodeSuccess;
  119. const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str()));
  120. WIN32_FILE_ATTRIBUTE_DATA data;
  121. if (!::GetFileAttributesExW((LPCWSTR)utf16Path.c_str(), GetFileExInfoStandard, &data))
  122. {
  123. auto lastError = ::GetLastError();
  124. #if IL2CPP_TARGET_WINRT
  125. if (lastError == ERROR_ACCESS_DENIED)
  126. return BrokeredFileSystem::GetFileStat(path, utf16Path, stat, error);
  127. #endif
  128. *error = FileWin32ErrorToErrorCode(lastError);
  129. return false;
  130. }
  131. stat->name = il2cpp::utils::PathUtils::Basename(path);
  132. stat->attributes = data.dwFileAttributes;
  133. stat->creation_time = FileTimeToInt64(data.ftCreationTime);
  134. stat->last_access_time = FileTimeToInt64(data.ftLastAccessTime);
  135. stat->last_write_time = FileTimeToInt64(data.ftLastWriteTime);
  136. stat->length = HighAndLowToInt64(data.nFileSizeHigh, data.nFileSizeLow);
  137. return true;
  138. }
  139. FileType File::GetFileType(FileHandle* handle)
  140. {
  141. int result = ::GetFileType((HANDLE)handle);
  142. /*if (result == FILE_TYPE_UNKNOWN)
  143. {
  144. *error = GetLastError();
  145. }*/
  146. return (FileType)result;
  147. }
  148. bool File::CopyFile(const std::string& src, const std::string& dest, bool overwrite, int* error)
  149. {
  150. const UTF16String utf16Src(utils::StringUtils::Utf8ToUtf16(src.c_str()));
  151. const UTF16String utf16Dest(utils::StringUtils::Utf8ToUtf16(dest.c_str()));
  152. *error = kErrorCodeSuccess;
  153. if (::CopyFileW((LPWSTR)utf16Src.c_str(), (LPWSTR)utf16Dest.c_str(), overwrite ? FALSE : TRUE))
  154. return true;
  155. auto lastError = ::GetLastError();
  156. #if IL2CPP_TARGET_WINRT
  157. if (lastError == ERROR_ACCESS_DENIED)
  158. return BrokeredFileSystem::CopyFileW(utf16Src, utf16Dest, overwrite, error);
  159. #endif
  160. *error = FileWin32ErrorToErrorCode(lastError);
  161. return false;
  162. }
  163. bool File::MoveFile(const std::string& src, const std::string& dest, int* error)
  164. {
  165. const UTF16String utf16Src(utils::StringUtils::Utf8ToUtf16(src.c_str()));
  166. const UTF16String utf16Dest(utils::StringUtils::Utf8ToUtf16(dest.c_str()));
  167. *error = kErrorCodeSuccess;
  168. if (::MoveFileExW((LPWSTR)utf16Src.c_str(), (LPWSTR)utf16Dest.c_str(), MOVEFILE_COPY_ALLOWED))
  169. return true;
  170. auto lastError = ::GetLastError();
  171. #if IL2CPP_TARGET_WINRT
  172. if (lastError == ERROR_ACCESS_DENIED)
  173. return BrokeredFileSystem::MoveFileW(utf16Src, utf16Dest, error);
  174. #endif
  175. *error = FileWin32ErrorToErrorCode(lastError);
  176. return false;
  177. }
  178. bool File::DeleteFile(const std::string& path, int *error)
  179. {
  180. *error = kErrorCodeSuccess;
  181. const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str()));
  182. if (::DeleteFileW((LPWSTR)utf16Path.c_str()))
  183. return true;
  184. auto lastError = ::GetLastError();
  185. #if IL2CPP_TARGET_WINRT
  186. if (lastError == ERROR_ACCESS_DENIED)
  187. {
  188. *error = BrokeredFileSystem::DeleteFileW(utf16Path);
  189. return *error == kErrorCodeSuccess;
  190. }
  191. #endif
  192. *error = FileWin32ErrorToErrorCode(lastError);
  193. return false;
  194. }
  195. bool File::ReplaceFile(const std::string& sourceFileName, const std::string& destinationFileName, const std::string& destinationBackupFileName, bool ignoreMetadataErrors, int* error)
  196. {
  197. const UTF16String utf16Src(utils::StringUtils::Utf8ToUtf16(sourceFileName.c_str()));
  198. const UTF16String utf16Dest(utils::StringUtils::Utf8ToUtf16(destinationFileName.c_str()));
  199. const UTF16String utf16Backup(utils::StringUtils::Utf8ToUtf16(destinationBackupFileName.c_str()));
  200. *error = kErrorCodeSuccess;
  201. DWORD flags = REPLACEFILE_WRITE_THROUGH;
  202. if (ignoreMetadataErrors)
  203. flags |= REPLACEFILE_IGNORE_MERGE_ERRORS;
  204. if (::ReplaceFileW((LPWSTR)utf16Dest.c_str(), (LPWSTR)utf16Src.c_str(), utf16Backup.empty() ? NULL : (LPWSTR)utf16Backup.c_str(), flags, NULL, NULL))
  205. return true;
  206. *error = FileWin32ErrorToErrorCode(::GetLastError());
  207. return false;
  208. }
  209. static inline int MonoToWindowsOpenMode(int monoOpenMode)
  210. {
  211. switch (monoOpenMode)
  212. {
  213. case kFileModeCreateNew:
  214. return CREATE_NEW;
  215. case kFileModeCreate:
  216. return CREATE_ALWAYS;
  217. case kFileModeOpen:
  218. return OPEN_EXISTING;
  219. case kFileModeOpenOrCreate:
  220. case kFileModeAppend:
  221. return OPEN_ALWAYS;
  222. case kFileModeTruncate:
  223. return TRUNCATE_EXISTING;
  224. default:
  225. Assert(false && "Unknown mono open mode");
  226. IL2CPP_UNREACHABLE;
  227. }
  228. }
  229. static inline int MonoToWindowsAccessMode(int monoAccessMode)
  230. {
  231. switch (monoAccessMode)
  232. {
  233. case kFileAccessRead:
  234. return GENERIC_READ;
  235. case kFileAccessWrite:
  236. return GENERIC_WRITE;
  237. case kFileAccessExecute:
  238. return GENERIC_EXECUTE;
  239. case kFileAccessReadWrite:
  240. return GENERIC_READ | GENERIC_WRITE;
  241. case kFileAccessReadWriteExecute:
  242. return GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE;
  243. default:
  244. return 0;
  245. }
  246. }
  247. static inline DWORD MonoOptionsToWindowsFlagsAndAttributes(const std::string& path, int options)
  248. {
  249. DWORD flagsAndAttributes;
  250. if (options != 0)
  251. {
  252. if (options & kFileOptionsEncrypted)
  253. flagsAndAttributes = FILE_ATTRIBUTE_ENCRYPTED;
  254. else
  255. flagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
  256. if (options & kFileOptionsDeleteOnClose)
  257. flagsAndAttributes |= FILE_FLAG_DELETE_ON_CLOSE;
  258. if (options & kFileOptionsSequentialScan)
  259. flagsAndAttributes |= FILE_FLAG_SEQUENTIAL_SCAN;
  260. if (options & kFileOptionsRandomAccess)
  261. flagsAndAttributes |= FILE_FLAG_RANDOM_ACCESS;
  262. if (options & kFileOptionsWriteThrough)
  263. flagsAndAttributes |= FILE_FLAG_WRITE_THROUGH;
  264. }
  265. else
  266. {
  267. flagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
  268. }
  269. int error;
  270. UnityPalFileAttributes currentAttributes = File::GetFileAttributes(path, &error);
  271. if (currentAttributes != INVALID_FILE_ATTRIBUTES && (currentAttributes & FILE_ATTRIBUTE_DIRECTORY))
  272. flagsAndAttributes |= FILE_FLAG_BACKUP_SEMANTICS; // Required to open a directory
  273. return flagsAndAttributes;
  274. }
  275. FileHandle* File::Open(const std::string& path, int openMode, int accessMode, int shareMode, int options, int *error)
  276. {
  277. const UTF16String utf16Path(utils::StringUtils::Utf8ToUtf16(path.c_str()));
  278. openMode = MonoToWindowsOpenMode(openMode);
  279. accessMode = MonoToWindowsAccessMode(accessMode);
  280. DWORD flagsAndAttributes = MonoOptionsToWindowsFlagsAndAttributes(path, options);
  281. HANDLE handle = ::CreateFileW((LPCWSTR)utf16Path.c_str(), accessMode, shareMode, NULL, openMode, flagsAndAttributes, NULL);
  282. if (INVALID_HANDLE_VALUE == handle)
  283. {
  284. auto lastError = ::GetLastError();
  285. #if IL2CPP_TARGET_WINRT
  286. if (lastError == ERROR_ACCESS_DENIED)
  287. return BrokeredFileSystem::Open(utf16Path, accessMode, shareMode, openMode, flagsAndAttributes, error);
  288. #endif
  289. *error = FileWin32ErrorToErrorCode(lastError);
  290. return (FileHandle*)INVALID_HANDLE_VALUE;
  291. }
  292. *error = kErrorCodeSuccess;
  293. return (FileHandle*)handle;
  294. }
  295. bool File::Close(FileHandle* handle, int *error)
  296. {
  297. *error = kErrorCodeSuccess;
  298. if (CloseHandle((HANDLE)handle))
  299. return true;
  300. *error = FileWin32ErrorToErrorCode(::GetLastError());
  301. return false;
  302. }
  303. bool File::SetFileTime(FileHandle* handle, int64_t creation_time, int64_t last_access_time, int64_t last_write_time, int* error)
  304. {
  305. FILE_BASIC_INFO fileInfo;
  306. fileInfo.CreationTime.QuadPart = creation_time;
  307. fileInfo.LastAccessTime.QuadPart = last_access_time;
  308. fileInfo.LastWriteTime.QuadPart = last_write_time;
  309. fileInfo.ChangeTime.QuadPart = 0; // 0 means don't change anything
  310. fileInfo.FileAttributes = 0; // 0 means don't change anything
  311. if (SetFileInformationByHandle(handle, FileBasicInfo, &fileInfo, sizeof(FILE_BASIC_INFO)) == FALSE)
  312. {
  313. *error = GetLastError();
  314. return false;
  315. }
  316. *error = kErrorCodeSuccess;
  317. return true;
  318. }
  319. int64_t File::GetLength(FileHandle* handle, int *error)
  320. {
  321. *error = kErrorCodeSuccess;
  322. LARGE_INTEGER size;
  323. if (!::GetFileSizeEx((HANDLE)handle, &size))
  324. {
  325. *error = FileWin32ErrorToErrorCode(::GetLastError());
  326. return 0;
  327. }
  328. return size.QuadPart;
  329. }
  330. #if !IL2CPP_USE_GENERIC_FILE
  331. bool File::Truncate(FileHandle* handle, int *error)
  332. {
  333. *error = kErrorCodeSuccess;
  334. if (!::SetEndOfFile((HANDLE)handle))
  335. {
  336. *error = FileWin32ErrorToErrorCode(::GetLastError());
  337. return false;
  338. }
  339. return true;
  340. }
  341. #endif // IL2CPP_USE_GENERIC_FILE
  342. bool File::SetLength(FileHandle* handle, int64_t length, int *error)
  343. {
  344. *error = kErrorCodeSuccess;
  345. LARGE_INTEGER zeroOffset = { 0 };
  346. LARGE_INTEGER requestedOffset = { 0 };
  347. requestedOffset.QuadPart = length;
  348. LARGE_INTEGER initialPosition = { 0 };
  349. // set position to 0 from current to retrieve current position
  350. if (!::SetFilePointerEx((HANDLE)handle, zeroOffset, &initialPosition, FILE_CURRENT))
  351. {
  352. *error = FileWin32ErrorToErrorCode(::GetLastError());
  353. return false;
  354. }
  355. // seek to requested length
  356. if (!::SetFilePointerEx((HANDLE)handle, requestedOffset, NULL, FILE_BEGIN))
  357. {
  358. *error = FileWin32ErrorToErrorCode(::GetLastError());
  359. return false;
  360. }
  361. // set requested length
  362. if (!::SetEndOfFile((HANDLE)handle))
  363. {
  364. *error = FileWin32ErrorToErrorCode(::GetLastError());
  365. return false;
  366. }
  367. // restore original position
  368. if (!::SetFilePointerEx((HANDLE)handle, initialPosition, NULL, FILE_BEGIN))
  369. {
  370. *error = FileWin32ErrorToErrorCode(::GetLastError());
  371. return false;
  372. }
  373. return true;
  374. }
  375. int64_t File::Seek(FileHandle* handle, int64_t offset, int origin, int *error)
  376. {
  377. *error = kErrorCodeSuccess;
  378. LARGE_INTEGER distance;
  379. distance.QuadPart = offset;
  380. LARGE_INTEGER position = { 0 };
  381. if (!::SetFilePointerEx((HANDLE)handle, distance, &position, origin))
  382. *error = FileWin32ErrorToErrorCode(::GetLastError());
  383. return position.QuadPart;
  384. }
  385. int File::Read(FileHandle* handle, char *dest, int count, int *error)
  386. {
  387. *error = kErrorCodeSuccess;
  388. DWORD bytesRead = 0;
  389. if (!::ReadFile(handle, dest, count, &bytesRead, NULL))
  390. *error = FileWin32ErrorToErrorCode(::GetLastError());
  391. return bytesRead;
  392. }
  393. int32_t File::Write(FileHandle* handle, const char* buffer, int count, int *error)
  394. {
  395. int32_t written;
  396. BOOL success = WriteFile((HANDLE)handle, buffer, count, (LPDWORD)&written, NULL);
  397. if (!success)
  398. {
  399. DWORD originalError = GetLastError();
  400. if (originalError == ERROR_INVALID_PARAMETER)
  401. {
  402. // Maybe this is an async file write, so try with those parameters.
  403. OVERLAPPED overlapped = {0};
  404. success = WriteFile((HANDLE)handle, buffer, count, NULL, &overlapped);
  405. if (success != 0 || GetLastError() == ERROR_IO_PENDING)
  406. {
  407. success = TRUE;
  408. // The async write succeeded. Now get the number of bytes written.
  409. #if IL2CPP_TARGET_WINDOWS_DESKTOP
  410. if (GetOverlappedResult((HANDLE)handle, &overlapped, (LPDWORD)&written, TRUE) == 0)
  411. #else
  412. if (GetOverlappedResultEx((HANDLE)handle, &overlapped, (LPDWORD)&written, INFINITE, FALSE) == 0)
  413. #endif
  414. {
  415. // Oops, we could not get the number of bytes writen, so return an error.
  416. *error = GetLastError();
  417. return -1;
  418. }
  419. }
  420. }
  421. if (!success)
  422. {
  423. *error = originalError;
  424. return -1;
  425. }
  426. }
  427. return written;
  428. }
  429. bool File::Flush(FileHandle* handle, int* error)
  430. {
  431. *error = kErrorCodeSuccess;
  432. if (FlushFileBuffers((HANDLE)handle))
  433. return true;
  434. *error = FileWin32ErrorToErrorCode(::GetLastError());
  435. return false;
  436. }
  437. void File::Lock(FileHandle* handle, int64_t position, int64_t length, int* error)
  438. {
  439. *error = kErrorCodeSuccess;
  440. OVERLAPPED overlapped;
  441. ZeroMemory(&overlapped, sizeof(overlapped));
  442. overlapped.Offset = position & 0xFFFFFFFF;
  443. overlapped.OffsetHigh = position >> 32;
  444. LARGE_INTEGER lengthUnion;
  445. lengthUnion.QuadPart = length;
  446. if (!::LockFileEx((HANDLE)handle, LOCKFILE_FAIL_IMMEDIATELY, 0, lengthUnion.LowPart, lengthUnion.HighPart, &overlapped))
  447. *error = FileWin32ErrorToErrorCode(::GetLastError());
  448. }
  449. void File::Unlock(FileHandle* handle, int64_t position, int64_t length, int* error)
  450. {
  451. *error = kErrorCodeSuccess;
  452. OVERLAPPED overlapped;
  453. ZeroMemory(&overlapped, sizeof(overlapped));
  454. overlapped.Offset = position & 0xFFFFFFFF;
  455. overlapped.OffsetHigh = position >> 32;
  456. LARGE_INTEGER lengthUnion;
  457. lengthUnion.QuadPart = length;
  458. if (!::UnlockFileEx((HANDLE)handle, 0, lengthUnion.LowPart, lengthUnion.HighPart, &overlapped))
  459. *error = FileWin32ErrorToErrorCode(::GetLastError());
  460. }
  461. utils::Expected<bool> File::DuplicateHandle(FileHandle* source_process_handle, FileHandle* source_handle, FileHandle* target_process_handle,
  462. FileHandle** target_handle, int access, int inherit, int options, int* error)
  463. {
  464. /* This is only used on Windows */
  465. //MONO_PREPARE_BLOCKING;
  466. BOOL ret = ::DuplicateHandle((HANDLE)source_process_handle, (HANDLE)source_handle, (HANDLE)target_process_handle, (LPHANDLE)target_handle, access, inherit, options);
  467. //MONO_FINISH_BLOCKING;
  468. if (ret == FALSE)
  469. {
  470. *error = GetLastError();
  471. /* FIXME: throw an exception? */
  472. return false;
  473. }
  474. return true;
  475. }
  476. static bool ends_with(const std::string& value, const std::string& ending)
  477. {
  478. if (value.length() >= ending.length())
  479. return value.compare(value.length() - ending.length(), ending.length(), ending) == 0;
  480. return false;
  481. }
  482. utils::Expected<bool> File::IsExecutable(const std::string& path)
  483. {
  484. return ends_with(path, "exe");
  485. }
  486. bool File::Cancel(FileHandle* handle)
  487. {
  488. return CancelIoEx((HANDLE)handle, NULL);
  489. }
  490. }
  491. }
  492. #endif