LocalFileIO.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzFramework/IO/LocalFileIO.h>
  9. #include <sys/stat.h>
  10. #include <AzCore/IO/SystemFile.h>
  11. #include <AzCore/IO/IOUtils.h>
  12. #include <AzCore/IO/Path/Path.h>
  13. #include <AzCore/Casting/numeric_cast.h>
  14. #include <AzCore/Casting/lossy_cast.h>
  15. #include <AzCore/std/containers/fixed_unordered_set.h>
  16. #include <AzCore/std/functional.h>
  17. #include <AzCore/std/string/conversions.h>
  18. #include <AzCore/std/string/string_view.h>
  19. #include <AzCore/StringFunc/StringFunc.h>
  20. #include <AzCore/Utils/Utils.h>
  21. #include <cctype>
  22. namespace AZ
  23. {
  24. namespace IO
  25. {
  26. const HandleType LocalHandleStartValue = 1000000; //start the local file io handles at 1 million
  27. LocalFileIO::LocalFileIO()
  28. {
  29. m_nextHandle = LocalHandleStartValue;
  30. }
  31. LocalFileIO::~LocalFileIO()
  32. {
  33. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
  34. while (!m_openFiles.empty())
  35. {
  36. const auto& handlePair = *m_openFiles.begin();
  37. Close(handlePair.first);
  38. }
  39. AZ_Assert(m_openFiles.empty(), "Trying to shutdown filing system with files still open");
  40. }
  41. Result LocalFileIO::Open(const char* filePath, OpenMode mode, HandleType& fileHandle)
  42. {
  43. char resolvedPath[AZ_MAX_PATH_LEN];
  44. ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
  45. AZ::IO::UpdateOpenModeForReading(mode);
  46. // Generate open modes for SystemFile
  47. int systemFileMode = TranslateOpenModeToSystemFileMode(resolvedPath, mode);
  48. bool write = AnyFlag(mode & (OpenMode::ModeWrite | OpenMode::ModeUpdate | OpenMode::ModeAppend));
  49. if (write)
  50. {
  51. CheckInvalidWrite(resolvedPath);
  52. }
  53. {
  54. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
  55. fileHandle = GetNextHandle();
  56. // Construct a new SystemFile in the map (SystemFiles don't copy/move very well).
  57. auto newPair = m_openFiles.emplace(fileHandle);
  58. // Check for successful insert
  59. if (!newPair.second)
  60. {
  61. fileHandle = InvalidHandle;
  62. return ResultCode::Error;
  63. }
  64. // Attempt to open the newly created file
  65. if (newPair.first->second.Open(resolvedPath, systemFileMode, 0))
  66. {
  67. return ResultCode::Success;
  68. }
  69. else
  70. {
  71. // Remove file, it's not actually open
  72. m_openFiles.erase(fileHandle);
  73. // On failure, ensure the fileHandle returned is invalid
  74. // some code does not check return but handle value (equivalent to checking for nullptr FILE*)
  75. fileHandle = InvalidHandle;
  76. return ResultCode::Error;
  77. }
  78. }
  79. }
  80. Result LocalFileIO::Close(HandleType fileHandle)
  81. {
  82. auto filePointer = GetFilePointerFromHandle(fileHandle);
  83. if (!filePointer)
  84. {
  85. return ResultCode::Error_HandleInvalid;
  86. }
  87. filePointer->Close();
  88. {
  89. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
  90. m_openFiles.erase(fileHandle);
  91. }
  92. return ResultCode::Success;
  93. }
  94. Result LocalFileIO::Read(HandleType fileHandle, void* buffer, AZ::u64 size, bool failOnFewerThanSizeBytesRead, AZ::u64* bytesRead)
  95. {
  96. auto filePointer = GetFilePointerFromHandle(fileHandle);
  97. if (!filePointer)
  98. {
  99. return ResultCode::Error_HandleInvalid;
  100. }
  101. SystemFile::SizeType readResult = filePointer->Read(size, buffer);
  102. if (bytesRead)
  103. {
  104. *bytesRead = aznumeric_cast<AZ::u64>(readResult);
  105. }
  106. if (static_cast<AZ::u64>(readResult) != size)
  107. {
  108. if (failOnFewerThanSizeBytesRead)
  109. {
  110. return ResultCode::Error;
  111. }
  112. // Reading less than desired is valid if ferror is not set
  113. AZ_Assert(Eof(fileHandle), "End of file unexpectedly reached before all data was read");
  114. }
  115. return ResultCode::Success;
  116. }
  117. Result LocalFileIO::Write(HandleType fileHandle, const void* buffer, AZ::u64 size, AZ::u64* bytesWritten)
  118. {
  119. auto filePointer = GetFilePointerFromHandle(fileHandle);
  120. if (!filePointer)
  121. {
  122. return ResultCode::Error_HandleInvalid;
  123. }
  124. SystemFile::SizeType writeResult = filePointer->Write(buffer, size);
  125. if (bytesWritten)
  126. {
  127. *bytesWritten = writeResult;
  128. }
  129. if (static_cast<AZ::u64>(writeResult) != size)
  130. {
  131. return ResultCode::Error;
  132. }
  133. return ResultCode::Success;
  134. }
  135. Result LocalFileIO::Flush(HandleType fileHandle)
  136. {
  137. auto filePointer = GetFilePointerFromHandle(fileHandle);
  138. if (!filePointer)
  139. {
  140. return ResultCode::Error_HandleInvalid;
  141. }
  142. filePointer->Flush();
  143. return ResultCode::Success;
  144. }
  145. Result LocalFileIO::Tell(HandleType fileHandle, AZ::u64& offset)
  146. {
  147. auto filePointer = GetFilePointerFromHandle(fileHandle);
  148. if (!filePointer)
  149. {
  150. return ResultCode::Error_HandleInvalid;
  151. }
  152. SystemFile::SizeType resultValue = filePointer->Tell();
  153. if (resultValue == -1)
  154. {
  155. return ResultCode::Error;
  156. }
  157. offset = static_cast<AZ::u64>(resultValue);
  158. return ResultCode::Success;
  159. }
  160. Result LocalFileIO::Seek(HandleType fileHandle, AZ::s64 offset, SeekType type)
  161. {
  162. auto filePointer = GetFilePointerFromHandle(fileHandle);
  163. if (!filePointer)
  164. {
  165. return ResultCode::Error_HandleInvalid;
  166. }
  167. SystemFile::SeekMode mode = SystemFile::SF_SEEK_BEGIN;
  168. if (type == SeekType::SeekFromCurrent)
  169. {
  170. mode = SystemFile::SF_SEEK_CURRENT;
  171. }
  172. else if (type == SeekType::SeekFromEnd)
  173. {
  174. mode = SystemFile::SF_SEEK_END;
  175. }
  176. filePointer->Seek(offset, mode);
  177. return ResultCode::Success;
  178. }
  179. Result LocalFileIO::Size(HandleType fileHandle, AZ::u64& size)
  180. {
  181. auto filePointer = GetFilePointerFromHandle(fileHandle);
  182. if (!filePointer)
  183. {
  184. return ResultCode::Error_HandleInvalid;
  185. }
  186. size = filePointer->Length();
  187. return ResultCode::Success;
  188. }
  189. Result LocalFileIO::Size(const char* filePath, AZ::u64& size)
  190. {
  191. char resolvedPath[AZ_MAX_PATH_LEN];
  192. ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
  193. size = SystemFile::Length(resolvedPath);
  194. if (!size)
  195. {
  196. return SystemFile::Exists(resolvedPath) ? ResultCode::Success : ResultCode::Error;
  197. }
  198. return ResultCode::Success;
  199. }
  200. bool LocalFileIO::IsReadOnly(const char* filePath)
  201. {
  202. char resolvedPath[AZ_MAX_PATH_LEN];
  203. ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
  204. return !SystemFile::IsWritable(resolvedPath);
  205. }
  206. bool LocalFileIO::Eof(HandleType fileHandle)
  207. {
  208. auto filePointer = GetFilePointerFromHandle(fileHandle);
  209. if (!filePointer)
  210. {
  211. return false;
  212. }
  213. return filePointer->Eof();
  214. }
  215. AZ::u64 LocalFileIO::ModificationTime(const char* filePath)
  216. {
  217. char resolvedPath[AZ_MAX_PATH_LEN];
  218. ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
  219. return SystemFile::ModificationTime(resolvedPath);
  220. }
  221. AZ::u64 LocalFileIO::ModificationTime(HandleType fileHandle)
  222. {
  223. auto filePointer = GetFilePointerFromHandle(fileHandle);
  224. if (!filePointer)
  225. {
  226. return 0;
  227. }
  228. return filePointer->ModificationTime();
  229. }
  230. bool LocalFileIO::Exists(const char* filePath)
  231. {
  232. char resolvedPath[AZ_MAX_PATH_LEN];
  233. ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
  234. return SystemFile::Exists(resolvedPath);
  235. }
  236. bool LocalFileIO::IsDirectory(const char* filePath)
  237. {
  238. char resolvedPath[AZ_MAX_PATH_LEN];
  239. ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
  240. return SystemFile::IsDirectory(resolvedPath);
  241. }
  242. void LocalFileIO::CheckInvalidWrite([[maybe_unused]] const char* path)
  243. {
  244. #if defined(AZ_ENABLE_TRACING)
  245. const char* assetAliasPath = GetAlias("@products@");
  246. if (path && assetAliasPath)
  247. {
  248. const AZ::IO::PathView pathView(path);
  249. if (pathView.IsRelativeTo(assetAliasPath))
  250. {
  251. AZ_Error("FileIO", false, "You may not alter data inside the asset cache. Please check the call stack and consider writing into the source asset folder instead.\n"
  252. "Attempted write location: %s", path);
  253. }
  254. }
  255. #endif
  256. }
  257. Result LocalFileIO::Remove(const char* filePath)
  258. {
  259. char resolvedPath[AZ_MAX_PATH_LEN];
  260. ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
  261. CheckInvalidWrite(resolvedPath);
  262. if (IsDirectory(resolvedPath))
  263. {
  264. return ResultCode::Error;
  265. }
  266. return SystemFile::Delete(resolvedPath) ? ResultCode::Success : ResultCode::Error;
  267. }
  268. Result LocalFileIO::Rename(const char* originalFilePath, const char* newFilePath)
  269. {
  270. char resolvedOldPath[AZ_MAX_PATH_LEN];
  271. char resolvedNewPath[AZ_MAX_PATH_LEN];
  272. ResolvePath(originalFilePath, resolvedOldPath, AZ_MAX_PATH_LEN);
  273. ResolvePath(newFilePath, resolvedNewPath, AZ_MAX_PATH_LEN);
  274. CheckInvalidWrite(resolvedNewPath);
  275. if (!SystemFile::Rename(resolvedOldPath, resolvedNewPath))
  276. {
  277. return ResultCode::Error;
  278. }
  279. return ResultCode::Success;
  280. }
  281. SystemFile* LocalFileIO::GetFilePointerFromHandle(HandleType fileHandle)
  282. {
  283. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
  284. auto openFileIterator = m_openFiles.find(fileHandle);
  285. if (openFileIterator != m_openFiles.end())
  286. {
  287. SystemFile& file = openFileIterator->second;
  288. return &file;
  289. }
  290. return nullptr;
  291. }
  292. HandleType LocalFileIO::GetNextHandle()
  293. {
  294. return m_nextHandle++;
  295. }
  296. static ResultCode DestroyPath_Recurse(LocalFileIO* fileIO, const char* filePath)
  297. {
  298. // this is a deltree command. It needs to eat everything. Even files.
  299. ResultCode res = ResultCode::Success;
  300. fileIO->FindFiles(filePath, "*", [&](const char* iterPath) -> bool
  301. {
  302. // depth first recurse into directories!
  303. // note: findFiles returns full path names.
  304. if (fileIO->IsDirectory(iterPath))
  305. {
  306. // recurse.
  307. if (DestroyPath_Recurse(fileIO, iterPath) != ResultCode::Success)
  308. {
  309. res = ResultCode::Error;
  310. return false; // stop the find files.
  311. }
  312. }
  313. else
  314. {
  315. // if its a file, remove it
  316. if (fileIO->Remove(iterPath) != ResultCode::Success)
  317. {
  318. res = ResultCode::Error;
  319. return false; // stop the find files.
  320. }
  321. }
  322. return true; // continue the find files
  323. });
  324. if (res != ResultCode::Success)
  325. {
  326. return res;
  327. }
  328. // now that we've finished recursing, rmdir on the folder itself
  329. return AZ::IO::SystemFile::DeleteDir(filePath) ? ResultCode::Success : ResultCode::Error;
  330. }
  331. Result LocalFileIO::DestroyPath(const char* filePath)
  332. {
  333. char resolvedPath[AZ_MAX_PATH_LEN];
  334. ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
  335. bool pathExists = Exists(resolvedPath);
  336. if (!pathExists)
  337. {
  338. return ResultCode::Success;
  339. }
  340. if (pathExists && (!IsDirectory(resolvedPath)))
  341. {
  342. return ResultCode::Error;
  343. }
  344. return DestroyPath_Recurse(this, resolvedPath);
  345. }
  346. static void ToUnixSlashes(char* path, AZ::u64 size)
  347. {
  348. auto PrevAndCurrentCharIsPathSeparator = [](const char prev, const char next) -> bool
  349. {
  350. constexpr AZStd::string_view pathSeparator = "/";
  351. const bool prevIsPathSeparator = pathSeparator.find_first_of(prev) != AZStd::string_view::npos;
  352. const bool nextIsPathSeparator = pathSeparator.find_first_of(next) != AZStd::string_view::npos;
  353. return prevIsPathSeparator && nextIsPathSeparator;
  354. };
  355. size_t copyOffset = 0;
  356. for (size_t i = 0; i < size && path[i] != '\0'; i++)
  357. {
  358. if (path[i] == '\\')
  359. {
  360. path[i] = '/';
  361. }
  362. // Replace runs of path separators with one path separator
  363. #if AZ_TRAIT_USE_WINDOWS_FILE_API
  364. // Network file systems for Windows based APIs start with consecutive path separators
  365. // so skip over the first character in this case
  366. constexpr size_t duplicateSeparatorStartOffet = 1;
  367. #else
  368. constexpr size_t duplicateSeparatorStartOffet = 0;
  369. #endif
  370. if (i > duplicateSeparatorStartOffet)
  371. {
  372. if (PrevAndCurrentCharIsPathSeparator(path[i - 1], path[i]))
  373. {
  374. continue;
  375. }
  376. }
  377. // If the copy offset is different than the iteration index of the path, then copy over it over
  378. if (copyOffset != i)
  379. {
  380. path[copyOffset] = path[i];
  381. }
  382. ++copyOffset;
  383. }
  384. // Null-terminate the path again in case duplicate slashes were collapsed
  385. path[copyOffset] = '\0';
  386. }
  387. bool LocalFileIO::ResolvePath(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const
  388. {
  389. if (resolvedPath == nullptr || resolvedPathSize == 0)
  390. {
  391. return false;
  392. }
  393. resolvedPath[0] = '\0';
  394. if (path == nullptr)
  395. {
  396. return false;
  397. }
  398. if (AZ::IO::PathView(path).HasRootPath())
  399. {
  400. size_t pathLen = strlen(path);
  401. if (pathLen + 1 < resolvedPathSize)
  402. {
  403. azstrncpy(resolvedPath, resolvedPathSize, path, pathLen + 1);
  404. //see if the absolute path matches the resolved value of @products@, if it does lowercase the relative part
  405. LowerIfBeginsWith(resolvedPath, resolvedPathSize, GetAlias("@products@"));
  406. ToUnixSlashes(resolvedPath, resolvedPathSize);
  407. return true;
  408. }
  409. else
  410. {
  411. return false;
  412. }
  413. }
  414. constexpr AZStd::string_view productAssetAlias = "@products@";
  415. // Add plus one for the path separator: <alias>/<path>
  416. constexpr size_t MaxPathSizeWithProductAssetAlias = AZ::IO::MaxPathLength + productAssetAlias.size() + 1;
  417. using RootedPathString = AZStd::fixed_string<MaxPathSizeWithProductAssetAlias>;
  418. RootedPathString rootedPathBuffer;
  419. const char* rootedPath = path;
  420. // if the path does not begin with an alias, then it is assumed to begin with @products@
  421. if (path[0] != '@')
  422. {
  423. if (GetAlias("@products@"))
  424. {
  425. if (const size_t requiredSize = productAssetAlias.size() + strlen(path) + 1;
  426. requiredSize > rootedPathBuffer.capacity())
  427. {
  428. AZ_Error("FileIO", false, "Prepending the %.*s alias to the input path results in a path longer than the"
  429. " AZ::IO::MaxPathLength + the alias size of %zu. The size of the potential failed path is %zu",
  430. AZ_STRING_ARG(productAssetAlias), rootedPathBuffer.capacity(), requiredSize)
  431. }
  432. else
  433. {
  434. rootedPathBuffer = RootedPathString::format("%.*s/%s", AZ_STRING_ARG(productAssetAlias), path);
  435. }
  436. }
  437. else
  438. {
  439. if (ConvertToAbsolutePath(path, rootedPathBuffer.data(), rootedPathBuffer.capacity()))
  440. {
  441. // Recalculate the internal string length
  442. rootedPathBuffer.resize_no_construct(AZStd::char_traits<char>::length(rootedPathBuffer.data()));
  443. }
  444. }
  445. rootedPath = rootedPathBuffer.c_str();
  446. }
  447. if (ResolveAliases(rootedPath, resolvedPath, resolvedPathSize))
  448. {
  449. ToUnixSlashes(resolvedPath, resolvedPathSize);
  450. return true;
  451. }
  452. return false;
  453. }
  454. bool LocalFileIO::ResolvePath(AZ::IO::FixedMaxPath& resolvedPath, const AZ::IO::PathView& path) const
  455. {
  456. if (AZ::IO::FixedMaxPathString fixedPath{ path.Native() };
  457. ResolvePath(fixedPath.c_str(), resolvedPath.Native().data(), resolvedPath.Native().capacity()))
  458. {
  459. // Update the null-terminator offset
  460. resolvedPath.Native().resize_no_construct(AZStd::char_traits<char>::length(resolvedPath.Native().data()));
  461. return true;
  462. }
  463. return false;
  464. }
  465. void LocalFileIO::SetAlias(const char* key, const char* path)
  466. {
  467. char fullPath[AZ_MAX_PATH_LEN];
  468. ConvertToAbsolutePath(path, fullPath, AZ_MAX_PATH_LEN);
  469. m_aliases[key] = fullPath;
  470. }
  471. const char* LocalFileIO::GetAlias(const char* key) const
  472. {
  473. if (const auto it = m_aliases.find(key); it != m_aliases.end())
  474. {
  475. return it->second.c_str();
  476. }
  477. else if (const auto deprecatedIt = m_deprecatedAliases.find(key);
  478. deprecatedIt != m_deprecatedAliases.end())
  479. {
  480. AZ_Error("FileIO", false, R"(Alias "%s" is deprecated. Please use alias "%s" instead)",
  481. key, deprecatedIt->second.c_str());
  482. AZStd::string_view aliasValue = deprecatedIt->second;
  483. // Contains the list of aliases resolved so far
  484. // If max_size is hit, than an error is logged and nullptr is returned
  485. using VisitedAliasSet = AZStd::fixed_unordered_set<AZStd::string_view, 8, 8>;
  486. VisitedAliasSet visitedAliasSet;
  487. while (aliasValue.starts_with("@"))
  488. {
  489. if (visitedAliasSet.contains(aliasValue))
  490. {
  491. AZ_Error("FileIO", false, "Cycle found with for alias %.*s when trying to resolve deprecated alias %s",
  492. AZ_STRING_ARG(aliasValue), key);
  493. return nullptr;
  494. }
  495. if(visitedAliasSet.size() == visitedAliasSet.max_size())
  496. {
  497. AZ_Error("FileIO", false, "Unable to resolve path to deprecated alias %s within %zu steps",
  498. key, visitedAliasSet.max_size());
  499. return nullptr;
  500. }
  501. // Add the current alias value to the visited set
  502. visitedAliasSet.emplace(aliasValue);
  503. // Check if the alias value corresponds to another alias
  504. if (auto resolvedIter = m_aliases.find(aliasValue); resolvedIter != m_aliases.end())
  505. {
  506. aliasValue = resolvedIter->second;
  507. }
  508. else if (resolvedIter = m_deprecatedAliases.find(aliasValue);
  509. resolvedIter != m_deprecatedAliases.end())
  510. {
  511. aliasValue = resolvedIter->second;
  512. }
  513. else
  514. {
  515. return nullptr;
  516. }
  517. }
  518. return aliasValue.data();
  519. }
  520. return nullptr;
  521. }
  522. void LocalFileIO::ClearAlias(const char* key)
  523. {
  524. m_aliases.erase(key);
  525. }
  526. void LocalFileIO::SetDeprecatedAlias(AZStd::string_view oldAlias, AZStd::string_view newAlias)
  527. {
  528. m_deprecatedAliases[oldAlias] = newAlias;
  529. }
  530. AZStd::optional<AZ::u64> LocalFileIO::ConvertToAliasBuffer(char* outBuffer, AZ::u64 outBufferLength, AZStd::string_view inBuffer) const
  531. {
  532. size_t longestMatch = 0;
  533. size_t bufStringLength = inBuffer.size();
  534. AZStd::string_view longestAlias;
  535. for (const auto& [alias, resolvedAlias] : m_aliases)
  536. {
  537. // here we are making sure that the buffer being passed in has enough space to include the alias in it.
  538. // we are trying to find the LONGEST match, meaning of the following two examples, the second should 'win'
  539. // File: g:/O3DE/dev/files/morefiles/blah.xml
  540. // Alias1 links to 'g:/O3DE/dev/'
  541. // Alias2 links to 'g:/O3DE/dev/files/morefiles'
  542. // so returning Alias2 is preferred as it is more specific, even though alias1 includes it.
  543. // note that its not possible for this to be matched if the string is shorter than the length of the alias itself so we skip
  544. // strings that are shorter than the alias's mapped path without checking.
  545. if ((longestMatch == 0) || (resolvedAlias.size() > longestMatch) && (resolvedAlias.size() <= bufStringLength))
  546. {
  547. // Check if the input path is relative to the alias value
  548. if (AZ::IO::PathView(inBuffer).IsRelativeTo(AZ::IO::PathView(resolvedAlias)))
  549. {
  550. longestMatch = resolvedAlias.size();
  551. longestAlias = alias;
  552. }
  553. }
  554. }
  555. if (!longestAlias.empty())
  556. {
  557. // rearrange the buffer to have
  558. // [alias][old path]
  559. size_t aliasSize = longestAlias.size();
  560. size_t charsToAbsorb = longestMatch;
  561. size_t remainingData = bufStringLength - charsToAbsorb;
  562. size_t finalStringSize = aliasSize + remainingData;
  563. if (finalStringSize >= outBufferLength)
  564. {
  565. AZ_Error("FileIO", false, "Alias %.*s cannot fit in output buffer. The output buffer is too small (max len %lu, actual len %zu)",
  566. aznumeric_cast<int>(longestAlias.size()), longestAlias.data(), outBufferLength, finalStringSize);
  567. // avoid buffer overflow, return original untouched
  568. return AZStd::nullopt;
  569. }
  570. // move the remaining piece of the string:
  571. memmove(outBuffer + aliasSize, inBuffer.data() + charsToAbsorb, remainingData);
  572. // copy the alias
  573. memcpy(outBuffer, longestAlias.data(), aliasSize);
  574. /// add a null
  575. outBuffer[finalStringSize] = 0;
  576. return finalStringSize;
  577. }
  578. // If the input and output buffer are different, copy over the input buffer to the output buffer
  579. if (outBuffer != inBuffer.data())
  580. {
  581. if (inBuffer.size() >= outBufferLength)
  582. {
  583. AZ_Error("FileIO", false, R"(Path "%.*s" cannot fit in output buffer. The output buffer is too small (max len %llu, actual len %zu))",
  584. aznumeric_cast<int>(inBuffer.size()), inBuffer.data(), outBufferLength, inBuffer.size());
  585. return AZStd::nullopt;
  586. }
  587. size_t finalStringSize = inBuffer.copy(outBuffer, outBufferLength);
  588. outBuffer[finalStringSize] = 0;
  589. }
  590. return bufStringLength;
  591. }
  592. AZStd::optional<AZ::u64> LocalFileIO::ConvertToAlias(char* inOutBuffer, AZ::u64 bufferLength) const
  593. {
  594. return ConvertToAliasBuffer(inOutBuffer, bufferLength, inOutBuffer);
  595. }
  596. bool LocalFileIO::ConvertToAlias(AZ::IO::FixedMaxPath& convertedPath, const AZ::IO::PathView& pathView) const
  597. {
  598. AZStd::optional<AZ::u64> convertedPathSize =
  599. ConvertToAliasBuffer(convertedPath.Native().data(), convertedPath.Native().capacity(), pathView.Native());
  600. if (convertedPathSize)
  601. {
  602. // Force update of AZStd::fixed_string m_size member to set correct null-terminator offset
  603. convertedPath.Native().resize_no_construct(*convertedPathSize);
  604. return true;
  605. }
  606. return false;
  607. }
  608. bool LocalFileIO::ResolveAliases(const char* path, char* resolvedPath, AZ::u64 resolvedPathSize) const
  609. {
  610. AZ_Assert(path && path[0] != '%', "%% is deprecated, @ is the only valid alias token");
  611. AZStd::string_view pathView(path);
  612. const auto found = AZStd::find_if(m_aliases.begin(), m_aliases.end(),
  613. [pathView](const auto& alias)
  614. {
  615. return pathView.starts_with(alias.first);
  616. });
  617. using string_view_pair = AZStd::pair<AZStd::string_view, AZStd::string_view>;
  618. auto [aliasKey, aliasValue] = (found != m_aliases.end()) ? string_view_pair(*found)
  619. : string_view_pair{};
  620. size_t requiredResolvedPathSize = pathView.size() - aliasKey.size() + aliasValue.size() + 1;
  621. AZ_Assert(path != resolvedPath, "ResolveAliases does not support inplace update of the path");
  622. AZ_Assert(resolvedPathSize >= requiredResolvedPathSize, "Resolved path size %llu not large enough. It needs to be %zu",
  623. resolvedPathSize, requiredResolvedPathSize);
  624. // we assert above, but we also need to properly handle the case when the resolvedPath buffer size
  625. // is too small to copy the source into.
  626. if (path == resolvedPath || (resolvedPathSize < requiredResolvedPathSize))
  627. {
  628. return false;
  629. }
  630. // Skip past the alias key in the pathView
  631. // must ensure that we are replacing the entire folder name, not a partial (e.g. @GAME01@/ vs @GAME0@/)
  632. if (AZStd::string_view postAliasView = pathView.substr(aliasKey.size());
  633. !aliasKey.empty() && (postAliasView.empty() || postAliasView.starts_with('/') || postAliasView.starts_with('\\')))
  634. {
  635. // Copy over resolved alias path first
  636. size_t resolvedPathLen = 0;
  637. aliasValue.copy(resolvedPath, aliasValue.size());
  638. resolvedPathLen += aliasValue.size();
  639. // Append the post alias path next
  640. postAliasView.copy(resolvedPath + resolvedPathLen, postAliasView.size());
  641. resolvedPathLen += postAliasView.size();
  642. // Null-Terminated the resolved path
  643. resolvedPath[resolvedPathLen] = '\0';
  644. // If the path started with one of the "asset cache" path aliases, lowercase the path
  645. const char* projectPlatformCacheAliasPath = GetAlias("@products@");
  646. const bool lowercasePath = projectPlatformCacheAliasPath != nullptr && AZ::StringFunc::StartsWith(resolvedPath, projectPlatformCacheAliasPath);
  647. if (lowercasePath)
  648. {
  649. // Lowercase only the relative part after the replaced alias.
  650. AZStd::to_lower(resolvedPath + aliasValue.size(), resolvedPath + resolvedPathLen);
  651. }
  652. // Replace any backslashes with posix slashes
  653. AZStd::replace(resolvedPath, resolvedPath + resolvedPathLen, AZ::IO::WindowsPathSeparator, AZ::IO::PosixPathSeparator);
  654. return true;
  655. }
  656. else
  657. {
  658. // The input path doesn't start with an available, copy it directly to the resolved path
  659. pathView.copy(resolvedPath, pathView.size());
  660. // Null-Terminated the resolved path
  661. resolvedPath[pathView.size()] = '\0';
  662. }
  663. // warn on failing to resolve an alias
  664. AZ_Warning("LocalFileIO::ResolveAlias", path && path[0] != '@', "Failed to resolve an alias: %s", path ? path : "(null)");
  665. return false;
  666. }
  667. bool LocalFileIO::ReplaceAlias(AZ::IO::FixedMaxPath& replacedAliasPath, const AZ::IO::PathView& path) const
  668. {
  669. if (path.empty())
  670. {
  671. replacedAliasPath = path;
  672. return true;
  673. }
  674. AZStd::string_view pathStrView = path.Native();
  675. for (const auto& [aliasKey, aliasValue] : m_aliases)
  676. {
  677. if (AZ::StringFunc::StartsWith(pathStrView, aliasKey))
  678. {
  679. // Add to the size of result path by the resolved alias length - the alias key length
  680. AZStd::string_view postAliasView = pathStrView.substr(aliasKey.size());
  681. size_t requiredFixedMaxPathSize = postAliasView.size();
  682. requiredFixedMaxPathSize += aliasValue.size();
  683. // The replaced alias path is greater than 1024 characters, return false
  684. if (requiredFixedMaxPathSize > replacedAliasPath.Native().max_size())
  685. {
  686. return false;
  687. }
  688. replacedAliasPath.Native() = AZ::IO::FixedMaxPathString(AZStd::string_view{ aliasValue });
  689. replacedAliasPath.Native() += postAliasView;
  690. return true;
  691. }
  692. }
  693. if (pathStrView.size() > replacedAliasPath.Native().max_size())
  694. {
  695. return false;
  696. }
  697. replacedAliasPath = path;
  698. return true;
  699. }
  700. bool LocalFileIO::GetFilename(HandleType fileHandle, char* filename, AZ::u64 filenameSize) const
  701. {
  702. AZStd::lock_guard<AZStd::recursive_mutex> lock(m_openFileGuard);
  703. auto fileIt = m_openFiles.find(fileHandle);
  704. if (fileIt != m_openFiles.end())
  705. {
  706. azstrncpy(filename, filenameSize, fileIt->second.Name(), filenameSize);
  707. return true;
  708. }
  709. return false;
  710. }
  711. bool LocalFileIO::LowerIfBeginsWith(char* inOutBuffer, AZ::u64 bufferLen, const char* alias) const
  712. {
  713. if (alias)
  714. {
  715. AZ::u64 aliasLen = azlossy_caster(strlen(alias));
  716. if (azstrnicmp(inOutBuffer, alias, aliasLen) == 0)
  717. {
  718. for (AZ::u64 i = aliasLen; i < bufferLen && inOutBuffer[i] != '\0'; ++i)
  719. {
  720. inOutBuffer[i] = static_cast<char>(std::tolower(static_cast<int>(inOutBuffer[i])));
  721. }
  722. return true;
  723. }
  724. }
  725. return false;
  726. }
  727. AZStd::string LocalFileIO::RemoveTrailingSlash(const AZStd::string& pathStr)
  728. {
  729. if (pathStr.empty() || (pathStr[pathStr.length() - 1] != '/' && pathStr[pathStr.length() - 1] != '\\'))
  730. {
  731. return pathStr;
  732. }
  733. return pathStr.substr(0, pathStr.length() - 1);
  734. }
  735. AZStd::string LocalFileIO::CheckForTrailingSlash(const AZStd::string& pathStr)
  736. {
  737. if (pathStr.empty() || pathStr[pathStr.length() - 1] == '/')
  738. {
  739. return pathStr;
  740. }
  741. if (pathStr[pathStr.length() - 1] == '\\')
  742. {
  743. return pathStr.substr(0, pathStr.length() - 1) + "/";
  744. }
  745. return pathStr + "/";
  746. }
  747. bool LocalFileIO::ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) const
  748. {
  749. return AZ::Utils::ConvertToAbsolutePath(path, absolutePath, maxLength);
  750. }
  751. } // namespace IO
  752. } // namespace AZ