fs_path.h 16 KB

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