fs_path.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #pragma once
  4. #include "common/alignment.h"
  5. #include "common/common_funcs.h"
  6. #include "core/file_sys/errors.h"
  7. #include "core/file_sys/fs_memory_management.h"
  8. #include "core/file_sys/fs_path_utility.h"
  9. #include "core/file_sys/fs_string_util.h"
  10. #include "core/hle/result.h"
  11. namespace FileSys {
  12. class DirectoryPathParser;
  13. class Path {
  14. YUZU_NON_COPYABLE(Path);
  15. YUZU_NON_MOVEABLE(Path);
  16. private:
  17. static constexpr const char* EmptyPath = "";
  18. static constexpr size_t WriteBufferAlignmentLength = 8;
  19. private:
  20. friend class DirectoryPathParser;
  21. public:
  22. class WriteBuffer {
  23. YUZU_NON_COPYABLE(WriteBuffer);
  24. private:
  25. char* m_buffer;
  26. size_t m_length_and_is_normalized;
  27. public:
  28. constexpr WriteBuffer() : m_buffer(nullptr), m_length_and_is_normalized(0) {}
  29. constexpr ~WriteBuffer() {
  30. if (m_buffer != nullptr) {
  31. Deallocate(m_buffer, this->GetLength());
  32. this->ResetBuffer();
  33. }
  34. }
  35. constexpr WriteBuffer(WriteBuffer&& rhs)
  36. : m_buffer(rhs.m_buffer), m_length_and_is_normalized(rhs.m_length_and_is_normalized) {
  37. rhs.ResetBuffer();
  38. }
  39. constexpr WriteBuffer& operator=(WriteBuffer&& rhs) {
  40. if (m_buffer != nullptr) {
  41. Deallocate(m_buffer, this->GetLength());
  42. }
  43. m_buffer = rhs.m_buffer;
  44. m_length_and_is_normalized = rhs.m_length_and_is_normalized;
  45. rhs.ResetBuffer();
  46. return *this;
  47. }
  48. constexpr void ResetBuffer() {
  49. m_buffer = nullptr;
  50. this->SetLength(0);
  51. }
  52. constexpr char* Get() const {
  53. return m_buffer;
  54. }
  55. constexpr size_t GetLength() const {
  56. return m_length_and_is_normalized >> 1;
  57. }
  58. constexpr bool IsNormalized() const {
  59. return static_cast<bool>(m_length_and_is_normalized & 1);
  60. }
  61. constexpr void SetNormalized() {
  62. m_length_and_is_normalized |= static_cast<size_t>(1);
  63. }
  64. constexpr void SetNotNormalized() {
  65. m_length_and_is_normalized &= ~static_cast<size_t>(1);
  66. }
  67. private:
  68. constexpr WriteBuffer(char* buffer, size_t length)
  69. : m_buffer(buffer), m_length_and_is_normalized(0) {
  70. this->SetLength(length);
  71. }
  72. public:
  73. static WriteBuffer Make(size_t length) {
  74. if (void* alloc = Allocate(length); alloc != nullptr) {
  75. return WriteBuffer(static_cast<char*>(alloc), length);
  76. } else {
  77. return WriteBuffer();
  78. }
  79. }
  80. private:
  81. constexpr void SetLength(size_t size) {
  82. m_length_and_is_normalized = (m_length_and_is_normalized & 1) | (size << 1);
  83. }
  84. };
  85. private:
  86. const char* m_str;
  87. WriteBuffer m_write_buffer;
  88. public:
  89. constexpr Path() : m_str(EmptyPath), m_write_buffer() {}
  90. constexpr Path(const char* s) : m_str(s), m_write_buffer() {
  91. m_write_buffer.SetNormalized();
  92. }
  93. constexpr ~Path() = default;
  94. constexpr Result SetShallowBuffer(const char* buffer) {
  95. // Check pre-conditions
  96. ASSERT(m_write_buffer.GetLength() == 0);
  97. // Check the buffer is valid
  98. R_UNLESS(buffer != nullptr, ResultNullptrArgument);
  99. // Set buffer
  100. this->SetReadOnlyBuffer(buffer);
  101. // Note that we're normalized
  102. this->SetNormalized();
  103. R_SUCCEED();
  104. }
  105. constexpr const char* GetString() const {
  106. // Check pre-conditions
  107. ASSERT(this->IsNormalized());
  108. return m_str;
  109. }
  110. constexpr size_t GetLength() const {
  111. if (std::is_constant_evaluated()) {
  112. return Strlen(this->GetString());
  113. } else {
  114. return std::strlen(this->GetString());
  115. }
  116. }
  117. constexpr bool IsEmpty() const {
  118. return *m_str == '\x00';
  119. }
  120. constexpr bool IsMatchHead(const char* p, size_t len) const {
  121. return Strncmp(this->GetString(), p, len) == 0;
  122. }
  123. Result Initialize(const Path& rhs) {
  124. // Check the other path is normalized
  125. const bool normalized = rhs.IsNormalized();
  126. R_UNLESS(normalized, ResultNotNormalized);
  127. // Allocate buffer for our path
  128. const auto len = rhs.GetLength();
  129. R_TRY(this->Preallocate(len + 1));
  130. // Copy the path
  131. const size_t copied = Strlcpy<char>(m_write_buffer.Get(), rhs.GetString(), len + 1);
  132. R_UNLESS(copied == len, ResultUnexpectedInPathA);
  133. // Set normalized
  134. this->SetNormalized();
  135. R_SUCCEED();
  136. }
  137. Result Initialize(const char* path, size_t len) {
  138. // Check the path is valid
  139. R_UNLESS(path != nullptr, ResultNullptrArgument);
  140. // Initialize
  141. R_TRY(this->InitializeImpl(path, len));
  142. // Set not normalized
  143. this->SetNotNormalized();
  144. R_SUCCEED();
  145. }
  146. Result Initialize(const char* path) {
  147. // Check the path is valid
  148. R_UNLESS(path != nullptr, ResultNullptrArgument);
  149. R_RETURN(this->Initialize(path, std::strlen(path)));
  150. }
  151. Result InitializeWithReplaceBackslash(const char* path) {
  152. // Check the path is valid
  153. R_UNLESS(path != nullptr, ResultNullptrArgument);
  154. // Initialize
  155. R_TRY(this->InitializeImpl(path, std::strlen(path)));
  156. // Replace slashes as desired
  157. if (const auto write_buffer_length = m_write_buffer.GetLength(); write_buffer_length > 1) {
  158. Replace(m_write_buffer.Get(), write_buffer_length - 1, '\\', '/');
  159. }
  160. // Set not normalized
  161. this->SetNotNormalized();
  162. R_SUCCEED();
  163. }
  164. Result InitializeWithReplaceForwardSlashes(const char* path) {
  165. // Check the path is valid
  166. R_UNLESS(path != nullptr, ResultNullptrArgument);
  167. // Initialize
  168. R_TRY(this->InitializeImpl(path, std::strlen(path)));
  169. // Replace slashes as desired
  170. if (m_write_buffer.GetLength() > 1) {
  171. if (auto* p = m_write_buffer.Get(); p[0] == '/' && p[1] == '/') {
  172. p[0] = '\\';
  173. p[1] = '\\';
  174. }
  175. }
  176. // Set not normalized
  177. this->SetNotNormalized();
  178. R_SUCCEED();
  179. }
  180. Result InitializeWithNormalization(const char* path, size_t size) {
  181. // Check the path is valid
  182. R_UNLESS(path != nullptr, ResultNullptrArgument);
  183. // Initialize
  184. R_TRY(this->InitializeImpl(path, size));
  185. // Set not normalized
  186. this->SetNotNormalized();
  187. // Perform normalization
  188. PathFlags path_flags;
  189. if (IsPathRelative(m_str)) {
  190. path_flags.AllowRelativePath();
  191. } else if (IsWindowsPath(m_str, true)) {
  192. path_flags.AllowWindowsPath();
  193. } else {
  194. /* NOTE: In this case, Nintendo checks is normalized, then sets is normalized, then
  195. * returns success. */
  196. /* This seems like a bug. */
  197. size_t dummy;
  198. bool normalized;
  199. R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy),
  200. m_str));
  201. this->SetNormalized();
  202. R_SUCCEED();
  203. }
  204. // Normalize
  205. R_TRY(this->Normalize(path_flags));
  206. this->SetNormalized();
  207. R_SUCCEED();
  208. }
  209. Result InitializeWithNormalization(const char* path) {
  210. // Check the path is valid
  211. R_UNLESS(path != nullptr, ResultNullptrArgument);
  212. R_RETURN(this->InitializeWithNormalization(path, std::strlen(path)));
  213. }
  214. Result InitializeAsEmpty() {
  215. // Clear our buffer
  216. this->ClearBuffer();
  217. // Set normalized
  218. this->SetNormalized();
  219. R_SUCCEED();
  220. }
  221. Result AppendChild(const char* child) {
  222. // Check the path is valid
  223. R_UNLESS(child != nullptr, ResultNullptrArgument);
  224. // Basic checks. If we have a path and the child is empty, we have nothing to do
  225. const char* c = child;
  226. if (m_str[0]) {
  227. // Skip an early separator
  228. if (*c == '/') {
  229. ++c;
  230. }
  231. R_SUCCEED_IF(*c == '\x00');
  232. }
  233. // If we don't have a string, we can just initialize
  234. auto cur_len = std::strlen(m_str);
  235. if (cur_len == 0) {
  236. R_RETURN(this->Initialize(child));
  237. }
  238. // Remove a trailing separator
  239. if (m_str[cur_len - 1] == '/' || m_str[cur_len - 1] == '\\') {
  240. --cur_len;
  241. }
  242. // Get the child path's length
  243. auto child_len = std::strlen(c);
  244. // Reset our write buffer
  245. WriteBuffer old_write_buffer;
  246. if (m_write_buffer.Get() != nullptr) {
  247. old_write_buffer = std::move(m_write_buffer);
  248. this->ClearBuffer();
  249. }
  250. // Pre-allocate the new buffer
  251. R_TRY(this->Preallocate(cur_len + 1 + child_len + 1));
  252. // Get our write buffer
  253. auto* dst = m_write_buffer.Get();
  254. if (old_write_buffer.Get() != nullptr && cur_len > 0) {
  255. Strlcpy<char>(dst, old_write_buffer.Get(), cur_len + 1);
  256. }
  257. // Add separator
  258. dst[cur_len] = '/';
  259. // Copy the child path
  260. const size_t copied = Strlcpy<char>(dst + cur_len + 1, c, child_len + 1);
  261. R_UNLESS(copied == child_len, ResultUnexpectedInPathA);
  262. R_SUCCEED();
  263. }
  264. Result AppendChild(const Path& rhs) {
  265. R_RETURN(this->AppendChild(rhs.GetString()));
  266. }
  267. Result Combine(const Path& parent, const Path& child) {
  268. // Get the lengths
  269. const auto p_len = parent.GetLength();
  270. const auto c_len = child.GetLength();
  271. // Allocate our buffer
  272. R_TRY(this->Preallocate(p_len + c_len + 1));
  273. // Initialize as parent
  274. R_TRY(this->Initialize(parent));
  275. // If we're empty, we can just initialize as child
  276. if (this->IsEmpty()) {
  277. R_TRY(this->Initialize(child));
  278. } else {
  279. // Otherwise, we should append the child
  280. R_TRY(this->AppendChild(child));
  281. }
  282. R_SUCCEED();
  283. }
  284. Result RemoveChild() {
  285. // If we don't have a write-buffer, ensure that we have one
  286. if (m_write_buffer.Get() == nullptr) {
  287. if (const auto len = std::strlen(m_str); len > 0) {
  288. R_TRY(this->Preallocate(len));
  289. Strlcpy<char>(m_write_buffer.Get(), m_str, len + 1);
  290. }
  291. }
  292. // Check that it's possible for us to remove a child
  293. auto* p = m_write_buffer.Get();
  294. s32 len = static_cast<s32>(std::strlen(p));
  295. R_UNLESS(len != 1 || (p[0] != '/' && p[0] != '.'), ResultNotImplemented);
  296. // Handle a trailing separator
  297. if (len > 0 && (p[len - 1] == '\\' || p[len - 1] == '/')) {
  298. --len;
  299. }
  300. // Remove the child path segment
  301. while ((--len) >= 0 && p[len]) {
  302. if (p[len] == '/' || p[len] == '\\') {
  303. if (len > 0) {
  304. p[len] = 0;
  305. } else {
  306. p[1] = 0;
  307. len = 1;
  308. }
  309. break;
  310. }
  311. }
  312. // Check that length remains > 0
  313. R_UNLESS(len > 0, ResultNotImplemented);
  314. R_SUCCEED();
  315. }
  316. Result Normalize(const PathFlags& flags) {
  317. // If we're already normalized, nothing to do
  318. R_SUCCEED_IF(this->IsNormalized());
  319. // Check if we're normalized
  320. bool normalized;
  321. size_t dummy;
  322. R_TRY(PathFormatter::IsNormalized(std::addressof(normalized), std::addressof(dummy), m_str,
  323. flags));
  324. // If we're not normalized, normalize
  325. if (!normalized) {
  326. // Determine necessary buffer length
  327. auto len = m_write_buffer.GetLength();
  328. if (flags.IsRelativePathAllowed() && IsPathRelative(m_str)) {
  329. len += 2;
  330. }
  331. if (flags.IsWindowsPathAllowed() && IsWindowsPath(m_str, true)) {
  332. len += 1;
  333. }
  334. // Allocate a new buffer
  335. const size_t size = Common::AlignUp(len, WriteBufferAlignmentLength);
  336. auto buf = WriteBuffer::Make(size);
  337. R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
  338. // Normalize into it
  339. R_TRY(PathFormatter::Normalize(buf.Get(), size, m_write_buffer.Get(),
  340. m_write_buffer.GetLength(), flags));
  341. // Set the normalized buffer as our buffer
  342. this->SetModifiableBuffer(std::move(buf));
  343. }
  344. // Set normalized
  345. this->SetNormalized();
  346. R_SUCCEED();
  347. }
  348. private:
  349. void ClearBuffer() {
  350. m_write_buffer.ResetBuffer();
  351. m_str = EmptyPath;
  352. }
  353. void SetModifiableBuffer(WriteBuffer&& buffer) {
  354. // Check pre-conditions
  355. ASSERT(buffer.Get() != nullptr);
  356. ASSERT(buffer.GetLength() > 0);
  357. ASSERT(Common::IsAligned(buffer.GetLength(), WriteBufferAlignmentLength));
  358. // Get whether we're normalized
  359. if (m_write_buffer.IsNormalized()) {
  360. buffer.SetNormalized();
  361. } else {
  362. buffer.SetNotNormalized();
  363. }
  364. // Set write buffer
  365. m_write_buffer = std::move(buffer);
  366. m_str = m_write_buffer.Get();
  367. }
  368. constexpr void SetReadOnlyBuffer(const char* buffer) {
  369. m_str = buffer;
  370. m_write_buffer.ResetBuffer();
  371. }
  372. Result Preallocate(size_t length) {
  373. // Allocate additional space, if needed
  374. if (length > m_write_buffer.GetLength()) {
  375. // Allocate buffer
  376. const size_t size = Common::AlignUp(length, WriteBufferAlignmentLength);
  377. auto buf = WriteBuffer::Make(size);
  378. R_UNLESS(buf.Get() != nullptr, ResultAllocationMemoryFailedMakeUnique);
  379. // Set write buffer
  380. this->SetModifiableBuffer(std::move(buf));
  381. }
  382. R_SUCCEED();
  383. }
  384. Result InitializeImpl(const char* path, size_t size) {
  385. if (size > 0 && path[0]) {
  386. // Pre allocate a buffer for the path
  387. R_TRY(this->Preallocate(size + 1));
  388. // Copy the path
  389. const size_t copied = Strlcpy<char>(m_write_buffer.Get(), path, size + 1);
  390. R_UNLESS(copied >= size, ResultUnexpectedInPathA);
  391. } else {
  392. // We can just clear the buffer
  393. this->ClearBuffer();
  394. }
  395. R_SUCCEED();
  396. }
  397. constexpr char* GetWriteBuffer() {
  398. ASSERT(m_write_buffer.Get() != nullptr);
  399. return m_write_buffer.Get();
  400. }
  401. constexpr size_t GetWriteBufferLength() const {
  402. return m_write_buffer.GetLength();
  403. }
  404. constexpr bool IsNormalized() const {
  405. return m_write_buffer.IsNormalized();
  406. }
  407. constexpr void SetNormalized() {
  408. m_write_buffer.SetNormalized();
  409. }
  410. constexpr void SetNotNormalized() {
  411. m_write_buffer.SetNotNormalized();
  412. }
  413. public:
  414. bool operator==(const FileSys::Path& rhs) const {
  415. return std::strcmp(this->GetString(), rhs.GetString()) == 0;
  416. }
  417. bool operator!=(const FileSys::Path& rhs) const {
  418. return !(*this == rhs);
  419. }
  420. bool operator==(const char* p) const {
  421. return std::strcmp(this->GetString(), p) == 0;
  422. }
  423. bool operator!=(const char* p) const {
  424. return !(*this == p);
  425. }
  426. };
  427. inline Result SetUpFixedPath(FileSys::Path* out, const char* s) {
  428. // Verify the path is normalized
  429. bool normalized;
  430. size_t dummy;
  431. R_TRY(PathNormalizer::IsNormalized(std::addressof(normalized), std::addressof(dummy), s));
  432. R_UNLESS(normalized, ResultInvalidPathFormat);
  433. // Set the fixed path
  434. R_RETURN(out->SetShallowBuffer(s));
  435. }
  436. constexpr inline bool IsWindowsDriveRootPath(const FileSys::Path& path) {
  437. const char* const str = path.GetString();
  438. return IsWindowsDrive(str) &&
  439. (str[2] == StringTraits::DirectorySeparator ||
  440. str[2] == StringTraits::AlternateDirectorySeparator) &&
  441. str[3] == StringTraits::NullTerminator;
  442. }
  443. } // namespace FileSys