diff --git a/src/poly/string.cc b/src/poly/string.cc index e3100f872..322f12dfb 100644 --- a/src/poly/string.cc +++ b/src/poly/string.cc @@ -104,4 +104,26 @@ std::wstring fix_path_separators(const std::wstring& source, wchar_t new_sep) { return dest; } +std::string fix_path_separators(const std::string& source, char new_sep) { + // Swap all separators to new_sep. + char old_sep = new_sep == '\\' ? '/' : '\\'; + std::string::size_type pos = 0; + std::string dest = source; + while ((pos = source.find_first_of(old_sep, pos)) != std::string::npos) { + dest[pos] = new_sep; + ++pos; + } + // Replace redundant separators. + pos = 0; + while ((pos = dest.find_first_of(new_sep, pos)) != std::string::npos) { + if (pos < dest.size() - 1) { + if (dest[pos + 1] == new_sep) { + dest.erase(pos + 1, 1); + } + } + ++pos; + } + return dest; +} + } // namespace poly diff --git a/src/poly/string.h b/src/poly/string.h index 8db5f9297..789710bd0 100644 --- a/src/poly/string.h +++ b/src/poly/string.h @@ -45,6 +45,8 @@ std::wstring join_paths(const std::wstring& left, const std::wstring& right, // separators. std::wstring fix_path_separators(const std::wstring& source, wchar_t new_sep = poly::path_separator); +std::string fix_path_separators(const std::string& source, + char new_sep = poly::path_separator); } // namespace poly diff --git a/src/xenia/kernel/fs/filesystem.cc b/src/xenia/kernel/fs/filesystem.cc index fa97b9478..622c05a89 100644 --- a/src/xenia/kernel/fs/filesystem.cc +++ b/src/xenia/kernel/fs/filesystem.cc @@ -146,18 +146,78 @@ int FileSystem::DeleteSymbolicLink(const std::string& path) { return 0; } -std::unique_ptr FileSystem::ResolvePath(const std::string& path) { - // Strip off prefix and pass to device. - // e.g., d:\some\PATH.foo -> some\PATH.foo - // Support both symlinks and device specifiers, like: - // \\Device\Foo\some\PATH.foo, d:\some\PATH.foo, etc. +std::string FileSystem::CanonicalizePath(const std::string& original_path) const { + char path_seperator('\\'); + std::string path(poly::fix_path_separators(original_path, path_seperator)); - // TODO(benvanik): normalize path/etc - // e.g., remove ..'s and such + std::vector hints; + + std::string::size_type pos(std::string::npos); + while ((pos = path.find_first_of(path_seperator, pos + 1)) != -1) { + hints.push_back(pos); + } + + if (!hints.empty()) { + { + const std::string::size_type len(path.size()); + // Treat last char as a hint for our range indexing + if (hints[hints.size() - 1] != len - 1) { + hints.push_back(len); + } + } + + auto it1 = hints.rbegin(); + auto it2 = it1; + ++it1; + while (it1 != hints.rend()) { + auto diff(*it2 - *it1); + switch (diff) { + case 2: + // Reference to current dir + if (path[*it1 + 1] == '.') { + path.erase(*it1, 2); + } + ++it1; ++it2; + break; + case 3: + // Reference to parent dir + if (path[*it1 + 1] == '.' && path[*it1 + 2] == '.') { + auto it3 = it1 + 1; + + if (it3 != hints.rend()) { + diff = (*it1 - *it3); + path.erase(*it3, diff + 3); + ++it2; + } else { + path.erase(*it1); + } + + it1 = it3; + } else { + ++it1; ++it2; + } + break; + default: + ++it1; ++it2; + } + } + } + + // Sanity checks + if ((path.size() == 1 && path[0] == '.') + || (path.size() == 2 && path[0] == '.' && path[1] == '.')) { + return ""; + } + + return path; +} + +std::unique_ptr FileSystem::ResolvePath(const std::string& path) { + // Resolve relative paths + std::string normalized_path(CanonicalizePath(path)); // If no path (starts with a slash) do it module-relative. // Which for now, we just make game:. - std::string normalized_path = path; if (path[0] == '\\') { normalized_path = "game:" + normalized_path; } diff --git a/src/xenia/kernel/fs/filesystem.h b/src/xenia/kernel/fs/filesystem.h index 690ea4284..6716c3273 100644 --- a/src/xenia/kernel/fs/filesystem.h +++ b/src/xenia/kernel/fs/filesystem.h @@ -56,6 +56,8 @@ class FileSystem { private: std::vector devices_; std::unordered_map symlinks_; + + std::string CanonicalizePath(const std::string& original_path) const; }; } // namespace fs